Updates to udp source/sink (select(), wait, cleanup)
[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 from .. base.Param import Param as _Param
21 from .. gui.Param import Param as _GUIParam
22 from .. gui.Param import EntryParam
23 import Constants
24 import numpy
25 import os
26 import pygtk
27 pygtk.require('2.0')
28 import gtk
29 from gnuradio import eng_notation
30 import re
31 from gnuradio import gr
32
33 _check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
34 _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
35
36 class FileParam(EntryParam):
37         """Provide an entry box for filename and a button to browse for a file."""
38
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)
44
45         def _handle_clicked(self, widget=None):
46                 """
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.
49                 """
50                 #get the paths
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
54                 #build the dialog
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
71
72 #blacklist certain ids, its not complete, but should help
73 import __builtin__
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)
86
87 class Param(_Param, _GUIParam):
88
89         def __init__(self, **kwargs):
90                 _Param.__init__(self, **kwargs)
91                 _GUIParam.__init__(self)
92                 self._init = False
93                 self._hostage_cells = list()
94
95         def get_types(self): return (
96                 'raw', 'enum',
97                 'complex', 'real', 'int',
98                 'complex_vector', 'real_vector', 'int_vector',
99                 'hex', 'string', 'bool',
100                 'file_open', 'file_save',
101                 'id', 'stream_id',
102                 'grid_pos', 'notebook',
103                 'import',
104         )
105
106         def __repr__(self):
107                 """
108                 Get the repr (nice string format) for this param.
109                 @return the string representation
110                 """
111                 if not self.is_valid(): return self.get_value()
112                 if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
113                 ##################################################
114                 # display logic for numbers
115                 ##################################################
116                 def num_to_str(num):
117                         if isinstance(num, COMPLEX_TYPES):
118                                 num = complex(num) #cast to python complex
119                                 if num == 0: return '0' #value is zero
120                                 elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
121                                 elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
122                                 elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
123                                 else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
124                         else: return str(num)
125                 ##################################################
126                 # split up formatting by type
127                 ##################################################
128                 truncate = 0 #default center truncate
129                 max_len = max(27 - len(self.get_name()), 3)
130                 e = self.get_evaluated()
131                 t = self.get_type()
132                 if isinstance(e, bool): return str(e)
133                 elif isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
134                 elif isinstance(e, VECTOR_TYPES): #vector types
135                         if len(e) > 8:
136                                 dt_str = self.get_value() #large vectors use code
137                                 truncate = 1
138                         else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
139                 elif t in ('file_open', 'file_save'):
140                         dt_str = self.get_value()
141                         truncate = -1
142                 else: dt_str = str(e) #other types
143                 ##################################################
144                 # truncate
145                 ##################################################
146                 if len(dt_str) > max_len:
147                         if truncate < 0: #front truncate
148                                 dt_str = '...' + dt_str[3-max_len:]
149                         elif truncate == 0: #center truncate
150                                 dt_str = dt_str[:max_len/2 -3] + '...' + dt_str[-max_len/2:]
151                         elif truncate > 0: #rear truncate
152                                 dt_str = dt_str[:max_len-3] + '...'
153                 return dt_str
154
155         def get_input(self, *args, **kwargs):
156                 if self.get_type() in ('file_open', 'file_save'): return FileParam(self, *args, **kwargs)
157                 return _GUIParam.get_input(self, *args, **kwargs)
158
159         def get_color(self):
160                 """
161                 Get the color that represents this param's type.
162                 @return a hex color code.
163                 """
164                 try:
165                         return {
166                                 #number types
167                                 'complex': Constants.COMPLEX_COLOR_SPEC,
168                                 'real': Constants.FLOAT_COLOR_SPEC,
169                                 'int': Constants.INT_COLOR_SPEC,
170                                 #vector types
171                                 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
172                                 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
173                                 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
174                                 #special
175                                 'bool': Constants.INT_COLOR_SPEC,
176                                 'hex': Constants.INT_COLOR_SPEC,
177                                 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
178                                 'id': Constants.ID_COLOR_SPEC,
179                                 'stream_id': Constants.ID_COLOR_SPEC,
180                                 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
181                                 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
182                                 'raw': Constants.WILDCARD_COLOR_SPEC,
183                         }[self.get_type()]
184                 except: return _Param.get_color(self)
185
186         def get_hide(self):
187                 """
188                 Get the hide value from the base class.
189                 Hide the ID parameter for most blocks. Exceptions below.
190                 If the parameter controls a port type, vlen, or nports, return part.
191                 If the parameter is an empty grid position, return part.
192                 These parameters are redundant to display in the flow graph view.
193                 @return hide the hide property string
194                 """
195                 hide = _Param.get_hide(self)
196                 if hide: return hide
197                 #hide ID in non variable blocks
198                 if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): return 'part'
199                 #hide port controllers for type and nports
200                 if self.get_key() in ' '.join(map(
201                         lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
202                 ): return 'part'
203                 #hide port controllers for vlen, when == 1
204                 if self.get_key() in ' '.join(map(
205                         lambda p: p._vlen, self.get_parent().get_ports())
206                 ):
207                         try:
208                                 assert int(self.get_evaluated()) == 1
209                                 return 'part'
210                         except: pass
211                 #hide empty grid positions
212                 if self.get_key() in ('grid_pos', 'notebook') and not self.get_value(): return 'part'
213                 return hide
214
215         def validate(self):
216                 """
217                 Validate the param.
218                 A test evaluation is performed
219                 """
220                 _Param.validate(self) #checks type
221                 self._evaluated = None
222                 try: self._evaluated = self.evaluate()
223                 except Exception, e: self.add_error_message(str(e))
224
225         def get_evaluated(self): return self._evaluated
226
227         def evaluate(self):
228                 """
229                 Evaluate the value.
230                 @return evaluated type
231                 """
232                 self._init = True
233                 self._lisitify_flag = False
234                 self._stringify_flag = False
235                 self._hostage_cells = list()
236                 def eval_string(v):
237                         try:
238                                 e = self.get_parent().get_parent().evaluate(v)
239                                 assert isinstance(e, str)
240                                 return e
241                         except:
242                                 self._stringify_flag = True
243                                 return v
244                 t = self.get_type()
245                 v = self.get_value()
246                 #########################
247                 # Enum Type
248                 #########################
249                 if self.is_enum(): return v
250                 #########################
251                 # Numeric Types
252                 #########################
253                 elif t in ('raw', 'complex', 'real', 'int', 'hex', 'bool'):
254                         #raise exception if python cannot evaluate this value
255                         try: e = self.get_parent().get_parent().evaluate(v)
256                         except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
257                         #raise an exception if the data is invalid
258                         if t == 'raw': return e
259                         elif t == 'complex':
260                                 try: assert isinstance(e, COMPLEX_TYPES)
261                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex.'%str(e)
262                                 return e
263                         elif t == 'real':
264                                 try: assert isinstance(e, REAL_TYPES)
265                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real.'%str(e)
266                                 return e
267                         elif t == 'int':
268                                 try: assert isinstance(e, INT_TYPES)
269                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer.'%str(e)
270                                 return e
271                         elif t == 'hex': return hex(e)
272                         elif t == 'bool':
273                                 try: assert isinstance(e, bool)
274                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type bool.'%str(e)
275                                 return e
276                         else: raise TypeError, 'Type "%s" not handled'%t
277                 #########################
278                 # Numeric Vector Types
279                 #########################
280                 elif t in ('complex_vector', 'real_vector', 'int_vector'):
281                         if not v: v = '()' #turn a blank string into an empty list, so it will eval
282                         #raise exception if python cannot evaluate this value
283                         try: e = self.get_parent().get_parent().evaluate(v)
284                         except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(v, e)
285                         #raise an exception if the data is invalid
286                         if t == 'complex_vector':
287                                 if not isinstance(e, VECTOR_TYPES):
288                                         self._lisitify_flag = True
289                                         e = [e]
290                                 try:
291                                         for ei in e: assert isinstance(ei, COMPLEX_TYPES)
292                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type complex vector.'%str(e)
293                                 return e
294                         elif t == 'real_vector':
295                                 if not isinstance(e, VECTOR_TYPES):
296                                         self._lisitify_flag = True
297                                         e = [e]
298                                 try:
299                                         for ei in e: assert isinstance(ei, REAL_TYPES)
300                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type real vector.'%str(e)
301                                 return e
302                         elif t == 'int_vector':
303                                 if not isinstance(e, VECTOR_TYPES):
304                                         self._lisitify_flag = True
305                                         e = [e]
306                                 try:
307                                         for ei in e: assert isinstance(ei, INT_TYPES)
308                                 except AssertionError: raise Exception, 'Expression "%s" is invalid for type integer vector.'%str(e)
309                                 return e
310                 #########################
311                 # String Types
312                 #########################
313                 elif t in ('string', 'file_open', 'file_save'):
314                         #do not check if file/directory exists, that is a runtime issue
315                         e = eval_string(v)
316                         return str(e)
317                 #########################
318                 # Unique ID Type
319                 #########################
320                 elif t == 'id':
321                         #can python use this as a variable?
322                         try: assert _check_id_matcher.match(v)
323                         except AssertionError: raise Exception, 'ID "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v
324                         ids = [param.get_value() for param in self.get_all_params(t)]
325                         try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
326                         except: raise Exception, 'ID "%s" is not unique.'%v
327                         try: assert v not in ID_BLACKLIST
328                         except: raise Exception, 'ID "%s" is blacklisted.'%v
329                         return v
330                 #########################
331                 # Stream ID Type
332                 #########################
333                 elif t == 'stream_id':
334                         #get a list of all stream ids used in the virtual sinks 
335                         ids = [param.get_value() for param in filter(
336                                 lambda p: p.get_parent().is_virtual_sink(),
337                                 self.get_all_params(t),
338                         )]
339                         #check that the virtual sink's stream id is unique
340                         if self.get_parent().is_virtual_sink():
341                                 try: assert ids.count(v) <= 1 #id should only appear once, or zero times if block is disabled
342                                 except: raise Exception, 'Stream ID "%s" is not unique.'%v
343                         #check that the virtual source's steam id is found
344                         if self.get_parent().is_virtual_source():
345                                 try: assert v in ids
346                                 except: raise Exception, 'Stream ID "%s" is not found.'%v
347                         return v
348                 #########################
349                 # Grid Position Type
350                 #########################
351                 elif t == 'grid_pos':
352                         if not v: return '' #allow for empty grid pos
353                         e = self.get_parent().get_parent().evaluate(v)
354                         try:
355                                 assert isinstance(e, (list, tuple)) and len(e) == 4
356                                 for ei in e: assert isinstance(ei, int)
357                         except AssertionError: raise Exception, 'A grid position must be a list of 4 integers.'
358                         row, col, row_span, col_span = e
359                         #check row, col
360                         try: assert row >= 0 and col >= 0
361                         except AssertionError: raise Exception, 'Row and column must be non-negative.'
362                         #check row span, col span
363                         try: assert row_span > 0 and col_span > 0
364                         except AssertionError: raise Exception, 'Row and column span must be greater than zero.'
365                         #get hostage cell parent
366                         try: my_parent = self.get_parent().get_param('notebook').evaluate()
367                         except: my_parent = ''
368                         #calculate hostage cells
369                         for r in range(row_span):
370                                 for c in range(col_span):
371                                         self._hostage_cells.append((my_parent, (row+r, col+c)))
372                         #avoid collisions
373                         params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
374                         for param in params:
375                                 for parent, cell in param._hostage_cells:
376                                         if (parent, cell) in self._hostage_cells:
377                                                 raise Exception, 'Another graphical element is using parent "%s", cell "%s".'%(str(parent), str(cell))
378                         return e
379                 #########################
380                 # Notebook Page Type
381                 #########################
382                 elif t == 'notebook':
383                         if not v: return '' #allow for empty notebook
384                         #get a list of all notebooks
385                         notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
386                         #check for notebook param syntax
387                         try: notebook_id, page_index = map(str.strip, v.split(','))
388                         except: raise Exception, 'Bad notebook page format.'
389                         #check that the notebook id is valid
390                         try: notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
391                         except: raise Exception, 'Notebook id "%s" is not an existing notebook id.'%notebook_id
392                         #check that page index exists
393                         try: assert int(page_index) in range(len(notebook_block.get_param('labels').evaluate()))
394                         except: raise Exception, 'Page index "%s" is not a valid index number.'%page_index
395                         return notebook_id, page_index
396                 #########################
397                 # Import Type
398                 #########################
399                 elif t == 'import':
400                         n = dict() #new namespace
401                         try: exec v in n
402                         except ImportError: raise Exception, 'Import "%s" failed.'%v
403                         except Exception: raise Exception, 'Bad import syntax: "%s".'%v
404                         return filter(lambda k: str(k) != '__builtins__', n.keys())
405                 #########################
406                 else: raise TypeError, 'Type "%s" not handled'%t
407
408         def to_code(self):
409                 """
410                 Convert the value to code.
411                 For string and list types, check the init flag, call evaluate().
412                 This ensures that evaluate() was called to set the xxxify_flags.
413                 @return a string representing the code
414                 """
415                 v = self.get_value()
416                 t = self.get_type()
417                 if t in ('string', 'file_open', 'file_save'): #string types
418                         if not self._init: self.evaluate()
419                         if self._stringify_flag: return '"%s"'%v.replace('"', '\"')
420                         else: return v
421                 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
422                         if not self._init: self.evaluate()
423                         if self._lisitify_flag: return '(%s, )'%v
424                         else: return '(%s)'%v
425                 else: return v
426
427         def get_all_params(self, type):
428                 """
429                 Get all the params from the flowgraph that have the given type.
430                 @param type the specified type
431                 @return a list of params
432                 """
433                 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])