diff options
Diffstat (limited to 'gr-wxgui/src/python/form.py')
-rw-r--r-- | gr-wxgui/src/python/form.py | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py new file mode 100644 index 000000000..0442e49c8 --- /dev/null +++ b/gr-wxgui/src/python/form.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python +# +# Copyright 2005 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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 3, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import wx +from gnuradio import eng_notation + +# ---------------------------------------------------------------- +# Wrappers for certain widgets +# ---------------------------------------------------------------- + +def button_with_callback(parent, label, callback): + new_id = wx.NewId() + btn = wx.Button(parent, new_id, label) + wx.EVT_BUTTON(parent, new_id, lambda evt: callback()) + return btn + + +# ---------------------------------------------------------------- +# Format converters +# ---------------------------------------------------------------- + +class abstract_converter(object): + def value_to_prim(self, v): + """ + Convert from user specified value to value acceptable to underlying primitive. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def prim_to_value(self, s): + """ + Convert from underlying primitive value to user specified value. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def help(self): + return "Any string is acceptable" + +class identity_converter(abstract_converter): + def value_to_prim(self,v): + return v + def prim_to_value(self, s): + return s + +class int_converter(abstract_converter): + def value_to_prim(self, v): + return str(v) + def prim_to_value(self, s): + return int(s, 0) + def help(self): + return "Enter an integer. Leading 0x indicates hex" + +class float_converter(abstract_converter): + def value_to_prim(self, v): + return eng_notation.num_to_str(v) + def prim_to_value(self, s): + return eng_notation.str_to_num(s) + def help(self): + return "Enter a float with optional scale suffix. E.g., 100.1M" + + +# ---------------------------------------------------------------- +# Various types of data entry fields +# ---------------------------------------------------------------- + +class field(object): + """ + A field in a form. + """ + def __init__(self, converter, value): + self.converter = converter + if value is not None: + self.set_value(value) + + def set_value(self, v): + self._set_prim_value(self.converter.value_to_prim(v)) + + def get_value(self): + return self.converter.prim_to_value(self._get_prim_value()) + + def get_value_with_check(self): + """ + Returns (value, error_msg), where error_msg is not None if there was problem + """ + try: + return (self.get_value(), None) + except: + return (None, self._error_msg()) + + def _set_prim_value(self, v): + raise NotImplementedError + + def _get_prim_value(self): + raise NotImplementedError + + def _pair_with_label(self, widget, parent=None, sizer=None, label=None, weight=1): + self.label = label + if label is None: + sizer.Add (widget, weight, wx.EXPAND) + return widget + elif 0: + hbox = wx.BoxSizer(wx.HORIZONTAL) + label_widget = wx.StaticText(parent, -1, label + ': ') + hbox.Add(label_widget, 0, wx.EXPAND) + hbox.Add(widget, 1, wx.EXPAND) + sizer.Add(hbox, weight, wx.EXPAND) + return widget + else: + label_widget = wx.StaticText(parent, -1, label + ': ') + sizer.Add(label_widget, 0, wx.EXPAND) + sizer.Add(widget, weight, wx.EXPAND) + return widget + + def _error_msg(self): + prefix = '' + if self.label: + prefix = self.label + ': ' + return "%s%s is invalid. %s" % (prefix, self._get_prim_value(), + self.converter.help()) + +# static (display-only) text fields + +class static_text_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), weight=0): + self.f = self._pair_with_label(wx.StaticText(parent, -1, ""), + parent=parent, sizer=sizer, label=label, weight=weight) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetLabel() + + def _set_prim_value(self, v): + self.f.SetLabel(v) + + +class static_int_field(static_text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0): + static_text_field.__init__(self, parent, sizer, label, value, int_converter(), weight) + +class static_float_field(static_text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0): + static_text_field.__init__(self, parent, sizer, label, value, float_converter(), weight) + + +# editable text fields + +class text_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, weight=1): + style = 0 + if callback: + style = wx.TE_PROCESS_ENTER + + new_id = wx.NewId() + w = wx.TextCtrl(parent, new_id, "", style=style) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + if callback: + wx.EVT_TEXT_ENTER(w, new_id, lambda evt: callback()) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetValue() + + def _set_prim_value(self, v): + self.f.SetValue(v) + + +class int_field(text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + callback=None, weight=1): + text_field.__init__(self, parent, sizer, label, value, int_converter(), callback, weight) + +class float_field(text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + callback=None, weight=1): + text_field.__init__(self, parent, sizer, label, value, float_converter(), callback, weight) + +# other fields + +class slider_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, min=0, max=100, weight=1): + new_id = wx.NewId() + w = wx.Slider(parent, new_id, (max+min)/2, min, max, + size=wx.Size(250, -1), style=wx.SL_HORIZONTAL | wx.SL_LABELS) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + if callback: + wx.EVT_COMMAND_SCROLL(w, new_id, lambda evt: callback(evt.GetInt())) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetValue() + + def _set_prim_value(self, v): + self.f.SetValue(int(v)) + +class quantized_slider_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, range=None, weight=1): + if not isinstance(range, (tuple, list)) or len(range) != 3: + raise ValueError, range + + self.min = range[0] + self.max = range[1] + self.step_size = float(range[2]) + nsteps = int((self.max-self.min)/self.step_size) + + new_id = wx.NewId() + w = wx.Slider(parent, new_id, 0, 0, nsteps, + size=wx.Size(250, -1), style=wx.SL_HORIZONTAL) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + if callback: + wx.EVT_COMMAND_SCROLL(w, new_id, + lambda evt: callback(self._map_out(evt.GetInt()))) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self._map_out(self.f.GetValue()) + + def _set_prim_value(self, v): + self.f.SetValue(self._map_in(v)) + + def _map_in(self, x): + return int((x-self.min) / self.step_size) + + def _map_out(self, x): + return x * self.step_size + self.min + +class checkbox_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, weight=1): + new_id = wx.NewId() + w = wx.CheckBox(parent, new_id, label, style=wx.CHK_2STATE) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight) + if callback: + wx.EVT_CHECKBOX(w, new_id, lambda evt: callback(evt.GetInt())) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetValue() + + def _set_prim_value(self, v): + self.f.SetValue(int(v)) + + +class radiobox_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, weight=1, + choices=None, major_dimension=1, specify_rows=False): + new_id = wx.NewId() + + if specify_rows: + style=wx.RA_SPECIFY_ROWS | wx.RA_HORIZONTAL + else: + style=wx.RA_SPECIFY_COLS | wx.RA_HORIZONTAL + + w = wx.RadioBox(parent, new_id, label=label, style=style, majorDimension=major_dimension, + choices=choices) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight) + if callback: + wx.EVT_RADIOBOX(w, new_id, lambda evt: callback(evt.GetString())) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetStringSelection() + + def _set_prim_value(self, v): + self.f.SetStringSelection(str(v)) + +# ---------------------------------------------------------------- +# the form class +# ---------------------------------------------------------------- + +class form(dict): + def __init__(self): + dict.__init__(self) + + def check_input_for_errors(self): + """ + Returns list of error messages if there's trouble, + else empty list. + """ + vals = [f.get_value_with_check() for f in self.values()] + return [t[1] for t in vals if t[1] is not None] + + def get_key_vals(self): + d = {} + for (key, f) in self.items(): + d[key] = f.get_value() + return d + + + def _nop(*args): pass + + def check_input_and_call(self, callback, status_handler=_nop): + """ + Return a function that checks the form for errors, and then if it's OK, + invokes the user specified callback, passing it the form key/value dictionary. + status_handler is called with a string indicating results. + """ + def doit_callback(*ignore): + errors = self.check_input_for_errors() + if errors: + status_handler(errors[0]) + #print '\n'.join(tuple(errors)) + else: + kv = self.get_key_vals() + if callback(kv): + status_handler("OK") + else: + status_handler("Failed") + + return doit_callback + + + +# ---------------------------------------------------------------- +# Stand-alone example code +# ---------------------------------------------------------------- + +import sys +from gnuradio.wxgui import stdgui2 + +class demo_app_flow_graph (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + self.frame = frame + self.panel = panel + + def _print_kv(kv): + print "kv =", kv + return True + + self.form = form() + + self.form['static1'] = \ + static_text_field(parent=panel, sizer=vbox, + label="Static Text", + value="The Static Value") + + self.form['text1'] = \ + text_field(parent=panel, sizer=vbox, + label="TextCtrl", + value="The Editable Value") + + self.form['int1'] = \ + int_field(parent=panel, sizer=vbox, + label="Int Field", + value=1234) + + self.form['float1'] = \ + float_field(parent=panel, sizer=vbox, + label="Float Field", + value=3.14159) + + self.doit = button_with_callback( + panel, "Do It!", + self.form.check_input_and_call(_print_kv, self._set_status_msg)) + + vbox.Add(self.doit, 0, wx.CENTER) + + def _set_status_msg(self, msg): + self.frame.GetStatusBar().SetStatusText(msg, 0) + + +def main (): + app = stdgui2.stdapp(demo_app_flow_graph, "wxgui form demo", nstatus=1) + app.MainLoop () + +if __name__ == '__main__': + main () |