Merge remote branch 'ets/grc-usrp2-clock-source'
[debian/gnuradio] / grc / python / Param.py
1 """
2 Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 """
19
20 from .. base.Param import Param as _Param
21 from .. gui.Param import Param as _GUIParam
22 from .. gui.Param import EntryParam
23 import Constants
24 import numpy
25 import os
26 import pygtk
27 pygtk.require('2.0')
28 import gtk
29 from gnuradio import eng_notation
30 import re
31 from gnuradio import gr
32
33 _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
34 _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
35
36 class FileParam(EntryParam):
37         """Provide an entry box for filename and a button to browse for a file."""
38
39         def __init__(self, *args, **kwargs):
40                 EntryParam.__init__(self, *args, **kwargs)
41                 input = gtk.Button('...')
42                 input.connect('clicked', self._handle_clicked)
43                 self.pack_start(input, False)
44
45         def _handle_clicked(self, widget=None):
46                 """
47                 If the button was clicked, open a file dialog in open/save format.
48                 Replace the text in the entry with the new filename from the file dialog.
49                 """
50                 #get the paths
51                 file_path = self.param.is_valid() and self.param.get_evaluated() or ''
52                 (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
53                 if not os.path.exists(dirname): dirname = os.getcwd() #fix bad paths
54                 #build the dialog
55                 if self.param.get_type() == 'file_open':
56                         file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
57                                 gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
58                 elif self.param.get_type() == 'file_save':
59                         file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
60                                 gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
61                         file_dialog.set_do_overwrite_confirmation(True)
62                         file_dialog.set_current_name(basename) #show the current filename
63                 file_dialog.set_current_folder(dirname) #current directory
64                 file_dialog.set_select_multiple(False)
65                 file_dialog.set_local_only(True)
66                 if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
67                         file_path = file_dialog.get_filename() #get the file path
68                         self._input.set_text(file_path)
69                         self._handle_changed()
70                 file_dialog.destroy() #destroy the dialog
71
72 #blacklist certain ids, its not complete, but should help
73 import __builtin__
74 ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + \
75         filter(lambda x: not x.startswith('_'), dir(gr.top_block())) + dir(__builtin__)
76 #define types, native python + numpy
77 VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
78 COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
79 REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
80 INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
81         numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
82 #cast to tuple for isinstance, concat subtypes
83 COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
84 REAL_TYPES = tuple(REAL_TYPES + INT_TYPES)
85 INT_TYPES = tuple(INT_TYPES)
86
87 class Param(_Param, _GUIParam):
88
89         def __init__(self, **kwargs):
90                 _Param.__init__(self, **kwargs)
91                 _GUIParam.__init__(self)
92                 self._init = False
93                 self._hostage_cells = list()
94
95         def get_types(self): return (
96                 'raw', 'enum',
97                 'complex', 'real', 'int',
98                 'complex_vector', 'real_vector', 'int_vector',
99                 'hex', 'string', 'bool',
100                 'file_open', 'file_save',
101                 'id', 'stream_id',
102                 'grid_pos', 'notebook',
103                 'import',
104         )
105
106         def __repr__(self):
107                 """
108                 Get the repr (nice string format) for this param.
109                 @return the string representation
110                 """
111                 ##################################################
112                 # truncate helper method
113                 ##################################################
114                 def _truncate(string, style=0):
115                         max_len = max(27 - len(self.get_name()), 3)
116                         if len(string) > max_len:
117                                 if style < 0: #front truncate
118                                         string = '...' + string[3-max_len:]
119                                 elif style == 0: #center truncate
120                                         string = string[:max_len/2 -3] + '...' + string[-max_len/2:]
121                                 elif style > 0: #rear truncate
122                                         string = string[:max_len-3] + '...'
123                         return string
124                 ##################################################
125                 # simple conditions
126                 ##################################################
127                 if not self.is_valid(): return _truncate(self.get_value())
128                 if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
129                 ##################################################
130                 # display logic for numbers
131                 ##################################################
132                 def num_to_str(num):
133                         if isinstance(num, COMPLEX_TYPES):
134                                 num = complex(num) #cast to python complex
135                                 if num == 0: return '0' #value is zero
136                                 elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
137                                 elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
138                                 elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
139                                 else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
140                         else: return str(num)
141                 ##################################################
142                 # split up formatting by type
143                 ##################################################
144                 truncate = 0 #default center truncate
145                 e = self.get_evaluated()
146                 t = self.get_type()
147                 if isinstance(e, bool): return str(e)
148                 elif isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
149                 elif isinstance(e, VECTOR_TYPES): #vector types
150                         if len(e) > 8:
151                                 dt_str = self.get_value() #large vectors use code
152                                 truncate = 1
153                         else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
154                 elif t in ('file_open', 'file_save'):
155                         dt_str = self.get_value()
156                         truncate = -1
157                 else: dt_str = str(e) #other types
158                 ##################################################
159                 # done
160                 ##################################################
161                 return _truncate(dt_str, truncate)
162
163         def get_input(self, *args, **kwargs):
164                 if self.get_type() in ('file_open', 'file_save'): return FileParam(self, *args, **kwargs)
165                 return _GUIParam.get_input(self, *args, **kwargs)
166
167         def get_color(self):
168                 """
169                 Get the color that represents this param's type.
170                 @return a hex color code.
171                 """
172                 try:
173                         return {
174                                 #number types
175                                 'complex': Constants.COMPLEX_COLOR_SPEC,
176                                 'real': Constants.FLOAT_COLOR_SPEC,
177                                 'int': Constants.INT_COLOR_SPEC,
178                                 #vector types
179                                 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
180                                 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
181                                 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
182                                 #special
183                                 'bool': Constants.INT_COLOR_SPEC,
184                                 'hex': Constants.INT_COLOR_SPEC,
185                                 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
186                                 'id': Constants.ID_COLOR_SPEC,
187                                 'stream_id': Constants.ID_COLOR_SPEC,
188                                 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
189                                 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
190                                 'raw': Constants.WILDCARD_COLOR_SPEC,
191                         }[self.get_type()]
192                 except: return _Param.get_color(self)
193
194         def get_hide(self):
195                 """
196                 Get the hide value from the base class.
197                 Hide the ID parameter for most blocks. Exceptions below.
198                 If the parameter controls a port type, vlen, or nports, return part.
199                 If the parameter is an empty grid position, return part.
200                 These parameters are redundant to display in the flow graph view.
201                 @return hide the hide property string
202                 """
203                 hide = _Param.get_hide(self)
204                 if hide: return hide
205                 #hide ID in non variable blocks
206                 if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): return 'part'
207                 #hide port controllers for type and nports
208                 if self.get_key() in ' '.join(map(
209                         lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
210                 ): return 'part'
211                 #hide port controllers for vlen, when == 1
212                 if self.get_key() in ' '.join(map(
213                         lambda p: p._vlen, self.get_parent().get_ports())
214                 ):
215                         try:
216                                 assert int(self.get_evaluated()) == 1
217                                 return 'part'
218                         except: pass
219                 #hide empty grid positions
220                 if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
221                 return hide
222
223         def validate(self):
224                 """
225                 Validate the param.
226                 A test evaluation is performed
227                 """
228                 _Param.validate(self) #checks type
229                 self._evaluated = None
230                 try: self._evaluated = self.evaluate()
231                 except Exception, e: self.add_error_message(str(e))
232
233         def get_evaluated(self): return self._evaluated
234
235         def evaluate(self):
236                 """
237                 Evaluate the value.
238                 @return evaluated type
239                 """
240                 self._init = True
241                 self._lisitify_flag = False
242                 self._stringify_flag = False
243                 self._hostage_cells = list()
244                 def eval_string(v):
245                         try:
246                                 e = self.get_parent().get_parent().evaluate(v)
247                                 assert isinstance(e, str)
248                                 return e
249                         except:
250                                 self._stringify_flag = True
251                                 return v
252                 t = self.get_type()
253                 v = self.get_value()
254                 #########################
255                 # Enum Type
256                 #########################
257                 if self.is_enum(): return v
258                 #########################
259                 # Numeric Types
260                 #########################
261                 elif t in ('raw', 'complex', 'real', 'int', 'hex', 'bool'):
262                         #raise exception if python cannot evaluate this value
263                         try: e = self.get_parent().get_parent().evaluate(v)
264                         except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
265                         #raise an exception if the data is invalid
266                         if t == 'raw': return e
267                         elif t == 'complex':
268                                 try: assert isinstance(e, COMPLEX_TYPES)
269                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
270                                 return e
271                         elif t == 'real':
272                                 try: assert isinstance(e, REAL_TYPES)
273                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
274                                 return e
275                         elif t == 'int':
276                                 try: assert isinstance(e, INT_TYPES)
277                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
278                                 return e
279                         elif t == 'hex': return hex(e)
280                         elif t == 'bool':
281                                 try: assert isinstance(e, bool)
282                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
283                                 return e
284                         else: raise TypeError, 'Type "%s" not handled'%t
285                 #########################
286                 # Numeric Vector Types
287                 #########################
288                 elif t in ('complex_vector', 'real_vector', 'int_vector'):
289                         if not v: v = '()' #turn a blank string into an empty list, so it will eval
290                         #raise exception if python cannot evaluate this value
291                         try: e = self.get_parent().get_parent().evaluate(v)
292                         except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
293                         #raise an exception if the data is invalid
294                         if t == 'complex_vector':
295                                 if not isinstance(e, VECTOR_TYPES):
296                                         self._lisitify_flag = True
297                                         e = [e]
298                                 try:
299                                         for ei in e: assert isinstance(ei, COMPLEX_TYPES)
300                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
301                                 return e
302                         elif t == 'real_vector':
303                                 if not isinstance(e, VECTOR_TYPES):
304                                         self._lisitify_flag = True
305                                         e = [e]
306                                 try:
307                                         for ei in e: assert isinstance(ei, REAL_TYPES)
308                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
309                                 return e
310                         elif t == 'int_vector':
311                                 if not isinstance(e, VECTOR_TYPES):
312                                         self._lisitify_flag = True
313                                         e = [e]
314                                 try:
315                                         for ei in e: assert isinstance(ei, INT_TYPES)
316                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
317                                 return e
318                 #########################
319                 # String Types
320                 #########################
321                 elif t in ('string', 'file_open', 'file_save'):
322                         #do not check if file/directory exists, that is a runtime issue
323                         e = eval_string(v)
324                         return str(e)
325                 #########################
326                 # Unique ID Type
327                 #########################
328                 elif t == 'id':
329                         #can python use this as a variable?
330                         try: assert _check_id_matcher.match(v)
331                         except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
332                         ids = [param.get_value() for param in self.get_all_params(t)]
333                         try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
334                         except: raise Exception, 'ID "%s" is not unique.'%v
335                         try: assert v not in ID_BLACKLIST
336                         except: raise Exception, 'ID "%s" is blacklisted.'%v
337                         return v
338                 #########################
339                 # Stream ID Type
340                 #########################
341                 elif t == 'stream_id':
342                         #get a list of all stream ids used in the virtual sinks 
343                         ids = [param.get_value() for param in filter(
344                                 lambda p: p.get_parent().is_virtual_sink(),
345                                 self.get_all_params(t),
346                         )]
347                         #check that the virtual sink's stream id is unique
348                         if self.get_parent().is_virtual_sink():
349                                 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
350                                 except: raise Exception, 'Stream ID "%s" is not unique.'%v
351                         #check that the virtual source's steam id is found
352                         if self.get_parent().is_virtual_source():
353                                 try: assert v in ids
354                                 except: raise Exception, 'Stream ID "%s" is not found.'%v
355                         return v
356                 #########################
357                 # Grid Position Type
358                 #########################
359                 elif t == 'grid_pos':
360                         if not v: return '' #allow for empty grid pos
361                         e = self.get_parent().get_parent().evaluate(v)
362                         try:
363                                 assert isinstance(e, (list, tuple)) and len(e) == 4
364                                 for ei in e: assert isinstance(ei, int)
365                         except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
366                         row, col, row_span, col_span = e
367                         #check row, col
368                         try: assert row >= 0 and col >= 0
369                         except AssertionError: raise Exception, 'Row and column must be non-negative.'
370                         #check row span, col span
371                         try: assert row_span > 0 and col_span > 0
372                         except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
373                         #get hostage cell parent
374                         try: my_parent = self.get_parent().get_param('notebook').evaluate()
375                         except: my_parent = ''
376                         #calculate hostage cells
377                         for r in range(row_span):
378                                 for c in range(col_span):
379                                         self._hostage_cells.append((my_parent, (row+r, col+c)))
380                         #avoid collisions
381                         params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
382                         for param in params:
383                                 for parent, cell in param._hostage_cells:
384                                         if (parent, cell) in self._hostage_cells:
385                                                 raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
386                         return e
387                 #########################
388                 # Notebook Page Type
389                 #########################
390                 elif t == 'notebook':
391                         if not v: return '' #allow for empty notebook
392                         #get a list of all notebooks
393                         notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
394                         #check for notebook param syntax
395                         try: notebook_id, page_index = map(str.strip, v.split(','))
396                         except: raise Exception, 'Bad notebook page format.'
397                         #check that the notebook id is valid
398                         try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
399                         except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
400                         #check that page index exists
401                         try: assert int(page_index) in range(len(notebook_block.get_param('labels').evaluate()))
402                         except: raise Exception, 'Page index "%s" is not a valid index number.'%page_index
403                         return notebook_id, page_index
404                 #########################
405                 # Import Type
406                 #########################
407                 elif t == 'import':
408                         n = dict() #new namespace
409                         try: exec v in n
410                         except ImportError: raise Exception, 'Import "%s" failed.'%v
411                         except Exception: raise Exception, 'Bad import syntax: "%s".'%v
412                         return filter(lambda k: str(k) != '__builtins__', n.keys())
413                 #########################
414                 else: raise TypeError, 'Type "%s" not handled'%t
415
416         def to_code(self):
417                 """
418                 Convert the value to code.
419                 For string and list types, check the init flag, call evaluate().
420                 This ensures that evaluate() was called to set the xxxify_flags.
421                 @return a string representing the code
422                 """
423                 v = self.get_value()
424                 t = self.get_type()
425                 if t in ('string', 'file_open', 'file_save'): #string types
426                         if not self._init: self.evaluate()
427                         if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
428                         else: return v
429                 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
430                         if not self._init: self.evaluate()
431                         if self._lisitify_flag: return '(%s, )'%v
432                         else: return '(%s)'%v
433                 else: return v
434
435         def get_all_params(self, type):
436                 """
437                 Get all the params from the flowgraph that have the given type.
438                 @param type the specified type
439                 @return a list of params
440                 """
441                 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])