Merging r11186:11273 from grc branch.
[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)$')
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', 'import',
99         ]
100
101         def __repr__(self):
102                 """
103                 Get the repr (nice string format) for this param.
104                 @return the string representation
105                 """
106                 if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
107                 ##################################################
108                 # display logic for numbers
109                 ##################################################
110                 def num_to_str(num):
111                         if isinstance(num, COMPLEX_TYPES):
112                                 num = complex(num) #cast to python complex
113                                 if num == 0: return '0' #value is zero
114                                 elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
115                                 elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
116                                 elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
117                                 else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
118                         else: return str(num)
119                 ##################################################
120                 # split up formatting by type
121                 ##################################################
122                 truncate = 0 #default center truncate
123                 max_len = max(27 - len(self.get_name()), 3)
124                 e = self.get_evaluated()
125                 t = self.get_type()
126                 if isinstance(e, bool): return str(e)
127                 elif isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
128                 elif isinstance(e, VECTOR_TYPES): #vector types
129                         if len(e) > 8:
130                                 dt_str = self.get_value() #large vectors use code
131                                 truncate = 1
132                         else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
133                 elif t in ('file_open', 'file_save'):
134                         dt_str = self.get_value()
135                         truncate = -1
136                 else: dt_str = str(e) #other types
137                 ##################################################
138                 # truncate
139                 ##################################################
140                 if len(dt_str) > max_len:
141                         if truncate < 0: #front truncate
142                                 dt_str = '...' + dt_str[3-max_len:]
143                         elif truncate == 0: #center truncate
144                                 dt_str = dt_str[:max_len/2 -3] + '...' + dt_str[-max_len/2:]
145                         elif truncate > 0: #rear truncate
146                                 dt_str = dt_str[:max_len-3] + '...'
147                 return dt_str
148
149         def get_input_class(self):
150                 if self.get_type() in ('file_open', 'file_save'): return FileParam
151                 return _Param.get_input_class(self)
152
153         def get_color(self):
154                 """
155                 Get the color that represents this param's type.
156                 @return a hex color code.
157                 """
158                 try:
159                         return {
160                                 #number types
161                                 'complex': Constants.COMPLEX_COLOR_SPEC,
162                                 'real': Constants.FLOAT_COLOR_SPEC,
163                                 'int': Constants.INT_COLOR_SPEC,
164                                 #vector types
165                                 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
166                                 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
167                                 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
168                                 #special
169                                 'bool': Constants.INT_COLOR_SPEC,
170                                 'hex': Constants.INT_COLOR_SPEC,
171                                 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
172                                 'id': Constants.ID_COLOR_SPEC,
173                                 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
174                                 'raw': Constants.WILDCARD_COLOR_SPEC,
175                         }[self.get_type()]
176                 except: return _Param.get_color(self)
177
178         def get_hide(self):
179                 """
180                 Get the hide value from the base class.
181                 Hide the ID parameter for most blocks. Exceptions below.
182                 If the parameter controls a port type, vlen, or nports, return part.
183                 If the parameter is an empty grid position, return part.
184                 These parameters are redundant to display in the flow graph view.
185                 @return hide the hide property string
186                 """
187                 hide = _Param.get_hide(self)
188                 if hide: return hide
189                 #hide ID in non variable blocks
190                 if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): return 'part'
191                 #hide port controllers for type and nports
192                 if self.get_key() in ' '.join(map(
193                         lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
194                 ): return 'part'
195                 #hide port controllers for vlen, when == 1
196                 if self.get_key() in ' '.join(map(
197                         lambda p: p._vlen, self.get_parent().get_ports())
198                 ):
199                         try:
200                                 assert int(self.get_evaluated()) == 1
201                                 return 'part'
202                         except: pass
203                 #hide empty grid positions
204                 if self.get_key() == 'grid_pos' and not self.get_value(): return 'part'
205                 return hide
206
207         def validate(self):
208                 """
209                 Validate the param.
210                 A test evaluation is performed
211                 """
212                 _Param.validate(self) #checks type
213                 self._evaluated = None
214                 try: self._evaluated = self.evaluate()
215                 except Exception, e: self.add_error_message(str(e))
216
217         def get_evaluated(self): return self._evaluated
218
219         def evaluate(self):
220                 """
221                 Evaluate the value.
222                 @return evaluated type
223                 """
224                 self._init = True
225                 self._lisitify_flag = False
226                 self._stringify_flag = False
227                 self._hostage_cells = list()
228                 def eval_string(v):
229                         try:
230                                 e = self.get_parent().get_parent().evaluate(v)
231                                 assert isinstance(e, str)
232                                 return e
233                         except:
234                                 self._stringify_flag = True
235                                 return v
236                 t = self.get_type()
237                 v = self.get_value()
238                 #########################
239                 # Enum Type
240                 #########################
241                 if self.is_enum(): return v
242                 #########################
243                 # Numeric Types
244                 #########################
245                 elif t in ('raw', 'complex', 'real', 'int', 'complex_vector', 'real_vector', 'int_vector', 'hex', 'bool'):
246                         #raise exception if python cannot evaluate this value
247                         try: e = self.get_parent().get_parent().evaluate(v)
248                         except Exception, e: raise Exception, 'Value "%s" cannot be evaluated: %s'%(v, e)
249                         #raise an exception if the data is invalid
250                         if t == 'raw': return e
251                         elif t == 'complex':
252                                 try: assert isinstance(e, COMPLEX_TYPES)
253                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
254                                 return e
255                         elif t == 'real':
256                                 try: assert isinstance(e, REAL_TYPES)
257                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
258                                 return e
259                         elif t == 'int':
260                                 try: assert isinstance(e, INT_TYPES)
261                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
262                                 return e
263                         #########################
264                         # Numeric Vector Types
265                         #########################
266                         elif t == 'complex_vector':
267                                 if not isinstance(e, VECTOR_TYPES):
268                                         self._lisitify_flag = True
269                                         e = [e]
270                                 try:
271                                         for ei in e: assert isinstance(ei, COMPLEX_TYPES)
272                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
273                                 return e
274                         elif t == 'real_vector':
275                                 if not isinstance(e, VECTOR_TYPES):
276                                         self._lisitify_flag = True
277                                         e = [e]
278                                 try:
279                                         for ei in e: assert isinstance(ei, REAL_TYPES)
280                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
281                                 return e
282                         elif t == 'int_vector':
283                                 if not isinstance(e, VECTOR_TYPES):
284                                         self._lisitify_flag = True
285                                         e = [e]
286                                 try:
287                                         for ei in e: assert isinstance(ei, INT_TYPES)
288                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
289                                 return e
290                         elif t == 'hex': return hex(e)
291                         elif t == 'bool':
292                                 try: assert isinstance(e, bool)
293                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
294                                 return e
295                         else: raise TypeError, 'Type "%s" not handled'%t
296                 #########################
297                 # String Types
298                 #########################
299                 elif t in ('string', 'file_open', 'file_save'):
300                         #do not check if file/directory exists, that is a runtime issue
301                         e = eval_string(v)
302                         return str(e)
303                 #########################
304                 # Unique ID Type
305                 #########################
306                 elif t == 'id':
307                         #can python use this as a variable?
308                         try: assert _check_id_matcher.match(v)
309                         except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
310                         params = self.get_all_params('id')
311                         keys = [param.get_value() for param in params]
312                         try: assert keys.count(v) <= 1 #id should only appear once, or zero times if block is disabled
313                         except: raise Exception, 'ID "%s" is not unique.'%v
314                         try: assert v not in ID_BLACKLIST
315                         except: raise Exception, 'ID "%s" is blacklisted.'%v
316                         return v
317                 #########################
318                 # Grid Position Type
319                 #########################
320                 elif t == 'grid_pos':
321                         if not v: return '' #allow for empty grid pos
322                         e = self.get_parent().get_parent().evaluate(v)
323                         try:
324                                 assert isinstance(e, (list, tuple)) and len(e) == 4
325                                 for ei in e: assert isinstance(ei, int)
326                         except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
327                         row, col, row_span, col_span = e
328                         #check row, col
329                         try: assert row >= 0 and col >= 0
330                         except AssertionError: raise Exception, 'Row and column must be non-negative.'
331                         #check row span, col span
332                         try: assert row_span > 0 and col_span > 0
333                         except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
334                         #calculate hostage cells
335                         for r in range(row_span):
336                                 for c in range(col_span):
337                                         self._hostage_cells.append((row+r, col+c))
338                         #avoid collisions
339                         params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
340                         for param in params:
341                                 for cell in param._hostage_cells:
342                                         if cell in self._hostage_cells: raise Exception, 'Another graphical element is using cell "%s".'%str(cell)
343                         return e
344                 #########################
345                 # Import Type
346                 #########################
347                 elif t == 'import':
348                         n = dict() #new namespace
349                         try: exec v in n
350                         except ImportError: raise Exception, 'Import "%s" failed.'%v
351                         except Exception: raise Exception, 'Bad import syntax: "%s".'%v
352                         return filter(lambda k: str(k) != '__builtins__', n.keys())
353                 #########################
354                 else: raise TypeError, 'Type "%s" not handled'%t
355
356         def to_code(self):
357                 """
358                 Convert the value to code.
359                 @return a string representing the code
360                 """
361                 #run init tasks in evaluate
362                 #such as setting flags
363                 if not self._init: self.evaluate()
364                 v = self.get_value()
365                 t = self.get_type()
366                 if t in ('string', 'file_open', 'file_save'): #string types
367                         if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
368                         else: return v
369                 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
370                         if self._lisitify_flag: return '(%s, )'%v
371                         else: return '(%s)'%v
372                 else: return v
373
374         def get_all_params(self, type):
375                 """
376                 Get all the params from the flowgraph that have the given type.
377                 @param type the specified type
378                 @return a list of params
379                 """
380                 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])