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', '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
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 #########################
273 # Numeric Vector Types
274 #########################
275 elif t == 'complex_vector':
276 if not isinstance(e, VECTOR_TYPES):
277 self._lisitify_flag = True
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)
283 elif t == 'real_vector':
284 if not isinstance(e, VECTOR_TYPES):
285 self._lisitify_flag = True
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)
291 elif t == 'int_vector':
292 if not isinstance(e, VECTOR_TYPES):
293 self._lisitify_flag = True
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)
299 elif t == 'hex': return hex(e)
301 try: assert isinstance(e, bool)
302 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
304 else: raise TypeError, 'Type "%s" not handled'%t
305 #########################
307 #########################
308 elif t in ('string', 'file_open', 'file_save'):
309 #do not check if file/directory exists, that is a runtime issue
312 #########################
314 #########################
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
325 #########################
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),
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():
341 except: raise Exception, 'Stream ID "%s" is not found.'%v
343 #########################
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)
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
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)))
368 params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
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))
374 #########################
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 #########################
393 #########################
395 n = dict() #new namespace
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
405 Convert the value to code.
406 For string and list types, check the init flag, call evaluate().
407 This ensures that evaluate() was called to set the xxxify_flags.
408 @return a string representing the code
412 if t in ('string', 'file_open', 'file_save'): #string types
413 if not self._init: self.evaluate()
414 if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
416 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
417 if not self._init: self.evaluate()
418 if self._lisitify_flag: return '(%s, )'%v
419 else: return '(%s)'%v
422 def get_all_params(self, type):
424 Get all the params from the flowgraph that have the given type.
425 @param type the specified type
426 @return a list of params
428 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])