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