Merged r9481:9518 on jblum/grc_reorganize into trunk. Reorganized grc source under...
[debian/gnuradio] / grc / src / platforms / gui / Param.py
1 """
2 Copyright 2007 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 Utils
21 from Element import Element
22 import pygtk
23 pygtk.require('2.0')
24 import gtk
25 import pango
26 import gobject
27 from Constants import PARAM_LABEL_FONT, PARAM_FONT
28 from os import path
29
30 ######################################################################################################
31 # gtk objects for handling input
32 ######################################################################################################
33
34 class InputParam(gtk.HBox):
35         """The base class for an input parameter inside the input parameters dialog."""
36
37         def __init__(self, param, _handle_changed):
38                 gtk.HBox.__init__(self)
39                 self.param = param
40                 self._handle_changed = _handle_changed
41                 self.label = gtk.Label('') #no label, markup is added by set_markup
42                 self.label.set_size_request(150, -1)
43                 self.pack_start(self.label, False)
44                 self.set_markup = lambda m: self.label.set_markup(m)
45                 self.tp = None
46
47 class EntryParam(InputParam):
48         """Provide an entry box for strings and numbers."""
49
50         def __init__(self, *args, **kwargs):
51                 InputParam.__init__(self, *args, **kwargs)
52                 self.entry = input = gtk.Entry()
53                 input.set_text(self.param.get_value())
54                 input.connect('changed', self._handle_changed)
55                 self.pack_start(input, True)
56                 self.get_text = input.get_text
57                 #tool tip
58                 self.tp = gtk.Tooltips()
59                 self.tp.set_tip(self.entry, '')
60                 self.tp.enable()
61
62 class FileParam(EntryParam):
63         """Provide an entry box for filename and a button to browse for a file."""
64
65         def __init__(self, *args, **kwargs):
66                 EntryParam.__init__(self, *args, **kwargs)
67                 input = gtk.Button('...')
68                 input.connect('clicked', self._handle_clicked)
69                 self.pack_start(input, False)
70
71         def _handle_clicked(self, widget=None):
72                 """
73                 If the button was clicked, open a file dialog in open/save format.
74                 Replace the text in the entry with the new filename from the file dialog.
75                 """
76                 file_path = self.param.is_valid() and self.param.evaluate() or ''
77                 #bad file paths will be redirected to default
78                 if not path.exists(path.dirname(file_path)): file_path = DEFAULT_FILE_PATH
79                 if self.param.get_type() == 'file_open':
80                         file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
81                                 gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
82                 elif self.param.get_type() == 'file_save':
83                         file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
84                                 gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
85                         file_dialog.set_do_overwrite_confirmation(True)
86                         file_dialog.set_current_name(path.basename(file_path)) #show the current filename
87                 file_dialog.set_current_folder(path.dirname(file_path)) #current directory
88                 file_dialog.set_select_multiple(False)
89                 file_dialog.set_local_only(True)
90                 if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
91                         file_path = file_dialog.get_filename() #get the file path
92                         self.entry.set_text(file_path)
93                         self._handle_changed()
94                 file_dialog.destroy() #destroy the dialog
95
96 class EnumParam(InputParam):
97         """Provide an entry box for Enum types with a drop down menu."""
98
99         def __init__(self, *args, **kwargs):
100                 InputParam.__init__(self, *args, **kwargs)
101                 input = gtk.ComboBox(gtk.ListStore(gobject.TYPE_STRING))
102                 cell = gtk.CellRendererText()
103                 input.pack_start(cell, True)
104                 input.add_attribute(cell, 'text', 0)
105                 for option in self.param.get_options(): input.append_text(option.get_name())
106                 input.set_active(int(self.param.get_option_keys().index(self.param.get_value())))
107                 input.connect("changed", self._handle_changed)
108                 self.pack_start(input, False)
109                 self.get_text = lambda: str(input.get_active()) #the get text parses the selected index to a string
110
111 ######################################################################################################
112 # A Flow Graph Parameter
113 ######################################################################################################
114
115 class Param(Element):
116         """The graphical parameter."""
117
118         def update(self):
119                 """
120                 Called when an external change occurs.
121                 Update the graphical input by calling the change handler.
122                 """
123                 if hasattr(self, 'input'): self._handle_changed()
124
125         def get_input_object(self, callback=None):
126                 """
127                 Get the graphical gtk class to represent this parameter.
128                 Create the input object with this data type and the handle changed method.
129                 @param callback a function of one argument(this param) to be called from the change handler
130                 @return gtk input object
131                 """
132                 self.callback = callback
133                 if self.is_enum(): input = EnumParam
134                 elif self.get_type() in ('file_open', 'file_save'): input = FileParam
135                 else: input = EntryParam
136                 self.input = input(self, self._handle_changed)
137                 if not callback: self.update()
138                 return self.input
139
140         def _handle_changed(self, widget=None):
141                 """
142                 When the input changes, write the inputs to the data type.
143                 Finish by calling the exteral callback.
144                 """
145                 value = self.input.get_text()
146                 if self.is_enum(): value = self.get_option_keys()[int(value)]
147                 self.set_value(value)
148                 #set the markup on the label, red for errors in corresponding data type.
149                 name = '<span font_desc="%s">%s</span>'%(PARAM_LABEL_FONT, Utils.xml_encode(self.get_name()))
150                 #special markups if param is involved in a callback
151                 if hasattr(self.get_parent(), 'get_callbacks') and \
152                         filter(lambda c: self.get_key() in c, self.get_parent()._callbacks):
153                         name = '<span underline="low">%s</span>'%name
154                 if not self.is_valid():
155                         self.input.set_markup('<span foreground="red">%s</span>'%name)
156                         tip = '- ' + '\n- '.join(self.get_error_messages())
157                 else:
158                         self.input.set_markup(name)
159                         tip = self.evaluate()
160                 #hide/show
161                 if self.get_hide() == 'all': self.input.hide_all()
162                 else: self.input.show_all()
163                 #set the tooltip
164                 if self.input.tp: self.input.tp.set_tip(self.input.entry, str(tip))
165                 #execute the external callback
166                 if self.callback: self.callback(self)
167
168         def get_markup(self):
169                 """
170                 Create a markup to display the Param as a label on the SignalBlock.
171                 If the data type is an Enum type, use the cname of the Enum's current choice.
172                 Otherwise, use parsed the data type and use its string representation.
173                 If the data type is not valid, use a red foreground color.
174                 @return pango markup string
175                 """
176                 ###########################################################################
177                 # display logic for numbers
178                 ###########################################################################
179                 def float_to_str(var):
180                         if var-int(var) == 0: return '%d'%int(var)
181                         if var*10-int(var*10) == 0: return '%.1f'%var
182                         if var*100-int(var*100) == 0: return '%.2f'%var
183                         if var*1000-int(var*1000) == 0: return '%.3f'%var
184                         else: return '%.3g'%var
185                 def to_str(var):
186                         if isinstance(var, str): return var
187                         elif isinstance(var, complex):
188                                 if var.imag == var.real == 0: return '0' #value is zero
189                                 elif var.imag == 0: return '%s'%float_to_str(var.real) #value is real
190                                 elif var.real == 0: return '%sj'%float_to_str(var.imag) #value is imaginary
191                                 elif var.imag < 0: return '%s-%sj'%(float_to_str(var.real), float_to_str(var.imag*-1))
192                                 else: return '%s+%sj'%(float_to_str(var.real), float_to_str(var.imag))
193                         elif isinstance(var, float): return float_to_str(var)
194                         elif isinstance(var, int): return '%d'%var
195                         else: return str(var)
196                 ###########################################################################
197                 if self.is_valid():
198                         data = self.evaluate()
199                         t = self.get_type()
200                         if self.is_enum():
201                                 dt_str = self.get_option(self.get_value()).get_name()
202                         elif isinstance(data, (list, tuple, set)): #vector types
203                                 dt_str = ', '.join(map(to_str, data))
204                         else: dt_str = to_str(data)     #other types
205                         #truncate
206                         max_len = max(42 - len(self.get_name()), 3)
207                         if len(dt_str) > max_len:
208                                 dt_str = dt_str[:max_len-3] + '...'
209                         return '<b>%s:</b> %s'%(Utils.xml_encode(self.get_name()), Utils.xml_encode(dt_str))
210                 else: return '<span foreground="red"><b>%s:</b> error</span>'%Utils.xml_encode(self.get_name())
211
212         def get_layout(self):
213                 """
214                 Create a layout based on the current markup.
215                 @return the pango layout
216                 """
217                 layout = gtk.DrawingArea().create_pango_layout('')
218                 layout.set_markup(self.get_markup())
219                 desc = pango.FontDescription(PARAM_FONT)
220                 layout.set_font_description(desc)
221                 return layout