added include <cstdio> statements in several files to make it compatible with g+...
[debian/gnuradio] / grc / src / platforms / 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 utils 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
30 class FileParam(EntryParam):
31         """Provide an entry box for filename and a button to browse for a file."""
32
33         def __init__(self, *args, **kwargs):
34                 EntryParam.__init__(self, *args, **kwargs)
35                 input = gtk.Button('...')
36                 input.connect('clicked', self._handle_clicked)
37                 self.pack_start(input, False)
38
39         def _handle_clicked(self, widget=None):
40                 """
41                 If the button was clicked, open a file dialog in open/save format.
42                 Replace the text in the entry with the new filename from the file dialog.
43                 """
44                 #get the paths
45                 file_path = self.param.is_valid() and self.param.evaluate() or ''
46                 (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
47                 if not os.path.exists(dirname): dirname = os.getcwd() #fix bad paths
48                 #build the dialog
49                 if self.param.get_type() == 'file_open':
50                         file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
51                                 gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
52                 elif self.param.get_type() == 'file_save':
53                         file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
54                                 gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
55                         file_dialog.set_do_overwrite_confirmation(True)
56                         file_dialog.set_current_name(basename) #show the current filename
57                 file_dialog.set_current_folder(dirname) #current directory
58                 file_dialog.set_select_multiple(False)
59                 file_dialog.set_local_only(True)
60                 if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
61                         file_path = file_dialog.get_filename() #get the file path
62                         self.entry.set_text(file_path)
63                         self._handle_changed()
64                 file_dialog.destroy() #destroy the dialog
65
66 #blacklist certain ids, its not complete, but should help
67 import __builtin__
68 ID_BLACKLIST = ['options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + dir(__builtin__)
69 #define types, native python + numpy
70 VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
71 COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
72 REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
73 INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
74         numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
75 #cast to tuple for isinstance, concat subtypes
76 COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
77 REAL_TYPES = tuple(REAL_TYPES + INT_TYPES)
78 INT_TYPES = tuple(INT_TYPES)
79
80 class Param(_Param):
81
82         _init = False
83         _hostage_cells = list()
84
85         ##possible param types
86         TYPES = _Param.TYPES + [
87                 'complex', 'real', 'int',
88                 'complex_vector', 'real_vector', 'int_vector',
89                 'hex', 'string',
90                 'file_open', 'file_save',
91                 'id',
92                 'grid_pos', 'import',
93         ]
94
95         def __repr__(self):
96                 """
97                 Get the repr (nice string format) for this param.
98                 @return the string representation
99                 """
100                 if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name()
101                 ##################################################
102                 # display logic for numbers
103                 ##################################################
104                 def num_to_str(num):
105                         if isinstance(num, COMPLEX_TYPES):
106                                 num = complex(num) #cast to python complex
107                                 if num == 0: return '0' #value is zero
108                                 elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real
109                                 elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary
110                                 elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag)))
111                                 else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag))
112                         else: return str(num)
113                 ##################################################
114                 # split up formatting by type
115                 ##################################################
116                 truncate = 0 #default center truncate
117                 max_len = max(27 - len(self.get_name()), 3)
118                 e = self.evaluate()
119                 t = self.get_type()
120                 if isinstance(e, COMPLEX_TYPES): dt_str = num_to_str(e)
121                 elif isinstance(e, VECTOR_TYPES): #vector types
122                         if len(e) > 8:
123                                 dt_str = self.get_value() #large vectors use code
124                                 truncate = 1
125                         else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval
126                 elif t in ('file_open', 'file_save'):
127                         dt_str = self.get_value()
128                         truncate = -1
129                 else: dt_str = str(e) #other types
130                 ##################################################
131                 # truncate
132                 ##################################################
133                 if len(dt_str) > max_len:
134                         if truncate < 0: #front truncate
135                                 dt_str = '...' + dt_str[3-max_len:]
136                         elif truncate == 0: #center truncate
137                                 dt_str = dt_str[:max_len/2 -3] + '...' + dt_str[-max_len/2:]
138                         elif truncate > 0: #rear truncate
139                                 dt_str = dt_str[:max_len-3] + '...'
140                 return dt_str
141
142         def get_input_class(self):
143                 if self.get_type() in ('file_open', 'file_save'): return FileParam
144                 return _Param.get_input_class(self)
145
146         def get_color(self):
147                 """
148                 Get the color that represents this param's type.
149                 @return a hex color code.
150                 """
151                 try:
152                         return {
153                                 #number types
154                                 'complex': Constants.COMPLEX_COLOR_SPEC,
155                                 'real': Constants.FLOAT_COLOR_SPEC,
156                                 'int': Constants.INT_COLOR_SPEC,
157                                 #vector types
158                                 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
159                                 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
160                                 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
161                                 #special
162                                 'hex': Constants.INT_COLOR_SPEC,
163                                 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
164                                 'id': Constants.ID_COLOR_SPEC,
165                                 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
166                         }[self.get_type()]
167                 except: return _Param.get_color(self)
168
169         def get_hide(self):
170                 """
171                 Get the hide value from the base class.
172                 Hide the ID parameter for most blocks. Exceptions below.
173                 If the parameter controls a port type, vlen, or nports, return part.
174                 If the parameter is an empty grid position, return part.
175                 These parameters are redundant to display in the flow graph view.
176                 @return hide the hide property string
177                 """
178                 hide = _Param.get_hide(self)
179                 if hide: return hide
180                 #hide ID in non variable blocks
181                 if self.get_key() == 'id' and self.get_parent().get_key() not in (
182                         'variable', 'variable_slider', 'variable_chooser', 'variable_text_box', 'parameter', 'options'
183                 ): return 'part'
184                 #hide port controllers for type and nports
185                 if self.get_key() in ' '.join(map(
186                         lambda p: ' '.join([p._type, p._nports]), self.get_parent().get_ports())
187                 ): return 'part'
188                 #hide port controllers for vlen, when == 1
189                 if self.get_key() in ' '.join(map(
190                         lambda p: p._vlen, self.get_parent().get_ports())
191                 ):
192                         try:
193                                 assert int(self.evaluate()) == 1
194                                 return 'part'
195                         except: pass
196                 #hide empty grid positions
197                 if self.get_key() == 'grid_pos' and not self.get_value(): return 'part'
198                 return hide
199
200         def evaluate(self):
201                 """
202                 Evaluate the value.
203                 @return evaluated type
204                 """
205                 self._lisitify_flag = False
206                 self._stringify_flag = False
207                 self._hostage_cells = list()
208                 def eval_string(v):
209                         try:
210                                 e = self.get_parent().get_parent().evaluate(v)
211                                 assert isinstance(e, str)
212                                 return e
213                         except:
214                                 self._stringify_flag = True
215                                 return v
216                 t = self.get_type()
217                 v = self.get_value()
218                 #########################
219                 # Enum Type
220                 #########################
221                 if self.is_enum(): return v
222                 #########################
223                 # Numeric Types
224                 #########################
225                 elif t in ('raw', 'complex', 'real', 'int', 'complex_vector', 'real_vector', 'int_vector', 'hex'):
226                         #raise exception if python cannot evaluate this value
227                         try: e = self.get_parent().get_parent().evaluate(v)
228                         except Exception, e:
229                                 self._add_error_message('Value "%s" cannot be evaluated: %s'%(v, e))
230                                 raise Exception
231                         #raise an exception if the data is invalid
232                         if t == 'raw': return e
233                         elif t == 'complex':
234                                 try: assert(isinstance(e, COMPLEX_TYPES))
235                                 except AssertionError:
236                                         self._add_error_message('Expression "%s" is invalid for type complex.'%str(e))
237                                         raise Exception
238                                 return e
239                         elif t == 'real':
240                                 try: assert(isinstance(e, REAL_TYPES))
241                                 except AssertionError:
242                                         self._add_error_message('Expression "%s" is invalid for type real.'%str(e))
243                                         raise Exception
244                                 return e
245                         elif t == 'int':
246                                 try: assert(isinstance(e, INT_TYPES))
247                                 except AssertionError:
248                                         self._add_error_message('Expression "%s" is invalid for type integer.'%str(e))
249                                         raise Exception
250                                 return e
251                         #########################
252                         # Numeric Vector Types
253                         #########################
254                         elif t == 'complex_vector':
255                                 if not isinstance(e, VECTOR_TYPES):
256                                         self._lisitify_flag = True
257                                         e = [e]
258                                 try:
259                                         for ei in e:
260                                                 assert(isinstance(ei, COMPLEX_TYPES))
261                                 except AssertionError:
262                                         self._add_error_message('Expression "%s" is invalid for type complex vector.'%str(e))
263                                         raise Exception
264                                 return e
265                         elif t == 'real_vector':
266                                 if not isinstance(e, VECTOR_TYPES):
267                                         self._lisitify_flag = True
268                                         e = [e]
269                                 try:
270                                         for ei in e:
271                                                 assert(isinstance(ei, REAL_TYPES))
272                                 except AssertionError:
273                                         self._add_error_message('Expression "%s" is invalid for type real vector.'%str(e))
274                                         raise Exception
275                                 return e
276                         elif t == 'int_vector':
277                                 if not isinstance(e, VECTOR_TYPES):
278                                         self._lisitify_flag = True
279                                         e = [e]
280                                 try:
281                                         for ei in e:
282                                                 assert(isinstance(ei, INT_TYPES))
283                                 except AssertionError:
284                                         self._add_error_message('Expression "%s" is invalid for type integer vector.'%str(e))
285                                         raise Exception
286                                 return e
287                         elif t == 'hex':
288                                 return hex(e)
289                         else: raise TypeError, 'Type "%s" not handled'%t
290                 #########################
291                 # String Types
292                 #########################
293                 elif t in ('string', 'file_open', 'file_save'):
294                         #do not check if file/directory exists, that is a runtime issue
295                         e = eval_string(v)
296                         return str(e)
297                 #########################
298                 # Unique ID Type
299                 #########################
300                 elif t == 'id':
301                         #can python use this as a variable?
302                         try:
303                                 assert(len(v) > 0)
304                                 assert(v[0].isalpha())
305                                 for c in v: assert(c.isalnum() or c in ('_',))
306                         except AssertionError:
307                                 self._add_error_message('ID "%s" must be alpha-numeric or underscored, and begin with a letter.'%v)
308                                 raise Exception
309                         params = self.get_all_params('id')
310                         keys = [param.get_value() for param in params]
311                         try: assert keys.count(v) <= 1 #id should only appear once, or zero times if block is disabled
312                         except:
313                                 self._add_error_message('ID "%s" is not unique.'%v)
314                                 raise Exception
315                         try: assert v not in ID_BLACKLIST
316                         except:
317                                 self._add_error_message('ID "%s" is blacklisted.'%v)
318                                 raise Exception
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:
330                                 self._add_error_message('A grid position must be a list of 4 integers.')
331                                 raise Exception
332                         row, col, row_span, col_span = e
333                         #check row, col
334                         try: assert(row >= 0 and col >= 0)
335                         except AssertionError:
336                                 self._add_error_message('Row and column must be non-negative.')
337                                 raise Exception
338                         #check row span, col span
339                         try: assert(row_span > 0 and col_span > 0)
340                         except AssertionError:
341                                 self._add_error_message('Row and column span must be greater than zero.')
342                                 raise Exception
343                         #calculate hostage cells
344                         for r in range(row_span):
345                                 for c in range(col_span):
346                                         self._hostage_cells.append((row+r, col+c))
347                         #avoid collisions
348                         params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
349                         for param in params:
350                                 for cell in param._hostage_cells:
351                                         if cell in self._hostage_cells:
352                                                 self._add_error_message('Another graphical element is using cell "%s".'%str(cell))
353                                                 raise Exception
354                         return e
355                 #########################
356                 # Import Type
357                 #########################
358                 elif t == 'import':
359                         n = dict() #new namespace
360                         try: exec v in n
361                         except ImportError:
362                                 self._add_error_message('Import "%s" failed.'%v)
363                                 raise Exception
364                         except Exception:
365                                 self._add_error_message('Bad import syntax: "%s".'%v)
366                                 raise Exception
367                         return filter(lambda k: str(k) != '__builtins__', n.keys())
368                 #########################
369                 else: raise TypeError, 'Type "%s" not handled'%t
370
371         def to_code(self):
372                 """
373                 Convert the value to code.
374                 @return a string representing the code
375                 """
376                 #run init tasks in evaluate
377                 #such as setting flags
378                 if not self._init:
379                         self.evaluate()
380                         self._init = True
381                 v = self.get_value()
382                 t = self.get_type()
383                 if t in ('string', 'file_open', 'file_save'): #string types
384                         if self._stringify_flag:
385                                 return '"%s"'%v.replace('"', '\"')
386                         else:
387                                 return v
388                 elif t in ('complex_vector', 'real_vector', 'int_vector'): #vector types
389                         if self._lisitify_flag:
390                                 return '(%s, )'%v
391                         else:
392                                 return '(%s)'%v
393                 else:
394                         return v
395
396         def get_all_params(self, type):
397                 """
398                 Get all the params from the flowgraph that have the given type.
399                 @param type the specified type
400                 @return a list of params
401                 """
402                 return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])