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 params = self.get_all_params('id')
314 keys = [param.get_value() for param in params]
315 try: assert keys.count(v) <= 1 #id should only appear once, or zero times if block is disabled
316 except: raise Exception, 'ID "%s" is not unique.'%v
317 try: assert v not in ID_BLACKLIST
318 except: raise Exception, 'ID "%s" is blacklisted.'%v
320 #########################
322 #########################
323 elif t == 'grid_pos':
324 if not v: return '' #allow for empty grid pos
325 e = self.get_parent().get_parent().evaluate(v)
327 assert isinstance(e, (list, tuple)) and len(e) == 4
328 for ei in e: assert isinstance(ei, int)
329 except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
330 row, col, row_span, col_span = e
332 try: assert row >= 0 and col >= 0
333 except AssertionError: raise Exception, 'Row and column must be non-negative.'
334 #check row span, col span
335 try: assert row_span > 0 and col_span > 0
336 except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
337 #get hostage cell parent
338 try: my_parent = self.get_parent().get_param('notebook').evaluate()
339 except: my_parent = ''
340 #calculate hostage cells
341 for r in range(row_span):
342 for c in range(col_span):
343 self._hostage_cells.append((my_parent, (row+r, col+c)))
345 params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
347 for parent, cell in param._hostage_cells:
348 if (parent, cell) in self._hostage_cells:
349 raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
351 #########################
353 #########################
354 elif t == 'notebook':
355 if not v: return '' #allow for empty notebook
356 #get a list of all notebooks
357 notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
358 #check for notebook param syntax
359 try: notebook_id, page_index = map(str.strip, v.split(','))
360 except: raise Exception, 'Bad notebook page format.'
361 #check that the notebook id is valid
362 try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
363 except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
364 #check that page index exists
365 try: assert int(page_index) in range(len(notebook_block.get_param('labels').get_evaluated()))
366 except: raise Exception, 'Page index "%s" is not a valid index number.'%page_index
367 return notebook_id, page_index
368 #########################
370 #########################
372 n = dict() #new namespace
374 except ImportError: raise Exception, 'Import "%s" failed.'%v
375 except Exception: raise Exception, 'Bad import syntax: "%s".'%v
376 return filter(lambda k: str(k) != '__builtins__', n.keys())
377 #########################
378 else: raise TypeError, 'Type "%s" not handled'%t
382 Convert the value to code.
383 @return a string representing the code
385 #run init tasks in evaluate
386 #such as setting flags
387 if not self._init: self.evaluate()
390 if t in ('string', 'file_open', 'file_save'): #string types
391 if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
393 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
394 if self._lisitify_flag: return '(%s, )'%v
395 else: return '(%s)'%v
398 def get_all_params(self, type):
400 Get all the params from the flowgraph that have the given type.
401 @param type the specified type
402 @return a list of params
404 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])