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
21 from Element import Element
27 from Constants import PARAM_LABEL_FONT, PARAM_FONT
30 ######################################################################################################
31 # gtk objects for handling input
32 ######################################################################################################
34 class InputParam(gtk.HBox):
35 """The base class for an input parameter inside the input parameters dialog."""
37 def __init__(self, param, _handle_changed):
38 gtk.HBox.__init__(self)
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)
47 class EntryParam(InputParam):
48 """Provide an entry box for strings and numbers."""
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
58 self.tp = gtk.Tooltips()
59 self.tp.set_tip(self.entry, '')
62 class FileParam(EntryParam):
63 """Provide an entry box for filename and a button to browse for a file."""
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)
71 def _handle_clicked(self, widget=None):
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.
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
96 class EnumParam(InputParam):
97 """Provide an entry box for Enum types with a drop down menu."""
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
111 ######################################################################################################
112 # A Flow Graph Parameter
113 ######################################################################################################
115 class Param(Element):
116 """The graphical parameter."""
120 Called when an external change occurs.
121 Update the graphical input by calling the change handler.
123 if hasattr(self, 'input'): self._handle_changed()
125 def get_input_object(self, callback=None):
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
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()
140 def _handle_changed(self, widget=None):
142 When the input changes, write the inputs to the data type.
143 Finish by calling the exteral callback.
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())
158 self.input.set_markup(name)
159 tip = self.evaluate()
161 if self.get_hide() == 'all': self.input.hide_all()
162 else: self.input.show_all()
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)
168 def get_markup(self):
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
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
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 ###########################################################################
198 data = self.evaluate()
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
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())
212 def get_layout(self):
214 Create a layout based on the current markup.
215 @return the pango layout
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)