Imported Upstream version 3.2.2
[debian/gnuradio] / grc / python / Param.py
1 """
2 Copyright 2008, 2009 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
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.
9
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.
14
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
18 """
19
20 import expr_utils
21 from .. base.Param import Param as _Param, EntryParam
22 import Constants
23 import numpy
24 import os
25 import pygtk
26 pygtk.require('2.0')
27 import gtk
28 from gnuradio import eng_notation
29 import re
30 from gnuradio import gr
31
32 _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
33 _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
34
35 class FileParam(EntryParam):
36         """Provide an entry box for filename and a button to browse for a file."""
37
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)
43
44         def _handle_clicked(self, widget=None):
45                 """
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.
48                 """
49                 #get the paths
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
53                 #build the dialog
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
70
71 #blacklist certain ids, its not complete, but should help
72 import __builtin__
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)
85
86 class Param(_Param):
87
88         _init = False
89         _hostage_cells = list()
90
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',
97                 'id',
98                 'grid_pos', 'notebook',
99                 'import',
100         ]
101
102         def __repr__(self):
103                 """
104                 Get the repr (nice string format) for this param.
105                 @return the string representation
106                 """
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                 ##################################################
112                 def num_to_str(num):
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()
127                 t = self.get_type()
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
131                         if len(e) > 8:
132                                 dt_str = self.get_value() #large vectors use code
133                                 truncate = 1
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()
137                         truncate = -1
138                 else: dt_str = str(e) #other types
139                 ##################################################
140                 # truncate
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] + '...'
149                 return dt_str
150
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)
154
155         def get_color(self):
156                 """
157                 Get the color that represents this param's type.
158                 @return a hex color code.
159                 """
160                 try:
161                         return {
162                                 #number types
163                                 'complex': Constants.COMPLEX_COLOR_SPEC,
164                                 'real': Constants.FLOAT_COLOR_SPEC,
165                                 'int': Constants.INT_COLOR_SPEC,
166                                 #vector types
167                                 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
168                                 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
169                                 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
170                                 #special
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,
178                         }[self.get_type()]
179                 except: return _Param.get_color(self)
180
181         def get_hide(self):
182                 """
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
189                 """
190                 hide = _Param.get_hide(self)
191                 if hide: return hide
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())
197                 ): return 'part'
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())
201                 ):
202                         try:
203                                 assert int(self.get_evaluated()) == 1
204                                 return 'part'
205                         except: pass
206                 #hide empty grid positions
207                 if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
208                 return hide
209
210         def validate(self):
211                 """
212                 Validate the param.
213                 A test evaluation is performed
214                 """
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))
219
220         def get_evaluated(self): return self._evaluated
221
222         def evaluate(self):
223                 """
224                 Evaluate the value.
225                 @return evaluated type
226                 """
227                 self._init = True
228                 self._lisitify_flag = False
229                 self._stringify_flag = False
230                 self._hostage_cells = list()
231                 def eval_string(v):
232                         try:
233                                 e = self.get_parent().get_parent().evaluate(v)
234                                 assert isinstance(e, str)
235                                 return e
236                         except:
237                                 self._stringify_flag = True
238                                 return v
239                 t = self.get_type()
240                 v = self.get_value()
241                 #########################
242                 # Enum Type
243                 #########################
244                 if self.is_enum(): return v
245                 #########################
246                 # Numeric Types
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
254                         elif t == 'complex':
255                                 try: assert isinstance(e, COMPLEX_TYPES)
256                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
257                                 return e
258                         elif t == 'real':
259                                 try: assert isinstance(e, REAL_TYPES)
260                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
261                                 return e
262                         elif t == 'int':
263                                 try: assert isinstance(e, INT_TYPES)
264                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
265                                 return e
266                         #########################
267                         # Numeric Vector Types
268                         #########################
269                         elif t == 'complex_vector':
270                                 if not isinstance(e, VECTOR_TYPES):
271                                         self._lisitify_flag = True
272                                         e = [e]
273                                 try:
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)
276                                 return e
277                         elif t == 'real_vector':
278                                 if not isinstance(e, VECTOR_TYPES):
279                                         self._lisitify_flag = True
280                                         e = [e]
281                                 try:
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)
284                                 return e
285                         elif t == 'int_vector':
286                                 if not isinstance(e, VECTOR_TYPES):
287                                         self._lisitify_flag = True
288                                         e = [e]
289                                 try:
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)
292                                 return e
293                         elif t == 'hex': return hex(e)
294                         elif t == 'bool':
295                                 try: assert isinstance(e, bool)
296                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
297                                 return e
298                         else: raise TypeError, 'Type "%s" not handled'%t
299                 #########################
300                 # String Types
301                 #########################
302                 elif t in ('string', 'file_open', 'file_save'):
303                         #do not check if file/directory exists, that is a runtime issue
304                         e = eval_string(v)
305                         return str(e)
306                 #########################
307                 # Unique ID Type
308                 #########################
309                 elif t == 'id':
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
319                         return v
320                 #########################
321                 # Grid Position Type
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)
326                         try:
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
331                         #check row, col
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)))
344                         #avoid collisions
345                         params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
346                         for param in params:
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))
350                         return e
351                 #########################
352                 # Notebook Page Type
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                 #########################
369                 # Import Type
370                 #########################
371                 elif t == 'import':
372                         n = dict() #new namespace
373                         try: exec v in n
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
379
380         def to_code(self):
381                 """
382                 Convert the value to code.
383                 @return a string representing the code
384                 """
385                 #run init tasks in evaluate
386                 #such as setting flags
387                 if not self._init: self.evaluate()
388                 v = self.get_value()
389                 t = self.get_type()
390                 if t in ('string', 'file_open', 'file_save'): #string types
391                         if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
392                         else: return v
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
396                 else: return v
397
398         def get_all_params(self, type):
399                 """
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
403                 """
404                 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])