diff options
Diffstat (limited to 'grc/src/grc_gnuradio')
-rw-r--r-- | grc/src/grc_gnuradio/wxgui/Makefile.am | 8 | ||||
-rw-r--r-- | grc/src/grc_gnuradio/wxgui/__init__.py | 8 | ||||
-rw-r--r-- | grc/src/grc_gnuradio/wxgui/callback_controls.py | 303 | ||||
-rw-r--r-- | grc/src/grc_gnuradio/wxgui/forms/__init__.py | 54 | ||||
-rw-r--r-- | grc/src/grc_gnuradio/wxgui/forms/converters.py | 143 | ||||
-rw-r--r-- | grc/src/grc_gnuradio/wxgui/forms/forms.py | 473 | ||||
-rw-r--r-- | grc/src/grc_gnuradio/wxgui/top_block_gui.py | 4 |
7 files changed, 679 insertions, 314 deletions
diff --git a/grc/src/grc_gnuradio/wxgui/Makefile.am b/grc/src/grc_gnuradio/wxgui/Makefile.am index 6f731f2cb..b82ca4c90 100644 --- a/grc/src/grc_gnuradio/wxgui/Makefile.am +++ b/grc/src/grc_gnuradio/wxgui/Makefile.am @@ -22,8 +22,12 @@ include $(top_srcdir)/grc/Makefile.inc ourpythondir = $(grc_gnuradio_prefix)/wxgui - ourpython_PYTHON = \ __init__.py \ - callback_controls.py \ top_block_gui.py + +oursubpythondir = $(grc_gnuradio_prefix)/wxgui/forms +oursubpython_PYTHON = \ + forms/__init__.py \ + forms/converters.py \ + forms/forms.py diff --git a/grc/src/grc_gnuradio/wxgui/__init__.py b/grc/src/grc_gnuradio/wxgui/__init__.py index 0b13ead9e..94a0adb8a 100644 --- a/grc/src/grc_gnuradio/wxgui/__init__.py +++ b/grc/src/grc_gnuradio/wxgui/__init__.py @@ -18,12 +18,4 @@ # Boston, MA 02110-1301, USA. # -from callback_controls import \ - button_control, \ - drop_down_control, \ - radio_buttons_horizontal_control, \ - radio_buttons_vertical_control, \ - slider_horizontal_control, \ - slider_vertical_control, \ - text_box_control from top_block_gui import top_block_gui diff --git a/grc/src/grc_gnuradio/wxgui/callback_controls.py b/grc/src/grc_gnuradio/wxgui/callback_controls.py deleted file mode 100644 index 32e5d8842..000000000 --- a/grc/src/grc_gnuradio/wxgui/callback_controls.py +++ /dev/null @@ -1,303 +0,0 @@ -# Copyright 2008 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 -import sys -from gnuradio import eng_notation - -class LabelText(wx.StaticText): - """Label text class for uniform labels among all controls.""" - - def __init__(self, window, label): - wx.StaticText.__init__(self, window, label=str(label)) - font = self.GetFont() - font.SetWeight(wx.FONTWEIGHT_BOLD) - self.SetFont(font) - -class _control_base(wx.BoxSizer): - """Control base class""" - - def __init__(self, window, callback): - self.window = window - self.callback = callback - wx.BoxSizer.__init__(self, wx.VERTICAL) - - def get_window(self): return self.window - - def call(self): return self.callback(self.get_value()) - - def get_value(self): raise NotImplementedError - - def set_value(self): raise NotImplementedError - -class _chooser_control_base(_control_base): - """House a drop down or radio buttons for variable control.""" - - def __init__(self, window, callback, label='Label', index=0, choices=[0], labels=[]): - """ - Chooser contructor. - Create the slider, text box, and label. - @param window the wx parent window - @param callback call the callback on changes - @param label the label title - @param index the default choice index - @param choices a list of choices - @param labels the choice labels or empty list - """ - #initialize - _control_base.__init__(self, window, callback) - label_text = LabelText(self.get_window(), label) - self.Add(label_text, 0, wx.ALIGN_CENTER) - self.index = index - self.choices = choices - self.labels = map(str, labels or choices) - self._init() - - def _handle_changed(self, event=None): - """ - A change is detected. Call the callback. - """ - try: self.call() - except Exception, e: print >> sys.stderr, 'Error in exec callback from handle changed.\n', e - - def get_value(self): - """ - Update the chooser. - @return one of the possible choices - """ - self._update() - return self.choices[self.index] - -############################################################################################## -# Button Control -############################################################################################## -class button_control(_chooser_control_base): - """House a button for variable control.""" - - def _init(self): - self.button = wx.Button(self.get_window(), label=self.labels[self.index]) - self.button.Bind(wx.EVT_BUTTON, self._handle_changed) - self.Add(self.button, 0, wx.ALIGN_CENTER) - - def _update(self): - self.index = (self.index + 1)%len(self.choices) #circularly increment index - self.button.SetLabel(self.labels[self.index]) - -############################################################################################## -# Drop Down Control -############################################################################################## -class drop_down_control(_chooser_control_base): - """House a drop down for variable control.""" - - def _init(self): - self.drop_down = wx.Choice(self.get_window(), choices=self.labels) - self.Add(self.drop_down, 0, wx.ALIGN_CENTER) - self.drop_down.Bind(wx.EVT_CHOICE, self._handle_changed) - self.drop_down.SetSelection(self.index) - - def _update(self): - self.index = self.drop_down.GetSelection() - -############################################################################################## -# Radio Buttons Control -############################################################################################## -class _radio_buttons_control_base(_chooser_control_base): - """House radio buttons for variable control.""" - - def _init(self): - #create box for radio buttons - radio_box = wx.BoxSizer(self.radio_box_orientation) - panel = wx.Panel(self.get_window()) - panel.SetSizer(radio_box) - self.Add(panel, 0, wx.ALIGN_CENTER) - #create radio buttons - self.radio_buttons = list() - for label in self.labels: - radio_button = wx.RadioButton(panel, label=label) - radio_button.SetValue(False) - self.radio_buttons.append(radio_button) - radio_box.Add(radio_button, 0, self.radio_button_align) - radio_button.Bind(wx.EVT_RADIOBUTTON, self._handle_changed) - #set one radio button active - self.radio_buttons[self.index].SetValue(True) - - def _update(self): - selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] - self.index = self.radio_buttons.index(selected_radio_button) - -class radio_buttons_horizontal_control(_radio_buttons_control_base): - radio_box_orientation = wx.HORIZONTAL - radio_button_align = wx.ALIGN_CENTER -class radio_buttons_vertical_control(_radio_buttons_control_base): - radio_box_orientation = wx.VERTICAL - radio_button_align = wx.ALIGN_LEFT - -############################################################################################## -# Slider Control -############################################################################################## -class _slider_control_base(_control_base): - """House a Slider and a Text Box for variable control.""" - - def __init__(self, window, callback, label='Label', value=50, min=0, max=100, num_steps=100, slider_length=200): - """ - Slider contructor. - Create the slider, text box, and label. - @param window the wx parent window - @param callback call the callback on changes - @param label the label title - @param value the default value - @param min the min - @param max the max - @param num_steps the number of steps - @param slider_length the length of the slider bar in pixels - """ - #initialize - _control_base.__init__(self, window, callback) - self.min = float(min) - self.max = float(max) - self.num_steps = int(num_steps) - self.slider_length = slider_length - #create gui elements - label_text_sizer = wx.BoxSizer(self.label_text_orientation) #label and text box container - label_text = LabelText(self.get_window(), '%s: '%str(label)) - self.text_box = text_box = wx.TextCtrl(self.get_window(), style=wx.TE_PROCESS_ENTER) - text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event - for obj in (label_text, text_box): #fill the container with label and text entry box - label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER) - self.Add(label_text_sizer, 0, wx.ALIGN_CENTER) - #make the slider - self.slider = slider = wx.Slider(self.get_window(), size=wx.Size(*self.get_slider_size()), style=self.slider_style) - try: slider.SetRange(0, num_steps) - except Exception, e: - print >> sys.stderr, 'Error in set slider range: "%s".'%e - exit(-1) - slider.Bind(wx.EVT_SCROLL, self._handle_scroll) #bind the scrolling event - self.Add(slider, 0, wx.ALIGN_CENTER) - #init slider and text box - self.set_value(value) - - def get_value(self): - """ - Get the current set value. - @return the value (float) - """ - return self._value - - def set_value(self, value): - """ - Set the current set value. - @param value the value (float) - """ - self._value = value - self._update_slider() - self._update_text_box() - - def _update_slider(self): - """ - Translate the real numerical value into a slider value. - """ - slider_value = (float(self.get_value()) - self.min)*self.num_steps/(self.max - self.min) - self.slider.SetValue(slider_value) - - def _update_text_box(self): - """ - Update the text box value. - Convert the value into engineering notation. - """ - self.text_box.SetValue(eng_notation.num_to_str(self.get_value())) - - def _handle_scroll(self, event=None): - """ - A scroll event is detected. Read the slider, call the callback. - """ - slider_value = self.slider.GetValue() - new_value = slider_value*(self.max - self.min)/self.num_steps + self.min - self.set_value(new_value) - try: self.call() - except Exception, e: print >> sys.stderr, 'Error in exec callback from handle scroll.\n', e - - def _handle_enter(self, event=None): - """ - An enter key was pressed. Read the text box, call the callback. - """ - new_value = eng_notation.str_to_num(self.text_box.GetValue()) - self.set_value(new_value) - try: self.call() - except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e - -class slider_horizontal_control(_slider_control_base): - label_text_orientation = wx.HORIZONTAL - slider_style = wx.SL_HORIZONTAL - def get_slider_size(self): return self.slider_length, -1 -class slider_vertical_control(_slider_control_base): - label_text_orientation = wx.VERTICAL - slider_style = wx.SL_VERTICAL - def get_slider_size(self): return -1, self.slider_length - -############################################################################################## -# Text Box Control -############################################################################################## -class text_box_control(_control_base): - """House a Text Box for variable control.""" - - def __init__(self, window, callback, label='Label', value=50): - """ - Text box contructor. - Create the text box, and label. - @param window the wx parent window - @param callback call the callback on changes - @param label the label title - @param value the default value - """ - #initialize - _control_base.__init__(self, window, callback) - #create gui elements - label_text_sizer = wx.BoxSizer(wx.HORIZONTAL) #label and text box container - label_text = LabelText(self.get_window(), '%s: '%str(label)) - self.text_box = text_box = wx.TextCtrl(self.get_window(), value=str(value), style=wx.TE_PROCESS_ENTER) - text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event - for obj in (label_text, text_box): #fill the container with label and text entry box - label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER) - self.Add(label_text_sizer, 0, wx.ALIGN_CENTER) - #detect string mode - self._string_mode = isinstance(value, str) - - def get_value(self): - """ - Get the current set value. - @return the value (float) - """ - return self._value - - def _handle_enter(self, event=None): - """ - An enter key was pressed. Read the text box, call the callback. - If the text cannot be evaluated, do not try callback. - Do not evaluate the text box value in string mode. - """ - if self._string_mode: - self._value = str(self.text_box.GetValue()) - else: - try: self._value = eval(self.text_box.GetValue()) - except Exception, e: - print >> sys.stderr, 'Error in evaluate value from handle enter.\n', e - return - try: self.call() - except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e diff --git a/grc/src/grc_gnuradio/wxgui/forms/__init__.py b/grc/src/grc_gnuradio/wxgui/forms/__init__.py new file mode 100644 index 000000000..07226668b --- /dev/null +++ b/grc/src/grc_gnuradio/wxgui/forms/__init__.py @@ -0,0 +1,54 @@ +# +# Copyright 2009 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. +# + +""" +The following classes will be available through gnuradio.wxgui.forms: +""" + +######################################################################## +# External Converters +######################################################################## +from converters import \ + eval_converter, str_converter, \ + float_converter, int_converter + +######################################################################## +# External Forms +######################################################################## +from forms import \ + radio_buttons, drop_down, notebook, \ + button, toggle_button, single_button, \ + check_box, text_box, static_text, \ + slider, log_slider + +######################################################################## +# Helpful widgets +######################################################################## +import wx + +class static_box_sizer(wx.StaticBoxSizer): + def __init__(self, parent, label='', bold=False, orient=wx.VERTICAL): + box = wx.StaticBox(parent=parent, label=label) + if bold: + font = box.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + box.SetFont(font) + wx.StaticBoxSizer.__init__(self, box=box, orient=orient) diff --git a/grc/src/grc_gnuradio/wxgui/forms/converters.py b/grc/src/grc_gnuradio/wxgui/forms/converters.py new file mode 100644 index 000000000..5971cfc09 --- /dev/null +++ b/grc/src/grc_gnuradio/wxgui/forms/converters.py @@ -0,0 +1,143 @@ +# +# Copyright 2009 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. +# + +from gnuradio import eng_notation +import math + +class abstract_converter(object): + def external_to_internal(self, v): + """ + Convert from user specified value to value acceptable to underlying primitive. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def internal_to_external(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 external_to_internal(self,v): + return v + def internal_to_external(self, s): + return s + +######################################################################## +# Commonly used converters +######################################################################## +class chooser_converter(abstract_converter): + """ + Convert between a set of possible choices and an index. + Used in the chooser base and all sub-classes. + """ + def __init__(self, choices): + self._choices = choices + def external_to_internal(self, choice): + return self._choices.index(choice) + def internal_to_external(self, index): + return self._choices[index] + def help(self): + return 'Enter a possible value in choices: "%s"'%str(self._choices) + +class bool_converter(abstract_converter): + """ + The internal representation is boolean. + The external representation is specified. + Used in the check box form. + """ + def __init__(self, true, false): + self._true = true + self._false = false + def external_to_internal(self, v): + return bool(v) + def internal_to_external(self, v): + if v: return self._true + else: return self._false + def help(self): + return "Value must be cast-able to type bool." + +class eval_converter(abstract_converter): + """ + A catchall converter when int and float are not enough. + Evaluate the internal representation with python's eval(). + Possible uses, set a complex number, constellation points. + Used in text box. + """ + def external_to_internal(self, s): + return str(s) + def internal_to_external(self, s): + return eval(s) + def help(self): + return "Value must be evaluatable by python's eval." + +class str_converter(abstract_converter): + def external_to_internal(self, v): + return str(v) + def internal_to_external(self, s): + return str(s) + +class int_converter(abstract_converter): + def external_to_internal(self, v): + return str(int(round(v))) + def internal_to_external(self, s): + return int(s, 0) + def help(self): + return "Enter an integer. Leading 0x indicates hex" + +class float_converter(abstract_converter): + def external_to_internal(self, v): + return eng_notation.num_to_str(v) + def internal_to_external(self, s): + return eng_notation.str_to_num(s) + def help(self): + return "Enter a float with optional scale suffix. E.g., 100.1M" + +class slider_converter(abstract_converter): + """ + Scale values to and from the slider. + """ + def __init__(self, minimum, maximum, num_steps, cast): + assert minimum < maximum + assert num_steps > 0 + self._offset = minimum + self._scaler = float(maximum - minimum)/num_steps + self._cast = cast + def external_to_internal(self, v): + return (v - self._offset)/self._scaler + def internal_to_external(self, v): + return self._cast(v*self._scaler + self._offset) + def help(self): + return "Value should be within slider range" + +class log_slider_converter(slider_converter): + def __init__(self, min_exp, max_exp, num_steps, base): + assert min_exp < max_exp + assert num_steps > 0 + self._base = base + slider_converter.__init__(self, minimum=min_exp, maximum=max_exp, num_steps=num_steps, cast=float) + def external_to_internal(self, v): + return slider_converter.external_to_internal(self, math.log(v, self._base)) + def internal_to_external(self, v): + return self._base**slider_converter.internal_to_external(self, v) diff --git a/grc/src/grc_gnuradio/wxgui/forms/forms.py b/grc/src/grc_gnuradio/wxgui/forms/forms.py new file mode 100644 index 000000000..5c5b6bad5 --- /dev/null +++ b/grc/src/grc_gnuradio/wxgui/forms/forms.py @@ -0,0 +1,473 @@ +# +# Copyright 2009 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. +# + +""" +The forms module contains general purpose wx-gui forms for gnuradio apps. + +The forms follow a layered model: + * internal layer + * deals with the wxgui objects directly + * implemented in event handler and update methods + * translation layer + * translates the between the external and internal layers + * handles parsing errors between layers + * external layer + * provided external access to the user + * set_value, get_value, and optional callback + * set and get through optional pubsub and key +""" + +EXT_KEY = 'external' +INT_KEY = 'internal' + +import wx +import sys +from gnuradio.gr.pubsub import pubsub +import converters + +EVT_DATA = wx.PyEventBinder(wx.NewEventType()) +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) + self.data = data + +######################################################################## +# Base Class Form +######################################################################## +class _form_base(pubsub, wx.BoxSizer): + def __init__(self, parent=None, sizer=None, proportion=0, flag=wx.EXPAND, ps=None, key='', value=None, callback=None, converter=converters.identity_converter()): + pubsub.__init__(self) + wx.BoxSizer.__init__(self, wx.HORIZONTAL) + self._parent = parent + self._converter = converter + self._callback = callback + self._widgets = list() + #add to the sizer if provided + if sizer: sizer.Add(self, proportion, flag) + #proxy the pubsub and key into this form + if ps is not None: + assert key + self.proxy(EXT_KEY, ps, key) + #no pubsub passed, must set initial value + else: self.set_value(value) + + def _add_widget(self, widget, label='', flag=0): + """ + Add the main widget to this object sizer. + If label is passed, add a label as well. + Register the widget and the label in the widgets list (for enable/disable). + Bind the update handler to the widget for data events. + This ensures that the gui thread handles updating widgets. + Setup the pusub triggers for external and internal. + @param widget the main widget + @param label the optional label + @param flag additional flags for widget + """ + #setup data event + widget.Bind(EVT_DATA, lambda x: self._update(x.data)) + update = lambda x: wx.PostEvent(widget, DataEvent(x)) + #register widget + self._widgets.append(widget) + #create optional label + if not label: self.Add(widget, 1, wx.ALIGN_CENTER_VERTICAL | flag) + else: + label_text = wx.StaticText(self._parent, label='%s: '%label) + self._widgets.append(label_text) + self.Add(label_text, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + self.Add(widget, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | flag) + #initialize without triggering pubsubs + self._translate_external_to_internal(self[EXT_KEY]) + update(self[INT_KEY]) + #subscribe all the functions + self.subscribe(INT_KEY, update) + self.subscribe(INT_KEY, self._translate_internal_to_external) + self.subscribe(EXT_KEY, self._translate_external_to_internal) + if self._callback: self.subscribe(EXT_KEY, self._callback) + + def _translate_external_to_internal(self, external): + try: + internal = self._converter.external_to_internal(external) + #prevent infinite loop between internal and external pubsub keys by only setting if changed + if self[INT_KEY] != internal: self[INT_KEY] = internal + except Exception, e: + self._err_msg(external, e) + self[INT_KEY] = self[INT_KEY] #reset to last good setting + + def _translate_internal_to_external(self, internal): + try: + external = self._converter.internal_to_external(internal) + #prevent infinite loop between internal and external pubsub keys by only setting if changed + if self[EXT_KEY] != external: self[EXT_KEY] = external + except Exception, e: + self._err_msg(internal, e) + self[EXT_KEY] = self[EXT_KEY] #reset to last good setting + + def _err_msg(self, value, e): + print >> sys.stderr, 'Error translating value: "%s"\n\t%s\n\t%s'%(value, e, self._converter.help()) + + #override in subclasses to handle the wxgui object + def _update(self, value): raise NotImplementedError + def _handle(self, event): raise NotImplementedError + + #provide a set/get interface for this form + def get_value(self): return self[EXT_KEY] + def set_value(self, value): self[EXT_KEY] = value + + def Disable(self, disable=True): self.Enable(not disable) + def Enable(self, enable=True): + if enable: + for widget in self._widgets: widget.Enable() + else: + for widget in self._widgets: widget.Disable() + +######################################################################## +# Static Text Form +######################################################################## +class static_text(_form_base): + def __init__(self, label='', width=-1, bold=False, converter=converters.str_converter(), **kwargs): + _form_base.__init__(self, converter=converter, **kwargs) + self._static_text = wx.StaticText(self._parent, size=wx.Size(width, -1)) + if bold: + font = self._static_text.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + self._static_text.SetFont(font) + self._add_widget(self._static_text, label) + + def _update(self, label): self._static_text.SetLabel(label) + +######################################################################## +# Text Box Form +######################################################################## +class text_box(_form_base): + def __init__(self, label='', width=-1, converter=converters.eval_converter(), **kwargs): + _form_base.__init__(self, converter=converter, **kwargs) + self._text_box = wx.TextCtrl(self._parent, size=wx.Size(width, -1), style=wx.TE_PROCESS_ENTER) + self._text_box.Bind(wx.EVT_TEXT_ENTER, self._handle) + self._add_widget(self._text_box, label) + + def _handle(self, event): self[INT_KEY] = self._text_box.GetValue() + def _update(self, value): self._text_box.SetValue(value) + +######################################################################## +# Slider Form +######################################################################## +class _slider_base(_form_base): + """ + Base class for linear and log slider. + @param length the length of the slider in px + @param style wx.SL_HORIZONTAL or wx.SL_VERTICAL + """ + def __init__(self, label='', length=-1, converter=None, num_steps=100, style=wx.SL_HORIZONTAL, **kwargs): + _form_base.__init__(self, converter=converter, **kwargs) + if style & wx.SL_HORIZONTAL: slider_size = wx.Size(length, -1) + elif style & wx.SL_VERTICAL: slider_size = wx.Size(-1, length) + else: raise NotImplementedError + self._slider = wx.Slider(self._parent, minValue=0, maxValue=num_steps, size=slider_size, style=style) + self._slider.Bind(wx.EVT_SCROLL, self._handle) + self._add_widget(self._slider, label, flag=wx.EXPAND) + + def _handle(self, event): self[INT_KEY] = self._slider.GetValue() + def _update(self, value): self._slider.SetValue(value) + +class slider(_slider_base): + """ + A generic linear slider. + @param cast a cast function, int, or float (default=float) + """ + def __init__(self, minimum=-100, maximum=100, num_steps=100, step_size=None, cast=float, **kwargs): + assert step_size or num_steps + if step_size is not None: num_steps = (maximum - minimum)/step_size + converter = converters.slider_converter(minimum=minimum, maximum=maximum, num_steps=num_steps, cast=cast) + _slider_base.__init__(self, converter=converter, num_steps=num_steps, **kwargs) + +class log_slider(_slider_base): + """ + A generic log slider. + """ + def __init__(self, min_exp=0, max_exp=1, base=10, num_steps=100, step_size=None, **kwargs): + assert step_size or num_steps + if step_size is not None: num_steps = (max_exp - min_exp)/step_size + converter = converters.log_slider_converter(min_exp=min_exp, max_exp=max_exp, num_steps=num_steps, base=base) + _slider_base.__init__(self, converter=converter, num_steps=num_steps, **kwargs) + +######################################################################## +# Check Box Form +######################################################################## +class check_box(_form_base): + def __init__(self, label='', true=True, false=False, **kwargs): + _form_base.__init__(self, converter=converters.bool_converter(true=true, false=false), **kwargs) + self._check_box = wx.CheckBox(self._parent, style=wx.CHK_2STATE, label=label) + self._check_box.Bind(wx.EVT_CHECKBOX, self._handle) + self._add_widget(self._check_box) + + def _handle(self, event): self[INT_KEY] = self._check_box.IsChecked() + def _update(self, checked): self._check_box.SetValue(checked) + +######################################################################## +# Base Class Chooser Form +######################################################################## +class _chooser_base(_form_base): + def __init__(self, choices=[], labels=None, **kwargs): + _form_base.__init__(self, converter=converters.chooser_converter(choices), **kwargs) + self._choices = choices + self._labels = map(str, labels or choices) + +######################################################################## +# Drop Down Chooser Form +######################################################################## +class drop_down(_chooser_base): + def __init__(self, label='', **kwargs): + _chooser_base.__init__(self, **kwargs) + self._drop_down = wx.Choice(self._parent, choices=self._labels) + self._drop_down.Bind(wx.EVT_CHOICE, self._handle) + self._add_widget(self._drop_down, label) + + def _handle(self, event): self[INT_KEY] = self._drop_down.GetSelection() + def _update(self, i): self._drop_down.SetSelection(i) + +######################################################################## +# Button Chooser Form +# Circularly move through the choices with each click. +# Can be a single-click button with one choice. +# Can be a 2-state button with two choices. +######################################################################## +class button(_chooser_base): + def __init__(self, label='', style=0, width=-1, **kwargs): + _chooser_base.__init__(self, **kwargs) + self._button = wx.Button(self._parent, size=wx.Size(width, -1), style=style) + self._button.Bind(wx.EVT_BUTTON, self._handle) + self._add_widget(self._button, label) + + def _handle(self, event): self[INT_KEY] = (self[INT_KEY] + 1)%len(self._choices) #circularly increment index + def _update(self, i): self._button.SetLabel(self._labels[i]); self.Layout() + +class toggle_button(button): + """ + Create a dual state button. + This button will alternate between True and False when clicked. + """ + def __init__(self, true_label='On (click to stop)', false_label='Off (click to start)', **kwargs): + button.__init__(self, choices=[True, False], labels=[true_label, false_label], **kwargs) + +class single_button(toggle_button): + """ + Create a single state button. + This button will callback() when clicked. + For use when state holding is not important. + """ + def __init__(self, label='click for callback', **kwargs): + toggle_button.__init__(self, true_label=label, false_label=label, value=True, **kwargs) + +######################################################################## +# Radio Buttons Chooser Form +######################################################################## +class radio_buttons(_chooser_base): + """ + Create a radio button form. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param choices list of possible values + @param labels list of labels for each choice (default=choices) + @param major_dimension the number of rows/cols (default=auto) + @param label title label for this widget (optional) + @param style useful style args: wx.RA_HORIZONTAL, wx.RA_VERTICAL, wx.NO_BORDER (default=wx.RA_HORIZONTAL) + """ + def __init__(self, style=wx.RA_HORIZONTAL, label='', major_dimension=0, **kwargs): + _chooser_base.__init__(self, **kwargs) + #create radio buttons + self._radio_buttons = wx.RadioBox(self._parent, choices=self._labels, style=style, label=label, majorDimension=major_dimension) + self._radio_buttons.Bind(wx.EVT_RADIOBOX, self._handle) + self._add_widget(self._radio_buttons) + + def _handle(self, event): self[INT_KEY] = self._radio_buttons.GetSelection() + def _update(self, i): self._radio_buttons.SetSelection(i) + +######################################################################## +# Notebook Chooser Form +# The notebook pages/tabs are for selecting between choices. +# A page must be added to the notebook for each choice. +######################################################################## +class notebook(_chooser_base): + def __init__(self, pages, notebook, **kwargs): + _chooser_base.__init__(self, **kwargs) + assert len(pages) == len(self._choices) + self._notebook = notebook + self._notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._handle) + #add pages, setting the label on each tab + for i, page in enumerate(pages): + self._notebook.AddPage(page, self._labels[i]) + self._add_widget(self._notebook) + + def _handle(self, event): self[INT_KEY] = self._notebook.GetSelection() + def _update(self, i): self._notebook.SetSelection(i) + +# ---------------------------------------------------------------- +# Stand-alone test application +# ---------------------------------------------------------------- + +import wx +from gnuradio.wxgui import gui + +class app_gui (object): + def __init__(self, frame, panel, vbox, top_block, options, args): + + def callback(v): print v + + radio_buttons( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=4, + style=wx.RA_HORIZONTAL, + label='test radio long string', + callback=callback, + #major_dimension = 2, + ) + + radio_buttons( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=4, + style=wx.RA_VERTICAL, + label='test radio long string', + callback=callback, + #major_dimension = 2, + ) + + radio_buttons( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=4, + style=wx.RA_VERTICAL | wx.NO_BORDER, + callback=callback, + #major_dimension = 2, + ) + + button( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=2, + label='button value', + callback=callback, + #width=100, + ) + + + drop_down( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + value=2, + label='Choose One', + callback=callback, + ) + check_box( + sizer=vbox, + parent=panel, + value=False, + label='check me', + callback=callback, + ) + text_box( + sizer=vbox, + parent=panel, + value=3, + label='text box', + callback=callback, + width=200, + ) + + static_text( + sizer=vbox, + parent=panel, + value='bob', + label='static text', + width=-1, + bold=True, + ) + + slider( + sizer=vbox, + parent=panel, + value=12, + label='slider', + callback=callback, + ) + + log_slider( + sizer=vbox, + parent=panel, + value=12, + label='slider', + callback=callback, + ) + + slider( + sizer=vbox, + parent=panel, + value=12, + label='slider', + callback=callback, + style=wx.SL_VERTICAL, + length=30, + ) + + toggle_button( + sizer=vbox, + parent=panel, + value=True, + label='toggle it', + callback=callback, + ) + + single_button( + sizer=vbox, + parent=panel, + label='sig test', + callback=callback, + ) + +if __name__ == "__main__": + try: + + # Create the GUI application + app = gui.app( + gui=app_gui, # User interface class + title="Test Forms", # Top window title + ) + + # And run it + app.MainLoop() + + except RuntimeError, e: + print e + sys.exit(1) diff --git a/grc/src/grc_gnuradio/wxgui/top_block_gui.py b/grc/src/grc_gnuradio/wxgui/top_block_gui.py index fef9d18ce..f3305d7c5 100644 --- a/grc/src/grc_gnuradio/wxgui/top_block_gui.py +++ b/grc/src/grc_gnuradio/wxgui/top_block_gui.py @@ -21,10 +21,11 @@ import wx import sys, os from gnuradio import gr +from gnuradio.gr.pubsub import pubsub default_gui_size = (200, 100) -class top_block_gui(gr.top_block): +class top_block_gui(gr.top_block, pubsub): """gr top block with wx gui app and grid sizer.""" def __init__(self, title='', size=default_gui_size, icon=None): @@ -37,6 +38,7 @@ class top_block_gui(gr.top_block): """ #initialize gr.top_block.__init__(self) + pubsub.__init__(self) self._size = size #set the icon if icon and os.path.isfile(icon): self._icon = icon |