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, EntryParam
28 from gnuradio import eng_notation
30 from gnuradio import gr
32 _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
33 _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
35 class FileParam(EntryParam):
36 """Provide an entry box for filename and a button to browse for a file."""
38 def __init__(self, *args, **kwargs):
39 EntryParam.__init__(self, *args, **kwargs)
40 input = gtk.Button('...')
41 input.connect('clicked', self._handle_clicked)
42 self.pack_start(input, False)
44 def _handle_clicked(self, widget=None):
46 If the button was clicked, open a file dialog in open/save format.
47 Replace the text in the entry with the new filename from the file dialog.
50 file_path = self.param.is_valid() and self.param.get_evaluated() or ''
51 (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
52 if not os.path.exists(dirname): dirname = os.getcwd() #fix bad paths
54 if self.param.get_type() == 'file_open':
55 file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
56 gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
57 elif self.param.get_type() == 'file_save':
58 file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
59 gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
60 file_dialog.set_do_overwrite_confirmation(True)
61 file_dialog.set_current_name(basename) #show the current filename
62 file_dialog.set_current_folder(dirname) #current directory
63 file_dialog.set_select_multiple(False)
64 file_dialog.set_local_only(True)
65 if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
66 file_path = file_dialog.get_filename() #get the file path
67 self.entry.set_text(file_path)
68 self._handle_changed()
69 file_dialog.destroy() #destroy the dialog
71 #blacklist certain ids, its not complete, but should help
73 ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + \
74 filter(lambda x: not x.startswith('_'), dir(gr.top_block())) + dir(__builtin__)
75 #define types, native python + numpy
76 VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
77 COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
78 REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
79 INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
80 numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
81 #cast to tuple for isinstance, concat subtypes
82 COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
83 REAL_TYPES = tuple(REAL_TYPES + INT_TYPES)
84 INT_TYPES = tuple(INT_TYPES)
89 _hostage_cells = list()
91 ##possible param types
92 TYPES = _Param.TYPES + [
93 'complex', 'real', 'int',
94 'complex_vector', 'real_vector', 'int_vector',
95 'hex', 'string', 'bool',
96 'file_open', 'file_save',
98 'grid_pos', 'notebook',
104 Get the repr (nice string format) for this param.
105 @return the string representation
107 if not self.is_valid(): return self.get_value()
108 if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
109 ##################################################
110 # display logic for numbers
111 ##################################################
113 if isinstance(num, COMPLEX_TYPES):
114 num = complex(num) #cast to python complex
115 if num == 0: return '0' #value is zero
116 elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
117 elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
118 elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
119 else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
120 else: return str(num)
121 ##################################################
122 # split up formatting by type
123 ##################################################
124 truncate = 0 #default center truncate
125 max_len = max(27 - len(self.get_name()), 3)
126 e = self.get_evaluated()
128 if isinstance(e, bool): return str(e)
129 elif isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
130 elif isinstance(e, VECTOR_TYPES): #vector types
132 dt_str = self.get_value() #large vectors use code
134 else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
135 elif t in ('file_open', 'file_save'):
136 dt_str = self.get_value()
138 else: dt_str = str(e) #other types
139 ##################################################
141 ##################################################
142 if len(dt_str) > max_len:
143 if truncate < 0: #front truncate
144 dt_str = '...' + dt_str[3-max_len:]
145 elif truncate == 0: #center truncate
146 dt_str = dt_str[:max_len/2 -3] + '...' + dt_str[-max_len/2:]
147 elif truncate > 0: #rear truncate
148 dt_str = dt_str[:max_len-3] + '...'
151 def get_input_class(self):
152 if self.get_type() in ('file_open', 'file_save'): return FileParam
153 return _Param.get_input_class(self)
157 Get the color that represents this param's type.
158 @return a hex color code.
163 'complex': Constants.COMPLEX_COLOR_SPEC,
164 'real': Constants.FLOAT_COLOR_SPEC,
165 'int': Constants.INT_COLOR_SPEC,
167 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
168 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
169 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
171 'bool': Constants.INT_COLOR_SPEC,
172 'hex': Constants.INT_COLOR_SPEC,
173 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
174 'id': Constants.ID_COLOR_SPEC,
175 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
176 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
177 'raw': Constants.WILDCARD_COLOR_SPEC,
179 except: return _Param.get_color(self)
183 Get the hide value from the base class.
184 Hide the ID parameter for most blocks. Exceptions below.
185 If the parameter controls a port type, vlen, or nports, return part.
186 If the parameter is an empty grid position, return part.
187 These parameters are redundant to display in the flow graph view.
188 @return hide the hide property string
190 hide = _Param.get_hide(self)
192 #hide ID in non variable blocks
193 if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): return 'part'
194 #hide port controllers for type and nports
195 if self.get_key() in ' '.join(map(
196 lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
198 #hide port controllers for vlen, when == 1
199 if self.get_key() in ' '.join(map(
200 lambda p: p._vlen, self.get_parent().get_ports())
203 assert int(self.get_evaluated()) == 1
206 #hide empty grid positions
207 if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
213 A test evaluation is performed
215 _Param.validate(self) #checks type
216 self._evaluated = None
217 try: self._evaluated = self.evaluate()
218 except Exception, e: self.add_error_message(str(e))
220 def get_evaluated(self): return self._evaluated
225 @return evaluated type
228 self._lisitify_flag = False
229 self._stringify_flag = False
230 self._hostage_cells = list()
233 e = self.get_parent().get_parent().evaluate(v)
234 assert isinstance(e, str)
237 self._stringify_flag = True
241 #########################
243 #########################
244 if self.is_enum(): return v
245 #########################
247 #########################
248 elif t in ('raw', 'complex', 'real', 'int', 'complex_vector', 'real_vector', 'int_vector', 'hex', 'bool'):
249 #raise exception if python cannot evaluate this value
250 try: e = self.get_parent().get_parent().evaluate(v)
251 except Exception, e: raise Exception, 'Value "%s" cannot be evaluated: %s'%(v, e)
252 #raise an exception if the data is invalid
253 if t == 'raw': return e
255 try: assert isinstance(e, COMPLEX_TYPES)
256 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
259 try: assert isinstance(e, REAL_TYPES)
260 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
263 try: assert isinstance(e, INT_TYPES)
264 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
266 #########################
267 # Numeric Vector Types
268 #########################
269 elif t == 'complex_vector':
270 if not isinstance(e, VECTOR_TYPES):
271 self._lisitify_flag = True
274 for ei in e: assert isinstance(ei, COMPLEX_TYPES)
275 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
277 elif t == 'real_vector':
278 if not isinstance(e, VECTOR_TYPES):
279 self._lisitify_flag = True
282 for ei in e: assert isinstance(ei, REAL_TYPES)
283 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
285 elif t == 'int_vector':
286 if not isinstance(e, VECTOR_TYPES):
287 self._lisitify_flag = True
290 for ei in e: assert isinstance(ei, INT_TYPES)
291 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
293 elif t == 'hex': return hex(e)
295 try: assert isinstance(e, bool)
296 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
298 else: raise TypeError, 'Type "%s" not handled'%t
299 #########################
301 #########################
302 elif t in ('string', 'file_open', 'file_save'):
303 #do not check if file/directory exists, that is a runtime issue
306 #########################
308 #########################
310 #can python use this as a variable?
311 try: assert _check_id_matcher.match(v)
312 except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
313 ids = [param.get_value() for param in self.get_all_params(t)]
314 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
315 except: raise Exception, 'ID "%s" is not unique.'%v
316 try: assert v not in ID_BLACKLIST
317 except: raise Exception, 'ID "%s" is blacklisted.'%v
319 #########################
321 #########################
322 elif t == 'stream_id':
323 #get a list of all stream ids used in the virtual sinks
324 ids = [param.get_value() for param in filter(
325 lambda p: p.get_parent().is_virtual_sink(),
326 self.get_all_params(t),
328 #check that the virtual sink's stream id is unique
329 if self.get_parent().is_virtual_sink():
330 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
331 except: raise Exception, 'Stream ID "%s" is not unique.'%v
332 #check that the virtual source's steam id is found
333 if self.get_parent().is_virtual_source():
335 except: raise Exception, 'Stream ID "%s" is not found.'%v
337 #########################
339 #########################
340 elif t == 'grid_pos':
341 if not v: return '' #allow for empty grid pos
342 e = self.get_parent().get_parent().evaluate(v)
344 assert isinstance(e, (list, tuple)) and len(e) == 4
345 for ei in e: assert isinstance(ei, int)
346 except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
347 row, col, row_span, col_span = e
349 try: assert row >= 0 and col >= 0
350 except AssertionError: raise Exception, 'Row and column must be non-negative.'
351 #check row span, col span
352 try: assert row_span > 0 and col_span > 0
353 except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
354 #get hostage cell parent
355 try: my_parent = self.get_parent().get_param('notebook').evaluate()
356 except: my_parent = ''
357 #calculate hostage cells
358 for r in range(row_span):
359 for c in range(col_span):
360 self._hostage_cells.append((my_parent, (row+r, col+c)))
362 params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
364 for parent, cell in param._hostage_cells:
365 if (parent, cell) in self._hostage_cells:
366 raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
368 #########################
370 #########################
371 elif t == 'notebook':
372 if not v: return '' #allow for empty notebook
373 #get a list of all notebooks
374 notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
375 #check for notebook param syntax
376 try: notebook_id, page_index = map(str.strip, v.split(','))
377 except: raise Exception, 'Bad notebook page format.'
378 #check that the notebook id is valid
379 try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
380 except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
381 #check that page index exists
382 try: assert int(page_index) in range(len(notebook_block.get_param('labels').get_evaluated()))
383 except: raise Exception, 'Page index "%s" is not a valid index number.'%page_index
384 return notebook_id, page_index
385 #########################
387 #########################
389 n = dict() #new namespace
391 except ImportError: raise Exception, 'Import "%s" failed.'%v
392 except Exception: raise Exception, 'Bad import syntax: "%s".'%v
393 return filter(lambda k: str(k) != '__builtins__', n.keys())
394 #########################
395 else: raise TypeError, 'Type "%s" not handled'%t
399 Convert the value to code.
400 @return a string representing the code
402 #run init tasks in evaluate
403 #such as setting flags
404 if not self._init: self.evaluate()
407 if t in ('string', 'file_open', 'file_save'): #string types
408 if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
410 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
411 if self._lisitify_flag: return '(%s, )'%v
412 else: return '(%s)'%v
415 def get_all_params(self, type):
417 Get all the params from the flowgraph that have the given type.
418 @param type the specified type
419 @return a list of params
421 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])