2 Copyright 2008, 2009, 2010 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._input.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 ##################################################
112 # truncate helper method
113 ##################################################
114 def _truncate(string, style=0):
115 max_len = max(27 - len(self.get_name()), 3)
116 if len(string) > max_len:
117 if style < 0: #front truncate
118 string = '...' + string[3-max_len:]
119 elif style == 0: #center truncate
120 string = string[:max_len/2 -3] + '...' + string[-max_len/2:]
121 elif style > 0: #rear truncate
122 string = string[:max_len-3] + '...'
124 ##################################################
126 ##################################################
127 if not self.is_valid(): return _truncate(self.get_value())
128 if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
129 ##################################################
130 # display logic for numbers
131 ##################################################
133 if isinstance(num, COMPLEX_TYPES):
134 num = complex(num) #cast to python complex
135 if num == 0: return '0' #value is zero
136 elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
137 elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
138 elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
139 else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
140 else: return str(num)
141 ##################################################
142 # split up formatting by type
143 ##################################################
144 truncate = 0 #default center truncate
145 e = self.get_evaluated()
147 if isinstance(e, bool): return str(e)
148 elif isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
149 elif isinstance(e, VECTOR_TYPES): #vector types
151 dt_str = self.get_value() #large vectors use code
153 else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
154 elif t in ('file_open', 'file_save'):
155 dt_str = self.get_value()
157 else: dt_str = str(e) #other types
158 ##################################################
160 ##################################################
161 return _truncate(dt_str, truncate)
163 def get_input(self, *args, **kwargs):
164 if self.get_type() in ('file_open', 'file_save'): return FileParam(self, *args, **kwargs)
165 return _GUIParam.get_input(self, *args, **kwargs)
169 Get the color that represents this param's type.
170 @return a hex color code.
175 'complex': Constants.COMPLEX_COLOR_SPEC,
176 'real': Constants.FLOAT_COLOR_SPEC,
177 'int': Constants.INT_COLOR_SPEC,
179 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
180 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
181 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
183 'bool': Constants.INT_COLOR_SPEC,
184 'hex': Constants.INT_COLOR_SPEC,
185 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
186 'id': Constants.ID_COLOR_SPEC,
187 'stream_id': Constants.ID_COLOR_SPEC,
188 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
189 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
190 'raw': Constants.WILDCARD_COLOR_SPEC,
192 except: return _Param.get_color(self)
196 Get the hide value from the base class.
197 Hide the ID parameter for most blocks. Exceptions below.
198 If the parameter controls a port type, vlen, or nports, return part.
199 If the parameter is an empty grid position, return part.
200 These parameters are redundant to display in the flow graph view.
201 @return hide the hide property string
203 hide = _Param.get_hide(self)
205 #hide ID in non variable blocks
206 if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): return 'part'
207 #hide port controllers for type and nports
208 if self.get_key() in ' '.join(map(
209 lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
211 #hide port controllers for vlen, when == 1
212 if self.get_key() in ' '.join(map(
213 lambda p: p._vlen, self.get_parent().get_ports())
216 assert int(self.get_evaluated()) == 1
219 #hide empty grid positions
220 if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
226 A test evaluation is performed
228 _Param.validate(self) #checks type
229 self._evaluated = None
230 try: self._evaluated = self.evaluate()
231 except Exception, e: self.add_error_message(str(e))
233 def get_evaluated(self): return self._evaluated
238 @return evaluated type
241 self._lisitify_flag = False
242 self._stringify_flag = False
243 self._hostage_cells = list()
246 e = self.get_parent().get_parent().evaluate(v)
247 assert isinstance(e, str)
250 self._stringify_flag = True
254 #########################
256 #########################
257 if self.is_enum(): return v
258 #########################
260 #########################
261 elif t in ('raw', 'complex', 'real', 'int', 'hex', 'bool'):
262 #raise exception if python cannot evaluate this value
263 try: e = self.get_parent().get_parent().evaluate(v)
264 except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
265 #raise an exception if the data is invalid
266 if t == 'raw': return e
268 try: assert isinstance(e, COMPLEX_TYPES)
269 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
272 try: assert isinstance(e, REAL_TYPES)
273 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
276 try: assert isinstance(e, INT_TYPES)
277 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
279 elif t == 'hex': return hex(e)
281 try: assert isinstance(e, bool)
282 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
284 else: raise TypeError, 'Type "%s" not handled'%t
285 #########################
286 # Numeric Vector Types
287 #########################
288 elif t in ('complex_vector', 'real_vector', 'int_vector'):
289 if not v: v = '()' #turn a blank string into an empty list, so it will eval
290 #raise exception if python cannot evaluate this value
291 try: e = self.get_parent().get_parent().evaluate(v)
292 except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
293 #raise an exception if the data is invalid
294 if t == 'complex_vector':
295 if not isinstance(e, VECTOR_TYPES):
296 self._lisitify_flag = True
299 for ei in e: assert isinstance(ei, COMPLEX_TYPES)
300 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
302 elif t == 'real_vector':
303 if not isinstance(e, VECTOR_TYPES):
304 self._lisitify_flag = True
307 for ei in e: assert isinstance(ei, REAL_TYPES)
308 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
310 elif t == 'int_vector':
311 if not isinstance(e, VECTOR_TYPES):
312 self._lisitify_flag = True
315 for ei in e: assert isinstance(ei, INT_TYPES)
316 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
318 #########################
320 #########################
321 elif t in ('string', 'file_open', 'file_save'):
322 #do not check if file/directory exists, that is a runtime issue
325 #########################
327 #########################
329 #can python use this as a variable?
330 try: assert _check_id_matcher.match(v)
331 except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
332 ids = [param.get_value() for param in self.get_all_params(t)]
333 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
334 except: raise Exception, 'ID "%s" is not unique.'%v
335 try: assert v not in ID_BLACKLIST
336 except: raise Exception, 'ID "%s" is blacklisted.'%v
338 #########################
340 #########################
341 elif t == 'stream_id':
342 #get a list of all stream ids used in the virtual sinks
343 ids = [param.get_value() for param in filter(
344 lambda p: p.get_parent().is_virtual_sink(),
345 self.get_all_params(t),
347 #check that the virtual sink's stream id is unique
348 if self.get_parent().is_virtual_sink():
349 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
350 except: raise Exception, 'Stream ID "%s" is not unique.'%v
351 #check that the virtual source's steam id is found
352 if self.get_parent().is_virtual_source():
354 except: raise Exception, 'Stream ID "%s" is not found.'%v
356 #########################
358 #########################
359 elif t == 'grid_pos':
360 if not v: return '' #allow for empty grid pos
361 e = self.get_parent().get_parent().evaluate(v)
363 assert isinstance(e, (list, tuple)) and len(e) == 4
364 for ei in e: assert isinstance(ei, int)
365 except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
366 row, col, row_span, col_span = e
368 try: assert row >= 0 and col >= 0
369 except AssertionError: raise Exception, 'Row and column must be non-negative.'
370 #check row span, col span
371 try: assert row_span > 0 and col_span > 0
372 except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
373 #get hostage cell parent
374 try: my_parent = self.get_parent().get_param('notebook').evaluate()
375 except: my_parent = ''
376 #calculate hostage cells
377 for r in range(row_span):
378 for c in range(col_span):
379 self._hostage_cells.append((my_parent, (row+r, col+c)))
381 params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
383 for parent, cell in param._hostage_cells:
384 if (parent, cell) in self._hostage_cells:
385 raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
387 #########################
389 #########################
390 elif t == 'notebook':
391 if not v: return '' #allow for empty notebook
392 #get a list of all notebooks
393 notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
394 #check for notebook param syntax
395 try: notebook_id, page_index = map(str.strip, v.split(','))
396 except: raise Exception, 'Bad notebook page format.'
397 #check that the notebook id is valid
398 try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
399 except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
400 #check that page index exists
401 try: assert int(page_index) in range(len(notebook_block.get_param('labels').evaluate()))
402 except: raise Exception, 'Page index "%s" is not a valid index number.'%page_index
403 return notebook_id, page_index
404 #########################
406 #########################
408 n = dict() #new namespace
410 except ImportError: raise Exception, 'Import "%s" failed.'%v
411 except Exception: raise Exception, 'Bad import syntax: "%s".'%v
412 return filter(lambda k: str(k) != '__builtins__', n.keys())
413 #########################
414 else: raise TypeError, 'Type "%s" not handled'%t
418 Convert the value to code.
419 For string and list types, check the init flag, call evaluate().
420 This ensures that evaluate() was called to set the xxxify_flags.
421 @return a string representing the code
425 if t in ('string', 'file_open', 'file_save'): #string types
426 if not self._init: self.evaluate()
427 if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
429 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
430 if not self._init: self.evaluate()
431 if self._lisitify_flag: return '(%s, )'%v
432 else: return '(%s)'%v
435 def get_all_params(self, type):
437 Get all the params from the flowgraph that have the given type.
438 @param type the specified type
439 @return a list of params
441 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])