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