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