2 Copyright 2008, 2009 Free Software Foundation, Inc.
3 This file is part of GNU Radio
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.
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.
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
20 from .. base.Param import Param as _Param
21 from .. gui.Param import Param as _GUIParam
22 from .. gui.Param import EntryParam
29 from gnuradio import eng_notation
31 from gnuradio import gr
33 _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
34 _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
36 class FileParam(EntryParam):
37 """Provide an entry box for filename and a button to browse for a file."""
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)
45 def _handle_clicked(self, widget=None):
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.
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
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.entry.set_text(file_path)
69 self._handle_changed()
70 file_dialog.destroy() #destroy the dialog
72 #blacklist certain ids, its not complete, but should help
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)
87 class Param(_Param, _GUIParam):
89 def __init__(self, **kwargs):
90 _Param.__init__(self, **kwargs)
91 _GUIParam.__init__(self)
93 self._hostage_cells = list()
95 def get_types(self): return (
97 'complex', 'real', 'int',
98 'complex_vector', 'real_vector', 'int_vector',
99 'hex', 'string', 'bool',
100 'file_open', 'file_save',
102 'grid_pos', 'notebook',
108 Get the repr (nice string format) for this param.
109 @return the string representation
111 if not self.is_valid(): return self.get_value()
112 if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
113 ##################################################
114 # display logic for numbers
115 ##################################################
117 if isinstance(num, COMPLEX_TYPES):
118 num = complex(num) #cast to python complex
119 if num == 0: return '0' #value is zero
120 elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
121 elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
122 elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
123 else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
124 else: return str(num)
125 ##################################################
126 # split up formatting by type
127 ##################################################
128 truncate = 0 #default center truncate
129 max_len = max(27 - len(self.get_name()), 3)
130 e = self.get_evaluated()
132 if isinstance(e, bool): return str(e)
133 elif isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
134 elif isinstance(e, VECTOR_TYPES): #vector types
136 dt_str = self.get_value() #large vectors use code
138 else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
139 elif t in ('file_open', 'file_save'):
140 dt_str = self.get_value()
142 else: dt_str = str(e) #other types
143 ##################################################
145 ##################################################
146 if len(dt_str) > max_len:
147 if truncate < 0: #front truncate
148 dt_str = '...' + dt_str[3-max_len:]
149 elif truncate == 0: #center truncate
150 dt_str = dt_str[:max_len/2 -3] + '...' + dt_str[-max_len/2:]
151 elif truncate > 0: #rear truncate
152 dt_str = dt_str[:max_len-3] + '...'
155 def get_input(self, *args, **kwargs):
156 if self.get_type() in ('file_open', 'file_save'): return FileParam(self, *args, **kwargs)
157 return _GUIParam.get_input(self, *args, **kwargs)
161 Get the color that represents this param's type.
162 @return a hex color code.
167 'complex': Constants.COMPLEX_COLOR_SPEC,
168 'real': Constants.FLOAT_COLOR_SPEC,
169 'int': Constants.INT_COLOR_SPEC,
171 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
172 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
173 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
175 'bool': Constants.INT_COLOR_SPEC,
176 'hex': Constants.INT_COLOR_SPEC,
177 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
178 'id': Constants.ID_COLOR_SPEC,
179 'stream_id': Constants.ID_COLOR_SPEC,
180 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
181 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
182 'raw': Constants.WILDCARD_COLOR_SPEC,
184 except: return _Param.get_color(self)
188 Get the hide value from the base class.
189 Hide the ID parameter for most blocks. Exceptions below.
190 If the parameter controls a port type, vlen, or nports, return part.
191 If the parameter is an empty grid position, return part.
192 These parameters are redundant to display in the flow graph view.
193 @return hide the hide property string
195 hide = _Param.get_hide(self)
197 #hide ID in non variable blocks
198 if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): return 'part'
199 #hide port controllers for type and nports
200 if self.get_key() in ' '.join(map(
201 lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
203 #hide port controllers for vlen, when == 1
204 if self.get_key() in ' '.join(map(
205 lambda p: p._vlen, self.get_parent().get_ports())
208 assert int(self.get_evaluated()) == 1
211 #hide empty grid positions
212 if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
218 A test evaluation is performed
220 _Param.validate(self) #checks type
221 self._evaluated = None
222 try: self._evaluated = self.evaluate()
223 except Exception, e: self.add_error_message(str(e))
225 def get_evaluated(self): return self._evaluated
230 @return evaluated type
233 self._lisitify_flag = False
234 self._stringify_flag = False
235 self._hostage_cells = list()
238 e = self.get_parent().get_parent().evaluate(v)
239 assert isinstance(e, str)
242 self._stringify_flag = True
246 #########################
248 #########################
249 if self.is_enum(): return v
250 #########################
252 #########################
253 elif t in ('raw', 'complex', 'real', 'int', 'hex', 'bool'):
254 #raise exception if python cannot evaluate this value
255 try: e = self.get_parent().get_parent().evaluate(v)
256 except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
257 #raise an exception if the data is invalid
258 if t == 'raw': return e
260 try: assert isinstance(e, COMPLEX_TYPES)
261 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
264 try: assert isinstance(e, REAL_TYPES)
265 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
268 try: assert isinstance(e, INT_TYPES)
269 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
271 elif t == 'hex': return hex(e)
273 try: assert isinstance(e, bool)
274 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
276 else: raise TypeError, 'Type "%s" not handled'%t
277 #########################
278 # Numeric Vector Types
279 #########################
280 elif t in ('complex_vector', 'real_vector', 'int_vector'):
281 if not v: v = '()' #turn a blank string into an empty list, so it will eval
282 #raise exception if python cannot evaluate this value
283 try: e = self.get_parent().get_parent().evaluate(v)
284 except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
285 #raise an exception if the data is invalid
286 if t == 'complex_vector':
287 if not isinstance(e, VECTOR_TYPES):
288 self._lisitify_flag = True
291 for ei in e: assert isinstance(ei, COMPLEX_TYPES)
292 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
294 elif t == 'real_vector':
295 if not isinstance(e, VECTOR_TYPES):
296 self._lisitify_flag = True
299 for ei in e: assert isinstance(ei, REAL_TYPES)
300 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
302 elif t == 'int_vector':
303 if not isinstance(e, VECTOR_TYPES):
304 self._lisitify_flag = True
307 for ei in e: assert isinstance(ei, INT_TYPES)
308 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
310 #########################
312 #########################
313 elif t in ('string', 'file_open', 'file_save'):
314 #do not check if file/directory exists, that is a runtime issue
317 #########################
319 #########################
321 #can python use this as a variable?
322 try: assert _check_id_matcher.match(v)
323 except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
324 ids = [param.get_value() for param in self.get_all_params(t)]
325 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
326 except: raise Exception, 'ID "%s" is not unique.'%v
327 try: assert v not in ID_BLACKLIST
328 except: raise Exception, 'ID "%s" is blacklisted.'%v
330 #########################
332 #########################
333 elif t == 'stream_id':
334 #get a list of all stream ids used in the virtual sinks
335 ids = [param.get_value() for param in filter(
336 lambda p: p.get_parent().is_virtual_sink(),
337 self.get_all_params(t),
339 #check that the virtual sink's stream id is unique
340 if self.get_parent().is_virtual_sink():
341 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
342 except: raise Exception, 'Stream ID "%s" is not unique.'%v
343 #check that the virtual source's steam id is found
344 if self.get_parent().is_virtual_source():
346 except: raise Exception, 'Stream ID "%s" is not found.'%v
348 #########################
350 #########################
351 elif t == 'grid_pos':
352 if not v: return '' #allow for empty grid pos
353 e = self.get_parent().get_parent().evaluate(v)
355 assert isinstance(e, (list, tuple)) and len(e) == 4
356 for ei in e: assert isinstance(ei, int)
357 except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
358 row, col, row_span, col_span = e
360 try: assert row >= 0 and col >= 0
361 except AssertionError: raise Exception, 'Row and column must be non-negative.'
362 #check row span, col span
363 try: assert row_span > 0 and col_span > 0
364 except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
365 #get hostage cell parent
366 try: my_parent = self.get_parent().get_param('notebook').evaluate()
367 except: my_parent = ''
368 #calculate hostage cells
369 for r in range(row_span):
370 for c in range(col_span):
371 self._hostage_cells.append((my_parent, (row+r, col+c)))
373 params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
375 for parent, cell in param._hostage_cells:
376 if (parent, cell) in self._hostage_cells:
377 raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
379 #########################
381 #########################
382 elif t == 'notebook':
383 if not v: return '' #allow for empty notebook
384 #get a list of all notebooks
385 notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
386 #check for notebook param syntax
387 try: notebook_id, page_index = map(str.strip, v.split(','))
388 except: raise Exception, 'Bad notebook page format.'
389 #check that the notebook id is valid
390 try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
391 except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
392 #check that page index exists
393 try: assert int(page_index) in range(len(notebook_block.get_param('labels').evaluate()))
394 except: raise Exception, 'Page index "%s" is not a valid index number.'%page_index
395 return notebook_id, page_index
396 #########################
398 #########################
400 n = dict() #new namespace
402 except ImportError: raise Exception, 'Import "%s" failed.'%v
403 except Exception: raise Exception, 'Bad import syntax: "%s".'%v
404 return filter(lambda k: str(k) != '__builtins__', n.keys())
405 #########################
406 else: raise TypeError, 'Type "%s" not handled'%t
410 Convert the value to code.
411 For string and list types, check the init flag, call evaluate().
412 This ensures that evaluate() was called to set the xxxify_flags.
413 @return a string representing the code
417 if t in ('string', 'file_open', 'file_save'): #string types
418 if not self._init: self.evaluate()
419 if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
421 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
422 if not self._init: self.evaluate()
423 if self._lisitify_flag: return '(%s, )'%v
424 else: return '(%s)'%v
427 def get_all_params(self, type):
429 Get all the params from the flowgraph that have the given type.
430 @param type the specified type
431 @return a list of params
433 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])