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
21 from .. base.Param import Param as _Param
22 from .. gui.Param import Param as _GUIParam
23 from .. gui.Param import EntryParam
30 from gnuradio import eng_notation
32 from gnuradio import gr
34 _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
35 _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
37 class FileParam(EntryParam):
38 """Provide an entry box for filename and a button to browse for a file."""
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)
46 def _handle_clicked(self, widget=None):
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.
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
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
73 #blacklist certain ids, its not complete, but should help
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)
88 class Param(_Param, _GUIParam):
90 def __init__(self, **kwargs):
91 _Param.__init__(self, **kwargs)
92 _GUIParam.__init__(self)
94 self._hostage_cells = list()
96 def get_types(self): return (
98 'complex', 'real', 'int',
99 'complex_vector', 'real_vector', 'int_vector',
100 'hex', 'string', 'bool',
101 'file_open', 'file_save',
103 'grid_pos', 'notebook',
109 Get the repr (nice string format) for this param.
110 @return the string representation
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 ##################################################
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()
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
137 dt_str = self.get_value() #large vectors use code
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()
143 else: dt_str = str(e) #other types
144 ##################################################
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] + '...'
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)
162 Get the color that represents this param's type.
163 @return a hex color code.
168 'complex': Constants.COMPLEX_COLOR_SPEC,
169 'real': Constants.FLOAT_COLOR_SPEC,
170 'int': Constants.INT_COLOR_SPEC,
172 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
173 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
174 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
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,
185 except: return _Param.get_color(self)
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
196 hide = _Param.get_hide(self)
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())
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())
209 assert int(self.get_evaluated()) == 1
212 #hide empty grid positions
213 if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
219 A test evaluation is performed
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))
226 def get_evaluated(self): return self._evaluated
231 @return evaluated type
234 self._lisitify_flag = False
235 self._stringify_flag = False
236 self._hostage_cells = list()
239 e = self.get_parent().get_parent().evaluate(v)
240 assert isinstance(e, str)
243 self._stringify_flag = True
247 #########################
249 #########################
250 if self.is_enum(): return v
251 #########################
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
261 try: assert isinstance(e, COMPLEX_TYPES)
262 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
265 try: assert isinstance(e, REAL_TYPES)
266 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
269 try: assert isinstance(e, INT_TYPES)
270 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
272 elif t == 'hex': return hex(e)
274 try: assert isinstance(e, bool)
275 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(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
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)
295 elif t == 'real_vector':
296 if not isinstance(e, VECTOR_TYPES):
297 self._lisitify_flag = True
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)
303 elif t == 'int_vector':
304 if not isinstance(e, VECTOR_TYPES):
305 self._lisitify_flag = True
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)
311 #########################
313 #########################
314 elif t in ('string', 'file_open', 'file_save'):
315 #do not check if file/directory exists, that is a runtime issue
318 #########################
320 #########################
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
331 #########################
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),
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():
347 except: raise Exception, 'Stream ID "%s" is not found.'%v
349 #########################
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)
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
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)))
374 params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
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))
380 #########################
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 #########################
399 #########################
401 n = dict() #new namespace
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
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
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('"', '\"')
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
428 def get_all_params(self, type):
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
434 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])