summaryrefslogtreecommitdiff
path: root/grc/src/platforms/gui/Param.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/src/platforms/gui/Param.py')
-rw-r--r--grc/src/platforms/gui/Param.py221
1 files changed, 221 insertions, 0 deletions
diff --git a/grc/src/platforms/gui/Param.py b/grc/src/platforms/gui/Param.py
new file mode 100644
index 000000000..f45d80bba
--- /dev/null
+++ b/grc/src/platforms/gui/Param.py
@@ -0,0 +1,221 @@
+"""
+Copyright 2007 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+"""
+
+import Utils
+from Element import Element
+import pygtk
+pygtk.require('2.0')
+import gtk
+import pango
+import gobject
+from Constants import PARAM_LABEL_FONT, PARAM_FONT
+from os import path
+
+######################################################################################################
+# gtk objects for handling input
+######################################################################################################
+
+class InputParam(gtk.HBox):
+ """The base class for an input parameter inside the input parameters dialog."""
+
+ def __init__(self, param, _handle_changed):
+ gtk.HBox.__init__(self)
+ self.param = param
+ self._handle_changed = _handle_changed
+ self.label = gtk.Label('') #no label, markup is added by set_markup
+ self.label.set_size_request(150, -1)
+ self.pack_start(self.label, False)
+ self.set_markup = lambda m: self.label.set_markup(m)
+ self.tp = None
+
+class EntryParam(InputParam):
+ """Provide an entry box for strings and numbers."""
+
+ def __init__(self, *args, **kwargs):
+ InputParam.__init__(self, *args, **kwargs)
+ self.entry = input = gtk.Entry()
+ input.set_text(self.param.get_value())
+ input.connect('changed', self._handle_changed)
+ self.pack_start(input, True)
+ self.get_text = input.get_text
+ #tool tip
+ self.tp = gtk.Tooltips()
+ self.tp.set_tip(self.entry, '')
+ self.tp.enable()
+
+class FileParam(EntryParam):
+ """Provide an entry box for filename and a button to browse for a file."""
+
+ def __init__(self, *args, **kwargs):
+ EntryParam.__init__(self, *args, **kwargs)
+ input = gtk.Button('...')
+ input.connect('clicked', self._handle_clicked)
+ self.pack_start(input, False)
+
+ def _handle_clicked(self, widget=None):
+ """
+ If the button was clicked, open a file dialog in open/save format.
+ Replace the text in the entry with the new filename from the file dialog.
+ """
+ file_path = self.param.is_valid() and self.param.evaluate() or ''
+ #bad file paths will be redirected to default
+ if not path.exists(path.dirname(file_path)): file_path = DEFAULT_FILE_PATH
+ if self.param.get_type() == 'file_open':
+ file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
+ gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
+ elif self.param.get_type() == 'file_save':
+ file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
+ gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
+ file_dialog.set_do_overwrite_confirmation(True)
+ file_dialog.set_current_name(path.basename(file_path)) #show the current filename
+ file_dialog.set_current_folder(path.dirname(file_path)) #current directory
+ file_dialog.set_select_multiple(False)
+ file_dialog.set_local_only(True)
+ if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
+ file_path = file_dialog.get_filename() #get the file path
+ self.entry.set_text(file_path)
+ self._handle_changed()
+ file_dialog.destroy() #destroy the dialog
+
+class EnumParam(InputParam):
+ """Provide an entry box for Enum types with a drop down menu."""
+
+ def __init__(self, *args, **kwargs):
+ InputParam.__init__(self, *args, **kwargs)
+ input = gtk.ComboBox(gtk.ListStore(gobject.TYPE_STRING))
+ cell = gtk.CellRendererText()
+ input.pack_start(cell, True)
+ input.add_attribute(cell, 'text', 0)
+ for option in self.param.get_options(): input.append_text(option.get_name())
+ input.set_active(int(self.param.get_option_keys().index(self.param.get_value())))
+ input.connect("changed", self._handle_changed)
+ self.pack_start(input, False)
+ self.get_text = lambda: str(input.get_active()) #the get text parses the selected index to a string
+
+######################################################################################################
+# A Flow Graph Parameter
+######################################################################################################
+
+class Param(Element):
+ """The graphical parameter."""
+
+ def update(self):
+ """
+ Called when an external change occurs.
+ Update the graphical input by calling the change handler.
+ """
+ if hasattr(self, 'input'): self._handle_changed()
+
+ def get_input_object(self, callback=None):
+ """
+ Get the graphical gtk class to represent this parameter.
+ Create the input object with this data type and the handle changed method.
+ @param callback a function of one argument(this param) to be called from the change handler
+ @return gtk input object
+ """
+ self.callback = callback
+ if self.is_enum(): input = EnumParam
+ elif self.get_type() in ('file_open', 'file_save'): input = FileParam
+ else: input = EntryParam
+ self.input = input(self, self._handle_changed)
+ if not callback: self.update()
+ return self.input
+
+ def _handle_changed(self, widget=None):
+ """
+ When the input changes, write the inputs to the data type.
+ Finish by calling the exteral callback.
+ """
+ value = self.input.get_text()
+ if self.is_enum(): value = self.get_option_keys()[int(value)]
+ self.set_value(value)
+ #set the markup on the label, red for errors in corresponding data type.
+ name = '<span font_desc="%s">%s</span>'%(PARAM_LABEL_FONT, Utils.xml_encode(self.get_name()))
+ #special markups if param is involved in a callback
+ if hasattr(self.get_parent(), 'get_callbacks') and \
+ filter(lambda c: self.get_key() in c, self.get_parent()._callbacks):
+ name = '<span underline="low">%s</span>'%name
+ if not self.is_valid():
+ self.input.set_markup('<span foreground="red">%s</span>'%name)
+ tip = '- ' + '\n- '.join(self.get_error_messages())
+ else:
+ self.input.set_markup(name)
+ tip = self.evaluate()
+ #hide/show
+ if self.get_hide() == 'all': self.input.hide_all()
+ else: self.input.show_all()
+ #set the tooltip
+ if self.input.tp: self.input.tp.set_tip(self.input.entry, str(tip))
+ #execute the external callback
+ if self.callback: self.callback(self)
+
+ def get_markup(self):
+ """
+ Create a markup to display the Param as a label on the SignalBlock.
+ If the data type is an Enum type, use the cname of the Enum's current choice.
+ Otherwise, use parsed the data type and use its string representation.
+ If the data type is not valid, use a red foreground color.
+ @return pango markup string
+ """
+ ###########################################################################
+ # display logic for numbers
+ ###########################################################################
+ def float_to_str(var):
+ if var-int(var) == 0: return '%d'%int(var)
+ if var*10-int(var*10) == 0: return '%.1f'%var
+ if var*100-int(var*100) == 0: return '%.2f'%var
+ if var*1000-int(var*1000) == 0: return '%.3f'%var
+ else: return '%.3g'%var
+ def to_str(var):
+ if isinstance(var, str): return var
+ elif isinstance(var, complex):
+ if var.imag == var.real == 0: return '0' #value is zero
+ elif var.imag == 0: return '%s'%float_to_str(var.real) #value is real
+ elif var.real == 0: return '%sj'%float_to_str(var.imag) #value is imaginary
+ elif var.imag < 0: return '%s-%sj'%(float_to_str(var.real), float_to_str(var.imag*-1))
+ else: return '%s+%sj'%(float_to_str(var.real), float_to_str(var.imag))
+ elif isinstance(var, float): return float_to_str(var)
+ elif isinstance(var, int): return '%d'%var
+ else: return str(var)
+ ###########################################################################
+ if self.is_valid():
+ data = self.evaluate()
+ t = self.get_type()
+ if self.is_enum():
+ dt_str = self.get_option(self.get_value()).get_name()
+ elif isinstance(data, (list, tuple, set)): #vector types
+ dt_str = ', '.join(map(to_str, data))
+ else: dt_str = to_str(data) #other types
+ #truncate
+ max_len = max(42 - len(self.get_name()), 3)
+ if len(dt_str) > max_len:
+ dt_str = dt_str[:max_len-3] + '...'
+ return '<b>%s:</b> %s'%(Utils.xml_encode(self.get_name()), Utils.xml_encode(dt_str))
+ else: return '<span foreground="red"><b>%s:</b> error</span>'%Utils.xml_encode(self.get_name())
+
+ def get_layout(self):
+ """
+ Create a layout based on the current markup.
+ @return the pango layout
+ """
+ layout = gtk.DrawingArea().create_pango_layout('')
+ layout.set_markup(self.get_markup())
+ desc = pango.FontDescription(PARAM_FONT)
+ layout.set_font_description(desc)
+ return layout