Switched the python classes to inherit from the base and gui classes.
[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_class(self):
157                 if self.get_type() in ('file_open', 'file_save'): return FileParam
158                 return _GUIParam.get_input_class(self)
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', 'complex_vector', 'real_vector', 'int_vector', '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: %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                         #########################
273                         # Numeric Vector Types
274                         #########################
275                         elif t == 'complex_vector':
276                                 if not isinstance(e, VECTOR_TYPES):
277                                         self._lisitify_flag = True
278                                         e = [e]
279                                 try:
280                                         for ei in e: assert isinstance(ei, COMPLEX_TYPES)
281                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
282                                 return e
283                         elif t == 'real_vector':
284                                 if not isinstance(e, VECTOR_TYPES):
285                                         self._lisitify_flag = True
286                                         e = [e]
287                                 try:
288                                         for ei in e: assert isinstance(ei, REAL_TYPES)
289                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
290                                 return e
291                         elif t == 'int_vector':
292                                 if not isinstance(e, VECTOR_TYPES):
293                                         self._lisitify_flag = True
294                                         e = [e]
295                                 try:
296                                         for ei in e: assert isinstance(ei, INT_TYPES)
297                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
298                                 return e
299                         elif t == 'hex': return hex(e)
300                         elif t == 'bool':
301                                 try: assert isinstance(e, bool)
302                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
303                                 return e
304                         else: raise TypeError, 'Type "%s" not handled'%t
305                 #########################
306                 # String Types
307                 #########################
308                 elif t in ('string', 'file_open', 'file_save'):
309                         #do not check if file/directory exists, that is a runtime issue
310                         e = eval_string(v)
311                         return str(e)
312                 #########################
313                 # Unique ID Type
314                 #########################
315                 elif t == 'id':
316                         #can python use this as a variable?
317                         try: assert _check_id_matcher.match(v)
318                         except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
319                         ids = [param.get_value() for param in self.get_all_params(t)]
320                         try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
321                         except: raise Exception, 'ID "%s" is not unique.'%v
322                         try: assert v not in ID_BLACKLIST
323                         except: raise Exception, 'ID "%s" is blacklisted.'%v
324                         return v
325                 #########################
326                 # Stream ID Type
327                 #########################
328                 elif t == 'stream_id':
329                         #get a list of all stream ids used in the virtual sinks 
330                         ids = [param.get_value() for param in filter(
331                                 lambda p: p.get_parent().is_virtual_sink(),
332                                 self.get_all_params(t),
333                         )]
334                         #check that the virtual sink's stream id is unique
335                         if self.get_parent().is_virtual_sink():
336                                 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
337                                 except: raise Exception, 'Stream ID "%s" is not unique.'%v
338                         #check that the virtual source's steam id is found
339                         if self.get_parent().is_virtual_source():
340                                 try: assert v in ids
341                                 except: raise Exception, 'Stream ID "%s" is not found.'%v
342                         return v
343                 #########################
344                 # Grid Position Type
345                 #########################
346                 elif t == 'grid_pos':
347                         if not v: return '' #allow for empty grid pos
348                         e = self.get_parent().get_parent().evaluate(v)
349                         try:
350                                 assert isinstance(e, (list, tuple)) and len(e) == 4
351                                 for ei in e: assert isinstance(ei, int)
352                         except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
353                         row, col, row_span, col_span = e
354                         #check row, col
355                         try: assert row >= 0 and col >= 0
356                         except AssertionError: raise Exception, 'Row and column must be non-negative.'
357                         #check row span, col span
358                         try: assert row_span > 0 and col_span > 0
359                         except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
360                         #get hostage cell parent
361                         try: my_parent = self.get_parent().get_param('notebook').evaluate()
362                         except: my_parent = ''
363                         #calculate hostage cells
364                         for r in range(row_span):
365                                 for c in range(col_span):
366                                         self._hostage_cells.append((my_parent, (row+r, col+c)))
367                         #avoid collisions
368                         params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
369                         for param in params:
370                                 for parent, cell in param._hostage_cells:
371                                         if (parent, cell) in self._hostage_cells:
372                                                 raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
373                         return e
374                 #########################
375                 # Notebook Page Type
376                 #########################
377                 elif t == 'notebook':
378                         if not v: return '' #allow for empty notebook
379                         #get a list of all notebooks
380                         notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
381                         #check for notebook param syntax
382                         try: notebook_id, page_index = map(str.strip, v.split(','))
383                         except: raise Exception, 'Bad notebook page format.'
384                         #check that the notebook id is valid
385                         try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
386                         except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
387                         #check that page index exists
388                         try: assert int(page_index) in range(len(notebook_block.get_param('labels').get_evaluated()))
389                         except: raise Exception, 'Page index "%s" is not a valid index number.'%page_index
390                         return notebook_id, page_index
391                 #########################
392                 # Import Type
393                 #########################
394                 elif t == 'import':
395                         n = dict() #new namespace
396                         try: exec v in n
397                         except ImportError: raise Exception, 'Import "%s" failed.'%v
398                         except Exception: raise Exception, 'Bad import syntax: "%s".'%v
399                         return filter(lambda k: str(k) != '__builtins__', n.keys())
400                 #########################
401                 else: raise TypeError, 'Type "%s" not handled'%t
402
403         def to_code(self):
404                 """
405                 Convert the value to code.
406                 @return a string representing the code
407                 """
408                 #run init tasks in evaluate
409                 #such as setting flags
410                 if not self._init: self.evaluate()
411                 v = self.get_value()
412                 t = self.get_type()
413                 if t in ('string', 'file_open', 'file_save'): #string types
414                         if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
415                         else: return v
416                 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
417                         if self._lisitify_flag: return '(%s, )'%v
418                         else: return '(%s)'%v
419                 else: return v
420
421         def get_all_params(self, type):
422                 """
423                 Get all the params from the flowgraph that have the given type.
424                 @param type the specified type
425                 @return a list of params
426                 """
427                 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])