summaryrefslogtreecommitdiff
path: root/grc/src/grc_gnuradio
diff options
context:
space:
mode:
Diffstat (limited to 'grc/src/grc_gnuradio')
-rw-r--r--grc/src/grc_gnuradio/wxgui/Makefile.am8
-rw-r--r--grc/src/grc_gnuradio/wxgui/__init__.py8
-rw-r--r--grc/src/grc_gnuradio/wxgui/callback_controls.py303
-rw-r--r--grc/src/grc_gnuradio/wxgui/forms/__init__.py54
-rw-r--r--grc/src/grc_gnuradio/wxgui/forms/converters.py143
-rw-r--r--grc/src/grc_gnuradio/wxgui/forms/forms.py473
-rw-r--r--grc/src/grc_gnuradio/wxgui/top_block_gui.py4
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