From 51af4269d3eebd3d611be918f8c799c96c650496 Mon Sep 17 00:00:00 2001 From: jblum Date: Sat, 13 Jun 2009 21:26:25 +0000 Subject: [PATCH] Merged wxgui/forms branch r11124:11183 The forms module is set of wxgui forms wrapped in pubsub aware convenience classes. The forms module will be used by the wxgui window classes (fft, scope, waterfall...) The forms module will be used in grc generated flowgraphs. The forms module will be used by future gui apps (usrp siggen...). Tasks: Moved forms module into wxgui. Modified *_window classes to use the forms module. Added features to forms as required. Removed pubsub aware forms in common.py. Switched grc to use the forms module in wxgui. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11184 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 7 + gr-wxgui/src/python/common.py | 162 ---------- gr-wxgui/src/python/const_window.py | 68 ++-- gr-wxgui/src/python/constants.py | 1 + gr-wxgui/src/python/fft_window.py | 116 +++---- gr-wxgui/src/python/forms/__init__.py | 103 ++++++ .../src/python}/forms/converters.py | 4 +- .../src/python}/forms/forms.py | 254 ++++++++++++--- gr-wxgui/src/python/histo_window.py | 43 +-- gr-wxgui/src/python/number_window.py | 128 ++++---- gr-wxgui/src/python/scope_window.py | 298 +++++++++++------- gr-wxgui/src/python/waterfall_window.py | 116 ++++--- .../python/blocks/variable_chooser.xml | 2 +- .../python/blocks/variable_slider.xml | 2 +- .../python/blocks/variable_text_box.xml | 2 +- grc/src/grc_gnuradio/wxgui/Makefile.am | 6 - grc/src/grc_gnuradio/wxgui/forms/__init__.py | 54 ---- 17 files changed, 784 insertions(+), 582 deletions(-) create mode 100644 gr-wxgui/src/python/forms/__init__.py rename {grc/src/grc_gnuradio/wxgui => gr-wxgui/src/python}/forms/converters.py (97%) rename {grc/src/grc_gnuradio/wxgui => gr-wxgui/src/python}/forms/forms.py (64%) delete mode 100644 grc/src/grc_gnuradio/wxgui/forms/__init__.py diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 45d75b60..e06298a2 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -59,3 +59,10 @@ ourpython_PYTHON = \ waterfall_window.py \ slider.py \ stdgui2.py + +formspythondir = $(grpythondir)/wxgui/forms + +formspython_PYTHON = \ + forms/__init__.py \ + forms/forms.py \ + forms/converters.py diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index c84827eb..c6b9509b 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -70,168 +70,6 @@ class input_watcher(threading.Thread): if self._arg2_key: self._controller[self._arg2_key] = msg.arg2() self._controller[self._msg_key] = msg.to_string() -################################################## -# WX Shared Classes -################################################## -import math -import wx - -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 - -class LabelText(wx.StaticText): - """ - Label text to give the wx plots a uniform look. - Get the default label text and set the font bold. - """ - def __init__(self, parent, label): - wx.StaticText.__init__(self, parent, label=label) - font = self.GetFont() - font.SetWeight(wx.FONTWEIGHT_BOLD) - self.SetFont(font) - -class LabelBox(wx.BoxSizer): - def __init__(self, parent, label, widget): - wx.BoxSizer.__init__(self, wx.HORIZONTAL) - self.Add(wx.StaticText(parent, label=' %s '%label), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) - self.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) - -class IncrDecrButtons(wx.BoxSizer): - """ - A horizontal box sizer with a increment and a decrement button. - """ - def __init__(self, parent, on_incr, on_decr): - """ - @param parent the parent window - @param on_incr the event handler for increment - @param on_decr the event handler for decrement - """ - wx.BoxSizer.__init__(self, wx.HORIZONTAL) - self._incr_button = wx.Button(parent, label='+', style=wx.BU_EXACTFIT) - self._incr_button.Bind(wx.EVT_BUTTON, on_incr) - self.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL) - self._decr_button = wx.Button(parent, label=' - ', style=wx.BU_EXACTFIT) - self._decr_button.Bind(wx.EVT_BUTTON, on_decr) - self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL) - - def Disable(self): self.Enable(False) - def Enable(self, enable=True): - if enable: - self._incr_button.Enable() - self._decr_button.Enable() - else: - self._incr_button.Disable() - self._decr_button.Disable() - -class ToggleButtonController(wx.Button): - def __init__(self, parent, controller, control_key, true_label, false_label): - self._controller = controller - self._control_key = control_key - wx.Button.__init__(self, parent, style=wx.BU_EXACTFIT) - self.Bind(wx.EVT_BUTTON, self._evt_button) - controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label)) - - def _evt_button(self, e): - self._controller[self._control_key] = not self._controller[self._control_key] - -class CheckBoxController(wx.CheckBox): - def __init__(self, parent, label, controller, control_key): - self._controller = controller - self._control_key = control_key - wx.CheckBox.__init__(self, parent, style=wx.CHK_2STATE, label=label) - self.Bind(wx.EVT_CHECKBOX, self._evt_checkbox) - controller.subscribe(control_key, lambda x: self.SetValue(bool(x))) - - def _evt_checkbox(self, e): - self._controller[self._control_key] = bool(e.IsChecked()) - -from gnuradio import eng_notation - -class TextBoxController(wx.TextCtrl): - def __init__(self, parent, controller, control_key, cast=float): - self._controller = controller - self._control_key = control_key - self._cast = cast - wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT_ENTER, self._evt_enter) - controller.subscribe(control_key, lambda x: self.SetValue(eng_notation.num_to_str(x))) - - def _evt_enter(self, e): - try: self._controller[self._control_key] = self._cast(eng_notation.str_to_num(self.GetValue())) - except: self._controller[self._control_key] = self._controller[self._control_key] - -class LogSliderController(wx.BoxSizer): - """ - Log slider controller with display label and slider. - Gives logarithmic scaling to slider operation. - """ - def __init__(self, parent, prefix, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x): - self._prefix = prefix - self._min_exp = min_exp - self._max_exp = max_exp - self._controller = controller - self._control_key = control_key - self._formatter = formatter - wx.BoxSizer.__init__(self, wx.VERTICAL) - self._label = wx.StaticText(parent, label=prefix + formatter(1/3.0)) - self.Add(self._label, 0, wx.EXPAND) - self._slider = wx.Slider(parent, minValue=0, maxValue=slider_steps, style=wx.SL_HORIZONTAL) - self.Add(self._slider, 0, wx.EXPAND) - self._slider.Bind(wx.EVT_SLIDER, self._on_slider_event) - controller.subscribe(control_key, self._on_controller_set) - - def _get_slope(self): - return float(self._max_exp-self._min_exp)/self._slider.GetMax() - - def _on_slider_event(self, e): - self._controller[self._control_key] = 10**(self._get_slope()*self._slider.GetValue() + self._min_exp) - - def _on_controller_set(self, value): - self._label.SetLabel(self._prefix + self._formatter(value)) - slider_value = (math.log10(value)-self._min_exp)/self._get_slope() - slider_value = min(max(self._slider.GetMin(), slider_value), self._slider.GetMax()) - if abs(slider_value - self._slider.GetValue()) > 1: - self._slider.SetValue(slider_value) - - def Disable(self): self.Enable(False) - def Enable(self, enable=True): - if enable: - self._slider.Enable() - self._label.Enable() - else: - self._slider.Disable() - self._label.Disable() - -class DropDownController(wx.Choice): - """ - Drop down controller with label and chooser. - Srop down selection from a set of choices. - """ - def __init__(self, parent, choices, controller, control_key, size=(-1, -1)): - """ - @param parent the parent window - @param choices a list of tuples -> (label, value) - @param controller the prop val controller - @param control_key the prop key for this control - """ - self._controller = controller - self._control_key = control_key - self._choices = choices - wx.Choice.__init__(self, parent, choices=[c[0] for c in choices], size=size) - self.Bind(wx.EVT_CHOICE, self._on_chooser_event) - controller.subscribe(control_key, self._on_controller_set) - - def _on_chooser_event(self, e): - self._controller[self._control_key] = self._choices[self.GetSelection()][1] - - def _on_controller_set(self, value): - #only set the chooser if the value is a possible choice - for i, choice in enumerate(self._choices): - if value == choice[1]: self.SetSelection(i) - ################################################## # Shared Functions ################################################## diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index 8e0e61ac..b128a4a9 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -63,34 +64,53 @@ class control_panel(wx.Panel): """ self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - control_box = wx.BoxSizer(wx.VERTICAL) - self.marker_index = 2 - #begin control box - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) + control_box = forms.static_box_sizer( + parent=self, label='Options', + bold=True, orient=wx.VERTICAL, + ) #alpha control_box.AddStretchSpacer() - alpha_slider = common.LogSliderController( - self, 'Alpha', - ALPHA_MIN_EXP, ALPHA_MAX_EXP, SLIDER_STEPS, - parent, ALPHA_KEY, + forms.text_box( + sizer=control_box, parent=self, label='Alpha', + converter=forms.float_converter(), + ps=parent, key=ALPHA_KEY, + ) + forms.log_slider( + sizer=control_box, parent=self, + min_exp=ALPHA_MIN_EXP, + max_exp=ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=ALPHA_KEY, ) - control_box.Add(alpha_slider, 0, wx.EXPAND) #gain_mu control_box.AddStretchSpacer() - gain_mu_slider = common.LogSliderController( - self, 'Gain Mu', - GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP, SLIDER_STEPS, - parent, GAIN_MU_KEY, + forms.text_box( + sizer=control_box, parent=self, label='Gain Mu', + converter=forms.float_converter(), + ps=parent, key=GAIN_MU_KEY, + ) + forms.log_slider( + sizer=control_box, parent=self, + min_exp=GAIN_MU_MIN_EXP, + max_exp=GAIN_MU_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=GAIN_MU_KEY, ) - control_box.Add(gain_mu_slider, 0, wx.EXPAND) #marker control_box.AddStretchSpacer() - marker_chooser = common.DropDownController(self, MARKER_TYPES, parent, MARKER_KEY) - control_box.Add(common.LabelBox(self, 'Marker', marker_chooser), 0, wx.EXPAND) + forms.drop_down( + sizer=control_box, parent=self, + ps=parent, key=MARKER_KEY, label='Marker', + choices=map(lambda x: x[1], MARKER_TYPES), + labels=map(lambda x: x[0], MARKER_TYPES), + ) #run/stop control_box.AddStretchSpacer() - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -121,6 +141,11 @@ class const_window(wx.Panel, pubsub.pubsub): self.proxy(GAIN_OMEGA_KEY, controller, gain_omega_key) self.proxy(OMEGA_KEY, controller, omega_key) self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + #initialize values + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 8 + self[MARKER_KEY] = DEFAULT_MARKER_TYPE #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) @@ -141,13 +166,6 @@ class const_window(wx.Panel, pubsub.pubsub): self.subscribe(ALPHA_KEY, set_beta) def set_gain_omega(gain_mu): self[GAIN_OMEGA_KEY] = .25*gain_mu**2 self.subscribe(GAIN_MU_KEY, set_gain_omega) - #initialize values - self[ALPHA_KEY] = self[ALPHA_KEY] - self[GAIN_MU_KEY] = self[GAIN_MU_KEY] - self[RUNNING_KEY] = True - self[X_DIVS_KEY] = 8 - self[Y_DIVS_KEY] = 8 - self[MARKER_KEY] = DEFAULT_MARKER_TYPE #register events self.subscribe(MSG_KEY, self.handle_msg) self.subscribe(X_DIVS_KEY, self.update_grid) diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index a4ccdca6..5e139570 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -66,3 +66,4 @@ MAXIMUM_KEY = 'maximum' MINIMUM_KEY = 'minimum' NUM_BINS_KEY = 'num_bins' FRAME_SIZE_KEY = 'frame_size' +CHANNEL_OPTIONS_KEY = 'channel_options' diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index fdd5562d..fded1a8f 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -59,48 +60,66 @@ class control_panel(wx.Panel): self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) - #checkboxes for average and peak hold control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) - control_box.Add(peak_hold_check_box, 0, wx.EXPAND) - average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) - control_box.Add(average_check_box, 0, wx.EXPAND) - control_box.AddSpacer(2) - avg_alpha_slider = common.LogSliderController( - self, 'Avg Alpha', - AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent, AVG_ALPHA_KEY, - formatter=lambda x: ': %.4f'%x, + #checkboxes for average and peak hold + options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Options', + bold=True, orient=wx.VERTICAL, + ) + forms.check_box( + sizer=options_box, parent=self, label='Peak Hold', + ps=parent, key=PEAK_HOLD_KEY, + ) + forms.check_box( + sizer=options_box, parent=self, label='Average', + ps=parent, key=AVERAGE_KEY, ) - parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) - control_box.Add(avg_alpha_slider, 0, wx.EXPAND) + #static text and slider for averaging + avg_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Avg Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=AVG_ALPHA_KEY, width=50, + ) + avg_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=AVG_ALPHA_MIN_EXP, + max_exp=AVG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=AVG_ALPHA_KEY, + ) + for widget in (avg_alpha_text, avg_alpha_slider): + parent.subscribe(AVERAGE_KEY, widget.Enable) + widget.Enable(parent[AVERAGE_KEY]) #radio buttons for div size control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Set dB/div'), 0, wx.ALIGN_CENTER) - radio_box = wx.BoxSizer(wx.VERTICAL) - self.radio_buttons = list() - for y_per_div in DIV_LEVELS: - radio_button = wx.RadioButton(self, label="%d dB/div"%y_per_div) - radio_button.Bind(wx.EVT_RADIOBUTTON, self._on_y_per_div) - self.radio_buttons.append(radio_button) - radio_box.Add(radio_button, 0, wx.ALIGN_LEFT) - parent.subscribe(Y_PER_DIV_KEY, self._on_set_y_per_div) - control_box.Add(radio_box, 0, wx.EXPAND) + y_ctrl_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Axis Options', + bold=True, orient=wx.VERTICAL, + ) + forms.radio_buttons( + sizer=y_ctrl_box, parent=self, + ps=parent, key=Y_PER_DIV_KEY, + style=wx.RA_VERTICAL|wx.NO_BORDER, choices=DIV_LEVELS, + labels=map(lambda x: '%s dB/div'%x, DIV_LEVELS), + ) #ref lvl buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - _ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) - control_box.Add(_ref_lvl_buttons, 0, wx.ALIGN_CENTER) + forms.incr_decr_buttons( + parent=self, sizer=y_ctrl_box, label='Ref Level', + on_incr=self._on_incr_ref_level, on_decr=self._on_decr_ref_level, + ) + y_ctrl_box.AddSpacer(2) #autoscale - control_box.AddStretchSpacer() - autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(autoscale_button, 0, wx.EXPAND) + forms.single_button( + sizer=y_ctrl_box, parent=self, label='Autoscale', + callback=self.parent.autoscale, + ) #run/stop - run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(run_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) #mouse wheel event @@ -112,15 +131,6 @@ class control_panel(wx.Panel): ################################################## # Event handlers ################################################## - def _on_set_y_per_div(self, y_per_div): - try: - index = list(DIV_LEVELS).index(y_per_div) - self.radio_buttons[index].SetValue(True) - except: pass - def _on_y_per_div(self, event): - selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] - index = self.radio_buttons.index(selected_radio_button) - self.parent[Y_PER_DIV_KEY] = DIV_LEVELS[index] def _on_incr_ref_level(self, event): self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY] def _on_decr_ref_level(self, event): @@ -161,6 +171,14 @@ class fft_window(wx.Panel, pubsub.pubsub): self.proxy(AVERAGE_KEY, controller, average_key) self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + #initialize values + self[PEAK_HOLD_KEY] = peak_hold + self[Y_PER_DIV_KEY] = y_per_div + self[Y_DIVS_KEY] = y_divs + self[X_DIVS_KEY] = 8 #approximate + self[REF_LEVEL_KEY] = ref_level + self[BASEBAND_FREQ_KEY] = baseband_freq + self[RUNNING_KEY] = True #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) @@ -175,16 +193,6 @@ class fft_window(wx.Panel, pubsub.pubsub): main_box.Add(self.plotter, 1, wx.EXPAND) main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) - #initialize values - self[AVERAGE_KEY] = self[AVERAGE_KEY] - self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] - self[PEAK_HOLD_KEY] = peak_hold - self[Y_PER_DIV_KEY] = y_per_div - self[Y_DIVS_KEY] = y_divs - self[X_DIVS_KEY] = 8 #approximate - self[REF_LEVEL_KEY] = ref_level - self[BASEBAND_FREQ_KEY] = baseband_freq - self[RUNNING_KEY] = True #register events self.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) self.subscribe(MSG_KEY, self.handle_msg) diff --git a/gr-wxgui/src/python/forms/__init__.py b/gr-wxgui/src/python/forms/__init__.py new file mode 100644 index 00000000..3f9f4c73 --- /dev/null +++ b/gr-wxgui/src/python/forms/__init__.py @@ -0,0 +1,103 @@ +# +# 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, gauge, \ + make_bold, DataEvent, EVT_DATA + +######################################################################## +# Helpful widgets +######################################################################## +import wx + +class static_box_sizer(wx.StaticBoxSizer): + """ + A box sizer with label and border. + @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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param label title label for this widget (optional) + @param bold true to boldify the label + @param orient the sizer orientation wx.VERTICAL or wx.HORIZONTAL (default=wx.VERTICAL) + """ + def __init__(self, parent, label='', bold=False, sizer=None, orient=wx.VERTICAL, proportion=0, flag=wx.EXPAND): + box = wx.StaticBox(parent=parent, label=label) + if bold: make_bold(box) + wx.StaticBoxSizer.__init__(self, box=box, orient=orient) + if sizer: sizer.Add(self, proportion, flag) + +class incr_decr_buttons(wx.BoxSizer): + """ + A horizontal box sizer with a increment and a decrement button. + @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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param label title label for this widget (optional) + @param on_incr the callback for pressing the + button + @param on_decr the callback for pressing the - button + """ + def __init__(self, parent, on_incr, on_decr, label='', sizer=None, proportion=0, flag=wx.EXPAND): + """ + @param parent the parent window + @param on_incr the event handler for increment + @param on_decr the event handler for decrement + """ + wx.BoxSizer.__init__(self, wx.HORIZONTAL) + buttons_box = wx.BoxSizer(wx.HORIZONTAL) + self._incr_button = wx.Button(parent, label='+', style=wx.BU_EXACTFIT) + self._incr_button.Bind(wx.EVT_BUTTON, on_incr) + buttons_box.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL) + self._decr_button = wx.Button(parent, label=' - ', style=wx.BU_EXACTFIT) + self._decr_button.Bind(wx.EVT_BUTTON, on_decr) + buttons_box.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL) + if label: #add label + self.Add(wx.StaticText(parent, label='%s: '%label), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + self.Add(buttons_box, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + else: self.Add(buttons_box, 0, wx.ALIGN_CENTER_VERTICAL) + if sizer: sizer.Add(self, proportion, flag) + + def Disable(self, disable=True): self.Enable(not disable) + def Enable(self, enable=True): + if enable: + self._incr_button.Enable() + self._decr_button.Enable() + else: + self._incr_button.Disable() + self._decr_button.Disable() diff --git a/grc/src/grc_gnuradio/wxgui/forms/converters.py b/gr-wxgui/src/python/forms/converters.py similarity index 97% rename from grc/src/grc_gnuradio/wxgui/forms/converters.py rename to gr-wxgui/src/python/forms/converters.py index 5971cfc0..123aefeb 100644 --- a/grc/src/grc_gnuradio/wxgui/forms/converters.py +++ b/gr-wxgui/src/python/forms/converters.py @@ -107,8 +107,10 @@ class int_converter(abstract_converter): return "Enter an integer. Leading 0x indicates hex" class float_converter(abstract_converter): + def __init__(self, formatter=eng_notation.num_to_str): + self._formatter = formatter def external_to_internal(self, v): - return eng_notation.num_to_str(v) + return self._formatter(v) def internal_to_external(self, s): return eng_notation.str_to_num(s) def help(self): diff --git a/grc/src/grc_gnuradio/wxgui/forms/forms.py b/gr-wxgui/src/python/forms/forms.py similarity index 64% rename from grc/src/grc_gnuradio/wxgui/forms/forms.py rename to gr-wxgui/src/python/forms/forms.py index 5c5b6bad..10f6a482 100644 --- a/grc/src/grc_gnuradio/wxgui/forms/forms.py +++ b/gr-wxgui/src/python/forms/forms.py @@ -33,6 +33,10 @@ The forms follow a layered model: * provided external access to the user * set_value, get_value, and optional callback * set and get through optional pubsub and key + +Known problems: + * An empty label in the radio box still consumes space. + * The static text cannot resize the parent at runtime. """ EXT_KEY = 'external' @@ -49,6 +53,11 @@ class DataEvent(wx.PyEvent): wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) self.data = data +def make_bold(widget): + font = widget.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + widget.SetFont(font) + ######################################################################## # Base Class Form ######################################################################## @@ -57,6 +66,7 @@ class _form_base(pubsub, wx.BoxSizer): pubsub.__init__(self) wx.BoxSizer.__init__(self, wx.HORIZONTAL) self._parent = parent + self._key = key self._converter = converter self._callback = callback self._widgets = list() @@ -69,7 +79,10 @@ class _form_base(pubsub, wx.BoxSizer): #no pubsub passed, must set initial value else: self.set_value(value) - def _add_widget(self, widget, label='', flag=0): + def __str__(self): + return "Form: %s -> %s"%(self.__class__, self._key) + + def _add_widget(self, widget, label='', flag=0, label_prop=0, widget_prop=1): """ Add the main widget to this object sizer. If label is passed, add a label as well. @@ -80,6 +93,8 @@ class _form_base(pubsub, wx.BoxSizer): @param widget the main widget @param label the optional label @param flag additional flags for widget + @param label_prop the proportion for the label + @param widget_prop the proportion for the widget """ #setup data event widget.Bind(EVT_DATA, lambda x: self._update(x.data)) @@ -87,12 +102,12 @@ class _form_base(pubsub, wx.BoxSizer): #register widget self._widgets.append(widget) #create optional label - if not label: self.Add(widget, 1, wx.ALIGN_CENTER_VERTICAL | flag) + if not label: self.Add(widget, widget_prop, 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) + self.Add(label_text, label_prop, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + self.Add(widget, widget_prop, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | flag) #initialize without triggering pubsubs self._translate_external_to_internal(self[EXT_KEY]) update(self[INT_KEY]) @@ -121,7 +136,7 @@ class _form_base(pubsub, wx.BoxSizer): 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()) + print >> sys.stderr, self, '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 @@ -138,25 +153,74 @@ class _form_base(pubsub, wx.BoxSizer): else: for widget in self._widgets: widget.Disable() +######################################################################## +# 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) + +######################################################################## +# Base Class Slider Form +######################################################################## +class _slider_base(_form_base): + 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) + ######################################################################## # Static Text Form ######################################################################## class static_text(_form_base): + """ + A text box 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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param width the width of the form in px + @param bold true to bold-ify the text (default=False) + @param converter forms.str_converter(), int_converter(), float_converter()... + """ 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) + if bold: make_bold(self._static_text) self._add_widget(self._static_text, label) - def _update(self, label): self._static_text.SetLabel(label) + def _update(self, label): self._static_text.SetLabel(label); self._parent.Layout() ######################################################################## # Text Box Form ######################################################################## class text_box(_form_base): + """ + A text box 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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param width the width of the form in px + @param converter forms.str_converter(), int_converter(), float_converter()... + """ 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) @@ -168,28 +232,26 @@ class text_box(_form_base): ######################################################################## # Slider Form +# Linear Slider +# Logarithmic Slider ######################################################################## -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 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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param length the length of the slider in px (optional) + @param style wx.SL_HORIZONTAL or wx.SL_VERTICAL (default=horizontal) + @param minimum the minimum value + @param maximum the maximum value + @param num_steps the number of slider steps (or specify step_size) + @param step_size the step between slider jumps (or specify num_steps) @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): @@ -200,7 +262,23 @@ class slider(_slider_base): class log_slider(_slider_base): """ - A generic log slider. + A generic logarithmic slider. + The sliders min and max values are base**min_exp and base**max_exp. + @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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param length the length of the slider in px (optional) + @param style wx.SL_HORIZONTAL or wx.SL_VERTICAL (default=horizontal) + @param min_exp the minimum exponent + @param max_exp the maximum exponent + @param base the exponent base in base**exp + @param num_steps the number of slider steps (or specify step_size) + @param step_size the exponent step size (or specify num_steps) """ def __init__(self, min_exp=0, max_exp=1, base=10, num_steps=100, step_size=None, **kwargs): assert step_size or num_steps @@ -208,10 +286,58 @@ class log_slider(_slider_base): 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) +######################################################################## +# Gauge Form +######################################################################## +class gauge(_form_base): + """ + A gauge bar. + The gauge displays floating point values between the minimum and maximum. + @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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param length the length of the slider in px (optional) + @param style wx.GA_HORIZONTAL or wx.GA_VERTICAL (default=horizontal) + @param minimum the minimum value + @param maximum the maximum value + @param num_steps the number of slider steps (or specify step_size) + @param step_size the step between slider jumps (or specify num_steps) + """ + def __init__(self, label='', length=-1, minimum=-100, maximum=100, num_steps=100, step_size=None, style=wx.GA_HORIZONTAL, **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=float) + _form_base.__init__(self, converter=converter, **kwargs) + if style & wx.SL_HORIZONTAL: gauge_size = wx.Size(length, -1) + elif style & wx.SL_VERTICAL: gauge_size = wx.Size(-1, length) + else: raise NotImplementedError + self._gauge = wx.Gauge(self._parent, range=num_steps, size=gauge_size, style=style) + self._add_widget(self._gauge, label, flag=wx.EXPAND) + + def _update(self, value): self._gauge.SetValue(value) + ######################################################################## # Check Box Form ######################################################################## class check_box(_form_base): + """ + Create a check box 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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param true the value for form when checked (default=True) + @param false the value for form when unchecked (default=False) + @param label title label for this widget (optional) + """ 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) @@ -221,24 +347,29 @@ class check_box(_form_base): 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): + """ + Create a drop down menu 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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @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 label title label for this widget (optional) + @param width the form width in px (optional) + """ + def __init__(self, label='', width=-1, **kwargs): _chooser_base.__init__(self, **kwargs) - self._drop_down = wx.Choice(self._parent, choices=self._labels) + self._drop_down = wx.Choice(self._parent, choices=self._labels, size=wx.Size(width, -1)) self._drop_down.Bind(wx.EVT_CHOICE, self._handle) - self._add_widget(self._drop_down, label) + self._add_widget(self._drop_down, label, widget_prop=0, label_prop=1) def _handle(self, event): self[INT_KEY] = self._drop_down.GetSelection() def _update(self, i): self._drop_down.SetSelection(i) @@ -250,19 +381,45 @@ class drop_down(_chooser_base): # Can be a 2-state button with two choices. ######################################################################## class button(_chooser_base): + """ + Create a multi-state button. + @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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @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 width the width of the button in pixels (optional) + @param style style arguments (optional) + @param label title label for this widget (optional) + """ 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) + self._add_widget(self._button, label, widget_prop=((not style&wx.BU_EXACTFIT) and 1 or 0)) 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. + Create a dual-state button. This button will alternate between True and False when clicked. + @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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param width the width of the button in pixels (optional) + @param style style arguments (optional) + @param true_label the button's label in the true state + @param false_label the button's label in the false state """ 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) @@ -272,6 +429,16 @@ class single_button(toggle_button): Create a single state button. This button will callback() when clicked. For use when state holding is not important. + @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 flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param width the width of the button in pixels (optional) + @param style style arguments (optional) + @param label the button's label """ def __init__(self, label='click for callback', **kwargs): toggle_button.__init__(self, true_label=label, false_label=label, value=True, **kwargs) @@ -285,6 +452,7 @@ class radio_buttons(_chooser_base): @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 flag the flag argument when added to the sizer (default=wx.EXPAND) @param ps the pubsub object (optional) @param key the pubsub key (optional) @param value the default value (optional) diff --git a/gr-wxgui/src/python/histo_window.py b/gr-wxgui/src/python/histo_window.py index dce52ff9..5f434d70 100644 --- a/gr-wxgui/src/python/histo_window.py +++ b/gr-wxgui/src/python/histo_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -53,23 +54,31 @@ class control_panel(wx.Panel): wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) SIZE = (100, -1) - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - control_box.AddStretchSpacer() + control_box = forms.static_box_sizer( + parent=self, label='Options', + bold=True, orient=wx.VERTICAL, + ) #num bins - def num_bins_cast(num): - num = int(num) - assert num > 1 - return num - num_bins_ctrl = common.TextBoxController(self, parent, NUM_BINS_KEY, cast=num_bins_cast) - control_box.Add(common.LabelBox(self, ' Num Bins ', num_bins_ctrl), 0, wx.EXPAND) control_box.AddStretchSpacer() + forms.text_box( + sizer=control_box, parent=self, label='Num Bins', + converter=forms.int_converter(), + ps=parent, key=NUM_BINS_KEY, + ) #frame size - frame_size_ctrl = common.TextBoxController(self, parent, FRAME_SIZE_KEY, cast=num_bins_cast) - control_box.Add(common.LabelBox(self, ' Frame Size ', frame_size_ctrl), 0, wx.EXPAND) control_box.AddStretchSpacer() + forms.text_box( + sizer=control_box, parent=self, label='Frame Size', + converter=forms.int_converter(), + ps=parent, key=FRAME_SIZE_KEY, + ) #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -98,6 +107,10 @@ class histo_window(wx.Panel, pubsub.pubsub): self.proxy(NUM_BINS_KEY, controller, num_bins_key) self.proxy(FRAME_SIZE_KEY, controller, frame_size_key) self.proxy(MSG_KEY, controller, msg_key) + #initialize values + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 4 #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.bar_plotter(self) @@ -111,12 +124,6 @@ class histo_window(wx.Panel, pubsub.pubsub): main_box.Add(self.plotter, 1, wx.EXPAND) main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) - #initialize values - self[NUM_BINS_KEY] = self[NUM_BINS_KEY] - self[FRAME_SIZE_KEY] = self[FRAME_SIZE_KEY] - self[RUNNING_KEY] = True - self[X_DIVS_KEY] = 8 - self[Y_DIVS_KEY] = 4 #register events self.subscribe(MSG_KEY, self.handle_msg) self.subscribe(X_DIVS_KEY, self.update_grid) diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index f12a1824..8a824976 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -28,6 +28,7 @@ import wx import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -38,6 +39,9 @@ AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 DEFAULT_NUMBER_RATE = gr.prefs().get_long('wxgui', 'number_rate', 5) DEFAULT_WIN_SIZE = (300, 300) DEFAULT_GAUGE_RANGE = 1000 +VALUE_REPR_KEY = 'value_repr' +VALUE_REAL_KEY = 'value_real' +VALUE_IMAG_KEY = 'value_imag' ################################################## # Number window control panel @@ -53,28 +57,45 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent) control_box = wx.BoxSizer(wx.VERTICAL) #checkboxes for average and peak hold control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) - control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) - self.average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) - control_box.Add(self.average_check_box, 0, wx.EXPAND) - control_box.AddSpacer(2) - self.avg_alpha_slider = common.LogSliderController( - self, 'Avg Alpha', - AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent, AVG_ALPHA_KEY, - formatter=lambda x: ': %.4f'%x, + options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Options', + bold=True, orient=wx.VERTICAL, + ) + forms.check_box( + sizer=options_box, parent=self, label='Peak Hold', + ps=parent, key=PEAK_HOLD_KEY, + ) + forms.check_box( + sizer=options_box, parent=self, label='Average', + ps=parent, key=AVERAGE_KEY, + ) + #static text and slider for averaging + avg_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Avg Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=AVG_ALPHA_KEY, width=50, + ) + avg_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=AVG_ALPHA_MIN_EXP, + max_exp=AVG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=AVG_ALPHA_KEY, ) - parent.subscribe(AVERAGE_KEY, self.avg_alpha_slider.Enable) - control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + for widget in (avg_alpha_text, avg_alpha_slider): + parent.subscribe(AVERAGE_KEY, widget.Enable) + widget.Enable(parent[AVERAGE_KEY]) #run/stop control_box.AddStretchSpacer() - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -107,38 +128,47 @@ class number_window(wx.Panel, pubsub.pubsub): self.peak_val_imag = NEG_INF self.real = real self.units = units - self.minval = minval - self.maxval = maxval self.decimal_places = decimal_places #proxy the keys self.proxy(MSG_KEY, controller, msg_key) self.proxy(AVERAGE_KEY, controller, average_key) self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + #initialize values + self[PEAK_HOLD_KEY] = peak_hold + self[RUNNING_KEY] = True + self[VALUE_REAL_KEY] = minval + self[VALUE_IMAG_KEY] = minval #setup the box with display and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) - sizer = wx.BoxSizer(wx.VERTICAL) - main_box.Add(sizer, 1, wx.EXPAND) + sizer = forms.static_box_sizer( + parent=self, sizer=main_box, label=title, + bold=True, orient=wx.VERTICAL, proportion=1, + ) main_box.Add(self.control_panel, 0, wx.EXPAND) - sizer.Add(common.LabelText(self, title), 1, wx.ALIGN_CENTER) - self.text = wx.StaticText(self, size=(size[0], -1)) - sizer.Add(self.text, 1, wx.EXPAND) - self.gauge_real = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL) - self.gauge_imag = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL) + sizer.AddStretchSpacer() + forms.static_text( + parent=self, sizer=sizer, + ps=self, key=VALUE_REPR_KEY, width=size[0], + converter=forms.str_converter(), + ) + sizer.AddStretchSpacer() + self.gauge_real = forms.gauge( + parent=self, sizer=sizer, style=wx.GA_HORIZONTAL, + ps=self, key=VALUE_REAL_KEY, length=size[0], + minimum=minval, maximum=maxval, num_steps=DEFAULT_GAUGE_RANGE, + ) + self.gauge_imag = forms.gauge( + parent=self, sizer=sizer, style=wx.GA_HORIZONTAL, + ps=self, key=VALUE_IMAG_KEY, length=size[0], + minimum=minval, maximum=maxval, num_steps=DEFAULT_GAUGE_RANGE, + ) #hide/show gauges self.show_gauges(show_gauge) - sizer.Add(self.gauge_real, 1, wx.EXPAND) - sizer.Add(self.gauge_imag, 1, wx.EXPAND) self.SetSizerAndFit(main_box) - #initialize values - self[PEAK_HOLD_KEY] = peak_hold - self[RUNNING_KEY] = True - self[AVERAGE_KEY] = self[AVERAGE_KEY] - self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] #register events self.subscribe(MSG_KEY, self.handle_msg) - self.Bind(common.EVT_DATA, self.update) def show_gauges(self, show_gauge): """ @@ -146,20 +176,10 @@ class number_window(wx.Panel, pubsub.pubsub): If this is real, never show the imaginary gauge. @param show_gauge true to show """ - if show_gauge: self.gauge_real.Show() - else: self.gauge_real.Hide() - if show_gauge and not self.real: self.gauge_imag.Show() - else: self.gauge_imag.Hide() + self.gauge_real.ShowItems(show_gauge) + self.gauge_imag.ShowItems(show_gauge and not self.real) def handle_msg(self, msg): - """ - Post this message into a data event. - Allow wx to handle the event to avoid threading issues. - @param msg the incoming numbersink data - """ - wx.PostEvent(self, common.DataEvent(msg)) - - def update(self, event): """ Handle a message from the message queue. Convert the string based message into a float or complex. @@ -168,29 +188,23 @@ class number_window(wx.Panel, pubsub.pubsub): @param event event.data is the number sample as a character array """ if not self[RUNNING_KEY]: return - #set gauge - def set_gauge_value(gauge, value): - gauge_val = DEFAULT_GAUGE_RANGE*(value-self.minval)/(self.maxval-self.minval) - gauge_val = max(0, gauge_val) #clip - gauge_val = min(DEFAULT_GAUGE_RANGE, gauge_val) #clip - gauge.SetValue(gauge_val) format_string = "%%.%df"%self.decimal_places if self.real: - sample = numpy.fromstring(event.data, numpy.float32)[-1] + sample = numpy.fromstring(msg, numpy.float32)[-1] if self[PEAK_HOLD_KEY]: sample = self.peak_val_real = max(self.peak_val_real, sample) label_text = "%s %s"%(format_string%sample, self.units) - set_gauge_value(self.gauge_real, sample) + self[VALUE_REAL_KEY] = sample else: - sample = numpy.fromstring(event.data, numpy.complex64)[-1] + sample = numpy.fromstring(msg, numpy.complex64)[-1] if self[PEAK_HOLD_KEY]: self.peak_val_real = max(self.peak_val_real, sample.real) self.peak_val_imag = max(self.peak_val_imag, sample.imag) sample = self.peak_val_real + self.peak_val_imag*1j label_text = "%s + %sj %s"%(format_string%sample.real, format_string%sample.imag, self.units) - set_gauge_value(self.gauge_real, sample.real) - set_gauge_value(self.gauge_imag, sample.imag) + self[VALUE_REAL_KEY] = sample.real + self[VALUE_IMAG_KEY] = sample.imag #set label text - self.text.SetLabel(label_text) + self[VALUE_REPR_KEY] = label_text #clear peak hold if not self[PEAK_HOLD_KEY]: self.peak_val_real = NEG_INF diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index bbc66426..44904640 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -30,6 +30,7 @@ import time import pubsub from constants import * from gnuradio import gr #for gr.prefs, trigger modes +import forms ################################################## # Constants @@ -42,12 +43,12 @@ COUPLING_MODES = ( ) TRIGGER_MODES = ( ('Freerun', gr.gr_TRIG_MODE_FREE), - ('Automatic', gr.gr_TRIG_MODE_AUTO), + ('Auto', gr.gr_TRIG_MODE_AUTO), ('Normal', gr.gr_TRIG_MODE_NORM), ) TRIGGER_SLOPES = ( - ('Positive +', gr.gr_TRIG_SLOPE_POS), - ('Negative -', gr.gr_TRIG_SLOPE_NEG), + ('Pos +', gr.gr_TRIG_SLOPE_POS), + ('Neg -', gr.gr_TRIG_SLOPE_NEG), ) CHANNEL_COLOR_SPECS = ( (0.3, 0.3, 1.0), @@ -78,7 +79,7 @@ class control_panel(wx.Panel): Create a new control panel. @param parent the wx parent window """ - SIZE = (100, -1) + WIDTH = 90 self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) @@ -86,161 +87,238 @@ class control_panel(wx.Panel): # Axes Options ################################################## control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) + axes_options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Axes Options', + bold=True, orient=wx.VERTICAL, + ) ################################################## # Scope Mode Box ################################################## scope_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(scope_mode_box, 0, wx.EXPAND) + axes_options_box.Add(scope_mode_box, 0, wx.EXPAND) #x axis divs - x_buttons_scope = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs) - scope_mode_box.Add(common.LabelBox(self, 'Secs/Div', x_buttons_scope), 0, wx.EXPAND) + forms.incr_decr_buttons( + parent=self, sizer=scope_mode_box, label='Secs/Div', + on_incr=self._on_incr_t_divs, on_decr=self._on_decr_t_divs, + ) #y axis divs - y_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons_scope.Enable(not x)) - scope_mode_box.Add(common.LabelBox(self, 'Counts/Div', y_buttons_scope), 0, wx.EXPAND) + y_buttons_scope = forms.incr_decr_buttons( + parent=self, sizer=scope_mode_box, label='Counts/Div', + on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs, + ) #y axis ref lvl - y_off_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons_scope.Enable(not x)) - scope_mode_box.Add(common.LabelBox(self, 'Y Offset', y_off_buttons_scope), 0, wx.EXPAND) + y_off_buttons_scope = forms.incr_decr_buttons( + parent=self, sizer=scope_mode_box, label='Y Offset', + on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off, + ) #t axis ref lvl scope_mode_box.AddSpacer(5) - t_off_slider = wx.Slider(self, size=SIZE, style=wx.SL_HORIZONTAL) - t_off_slider.SetRange(0, 1000) - def t_off_slider_changed(evt): parent[T_FRAC_OFF_KEY] = float(t_off_slider.GetValue())/t_off_slider.GetMax() - t_off_slider.Bind(wx.EVT_SLIDER, t_off_slider_changed) - parent.subscribe(T_FRAC_OFF_KEY, lambda x: t_off_slider.SetValue(int(round(x*t_off_slider.GetMax())))) - scope_mode_box.Add(common.LabelBox(self, 'T Offset', t_off_slider), 0, wx.EXPAND) + forms.slider( + parent=self, sizer=scope_mode_box, + ps=parent, key=T_FRAC_OFF_KEY, label='T Offset', + minimum=0, maximum=1, num_steps=1000, + ) scope_mode_box.AddSpacer(5) ################################################## # XY Mode Box ################################################## xy_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(xy_mode_box, 0, wx.EXPAND) + axes_options_box.Add(xy_mode_box, 0, wx.EXPAND) #x div controls - x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs) - parent.subscribe(AUTORANGE_KEY, lambda x: x_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'X/Div', x_buttons), 0, wx.EXPAND) + x_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='X/Div', + on_incr=self._on_incr_x_divs, on_decr=self._on_decr_x_divs, + ) #y div controls - y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'Y/Div', y_buttons), 0, wx.EXPAND) + y_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='Y/Div', + on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs, + ) #x offset controls - x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off) - parent.subscribe(AUTORANGE_KEY, lambda x: x_off_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'X Off', x_off_buttons), 0, wx.EXPAND) + x_off_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='X Off', + on_incr=self._on_incr_x_off, on_decr=self._on_decr_x_off, + ) #y offset controls - y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'Y Off', y_off_buttons), 0, wx.EXPAND) + y_off_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='Y Off', + on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off, + ) + for widget in (y_buttons_scope, y_off_buttons_scope, x_buttons, y_buttons, x_off_buttons, y_off_buttons): + parent.subscribe(AUTORANGE_KEY, widget.Disable) + widget.Disable(parent[AUTORANGE_KEY]) xy_mode_box.ShowItems(False) #autorange check box - self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY) - control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT) - control_box.AddStretchSpacer() + forms.check_box( + parent=self, sizer=axes_options_box, label='Autorange', + ps=parent, key=AUTORANGE_KEY, + ) ################################################## # Channel Options ################################################## TRIGGER_PAGE_INDEX = parent.num_inputs XY_PAGE_INDEX = parent.num_inputs+1 - control_box.Add(common.LabelText(self, 'Channel Options'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) + control_box.AddStretchSpacer() + chan_options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Channel Options', + bold=True, orient=wx.VERTICAL, + ) options_notebook = wx.Notebook(self) - control_box.Add(options_notebook, 0, wx.EXPAND) - def options_notebook_changed(evt): - try: - parent[TRIGGER_SHOW_KEY] = options_notebook.GetSelection() == TRIGGER_PAGE_INDEX - parent[XY_MODE_KEY] = options_notebook.GetSelection() == XY_PAGE_INDEX - except wx.PyDeadObjectError: pass - options_notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, options_notebook_changed) - def xy_mode_changed(mode): - #ensure xy tab is selected - if mode and options_notebook.GetSelection() != XY_PAGE_INDEX: - options_notebook.SetSelection(XY_PAGE_INDEX) - #ensure xy tab is not selected - elif not mode and options_notebook.GetSelection() == XY_PAGE_INDEX: - options_notebook.SetSelection(0) - #show/hide control buttons - scope_mode_box.ShowItems(not mode) - xy_mode_box.ShowItems(mode) - control_box.Layout() - parent.subscribe(XY_MODE_KEY, xy_mode_changed) + options_notebook_args = list() + CHANNELS = [('Ch %d'%(i+1), i) for i in range(parent.num_inputs)] ################################################## # Channel Menu Boxes ################################################## for i in range(parent.num_inputs): channel_menu_panel = wx.Panel(options_notebook) - options_notebook.AddPage(channel_menu_panel, 'Ch%d'%(i+1)) + options_notebook_args.append((channel_menu_panel, i, 'Ch%d'%(i+1))) channel_menu_box = wx.BoxSizer(wx.VERTICAL) channel_menu_panel.SetSizer(channel_menu_box) #ac couple check box channel_menu_box.AddStretchSpacer() - coupling_chooser = common.DropDownController(channel_menu_panel, COUPLING_MODES, parent, common.index_key(AC_COUPLE_KEY, i), SIZE) - channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Coupling', coupling_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=channel_menu_panel, sizer=channel_menu_box, + ps=parent, key=common.index_key(AC_COUPLE_KEY, i), + choices=map(lambda x: x[1], COUPLING_MODES), + labels=map(lambda x: x[0], COUPLING_MODES), + label='Coupling', width=WIDTH, + ) #marker channel_menu_box.AddStretchSpacer() - marker_chooser = common.DropDownController(channel_menu_panel, MARKER_TYPES, parent, common.index_key(MARKER_KEY, i), SIZE) - channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=channel_menu_panel, sizer=channel_menu_box, + ps=parent, key=common.index_key(MARKER_KEY, i), + choices=map(lambda x: x[1], MARKER_TYPES), + labels=map(lambda x: x[0], MARKER_TYPES), + label='Marker', width=WIDTH, + ) channel_menu_box.AddStretchSpacer() ################################################## # Trigger Menu Box ################################################## trigger_menu_panel = wx.Panel(options_notebook) - options_notebook.AddPage(trigger_menu_panel, 'Trig') + options_notebook_args.append((trigger_menu_panel, TRIGGER_PAGE_INDEX, 'Trig')) trigger_menu_box = wx.BoxSizer(wx.VERTICAL) trigger_menu_panel.SetSizer(trigger_menu_box) #trigger mode - trigger_mode_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_MODES, parent, TRIGGER_MODE_KEY, SIZE) - trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Mode', trigger_mode_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=trigger_menu_panel, sizer=trigger_menu_box, + ps=parent, key=TRIGGER_MODE_KEY, + choices=map(lambda x: x[1], TRIGGER_MODES), + labels=map(lambda x: x[0], TRIGGER_MODES), + label='Mode', width=WIDTH, + ) #trigger slope - trigger_slope_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_SLOPES, parent, TRIGGER_SLOPE_KEY, SIZE) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_slope_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) - trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Slope', trigger_slope_chooser), 0, wx.EXPAND) + trigger_slope_chooser = forms.drop_down( + parent=trigger_menu_panel, sizer=trigger_menu_box, + ps=parent, key=TRIGGER_SLOPE_KEY, + choices=map(lambda x: x[1], TRIGGER_SLOPES), + labels=map(lambda x: x[0], TRIGGER_SLOPES), + label='Slope', width=WIDTH, + ) #trigger channel - choices = [('Channel %d'%(i+1), i) for i in range(parent.num_inputs)] - trigger_channel_chooser = common.DropDownController(trigger_menu_panel, choices, parent, TRIGGER_CHANNEL_KEY, SIZE) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_channel_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) - trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Channel', trigger_channel_chooser), 0, wx.EXPAND) + trigger_channel_chooser = forms.drop_down( + parent=trigger_menu_panel, sizer=trigger_menu_box, + ps=parent, key=TRIGGER_CHANNEL_KEY, + choices=map(lambda x: x[1], CHANNELS), + labels=map(lambda x: x[0], CHANNELS), + label='Channel', width=WIDTH, + ) #trigger level hbox = wx.BoxSizer(wx.HORIZONTAL) trigger_menu_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(trigger_menu_panel, label=' Level '), 1, wx.ALIGN_CENTER_VERTICAL) - trigger_level_button = wx.Button(trigger_menu_panel, label='50%', style=wx.BU_EXACTFIT) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_button.Enable(x!=gr.gr_TRIG_MODE_FREE)) - trigger_level_button.Bind(wx.EVT_BUTTON, self.parent.set_auto_trigger_level) - hbox.Add(trigger_level_button, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(10) - trigger_level_buttons = common.IncrDecrButtons(trigger_menu_panel, self._on_incr_trigger_level, self._on_decr_trigger_level) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_buttons.Enable(x!=gr.gr_TRIG_MODE_FREE)) - hbox.Add(trigger_level_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.Add(wx.StaticText(trigger_menu_panel, label='Level:'), 1, wx.ALIGN_CENTER_VERTICAL) + trigger_level_button = forms.single_button( + parent=trigger_menu_panel, sizer=hbox, label='50%', + callback=parent.set_auto_trigger_level, style=wx.BU_EXACTFIT, + ) + hbox.AddSpacer(WIDTH-60) + trigger_level_buttons = forms.incr_decr_buttons( + parent=trigger_menu_panel, sizer=hbox, + on_incr=self._on_incr_trigger_level, on_decr=self._on_decr_trigger_level, + ) + def disable_all(trigger_mode): + for widget in (trigger_slope_chooser, trigger_channel_chooser, trigger_level_buttons, trigger_level_button): + widget.Disable(trigger_mode == gr.gr_TRIG_MODE_FREE) + parent.subscribe(TRIGGER_MODE_KEY, disable_all) + disable_all(parent[TRIGGER_MODE_KEY]) ################################################## # XY Menu Box ################################################## if parent.num_inputs > 1: xy_menu_panel = wx.Panel(options_notebook) - options_notebook.AddPage(xy_menu_panel, 'XY') + options_notebook_args.append((xy_menu_panel, XY_PAGE_INDEX, 'XY')) xy_menu_box = wx.BoxSizer(wx.VERTICAL) xy_menu_panel.SetSizer(xy_menu_box) #x and y channel choosers xy_menu_box.AddStretchSpacer() - choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] - x_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, X_CHANNEL_KEY, SIZE) - xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch X', x_channel_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=xy_menu_panel, sizer=xy_menu_box, + ps=parent, key=X_CHANNEL_KEY, + choices=map(lambda x: x[1], CHANNELS), + labels=map(lambda x: x[0], CHANNELS), + label='Channel X', width=WIDTH, + ) xy_menu_box.AddStretchSpacer() - y_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, Y_CHANNEL_KEY, SIZE) - xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch Y', y_channel_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=xy_menu_panel, sizer=xy_menu_box, + ps=parent, key=Y_CHANNEL_KEY, + choices=map(lambda x: x[1], CHANNELS), + labels=map(lambda x: x[0], CHANNELS), + label='Channel Y', width=WIDTH, + ) #marker xy_menu_box.AddStretchSpacer() - marker_chooser = common.DropDownController(xy_menu_panel, MARKER_TYPES, parent, XY_MARKER_KEY, SIZE) - xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=xy_menu_panel, sizer=xy_menu_box, + ps=parent, key=XY_MARKER_KEY, + choices=map(lambda x: x[1], MARKER_TYPES), + labels=map(lambda x: x[0], MARKER_TYPES), + label='Marker', width=WIDTH, + ) xy_menu_box.AddStretchSpacer() ################################################## + # Setup Options Notebook + ################################################## + forms.notebook( + parent=self, sizer=chan_options_box, + notebook=options_notebook, + ps=parent, key=CHANNEL_OPTIONS_KEY, + pages=map(lambda x: x[0], options_notebook_args), + choices=map(lambda x: x[1], options_notebook_args), + labels=map(lambda x: x[2], options_notebook_args), + ) + #gui handling for channel options changing + def options_notebook_changed(chan_opt): + try: + parent[TRIGGER_SHOW_KEY] = chan_opt == TRIGGER_PAGE_INDEX + parent[XY_MODE_KEY] = chan_opt == XY_PAGE_INDEX + except wx.PyDeadObjectError: pass + parent.subscribe(CHANNEL_OPTIONS_KEY, options_notebook_changed) + #gui handling for xy mode changing + def xy_mode_changed(mode): + #ensure xy tab is selected + if mode and parent[CHANNEL_OPTIONS_KEY] != XY_PAGE_INDEX: + parent[CHANNEL_OPTIONS_KEY] = XY_PAGE_INDEX + #ensure xy tab is not selected + elif not mode and parent[CHANNEL_OPTIONS_KEY] == XY_PAGE_INDEX: + parent[CHANNEL_OPTIONS_KEY] = 0 + #show/hide control buttons + scope_mode_box.ShowItems(not mode) + xy_mode_box.ShowItems(mode) + control_box.Layout() + parent.subscribe(XY_MODE_KEY, xy_mode_changed) + xy_mode_changed(parent[XY_MODE_KEY]) + ################################################## # Run/Stop Button ################################################## #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) #mouse wheel event @@ -323,28 +401,10 @@ class scope_window(wx.Panel, pubsub.pubsub): self.proxy(TRIGGER_SLOPE_KEY, controller, trigger_slope_key) self.proxy(TRIGGER_CHANNEL_KEY, controller, trigger_channel_key) self.proxy(DECIMATION_KEY, controller, decimation_key) - for i in range(num_inputs): - self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) - #init panel and plot - wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) - self.plotter = plotter.channel_plotter(self) - self.plotter.SetSize(wx.Size(*size)) - self.plotter.set_title(title) - self.plotter.enable_legend(True) - self.plotter.enable_point_label(True) - self.plotter.enable_grid_lines(True) - #setup the box with plot and controls - self.control_panel = control_panel(self) - main_box = wx.BoxSizer(wx.HORIZONTAL) - main_box.Add(self.plotter, 1, wx.EXPAND) - main_box.Add(self.control_panel, 0, wx.EXPAND) - self.SetSizerAndFit(main_box) #initialize values self[RUNNING_KEY] = True - for i in range(self.num_inputs): - self[common.index_key(AC_COUPLE_KEY, i)] = self[common.index_key(AC_COUPLE_KEY, i)] - self[common.index_key(MARKER_KEY, i)] = DEFAULT_MARKER_TYPE self[XY_MARKER_KEY] = 2.0 + self[CHANNEL_OPTIONS_KEY] = 0 self[XY_MODE_KEY] = xy_mode self[X_CHANNEL_KEY] = 0 self[Y_CHANNEL_KEY] = self.num_inputs-1 @@ -364,6 +424,22 @@ class scope_window(wx.Panel, pubsub.pubsub): self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS self[T_FRAC_OFF_KEY] = 0.5 + for i in range(num_inputs): + self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) + #init panel and plot + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) + self.plotter = plotter.channel_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.enable_legend(True) + self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) + #setup the box with plot and controls + self.control_panel = control_panel(self) + main_box = wx.BoxSizer(wx.HORIZONTAL) + main_box.Add(self.plotter, 1, wx.EXPAND) + main_box.Add(self.control_panel, 0, wx.EXPAND) + self.SetSizerAndFit(main_box) #register events for message self.subscribe(MSG_KEY, self.handle_msg) #register events for grid diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 8dcb4b61..77819b73 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -64,54 +65,75 @@ class control_panel(wx.Panel): wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - #color mode - control_box.AddStretchSpacer() - color_mode_chooser = common.DropDownController(self, COLOR_MODES, parent, COLOR_MODE_KEY) - control_box.Add(common.LabelBox(self, 'Color', color_mode_chooser), 0, wx.EXPAND) + options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Options', + bold=True, orient=wx.VERTICAL, + ) #average + forms.check_box( + sizer=options_box, parent=self, label='Average', + ps=parent, key=AVERAGE_KEY, + ) + avg_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Avg Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=AVG_ALPHA_KEY, width=50, + ) + avg_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=AVG_ALPHA_MIN_EXP, + max_exp=AVG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=AVG_ALPHA_KEY, + ) + for widget in (avg_alpha_text, avg_alpha_slider): + parent.subscribe(AVERAGE_KEY, widget.Enable) + widget.Enable(parent[AVERAGE_KEY]) + #begin axes box control_box.AddStretchSpacer() - average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) - control_box.Add(average_check_box, 0, wx.EXPAND) - control_box.AddSpacer(2) - avg_alpha_slider = common.LogSliderController( - self, 'Avg Alpha', - AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent, AVG_ALPHA_KEY, - formatter=lambda x: ': %.4f'%x, + axes_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Axes Options', + bold=True, orient=wx.VERTICAL, + ) + #num lines buttons + forms.incr_decr_buttons( + parent=self, sizer=axes_box, label='Time Scale', + on_incr=self._on_incr_time_scale, on_decr=self._on_decr_time_scale, ) - parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) - control_box.Add(avg_alpha_slider, 0, wx.EXPAND) #dyanmic range buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Dynamic Range'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range) - control_box.Add(dynamic_range_buttons, 0, wx.ALIGN_CENTER) + forms.incr_decr_buttons( + parent=self, sizer=axes_box, label='Dyn Range', + on_incr=self._on_incr_dynamic_range, on_decr=self._on_decr_dynamic_range, + ) #ref lvl buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) - control_box.Add(ref_lvl_buttons, 0, wx.ALIGN_CENTER) - #num lines buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Set Time Scale'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale) - control_box.Add(time_scale_buttons, 0, wx.ALIGN_CENTER) + forms.incr_decr_buttons( + parent=self, sizer=axes_box, label='Ref Level', + on_incr=self._on_incr_ref_level, on_decr=self._on_decr_ref_level, + ) + #color mode + forms.drop_down( + parent=self, sizer=axes_box, width=100, + ps=parent, key=COLOR_MODE_KEY, label='Color', + choices=map(lambda x: x[1], COLOR_MODES), + labels=map(lambda x: x[0], COLOR_MODES), + ) #autoscale - control_box.AddStretchSpacer() - autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(autoscale_button, 0, wx.EXPAND) + forms.single_button( + parent=self, sizer=axes_box, label='Autoscale', + callback=self.parent.autoscale, + ) #clear - clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT) - clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button) - control_box.Add(clear_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.single_button( + parent=self, sizer=control_box, label='Clear', + callback=self._on_clear_button, + ) #run/stop - run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(run_button, 0, wx.EXPAND) + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -181,18 +203,10 @@ class waterfall_window(wx.Panel, pubsub.pubsub): self.plotter.set_title(title) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(False) - #setup the box with plot and controls - self.control_panel = control_panel(self) - main_box = wx.BoxSizer(wx.HORIZONTAL) - main_box.Add(self.plotter, 1, wx.EXPAND) - main_box.Add(self.control_panel, 0, wx.EXPAND) - self.SetSizerAndFit(main_box) #plotter listeners self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode) self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines) #initialize values - self[AVERAGE_KEY] = self[AVERAGE_KEY] - self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] self[DYNAMIC_RANGE_KEY] = dynamic_range self[NUM_LINES_KEY] = num_lines self[Y_DIVS_KEY] = 8 @@ -201,6 +215,12 @@ class waterfall_window(wx.Panel, pubsub.pubsub): self[BASEBAND_FREQ_KEY] = baseband_freq self[COLOR_MODE_KEY] = COLOR_MODES[0][1] self[RUNNING_KEY] = True + #setup the box with plot and controls + self.control_panel = control_panel(self) + main_box = wx.BoxSizer(wx.HORIZONTAL) + main_box.Add(self.plotter, 1, wx.EXPAND) + main_box.Add(self.control_panel, 0, wx.EXPAND) + self.SetSizerAndFit(main_box) #register events self.subscribe(MSG_KEY, self.handle_msg) for key in ( diff --git a/grc/data/platforms/python/blocks/variable_chooser.xml b/grc/data/platforms/python/blocks/variable_chooser.xml index 6827c367..3ca33045 100644 --- a/grc/data/platforms/python/blocks/variable_chooser.xml +++ b/grc/data/platforms/python/blocks/variable_chooser.xml @@ -9,7 +9,7 @@ Variable Chooser variable_chooser - from grc_gnuradio.wxgui import forms + from gnuradio.wxgui import forms $value self['$id'] = $id self.subscribe('$id', self.set_$(id)) diff --git a/grc/data/platforms/python/blocks/variable_slider.xml b/grc/data/platforms/python/blocks/variable_slider.xml index 9c8e7ece..f8a5543f 100644 --- a/grc/data/platforms/python/blocks/variable_slider.xml +++ b/grc/data/platforms/python/blocks/variable_slider.xml @@ -8,7 +8,7 @@ Variable Slider variable_slider - from grc_gnuradio.wxgui import forms + from gnuradio.wxgui import forms $value self['$id'] = $id self.subscribe('$id', self.set_$(id)) diff --git a/grc/data/platforms/python/blocks/variable_text_box.xml b/grc/data/platforms/python/blocks/variable_text_box.xml index 2857fa36..b5acd9d2 100644 --- a/grc/data/platforms/python/blocks/variable_text_box.xml +++ b/grc/data/platforms/python/blocks/variable_text_box.xml @@ -8,7 +8,7 @@ Variable Text Box variable_text_box - from grc_gnuradio.wxgui import forms + from gnuradio.wxgui import forms $value self['$id'] = $id self.subscribe('$id', self.set_$(id)) diff --git a/grc/src/grc_gnuradio/wxgui/Makefile.am b/grc/src/grc_gnuradio/wxgui/Makefile.am index b82ca4c9..2e5e7ebd 100644 --- a/grc/src/grc_gnuradio/wxgui/Makefile.am +++ b/grc/src/grc_gnuradio/wxgui/Makefile.am @@ -25,9 +25,3 @@ ourpythondir = $(grc_gnuradio_prefix)/wxgui ourpython_PYTHON = \ __init__.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/forms/__init__.py b/grc/src/grc_gnuradio/wxgui/forms/__init__.py deleted file mode 100644 index 07226668..00000000 --- a/grc/src/grc_gnuradio/wxgui/forms/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -# -# 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) -- 2.47.2