diff options
Diffstat (limited to 'gr-wxgui/python')
41 files changed, 0 insertions, 11607 deletions
diff --git a/gr-wxgui/python/wxgui/CMakeLists.txt b/gr-wxgui/python/wxgui/CMakeLists.txt deleted file mode 100644 index a06cba70b7..0000000000 --- a/gr-wxgui/python/wxgui/CMakeLists.txt +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright 2011 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. - -######################################################################## -include(GrPython) - -######################################################################## -# Install python files into wxgui module -######################################################################## -GR_PYTHON_INSTALL( - FILES - __init__.py - common.py - constants.py - constsink_gl.py - const_window.py - form.py - fftsink2.py - fftsink_nongl.py - fftsink_gl.py - fft_window.py - gui.py - histosink_gl.py - histo_window.py - numbersink2.py - number_window.py - plot.py - powermate.py - pubsub.py - scopesink2.py - scopesink_nongl.py - scopesink_gl.py - scope_window.py - termsink.py - waterfallsink2.py - waterfallsink_nongl.py - waterfallsink_gl.py - waterfall_window.py - slider.py - stdgui2.py - DESTINATION ${GR_PYTHON_DIR}/gnuradio/wxgui -) - -######################################################################## -# Install python files into wxgui forms sub-module -######################################################################## -GR_PYTHON_INSTALL( - FILES - forms/__init__.py - forms/forms.py - forms/converters.py - DESTINATION ${GR_PYTHON_DIR}/gnuradio/wxgui/forms -) - -######################################################################## -# Install python files into wxgui plotter sub-module -######################################################################## -GR_PYTHON_INSTALL( - FILES - plotter/__init__.py - plotter/bar_plotter.py - plotter/channel_plotter.py - plotter/common.py - plotter/gltext.py - plotter/grid_plotter_base.py - plotter/plotter_base.py - plotter/waterfall_plotter.py - DESTINATION ${GR_PYTHON_DIR}/gnuradio/wxgui/plotter -) diff --git a/gr-wxgui/python/wxgui/__init__.py b/gr-wxgui/python/wxgui/__init__.py deleted file mode 100644 index 1c707434c0..0000000000 --- a/gr-wxgui/python/wxgui/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright 2011 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. -# - -''' -Provides a GUI interface using the Wx backend. -''' - -try: - from wxgui_swig import * -except ImportError: - import os - dirname, filename = os.path.split(os.path.abspath(__file__)) - __path__.append(os.path.join(dirname, "..", "..", "swig")) - from wxgui_swig import * diff --git a/gr-wxgui/python/wxgui/common.py b/gr-wxgui/python/wxgui/common.py deleted file mode 100644 index de2648466b..0000000000 --- a/gr-wxgui/python/wxgui/common.py +++ /dev/null @@ -1,296 +0,0 @@ -# -# Copyright 2008, 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. -# - -################################################## -# conditional disconnections of wx flow graph -################################################## -import wx -from gnuradio import gr -from gnuradio import blocks - -RUN_ALWAYS = gr.prefs().get_bool('wxgui', 'run_always', False) - - -class wxgui_hb(object): - """ - The wxgui hier block helper/wrapper class: - A hier block should inherit from this class to make use of the wxgui connect method. - To use, call wxgui_connect in place of regular connect; self.win must be defined. - The implementation will conditionally enable the copy block after the source (self). - This condition depends on weather or not the window is visible with the parent notebooks. - This condition will be re-checked on every ui update event. - """ - - def wxgui_connect(self, *points): - """ - Use wxgui connect when the first point is the self source of the hb. - The win property of this object should be set to the wx window. - When this method tries to connect self to the next point, - it will conditionally make this connection based on the visibility state. - All other points will be connected normally. - """ - try: - assert points[0] == self or points[0][0] == self - copy = blocks.copy(self.input_signature().sizeof_stream_item(0)) - handler = self._handler_factory(copy.set_enabled) - if not RUN_ALWAYS: - handler(False) # initially disable the copy block - else: - handler(True) # initially enable the copy block - self._bind_to_visible_event(win=self.win, handler=handler) - points = list(points) - points.insert(1, copy) # insert the copy block into the chain - except (AssertionError, IndexError): pass - self.connect(*points) # actually connect the blocks - - @staticmethod - def _handler_factory(handler): - """ - Create a function that will cache the visibility flag, - and only call the handler when that flag changes. - - Args: - handler: the function to call on a change - - Returns: - a function of 1 argument - """ - cache = [None] - - def callback(visible): - if cache[0] == visible: return - cache[0] = visible - # print visible, handler - if not RUN_ALWAYS: - handler(visible) - else: - handler(True) - return callback - - @staticmethod - def _bind_to_visible_event(win, handler): - """ - Bind a handler to a window when its visibility changes. - Specifically, call the handler when the window visibility changes. - This condition is checked on every update ui event. - - Args: - win: the wx window - handler: a function of 1 param - """ - # is the window visible in the hierarchy - def is_wx_window_visible(my_win): - while True: - parent = my_win.GetParent() - if not parent: return True # reached the top of the hierarchy - # if we are hidden, then finish, otherwise keep traversing up - if isinstance(parent, wx.Notebook) and parent.GetCurrentPage() != my_win: return False - my_win = parent - # call the handler, the arg is shown or not - def handler_factory(my_win, my_handler): - def callback(evt): - my_handler(is_wx_window_visible(my_win)) - evt.Skip() # skip so all bound handlers are called - return callback - handler = handler_factory(win, handler) - # bind the handler to all the parent notebooks - win.Bind(wx.EVT_UPDATE_UI, handler) - -################################################## -# Helpful Functions -################################################## - -# A macro to apply an index to a key -index_key = lambda key, i: "%s_%d" % (key, i+1) - - -def _register_access_method(destination, controller, key): - """ - Helper function for register access methods. - This helper creates distinct set and get methods for each key - and adds them to the destination object. - """ - def set(value): controller[key] = value - setattr(destination, 'set_'+key, set) - def get(): return controller[key] - setattr(destination, 'get_'+key, get) - - -def register_access_methods(destination, controller): - """ - Register setter and getter functions in the destination object for all keys in the controller. - - Args: - destination: the object to get new setter and getter methods - controller: the pubsub controller - """ - for key in controller.keys(): _register_access_method(destination, controller, key) - -################################################## -# Input Watcher Thread -################################################## -from gnuradio import gru - - -class input_watcher(gru.msgq_runner): - """ - Input watcher thread runs forever. - Read messages from the message queue. - Forward messages to the message handler. - """ - def __init__ (self, msgq, controller, msg_key, arg1_key='', arg2_key=''): - self._controller = controller - self._msg_key = msg_key - self._arg1_key = arg1_key - self._arg2_key = arg2_key - gru.msgq_runner.__init__(self, msgq, self.handle_msg) - - def handle_msg(self, msg): - if self._arg1_key: self._controller[self._arg1_key] = msg.arg1() - if self._arg2_key: self._controller[self._arg2_key] = msg.arg2() - self._controller[self._msg_key] = msg.to_string() - - -################################################## -# Shared Functions -################################################## -import numpy -import math - - -def get_exp(num): - """ - Get the exponent of the number in base 10. - - Args: - num: the floating point number - - Returns: - the exponent as an integer - """ - if num == 0: return 0 - return int(math.floor(math.log10(abs(num)))) - - -def get_clean_num(num): - """ - Get the closest clean number match to num with bases 1, 2, 5. - - Args: - num: the number - - Returns: - the closest number - """ - if num == 0: return 0 - sign = num > 0 and 1 or -1 - exp = get_exp(num) - nums = numpy.array((1, 2, 5, 10))*(10**exp) - return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))] - - -def get_clean_incr(num): - """ - Get the next higher clean number with bases 1, 2, 5. - - Args: - num: the number - - Returns: - the next higher number - """ - num = get_clean_num(num) - exp = get_exp(num) - coeff = int(round(num/10**exp)) - return { - -5: -2, - -2: -1, - -1: -.5, - 1: 2, - 2: 5, - 5: 10, - }[coeff]*(10**exp) - - -def get_clean_decr(num): - """ - Get the next lower clean number with bases 1, 2, 5. - - Args: - num: the number - - Returns: - the next lower number - """ - num = get_clean_num(num) - exp = get_exp(num) - coeff = int(round(num/10**exp)) - return { - -5: -10, - -2: -5, - -1: -2, - 1: .5, - 2: 1, - 5: 2, - }[coeff]*(10**exp) - - -def get_min_max(samples): - """ - Get the minimum and maximum bounds for an array of samples. - - Args: - samples: the array of real values - - Returns: - a tuple of min, max - """ - factor = 2.0 - mean = numpy.average(samples) - std = numpy.std(samples) - fft = numpy.abs(numpy.fft.fft(samples - mean)) - envelope = 2*numpy.max(fft)/len(samples) - ampl = max(std, envelope) or 0.1 - return mean - factor*ampl, mean + factor*ampl - - -def get_min_max_fft(fft_samps): - """ - Get the minimum and maximum bounds for an array of fft samples. - - Args: - samples: the array of real values - - Returns: - a tuple of min, max - """ - # get the peak level (max of the samples) - peak_level = numpy.max(fft_samps) - # separate noise samples - noise_samps = numpy.sort(fft_samps)[:len(fft_samps)/2] - # get the noise floor - noise_floor = numpy.average(noise_samps) - # get the noise deviation - noise_dev = numpy.std(noise_samps) - # determine the maximum and minimum levels - max_level = peak_level - min_level = noise_floor - abs(2*noise_dev) - return min_level, max_level diff --git a/gr-wxgui/python/wxgui/const_window.py b/gr-wxgui/python/wxgui/const_window.py deleted file mode 100644 index 2c499b54bd..0000000000 --- a/gr-wxgui/python/wxgui/const_window.py +++ /dev/null @@ -1,211 +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. -# - -################################################## -# Imports -################################################## -import plotter -import common -import wx -import numpy -import math -import pubsub -from constants import * -from gnuradio import gr #for gr.prefs -import forms - -################################################## -# Constants -################################################## -SLIDER_STEPS = 200 -LOOP_BW_MIN_EXP, LOOP_BW_MAX_EXP = -6, 0.0 -GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP = -6, -0.301 -DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'const_rate', 5) -DEFAULT_WIN_SIZE = (500, 400) -DEFAULT_CONST_SIZE = gr.prefs().get_long('wxgui', 'const_size', 2048) -CONST_PLOT_COLOR_SPEC = (0, 0, 1) -MARKER_TYPES = ( - ('Dot Small', 1.0), - ('Dot Medium', 2.0), - ('Dot Large', 3.0), - ('Line Link', None), -) -DEFAULT_MARKER_TYPE = 2.0 - -################################################## -# Constellation window control panel -################################################## -class control_panel(wx.Panel): - """ - A control panel with wx widgits to control the plotter. - """ - def __init__(self, parent): - """ - Create a new control panel. - - Args: - parent: the wx parent window - """ - self.parent = parent - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - parent[SHOW_CONTROL_PANEL_KEY] = True - parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) - control_box = forms.static_box_sizer( - parent=self, label='Options', - bold=True, orient=wx.VERTICAL, - ) - #loop_bw - control_box.AddStretchSpacer() - forms.text_box( - sizer=control_box, parent=self, label='Loop Bandwidth', - converter=forms.float_converter(), - ps=parent, key=LOOP_BW_KEY, - ) - forms.log_slider( - sizer=control_box, parent=self, - min_exp=LOOP_BW_MIN_EXP, - max_exp=LOOP_BW_MAX_EXP, - num_steps=SLIDER_STEPS, - ps=parent, key=LOOP_BW_KEY, - ) - #gain_mu - control_box.AddStretchSpacer() - 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, - ) - #marker - control_box.AddStretchSpacer() - 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() - 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) - -################################################## -# Constellation window with plotter and control panel -################################################## -class const_window(wx.Panel, pubsub.pubsub): - def __init__( - self, - parent, - controller, - size, - title, - msg_key, - loop_bw_key, - gain_mu_key, - gain_omega_key, - omega_key, - sample_rate_key, - ): - pubsub.pubsub.__init__(self) - #proxy the keys - self.proxy(MSG_KEY, controller, msg_key) - self.proxy(LOOP_BW_KEY, controller, loop_bw_key) - self.proxy(GAIN_MU_KEY, controller, gain_mu_key) - 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) - self.plotter.SetSize(wx.Size(*size)) - self.plotter.SetSizeHints(*size) - self.plotter.set_title(title) - self.plotter.set_x_label('Inphase') - self.plotter.set_y_label('Quadrature') - 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) - #alpha and gain mu 2nd orders - def set_gain_omega(gain_mu): self[GAIN_OMEGA_KEY] = .25*gain_mu**2 - self.subscribe(GAIN_MU_KEY, set_gain_omega) - #register events - self.subscribe(MSG_KEY, self.handle_msg) - self.subscribe(X_DIVS_KEY, self.update_grid) - self.subscribe(Y_DIVS_KEY, self.update_grid) - #initial update - self.update_grid() - - def handle_msg(self, msg): - """ - Plot the samples onto the complex grid. - - Args: - msg: the array of complex samples - """ - if not self[RUNNING_KEY]: return - #convert to complex floating point numbers - samples = numpy.fromstring(msg, numpy.complex64) - real = numpy.real(samples) - imag = numpy.imag(samples) - #plot - self.plotter.set_waveform( - channel=0, - samples=(real, imag), - color_spec=CONST_PLOT_COLOR_SPEC, - marker=self[MARKER_KEY], - ) - #update the plotter - self.plotter.update() - - def update_grid(self): - #update the x axis - x_max = 2.0 - self.plotter.set_x_grid(-x_max, x_max, common.get_clean_num(2.0*x_max/self[X_DIVS_KEY])) - #update the y axis - y_max = 2.0 - self.plotter.set_y_grid(-y_max, y_max, common.get_clean_num(2.0*y_max/self[Y_DIVS_KEY])) - #update plotter - self.plotter.update() - - - - diff --git a/gr-wxgui/python/wxgui/constants.py b/gr-wxgui/python/wxgui/constants.py deleted file mode 100644 index 08cc6a6343..0000000000 --- a/gr-wxgui/python/wxgui/constants.py +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright 2008,2010 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. -# - -################################################## -# Controller Keys -################################################## -AC_COUPLE_KEY = 'ac_couple' -ALPHA_KEY = 'alpha' -AUTORANGE_KEY = 'autorange' -AVERAGE_KEY = 'average' -AVG_ALPHA_KEY = 'avg_alpha' -USE_PERSISTENCE_KEY = 'use_persistence' -PERSIST_ALPHA_KEY = 'persist_alpha' -BASEBAND_FREQ_KEY = 'baseband_freq' -BETA_KEY = 'beta' -COLOR_MODE_KEY = 'color_mode' -DECIMATION_KEY = 'decimation' -DYNAMIC_RANGE_KEY = 'dynamic_range' -FRAME_RATE_KEY = 'frame_rate' -GAIN_MU_KEY = 'gain_mu' -GAIN_OMEGA_KEY = 'gain_omega' -MARKER_KEY = 'marker' -XY_MARKER_KEY = 'xy_marker' -MSG_KEY = 'msg' -NUM_LINES_KEY = 'num_lines' -OMEGA_KEY = 'omega' -PEAK_HOLD_KEY = 'peak_hold' -TRACE_STORE_KEY = 'trace_store' -TRACE_SHOW_KEY = 'trace_show' -REF_LEVEL_KEY = 'ref_level' -RUNNING_KEY = 'running' -SAMPLE_RATE_KEY = 'sample_rate' -TRIGGER_CHANNEL_KEY = 'trigger_channel' -TRIGGER_LEVEL_KEY = 'trigger_level' -TRIGGER_MODE_KEY = 'trigger_mode' -TRIGGER_SLOPE_KEY = 'trigger_slope' -TRIGGER_SHOW_KEY = 'trigger_show' -XY_MODE_KEY = 'xy_mode' -X_CHANNEL_KEY = 'x_channel' -Y_CHANNEL_KEY = 'y_channel' -T_FRAC_OFF_KEY = 't_frac_off' -T_DIVS_KEY = 't_divs' -T_OFF_KEY = 't_off' -T_PER_DIV_KEY = 't_per_div' -X_DIVS_KEY = 'x_divs' -X_OFF_KEY = 'x_off' -X_PER_DIV_KEY = 'x_per_div' -Y_DIVS_KEY = 'y_divs' -Y_OFF_KEY = 'y_off' -Y_PER_DIV_KEY = 'y_per_div' -Y_AXIS_LABEL = 'y_axis_label' -MAXIMUM_KEY = 'maximum' -MINIMUM_KEY = 'minimum' -NUM_BINS_KEY = 'num_bins' -FRAME_SIZE_KEY = 'frame_size' -CHANNEL_OPTIONS_KEY = 'channel_options' -SHOW_CONTROL_PANEL_KEY = 'show_control_panel' -LOOP_BW_KEY = 'loop_bw' diff --git a/gr-wxgui/python/wxgui/constsink_gl.py b/gr-wxgui/python/wxgui/constsink_gl.py deleted file mode 100644 index dea76ea753..0000000000 --- a/gr-wxgui/python/wxgui/constsink_gl.py +++ /dev/null @@ -1,142 +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. -# - -################################################## -# Imports -################################################## -import const_window -import common -from gnuradio import gr -from gnuradio import blocks -from gnuradio import analog -from pubsub import pubsub -from constants import * -import sys -try: - from gnuradio import digital -except ImportError: - sys.stderr.write("Error: could not import gnuradio.digital, please install gr-digitial.\n") - sys.exit(1) - -################################################## -# Constellation sink block (wrapper for old wxgui) -################################################## -class const_sink_c(gr.hier_block2, common.wxgui_hb): - """ - A constellation block with a gui window. - """ - - def __init__( - self, - parent, - title='', - sample_rate=1, - size=const_window.DEFAULT_WIN_SIZE, - frame_rate=const_window.DEFAULT_FRAME_RATE, - const_size=const_window.DEFAULT_CONST_SIZE, - #mpsk recv params - M=4, - theta=0, - loop_bw=6.28/100.0, - fmax=0.06, - mu=0.5, - gain_mu=0.005, - symbol_rate=1, - omega_limit=0.005, - ): - #init - gr.hier_block2.__init__( - self, - "const_sink", - gr.io_signature(1, 1, gr.sizeof_gr_complex), - gr.io_signature(0, 0, 0), - ) - #blocks - sd = blocks.stream_to_vector_decimator( - item_size=gr.sizeof_gr_complex, - sample_rate=sample_rate, - vec_rate=frame_rate, - vec_len=const_size, - ) - fmin = -fmax - gain_omega = .25*gain_mu**2 #redundant, will be updated - omega = 1 #set_sample_rate will update this - # Costas frequency/phase recovery loop - # Critically damped 2nd order PLL - self._costas = digital.costas_loop_cc(loop_bw, M) - # Timing recovery loop - # Critically damped 2nd order DLL - self._retime = digital.clock_recovery_mm_cc(omega, - gain_omega, - mu, gain_mu, - omega_limit) - #sync = gr.mpsk_receiver_cc( - # M, #psk order - # theta, - # alpha, - # beta, - # fmin, - # fmax, - # mu, - # gain_mu, - # omega, - # gain_omega, - # omega_limit, - #) - agc = analog.feedforward_agc_cc(16, 1) - msgq = gr.msg_queue(2) - sink = blocks.message_sink(gr.sizeof_gr_complex*const_size, msgq, True) - #controller - def setter(p, k, x): p[k] = x - self.controller = pubsub() - self.controller.subscribe(LOOP_BW_KEY, self._costas.set_loop_bandwidth) - self.controller.publish(LOOP_BW_KEY, self._costas.get_loop_bandwidth) - self.controller.subscribe(GAIN_MU_KEY, self._retime.set_gain_mu) - self.controller.publish(GAIN_MU_KEY, self._retime.gain_mu) - self.controller.subscribe(OMEGA_KEY, self._retime.set_omega) - self.controller.publish(OMEGA_KEY, self._retime.omega) - self.controller.subscribe(GAIN_OMEGA_KEY, self._retime.set_gain_omega) - self.controller.publish(GAIN_OMEGA_KEY, self._retime.gain_omega) - self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) - self.controller.subscribe(SAMPLE_RATE_KEY, lambda x: setter(self.controller, OMEGA_KEY, float(x)/symbol_rate)) - self.controller.publish(SAMPLE_RATE_KEY, sd.sample_rate) - #initial update - self.controller[SAMPLE_RATE_KEY] = sample_rate - #start input watcher - common.input_watcher(msgq, self.controller, MSG_KEY) - #create window - self.win = const_window.const_window( - parent=parent, - controller=self.controller, - size=size, - title=title, - msg_key=MSG_KEY, - loop_bw_key=LOOP_BW_KEY, - gain_mu_key=GAIN_MU_KEY, - gain_omega_key=GAIN_OMEGA_KEY, - omega_key=OMEGA_KEY, - sample_rate_key=SAMPLE_RATE_KEY, - ) - common.register_access_methods(self, self.win) - #connect - self.wxgui_connect(self, self._costas, self._retime, agc, sd, sink) - - diff --git a/gr-wxgui/python/wxgui/fft_window.py b/gr-wxgui/python/wxgui/fft_window.py deleted file mode 100644 index 024200556e..0000000000 --- a/gr-wxgui/python/wxgui/fft_window.py +++ /dev/null @@ -1,412 +0,0 @@ -# -# Copyright 2008, 2009, 2010 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. -# - -################################################## -# Imports -################################################## -import plotter -import common -import wx -import numpy -import math -import pubsub -from constants import * -from gnuradio import gr #for gr.prefs -import forms - -################################################## -# Constants -################################################## -SLIDER_STEPS = 100 -AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 -PERSIST_ALPHA_MIN_EXP, PERSIST_ALPHA_MAX_EXP = -2, 0 -DEFAULT_WIN_SIZE = (600, 300) -DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) -DB_DIV_MIN, DB_DIV_MAX = 1, 20 -FFT_PLOT_COLOR_SPEC = (0.3, 0.3, 1.0) -PEAK_VALS_COLOR_SPEC = (0.0, 0.8, 0.0) -EMPTY_TRACE = list() -TRACES = ('A', 'B') -TRACES_COLOR_SPEC = { - 'A': (1.0, 0.0, 0.0), - 'B': (0.8, 0.0, 0.8), -} - -################################################## -# FFT window control panel -################################################## -class control_panel(wx.Panel): - """ - A control panel with wx widgits to control the plotter and fft block chain. - """ - - def __init__(self, parent): - """ - Create a new control panel. - - Args: - parent: the wx parent window - """ - self.parent = parent - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - parent[SHOW_CONTROL_PANEL_KEY] = True - parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) - control_box = wx.BoxSizer(wx.VERTICAL) - control_box.AddStretchSpacer() - #checkboxes for average and peak hold - options_box = forms.static_box_sizer( - parent=self, sizer=control_box, label='Trace 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, - ) - for widget in (avg_alpha_text, avg_alpha_slider): - parent.subscribe(AVERAGE_KEY, widget.Enable) - widget.Enable(parent[AVERAGE_KEY]) - parent.subscribe(AVERAGE_KEY, widget.ShowItems) - #allways show initially, so room is reserved for them - widget.ShowItems(True) # (parent[AVERAGE_KEY]) - - parent.subscribe(AVERAGE_KEY, self._update_layout) - - forms.check_box( - sizer=options_box, parent=self, label='Persistence', - ps=parent, key=USE_PERSISTENCE_KEY, - ) - #static text and slider for persist alpha - persist_alpha_text = forms.static_text( - sizer=options_box, parent=self, label='Persist Alpha', - converter=forms.float_converter(lambda x: '%.4f'%x), - ps=parent, key=PERSIST_ALPHA_KEY, width=50, - ) - persist_alpha_slider = forms.log_slider( - sizer=options_box, parent=self, - min_exp=PERSIST_ALPHA_MIN_EXP, - max_exp=PERSIST_ALPHA_MAX_EXP, - num_steps=SLIDER_STEPS, - ps=parent, key=PERSIST_ALPHA_KEY, - ) - for widget in (persist_alpha_text, persist_alpha_slider): - parent.subscribe(USE_PERSISTENCE_KEY, widget.Enable) - widget.Enable(parent[USE_PERSISTENCE_KEY]) - parent.subscribe(USE_PERSISTENCE_KEY, widget.ShowItems) - #allways show initially, so room is reserved for them - widget.ShowItems(True) # (parent[USE_PERSISTENCE_KEY]) - - parent.subscribe(USE_PERSISTENCE_KEY, self._update_layout) - - #trace menu - for trace in TRACES: - trace_box = wx.BoxSizer(wx.HORIZONTAL) - options_box.Add(trace_box, 0, wx.EXPAND) - forms.check_box( - sizer=trace_box, parent=self, - ps=parent, key=TRACE_SHOW_KEY+trace, - label='Trace %s'%trace, - ) - trace_box.AddSpacer(10) - forms.single_button( - sizer=trace_box, parent=self, - ps=parent, key=TRACE_STORE_KEY+trace, - label='Store', style=wx.BU_EXACTFIT, - ) - trace_box.AddSpacer(10) - #radio buttons for div size - control_box.AddStretchSpacer() - y_ctrl_box = forms.static_box_sizer( - parent=self, sizer=control_box, label='Axis Options', - bold=True, orient=wx.VERTICAL, - ) - forms.incr_decr_buttons( - parent=self, sizer=y_ctrl_box, label='dB/Div', - on_incr=self._on_incr_db_div, on_decr=self._on_decr_db_div, - ) - #ref lvl buttons - 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 - forms.single_button( - sizer=y_ctrl_box, parent=self, label='Autoscale', - callback=self.parent.autoscale, - ) - #run/stop - 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 - def on_mouse_wheel(event): - if event.GetWheelRotation() < 0: self._on_incr_ref_level(event) - else: self._on_decr_ref_level(event) - parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel) - - ################################################## - # Event handlers - ################################################## - 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): - self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY] - def _on_incr_db_div(self, event): - self.parent[Y_PER_DIV_KEY] = min(DB_DIV_MAX, common.get_clean_incr(self.parent[Y_PER_DIV_KEY])) - def _on_decr_db_div(self, event): - self.parent[Y_PER_DIV_KEY] = max(DB_DIV_MIN, common.get_clean_decr(self.parent[Y_PER_DIV_KEY])) - ################################################## - # subscriber handlers - ################################################## - def _update_layout(self,key): - # Just ignore the key value we get - # we only need to now that the visability or size of something has changed - self.parent.Layout() - #self.parent.Fit() - -################################################## -# FFT window with plotter and control panel -################################################## -class fft_window(wx.Panel, pubsub.pubsub): - def __init__( - self, - parent, - controller, - size, - title, - real, - fft_size, - baseband_freq, - sample_rate_key, - y_per_div, - y_divs, - ref_level, - average_key, - avg_alpha_key, - peak_hold, - msg_key, - use_persistence, - persist_alpha, - ): - - pubsub.pubsub.__init__(self) - #setup - self.samples = EMPTY_TRACE - self.real = real - self.fft_size = fft_size - self._reset_peak_vals() - self._traces = dict() - #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[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 - self[USE_PERSISTENCE_KEY] = use_persistence - self[PERSIST_ALPHA_KEY] = persist_alpha - for trace in TRACES: - #a function that returns a function - #so the function wont use local trace - def new_store_trace(my_trace): - def store_trace(*args): - self._traces[my_trace] = self.samples - self.update_grid() - return store_trace - def new_toggle_trace(my_trace): - def toggle_trace(toggle): - #do an automatic store if toggled on and empty trace - if toggle and not len(self._traces[my_trace]): - self._traces[my_trace] = self.samples - self.update_grid() - return toggle_trace - self._traces[trace] = EMPTY_TRACE - self[TRACE_STORE_KEY+trace] = False - self[TRACE_SHOW_KEY+trace] = False - self.subscribe(TRACE_STORE_KEY+trace, new_store_trace(trace)) - self.subscribe(TRACE_SHOW_KEY+trace, new_toggle_trace(trace)) - #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.SetSizeHints(*size) - self.plotter.set_title(title) - self.plotter.enable_legend(True) - self.plotter.enable_point_label(True) - self.plotter.enable_grid_lines(True) - self.plotter.set_use_persistence(use_persistence) - self.plotter.set_persist_alpha(persist_alpha) - #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(AVERAGE_KEY, self._reset_peak_vals) - self.subscribe(MSG_KEY, self.handle_msg) - self.subscribe(SAMPLE_RATE_KEY, self.update_grid) - for key in ( - BASEBAND_FREQ_KEY, - Y_PER_DIV_KEY, X_DIVS_KEY, - Y_DIVS_KEY, REF_LEVEL_KEY, - ): self.subscribe(key, self.update_grid) - self.subscribe(USE_PERSISTENCE_KEY, self.plotter.set_use_persistence) - self.subscribe(PERSIST_ALPHA_KEY, self.plotter.set_persist_alpha) - #initial update - self.update_grid() - - def set_callback(self,callb): - self.plotter.set_callback(callb) - - def autoscale(self, *args): - """ - Autoscale the fft plot to the last frame. - Set the dynamic range and reference level. - """ - if not len(self.samples): return - min_level, max_level = common.get_min_max_fft(self.samples) - #set the range to a clean number of the dynamic range - self[Y_PER_DIV_KEY] = common.get_clean_num(1+(max_level - min_level)/self[Y_DIVS_KEY]) - #set the reference level to a multiple of y per div - self[REF_LEVEL_KEY] = self[Y_PER_DIV_KEY]*round(.5+max_level/self[Y_PER_DIV_KEY]) - - def _reset_peak_vals(self, *args): self.peak_vals = EMPTY_TRACE - - def handle_msg(self, msg): - """ - Handle the message from the fft sink message queue. - If complex, reorder the fft samples so the negative bins come first. - If real, keep take only the positive bins. - Plot the samples onto the grid as channel 1. - If peak hold is enabled, plot peak vals as channel 2. - - Args: - msg: the fft array as a character array - """ - if not self[RUNNING_KEY]: return - #convert to floating point numbers - samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame - num_samps = len(samples) - #reorder fft - if self.real: samples = samples[:(num_samps+2)/2] - else: samples = numpy.concatenate((samples[(num_samps+1)/2:], samples[:(num_samps+2)/2])) - self.samples = samples - #peak hold calculation - if self[PEAK_HOLD_KEY]: - if len(self.peak_vals) != len(samples): self.peak_vals = samples - self.peak_vals = numpy.maximum(samples, self.peak_vals) - #plot the peak hold - self.plotter.set_waveform( - channel='Peak', - samples=self.peak_vals, - color_spec=PEAK_VALS_COLOR_SPEC, - ) - else: - self._reset_peak_vals() - self.plotter.clear_waveform(channel='Peak') - #plot the fft - self.plotter.set_waveform( - channel='FFT', - samples=samples, - color_spec=FFT_PLOT_COLOR_SPEC, - ) - #update the plotter - self.plotter.update() - - def update_grid(self, *args): - """ - Update the plotter grid. - This update method is dependent on the variables below. - Determine the x and y axis grid parameters. - The x axis depends on sample rate, baseband freq, and x divs. - The y axis depends on y per div, y divs, and ref level. - """ - for trace in TRACES: - channel = '%s'%trace.upper() - if self[TRACE_SHOW_KEY+trace]: - self.plotter.set_waveform( - channel=channel, - samples=self._traces[trace], - color_spec=TRACES_COLOR_SPEC[trace], - ) - else: self.plotter.clear_waveform(channel=channel) - #grid parameters - sample_rate = self[SAMPLE_RATE_KEY] - baseband_freq = self[BASEBAND_FREQ_KEY] - y_per_div = self[Y_PER_DIV_KEY] - y_divs = self[Y_DIVS_KEY] - x_divs = self[X_DIVS_KEY] - ref_level = self[REF_LEVEL_KEY] - #determine best fitting x_per_div - if self.real: x_width = sample_rate/2.0 - else: x_width = sample_rate/1.0 - x_per_div = common.get_clean_num(x_width/x_divs) - #update the x grid - if self.real: - self.plotter.set_x_grid( - baseband_freq, - baseband_freq + sample_rate/2.0, - x_per_div, True, - ) - else: - self.plotter.set_x_grid( - baseband_freq - sample_rate/2.0, - baseband_freq + sample_rate/2.0, - x_per_div, True, - ) - #update x units - self.plotter.set_x_label('Frequency', 'Hz') - #update y grid - self.plotter.set_y_grid(ref_level-y_per_div*y_divs, ref_level, y_per_div) - #update y units - self.plotter.set_y_label('Power', 'dB') - #update plotter - self.plotter.update() diff --git a/gr-wxgui/python/wxgui/fftsink2.py b/gr-wxgui/python/wxgui/fftsink2.py deleted file mode 100644 index 3277cd3ffa..0000000000 --- a/gr-wxgui/python/wxgui/fftsink2.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 2008,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 gr - -p = gr.prefs() -style = p.get_string('wxgui', 'style', 'auto') - -if style == 'auto' or style == 'gl': - try: - import wx.glcanvas - from OpenGL.GL import * - from fftsink_gl import fft_sink_f, fft_sink_c - except ImportError: - if style == 'gl': - raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - else: - # Fall backto non-gl sinks - from fftsink_nongl import fft_sink_f, fft_sink_c -elif style == 'nongl': - from fftsink_nongl import fft_sink_f, fft_sink_c -else: - raise RuntimeError("Unknown wxgui style") diff --git a/gr-wxgui/python/wxgui/fftsink_gl.py b/gr-wxgui/python/wxgui/fftsink_gl.py deleted file mode 100644 index a0b245c98a..0000000000 --- a/gr-wxgui/python/wxgui/fftsink_gl.py +++ /dev/null @@ -1,204 +0,0 @@ -# -# Copyright 2008-2010,2012 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 __future__ import division - -################################################## -# Imports -################################################## -import fft_window -import common -from gnuradio import gr, fft -from gnuradio import analog -from gnuradio import blocks -from gnuradio.fft import logpwrfft -from pubsub import pubsub -from constants import * -import math - -################################################## -# FFT sink block (wrapper for old wxgui) -################################################## -class _fft_sink_base(gr.hier_block2, common.wxgui_hb): - """ - An fft block with real/complex inputs and a gui window. - """ - - def __init__( - self, - parent, - baseband_freq=0, - ref_scale=2.0, - y_per_div=10, - y_divs=8, - ref_level=50, - sample_rate=1, - fft_size=512, - fft_rate=fft_window.DEFAULT_FRAME_RATE, - average=False, - avg_alpha=None, - title='', - size=fft_window.DEFAULT_WIN_SIZE, - peak_hold=False, - win=None, - use_persistence=False, - persist_alpha=None, - **kwargs #do not end with a comma - ): - #ensure avg alpha - if avg_alpha is None: avg_alpha = 2.0/fft_rate - #ensure analog alpha - if persist_alpha is None: - actual_fft_rate=float(sample_rate/fft_size)/float(max(1,int(float((sample_rate/fft_size)/fft_rate)))) - #print "requested_fft_rate ",fft_rate - #print "actual_fft_rate ",actual_fft_rate - analog_cutoff_freq=0.5 # Hertz - #calculate alpha from wanted cutoff freq - persist_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_fft_rate) - - #init - gr.hier_block2.__init__( - self, - "fft_sink", - gr.io_signature(1, 1, self._item_size), - gr.io_signature(0, 0, 0), - ) - #blocks - fft = self._fft_chain( - sample_rate=sample_rate, - fft_size=fft_size, - frame_rate=fft_rate, - ref_scale=ref_scale, - avg_alpha=avg_alpha, - average=average, - win=win, - ) - msgq = gr.msg_queue(2) - sink = blocks.message_sink(gr.sizeof_float*fft_size, msgq, True) - - - #controller - self.controller = pubsub() - self.controller.subscribe(AVERAGE_KEY, fft.set_average) - self.controller.publish(AVERAGE_KEY, fft.average) - self.controller.subscribe(AVG_ALPHA_KEY, fft.set_avg_alpha) - self.controller.publish(AVG_ALPHA_KEY, fft.avg_alpha) - self.controller.subscribe(SAMPLE_RATE_KEY, fft.set_sample_rate) - self.controller.publish(SAMPLE_RATE_KEY, fft.sample_rate) - #start input watcher - common.input_watcher(msgq, self.controller, MSG_KEY) - #create window - self.win = fft_window.fft_window( - parent=parent, - controller=self.controller, - size=size, - title=title, - real=self._real, - fft_size=fft_size, - baseband_freq=baseband_freq, - sample_rate_key=SAMPLE_RATE_KEY, - y_per_div=y_per_div, - y_divs=y_divs, - ref_level=ref_level, - average_key=AVERAGE_KEY, - avg_alpha_key=AVG_ALPHA_KEY, - peak_hold=peak_hold, - msg_key=MSG_KEY, - use_persistence=use_persistence, - persist_alpha=persist_alpha, - ) - common.register_access_methods(self, self.win) - setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS - setattr(self.win, 'set_peak_hold', getattr(self, 'set_peak_hold')) #BACKWARDS - #connect - self.wxgui_connect(self, fft, sink) - - def set_callback(self,callb): - self.win.set_callback(callb) - -class fft_sink_f(_fft_sink_base): - _fft_chain = logpwrfft.logpwrfft_f - _item_size = gr.sizeof_float - _real = True - -class fft_sink_c(_fft_sink_base): - _fft_chain = logpwrfft.logpwrfft_c - _item_size = gr.sizeof_gr_complex - _real = False - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -import wx -from gnuradio.wxgui import stdgui2 - -class test_app_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) - - fft_size = 256 - - # build our flow graph - input_rate = 2048.0e3 - - #Generate some noise - noise = analog.noise_source_c(analog.GR_UNIFORM, 1.0/10) - - # Generate a complex sinusoid - #src1 = analog.sig_source_c(input_rate, analog.GR_SIN_WAVE, 2e3, 1) - src1 = analog.sig_source_c(input_rate, analog.GR_CONST_WAVE, 57.50e3, 1) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - thr1 = blocks.throttle(gr.sizeof_gr_complex, input_rate) - - sink1 = fft_sink_c(panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20, y_divs=10) - vbox.Add(sink1.win, 1, wx.EXPAND) - - combine1 = blocks.add_cc() - self.connect(src1, (combine1,0)) - self.connect(noise,(combine1,1)) - self.connect(combine1,thr1, sink1) - - #src2 = analog.sig_source_f(input_rate, analog.GR_SIN_WAVE, 2e3, 1) - src2 = analog.sig_source_f (input_rate, analog.GR_CONST_WAVE, 57.50e3, 1) - thr2 = blocks.throttle(gr.sizeof_float, input_rate) - sink2 = fft_sink_f(panel, title="Real Data", fft_size=fft_size*2, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20, y_divs=10) - vbox.Add(sink2.win, 1, wx.EXPAND) - - combine2 = blocks.add_ff() - c2f2 = blocks.complex_to_float() - - self.connect(src2, (combine2,0)) - self.connect(noise,c2f2,(combine2,1)) - self.connect(combine2, thr2,sink2) - -def main (): - app = stdgui2.stdapp(test_app_block, "FFT Sink Test App") - app.MainLoop() - -if __name__ == '__main__': - main() diff --git a/gr-wxgui/python/wxgui/fftsink_nongl.py b/gr-wxgui/python/wxgui/fftsink_nongl.py deleted file mode 100644 index c63f0fb268..0000000000 --- a/gr-wxgui/python/wxgui/fftsink_nongl.py +++ /dev/null @@ -1,656 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2003-2007,2009,2010,2012 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 gr, gru, fft -import gnuradio.filter as grfilter -from gnuradio import blocks -from gnuradio import analog -from gnuradio.wxgui import stdgui2 -from gnuradio.filter import window -import wx -import plot -import numpy -import math - -DIV_LEVELS = (1, 2, 5, 10, 20) - -default_fftsink_size = (640,240) -default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) - -class fft_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, - y_divs=8, ref_level=50, - sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, - average=False, avg_alpha=None, title='', - peak_hold=False, use_persistence=False, persist_alpha=0.2): - - # initialize common attributes - self.baseband_freq = baseband_freq - self.y_per_div=y_per_div - self.y_divs = y_divs - self.ref_level = ref_level - self.sample_rate = sample_rate - self.fft_size = fft_size - self.fft_rate = fft_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / fft_rate - else: - self.avg_alpha = avg_alpha - self.use_persistence = use_persistence - self.persist_alpha = persist_alpha - - self.title = title - self.peak_hold = peak_hold - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages - - def set_y_per_div(self, y_per_div): - self.y_per_div = y_per_div - - def set_ref_level(self, ref_level): - self.ref_level = ref_level - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - else: - self.avg.set_taps(1.0) - self.win.peak_vals = None - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.win.set_peak_hold(enable) - - def set_use_persistence(self, enable): - self.use_persistence = enable - self.win.set_use_persistence(enable) - - def set_persist_alpha(self, persist_alpha): - self.persist_alpha = persist_alpha - self.win.set_persist_alpha(persist_alpha) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_baseband_freq(self, baseband_freq): - self.baseband_freq = baseband_freq - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - self._set_n() - - def _set_n(self): - self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - def set_callback(self, callb): - return - -class fft_sink_f(gr.hier_block2, fft_sink_base): - def __init__(self, parent, baseband_freq=0, ref_scale=2.0, - y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False, - use_persistence=False, persist_alpha=0.2, **kwargs): - - gr.hier_block2.__init__(self, "fft_sink_f", - gr.io_signature(1, 1, gr.sizeof_float), - gr.io_signature(0,0,0)) - - fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, - y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold, use_persistence=use_persistence, - persist_alpha=persist_alpha) - - self.s2p = blocks.stream_to_vector(gr.sizeof_float, self.fft_size) - self.one_in_n = blocks.keep_one_in_n(gr.sizeof_float * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = fft.fft_vfc(self.fft_size, True, mywindow) - power = 0 - for tap in mywindow: - power += tap*tap - - self.c2magsq = blocks.complex_to_mag_squared(self.fft_size) - self.avg = grfilter.single_pole_iir_filter_ff(1.0, self.fft_size) - - # FIXME We need to add 3dB to all bins but the DC bin - self.log = blocks.nlog10_ff(10, self.fft_size, - -20*math.log10(self.fft_size) # Adjust for number of bins - -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale/2)) # Adjust for reference scale - - self.sink = blocks.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2magsq, self.avg, self.log, self.sink) - - self.win = fft_window(self, parent, size=size) - self.set_average(self.average) - self.set_peak_hold(self.peak_hold) - self.set_use_persistence(self.use_persistence) - self.set_persist_alpha(self.persist_alpha) - -class fft_sink_c(gr.hier_block2, fft_sink_base): - def __init__(self, parent, baseband_freq=0, ref_scale=2.0, - y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False, - use_persistence=False, persist_alpha=0.2, **kwargs): - - gr.hier_block2.__init__(self, "fft_sink_c", - gr.io_signature(1, 1, gr.sizeof_gr_complex), - gr.io_signature(0,0,0)) - - fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, - y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold, use_persistence=use_persistence, - persist_alpha=persist_alpha) - - self.s2p = blocks.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) - self.one_in_n = blocks.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = fft.fft_vcc(self.fft_size, True, mywindow) - power = 0 - for tap in mywindow: - power += tap*tap - - self.c2magsq = blocks.complex_to_mag_squared(self.fft_size) - self.avg = grfilter.single_pole_iir_filter_ff(1.0, self.fft_size) - - # FIXME We need to add 3dB to all bins but the DC bin - self.log = blocks.nlog10_ff(10, self.fft_size, - -20*math.log10(self.fft_size) # Adjust for number of bins - -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale/2)) # Adjust for reference scale - - self.sink = blocks.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2magsq, self.avg, self.log, self.sink) - - self.win = fft_window(self, parent, size=size) - self.set_average(self.average) - self.set_use_persistence(self.use_persistence) - self.set_persist_alpha(self.persist_alpha) - self.set_peak_hold(self.peak_hold) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class input_watcher (gru.msgq_runner): - def __init__ (self, msgq, fft_size, event_receiver, **kwds): - self.fft_size = fft_size - self.event_receiver = event_receiver - gru.msgq_runner.__init__(self, msgq, self.handle_msg) - - def handle_msg(self, msg): - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - -class control_panel(wx.Panel): - - class LabelText(wx.StaticText): - def __init__(self, window, label): - wx.StaticText.__init__(self, window, -1, label) - font = self.GetFont() - font.SetWeight(wx.FONTWEIGHT_BOLD) - font.SetUnderlined(True) - self.SetFont(font) - - def __init__(self, parent): - self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) - control_box = wx.BoxSizer(wx.VERTICAL) - - #checkboxes for average and peak hold - control_box.AddStretchSpacer() - control_box.Add(self.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - self.average_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Average") - self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.on_average) - control_box.Add(self.average_check_box, 0, wx.EXPAND) - self.use_persistence_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Persistence") - self.use_persistence_check_box.Bind(wx.EVT_CHECKBOX, parent.on_use_persistence) - control_box.Add(self.use_persistence_check_box, 0, wx.EXPAND) - self.peak_hold_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Peak Hold") - self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX, parent.on_peak_hold) - control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) - - #radio buttons for div size - control_box.AddStretchSpacer() - control_box.Add(self.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, -1, "%d dB/div"%y_per_div) - radio_button.Bind(wx.EVT_RADIOBUTTON, self.on_radio_button_change) - self.radio_buttons.append(radio_button) - radio_box.Add(radio_button, 0, wx.ALIGN_LEFT) - control_box.Add(radio_box, 0, wx.EXPAND) - - #ref lvl buttons - control_box.AddStretchSpacer() - control_box.Add(self.LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - button_box = wx.BoxSizer(wx.HORIZONTAL) - self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT) - self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level) - button_box.Add(self.ref_plus_button, 0, wx.ALIGN_CENTER) - self.ref_minus_button = wx.Button(self, -1, ' - ', style=wx.BU_EXACTFIT) - self.ref_minus_button.Bind(wx.EVT_BUTTON, parent.on_decr_ref_level) - button_box.Add(self.ref_minus_button, 0, wx.ALIGN_CENTER) - control_box.Add(button_box, 0, wx.ALIGN_CENTER) - control_box.AddStretchSpacer() - #set sizer - self.SetSizerAndFit(control_box) - #update - self.update() - - def update(self): - """ - Read the state of the fft plot settings and update the control panel. - """ - #update checkboxes - self.average_check_box.SetValue(self.parent.fftsink.average) - self.use_persistence_check_box.SetValue(self.parent.fftsink.use_persistence) - self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) - #update radio buttons - try: - index = list(DIV_LEVELS).index(self.parent.fftsink.y_per_div) - self.radio_buttons[index].SetValue(True) - except: pass - - def on_radio_button_change(self, evt): - selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] - index = self.radio_buttons.index(selected_radio_button) - self.parent.fftsink.set_y_per_div(DIV_LEVELS[index]) - -class fft_window (wx.Panel): - def __init__ (self, fftsink, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - - self.fftsink = fftsink - #init panel and plot - wx.Panel.__init__(self, parent, -1) - self.plot = plot.PlotCanvas(self, id, pos, size, style, name) - #setup the box with plot and controls - self.control_panel = control_panel(self) - main_box = wx.BoxSizer (wx.HORIZONTAL) - main_box.Add (self.plot, 1, wx.EXPAND) - main_box.Add (self.control_panel, 0, wx.EXPAND) - self.SetSizerAndFit(main_box) - - self.peak_hold = False - self.peak_vals = None - - self.use_persistence=False - self.persist_alpha=0.2 - - - self.plot.SetEnableGrid (True) - # self.SetEnableZoom (True) - # self.SetBackgroundColour ('black') - - self.build_popup_menu() - self.set_baseband_freq(self.fftsink.baseband_freq) - - EVT_DATA_EVENT (self, self.set_data) - wx.EVT_CLOSE (self, self.on_close_window) - self.plot.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - self.plot.Bind(wx.EVT_MOTION, self.evt_motion) - - self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) - - def set_scale(self, freq): - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) - if x >= 1e9: - self._scale_factor = 1e-9 - self._units = "GHz" - self._format = "%3.6f" - elif x >= 1e6: - self._scale_factor = 1e-6 - self._units = "MHz" - self._format = "%3.3f" - else: - self._scale_factor = 1e-3 - self._units = "kHz" - self._format = "%3.3f" - - def set_baseband_freq(self, baseband_freq): - if self.peak_hold: - self.peak_vals = None - self.set_scale(baseband_freq) - self.fftsink.set_baseband_freq(baseband_freq) - - def on_close_window (self, event): - print "fft_window:on_close_window" - self.keep_running = False - - - def set_data (self, evt): - dB = evt.data - L = len (dB) - - if self.peak_hold: - if self.peak_vals is None: - self.peak_vals = dB - else: - self.peak_vals = numpy.maximum(dB, self.peak_vals) - - if self.fftsink.input_is_real: # only plot 1/2 the points - x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate - * self._scale_factor / L)) - + self.fftsink.baseband_freq * self._scale_factor) - self._points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._points[:,0] = x_vals - self._points[:,1] = dB[0:L/2] - if self.peak_hold: - self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._peak_points[:,0] = x_vals - self._peak_points[:,1] = self.peak_vals[0:L/2] - else: - # the "negative freqs" are in the second half of the array - x_vals = ((numpy.arange (-L/2, L/2) - * (self.fftsink.sample_rate * self._scale_factor / L)) - + self.fftsink.baseband_freq * self._scale_factor) - self._points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._points[:,0] = x_vals - self._points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) - if self.peak_hold: - self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._peak_points[:,0] = x_vals - self._peak_points[:,1] = numpy.concatenate ((self.peak_vals[L/2:], self.peak_vals[0:L/2])) - - lines = [plot.PolyLine (self._points, colour='BLUE'),] - if self.peak_hold: - lines.append(plot.PolyLine (self._peak_points, colour='GREEN')) - - graphics = plot.PlotGraphics (lines, - title=self.fftsink.title, - xLabel = self._units, yLabel = "dB") - x_range = x_vals[0], x_vals[-1] - ymax = self.fftsink.ref_level - ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs - y_range = ymin, ymax - self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) - - def set_use_persistence(self, enable): - self.use_persistence = enable - self.plot.set_use_persistence( enable) - - def set_persist_alpha(self, persist_alpha): - self.persist_alpha = persist_alpha - self.plot.set_persist_alpha(persist_alpha) - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.peak_vals = None - - def on_average(self, evt): - # print "on_average" - self.fftsink.set_average(evt.IsChecked()) - self.control_panel.update() - - def on_use_persistence(self, evt): - # print "on_analog" - self.fftsink.set_use_persistence(evt.IsChecked()) - self.control_panel.update() - - def on_peak_hold(self, evt): - # print "on_peak_hold" - self.fftsink.set_peak_hold(evt.IsChecked()) - self.control_panel.update() - - def on_incr_ref_level(self, evt): - # print "on_incr_ref_level" - self.fftsink.set_ref_level(self.fftsink.ref_level - + self.fftsink.y_per_div) - - def on_decr_ref_level(self, evt): - # print "on_decr_ref_level" - self.fftsink.set_ref_level(self.fftsink.ref_level - - self.fftsink.y_per_div) - - def on_incr_y_per_div(self, evt): - # print "on_incr_y_per_div" - self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, DIV_LEVELS)) - self.control_panel.update() - - def on_decr_y_per_div(self, evt): - # print "on_decr_y_per_div" - self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, DIV_LEVELS)) - self.control_panel.update() - - def on_y_per_div(self, evt): - # print "on_y_per_div" - Id = evt.GetId() - if Id == self.id_y_per_div_1: - self.fftsink.set_y_per_div(1) - elif Id == self.id_y_per_div_2: - self.fftsink.set_y_per_div(2) - elif Id == self.id_y_per_div_5: - self.fftsink.set_y_per_div(5) - elif Id == self.id_y_per_div_10: - self.fftsink.set_y_per_div(10) - elif Id == self.id_y_per_div_20: - self.fftsink.set_y_per_div(20) - self.control_panel.update() - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.plot.PopupMenu(menu, event.GetPosition()) - - def evt_motion(self, event): - if not hasattr(self, "_points"): - return # Got here before first window data update - - # Clip to plotted values - (ux, uy) = self.plot.GetXY(event) # Scaled position - x_vals = numpy.array(self._points[:,0]) - if ux < x_vals[0] or ux > x_vals[-1]: - tip = self.GetToolTip() - if tip: - tip.Enable(False) - return - - # Get nearest X value (is there a better way)? - ind = numpy.argmin(numpy.abs(x_vals-ux)) - x_val = x_vals[ind] - db_val = self._points[ind, 1] - text = (self._format+" %s dB=%3.3f") % (x_val, self._units, db_val) - - # Display the tooltip - tip = wx.ToolTip(text) - tip.Enable(True) - tip.SetDelay(0) - self.SetToolTip(tip) - - def build_popup_menu(self): - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_y_per_div = wx.NewId() - self.id_decr_y_per_div = wx.NewId() - self.id_y_per_div_1 = wx.NewId() - self.id_y_per_div_2 = wx.NewId() - self.id_y_per_div_5 = wx.NewId() - self.id_y_per_div_10 = wx.NewId() - self.id_y_per_div_20 = wx.NewId() - self.id_average = wx.NewId() - self.id_use_persistence = wx.NewId() - self.id_peak_hold = wx.NewId() - - self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - self.plot.Bind(wx.EVT_MENU, self.on_use_persistence, id=self.id_use_persistence) - self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) - self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - self.plot.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) - self.plot.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - menu.AppendCheckItem(self.id_use_persistence, "Persistence") - menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") - menu.Append(self.id_incr_ref_level, "Incr Ref Level") - menu.Append(self.id_decr_ref_level, "Decr Ref Level") - # menu.Append(self.id_incr_y_per_div, "Incr dB/div") - # menu.Append(self.id_decr_y_per_div, "Decr dB/div") - menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") - menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") - menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") - menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") - menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") - - self.checkmarks = { - self.id_average : lambda : self.fftsink.average, - self.id_use_persistence : lambda : self.fftsink.use_persistence, - self.id_peak_hold : lambda : self.fftsink.peak_hold, - self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, - self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, - self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, - self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, - self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, - } - - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -class test_app_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - fft_size = 256 - - # build our flow graph - input_rate = 100*20.48e3 - - # Generate a complex sinusoid - #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) - src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) - noise1 = analog.noise_source_c(analog.GR_UNIFORM, 1.0/10) - add1 = blocks.add_cc() - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - thr1 = blocks.throttle(gr.sizeof_gr_complex, input_rate) - - sink1 = fft_sink_c(panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20, y_divs=10) - vbox.Add(sink1.win, 1, wx.EXPAND) - - self.connect(src1, (add1,0)) - self.connect(noise1, (add1,1)) - self.connect(add1, thr1, sink1) - - #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) - src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) - noise2 = analog.noise_source_f(analog.GR_UNIFORM, 1.0/10) - add2 = blocks.add_ff() - - thr2 = gr.throttle(gr.sizeof_float, input_rate) - sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20, y_divs=10) - vbox.Add (sink2.win, 1, wx.EXPAND) - - self.connect(src2, (add2,0)) - self.connect(noise2, (add2,1)) - self.connect(add2, thr2, sink2) - -def main (): - app = stdgui2.stdapp(test_app_block, "FFT Sink Test App") - app.MainLoop() - -if __name__ == '__main__': - main () diff --git a/gr-wxgui/python/wxgui/form.py b/gr-wxgui/python/wxgui/form.py deleted file mode 100644 index 0442e49c84..0000000000 --- a/gr-wxgui/python/wxgui/form.py +++ /dev/null @@ -1,391 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2005 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -import wx -from gnuradio import eng_notation - -# ---------------------------------------------------------------- -# Wrappers for certain widgets -# ---------------------------------------------------------------- - -def button_with_callback(parent, label, callback): - new_id = wx.NewId() - btn = wx.Button(parent, new_id, label) - wx.EVT_BUTTON(parent, new_id, lambda evt: callback()) - return btn - - -# ---------------------------------------------------------------- -# Format converters -# ---------------------------------------------------------------- - -class abstract_converter(object): - def value_to_prim(self, v): - """ - Convert from user specified value to value acceptable to underlying primitive. - The underlying primitive usually expects strings. - """ - raise NotImplementedError - def prim_to_value(self, s): - """ - Convert from underlying primitive value to user specified value. - The underlying primitive usually expects strings. - """ - raise NotImplementedError - def help(self): - return "Any string is acceptable" - -class identity_converter(abstract_converter): - def value_to_prim(self,v): - return v - def prim_to_value(self, s): - return s - -class int_converter(abstract_converter): - def value_to_prim(self, v): - return str(v) - def prim_to_value(self, s): - return int(s, 0) - def help(self): - return "Enter an integer. Leading 0x indicates hex" - -class float_converter(abstract_converter): - def value_to_prim(self, v): - return eng_notation.num_to_str(v) - def prim_to_value(self, s): - return eng_notation.str_to_num(s) - def help(self): - return "Enter a float with optional scale suffix. E.g., 100.1M" - - -# ---------------------------------------------------------------- -# Various types of data entry fields -# ---------------------------------------------------------------- - -class field(object): - """ - A field in a form. - """ - def __init__(self, converter, value): - self.converter = converter - if value is not None: - self.set_value(value) - - def set_value(self, v): - self._set_prim_value(self.converter.value_to_prim(v)) - - def get_value(self): - return self.converter.prim_to_value(self._get_prim_value()) - - def get_value_with_check(self): - """ - Returns (value, error_msg), where error_msg is not None if there was problem - """ - try: - return (self.get_value(), None) - except: - return (None, self._error_msg()) - - def _set_prim_value(self, v): - raise NotImplementedError - - def _get_prim_value(self): - raise NotImplementedError - - def _pair_with_label(self, widget, parent=None, sizer=None, label=None, weight=1): - self.label = label - if label is None: - sizer.Add (widget, weight, wx.EXPAND) - return widget - elif 0: - hbox = wx.BoxSizer(wx.HORIZONTAL) - label_widget = wx.StaticText(parent, -1, label + ': ') - hbox.Add(label_widget, 0, wx.EXPAND) - hbox.Add(widget, 1, wx.EXPAND) - sizer.Add(hbox, weight, wx.EXPAND) - return widget - else: - label_widget = wx.StaticText(parent, -1, label + ': ') - sizer.Add(label_widget, 0, wx.EXPAND) - sizer.Add(widget, weight, wx.EXPAND) - return widget - - def _error_msg(self): - prefix = '' - if self.label: - prefix = self.label + ': ' - return "%s%s is invalid. %s" % (prefix, self._get_prim_value(), - self.converter.help()) - -# static (display-only) text fields - -class static_text_field(field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - converter=identity_converter(), weight=0): - self.f = self._pair_with_label(wx.StaticText(parent, -1, ""), - parent=parent, sizer=sizer, label=label, weight=weight) - field.__init__(self, converter, value) - - def _get_prim_value(self): - return self.f.GetLabel() - - def _set_prim_value(self, v): - self.f.SetLabel(v) - - -class static_int_field(static_text_field): - def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0): - static_text_field.__init__(self, parent, sizer, label, value, int_converter(), weight) - -class static_float_field(static_text_field): - def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0): - static_text_field.__init__(self, parent, sizer, label, value, float_converter(), weight) - - -# editable text fields - -class text_field(field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - converter=identity_converter(), callback=None, weight=1): - style = 0 - if callback: - style = wx.TE_PROCESS_ENTER - - new_id = wx.NewId() - w = wx.TextCtrl(parent, new_id, "", style=style) - self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) - if callback: - wx.EVT_TEXT_ENTER(w, new_id, lambda evt: callback()) - field.__init__(self, converter, value) - - def _get_prim_value(self): - return self.f.GetValue() - - def _set_prim_value(self, v): - self.f.SetValue(v) - - -class int_field(text_field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - callback=None, weight=1): - text_field.__init__(self, parent, sizer, label, value, int_converter(), callback, weight) - -class float_field(text_field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - callback=None, weight=1): - text_field.__init__(self, parent, sizer, label, value, float_converter(), callback, weight) - -# other fields - -class slider_field(field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - converter=identity_converter(), callback=None, min=0, max=100, weight=1): - new_id = wx.NewId() - w = wx.Slider(parent, new_id, (max+min)/2, min, max, - size=wx.Size(250, -1), style=wx.SL_HORIZONTAL | wx.SL_LABELS) - self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) - if callback: - wx.EVT_COMMAND_SCROLL(w, new_id, lambda evt: callback(evt.GetInt())) - field.__init__(self, converter, value) - - def _get_prim_value(self): - return self.f.GetValue() - - def _set_prim_value(self, v): - self.f.SetValue(int(v)) - -class quantized_slider_field(field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - converter=identity_converter(), callback=None, range=None, weight=1): - if not isinstance(range, (tuple, list)) or len(range) != 3: - raise ValueError, range - - self.min = range[0] - self.max = range[1] - self.step_size = float(range[2]) - nsteps = int((self.max-self.min)/self.step_size) - - new_id = wx.NewId() - w = wx.Slider(parent, new_id, 0, 0, nsteps, - size=wx.Size(250, -1), style=wx.SL_HORIZONTAL) - self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) - if callback: - wx.EVT_COMMAND_SCROLL(w, new_id, - lambda evt: callback(self._map_out(evt.GetInt()))) - field.__init__(self, converter, value) - - def _get_prim_value(self): - return self._map_out(self.f.GetValue()) - - def _set_prim_value(self, v): - self.f.SetValue(self._map_in(v)) - - def _map_in(self, x): - return int((x-self.min) / self.step_size) - - def _map_out(self, x): - return x * self.step_size + self.min - -class checkbox_field(field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - converter=identity_converter(), callback=None, weight=1): - new_id = wx.NewId() - w = wx.CheckBox(parent, new_id, label, style=wx.CHK_2STATE) - self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight) - if callback: - wx.EVT_CHECKBOX(w, new_id, lambda evt: callback(evt.GetInt())) - field.__init__(self, converter, value) - - def _get_prim_value(self): - return self.f.GetValue() - - def _set_prim_value(self, v): - self.f.SetValue(int(v)) - - -class radiobox_field(field): - def __init__(self, parent=None, sizer=None, label=None, value=None, - converter=identity_converter(), callback=None, weight=1, - choices=None, major_dimension=1, specify_rows=False): - new_id = wx.NewId() - - if specify_rows: - style=wx.RA_SPECIFY_ROWS | wx.RA_HORIZONTAL - else: - style=wx.RA_SPECIFY_COLS | wx.RA_HORIZONTAL - - w = wx.RadioBox(parent, new_id, label=label, style=style, majorDimension=major_dimension, - choices=choices) - self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight) - if callback: - wx.EVT_RADIOBOX(w, new_id, lambda evt: callback(evt.GetString())) - field.__init__(self, converter, value) - - def _get_prim_value(self): - return self.f.GetStringSelection() - - def _set_prim_value(self, v): - self.f.SetStringSelection(str(v)) - -# ---------------------------------------------------------------- -# the form class -# ---------------------------------------------------------------- - -class form(dict): - def __init__(self): - dict.__init__(self) - - def check_input_for_errors(self): - """ - Returns list of error messages if there's trouble, - else empty list. - """ - vals = [f.get_value_with_check() for f in self.values()] - return [t[1] for t in vals if t[1] is not None] - - def get_key_vals(self): - d = {} - for (key, f) in self.items(): - d[key] = f.get_value() - return d - - - def _nop(*args): pass - - def check_input_and_call(self, callback, status_handler=_nop): - """ - Return a function that checks the form for errors, and then if it's OK, - invokes the user specified callback, passing it the form key/value dictionary. - status_handler is called with a string indicating results. - """ - def doit_callback(*ignore): - errors = self.check_input_for_errors() - if errors: - status_handler(errors[0]) - #print '\n'.join(tuple(errors)) - else: - kv = self.get_key_vals() - if callback(kv): - status_handler("OK") - else: - status_handler("Failed") - - return doit_callback - - - -# ---------------------------------------------------------------- -# Stand-alone example code -# ---------------------------------------------------------------- - -import sys -from gnuradio.wxgui import stdgui2 - -class demo_app_flow_graph (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - self.frame = frame - self.panel = panel - - def _print_kv(kv): - print "kv =", kv - return True - - self.form = form() - - self.form['static1'] = \ - static_text_field(parent=panel, sizer=vbox, - label="Static Text", - value="The Static Value") - - self.form['text1'] = \ - text_field(parent=panel, sizer=vbox, - label="TextCtrl", - value="The Editable Value") - - self.form['int1'] = \ - int_field(parent=panel, sizer=vbox, - label="Int Field", - value=1234) - - self.form['float1'] = \ - float_field(parent=panel, sizer=vbox, - label="Float Field", - value=3.14159) - - self.doit = button_with_callback( - panel, "Do It!", - self.form.check_input_and_call(_print_kv, self._set_status_msg)) - - vbox.Add(self.doit, 0, wx.CENTER) - - def _set_status_msg(self, msg): - self.frame.GetStatusBar().SetStatusText(msg, 0) - - -def main (): - app = stdgui2.stdapp(demo_app_flow_graph, "wxgui form demo", nstatus=1) - app.MainLoop () - -if __name__ == '__main__': - main () diff --git a/gr-wxgui/python/wxgui/forms/__init__.py b/gr-wxgui/python/wxgui/forms/__init__.py deleted file mode 100644 index 058fa2ec28..0000000000 --- a/gr-wxgui/python/wxgui/forms/__init__.py +++ /dev/null @@ -1,102 +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, gauge, \ - make_bold, DataEvent, EVT_DATA - -######################################################################## -# Helpful widgets -######################################################################## -import wx - -class static_box_sizer(wx.StaticBoxSizer): - """ - A box sizer with label and border. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - label: title label for this widget (optional) - bold: true to boldify the label - 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. - - Args: - parent: the parent widget - on_incr: the callback for pressing the + button - on_decr: the callback for pressing the - button - label: title label for this widget (optional) - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - """ - def __init__(self, parent, on_incr, on_decr, label='', sizer=None, proportion=0, flag=wx.EXPAND): - 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/gr-wxgui/python/wxgui/forms/converters.py b/gr-wxgui/python/wxgui/forms/converters.py deleted file mode 100644 index db14d2752c..0000000000 --- a/gr-wxgui/python/wxgui/forms/converters.py +++ /dev/null @@ -1,154 +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. -# - -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): - #choices must be a list because tuple does not have .index() in python2.5 - self._choices = list(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): - if v == self._true: return True - if v == self._false: return False - raise Exception, 'Value "%s" is not a possible option.'%v - def internal_to_external(self, v): - if v: return self._true - else: return self._false - def help(self): - return "Value must be in (%s, %s)."%(self._true, self._false) - -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 __init__(self, formatter=lambda x: '%s'%(x)): - self._formatter = formatter - def external_to_internal(self, v): - return self._formatter(v) - 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 __init__(self, formatter=lambda x: '%s'%(x)): - self._formatter = formatter - def external_to_internal(self, v): - return self._formatter(v) - def internal_to_external(self, s): - return str(s) - -class int_converter(abstract_converter): - def __init__(self, formatter=lambda x: '%d'%round(x)): - self._formatter = formatter - def external_to_internal(self, v): - return self._formatter(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 __init__(self, formatter=eng_notation.num_to_str): - self._formatter = formatter - def external_to_internal(self, v): - return self._formatter(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/gr-wxgui/python/wxgui/forms/forms.py b/gr-wxgui/python/wxgui/forms/forms.py deleted file mode 100644 index f819f13397..0000000000 --- a/gr-wxgui/python/wxgui/forms/forms.py +++ /dev/null @@ -1,675 +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 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 - -Known problems: - * An empty label in the radio box still consumes space. - * The static text cannot resize the parent at runtime. -""" - -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 - -def make_bold(widget): - font = widget.GetFont() - font.SetWeight(wx.FONTWEIGHT_BOLD) - widget.SetFont(font) - -######################################################################## -# 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._key = key - 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 __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. - 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. - - Args: - widget: the main widget - label: the optional label - flag: additional flags for widget - label_prop: the proportion for the label - widget_prop: the proportion for the 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, 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, 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]) - #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) - - 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 - if self._callback: self._callback(self[EXT_KEY]) - - def _err_msg(self, value, e): - 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 - 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() - -######################################################################## -# 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(int(round(value))) - -######################################################################## -# Static Text Form -######################################################################## -class static_text(_form_base): - """ - A text box form. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - label: title label for this widget (optional) - width: the width of the form in px - bold: true to bold-ify the text (default=False) - units: a suffix to add after the text - converter: forms.str_converter(), int_converter(), float_converter()... - """ - def __init__(self, label='', width=-1, bold=False, units='', converter=converters.str_converter(), **kwargs): - self._units = units - _form_base.__init__(self, converter=converter, **kwargs) - self._static_text = wx.StaticText(self._parent, size=wx.Size(width, -1)) - if bold: make_bold(self._static_text) - self._add_widget(self._static_text, label) - - def _update(self, label): - if self._units: label += ' ' + self._units - self._static_text.SetLabel(label); self._parent.Layout() - -######################################################################## -# Text Box Form -######################################################################## -class text_box(_form_base): - """ - A text box form. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - label: title label for this widget (optional) - width: the width of the form in px - 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) - self._default_bg_colour = self._text_box.GetBackgroundColour() - self._text_box.Bind(wx.EVT_TEXT_ENTER, self._handle) - self._text_box.Bind(wx.EVT_TEXT, self._update_color) - self._add_widget(self._text_box, label) - - def _update_color(self, *args): - if self._text_box.GetValue() == self[INT_KEY]: - self._text_box.SetBackgroundColour(self._default_bg_colour) - else: self._text_box.SetBackgroundColour('#EEDDDD') - - def _handle(self, event): self[INT_KEY] = self._text_box.GetValue() - def _update(self, value): self._text_box.SetValue(value); self._update_color() - -######################################################################## -# Slider Form -# Linear Slider -# Logarithmic Slider -######################################################################## -class slider(_slider_base): - """ - A generic linear slider. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - label: title label for this widget (optional) - length: the length of the slider in px (optional) - style: wx.SL_HORIZONTAL or wx.SL_VERTICAL (default=horizontal) - minimum: the minimum value - maximum: the maximum value - num_steps: the number of slider steps (or specify step_size) - step_size: the step between slider jumps (or specify num_steps) - 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 logarithmic slider. - The sliders min and max values are base**min_exp and base**max_exp. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - label: title label for this widget (optional) - length: the length of the slider in px (optional) - style: wx.SL_HORIZONTAL or wx.SL_VERTICAL (default=horizontal) - min_exp: the minimum exponent - max_exp: the maximum exponent - base: the exponent base in base**exp - num_steps: the number of slider steps (or specify step_size) - 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 - 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) - -######################################################################## -# Gauge Form -######################################################################## -class gauge(_form_base): - """ - A gauge bar. - The gauge displays floating point values between the minimum and maximum. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - label: title label for this widget (optional) - length: the length of the slider in px (optional) - style: wx.GA_HORIZONTAL or wx.GA_VERTICAL (default=horizontal) - minimum: the minimum value - maximum: the maximum value - num_steps: the number of slider steps (or specify step_size) - 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. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - true: the value for form when checked (default=True) - false: the value for form when unchecked (default=False) - 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) - 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) - -######################################################################## -# Drop Down Chooser Form -######################################################################## -class drop_down(_chooser_base): - """ - Create a drop down menu form. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - choices: list of possible values - labels: list of labels for each choice (default=choices) - label: title label for this widget (optional) - 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, size=wx.Size(width, -1)) - self._drop_down.Bind(wx.EVT_CHOICE, self._handle) - 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) - -######################################################################## -# 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): - """ - Create a multi-state button. - parent the parent widget - sizer add this widget to sizer if provided (optional) - proportion the proportion when added to the sizer (default=0) - flag the flag argument when added to the sizer (default=wx.EXPAND) - ps the pubsub object (optional) - key the pubsub key (optional) - value the default value (optional) - choices list of possible values - labels list of labels for each choice (default=choices) - width the width of the button in pixels (optional) - style style arguments (optional) - 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, 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. - This button will alternate between True and False when clicked. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - width: the width of the button in pixels (optional) - style: style arguments (optional) - true_label: the button's label in the true state - 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) - -class single_button(toggle_button): - """ - Create a single state button. - This button will callback() when clicked. - For use when state holding is not important. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value: the default value (optional) - width: the width of the button in pixels (optional) - style: style arguments (optional) - 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) - -######################################################################## -# Radio Buttons Chooser Form -######################################################################## -class radio_buttons(_chooser_base): - """ - Create a radio button form. - - Args: - parent: the parent widget - sizer: add this widget to sizer if provided (optional) - proportion: the proportion when added to the sizer (default=0) - flag: the flag argument when added to the sizer (default=wx.EXPAND) - ps: the pubsub object (optional) - key: the pubsub key (optional) - value the default value (optional) - choices: list of possible values - labels: list of labels for each choice (default=choices) - major_dimension: the number of rows/cols (default=auto) - label: title label for this widget (optional) - 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_CHANGING, 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] = event.GetSelection() - # SetSelection triggers a page change event (deprecated, breaks on Windows) and ChangeSelection does not - def _update(self, i): self._notebook.ChangeSelection(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/gr-wxgui/python/wxgui/gui.py b/gr-wxgui/python/wxgui/gui.py deleted file mode 100644 index ccc773eabf..0000000000 --- a/gr-wxgui/python/wxgui/gui.py +++ /dev/null @@ -1,135 +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. -# - -import wx -from gnuradio import gr - -# -# Top-level display panel with vertical box sizer. User does not create or -# subclass this class; rather, the user supplies his own class constructor -# that gets invoked with needed parameters. -# -class top_panel(wx.Panel): - def __init__(self, frame, top_block, gui, options, args): - wx.Panel.__init__(self, frame, -1) - vbox = wx.BoxSizer(wx.VERTICAL) - - # Create the user's GUI class - if gui is not None: - self.gui = gui(frame, # Top-level window frame - self, # Parent class for user created windows - vbox, # Sizer for user to add windows to - top_block, # GUI-unaware flowgraph to manipulate - options, # Command-line options - args) # Command-line arguments - - else: - # User hasn't made their own GUI, create our default - # We don't have a default GUI yet either :) - p = wx.Panel(self) - p.SetSize((640,480)) - vbox.Add(p, 1, wx.EXPAND) - - self.SetSizer(vbox) - self.SetAutoLayout(True) - vbox.Fit(self) - - def shutdown(self): - try: - self.gui.shutdown() - except AttributeError: - pass - -# -# Top-level window frame with menu and status bars. -# -class top_frame(wx.Frame): - def __init__ (self, top_block, gui, options, args, - title, nstatus, start, realtime): - - wx.Frame.__init__(self, None, -1, title) - self.top_block = top_block - - self.CreateStatusBar(nstatus) - mainmenu = wx.MenuBar() - self.SetMenuBar(mainmenu) - - menu = wx.Menu() - - item = menu.Append(200, 'E&xit', 'Exit Application') # FIXME magic ID - self.Bind(wx.EVT_MENU, self.OnCloseWindow, item) - mainmenu.Append(menu, "&File") - self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) - - # Create main panel, creates user GUI class with supplied parameters - self.panel = top_panel(self, top_block, gui, options, args) - - vbox = wx.BoxSizer(wx.VERTICAL) - vbox.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(vbox) - self.SetAutoLayout(True) - vbox.Fit(self) - - if realtime: - if gr.enable_realtime_scheduling() != gr.RT_OK: - self.SetStatusText("Failed to enable realtime scheduling") - - if start and self.top_block is not None: - self.top_block.start() - - def OnCloseWindow(self, event): - # Give user API a chance to do something - self.panel.shutdown() - - # Stop flowgraph as a convenience - self.SetStatusText("Ensuring flowgraph has completed before exiting...") - if self.top_block is not None: - self.top_block.stop() - self.top_block.wait() - - self.Destroy() - - -# -# Top-level wxPython application object. User creates or subclasses this -# in their GUI script. -# -class app(wx.App): - def __init__ (self, top_block=None, gui=None, options=None, args=None, - title="GNU Radio", nstatus=1, start=False, realtime=False): - self.top_block = top_block - self.gui = gui - self.options = options - self.args = args - self.title = title - self.nstatus = nstatus - self.start = start - self.realtime = realtime - - wx.App.__init__ (self, redirect=False) - - def OnInit(self): - # Pass user parameters to top window frame - frame = top_frame(self.top_block, self.gui, self.options, self.args, - self.title, self.nstatus, self.start, self.realtime) - frame.Show(True) - self.SetTopWindow(frame) - return True diff --git a/gr-wxgui/python/wxgui/histo_window.py b/gr-wxgui/python/wxgui/histo_window.py deleted file mode 100644 index 424ee7bbdb..0000000000 --- a/gr-wxgui/python/wxgui/histo_window.py +++ /dev/null @@ -1,168 +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. -# - -################################################## -# Imports -################################################## -import plotter -import common -import wx -import numpy -import math -import pubsub -from constants import * -from gnuradio import gr #for gr.prefs -import forms - -################################################## -# Constants -################################################## -DEFAULT_WIN_SIZE = (600, 300) - -################################################## -# histo window control panel -################################################## -class control_panel(wx.Panel): - """ - A control panel with wx widgits to control the plotter and histo sink. - """ - - def __init__(self, parent): - """ - Create a new control panel. - - Args: - parent: the wx parent window - """ - self.parent = parent - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - parent[SHOW_CONTROL_PANEL_KEY] = True - parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) - control_box = wx.BoxSizer(wx.VERTICAL) - SIZE = (100, -1) - control_box = forms.static_box_sizer( - parent=self, label='Options', - bold=True, orient=wx.VERTICAL, - ) - #num bins - 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 - 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 - 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) - -################################################## -# histo window with plotter and control panel -################################################## -class histo_window(wx.Panel, pubsub.pubsub): - def __init__( - self, - parent, - controller, - size, - title, - maximum_key, - minimum_key, - num_bins_key, - frame_size_key, - msg_key, - ): - pubsub.pubsub.__init__(self) - #setup - self.samples = list() - #proxy the keys - self.proxy(MAXIMUM_KEY, controller, maximum_key) - self.proxy(MINIMUM_KEY, controller, minimum_key) - 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) - self.plotter.SetSize(wx.Size(*size)) - self.plotter.SetSizeHints(*size) - 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) - #register events - self.subscribe(MSG_KEY, self.handle_msg) - self.subscribe(X_DIVS_KEY, self.update_grid) - self.subscribe(Y_DIVS_KEY, self.update_grid) - - def handle_msg(self, msg): - """ - Handle the message from the fft sink message queue. - - Args: - msg: the frame as a character array - """ - if not self[RUNNING_KEY]: return - #convert to floating point numbers - self.samples = 100*numpy.fromstring(msg, numpy.float32)[:self[NUM_BINS_KEY]] #only take first frame - self.plotter.set_bars( - bars=self.samples, - bar_width=0.6, - color_spec=(0, 0, 1), - ) - self.update_grid() - - def update_grid(self): - if not len(self.samples): return - #calculate the maximum y value - y_off = math.ceil(numpy.max(self.samples)) - y_off = min(max(y_off, 1.0), 100.0) #between 1% and 100% - #update the x grid - self.plotter.set_x_grid( - self[MINIMUM_KEY], self[MAXIMUM_KEY], - common.get_clean_num((self[MAXIMUM_KEY] - self[MINIMUM_KEY])/self[X_DIVS_KEY]), - ) - self.plotter.set_x_label('Counts') - #update the y grid - self.plotter.set_y_grid(0, y_off, y_off/self[Y_DIVS_KEY]) - self.plotter.set_y_label('Frequency', '%') - self.plotter.update() diff --git a/gr-wxgui/python/wxgui/histosink_gl.py b/gr-wxgui/python/wxgui/histosink_gl.py deleted file mode 100644 index 2126d63191..0000000000 --- a/gr-wxgui/python/wxgui/histosink_gl.py +++ /dev/null @@ -1,113 +0,0 @@ -# -# Copyright 2009,2012 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. -# - -################################################## -# Imports -################################################## -import histo_window -import common -from gnuradio import gr -from gnuradio import analog -from gnuradio import blocks -from gnuradio import wxgui -from pubsub import pubsub -from constants import * - -################################################## -# histo sink block (wrapper for old wxgui) -################################################## -class histo_sink_f(gr.hier_block2, common.wxgui_hb): - """ - A histogram block and a gui window. - """ - - def __init__( - self, - parent, - size=histo_window.DEFAULT_WIN_SIZE, - title='', - num_bins=11, - frame_size=1000, - ): - #init - gr.hier_block2.__init__( - self, - "histo_sink", - gr.io_signature(1, 1, gr.sizeof_float), - gr.io_signature(0, 0, 0), - ) - #blocks - msgq = gr.msg_queue(2) - histo = wxgui.histo_sink_f(msgq) - histo.set_num_bins(num_bins) - histo.set_frame_size(frame_size) - #controller - self.controller = pubsub() - self.controller.subscribe(NUM_BINS_KEY, histo.set_num_bins) - self.controller.publish(NUM_BINS_KEY, histo.get_num_bins) - self.controller.subscribe(FRAME_SIZE_KEY, histo.set_frame_size) - self.controller.publish(FRAME_SIZE_KEY, histo.get_frame_size) - #start input watcher - common.input_watcher(msgq, self.controller, MSG_KEY, arg1_key=MINIMUM_KEY, arg2_key=MAXIMUM_KEY) - #create window - self.win = histo_window.histo_window( - parent=parent, - controller=self.controller, - size=size, - title=title, - maximum_key=MAXIMUM_KEY, - minimum_key=MINIMUM_KEY, - num_bins_key=NUM_BINS_KEY, - frame_size_key=FRAME_SIZE_KEY, - msg_key=MSG_KEY, - ) - common.register_access_methods(self, self.win) - #connect - self.wxgui_connect(self, histo) - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -import wx -from gnuradio.wxgui import stdgui2 - -class test_app_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - # build our flow graph - input_rate = 20.48e3 - - src2 = analog.sig_source_f(input_rate, analog.GR_SIN_WAVE, 2e3, 1) - #src2 = analog.sig_source_f(input_rate, analog.GR_CONST_WAVE, 5.75e3, 1) - thr2 = blocks.throttle(gr.sizeof_float, input_rate) - sink2 = histo_sink_f(panel, title="Data", num_bins=31, frame_size=1000) - vbox.Add(sink2.win, 1, wx.EXPAND) - - self.connect(src2, thr2, sink2) - -def main (): - app = stdgui2.stdapp(test_app_block, "Histo Sink Test App") - app.MainLoop() - -if __name__ == '__main__': - main() diff --git a/gr-wxgui/python/wxgui/number_window.py b/gr-wxgui/python/wxgui/number_window.py deleted file mode 100644 index 2e54f8460f..0000000000 --- a/gr-wxgui/python/wxgui/number_window.py +++ /dev/null @@ -1,219 +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. -# - -################################################## -# Imports -################################################## -import common -import numpy -import wx -import pubsub -from constants import * -from gnuradio import gr #for gr.prefs -import forms - -################################################## -# Constants -################################################## -NEG_INF = float('-inf') -SLIDER_STEPS = 100 -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 -################################################## -class control_panel(wx.Panel): - """ - A control panel with wx widgits to control the averaging. - """ - - def __init__(self, parent): - """ - Create a new control panel. - - Args: - parent: the wx parent window - """ - self.parent = parent - wx.Panel.__init__(self, parent) - parent[SHOW_CONTROL_PANEL_KEY] = True - parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) - control_box = wx.BoxSizer(wx.VERTICAL) - #checkboxes for average and peak hold - control_box.AddStretchSpacer() - 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, - ) - 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() - 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) - -################################################## -# Numbersink window with label and gauges -################################################## -class number_window(wx.Panel, pubsub.pubsub): - def __init__( - self, - parent, - controller, - size, - title, - units, - show_gauge, - real, - minval, - maxval, - decimal_places, - average_key, - avg_alpha_key, - peak_hold, - msg_key, - sample_rate_key, - ): - pubsub.pubsub.__init__(self) - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - #setup - self.peak_val_real = NEG_INF - self.peak_val_imag = NEG_INF - self.real = real - self.units = units - 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 = 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.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) - self.SetSizerAndFit(main_box) - #register events - self.subscribe(MSG_KEY, self.handle_msg) - - def show_gauges(self, show_gauge): - """ - Show or hide the gauges. - If this is real, never show the imaginary gauge. - - Args: - show_gauge: true to show - """ - self.gauge_real.ShowItems(show_gauge) - self.gauge_imag.ShowItems(show_gauge and not self.real) - - def handle_msg(self, msg): - """ - Handle a message from the message queue. - Convert the string based message into a float or complex. - If more than one number was read, only take the last number. - Perform peak hold operations, set the gauges and display. - - Args: - event: event.data is the number sample as a character array - """ - if not self[RUNNING_KEY]: return - format_string = "%%.%df"%self.decimal_places - if self.real: - 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) - self[VALUE_REAL_KEY] = sample - else: - 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) - self[VALUE_REAL_KEY] = sample.real - self[VALUE_IMAG_KEY] = sample.imag - #set label text - self[VALUE_REPR_KEY] = label_text - #clear peak hold - if not self[PEAK_HOLD_KEY]: - self.peak_val_real = NEG_INF - self.peak_val_imag = NEG_INF diff --git a/gr-wxgui/python/wxgui/numbersink2.py b/gr-wxgui/python/wxgui/numbersink2.py deleted file mode 100644 index 62a096e112..0000000000 --- a/gr-wxgui/python/wxgui/numbersink2.py +++ /dev/null @@ -1,173 +0,0 @@ -# -# Copyright 2008,2012 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. -# - -################################################## -# Imports -################################################## -import number_window -import common -from gnuradio import gr, filter -from gnuradio import analog -from gnuradio import blocks -from pubsub import pubsub -from constants import * - -################################################## -# Number sink block (wrapper for old wxgui) -################################################## -class _number_sink_base(gr.hier_block2, common.wxgui_hb): - """ - An decimator block with a number window display - """ - - def __init__( - self, - parent, - unit='units', - minval=0, - maxval=1, - factor=1, - decimal_places=3, - ref_level=0, - sample_rate=1, - number_rate=number_window.DEFAULT_NUMBER_RATE, - average=False, - avg_alpha=None, - label='Number Plot', - size=number_window.DEFAULT_WIN_SIZE, - peak_hold=False, - show_gauge=True, - **kwargs #catchall for backwards compatibility - ): - #ensure avg alpha - if avg_alpha is None: avg_alpha = 2.0/number_rate - #init - gr.hier_block2.__init__( - self, - "number_sink", - gr.io_signature(1, 1, self._item_size), - gr.io_signature(0, 0, 0), - ) - #blocks - sd = blocks.stream_to_vector_decimator( - item_size=self._item_size, - sample_rate=sample_rate, - vec_rate=number_rate, - vec_len=1, - ) - if self._real: - mult = blocks.multiply_const_ff(factor) - add = blocks.add_const_ff(ref_level) - avg = filter.single_pole_iir_filter_ff(1.0) - else: - mult = blocks.multiply_const_cc(factor) - add = blocks.add_const_cc(ref_level) - avg = filter.single_pole_iir_filter_cc(1.0) - msgq = gr.msg_queue(2) - sink = blocks.message_sink(self._item_size, msgq, True) - #controller - self.controller = pubsub() - self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) - self.controller.publish(SAMPLE_RATE_KEY, sd.sample_rate) - self.controller[AVERAGE_KEY] = average - self.controller[AVG_ALPHA_KEY] = avg_alpha - def update_avg(*args): - if self.controller[AVERAGE_KEY]: avg.set_taps(self.controller[AVG_ALPHA_KEY]) - else: avg.set_taps(1.0) - update_avg() - self.controller.subscribe(AVERAGE_KEY, update_avg) - self.controller.subscribe(AVG_ALPHA_KEY, update_avg) - #start input watcher - common.input_watcher(msgq, self.controller, MSG_KEY) - #create window - self.win = number_window.number_window( - parent=parent, - controller=self.controller, - size=size, - title=label, - units=unit, - real=self._real, - minval=minval, - maxval=maxval, - decimal_places=decimal_places, - show_gauge=show_gauge, - average_key=AVERAGE_KEY, - avg_alpha_key=AVG_ALPHA_KEY, - peak_hold=peak_hold, - msg_key=MSG_KEY, - sample_rate_key=SAMPLE_RATE_KEY, - ) - common.register_access_methods(self, self.controller) - #backwards compadibility - self.set_show_gauge = self.win.show_gauges - #connect - self.wxgui_connect(self, sd, mult, add, avg, sink) - -class number_sink_f(_number_sink_base): - _item_size = gr.sizeof_float - _real = True - -class number_sink_c(_number_sink_base): - _item_size = gr.sizeof_gr_complex - _real = False - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -import wx -from gnuradio.wxgui import stdgui2 - -class test_app_flow_graph(stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) - - # build our flow graph - input_rate = 20.48e3 - - # Generate a real and complex sinusoids - src1 = analog.sig_source_f(input_rate, analog.GR_SIN_WAVE, 2.21e3, 1) - src2 = analog.sig_source_c(input_rate, analog.GR_SIN_WAVE, 2.21e3, 1) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - thr1 = blocks.throttle(gr.sizeof_float, input_rate) - thr2 = blocks.throttle(gr.sizeof_gr_complex, input_rate) - - sink1 = number_sink_f(panel, unit='V',label="Real Data", avg_alpha=0.001, - sample_rate=input_rate, minval=-1, maxval=1, - ref_level=0, decimal_places=3) - vbox.Add(sink1.win, 1, wx.EXPAND) - sink2 = number_sink_c(panel, unit='V',label="Complex Data", avg_alpha=0.001, - sample_rate=input_rate, minval=-1, maxval=1, - ref_level=0, decimal_places=3) - vbox.Add(sink2.win, 1, wx.EXPAND) - - self.connect(src1, thr1, sink1) - self.connect(src2, thr2, sink2) - -def main (): - app = stdgui2.stdapp(test_app_flow_graph, "Number Sink Test App") - app.MainLoop() - -if __name__ == '__main__': - main() - diff --git a/gr-wxgui/python/wxgui/plot.py b/gr-wxgui/python/wxgui/plot.py deleted file mode 100644 index 041a2a7a50..0000000000 --- a/gr-wxgui/python/wxgui/plot.py +++ /dev/null @@ -1,1834 +0,0 @@ -#----------------------------------------------------------------------------- -# Name: wx.lib.plot.py -# Purpose: Line, Bar and Scatter Graphs -# -# Author: Gordon Williams -# -# Created: 2003/11/03 -# RCS-ID: $Id$ -# Copyright: (c) 2002,2007,2010 -# Licence: Use as you wish. -#----------------------------------------------------------------------------- -# 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net) -# -# o 2.5 compatability update. -# o Renamed to plot.py in the wx.lib directory. -# o Reworked test frame to work with wx demo framework. This saves a bit -# of tedious cut and paste, and the test app is excellent. -# -# 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net) -# -# o wxScrolledMessageDialog -> ScrolledMessageDialog -# -# Oct 6, 2004 Gordon Williams (g_will@cyberus.ca) -# - Added bar graph demo -# - Modified line end shape from round to square. -# - Removed FloatDCWrapper for conversion to ints and ints in arguments -# -# Oct 15, 2004 Gordon Williams (g_will@cyberus.ca) -# - Imported modules given leading underscore to name. -# - Added Cursor Line Tracking and User Point Labels. -# - Demo for Cursor Line Tracking and Point Labels. -# - Size of plot preview frame adjusted to show page better. -# - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas. -# - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve) -# can be in either user coords or screen coords. -# -# May 27, 2007 Johnathan Corgan (jcorgan@corganenterprises.com) -# - Converted from numarray to numpy -# -# Apr 23, 2010 Martin Dudok van Heel (http://www.olifantasia.com/gnuradio/contact_olifantasia.gif) -# - Added Persistence option (emulate after glow of an analog CRT display using IIR) - -""" -This is a simple light weight plotting module that can be used with -Boa or easily integrated into your own wxPython application. The -emphasis is on small size and fast plotting for large data sets. It -has a reasonable number of features to do line and scatter graphs -easily as well as simple bar graphs. It is not as sophisticated or -as powerful as SciPy Plt or Chaco. Both of these are great packages -but consume huge amounts of computer resources for simple plots. -They can be found at http://scipy.com - -This file contains two parts; first the re-usable library stuff, then, -after a "if __name__=='__main__'" test, a simple frame and a few default -plots for examples and testing. - -Based on wxPlotCanvas -Written by K.Hinsen, R. Srinivasan; -Ported to wxPython Harm van der Heijden, feb 1999 - -Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca) - -More style options - -Zooming using mouse 'rubber band' - -Scroll left, right - -Grid(graticule) - -Printing, preview, and page set up (margins) - -Axis and title labels - -Cursor xy axis values - -Doc strings and lots of comments - -Optimizations for large number of points - -Legends - -Did a lot of work here to speed markers up. Only a factor of 4 -improvement though. Lines are much faster than markers, especially -filled markers. Stay away from circles and triangles unless you -only have a few thousand points. - -Times for 25,000 points -Line - 0.078 sec -Markers -Square - 0.22 sec -dot - 0.10 -circle - 0.87 -cross,plus - 0.28 -triangle, triangle_down - 0.90 - -Thanks to Chris Barker for getting this version working on Linux. - -Zooming controls with mouse (when enabled): - Left mouse drag - Zoom box. - Left mouse double click - reset zoom. - Right mouse click - zoom out centred on click location. -""" - -import string as _string -import time as _time -import wx - -# Needs numpy or numarray -try: - import numpy as _numpy -except: - try: - import numarray as _numpy #if numarray is used it is renamed numpy - except: - msg= """ - This module requires the numpy or numarray module, - which could not be imported. It probably is not installed - (it's not part of the standard Python distribution). See the - Python site (http://www.python.org) for information on - downloading source or binaries.""" - raise ImportError, "numpy or numarray not found. \n" + msg - - - -# -# Plotting classes... -# -class PolyPoints: - """Base Class for lines and markers - - All methods are private. - """ - - def __init__(self, points, attr): - self.points = _numpy.array(points) - self.currentScale= (1,1) - self.currentShift= (0,0) - self.scaled = self.points - self.attributes = {} - self.attributes.update(self._attributes) - for name, value in attr.items(): - if name not in self._attributes.keys(): - raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys() - self.attributes[name] = value - - def boundingBox(self): - if len(self.points) == 0: - # no curves to draw - # defaults to (-1,-1) and (1,1) but axis can be set in Draw - minXY= _numpy.array([-1,-1]) - maxXY= _numpy.array([ 1, 1]) - else: - minXY= _numpy.minimum.reduce(self.points) - maxXY= _numpy.maximum.reduce(self.points) - return minXY, maxXY - - def scaleAndShift(self, scale=(1,1), shift=(0,0)): - if len(self.points) == 0: - # no curves to draw - return - if (scale is not self.currentScale) or (shift is not self.currentShift): - # update point scaling - self.scaled = scale*self.points+shift - self.currentScale= scale - self.currentShift= shift - # else unchanged use the current scaling - - def getLegend(self): - return self.attributes['legend'] - - def getClosestPoint(self, pntXY, pointScaled= True): - """Returns the index of closest point on the curve, pointXY, scaledXY, distance - x, y in user coords - if pointScaled == True based on screen coords - if pointScaled == False based on user coords - """ - if pointScaled == True: - #Using screen coords - p = self.scaled - pxy = self.currentScale * _numpy.array(pntXY)+ self.currentShift - else: - #Using user coords - p = self.points - pxy = _numpy.array(pntXY) - #determine distance for each point - d= _numpy.sqrt(_numpy.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2) - pntIndex = _numpy.argmin(d) - dist = d[pntIndex] - return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist] - - -class PolyLine(PolyPoints): - """Class to define line type and style - - All methods except __init__ are private. - """ - - _attributes = {'colour': 'black', - 'width': 1, - 'style': wx.SOLID, - 'legend': ''} - - def __init__(self, points, **attr): - """Creates PolyLine object - points - sequence (array, tuple or list) of (x,y) points making up line - **attr - key word attributes - Defaults: - 'colour'= 'black', - wx.Pen Colour any wx.NamedColour - 'width'= 1, - Pen width - 'style'= wx.SOLID, - wx.Pen style - 'legend'= '' - Line Legend to display - """ - PolyPoints.__init__(self, points, attr) - - def draw(self, dc, printerScale, coord= None): - colour = self.attributes['colour'] - width = self.attributes['width'] * printerScale - style= self.attributes['style'] - pen = wx.Pen(wx.NamedColour(colour), width, style) - pen.SetCap(wx.CAP_BUTT) - dc.SetPen(pen) - if coord == None: - dc.DrawLines(self.scaled) - else: - dc.DrawLines(coord) # draw legend line - - def getSymExtent(self, printerScale): - """Width and Height of Marker""" - h= self.attributes['width'] * printerScale - w= 5 * h - return (w,h) - - -class PolyMarker(PolyPoints): - """Class to define marker type and style - - All methods except __init__ are private. - """ - - _attributes = {'colour': 'black', - 'width': 1, - 'size': 2, - 'fillcolour': None, - 'fillstyle': wx.SOLID, - 'marker': 'circle', - 'legend': ''} - - def __init__(self, points, **attr): - """Creates PolyMarker object - points - sequence (array, tuple or list) of (x,y) points - **attr - key word attributes - Defaults: - 'colour'= 'black', - wx.Pen Colour any wx.NamedColour - 'width'= 1, - Pen width - 'size'= 2, - Marker size - 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour - 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill) - 'marker'= 'circle' - Marker shape - 'legend'= '' - Marker Legend to display - - Marker Shapes: - - 'circle' - - 'dot' - - 'square' - - 'triangle' - - 'triangle_down' - - 'cross' - - 'plus' - """ - - PolyPoints.__init__(self, points, attr) - - def draw(self, dc, printerScale, coord= None): - colour = self.attributes['colour'] - width = self.attributes['width'] * printerScale - size = self.attributes['size'] * printerScale - fillcolour = self.attributes['fillcolour'] - fillstyle = self.attributes['fillstyle'] - marker = self.attributes['marker'] - - dc.SetPen(wx.Pen(wx.NamedColour(colour), width)) - if fillcolour: - dc.SetBrush(wx.Brush(wx.NamedColour(fillcolour),fillstyle)) - else: - dc.SetBrush(wx.Brush(wx.NamedColour(colour), fillstyle)) - if coord == None: - self._drawmarkers(dc, self.scaled, marker, size) - else: - self._drawmarkers(dc, coord, marker, size) # draw legend marker - - def getSymExtent(self, printerScale): - """Width and Height of Marker""" - s= 5*self.attributes['size'] * printerScale - return (s,s) - - def _drawmarkers(self, dc, coords, marker,size=1): - f = eval('self._' +marker) - f(dc, coords, size) - - def _circle(self, dc, coords, size=1): - fact= 2.5*size - wh= 5.0*size - rect= _numpy.zeros((len(coords),4),_numpy.float)+[0.0,0.0,wh,wh] - rect[:,0:2]= coords-[fact,fact] - dc.DrawEllipseList(rect.astype(_numpy.int32)) - - def _dot(self, dc, coords, size=1): - dc.DrawPointList(coords) - - def _square(self, dc, coords, size=1): - fact= 2.5*size - wh= 5.0*size - rect= _numpy.zeros((len(coords),4),_numpy.float)+[0.0,0.0,wh,wh] - rect[:,0:2]= coords-[fact,fact] - dc.DrawRectangleList(rect.astype(_numpy.int32)) - - def _triangle(self, dc, coords, size=1): - shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)] - poly= _numpy.repeat(coords,3) - poly.shape= (len(coords),3,2) - poly += shape - dc.DrawPolygonList(poly.astype(_numpy.int32)) - - def _triangle_down(self, dc, coords, size=1): - shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)] - poly= _numpy.repeat(coords,3) - poly.shape= (len(coords),3,2) - poly += shape - dc.DrawPolygonList(poly.astype(_numpy.int32)) - - def _cross(self, dc, coords, size=1): - fact= 2.5*size - for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]: - lines= _numpy.concatenate((coords,coords),axis=1)+f - dc.DrawLineList(lines.astype(_numpy.int32)) - - def _plus(self, dc, coords, size=1): - fact= 2.5*size - for f in [[-fact,0,fact,0],[0,-fact,0,fact]]: - lines= _numpy.concatenate((coords,coords),axis=1)+f - dc.DrawLineList(lines.astype(_numpy.int32)) - -class PlotGraphics: - """Container to hold PolyXXX objects and graph labels - - All methods except __init__ are private. - """ - - def __init__(self, objects, title='', xLabel='', yLabel= ''): - """Creates PlotGraphics object - objects - list of PolyXXX objects to make graph - title - title shown at top of graph - xLabel - label shown on x-axis - yLabel - label shown on y-axis - """ - if type(objects) not in [list,tuple]: - raise TypeError, "objects argument should be list or tuple" - self.objects = objects - self.title= title - self.xLabel= xLabel - self.yLabel= yLabel - - def boundingBox(self): - p1, p2 = self.objects[0].boundingBox() - for o in self.objects[1:]: - p1o, p2o = o.boundingBox() - p1 = _numpy.minimum(p1, p1o) - p2 = _numpy.maximum(p2, p2o) - return p1, p2 - - def scaleAndShift(self, scale=(1,1), shift=(0,0)): - for o in self.objects: - o.scaleAndShift(scale, shift) - - def setPrinterScale(self, scale): - """Thickens up lines and markers only for printing""" - self.printerScale= scale - - def setXLabel(self, xLabel= ''): - """Set the X axis label on the graph""" - self.xLabel= xLabel - - def setYLabel(self, yLabel= ''): - """Set the Y axis label on the graph""" - self.yLabel= yLabel - - def setTitle(self, title= ''): - """Set the title at the top of graph""" - self.title= title - - def getXLabel(self): - """Get x axis label string""" - return self.xLabel - - def getYLabel(self): - """Get y axis label string""" - return self.yLabel - - def getTitle(self, title= ''): - """Get the title at the top of graph""" - return self.title - - def draw(self, dc): - for o in self.objects: - #t=_time.clock() # profile info - o.draw(dc, self.printerScale) - #dt= _time.clock()-t - #print o, "time=", dt - - def getSymExtent(self, printerScale): - """Get max width and height of lines and markers symbols for legend""" - symExt = self.objects[0].getSymExtent(printerScale) - for o in self.objects[1:]: - oSymExt = o.getSymExtent(printerScale) - symExt = _numpy.maximum(symExt, oSymExt) - return symExt - - def getLegendNames(self): - """Returns list of legend names""" - lst = [None]*len(self) - for i in range(len(self)): - lst[i]= self.objects[i].getLegend() - return lst - - def __len__(self): - return len(self.objects) - - def __getitem__(self, item): - return self.objects[item] - - -#------------------------------------------------------------------------------- -# Main window that you will want to import into your application. - -class PlotCanvas(wx.Window): - """Subclass of a wx.Window to allow simple general plotting - of data with zoom, labels, and automatic axis scaling.""" - - def __init__(self, parent, id = -1, pos=wx.DefaultPosition, - size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""): - - self.use_persistence=False - self.alpha=0.3 - self.decimation=10 - self.decim_counter=0 - """Constucts a window, which can be a child of a frame, dialog or - any other non-control window""" - - wx.Window.__init__(self, parent, id, pos, size, style, name) - self.border = (1,1) - - self.SetBackgroundColour("white") - - # Create some mouse events for zooming - self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown) - self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp) - self.Bind(wx.EVT_MOTION, self.OnMotion) - self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick) - self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown) - - # set curser as cross-hairs - self.SetCursor(wx.CROSS_CURSOR) - - # Things for printing - self.print_data = wx.PrintData() - self.print_data.SetPaperId(wx.PAPER_LETTER) - self.print_data.SetOrientation(wx.LANDSCAPE) - self.pageSetupData= wx.PageSetupDialogData() - self.pageSetupData.SetMarginBottomRight((25,25)) - self.pageSetupData.SetMarginTopLeft((25,25)) - self.pageSetupData.SetPrintData(self.print_data) - self.printerScale = 1 - self.parent= parent - - # Zooming variables - self._zoomInFactor = 0.5 - self._zoomOutFactor = 2 - self._zoomCorner1= _numpy.array([0.0, 0.0]) # left mouse down corner - self._zoomCorner2= _numpy.array([0.0, 0.0]) # left mouse up corner - self._zoomEnabled= False - self._hasDragged= False - - # Drawing Variables - self.last_draw = None - self._pointScale= 1 - self._pointShift= 0 - self._xSpec= 'auto' - self._ySpec= 'auto' - self._gridEnabled= False - self._legendEnabled= False - self._xUseScopeTicks= False - - # Fonts - self._fontCache = {} - self._fontSizeAxis= 10 - self._fontSizeTitle= 15 - self._fontSizeLegend= 7 - - # pointLabels - self._pointLabelEnabled= False - self.last_PointLabel= None - self._pointLabelFunc= None - self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) - - self.Bind(wx.EVT_PAINT, self.OnPaint) - self.Bind(wx.EVT_SIZE, self.OnSize) - # OnSize called to make sure the buffer is initialized. - # This might result in OnSize getting called twice on some - # platforms at initialization, but little harm done. - self.OnSize(None) # sets the initial size based on client size - # UNCONDITIONAL, needed to create self._Buffer - - - def set_use_persistence(self, enable): - self.use_persistence = enable - - def set_persist_alpha(self, persist_alpha): - self.alpha = persist_alpha - - - # SaveFile - def SaveFile(self, fileName= ''): - """Saves the file to the type specified in the extension. If no file - name is specified a dialog box is provided. Returns True if sucessful, - otherwise False. - - .bmp Save a Windows bitmap file. - .xbm Save an X bitmap file. - .xpm Save an XPM bitmap file. - .png Save a Portable Network Graphics file. - .jpg Save a Joint Photographic Experts Group file. - """ - if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']: - dlg1 = wx.FileDialog( - self, - "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "", - "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg", - wx.SAVE|wx.OVERWRITE_PROMPT - ) - try: - while 1: - if dlg1.ShowModal() == wx.ID_OK: - fileName = dlg1.GetPath() - # Check for proper exension - if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']: - dlg2 = wx.MessageDialog(self, 'File name extension\n' - 'must be one of\n' - 'bmp, xbm, xpm, png, or jpg', - 'File Name Error', wx.OK | wx.ICON_ERROR) - try: - dlg2.ShowModal() - finally: - dlg2.Destroy() - else: - break # now save file - else: # exit without saving - return False - finally: - dlg1.Destroy() - - # File name has required extension - fType = _string.lower(fileName[-3:]) - if fType == "bmp": - tp= wx.BITMAP_TYPE_BMP # Save a Windows bitmap file. - elif fType == "xbm": - tp= wx.BITMAP_TYPE_XBM # Save an X bitmap file. - elif fType == "xpm": - tp= wx.BITMAP_TYPE_XPM # Save an XPM bitmap file. - elif fType == "jpg": - tp= wx.BITMAP_TYPE_JPEG # Save a JPG file. - else: - tp= wx.BITMAP_TYPE_PNG # Save a PNG file. - # Save Bitmap - res= self._Buffer.SaveFile(fileName, tp) - return res - - def PageSetup(self): - """Brings up the page setup dialog""" - data = self.pageSetupData - data.SetPrintData(self.print_data) - dlg = wx.PageSetupDialog(self.parent, data) - try: - if dlg.ShowModal() == wx.ID_OK: - data = dlg.GetPageSetupData() # returns wx.PageSetupDialogData - # updates page parameters from dialog - self.pageSetupData.SetMarginBottomRight(data.GetMarginBottomRight()) - self.pageSetupData.SetMarginTopLeft(data.GetMarginTopLeft()) - self.pageSetupData.SetPrintData(data.GetPrintData()) - self.print_data=data.GetPrintData() # updates print_data - finally: - dlg.Destroy() - - def Printout(self, paper=None): - """Print current plot.""" - if paper != None: - self.print_data.SetPaperId(paper) - pdd = wx.PrintDialogData() - pdd.SetPrintData(self.print_data) - printer = wx.Printer(pdd) - out = PlotPrintout(self) - print_ok = printer.Print(self.parent, out) - if print_ok: - self.print_data = printer.GetPrintDialogData().GetPrintData() - out.Destroy() - - def PrintPreview(self): - """Print-preview current plot.""" - printout = PlotPrintout(self) - printout2 = PlotPrintout(self) - self.preview = wx.PrintPreview(printout, printout2, self.print_data) - if not self.preview.Ok(): - wx.MessageDialog(self, "Print Preview failed.\n" \ - "Check that default printer is configured\n", \ - "Print error", wx.OK|wx.CENTRE).ShowModal() - self.preview.SetZoom(40) - # search up tree to find frame instance - frameInst= self - while not isinstance(frameInst, wx.Frame): - frameInst= frameInst.GetParent() - frame = wx.PreviewFrame(self.preview, frameInst, "Preview") - frame.Initialize() - frame.SetPosition(self.GetPosition()) - frame.SetSize((600,550)) - frame.Centre(wx.BOTH) - frame.Show(True) - - def SetFontSizeAxis(self, point= 10): - """Set the tick and axis label font size (default is 10 point)""" - self._fontSizeAxis= point - - def GetFontSizeAxis(self): - """Get current tick and axis label font size in points""" - return self._fontSizeAxis - - def SetFontSizeTitle(self, point= 15): - """Set Title font size (default is 15 point)""" - self._fontSizeTitle= point - - def GetFontSizeTitle(self): - """Get current Title font size in points""" - return self._fontSizeTitle - - def SetFontSizeLegend(self, point= 7): - """Set Legend font size (default is 7 point)""" - self._fontSizeLegend= point - - def GetFontSizeLegend(self): - """Get current Legend font size in points""" - return self._fontSizeLegend - - def SetEnableZoom(self, value): - """Set True to enable zooming.""" - if value not in [True,False]: - raise TypeError, "Value should be True or False" - self._zoomEnabled= value - - def GetEnableZoom(self): - """True if zooming enabled.""" - return self._zoomEnabled - - def SetEnableGrid(self, value): - """Set True to enable grid.""" - if value not in [True,False]: - raise TypeError, "Value should be True or False" - self._gridEnabled= value - self.Redraw() - - def GetEnableGrid(self): - """True if grid enabled.""" - return self._gridEnabled - - def SetEnableLegend(self, value): - """Set True to enable legend.""" - if value not in [True,False]: - raise TypeError, "Value should be True or False" - self._legendEnabled= value - self.Redraw() - - def GetEnableLegend(self): - """True if Legend enabled.""" - return self._legendEnabled - - def SetEnablePointLabel(self, value): - """Set True to enable pointLabel.""" - if value not in [True,False]: - raise TypeError, "Value should be True or False" - self._pointLabelEnabled= value - self.Redraw() #will erase existing pointLabel if present - self.last_PointLabel = None - - def GetEnablePointLabel(self): - """True if pointLabel enabled.""" - return self._pointLabelEnabled - - def SetPointLabelFunc(self, func): - """Sets the function with custom code for pointLabel drawing - ******** more info needed *************** - """ - self._pointLabelFunc= func - - def GetPointLabelFunc(self): - """Returns pointLabel Drawing Function""" - return self._pointLabelFunc - - def Reset(self): - """Unzoom the plot.""" - self.last_PointLabel = None #reset pointLabel - if self.last_draw is not None: - self.Draw(self.last_draw[0]) - - def ScrollRight(self, units): - """Move view right number of axis units.""" - self.last_PointLabel = None #reset pointLabel - if self.last_draw is not None: - graphics, xAxis, yAxis= self.last_draw - xAxis= (xAxis[0]+units, xAxis[1]+units) - self.Draw(graphics,xAxis,yAxis) - - def ScrollUp(self, units): - """Move view up number of axis units.""" - self.last_PointLabel = None #reset pointLabel - if self.last_draw is not None: - graphics, xAxis, yAxis= self.last_draw - yAxis= (yAxis[0]+units, yAxis[1]+units) - self.Draw(graphics,xAxis,yAxis) - - def GetXY(self,event): - """Takes a mouse event and returns the XY user axis values.""" - x,y= self.PositionScreenToUser(event.GetPosition()) - return x,y - - def PositionUserToScreen(self, pntXY): - """Converts User position to Screen Coordinates""" - userPos= _numpy.array(pntXY) - x,y= userPos * self._pointScale + self._pointShift - return x,y - - def PositionScreenToUser(self, pntXY): - """Converts Screen position to User Coordinates""" - screenPos= _numpy.array(pntXY) - x,y= (screenPos-self._pointShift)/self._pointScale - return x,y - - def SetXSpec(self, type= 'auto'): - """xSpec- defines x axis type. Can be 'none', 'min' or 'auto' - where: - 'none' - shows no axis or tick mark values - 'min' - shows min bounding box values - 'auto' - rounds axis range to sensible values - """ - self._xSpec= type - - def SetYSpec(self, type= 'auto'): - """ySpec- defines x axis type. Can be 'none', 'min' or 'auto' - where: - 'none' - shows no axis or tick mark values - 'min' - shows min bounding box values - 'auto' - rounds axis range to sensible values - """ - self._ySpec= type - - def GetXSpec(self): - """Returns current XSpec for axis""" - return self._xSpec - - def GetYSpec(self): - """Returns current YSpec for axis""" - return self._ySpec - - def GetXMaxRange(self): - """Returns (minX, maxX) x-axis range for displayed graph""" - graphics= self.last_draw[0] - p1, p2 = graphics.boundingBox() # min, max points of graphics - xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units - return xAxis - - def GetYMaxRange(self): - """Returns (minY, maxY) y-axis range for displayed graph""" - graphics= self.last_draw[0] - p1, p2 = graphics.boundingBox() # min, max points of graphics - yAxis = self._axisInterval(self._ySpec, p1[1], p2[1]) - return yAxis - - def GetXCurrentRange(self): - """Returns (minX, maxX) x-axis for currently displayed portion of graph""" - return self.last_draw[1] - - def GetYCurrentRange(self): - """Returns (minY, maxY) y-axis for currently displayed portion of graph""" - return self.last_draw[2] - - def SetXUseScopeTicks(self, v=False): - """Always 10 divisions, no labels""" - self._xUseScopeTicks = v - - def GetXUseScopeTicks(self): - return self._xUseScopeTicks - - def Draw(self, graphics, xAxis = None, yAxis = None, dc = None, step=None): - """Draw objects in graphics with specified x and y axis. - graphics- instance of PlotGraphics with list of PolyXXX objects - xAxis - tuple with (min, max) axis range to view - yAxis - same as xAxis - dc - drawing context - doesn't have to be specified. - If it's not, the offscreen buffer is used - """ - # check Axis is either tuple or none - if type(xAxis) not in [type(None),tuple]: - raise TypeError, "xAxis should be None or (minX,maxX)" - if type(yAxis) not in [type(None),tuple]: - raise TypeError, "yAxis should be None or (minY,maxY)" - - # check case for axis = (a,b) where a==b caused by improper zooms - if xAxis != None: - if xAxis[0] == xAxis[1]: - return - if yAxis != None: - if yAxis[0] == yAxis[1]: - return - - if dc == None: - # sets new dc and clears it - if self.use_persistence: - dc = wx.MemoryDC() - dc.SelectObject(self._Buffer) - dc.Clear() - else: - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - dc.Clear() - - dc.BeginDrawing() - # dc.Clear() - - - - # set font size for every thing but title and legend - dc.SetFont(self._getFont(self._fontSizeAxis)) - - # sizes axis to axis type, create lower left and upper right corners of plot - if xAxis == None or yAxis == None: - # One or both axis not specified in Draw - p1, p2 = graphics.boundingBox() # min, max points of graphics - if xAxis == None: - xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units - if yAxis == None: - yAxis = self._axisInterval(self._ySpec, p1[1], p2[1]) - # Adjust bounding box for axis spec - p1[0],p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin,ymin) - p2[0],p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax,ymax) - else: - # Both axis specified in Draw - p1= _numpy.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin) - p2= _numpy.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax) - - self.last_draw = (graphics, xAxis, yAxis) # saves most recient values - - if False: - ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2) - #dc.SetPen(wx.Pen(wx.BLACK)) - dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) ) #wx.SOLID wx.TRANSPARENT ) ) - #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT - dc.DrawRectangle( ptx,pty, rectWidth,rectHeight) - #dc.SetBrush(wx.Brush( wx.WHITE, wx.SOLID ) ) - #dc.SetLogicalFunction(wx.COPY) - - # Get ticks and textExtents for axis if required - if self._xSpec is not 'none': - if self._xUseScopeTicks: - xticks = self._scope_ticks(xAxis[0], xAxis[1]) - else: - xticks = self._ticks(xAxis[0], xAxis[1]) - xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis - else: - xticks = None - xTextExtent= (0,0) # No text for ticks - if self._ySpec is not 'none': - yticks = self._ticks(yAxis[0], yAxis[1], step) - yTextExtentBottom= dc.GetTextExtent(yticks[0][1]) - yTextExtentTop = dc.GetTextExtent(yticks[-1][1]) - yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]), - max(yTextExtentBottom[1],yTextExtentTop[1])) - else: - yticks = None - yTextExtent= (0,0) # No text for ticks - - # TextExtents for Title and Axis Labels - titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics) - - # TextExtents for Legend - legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics) - - # room around graph area - rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width - lhsW= yTextExtent[0]+ yLabelWH[1] - bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1] - topH= yTextExtent[1]/2. + titleWH[1] - textSize_scale= _numpy.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size - textSize_shift= _numpy.array([lhsW, bottomH]) # shift plot area by this amount - - # drawing title and labels text - dc.SetFont(self._getFont(self._fontSizeTitle)) - titlePos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- titleWH[0]/2., - self.plotbox_origin[1]- self.plotbox_size[1]) - dc.DrawText(graphics.getTitle(),titlePos[0],titlePos[1]) - dc.SetFont(self._getFont(self._fontSizeAxis)) - xLabelPos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- xLabelWH[0]/2., - self.plotbox_origin[1]- xLabelWH[1]) - dc.DrawText(graphics.getXLabel(),xLabelPos[0],xLabelPos[1]) - yLabelPos= (self.plotbox_origin[0], - self.plotbox_origin[1]- bottomH- (self.plotbox_size[1]-bottomH-topH)/2.+ yLabelWH[0]/2.) - if graphics.getYLabel(): # bug fix for Linux - dc.DrawRotatedText(graphics.getYLabel(),yLabelPos[0],yLabelPos[1],90) - - # drawing legend makers and text - if self._legendEnabled: - self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt) - - # allow for scaling and shifting plotted points - scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _numpy.array((1,-1)) - shift = -p1*scale + self.plotbox_origin + textSize_shift * _numpy.array((1,-1)) - self._pointScale= scale # make available for mouse events - self._pointShift= shift - - #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT - self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks) - #dc.SetLogicalFunction(wx.COPY) - - graphics.scaleAndShift(scale, shift) - graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing - - # set clipping area so drawing does not occur outside axis box - ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2) - dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight) - # Draw the lines and markers - #start = _time.clock() - - graphics.draw(dc) - # print "entire graphics drawing took: %f second"%(_time.clock() - start) - # remove the clipping region - dc.DestroyClippingRegion() - dc.EndDrawing() - - - if self.use_persistence: - dc=None - self._Buffer.CopyToBuffer(self._Bufferarray) #, format=wx.BitmapBufferFormat_RGB, stride=-1) - ## do the IIR filter - alpha_int=int(float(self.alpha*256)) - if True: - _numpy.add(self._Bufferarray,0,self._Buffer3array) - _numpy.multiply(self._Buffer3array,alpha_int,self._Buffer3array) - _numpy.multiply(self._Buffer2array,(256-alpha_int),self._Buffer2array) - _numpy.add(self._Buffer3array,self._Buffer2array,self._Buffer2array) - _numpy.right_shift(self._Buffer2array,8,self._Buffer2array) - elif False: - self._Buffer2array=(self._Bufferarray.astype(_numpy.uint32) *alpha_int + self._Buffer2array*(256-alpha_int)).__rshift__(8) - elif False: - self._Buffer2array *=(256-alpha_int) - self._Buffer2array +=self._Bufferarray.astype(_numpy.uint32)*alpha_int - self._Buffer2array /=256 - - ##copy back to image buffer - self._Buffer2.CopyFromBuffer(self._Buffer2array.astype(_numpy.uint8)) #, format=wx.BitmapBufferFormat_RGB, stride=-1) - - #draw to the screen - #self.decim_counter=self.decim_counter+1 - if True: #self.decim_counter>self.decimation: - #self.decim_counter=0 - dc2 = wx.ClientDC( self ) - dc2.BeginDrawing() - dc2.DrawBitmap(self._Buffer2, 0, 0, False) - #dc2.DrawBitmap(self._Buffer, 0, 0, False) - dc2.EndDrawing() - - def Redraw(self, dc= None): - """Redraw the existing plot.""" - if self.last_draw is not None: - graphics, xAxis, yAxis= self.last_draw - self.Draw(graphics,xAxis,yAxis,dc) - - def Clear(self): - """Erase the window.""" - self.last_PointLabel = None #reset pointLabel - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - dc.Clear() - self.last_draw = None - - def Zoom(self, Center, Ratio): - """ Zoom on the plot - Centers on the X,Y coords given in Center - Zooms by the Ratio = (Xratio, Yratio) given - """ - self.last_PointLabel = None #reset maker - x,y = Center - if self.last_draw != None: - (graphics, xAxis, yAxis) = self.last_draw - w = (xAxis[1] - xAxis[0]) * Ratio[0] - h = (yAxis[1] - yAxis[0]) * Ratio[1] - xAxis = ( x - w/2, x + w/2 ) - yAxis = ( y - h/2, y + h/2 ) - self.Draw(graphics, xAxis, yAxis) - - def GetClosestPoints(self, pntXY, pointScaled= True): - """Returns list with - [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] - list for each curve. - Returns [] if no curves are being plotted. - - x, y in user coords - if pointScaled == True based on screen coords - if pointScaled == False based on user coords - """ - if self.last_draw == None: - #no graph available - return [] - graphics, xAxis, yAxis= self.last_draw - l = [] - for curveNum,obj in enumerate(graphics): - #check there are points in the curve - if len(obj.points) == 0: - continue #go to next obj - #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance] - cn = [curveNum]+ [obj.getLegend()]+ obj.getClosestPoint( pntXY, pointScaled) - l.append(cn) - return l - - def GetClosetPoint(self, pntXY, pointScaled= True): - """Returns list with - [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] - list for only the closest curve. - Returns [] if no curves are being plotted. - - x, y in user coords - if pointScaled == True based on screen coords - if pointScaled == False based on user coords - """ - #closest points on screen based on screen scaling (pointScaled= True) - #list [curveNumber, index, pointXY, scaledXY, distance] for each curve - closestPts= self.GetClosestPoints(pntXY, pointScaled) - if closestPts == []: - return [] #no graph present - #find one with least distance - dists = [c[-1] for c in closestPts] - mdist = min(dists) #Min dist - i = dists.index(mdist) #index for min dist - return closestPts[i] #this is the closest point on closest curve - - def UpdatePointLabel(self, mDataDict): - """Updates the pointLabel point on screen with data contained in - mDataDict. - - mDataDict will be passed to your function set by - SetPointLabelFunc. It can contain anything you - want to display on the screen at the scaledXY point - you specify. - - This function can be called from parent window with onClick, - onMotion events etc. - """ - if self.last_PointLabel != None: - #compare pointXY - if mDataDict["pointXY"] != self.last_PointLabel["pointXY"]: - #closest changed - self._drawPointLabel(self.last_PointLabel) #erase old - self._drawPointLabel(mDataDict) #plot new - else: - #just plot new with no erase - self._drawPointLabel(mDataDict) #plot new - #save for next erase - self.last_PointLabel = mDataDict - - # event handlers ********************************** - def OnMotion(self, event): - if self._zoomEnabled and event.LeftIsDown(): - if self._hasDragged: - self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old - else: - self._hasDragged= True - self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event) - self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new - - def OnMouseLeftDown(self,event): - self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event) - - def OnMouseLeftUp(self, event): - if self._zoomEnabled: - if self._hasDragged == True: - self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old - self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event) - self._hasDragged = False # reset flag - minX, minY= _numpy.minimum( self._zoomCorner1, self._zoomCorner2) - maxX, maxY= _numpy.maximum( self._zoomCorner1, self._zoomCorner2) - self.last_PointLabel = None #reset pointLabel - if self.last_draw != None: - self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None) - #else: # A box has not been drawn, zoom in on a point - ## this interfered with the double click, so I've disables it. - # X,Y = self.GetXY(event) - # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) ) - - def OnMouseDoubleClick(self,event): - if self._zoomEnabled: - self.Reset() - - def OnMouseRightDown(self,event): - if self._zoomEnabled: - X,Y = self.GetXY(event) - self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) ) - - def OnPaint(self, event): - # All that is needed here is to draw the buffer to screen - if self.last_PointLabel != None: - self._drawPointLabel(self.last_PointLabel) #erase old - self.last_PointLabel = None - - #paint current buffer to screen - dc = wx.BufferedPaintDC(self, self._Buffer) - - def OnSize(self,event): - # The Buffer init is done here, to make sure the buffer is always - # the same size as the Window - Size = self.GetClientSize() - - # Make new offscreen bitmap: this bitmap will always have the - # current drawing in it, so it can be used to save the image to - # a file, or whatever. - self._Buffer = wx.EmptyBitmap(Size[0],Size[1],24) - - - if True: #self.use_persistence: - #self._Bufferarray = _numpy.zeros((Size[0], Size[1],3), dtype=_numpy.uint8) - self._Bufferarray = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint8) - - # Make new second offscreen bitmap: this bitmap will always have the - # last drawing in it, so it can be used to do display time dependent processing - # like averaging (IIR) or show differences between updates - self._Buffer2 = wx.EmptyBitmap(Size[0],Size[1],24) - # now the extra buffers for the IIR processing - # note the different datatype uint32 - self._Buffer2array = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint32) #dtype=_numpy.float - self._Buffer3array = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint32) #dtype=_numpy.float - # optional you can set the ufunct buffer size to improve speed - #_numpy.setbufsize(16*((Size[0]* Size[1]*3)/16 +1)) - self._setSize() - - self.last_PointLabel = None #reset pointLabel - - if self.last_draw is None: - self.Clear() - else: - graphics, xSpec, ySpec = self.last_draw - self.Draw(graphics,xSpec,ySpec) - - def OnLeave(self, event): - """Used to erase pointLabel when mouse outside window""" - if self.last_PointLabel != None: - self._drawPointLabel(self.last_PointLabel) #erase old - self.last_PointLabel = None - - - # Private Methods ************************************************** - def _setSize(self, width=None, height=None): - """DC width and height.""" - if width == None: - (self.width,self.height) = self.GetClientSize() - else: - self.width, self.height= width,height - self.plotbox_size = 0.97*_numpy.array([self.width, self.height]) - xo = 0.5*(self.width-self.plotbox_size[0]) - yo = self.height-0.5*(self.height-self.plotbox_size[1]) - self.plotbox_origin = _numpy.array([xo, yo]) - - def _setPrinterScale(self, scale): - """Used to thicken lines and increase marker size for print out.""" - # line thickness on printer is very thin at 600 dot/in. Markers small - self.printerScale= scale - - def _printDraw(self, printDC): - """Used for printing.""" - if self.last_draw != None: - graphics, xSpec, ySpec= self.last_draw - self.Draw(graphics,xSpec,ySpec,printDC) - - def _drawPointLabel(self, mDataDict): - """Draws and erases pointLabels""" - width = self._Buffer.GetWidth() - height = self._Buffer.GetHeight() - tmp_Buffer = wx.EmptyBitmap(width,height) - dcs = wx.MemoryDC() - dcs.SelectObject(tmp_Buffer) - dcs.Clear() - dcs.BeginDrawing() - self._pointLabelFunc(dcs,mDataDict) #custom user pointLabel function - dcs.EndDrawing() - - dc = wx.ClientDC( self ) - #this will erase if called twice - dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst - - - def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt): - """Draws legend symbols and text""" - # top right hand corner of graph box is ref corner - trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1] - legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box - lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines - dc.SetFont(self._getFont(self._fontSizeLegend)) - for i in range(len(graphics)): - o = graphics[i] - s= i*lineHeight - if isinstance(o,PolyMarker): - # draw marker with legend - pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.) - o.draw(dc, self.printerScale, coord= _numpy.array([pnt])) - elif isinstance(o,PolyLine): - # draw line with legend - pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.) - pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.) - o.draw(dc, self.printerScale, coord= _numpy.array([pnt1,pnt2])) - else: - raise TypeError, "object is neither PolyMarker or PolyLine instance" - # draw legend txt - pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2) - dc.DrawText(o.getLegend(),pnt[0],pnt[1]) - dc.SetFont(self._getFont(self._fontSizeAxis)) # reset - - def _titleLablesWH(self, dc, graphics): - """Draws Title and labels and returns width and height for each""" - # TextExtents for Title and Axis Labels - dc.SetFont(self._getFont(self._fontSizeTitle)) - title= graphics.getTitle() - titleWH= dc.GetTextExtent(title) - dc.SetFont(self._getFont(self._fontSizeAxis)) - xLabel, yLabel= graphics.getXLabel(),graphics.getYLabel() - xLabelWH= dc.GetTextExtent(xLabel) - yLabelWH= dc.GetTextExtent(yLabel) - return titleWH, xLabelWH, yLabelWH - - def _legendWH(self, dc, graphics): - """Returns the size in screen units for legend box""" - if self._legendEnabled != True: - legendBoxWH= symExt= txtExt= (0,0) - else: - # find max symbol size - symExt= graphics.getSymExtent(self.printerScale) - # find max legend text extent - dc.SetFont(self._getFont(self._fontSizeLegend)) - txtList= graphics.getLegendNames() - txtExt= dc.GetTextExtent(txtList[0]) - for txt in graphics.getLegendNames()[1:]: - txtExt= _numpy.maximum(txtExt,dc.GetTextExtent(txt)) - maxW= symExt[0]+txtExt[0] - maxH= max(symExt[1],txtExt[1]) - # padding .1 for lhs of legend box and space between lines - maxW= maxW* 1.1 - maxH= maxH* 1.1 * len(txtList) - dc.SetFont(self._getFont(self._fontSizeAxis)) - legendBoxWH= (maxW,maxH) - return (legendBoxWH, symExt, txtExt) - - def _drawRubberBand(self, corner1, corner2): - """Draws/erases rect box from corner1 to corner2""" - ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2) - # draw rectangle - dc = wx.ClientDC( self ) - dc.BeginDrawing() - dc.SetPen(wx.Pen(wx.BLACK)) - dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) ) - dc.SetLogicalFunction(wx.INVERT) - dc.DrawRectangle( ptx,pty, rectWidth,rectHeight) - dc.SetLogicalFunction(wx.COPY) - dc.EndDrawing() - - def _getFont(self,size): - """Take font size, adjusts if printing and returns wx.Font""" - s = size*self.printerScale - of = self.GetFont() - # Linux speed up to get font from cache rather than X font server - key = (int(s), of.GetFamily (), of.GetStyle (), of.GetWeight ()) - font = self._fontCache.get (key, None) - if font: - return font # yeah! cache hit - else: - font = wx.Font(int(s), of.GetFamily(), of.GetStyle(), of.GetWeight()) - self._fontCache[key] = font - return font - - - def _point2ClientCoord(self, corner1, corner2): - """Converts user point coords to client screen int coords x,y,width,height""" - c1= _numpy.array(corner1) - c2= _numpy.array(corner2) - # convert to screen coords - pt1= c1*self._pointScale+self._pointShift - pt2= c2*self._pointScale+self._pointShift - # make height and width positive - pul= _numpy.minimum(pt1,pt2) # Upper left corner - plr= _numpy.maximum(pt1,pt2) # Lower right corner - rectWidth, rectHeight= plr-pul - ptx,pty= pul - return ptx, pty, rectWidth, rectHeight - - def _axisInterval(self, spec, lower, upper): - """Returns sensible axis range for given spec""" - if spec == 'none' or spec == 'min': - if lower == upper: - return lower-0.5, upper+0.5 - else: - return lower, upper - elif spec == 'auto': - range = upper-lower - # if range == 0.: - if abs(range) < 1e-36: - return lower-0.5, upper+0.5 - log = _numpy.log10(range) - power = _numpy.floor(log) - fraction = log-power - if fraction <= 0.05: - power = power-1 - grid = 10.**power - lower = lower - lower % grid - mod = upper % grid - if mod != 0: - upper = upper - mod + grid - return lower, upper - elif type(spec) == type(()): - lower, upper = spec - if lower <= upper: - return lower, upper - else: - return upper, lower - else: - raise ValueError, str(spec) + ': illegal axis specification' - - def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks): - - penWidth= self.printerScale # increases thickness for printing only - dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth)) - - # set length of tick marks--long ones make grid - if self._gridEnabled: - x,y,width,height= self._point2ClientCoord(p1,p2) - yTickLength= width/2.0 +1 - xTickLength= height/2.0 +1 - else: - yTickLength= 3 * self.printerScale # lengthens lines for printing - xTickLength= 3 * self.printerScale - - if self._xSpec is not 'none': - lower, upper = p1[0],p2[0] - text = 1 - for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths - a1 = scale*_numpy.array([lower, y])+shift - a2 = scale*_numpy.array([upper, y])+shift - dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line - for x, label in xticks: - pt = scale*_numpy.array([x, y])+shift - dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units - if text: - dc.DrawText(label,pt[0],pt[1]) - text = 0 # axis values not drawn on top side - - if self._ySpec is not 'none': - lower, upper = p1[1],p2[1] - text = 1 - h = dc.GetCharHeight() - for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]: - a1 = scale*_numpy.array([x, lower])+shift - a2 = scale*_numpy.array([x, upper])+shift - dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) - for y, label in yticks: - pt = scale*_numpy.array([x, y])+shift - dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1]) - if text: - dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0], - pt[1]-0.5*h) - text = 0 # axis values not drawn on right side - - def _ticks(self, lower, upper, step=None): - ideal = (upper-lower)/7. - log = _numpy.log10(ideal) - power = _numpy.floor(log) - fraction = log-power - factor = 1. - error = fraction - for f, lf in self._multiples: - e = _numpy.fabs(fraction-lf) - if e < error: - error = e - factor = f - grid = factor * 10.**power - if power > 4 or power < -4: - format = '%+7.1e' - elif power >= 0: - digits = max(1, int(power)) - format = '%' + `digits`+'.0f' - else: - digits = -int(power) - format = '%'+`digits+2`+'.'+`digits`+'f' - #force grid when step is not None - if step is not None: grid = step - ticks = [] - t = -grid*_numpy.floor(-lower/grid) - while t <= upper: - if t == -0: t = 0 #remove neg zero condition - ticks.append( (t, format % (t,)) ) - t = t + grid - return ticks - - def _scope_ticks (self, lower, upper): - '''Always 10 divisions, no labels''' - grid = (upper - lower) / 10.0 - ticks = [] - t = lower - while t <= upper: - ticks.append( (t, "")) - t = t + grid - return ticks - - _multiples = [(2., _numpy.log10(2.)), (5., _numpy.log10(5.))] - - -#------------------------------------------------------------------------------- -# Used to layout the printer page - -class PlotPrintout(wx.Printout): - """Controls how the plot is made in printing and previewing""" - # Do not change method names in this class, - # we have to override wx.Printout methods here! - def __init__(self, graph): - """graph is instance of plotCanvas to be printed or previewed""" - wx.Printout.__init__(self) - self.graph = graph - - def HasPage(self, page): - if page == 1: - return True - else: - return False - - def GetPageInfo(self): - return (1, 1, 1, 1) # disable page numbers - - def OnPrintPage(self, page): - dc = self.GetDC() # allows using floats for certain functions -## print "PPI Printer",self.GetPPIPrinter() -## print "PPI Screen", self.GetPPIScreen() -## print "DC GetSize", dc.GetSize() -## print "GetPageSizePixels", self.GetPageSizePixels() - # Note PPIScreen does not give the correct number - # Calulate everything for printer and then scale for preview - PPIPrinter= self.GetPPIPrinter() # printer dots/inch (w,h) - #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h) - dcSize= dc.GetSize() # DC size - pageSize= self.GetPageSizePixels() # page size in terms of pixcels - clientDcSize= self.graph.GetClientSize() - - # find what the margins are (mm) - margLeftSize,margTopSize= self.graph.pageSetupData.GetMarginTopLeft() - margRightSize, margBottomSize= self.graph.pageSetupData.GetMarginBottomRight() - - # calculate offset and scale for dc - pixLeft= margLeftSize*PPIPrinter[0]/25.4 # mm*(dots/in)/(mm/in) - pixRight= margRightSize*PPIPrinter[0]/25.4 - pixTop= margTopSize*PPIPrinter[1]/25.4 - pixBottom= margBottomSize*PPIPrinter[1]/25.4 - - plotAreaW= pageSize[0]-(pixLeft+pixRight) - plotAreaH= pageSize[1]-(pixTop+pixBottom) - - # ratio offset and scale to screen size if preview - if self.IsPreview(): - ratioW= float(dcSize[0])/pageSize[0] - ratioH= float(dcSize[1])/pageSize[1] - pixLeft *= ratioW - pixTop *= ratioH - plotAreaW *= ratioW - plotAreaH *= ratioH - - # rescale plot to page or preview plot area - self.graph._setSize(plotAreaW,plotAreaH) - - # Set offset and scale - dc.SetDeviceOrigin(pixLeft,pixTop) - - # Thicken up pens and increase marker size for printing - ratioW= float(plotAreaW)/clientDcSize[0] - ratioH= float(plotAreaH)/clientDcSize[1] - aveScale= (ratioW+ratioH)/2 - self.graph._setPrinterScale(aveScale) # tickens up pens for printing - - self.graph._printDraw(dc) - # rescale back to original - self.graph._setSize() - self.graph._setPrinterScale(1) - self.graph.Redraw() #to get point label scale and shift correct - - return True - - - - -#--------------------------------------------------------------------------- -# if running standalone... -# -# ...a sample implementation using the above -# - -def _draw1Objects(): - # 100 points sin function, plotted as green circles - data1 = 2.*_numpy.pi*_numpy.arange(200)/200. - data1.shape = (100, 2) - data1[:,1] = _numpy.sin(data1[:,0]) - markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1) - - # 50 points cos function, plotted as red line - data1 = 2.*_numpy.pi*_numpy.arange(100)/100. - data1.shape = (50,2) - data1[:,1] = _numpy.cos(data1[:,0]) - lines = PolyLine(data1, legend= 'Red Line', colour='red') - - # A few more points... - pi = _numpy.pi - markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), - (3.*pi/4., -1)], legend='Cross Legend', colour='blue', - marker='cross') - - return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis") - -def _draw2Objects(): - # 100 points sin function, plotted as green dots - data1 = 2.*_numpy.pi*_numpy.arange(200)/200. - data1.shape = (100, 2) - data1[:,1] = _numpy.sin(data1[:,0]) - line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT) - - # 50 points cos function, plotted as red dot-dash - data1 = 2.*_numpy.pi*_numpy.arange(100)/100. - data1.shape = (50,2) - data1[:,1] = _numpy.cos(data1[:,0]) - line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH) - - # A few more points... - pi = _numpy.pi - markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), - (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6, - fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH, - marker='square') - - return PlotGraphics([markers1, line1, line2], "Big Markers with Different Line Styles") - -def _draw3Objects(): - markerList= ['circle', 'dot', 'square', 'triangle', 'triangle_down', - 'cross', 'plus', 'circle'] - m=[] - for i in range(len(markerList)): - m.append(PolyMarker([(2*i+.5,i+.5)], legend=markerList[i], colour='blue', - marker=markerList[i])) - return PlotGraphics(m, "Selection of Markers", "Minimal Axis", "No Axis") - -def _draw4Objects(): - # 25,000 point line - data1 = _numpy.arange(5e5,1e6,10) - data1.shape = (25000, 2) - line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5) - - # A few more points... - markers2 = PolyMarker(data1, legend='Square', colour='blue', - marker='square') - return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "") - -def _draw5Objects(): - # Empty graph with axis defined but no points/lines - points=[] - line1 = PolyLine(points, legend='Wide Line', colour='green', width=5) - return PlotGraphics([line1], "Empty Plot With Just Axes", "Value X", "Value Y") - -def _draw6Objects(): - # Bar graph - points1=[(1,0), (1,10)] - line1 = PolyLine(points1, colour='green', legend='Feb.', width=10) - points1g=[(2,0), (2,4)] - line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10) - points1b=[(3,0), (3,6)] - line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10) - - points2=[(4,0), (4,12)] - line2 = PolyLine(points2, colour='Yellow', legend='May', width=10) - points2g=[(5,0), (5,8)] - line2g = PolyLine(points2g, colour='orange', legend='June', width=10) - points2b=[(6,0), (6,4)] - line2b = PolyLine(points2b, colour='brown', legend='July', width=10) - - return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b], - "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students") - - -class TestFrame(wx.Frame): - def __init__(self, parent, id, title): - wx.Frame.__init__(self, parent, id, title, - wx.DefaultPosition, (600, 400)) - - # Now Create the menu bar and items - self.mainmenu = wx.MenuBar() - - menu = wx.Menu() - menu.Append(200, 'Page Setup...', 'Setup the printer page') - self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200) - - menu.Append(201, 'Print Preview...', 'Show the current plot on page') - self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201) - - menu.Append(202, 'Print...', 'Print the current plot') - self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202) - - menu.Append(203, 'Save Plot...', 'Save current plot') - self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203) - - menu.Append(205, 'E&xit', 'Enough of this already!') - self.Bind(wx.EVT_MENU, self.OnFileExit, id=205) - self.mainmenu.Append(menu, '&File') - - menu = wx.Menu() - menu.Append(206, 'Draw1', 'Draw plots1') - self.Bind(wx.EVT_MENU,self.OnPlotDraw1, id=206) - menu.Append(207, 'Draw2', 'Draw plots2') - self.Bind(wx.EVT_MENU,self.OnPlotDraw2, id=207) - menu.Append(208, 'Draw3', 'Draw plots3') - self.Bind(wx.EVT_MENU,self.OnPlotDraw3, id=208) - menu.Append(209, 'Draw4', 'Draw plots4') - self.Bind(wx.EVT_MENU,self.OnPlotDraw4, id=209) - menu.Append(210, 'Draw5', 'Draw plots5') - self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210) - menu.Append(260, 'Draw6', 'Draw plots6') - self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260) - - - menu.Append(211, '&Redraw', 'Redraw plots') - self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211) - menu.Append(212, '&Clear', 'Clear canvas') - self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212) - menu.Append(213, '&Scale', 'Scale canvas') - self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213) - menu.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK) - self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214) - menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK) - self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215) - menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK) - self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220) - menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK) - self.Bind(wx.EVT_MENU,self.OnEnablePointLabel, id=222) - - menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit') - self.Bind(wx.EVT_MENU,self.OnScrUp, id=225) - menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units') - self.Bind(wx.EVT_MENU,self.OnScrRt, id=230) - menu.Append(235, '&Plot Reset', 'Reset to original plot') - self.Bind(wx.EVT_MENU,self.OnReset, id=235) - - self.mainmenu.Append(menu, '&Plot') - - menu = wx.Menu() - menu.Append(300, '&About', 'About this thing...') - self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300) - self.mainmenu.Append(menu, '&Help') - - self.SetMenuBar(self.mainmenu) - - # A status bar to tell people what's happening - self.CreateStatusBar(1) - - self.client = PlotCanvas(self) - #define the function for drawing pointLabels - self.client.SetPointLabelFunc(self.DrawPointLabel) - # Create mouse event for showing cursor coords in status bar - self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown) - # Show closest point when enabled - self.client.Bind(wx.EVT_MOTION, self.OnMotion) - - self.Show(True) - - def DrawPointLabel(self, dc, mDataDict): - """This is the fuction that defines how the pointLabels are plotted - dc - DC that will be passed - mDataDict - Dictionary of data that you want to use for the pointLabel - - As an example I have decided I want a box at the curve point - with some text information about the curve plotted below. - Any wxDC method can be used. - """ - # ---------- - dc.SetPen(wx.Pen(wx.BLACK)) - dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) ) - - sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point - dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point - px,py = mDataDict["pointXY"] - cNum = mDataDict["curveNum"] - pntIn = mDataDict["pIndex"] - legend = mDataDict["legend"] - #make a string to display - s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn) - dc.DrawText(s, sx , sy+1) - # ----------- - - def OnMouseLeftDown(self,event): - s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event) - self.SetStatusText(s) - event.Skip() #allows plotCanvas OnMouseLeftDown to be called - - def OnMotion(self, event): - #show closest point (when enbled) - if self.client.GetEnablePointLabel() == True: - #make up dict with info for the pointLabel - #I've decided to mark the closest point on the closest curve - dlst= self.client.GetClosetPoint( self.client.GetXY(event), pointScaled= True) - if dlst != []: #returns [] if none - curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst - #make up dictionary to pass to my user function (see DrawPointLabel) - mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\ - "pointXY":pointXY, "scaledXY":scaledXY} - #pass dict to update the pointLabel - self.client.UpdatePointLabel(mDataDict) - event.Skip() #go to next handler - - def OnFilePageSetup(self, event): - self.client.PageSetup() - - def OnFilePrintPreview(self, event): - self.client.PrintPreview() - - def OnFilePrint(self, event): - self.client.Printout() - - def OnSaveFile(self, event): - self.client.SaveFile() - - def OnFileExit(self, event): - self.Close() - - def OnPlotDraw1(self, event): - self.resetDefaults() - self.client.Draw(_draw1Objects()) - - def OnPlotDraw2(self, event): - self.resetDefaults() - self.client.Draw(_draw2Objects()) - - def OnPlotDraw3(self, event): - self.resetDefaults() - self.client.SetFont(wx.Font(10,wx.SCRIPT,wx.NORMAL,wx.NORMAL)) - self.client.SetFontSizeAxis(20) - self.client.SetFontSizeLegend(12) - self.client.SetXSpec('min') - self.client.SetYSpec('none') - self.client.Draw(_draw3Objects()) - - def OnPlotDraw4(self, event): - self.resetDefaults() - drawObj= _draw4Objects() - self.client.Draw(drawObj) -## # profile -## start = _time.clock() -## for x in range(10): -## self.client.Draw(drawObj) -## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start) -## # profile end - - def OnPlotDraw5(self, event): - # Empty plot with just axes - self.resetDefaults() - drawObj= _draw5Objects() - # make the axis X= (0,5), Y=(0,10) - # (default with None is X= (-1,1), Y= (-1,1)) - self.client.Draw(drawObj, xAxis= (0,5), yAxis= (0,10)) - - def OnPlotDraw6(self, event): - #Bar Graph Example - self.resetDefaults() - #self.client.SetEnableLegend(True) #turn on Legend - #self.client.SetEnableGrid(True) #turn on Grid - self.client.SetXSpec('none') #turns off x-axis scale - self.client.SetYSpec('auto') - self.client.Draw(_draw6Objects(), xAxis= (0,7)) - - def OnPlotRedraw(self,event): - self.client.Redraw() - - def OnPlotClear(self,event): - self.client.Clear() - - def OnPlotScale(self, event): - if self.client.last_draw != None: - graphics, xAxis, yAxis= self.client.last_draw - self.client.Draw(graphics,(1,3.05),(0,1)) - - def OnEnableZoom(self, event): - self.client.SetEnableZoom(event.IsChecked()) - - def OnEnableGrid(self, event): - self.client.SetEnableGrid(event.IsChecked()) - - def OnEnableLegend(self, event): - self.client.SetEnableLegend(event.IsChecked()) - - def OnEnablePointLabel(self, event): - self.client.SetEnablePointLabel(event.IsChecked()) - - def OnScrUp(self, event): - self.client.ScrollUp(1) - - def OnScrRt(self,event): - self.client.ScrollRight(2) - - def OnReset(self,event): - self.client.Reset() - - def OnHelpAbout(self, event): - from wx.lib.dialogs import ScrolledMessageDialog - about = ScrolledMessageDialog(self, __doc__, "About...") - about.ShowModal() - - def resetDefaults(self): - """Just to reset the fonts back to the PlotCanvas defaults""" - self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL)) - self.client.SetFontSizeAxis(10) - self.client.SetFontSizeLegend(7) - self.client.SetXSpec('auto') - self.client.SetYSpec('auto') - - -def __test(): - - class MyApp(wx.App): - def OnInit(self): - wx.InitAllImageHandlers() - frame = TestFrame(None, -1, "PlotCanvas") - #frame.Show(True) - self.SetTopWindow(frame) - return True - - - app = MyApp(0) - app.MainLoop() - -if __name__ == '__main__': - __test() diff --git a/gr-wxgui/python/wxgui/plotter/__init__.py b/gr-wxgui/python/wxgui/plotter/__init__.py deleted file mode 100644 index 616492a3e6..0000000000 --- a/gr-wxgui/python/wxgui/plotter/__init__.py +++ /dev/null @@ -1,24 +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. -# - -from channel_plotter import channel_plotter -from waterfall_plotter import waterfall_plotter -from bar_plotter import bar_plotter diff --git a/gr-wxgui/python/wxgui/plotter/bar_plotter.py b/gr-wxgui/python/wxgui/plotter/bar_plotter.py deleted file mode 100644 index 487db66b64..0000000000 --- a/gr-wxgui/python/wxgui/plotter/bar_plotter.py +++ /dev/null @@ -1,150 +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. -# - -import wx -from grid_plotter_base import grid_plotter_base -from OpenGL import GL -import common -import numpy - -LEGEND_TEXT_FONT_SIZE = 8 -LEGEND_BOX_PADDING = 3 -MIN_PADDING = 0, 0, 0, 70 #top, right, bottom, left -#constants for the waveform storage -SAMPLES_KEY = 'samples' -COLOR_SPEC_KEY = 'color_spec' -MARKERY_KEY = 'marker' -TRIG_OFF_KEY = 'trig_off' - -################################################## -# Bar Plotter for histogram waveforms -################################################## -class bar_plotter(grid_plotter_base): - - def __init__(self, parent): - """ - Create a new bar plotter. - """ - #init - grid_plotter_base.__init__(self, parent, MIN_PADDING) - self._bars = list() - self._bar_width = .5 - self._color_spec = (0, 0, 0) - #setup bar cache - self._bar_cache = self.new_gl_cache(self._draw_bars) - #setup bar plotter - self.register_init(self._init_bar_plotter) - - def _init_bar_plotter(self): - """ - Run gl initialization tasks. - """ - GL.glEnableClientState(GL.GL_VERTEX_ARRAY) - - def _draw_bars(self): - """ - Draw the vertical bars. - """ - bars = self._bars - num_bars = len(bars) - if num_bars == 0: return - #use scissor to prevent drawing outside grid - GL.glEnable(GL.GL_SCISSOR_TEST) - GL.glScissor( - self.padding_left, - self.padding_bottom+1, - self.width-self.padding_left-self.padding_right-1, - self.height-self.padding_top-self.padding_bottom-1, - ) - #load the points - points = list() - width = self._bar_width/2 - for i, bar in enumerate(bars): - points.extend([ - (i-width, 0), - (i+width, 0), - (i+width, bar), - (i-width, bar), - ] - ) - GL.glColor3f(*self._color_spec) - #matrix transforms - GL.glPushMatrix() - GL.glTranslatef(self.padding_left, self.padding_top, 0) - GL.glScalef( - (self.width-self.padding_left-self.padding_right), - (self.height-self.padding_top-self.padding_bottom), - 1, - ) - GL.glTranslatef(0, 1, 0) - GL.glScalef(1.0/(num_bars-1), -1.0/(self.y_max-self.y_min), 1) - GL.glTranslatef(0, -self.y_min, 0) - #draw the bars - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_QUADS, 0, len(points)) - GL.glPopMatrix() - GL.glDisable(GL.GL_SCISSOR_TEST) - - def _populate_point_label(self, x_val, y_val): - """ - Get the text the will populate the point label. - Give X and Y values for the current point. - Give values for the channel at the X coordinate. - - Args: - x_val: the current x value - y_val: the current y value - - Returns: - a string with newlines - """ - if len(self._bars) == 0: return '' - scalar = float(len(self._bars)-1)/(self.x_max - self.x_min) - #convert x val to bar # - bar_index = scalar*(x_val - self.x_min) - #if abs(bar_index - round(bar_index)) > self._bar_width/2: return '' - bar_index = int(round(bar_index)) - bar_start = (bar_index - self._bar_width/2)/scalar + self.x_min - bar_end = (bar_index + self._bar_width/2)/scalar + self.x_min - bar_value = self._bars[bar_index] - return '%s to %s\n%s: %s'%( - common.eng_format(bar_start, self.x_units), - common.eng_format(bar_end, self.x_units), - self.y_label, common.eng_format(bar_value, self.y_units), - ) - - def set_bars(self, bars, bar_width, color_spec): - """ - Set the bars. - - Args: - bars: a list of bars - bar_width: the fractional width of the bar, between 0 and 1 - color_spec: the color tuple - """ - self.lock() - self._bars = bars - self._bar_width = float(bar_width) - self._color_spec = color_spec - self._bar_cache.changed(True) - self.unlock() - - diff --git a/gr-wxgui/python/wxgui/plotter/channel_plotter.py b/gr-wxgui/python/wxgui/plotter/channel_plotter.py deleted file mode 100644 index db174c7b28..0000000000 --- a/gr-wxgui/python/wxgui/plotter/channel_plotter.py +++ /dev/null @@ -1,249 +0,0 @@ -# -# Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -import wx -from grid_plotter_base import grid_plotter_base -from OpenGL import GL -import common -import numpy -import gltext -import math - -LEGEND_TEXT_FONT_SIZE = 8 -LEGEND_BOX_PADDING = 3 -MIN_PADDING = 35, 10, 0, 0 #top, right, bottom, left -#constants for the waveform storage -SAMPLES_KEY = 'samples' -COLOR_SPEC_KEY = 'color_spec' -MARKERY_KEY = 'marker' -TRIG_OFF_KEY = 'trig_off' - -################################################## -# Channel Plotter for X Y Waveforms -################################################## -class channel_plotter(grid_plotter_base): - - def __init__(self, parent): - """ - Create a new channel plotter. - """ - #init - grid_plotter_base.__init__(self, parent, MIN_PADDING) - self.set_use_persistence(False) - #setup legend cache - self._legend_cache = self.new_gl_cache(self._draw_legend, 50) - self.enable_legend(False) - #setup waveform cache - self._waveform_cache = self.new_gl_cache(self._draw_waveforms, 50) - self._channels = dict() - #init channel plotter - self.register_init(self._init_channel_plotter) - self.callback = None - - def _init_channel_plotter(self): - """ - Run gl initialization tasks. - """ - GL.glEnableClientState(GL.GL_VERTEX_ARRAY) - - def enable_legend(self, enable=None): - """ - Enable/disable the legend. - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_legend - self.lock() - self._enable_legend = enable - self._legend_cache.changed(True) - self.unlock() - - def _draw_waveforms(self): - """ - Draw the waveforms for each channel. - Scale the waveform data to the grid using gl matrix operations. - """ - #use scissor to prevent drawing outside grid - GL.glEnable(GL.GL_SCISSOR_TEST) - GL.glScissor( - self.padding_left+1, - self.padding_bottom+1, - self.width-self.padding_left-self.padding_right-1, - self.height-self.padding_top-self.padding_bottom-1, - ) - for channel in reversed(sorted(self._channels.keys())): - samples = self._channels[channel][SAMPLES_KEY] - num_samps = len(samples) - if not num_samps: continue - #use opengl to scale the waveform - GL.glPushMatrix() - GL.glTranslatef(self.padding_left, self.padding_top, 0) - GL.glScalef( - (self.width-self.padding_left-self.padding_right), - (self.height-self.padding_top-self.padding_bottom), - 1, - ) - GL.glTranslatef(0, 1, 0) - if isinstance(samples, tuple): - x_scale, x_trans = 1.0/(self.x_max-self.x_min), -self.x_min - points = zip(*samples) - else: - x_scale, x_trans = 1.0/(num_samps-1), -self._channels[channel][TRIG_OFF_KEY] - points = zip(numpy.arange(0, num_samps), samples) - GL.glScalef(x_scale, -1.0/(self.y_max-self.y_min), 1) - GL.glTranslatef(x_trans, -self.y_min, 0) - #draw the points/lines - GL.glColor3f(*self._channels[channel][COLOR_SPEC_KEY]) - marker = self._channels[channel][MARKERY_KEY] - if marker is None: - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_LINE_STRIP, 0, len(points)) - elif isinstance(marker, (int, float)) and marker > 0: - GL.glPointSize(marker) - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_POINTS, 0, len(points)) - GL.glPopMatrix() - GL.glDisable(GL.GL_SCISSOR_TEST) - - def _populate_point_label(self, x_val, y_val): - """ - Get the text the will populate the point label. - Give X and Y values for the current point. - Give values for the channel at the X coordinate. - - Args: - x_val: the current x value - y_val: the current y value - - Returns: - a string with newlines - """ - #create text - label_str = '%s: %s\n%s: %s'%( - self.x_label, common.eng_format(x_val, self.x_units), - self.y_label, common.eng_format(y_val, self.y_units), - ) - for channel in sorted(self._channels.keys()): - samples = self._channels[channel][SAMPLES_KEY] - num_samps = len(samples) - if not num_samps: continue - if isinstance(samples, tuple): continue - #linear interpolation - x_index = (num_samps-1)*(x_val-self.x_min)/(self.x_max-self.x_min) - x_index_low = int(math.floor(x_index)) - x_index_high = int(math.ceil(x_index)) - scale = x_index - x_index_low + self._channels[channel][TRIG_OFF_KEY] - y_value = (samples[x_index_high] - samples[x_index_low])*scale + samples[x_index_low] - if math.isnan(y_value): continue - label_str += '\n%s: %s'%(channel, common.eng_format(y_value, self.y_units)) - return label_str - - def _call_callback (self, x_val, y_val): - if self.callback != None: - self.callback(x_val, y_val) - - def set_callback (self, callback): - self.callback = callback - - def _draw_legend(self): - """ - Draw the legend in the upper right corner. - For each channel, draw a rectangle out of the channel color, - and overlay the channel text on top of the rectangle. - """ - if not self.enable_legend(): return - x_off = self.width - self.padding_right - LEGEND_BOX_PADDING - for i, channel in enumerate(reversed(sorted(self._channels.keys()))): - samples = self._channels[channel][SAMPLES_KEY] - if not len(samples): continue - color_spec = self._channels[channel][COLOR_SPEC_KEY] - txt = gltext.Text(channel, font_size=LEGEND_TEXT_FONT_SIZE) - w, h = txt.get_size() - #draw rect + text - GL.glColor3f(*color_spec) - self._draw_rect( - x_off - w - LEGEND_BOX_PADDING, - self.padding_top/2 - h/2 - LEGEND_BOX_PADDING, - w+2*LEGEND_BOX_PADDING, - h+2*LEGEND_BOX_PADDING, - ) - txt.draw_text(wx.Point(x_off - w, self.padding_top/2 - h/2)) - x_off -= w + 4*LEGEND_BOX_PADDING - - def clear_waveform(self, channel): - """ - Remove a waveform from the list of waveforms. - - Args: - channel: the channel key - """ - self.lock() - if channel in self._channels.keys(): - self._channels.pop(channel) - self._legend_cache.changed(True) - self._waveform_cache.changed(True) - self.unlock() - - def set_waveform(self, channel, samples=[], color_spec=(0, 0, 0), marker=None, trig_off=0): - """ - Set the waveform for a given channel. - - Args: - channel: the channel key - samples: the waveform samples - color_spec: the 3-tuple for line color - marker: None for line - trig_off: fraction of sample for trigger offset - """ - self.lock() - if channel not in self._channels.keys(): self._legend_cache.changed(True) - self._channels[channel] = { - SAMPLES_KEY: samples, - COLOR_SPEC_KEY: color_spec, - MARKERY_KEY: marker, - TRIG_OFF_KEY: trig_off, - } - self._waveform_cache.changed(True) - self.unlock() - -if __name__ == '__main__': - app = wx.PySimpleApp() - frame = wx.Frame(None, -1, 'Demo', wx.DefaultPosition) - vbox = wx.BoxSizer(wx.VERTICAL) - - plotter = channel_plotter(frame) - plotter.set_x_grid(-1, 1, .2) - plotter.set_y_grid(-1, 1, .4) - vbox.Add(plotter, 1, wx.EXPAND) - - plotter = channel_plotter(frame) - plotter.set_x_grid(-1, 1, .2) - plotter.set_y_grid(-1, 1, .4) - vbox.Add(plotter, 1, wx.EXPAND) - - frame.SetSizerAndFit(vbox) - frame.SetSize(wx.Size(800, 600)) - frame.Show() - app.MainLoop() diff --git a/gr-wxgui/python/wxgui/plotter/common.py b/gr-wxgui/python/wxgui/plotter/common.py deleted file mode 100644 index c296b1fa79..0000000000 --- a/gr-wxgui/python/wxgui/plotter/common.py +++ /dev/null @@ -1,149 +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. -# - -import threading -import time -import math -import wx - -################################################## -# Number formatting -################################################## -def get_exp(num): - """ - Get the exponent of the number in base 10. - - Args: - num: the floating point number - - Returns: - the exponent as an integer - """ - if num == 0: return 0 - return int(math.floor(math.log10(abs(num)))) - -def get_si_components(num): - """ - Get the SI units for the number. - Extract the coeff and exponent of the number. - The exponent will be a multiple of 3. - - Args: - num: the floating point number - - Returns: - the tuple coeff, exp, prefix - """ - num = float(num) - exp = get_exp(num) - exp -= exp%3 - exp = min(max(exp, -24), 24) #bounds on SI table below - prefix = { - 24: 'Y', 21: 'Z', - 18: 'E', 15: 'P', - 12: 'T', 9: 'G', - 6: 'M', 3: 'k', - 0: '', - -3: 'm', -6: 'u', - -9: 'n', -12: 'p', - -15: 'f', -18: 'a', - -21: 'z', -24: 'y', - }[exp] - coeff = num/10**exp - return coeff, exp, prefix - -def sci_format(num): - """ - Format a floating point number into scientific notation. - - Args: - num: the number to format - - Returns: - a label string - """ - coeff, exp, prefix = get_si_components(num) - if -3 <= exp < 3: return '%g'%num - return '%.3ge%d'%(coeff, exp) - -def eng_format(num, units=''): - """ - Format a floating point number into engineering notation. - - Args: - num: the number to format - units: the units to append - - Returns: - a label string - """ - coeff, exp, prefix = get_si_components(num) - if -3 <= exp < 3: return '%g'%num - return '%g%s%s%s'%(coeff, units and ' ' or '', prefix, units) - -################################################## -# Interface with thread safe lock/unlock -################################################## -class mutex(object): - _lock = threading.Lock() - def lock(self): self._lock.acquire() - def unlock(self): self._lock.release() - -################################################## -# Periodic update thread for point label -################################################## -class point_label_thread(threading.Thread, mutex): - - def __init__(self, plotter): - self._plotter = plotter - self._coor_queue = list() - #bind plotter mouse events - self._plotter.Bind(wx.EVT_MOTION, lambda evt: self.enqueue(evt.GetPosition())) - self._plotter.Bind(wx.EVT_LEAVE_WINDOW, lambda evt: self.enqueue(None)) - self._plotter.Bind(wx.EVT_RIGHT_DOWN, lambda evt: plotter.enable_point_label(not plotter.enable_point_label())) - self._plotter.Bind(wx.EVT_LEFT_DOWN, lambda evt: plotter.call_freq_callback(evt.GetPosition())) - #start the thread - threading.Thread.__init__(self) - self.start() - - def enqueue(self, coor): - self.lock() - self._coor_queue.append(coor) - self.unlock() - - def run(self): - last_ts = time.time() - last_coor = coor = None - try: - while True: - time.sleep(1.0/30.0) - self.lock() - #get most recent coor change - if self._coor_queue: - coor = self._coor_queue[-1] - self._coor_queue = list() - self.unlock() - #update if coor change, or enough time expired - if last_coor != coor or (time.time() - last_ts) > (1.0/2.0): - self._plotter.set_point_label_coordinate(coor) - last_coor = coor - last_ts = time.time() - except wx.PyDeadObjectError: pass diff --git a/gr-wxgui/python/wxgui/plotter/gltext.py b/gr-wxgui/python/wxgui/plotter/gltext.py deleted file mode 100644 index 55627bceb1..0000000000 --- a/gr-wxgui/python/wxgui/plotter/gltext.py +++ /dev/null @@ -1,507 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -# -# Provides some text display functions for wx + ogl -# Copyright (C) 2007 Christian Brugger, Stefan Hacker -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import wx -from OpenGL.GL import * - -""" -Optimize with psyco if possible, this gains us about 50% speed when -creating our textures in trade for about 4MBytes of additional memory usage for -psyco. If you don't like loosing the memory you have to turn the lines following -"enable psyco" into a comment while uncommenting the line after "Disable psyco". -""" -#Try to enable psyco -try: - import psyco - psyco_optimized = False -except ImportError: - psyco = None - -#Disable psyco -#psyco = None - -class TextElement(object): - """ - A simple class for using system Fonts to display - text in an OpenGL scene - """ - def __init__(self, - text = '', - font = None, - foreground = wx.BLACK, - centered = False): - """ - text (String) - Text - font (wx.Font) - Font to draw with (None = System default) - foreground (wx.Color) - Color of the text - or (wx.Bitmap)- Bitmap to overlay the text with - centered (bool) - Center the text - - Initializes the TextElement - """ - # save given variables - self._text = text - self._lines = text.split('\n') - self._font = font - self._foreground = foreground - self._centered = centered - - # init own variables - self._owner_cnt = 0 #refcounter - self._texture = None #OpenGL texture ID - self._text_size = None #x/y size tuple of the text - self._texture_size= None #x/y Texture size tuple - - # create Texture - self.createTexture() - - - #---Internal helpers - - def _getUpper2Base(self, value): - """ - Returns the lowest value with the power of - 2 greater than 'value' (2^n>value) - """ - base2 = 1 - while base2 < value: - base2 *= 2 - return base2 - - #---Functions - - def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0): - """ - position (wx.Point) - x/y Position to draw in scene - scale (float) - Scale - rotation (int) - Rotation in degree - - Draws the text to the scene - """ - #Enable necessary functions - glColor(1,1,1,1) - glEnable(GL_TEXTURE_2D) - glEnable(GL_ALPHA_TEST) #Enable alpha test - glAlphaFunc(GL_GREATER, 0) - glEnable(GL_BLEND) #Enable blending - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #Bind texture - glBindTexture(GL_TEXTURE_2D, self._texture) - - ow, oh = self._text_size - w , h = self._texture_size - #Perform transformations - glPushMatrix() - glTranslated(position.x, position.y, 0) - glRotate(-rotation, 0, 0, 1) - glScaled(scale, scale, scale) - if self._centered: - glTranslate(-w/2, -oh/2, 0) - #Draw vertices - glBegin(GL_QUADS) - glTexCoord2f(0,0); glVertex2f(0,0) - glTexCoord2f(0,1); glVertex2f(0,h) - glTexCoord2f(1,1); glVertex2f(w,h) - glTexCoord2f(1,0); glVertex2f(w,0) - glEnd() - glPopMatrix() - - #Disable features - glDisable(GL_BLEND) - glDisable(GL_ALPHA_TEST) - glDisable(GL_TEXTURE_2D) - - def createTexture(self): - """ - Creates a texture from the settings saved in TextElement, to be able to use normal - system fonts conviently a wx.MemoryDC is used to draw on a wx.Bitmap. As wxwidgets - device contexts don't support alpha at all it is necessary to apply a little hack - to preserve antialiasing without sticking to a fixed background color: - - We draw the bmp in b/w mode so we can use its data as a alpha channel for a solid - color bitmap which after GL_ALPHA_TEST and GL_BLEND will show a nicely antialiased - text on any surface. - - To access the raw pixel data the bmp gets converted to a wx.Image. Now we just have - to merge our foreground color with the alpha data we just created and push it all - into a OpenGL texture and we are DONE *inhalesdelpy* - - DRAWBACK of the whole conversion thing is a really long time for creating the - texture. If you see any optimizations that could save time PLEASE CREATE A PATCH!!! - """ - # get a memory dc - dc = wx.MemoryDC() - - # Select an empty bitmap into the MemoryDC - otherwise the call to - # GetMultiLineTextExtent() may fail below - dc.SelectObject(wx.EmptyBitmap(1,1)) - - # set our font - dc.SetFont(self._font) - - # Approximate extend to next power of 2 and create our bitmap - # REMARK: You wouldn't believe how much fucking speed this little - # sucker gains compared to sizes not of the power of 2. It's like - # 500ms --> 0.5ms (on my ATI-GPU powered Notebook). On Sams nvidia - # machine there don't seem to occur any losses...bad drivers? - ow, oh = dc.GetMultiLineTextExtent(self._text)[:2] - w, h = self._getUpper2Base(ow), self._getUpper2Base(oh) - - self._text_size = wx.Size(ow,oh) - self._texture_size = wx.Size(w,h) - bmp = wx.EmptyBitmap(w,h) - - - #Draw in b/w mode to bmp so we can use it as alpha channel - dc.SelectObject(bmp) - dc.SetBackground(wx.BLACK_BRUSH) - dc.Clear() - dc.SetTextForeground(wx.WHITE) - x,y = 0,0 - centered = self.centered - for line in self._lines: - if not line: line = ' ' - tw, th = dc.GetTextExtent(line) - if centered: - x = int(round((w-tw)/2)) - dc.DrawText(line, x, y) - x = 0 - y += th - #Release the dc - dc.SelectObject(wx.NullBitmap) - del dc - - #Generate a correct RGBA data string from our bmp - """ - NOTE: You could also use wx.AlphaPixelData to access the pixel data - in 'bmp' directly, but the iterator given by it is much slower than - first converting to an image and using wx.Image.GetData(). - """ - img = wx.ImageFromBitmap(bmp) - alpha = img.GetData() - - if isinstance(self._foreground, wx.Colour): - """ - If we have a static color... - """ - r,g,b = self._foreground.Get() - color = "%c%c%c" % (chr(r), chr(g), chr(b)) - - data = '' - for i in xrange(0, len(alpha)-1, 3): - data += color + alpha[i] - - elif isinstance(self._foreground, wx.Bitmap): - """ - If we have a bitmap... - """ - bg_img = wx.ImageFromBitmap(self._foreground) - bg = bg_img.GetData() - bg_width = self._foreground.GetWidth() - bg_height = self._foreground.GetHeight() - - data = '' - - for y in xrange(0, h): - for x in xrange(0, w): - if (y > (bg_height-1)) or (x > (bg_width-1)): - color = "%c%c%c" % (chr(0),chr(0),chr(0)) - else: - pos = (x+y*bg_width) * 3 - color = bg[pos:pos+3] - data += color + alpha[(x+y*w)*3] - - - # now convert it to ogl texture - self._texture = glGenTextures(1) - glBindTexture(GL_TEXTURE_2D, self._texture) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0) - glPixelStorei(GL_UNPACK_ALIGNMENT, 2) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) - - def deleteTexture(self): - """ - Deletes the OpenGL texture object - """ - if self._texture: - if glIsTexture(self._texture): - glDeleteTextures(self._texture) - else: - self._texture = None - - def bind(self): - """ - Increase refcount - """ - self._owner_cnt += 1 - - def release(self): - """ - Decrease refcount - """ - self._owner_cnt -= 1 - - def isBound(self): - """ - Return refcount - """ - return self._owner_cnt - - def __del__(self): - """ - Destructor - """ - self.deleteTexture() - - #---Getters/Setters - - def getText(self): return self._text - def getFont(self): return self._font - def getForeground(self): return self._foreground - def getCentered(self): return self._centered - def getTexture(self): return self._texture - def getTexture_size(self): return self._texture_size - - def getOwner_cnt(self): return self._owner_cnt - def setOwner_cnt(self, value): - self._owner_cnt = value - - #---Properties - - text = property(getText, None, None, "Text of the object") - font = property(getFont, None, None, "Font of the object") - foreground = property(getForeground, None, None, "Color of the text") - centered = property(getCentered, None, None, "Is text centered") - owner_cnt = property(getOwner_cnt, setOwner_cnt, None, "Owner count") - texture = property(getTexture, None, None, "Used texture") - texture_size = property(getTexture_size, None, None, "Size of the used texture") - - -class Text(object): - """ - A simple class for using System Fonts to display text in - an OpenGL scene. The Text adds a global Cache of already - created text elements to TextElement's base functionality - so you can save some memory and increase speed - """ - _texts = [] #Global cache for TextElements - - def __init__(self, - text = 'Text', - font = None, - font_size = 8, - foreground = wx.BLACK, - centered = False, - bold = False): - """ - text (string) - displayed text - font (wx.Font) - if None, system default font will be used with font_size - font_size (int) - font size in points - foreground (wx.Color) - Color of the text - or (wx.Bitmap) - Bitmap to overlay the text with - centered (bool) - should the text drawn centered towards position? - - Initializes the text object - """ - #Init/save variables - self._aloc_text = None - self._text = text - self._font_size = font_size - self._foreground= foreground - self._centered = centered - - #Check if we are offered a font - if not font: - #if not use the system default - self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) - else: - #save it - self._font = font - - if bold: self._font.SetWeight(wx.FONTWEIGHT_BOLD) - - #Bind us to our texture - self._initText() - - #---Internal helpers - - def _initText(self): - """ - Initializes/Reinitializes the Text object by binding it - to a TextElement suitable for its current settings - """ - #Check if we already bound to a texture - if self._aloc_text: - #if so release it - self._aloc_text.release() - if not self._aloc_text.isBound(): - self._texts.remove(self._aloc_text) - self._aloc_text = None - - #Adjust our font - self._font.SetPointSize(self._font_size) - - #Search for existing element in our global buffer - for element in self._texts: - if element.text == self._text and\ - element.font == self._font and\ - element.foreground == self._foreground and\ - element.centered == self._centered: - # We already exist in global buffer ;-) - element.bind() - self._aloc_text = element - break - - if not self._aloc_text: - # We are not in the global buffer, let's create ourselves - aloc_text = self._aloc_text = TextElement(self._text, - self._font, - self._foreground, - self._centered) - aloc_text.bind() - self._texts.append(aloc_text) - - def __del__(self): - """ - Destructor - """ - aloc_text = self._aloc_text - aloc_text.release() - if not aloc_text.isBound(): - self._texts.remove(aloc_text) - - #---Functions - - def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0): - """ - position (wx.Point) - x/y Position to draw in scene - scale (float) - Scale - rotation (int) - Rotation in degree - - Draws the text to the scene - """ - - self._aloc_text.draw_text(position, scale, rotation) - - #---Setter/Getter - - def getText(self): return self._text - def setText(self, value, reinit = True): - """ - value (bool) - New Text - reinit (bool) - Create a new texture - - Sets a new text - """ - self._text = value - if reinit: - self._initText() - - def getFont(self): return self._font - def setFont(self, value, reinit = True): - """ - value (bool) - New Font - reinit (bool) - Create a new texture - - Sets a new font - """ - self._font = value - if reinit: - self._initText() - - def getFont_size(self): return self._font_size - def setFont_size(self, value, reinit = True): - """ - value (bool) - New font size - reinit (bool) - Create a new texture - - Sets a new font size - """ - self._font_size = value - if reinit: - self._initText() - - def getForeground(self): return self._foreground - def setForeground(self, value, reinit = True): - """ - value (bool) - New centered value - reinit (bool) - Create a new texture - - Sets a new value for 'centered' - """ - self._foreground = value - if reinit: - self._initText() - - def getCentered(self): return self._centered - def setCentered(self, value, reinit = True): - """ - value (bool) - New centered value - reinit (bool) - Create a new texture - - Sets a new value for 'centered' - """ - self._centered = value - if reinit: - self._initText() - - def get_size(self): - """ - Returns a text size tuple - """ - return self._aloc_text._text_size - - def getTexture_size(self): - """ - Returns a texture size tuple - """ - return self._aloc_text.texture_size - - def getTextElement(self): - """ - Returns the text element bound to the Text class - """ - return self._aloc_text - - def getTexture(self): - """ - Returns the texture of the bound TextElement - """ - return self._aloc_text.texture - - - #---Properties - - text = property(getText, setText, None, "Text of the object") - font = property(getFont, setFont, None, "Font of the object") - font_size = property(getFont_size, setFont_size, None, "Font size") - foreground = property(getForeground, setForeground, None, "Color/Overlay bitmap of the text") - centered = property(getCentered, setCentered, None, "Display the text centered") - texture_size = property(getTexture_size, None, None, "Size of the used texture") - texture = property(getTexture, None, None, "Texture of bound TextElement") - text_element = property(getTextElement,None , None, "TextElement bound to this class") - -#Optimize critical functions -if psyco and not psyco_optimized: - psyco.bind(TextElement.createTexture) - psyco_optimized = True diff --git a/gr-wxgui/python/wxgui/plotter/grid_plotter_base.py b/gr-wxgui/python/wxgui/plotter/grid_plotter_base.py deleted file mode 100644 index bc48ad72f5..0000000000 --- a/gr-wxgui/python/wxgui/plotter/grid_plotter_base.py +++ /dev/null @@ -1,458 +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. -# - -import wx -import wx.glcanvas -from OpenGL import GL -import common -from plotter_base import plotter_base -import gltext -import math - -GRID_LINE_COLOR_SPEC = (.7, .7, .7) #gray -GRID_BORDER_COLOR_SPEC = (0, 0, 0) #black -TICK_TEXT_FONT_SIZE = 9 -TITLE_TEXT_FONT_SIZE = 13 -UNITS_TEXT_FONT_SIZE = 9 -AXIS_LABEL_PADDING = 5 -TICK_LABEL_PADDING = 5 -TITLE_LABEL_PADDING = 7 -POINT_LABEL_FONT_SIZE = 8 -POINT_LABEL_COLOR_SPEC = (1, 1, 0.5, 0.75) -POINT_LABEL_PADDING = 3 -POINT_LABEL_OFFSET = 10 -GRID_LINE_DASH_LEN = 4 - -################################################## -# Grid Plotter Base Class -################################################## -class grid_plotter_base(plotter_base): - - def __init__(self, parent, min_padding=(0, 0, 0, 0)): - plotter_base.__init__(self, parent) - #setup grid cache - self._grid_cache = self.new_gl_cache(self._draw_grid, 25) - self.enable_grid_lines(True) - #setup padding - self.padding_top_min, self.padding_right_min, self.padding_bottom_min, self.padding_left_min = min_padding - #store title and unit strings - self.set_title('Title') - self.set_x_label('X Label') - self.set_y_label('Y Label') - #init the grid to some value - self.set_x_grid(-1, 1, 1) - self.set_y_grid(-1, 1, 1) - #setup point label cache - self._point_label_cache = self.new_gl_cache(self._draw_point_label, 75) - self.enable_point_label(False) - self.enable_grid_aspect_ratio(False) - self.set_point_label_coordinate(None) - common.point_label_thread(self) - #init grid plotter - self.register_init(self._init_grid_plotter) - - def _init_grid_plotter(self): - """ - Run gl initialization tasks. - """ - GL.glEnableClientState(GL.GL_VERTEX_ARRAY) - - def set_point_label_coordinate(self, coor): - """ - Set the point label coordinate. - - Args: - coor: the coordinate x, y tuple or None - """ - self.lock() - self._point_label_coordinate = coor - self._point_label_cache.changed(True) - self.update() - self.unlock() - - def call_freq_callback(self, coor): - try: - x, y = self._point_label_coordinate - except: - return - if x < self.padding_left or x > self.width-self.padding_right: return - if y < self.padding_top or y > self.height-self.padding_bottom: return - #scale to window bounds - x_win_scalar = float(x - self.padding_left)/(self.width-self.padding_left-self.padding_right) - y_win_scalar = float((self.height - y) - self.padding_bottom)/(self.height-self.padding_top-self.padding_bottom) - #scale to grid bounds - x_val = x_win_scalar*(self.x_max-self.x_min) + self.x_min - y_val = y_win_scalar*(self.y_max-self.y_min) + self.y_min - self._call_callback(x_val, y_val) - - def enable_grid_aspect_ratio(self, enable=None): - """ - Enable/disable the grid aspect ratio. - If enabled, enforce the aspect ratio on the padding: - horizontal_padding:vertical_padding == width:height - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_grid_aspect_ratio - self.lock() - self._enable_grid_aspect_ratio = enable - for cache in self._gl_caches: cache.changed(True) - self.unlock() - - def enable_point_label(self, enable=None): - """ - Enable/disable the point label. - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_point_label - self.lock() - self._enable_point_label = enable - self._point_label_cache.changed(True) - self.unlock() - - def set_title(self, title): - """ - Set the title. - - Args: - title the title string - """ - self.lock() - self.title = title - self._grid_cache.changed(True) - self.unlock() - - def set_x_label(self, x_label, x_units=''): - """ - Set the x label and units. - - Args: - x_label: the x label string - x_units: the x units string - """ - self.lock() - self.x_label = x_label - self.x_units = x_units - self._grid_cache.changed(True) - self.unlock() - - def set_y_label(self, y_label, y_units=''): - """ - Set the y label and units. - - Args: - y_label: the y label string - y_units: the y units string - """ - self.lock() - self.y_label = y_label - self.y_units = y_units - self._grid_cache.changed(True) - self.unlock() - - def set_x_grid(self, minimum, maximum, step, scale=False): - """ - Set the x grid parameters. - - Args: - minimum: the left-most value - maximum: the right-most value - step: the grid spacing - scale: true to scale the x grid - """ - self.lock() - self.x_min = float(minimum) - self.x_max = float(maximum) - self.x_step = float(step) - if scale: - coeff, exp, prefix = common.get_si_components(max(abs(self.x_min), abs(self.x_max))) - self.x_scalar = 10**(-exp) - self.x_prefix = prefix - else: - self.x_scalar = 1.0 - self.x_prefix = '' - for cache in self._gl_caches: cache.changed(True) - self.unlock() - - def set_y_grid(self, minimum, maximum, step, scale=False): - """ - Set the y grid parameters. - - Args: - minimum: the bottom-most value - maximum: the top-most value - step: the grid spacing - scale: true to scale the y grid - """ - self.lock() - self.y_min = float(minimum) - self.y_max = float(maximum) - self.y_step = float(step) - if scale: - coeff, exp, prefix = common.get_si_components(max(abs(self.y_min), abs(self.y_max))) - self.y_scalar = 10**(-exp) - self.y_prefix = prefix - else: - self.y_scalar = 1.0 - self.y_prefix = '' - for cache in self._gl_caches: cache.changed(True) - self.unlock() - - def _draw_grid(self): - """ - Create the x, y, tick, and title labels. - Resize the padding for the labels. - Draw the border, grid, title, and labels. - """ - ################################################## - # Create GL text labels - ################################################## - #create x tick labels - x_tick_labels = [(tick, self._get_tick_label(tick, self.x_units)) - for tick in self._get_ticks(self.x_min, self.x_max, self.x_step, self.x_scalar)] - #create x tick labels - y_tick_labels = [(tick, self._get_tick_label(tick, self.y_units)) - for tick in self._get_ticks(self.y_min, self.y_max, self.y_step, self.y_scalar)] - #create x label - x_label_str = self.x_units and "%s (%s%s)"%(self.x_label, self.x_prefix, self.x_units) or self.x_label - x_label = gltext.Text(x_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) - #create y label - y_label_str = self.y_units and "%s (%s%s)"%(self.y_label, self.y_prefix, self.y_units) or self.y_label - y_label = gltext.Text(y_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) - #create title - title_label = gltext.Text(self.title, bold=True, font_size=TITLE_TEXT_FONT_SIZE, centered=True) - ################################################## - # Resize the padding - ################################################## - self.padding_top = max(2*TITLE_LABEL_PADDING + title_label.get_size()[1], self.padding_top_min) - self.padding_right = max(2*TICK_LABEL_PADDING, self.padding_right_min) - self.padding_bottom = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + x_label.get_size()[1] + max([label.get_size()[1] for tick, label in x_tick_labels]), self.padding_bottom_min) - self.padding_left = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + y_label.get_size()[1] + max([label.get_size()[0] for tick, label in y_tick_labels]), self.padding_left_min) - #enforce padding aspect ratio if enabled - if self.enable_grid_aspect_ratio(): - w_over_h_ratio = float(self.width)/float(self.height) - horizontal_padding = float(self.padding_right + self.padding_left) - veritical_padding = float(self.padding_top + self.padding_bottom) - if w_over_h_ratio > horizontal_padding/veritical_padding: - #increase the horizontal padding - new_padding = veritical_padding*w_over_h_ratio - horizontal_padding - #distribute the padding to left and right - self.padding_left += int(round(new_padding/2)) - self.padding_right += int(round(new_padding/2)) - else: - #increase the vertical padding - new_padding = horizontal_padding/w_over_h_ratio - veritical_padding - #distribute the padding to top and bottom - self.padding_top += int(round(new_padding/2)) - self.padding_bottom += int(round(new_padding/2)) - ################################################## - # Draw Grid X - ################################################## - for tick, label in x_tick_labels: - scaled_tick = (self.width-self.padding_left-self.padding_right)*\ - (tick/self.x_scalar-self.x_min)/(self.x_max-self.x_min) + self.padding_left - self._draw_grid_line( - (scaled_tick, self.padding_top), - (scaled_tick, self.height-self.padding_bottom), - ) - w, h = label.get_size() - label.draw_text(wx.Point(scaled_tick-w/2, self.height-self.padding_bottom+TICK_LABEL_PADDING)) - ################################################## - # Draw Grid Y - ################################################## - for tick, label in y_tick_labels: - scaled_tick = (self.height-self.padding_top-self.padding_bottom)*\ - (1 - (tick/self.y_scalar-self.y_min)/(self.y_max-self.y_min)) + self.padding_top - self._draw_grid_line( - (self.padding_left, scaled_tick), - (self.width-self.padding_right, scaled_tick), - ) - w, h = label.get_size() - label.draw_text(wx.Point(self.padding_left-w-TICK_LABEL_PADDING, scaled_tick-h/2)) - ################################################## - # Draw Border - ################################################## - GL.glColor3f(*GRID_BORDER_COLOR_SPEC) - self._draw_rect( - self.padding_left, - self.padding_top, - self.width - self.padding_right - self.padding_left, - self.height - self.padding_top - self.padding_bottom, - fill=False, - ) - ################################################## - # Draw Labels - ################################################## - #draw title label - title_label.draw_text(wx.Point(self.width/2.0, TITLE_LABEL_PADDING + title_label.get_size()[1]/2)) - #draw x labels - x_label.draw_text(wx.Point( - (self.width-self.padding_left-self.padding_right)/2.0 + self.padding_left, - self.height-(AXIS_LABEL_PADDING + x_label.get_size()[1]/2), - ) - ) - #draw y labels - y_label.draw_text(wx.Point( - AXIS_LABEL_PADDING + y_label.get_size()[1]/2, - (self.height-self.padding_top-self.padding_bottom)/2.0 + self.padding_top, - ), rotation=90, - ) - - def _get_tick_label(self, tick, unit): - """ - Format the tick value and create a gl text. - - Args: - tick: the floating point tick value - unit: the axis unit - - Returns: - the tick label text - """ - if unit: tick_str = common.sci_format(tick) - else: tick_str = common.eng_format(tick) - return gltext.Text(tick_str, font_size=TICK_TEXT_FONT_SIZE) - - def _get_ticks(self, min, max, step, scalar): - """ - Determine the positions for the ticks. - - Args: - min: the lower bound - max: the upper bound - step: the grid spacing - scalar: the grid scaling - - Returns: - a list of tick positions between min and max - """ - #cast to float - min = float(min) - max = float(max) - step = float(step) - #check for valid numbers - try: - assert step > 0 - assert max > min - assert max - min > step - except AssertionError: return [-1, 1] - #determine the start and stop value - start = int(math.ceil(min/step)) - stop = int(math.floor(max/step)) - return [i*step*scalar for i in range(start, stop+1)] - - def enable_grid_lines(self, enable=None): - """ - Enable/disable the grid lines. - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_grid_lines - self.lock() - self._enable_grid_lines = enable - self._grid_cache.changed(True) - self.unlock() - - def _draw_grid_line(self, coor1, coor2): - """ - Draw a dashed line from coor1 to coor2. - - Args: - corr1: a tuple of x, y - corr2: a tuple of x, y - """ - if not self.enable_grid_lines(): return - length = math.sqrt((coor1[0] - coor2[0])**2 + (coor1[1] - coor2[1])**2) - num_points = int(length/GRID_LINE_DASH_LEN) - #calculate points array - points = [( - coor1[0] + i*(coor2[0]-coor1[0])/(num_points - 1), - coor1[1] + i*(coor2[1]-coor1[1])/(num_points - 1) - ) for i in range(num_points)] - #set color and draw - GL.glColor3f(*GRID_LINE_COLOR_SPEC) - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_LINES, 0, len(points)) - - def _draw_rect(self, x, y, width, height, fill=True): - """ - Draw a rectangle on the x, y plane. - X and Y are the top-left corner. - - Args: - x: the left position of the rectangle - y: the top position of the rectangle - width: the width of the rectangle - height: the height of the rectangle - fill: true to color inside of rectangle - """ - GL.glBegin(fill and GL.GL_QUADS or GL.GL_LINE_LOOP) - GL.glVertex2f(x, y) - GL.glVertex2f(x+width, y) - GL.glVertex2f(x+width, y+height) - GL.glVertex2f(x, y+height) - GL.glEnd() - - def _draw_point_label(self): - """ - Draw the point label for the last mouse motion coordinate. - The mouse coordinate must be an X, Y tuple. - The label will be drawn at the X, Y coordinate. - The values of the X, Y coordinate will be scaled to the current X, Y bounds. - """ - if not self.enable_point_label(): return - if not self._point_label_coordinate: return - x, y = self._point_label_coordinate - if x < self.padding_left or x > self.width-self.padding_right: return - if y < self.padding_top or y > self.height-self.padding_bottom: return - #scale to window bounds - x_win_scalar = float(x - self.padding_left)/(self.width-self.padding_left-self.padding_right) - y_win_scalar = float((self.height - y) - self.padding_bottom)/(self.height-self.padding_top-self.padding_bottom) - #scale to grid bounds - x_val = x_win_scalar*(self.x_max-self.x_min) + self.x_min - y_val = y_win_scalar*(self.y_max-self.y_min) + self.y_min - #create text - label_str = self._populate_point_label(x_val, y_val) - if not label_str: return - txt = gltext.Text(label_str, font_size=POINT_LABEL_FONT_SIZE) - w, h = txt.get_size() - #enable transparency - GL.glEnable(GL.GL_BLEND) - GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) - #draw rect + text - GL.glColor4f(*POINT_LABEL_COLOR_SPEC) - if x > self.width/2: x -= w+2*POINT_LABEL_PADDING + POINT_LABEL_OFFSET - else: x += POINT_LABEL_OFFSET - self._draw_rect(x, y-h-2*POINT_LABEL_PADDING, w+2*POINT_LABEL_PADDING, h+2*POINT_LABEL_PADDING) - txt.draw_text(wx.Point(x+POINT_LABEL_PADDING, y-h-POINT_LABEL_PADDING)) diff --git a/gr-wxgui/python/wxgui/plotter/plotter_base.py b/gr-wxgui/python/wxgui/plotter/plotter_base.py deleted file mode 100644 index ca904908ec..0000000000 --- a/gr-wxgui/python/wxgui/plotter/plotter_base.py +++ /dev/null @@ -1,220 +0,0 @@ -# -# Copyright 2008, 2009, 2010 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 wx.glcanvas -from OpenGL import GL -import common - -BACKGROUND_COLOR_SPEC = (1, 0.976, 1, 1) #creamy white - -################################################## -# GL caching interface -################################################## -class gl_cache(object): - """ - Cache a set of gl drawing routines in a compiled list. - """ - - def __init__(self, draw): - """ - Create a new cache. - - Args: - draw: a function to draw gl stuff - """ - self.changed(True) - self._draw = draw - - def init(self): - """ - To be called when gl initializes. - Create a new compiled list. - """ - self._grid_compiled_list_id = GL.glGenLists(1) - - def draw(self): - """ - Draw the gl stuff using a compiled list. - If changed, reload the compiled list. - """ - if self.changed(): - GL.glNewList(self._grid_compiled_list_id, GL.GL_COMPILE) - self._draw() - GL.glEndList() - self.changed(False) - #draw the grid - GL.glCallList(self._grid_compiled_list_id) - - def changed(self, state=None): - """ - Set the changed flag if state is not None. - Otherwise return the changed flag. - """ - if state is None: return self._changed - self._changed = state - -################################################## -# OpenGL WX Plotter Canvas -################################################## -class plotter_base(wx.glcanvas.GLCanvas, common.mutex): - """ - Plotter base class for all plot types. - """ - - def __init__(self, parent): - """ - Create a new plotter base. - Initialize the GLCanvas with double buffering. - Initialize various plotter flags. - Bind the paint and size events. - - Args: - parent: the parent widgit - """ - attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) - wx.glcanvas.GLCanvas.__init__(self, parent, wx.ID_ANY, attribList=attribList); # Specifically use the CTOR which does NOT create an implicit GL context - self._gl_ctx = wx.glcanvas.GLContext(self) # Create the explicit GL context - self.use_persistence=False - self.persist_alpha=2.0/15 - self.clear_accum=True - self._gl_init_flag = False - self._resized_flag = True - self._init_fcns = list() - self._draw_fcns = list() - self._gl_caches = list() - self.Bind(wx.EVT_PAINT, self._on_paint) - self.Bind(wx.EVT_SIZE, self._on_size) - self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) - - def set_use_persistence(self,enable): - self.use_persistence=enable - self.clear_accum=True - - def set_persist_alpha(self,analog_alpha): - self.persist_alpha=analog_alpha - - def new_gl_cache(self, draw_fcn, draw_pri=50): - """ - Create a new gl cache. - Register its draw and init function. - - Returns: - the new cache object - """ - cache = gl_cache(draw_fcn) - self.register_init(cache.init) - self.register_draw(cache.draw, draw_pri) - self._gl_caches.append(cache) - return cache - - def register_init(self, init_fcn): - self._init_fcns.append(init_fcn) - - def register_draw(self, draw_fcn, draw_pri=50): - """ - Register a draw function with a layer priority. - Large pri values are drawn last. - Small pri values are drawn first. - """ - for i in range(len(self._draw_fcns)): - if draw_pri < self._draw_fcns[i][0]: - self._draw_fcns.insert(i, (draw_pri, draw_fcn)) - return - self._draw_fcns.append((draw_pri, draw_fcn)) - - def _on_size(self, event): - """ - Flag the resize event. - The paint event will handle the actual resizing. - """ - self.lock() - self._resized_flag = True - self.clear_accum=True - self.unlock() - - def _on_paint(self, event): - """ - Respond to paint events. - Initialize GL if this is the first paint event. - Resize the view port if the width or height changed. - Redraw the screen, calling the draw functions. - """ - if not self.IsShownOnScreen(): # Cannot realise a GL context on OS X if window is not yet shown - return - # create device context (needed on Windows, noop on X) - dc = None - if event.GetEventObject(): # Only create DC if paint triggered by WM message (for OS X) - dc = wx.PaintDC(self) - self.lock() - self.SetCurrent(self._gl_ctx) # Real the explicit GL context - - # check if gl was initialized - if not self._gl_init_flag: - GL.glClearColor(*BACKGROUND_COLOR_SPEC) - for fcn in self._init_fcns: fcn() - self._gl_init_flag = True - - # check for a change in window size - if self._resized_flag: - self.width, self.height = self.GetSize() - GL.glMatrixMode(GL.GL_PROJECTION) - GL.glLoadIdentity() - GL.glOrtho(0, self.width, self.height, 0, 1, 0) - GL.glMatrixMode(GL.GL_MODELVIEW) - GL.glLoadIdentity() - GL.glViewport(0, 0, self.width, self.height) - for cache in self._gl_caches: cache.changed(True) - self._resized_flag = False - - # clear buffer if needed - if self.clear_accum or not self.use_persistence: - GL.glClear(GL.GL_COLOR_BUFFER_BIT) - self.clear_accum=False - - # apply fading - if self.use_persistence: - GL.glEnable(GL.GL_BLEND) - GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) - - GL.glBegin(GL.GL_QUADS) - GL.glColor4f(1,1,1,self.persist_alpha) - GL.glVertex2f(0, self.height) - GL.glVertex2f(self.width, self.height) - GL.glVertex2f(self.width, 0) - GL.glVertex2f(0, 0) - GL.glEnd() - - GL.glDisable(GL.GL_BLEND) - - # draw functions - for fcn in self._draw_fcns: fcn[1]() - - # show result - self.SwapBuffers() - self.unlock() - - def update(self): - """ - Force a paint event. - """ - if not self._gl_init_flag: return - wx.PostEvent(self, wx.PaintEvent()) diff --git a/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py b/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py deleted file mode 100644 index a5601e25bc..0000000000 --- a/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py +++ /dev/null @@ -1,294 +0,0 @@ -# -# Copyright 2008, 2009, 2010 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -import wx -from grid_plotter_base import grid_plotter_base -from OpenGL import GL -import common -import numpy -import gltext -import math -import struct - -LEGEND_LEFT_PAD = 7 -LEGEND_NUM_BLOCKS = 256 -LEGEND_NUM_LABELS = 9 -LEGEND_WIDTH = 8 -LEGEND_FONT_SIZE = 8 -LEGEND_BORDER_COLOR_SPEC = (0, 0, 0) #black -MIN_PADDING = 0, 60, 0, 0 #top, right, bottom, left - -ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2))) - -pack_color = lambda x: struct.unpack('I', struct.pack('BBBB', *x))[0] -unpack_color = lambda x: struct.unpack('BBBB', struct.pack('I', int(x))) - -def _get_rbga(red_pts, green_pts, blue_pts, alpha_pts=[(0, 0), (1, 0)]): - """ - Get an array of 256 rgba values where each index maps to a color. - The scaling for red, green, blue, alpha are specified in piece-wise functions. - The piece-wise functions consist of a set of x, y coordinates. - The x and y values of the coordinates range from 0 to 1. - The coordinates must be specified so that x increases with the index value. - Resulting values are calculated along the line formed between 2 coordinates. - - Args: - red_pts, green_pts, blue_pts, alpha_pts: an array of x,y coordinates for each color element - - Returns: - array of rbga values (4 bytes) each - """ - def _fcn(x, pw): - for (x1, y1), (x2, y2) in zip(pw, pw[1:]): - #linear interpolation - if x <= x2: return float(y1 - y2)/(x1 - x2)*(x - x1) + y1 - raise Exception - return numpy.array([pack_color(map( - lambda pw: int(255*_fcn(i/255.0, pw)), - (red_pts, green_pts, blue_pts, alpha_pts), - )) for i in range(0, 256)], numpy.uint32) - -COLORS = { - 'rgb1': _get_rbga( #http://www.ks.uiuc.edu/Research/vmd/vmd-1.7.1/ug/img47.gif - red_pts = [(0, 0), (.5, 0), (1, 1)], - green_pts = [(0, 0), (.5, 1), (1, 0)], - blue_pts = [(0, 1), (.5, 0), (1, 0)], - ), - 'rgb2': _get_rbga( #http://xtide.ldeo.columbia.edu/~krahmann/coledit/screen.jpg - red_pts = [(0, 0), (3.0/8, 0), (5.0/8, 1), (7.0/8, 1), (1, .5)], - green_pts = [(0, 0), (1.0/8, 0), (3.0/8, 1), (5.0/8, 1), (7.0/8, 0), (1, 0)], - blue_pts = [(0, .5), (1.0/8, 1), (3.0/8, 1), (5.0/8, 0), (1, 0)], - ), - 'rgb3': _get_rbga( - red_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 0), (1, 1)], - green_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 1), (1, 0)], - blue_pts = [(0, 0), (1.0/3.0, 1), (2.0/3.0, 0), (1, 0)], - ), - 'gray': _get_rbga( - red_pts = [(0, 0), (1, 1)], - green_pts = [(0, 0), (1, 1)], - blue_pts = [(0, 0), (1, 1)], - ), -} - -################################################## -# Waterfall Plotter -################################################## -class waterfall_plotter(grid_plotter_base): - def __init__(self, parent): - """ - Create a new channel plotter. - """ - #init - grid_plotter_base.__init__(self, parent, MIN_PADDING) - #setup legend cache - self._legend_cache = self.new_gl_cache(self._draw_legend) - #setup waterfall cache - self._waterfall_cache = self.new_gl_cache(self._draw_waterfall, 50) - #setup waterfall plotter - self.register_init(self._init_waterfall) - self._resize_texture(False) - self._minimum = 0 - self._maximum = 0 - self._fft_size = 1 - self._buffer = list() - self._pointer = 0 - self._counter = 0 - self.set_num_lines(0) - self.set_color_mode(COLORS.keys()[0]) - self.callback = None - - def _init_waterfall(self): - """ - Run gl initialization tasks. - """ - self._waterfall_texture = GL.glGenTextures(1) - - def _draw_waterfall(self): - """ - Draw the waterfall from the texture. - The texture is circularly filled and will wrap around. - Use matrix modeling to shift and scale the texture onto the coordinate plane. - """ - #resize texture - self._resize_texture() - #setup texture - GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture) - GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR) - GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) - GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT) - GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE) - #write the buffer to the texture - while self._buffer: - GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, self._pointer, self._fft_size, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, self._buffer.pop(0)) - self._pointer = (self._pointer + 1)%self._num_lines - #begin drawing - GL.glEnable(GL.GL_TEXTURE_2D) - GL.glPushMatrix() - #matrix scaling - GL.glTranslatef(self.padding_left, self.padding_top, 0) - GL.glScalef( - float(self.width-self.padding_left-self.padding_right), - float(self.height-self.padding_top-self.padding_bottom), - 1.0, - ) - #draw texture with wrapping - GL.glBegin(GL.GL_QUADS) - prop_y = float(self._pointer)/(self._num_lines-1) - prop_x = float(self._fft_size)/ceil_log2(self._fft_size) - off = 1.0/(self._num_lines-1) - GL.glTexCoord2f(0, prop_y+1-off) - GL.glVertex2f(0, 1) - GL.glTexCoord2f(prop_x, prop_y+1-off) - GL.glVertex2f(1, 1) - GL.glTexCoord2f(prop_x, prop_y) - GL.glVertex2f(1, 0) - GL.glTexCoord2f(0, prop_y) - GL.glVertex2f(0, 0) - GL.glEnd() - GL.glPopMatrix() - GL.glDisable(GL.GL_TEXTURE_2D) - - def _populate_point_label(self, x_val, y_val): - """ - Get the text the will populate the point label. - Give the X value for the current point. - - Args: - x_val: the current x value - y_val: the current y value - - Returns: - a value string with units - """ - return '%s: %s'%(self.x_label, common.eng_format(x_val, self.x_units)) - - def _call_callback(self, x_val, y_val): - if self.callback != None: - self.callback(x_val,y_val) - - def set_callback(self,callback): - self.callback = callback - - def _draw_legend(self): - """ - Draw the color scale legend. - """ - if not self._color_mode: return - legend_height = self.height-self.padding_top-self.padding_bottom - #draw each legend block - block_height = float(legend_height)/LEGEND_NUM_BLOCKS - x = self.width - self.padding_right + LEGEND_LEFT_PAD - for i in range(LEGEND_NUM_BLOCKS): - color = unpack_color(COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))]) - GL.glColor4f(*numpy.array(color)/255.0) - y = self.height - (i+1)*block_height - self.padding_bottom - self._draw_rect(x, y, LEGEND_WIDTH, block_height) - #draw rectangle around color scale border - GL.glColor3f(*LEGEND_BORDER_COLOR_SPEC) - self._draw_rect(x, self.padding_top, LEGEND_WIDTH, legend_height, fill=False) - #draw each legend label - label_spacing = float(legend_height)/(LEGEND_NUM_LABELS-1) - x = self.width - (self.padding_right - LEGEND_LEFT_PAD - LEGEND_WIDTH)/2 - for i in range(LEGEND_NUM_LABELS): - proportion = i/float(LEGEND_NUM_LABELS-1) - dB = proportion*(self._maximum - self._minimum) + self._minimum - y = self.height - i*label_spacing - self.padding_bottom - txt = gltext.Text('%ddB'%int(dB), font_size=LEGEND_FONT_SIZE, centered=True) - txt.draw_text(wx.Point(x, y)) - - def _resize_texture(self, flag=None): - """ - Create the texture to fit the fft_size X num_lines. - - Args: - flag: the set/unset or update flag - """ - if flag is not None: - self._resize_texture_flag = flag - return - if not self._resize_texture_flag: return - self._buffer = list() - self._pointer = 0 - if self._num_lines and self._fft_size: - GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture) - data = numpy.zeros(self._num_lines*ceil_log2(self._fft_size)*4, numpy.uint8).tostring() - GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, ceil_log2(self._fft_size), self._num_lines, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, data) - self._resize_texture_flag = False - - def set_color_mode(self, color_mode): - """ - Set the color mode. - New samples will be converted to the new color mode. - Old samples will not be recolorized. - - Args: - color_mode: the new color mode string - """ - self.lock() - if color_mode in COLORS.keys(): - self._color_mode = color_mode - self._legend_cache.changed(True) - self.update() - self.unlock() - - def set_num_lines(self, num_lines): - """ - Set number of lines. - Powers of two only. - - Args: - num_lines: the new number of lines - """ - self.lock() - self._num_lines = num_lines - self._resize_texture(True) - self.update() - self.unlock() - - def set_samples(self, samples, minimum, maximum): - """ - Set the samples to the waterfall. - Convert the samples to color data. - - Args: - samples: the array of floats - minimum: the minimum value to scale - maximum: the maximum value to scale - """ - self.lock() - #set the min, max values - if self._minimum != minimum or self._maximum != maximum: - self._minimum = minimum - self._maximum = maximum - self._legend_cache.changed(True) - if self._fft_size != len(samples): - self._fft_size = len(samples) - self._resize_texture(True) - #normalize the samples to min/max - samples = (samples - minimum)*float(255/(maximum-minimum)) - samples = numpy.clip(samples, 0, 255) #clip - samples = numpy.array(samples, numpy.uint8) - #convert the samples to RGBA data - data = COLORS[self._color_mode][samples].tostring() - self._buffer.append(data) - self._waterfall_cache.changed(True) - self.unlock() diff --git a/gr-wxgui/python/wxgui/powermate.py b/gr-wxgui/python/wxgui/powermate.py deleted file mode 100644 index 7c324c5d95..0000000000 --- a/gr-wxgui/python/wxgui/powermate.py +++ /dev/null @@ -1,448 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2005 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -""" -Handler for Griffin PowerMate, Contour ShuttlePro & ShuttleXpress USB knobs - -This is Linux and wxPython specific. -""" - -import os -import sys -import struct -import exceptions -import threading -import wx -from gnuradio import gru - -imported_ok = True - -try: - import select - import fcntl -except ImportError: - imported_ok = False - - -# First a little bit of background: -# -# The Griffin PowerMate has -# * a single knob which rotates -# * a single button (pressing the knob) -# -# The Contour ShuttleXpress (aka SpaceShuttle) has -# * "Jog Wheel" -- the knob (rotary encoder) on the inside -# * "Shuttle Ring" -- the spring loaded rubber covered ring -# * 5 buttons -# -# The Contour ShuttlePro has -# * "Jog Wheel" -- the knob (rotary encoder) on the inside -# * "Shuttle Ring" -- the spring loaded rubber covered ring -# * 13 buttons -# -# The Contour ShuttlePro V2 has -# *"Jog Wheel" -- the knob (rotary encoder) on the inside -# * "Shuttle Ring" -- the spring loaded rubber covered ring -# * 15 buttons - -# We remap all the buttons on the devices so that they start at zero. - -# For the ShuttleXpress the buttons are 0 to 4 (left to right) - -# For the ShuttlePro, we number the buttons immediately above -# the ring 0 to 4 (left to right) so that they match our numbering -# on the ShuttleXpress. The top row is 5, 6, 7, 8. The first row below -# the ring is 9, 10, and the bottom row is 11, 12. - -# For the ShuttlePro V2, buttons 13 & 14 are to the -# left and right of the wheel respectively. - -# We generate 3 kinds of events: -# -# button press/release (button_number, press/release) -# knob rotation (relative_clicks) # typically -1, +1 -# shuttle position (absolute_position) # -7,-6,...,0,...,6,7 - -# ---------------------------------------------------------------- -# Our ID's for the devices: -# Not to be confused with anything related to magic hardware numbers. - -ID_POWERMATE = 'powermate' -ID_SHUTTLE_XPRESS = 'shuttle xpress' -ID_SHUTTLE_PRO = 'shuttle pro' -ID_SHUTTLE_PRO_V2 = 'shuttle pro v2' - -# ------------------------------------------------------------------------ -# format of messages that we read from /dev/input/event* -# See /usr/include/linux/input.h for more info -# -#struct input_event { -# struct timeval time; = {long seconds, long microseconds} -# unsigned short type; -# unsigned short code; -# unsigned int value; -#}; - -input_event_struct = "@llHHi" -input_event_size = struct.calcsize(input_event_struct) - -# ------------------------------------------------------------------------ -# input_event types -# ------------------------------------------------------------------------ - -IET_SYN = 0x00 # aka RESET -IET_KEY = 0x01 # key or button press/release -IET_REL = 0x02 # relative movement (knob rotation) -IET_ABS = 0x03 # absolute position (graphics pad, etc) -IET_MSC = 0x04 -IET_LED = 0x11 -IET_SND = 0x12 -IET_REP = 0x14 -IET_FF = 0x15 -IET_PWR = 0x16 -IET_FF_STATUS = 0x17 -IET_MAX = 0x1f - -# ------------------------------------------------------------------------ -# input_event codes (there are a zillion of them, we only define a few) -# ------------------------------------------------------------------------ - -# these are valid for IET_KEY - -IEC_BTN_0 = 0x100 -IEC_BTN_1 = 0x101 -IEC_BTN_2 = 0x102 -IEC_BTN_3 = 0x103 -IEC_BTN_4 = 0x104 -IEC_BTN_5 = 0x105 -IEC_BTN_6 = 0x106 -IEC_BTN_7 = 0x107 -IEC_BTN_8 = 0x108 -IEC_BTN_9 = 0x109 -IEC_BTN_10 = 0x10a -IEC_BTN_11 = 0x10b -IEC_BTN_12 = 0x10c -IEC_BTN_13 = 0x10d -IEC_BTN_14 = 0x10e -IEC_BTN_15 = 0x10f - -# these are valid for IET_REL (Relative axes) - -IEC_REL_X = 0x00 -IEC_REL_Y = 0x01 -IEC_REL_Z = 0x02 -IEC_REL_HWHEEL = 0x06 -IEC_REL_DIAL = 0x07 # rotating the knob -IEC_REL_WHEEL = 0x08 # moving the shuttle ring -IEC_REL_MISC = 0x09 -IEC_REL_MAX = 0x0f - -# ------------------------------------------------------------------------ - -class powermate(threading.Thread): - """ - Interface to Griffin PowerMate and Contour Shuttles - """ - def __init__(self, event_receiver=None, filename=None, **kwargs): - self.event_receiver = event_receiver - self.handle = -1 - if not imported_ok: - raise exceptions.RuntimeError, 'powermate not supported on this platform' - - if filename: - if not self._open_device(filename): - raise exceptions.RuntimeError, 'Unable to find powermate' - else: - ok = False - for d in range(0, 16): - if self._open_device("/dev/input/event%d" % d): - ok = True - break - if not ok: - raise exceptions.RuntimeError, 'Unable to find powermate' - - threading.Thread.__init__(self, **kwargs) - self.setDaemon (1) - self.keep_running = True - self.start () - - def __del__(self): - self.keep_running = False - if self.handle >= 0: - os.close(self.handle) - self.handle = -1 - - def _open_device(self, filename): - try: - self.handle = os.open(filename, os.O_RDWR) - if self.handle < 0: - return False - - # read event device name - name = fcntl.ioctl(self.handle, gru.hexint(0x80ff4506), chr(0) * 256) - name = name.replace(chr(0), '') - - # do we see anything we recognize? - if name == 'Griffin PowerMate' or name == 'Griffin SoundKnob': - self.id = ID_POWERMATE - self.mapper = _powermate_remapper() - elif name == 'CAVS SpaceShuttle A/V' or name == 'Contour Design ShuttleXpress': - self.id = ID_SHUTTLE_XPRESS - self.mapper = _contour_remapper() - elif name == 'Contour Design ShuttlePRO': - self.id = ID_SHUTTLE_PRO - self.mapper = _contour_remapper() - elif name == 'Contour Design ShuttlePRO v2': - self.id = ID_SHUTTLE_PRO_V2 - self.mapper = _contour_remapper() - else: - os.close(self.handle) - self.handle = -1 - return False - - # get exclusive control of the device, using ioctl EVIOCGRAB - # there may be an issue with this on non x86 platforms and if - # the _IOW,_IOC,... macros in <asm/ioctl.h> are changed - fcntl.ioctl(self.handle,gru.hexint(0x40044590), 1) - return True - except exceptions.OSError: - return False - - - def set_event_receiver(self, obj): - self.event_receiver = obj - - - def set_led_state(self, static_brightness, pulse_speed=0, - pulse_table=0, pulse_on_sleep=0, pulse_on_wake=0): - """ - What do these magic values mean... - """ - if self.id != ID_POWERMATE: - return False - - static_brightness &= 0xff; - if pulse_speed < 0: - pulse_speed = 0 - if pulse_speed > 510: - pulse_speed = 510 - if pulse_table < 0: - pulse_table = 0 - if pulse_table > 2: - pulse_table = 2 - pulse_on_sleep = not not pulse_on_sleep # not not = convert to 0/1 - pulse_on_wake = not not pulse_on_wake - magic = (static_brightness - | (pulse_speed << 8) - | (pulse_table << 17) - | (pulse_on_sleep << 19) - | (pulse_on_wake << 20)) - data = struct.pack(input_event_struct, 0, 0, 0x04, 0x01, magic) - os.write(self.handle, data) - return True - - def run (self): - while (self.keep_running): - s = os.read (self.handle, input_event_size) - if not s: - self.keep_running = False - break - - raw_input_event = struct.unpack(input_event_struct,s) - sec, usec, type, code, val = self.mapper(raw_input_event) - - if self.event_receiver is None: - continue - - if type == IET_SYN: # ignore - pass - elif type == IET_MSC: # ignore (seems to be PowerMate reporting led brightness) - pass - elif type == IET_REL and code == IEC_REL_DIAL: - #print "Dial: %d" % (val,) - wx.PostEvent(self.event_receiver, PMRotateEvent(val)) - elif type == IET_REL and code == IEC_REL_WHEEL: - #print "Shuttle: %d" % (val,) - wx.PostEvent(self.event_receiver, PMShuttleEvent(val)) - elif type == IET_KEY: - #print "Key: Btn%d %d" % (code - IEC_BTN_0, val) - wx.PostEvent(self.event_receiver, - PMButtonEvent(code - IEC_BTN_0, val)) - else: - print "powermate: unrecognized event: type = 0x%x code = 0x%x val = %d" % (type, code, val) - - -class _powermate_remapper(object): - def __init__(self): - pass - def __call__(self, event): - """ - Notice how nice and simple this is... - """ - return event - -class _contour_remapper(object): - def __init__(self): - self.prev = None - def __call__(self, event): - """ - ...and how screwed up this is - """ - sec, usec, type, code, val = event - if type == IET_REL and code == IEC_REL_WHEEL: - # === Shuttle ring === - # First off, this really ought to be IET_ABS, not IET_REL! - # They never generate a zero value so you can't - # tell when the shuttle ring is back in the center. - # We kludge around this by calling both -1 and 1 zero. - if val == -1 or val == 1: - return (sec, usec, type, code, 0) - return event - - if type == IET_REL and code == IEC_REL_DIAL: - # === Jog knob (rotary encoder) === - # Dim wits got it wrong again! This one should return a - # a relative value, e.g., -1, +1. Instead they return - # a total that runs modulo 256 (almost!). For some - # reason they count like this 253, 254, 255, 1, 2, 3 - - if self.prev is None: # first time call - self.prev = val - return (sec, usec, IET_SYN, 0, 0) # will be ignored above - - diff = val - self.prev - if diff == 0: # sometimes it just sends stuff... - return (sec, usec, IET_SYN, 0, 0) # will be ignored above - - if abs(diff) > 100: # crossed into the twilight zone - if self.prev > val: # we've wrapped going forward - self.prev = val - return (sec, usec, type, code, +1) - else: # we've wrapped going backward - self.prev = val - return (sec, usec, type, code, -1) - - self.prev = val - return (sec, usec, type, code, diff) - - if type == IET_KEY: - # remap keys so that all 3 gadgets have buttons 0 to 4 in common - return (sec, usec, type, - (IEC_BTN_5, IEC_BTN_6, IEC_BTN_7, IEC_BTN_8, - IEC_BTN_0, IEC_BTN_1, IEC_BTN_2, IEC_BTN_3, IEC_BTN_4, - IEC_BTN_9, IEC_BTN_10, - IEC_BTN_11, IEC_BTN_12, - IEC_BTN_13, IEC_BTN_14)[code - IEC_BTN_0], val) - - return event - -# ------------------------------------------------------------------------ -# new wxPython event classes -# ------------------------------------------------------------------------ - -grEVT_POWERMATE_BUTTON = wx.NewEventType() -grEVT_POWERMATE_ROTATE = wx.NewEventType() -grEVT_POWERMATE_SHUTTLE = wx.NewEventType() - -EVT_POWERMATE_BUTTON = wx.PyEventBinder(grEVT_POWERMATE_BUTTON, 0) -EVT_POWERMATE_ROTATE = wx.PyEventBinder(grEVT_POWERMATE_ROTATE, 0) -EVT_POWERMATE_SHUTTLE = wx.PyEventBinder(grEVT_POWERMATE_SHUTTLE, 0) - -class PMButtonEvent(wx.PyEvent): - def __init__(self, button, value): - wx.PyEvent.__init__(self) - self.SetEventType(grEVT_POWERMATE_BUTTON) - self.button = button - self.value = value - - def Clone (self): - self.__class__(self.GetId()) - - -class PMRotateEvent(wx.PyEvent): - def __init__(self, delta): - wx.PyEvent.__init__(self) - self.SetEventType (grEVT_POWERMATE_ROTATE) - self.delta = delta - - def Clone (self): - self.__class__(self.GetId()) - - -class PMShuttleEvent(wx.PyEvent): - def __init__(self, position): - wx.PyEvent.__init__(self) - self.SetEventType (grEVT_POWERMATE_SHUTTLE) - self.position = position - - def Clone (self): - self.__class__(self.GetId()) - -# ------------------------------------------------------------------------ -# Example usage -# ------------------------------------------------------------------------ - -if __name__ == '__main__': - class Frame(wx.Frame): - def __init__(self,parent=None,id=-1,title='Title', - pos=wx.DefaultPosition, size=(400,200)): - wx.Frame.__init__(self,parent,id,title,pos,size) - EVT_POWERMATE_BUTTON(self, self.on_button) - EVT_POWERMATE_ROTATE(self, self.on_rotate) - EVT_POWERMATE_SHUTTLE(self, self.on_shuttle) - self.brightness = 128 - self.pulse_speed = 0 - - try: - self.pm = powermate(self) - except: - sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n") - sys.exit(1) - - self.pm.set_led_state(self.brightness, self.pulse_speed) - - - def on_button(self, evt): - print "Button %d %s" % (evt.button, - ("Released", "Pressed")[evt.value]) - - def on_rotate(self, evt): - print "Rotated %d" % (evt.delta,) - if 0: - new = max(0, min(255, self.brightness + evt.delta)) - if new != self.brightness: - self.brightness = new - self.pm.set_led_state(self.brightness, self.pulse_speed) - - def on_shuttle(self, evt): - print "Shuttle %d" % (evt.position,) - - class App(wx.App): - def OnInit(self): - title='PowerMate Demo' - self.frame = Frame(parent=None,id=-1,title=title) - self.frame.Show() - self.SetTopWindow(self.frame) - return True - - app = App() - app.MainLoop () diff --git a/gr-wxgui/python/wxgui/pubsub.py b/gr-wxgui/python/wxgui/pubsub.py deleted file mode 100644 index e55d691978..0000000000 --- a/gr-wxgui/python/wxgui/pubsub.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python -# -# 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. -# - -""" -Abstract GNU Radio publisher/subscriber interface - -This is a proof of concept implementation, will likely change significantly. -""" - -class pubsub(dict): - def __init__(self): - self._publishers = { } - self._subscribers = { } - self._proxies = { } - - def __missing__(self, key, value=None): - dict.__setitem__(self, key, value) - self._publishers[key] = None - self._subscribers[key] = [] - self._proxies[key] = None - - def __setitem__(self, key, val): - if not self.has_key(key): - self.__missing__(key, val) - elif self._proxies[key] is not None: - (p, pkey) = self._proxies[key] - p[pkey] = val - else: - dict.__setitem__(self, key, val) - for sub in self._subscribers[key]: - # Note this means subscribers will get called in the thread - # context of the 'set' caller. - sub(val) - - def __getitem__(self, key): - if not self.has_key(key): self.__missing__(key) - if self._proxies[key] is not None: - (p, pkey) = self._proxies[key] - return p[pkey] - elif self._publishers[key] is not None: - return self._publishers[key]() - else: - return dict.__getitem__(self, key) - - def publish(self, key, publisher): - if not self.has_key(key): self.__missing__(key) - if self._proxies[key] is not None: - (p, pkey) = self._proxies[key] - p.publish(pkey, publisher) - else: - self._publishers[key] = publisher - - def subscribe(self, key, subscriber): - if not self.has_key(key): self.__missing__(key) - if self._proxies[key] is not None: - (p, pkey) = self._proxies[key] - p.subscribe(pkey, subscriber) - else: - self._subscribers[key].append(subscriber) - - def unpublish(self, key): - if self._proxies[key] is not None: - (p, pkey) = self._proxies[key] - p.unpublish(pkey) - else: - self._publishers[key] = None - - def unsubscribe(self, key, subscriber): - if self._proxies[key] is not None: - (p, pkey) = self._proxies[key] - p.unsubscribe(pkey, subscriber) - else: - self._subscribers[key].remove(subscriber) - - def proxy(self, key, p, pkey=None): - if not self.has_key(key): self.__missing__(key) - if pkey is None: pkey = key - self._proxies[key] = (p, pkey) - - def unproxy(self, key): - self._proxies[key] = None - -# Test code -if __name__ == "__main__": - import sys - o = pubsub() - - # Non-existent key gets auto-created with None value - print "Auto-created key 'foo' value:", o['foo'] - - # Add some subscribers - # First is a bare function - def print_len(x): - print "len=%i" % (len(x), ) - o.subscribe('foo', print_len) - - # The second is a class member function - class subber(object): - def __init__(self, param): - self._param = param - def printer(self, x): - print self._param, `x` - s = subber('param') - o.subscribe('foo', s.printer) - - # The third is a lambda function - o.subscribe('foo', lambda x: sys.stdout.write('val='+`x`+'\n')) - - # Update key 'foo', will notify subscribers - print "Updating 'foo' with three subscribers:" - o['foo'] = 'bar'; - - # Remove first subscriber - o.unsubscribe('foo', print_len) - - # Update now will only trigger second and third subscriber - print "Updating 'foo' after removing a subscriber:" - o['foo'] = 'bar2'; - - # Publish a key as a function, in this case, a lambda function - o.publish('baz', lambda : 42) - print "Published value of 'baz':", o['baz'] - - # Unpublish the key - o.unpublish('baz') - - # This will return None, as there is no publisher - print "Value of 'baz' with no publisher:", o['baz'] - - # Set 'baz' key, it gets cached - o['baz'] = 'bazzz' - - # Now will return cached value, since no provider - print "Cached value of 'baz' after being set:", o['baz'] diff --git a/gr-wxgui/python/wxgui/scope_window.py b/gr-wxgui/python/wxgui/scope_window.py deleted file mode 100644 index 71e2b8e285..0000000000 --- a/gr-wxgui/python/wxgui/scope_window.py +++ /dev/null @@ -1,691 +0,0 @@ -# -# Copyright 2008,2010 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. -# - -################################################## -# Imports -################################################## -import plotter -import common -import wx -import numpy -import time -import pubsub -from constants import * -from gnuradio import gr #for gr.prefs, trigger modes -from gnuradio import wxgui -import forms - -################################################## -# Constants -################################################## -DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) -PERSIST_ALPHA_MIN_EXP, PERSIST_ALPHA_MAX_EXP = -2, 0 -SLIDER_STEPS = 100 -DEFAULT_TRIG_MODE = gr.prefs().get_long('wxgui', 'trig_mode', wxgui.TRIG_MODE_AUTO) -DEFAULT_WIN_SIZE = (600, 300) -COUPLING_MODES = ( - ('DC', False), - ('AC', True), -) -TRIGGER_MODES = ( - ('Freerun', wxgui.TRIG_MODE_FREE), - ('Auto', wxgui.TRIG_MODE_AUTO), - ('Normal', wxgui.TRIG_MODE_NORM), - ('Stripchart', wxgui.TRIG_MODE_STRIPCHART), -) -TRIGGER_SLOPES = ( - ('Pos +', wxgui.TRIG_SLOPE_POS), - ('Neg -', wxgui.TRIG_SLOPE_NEG), -) -CHANNEL_COLOR_SPECS = ( - (0.3, 0.3, 1.0), - (0.0, 0.8, 0.0), - (1.0, 0.0, 0.0), - (0.8, 0.0, 0.8), - (0.7, 0.7, 0.0), - (0.15, 0.90, 0.98), - -) -TRIGGER_COLOR_SPEC = (1.0, 0.4, 0.0) -AUTORANGE_UPDATE_RATE = 0.5 #sec -MARKER_TYPES = ( - ('Line Link', None), - ('Dot Large', 3.0), - ('Dot Med', 2.0), - ('Dot Small', 1.0), - ('None', 0.0), -) -DEFAULT_MARKER_TYPE = None - -################################################## -# Scope window control panel -################################################## -class control_panel(wx.Panel): - """ - A control panel with wx widgits to control the plotter and scope block. - """ - def __init__(self, parent): - """ - Create a new control panel. - - Args: - parent: the wx parent window - """ - WIDTH = 90 - self.parent = parent - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - parent[SHOW_CONTROL_PANEL_KEY] = True - parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) - control_box = wx.BoxSizer(wx.VERTICAL) - - ################################################## - # Persistence - ################################################## - - forms.check_box( - sizer=control_box, parent=self, label='Persistence', - ps=parent, key=USE_PERSISTENCE_KEY, - ) - #static text and slider for analog alpha - persist_alpha_text = forms.static_text( - sizer=control_box, parent=self, label='Analog Alpha', - converter=forms.float_converter(lambda x: '%.4f'%x), - ps=parent, key=PERSIST_ALPHA_KEY, width=50, - ) - persist_alpha_slider = forms.log_slider( - sizer=control_box, parent=self, - min_exp=PERSIST_ALPHA_MIN_EXP, - max_exp=PERSIST_ALPHA_MAX_EXP, - num_steps=SLIDER_STEPS, - ps=parent, key=PERSIST_ALPHA_KEY, - ) - for widget in (persist_alpha_text, persist_alpha_slider): - parent.subscribe(USE_PERSISTENCE_KEY, widget.Enable) - widget.Enable(parent[USE_PERSISTENCE_KEY]) - parent.subscribe(USE_PERSISTENCE_KEY, widget.ShowItems) - #allways show initially, so room is reserved for them - widget.ShowItems(True) # (parent[USE_PERSISTENCE_KEY]) - - parent.subscribe(USE_PERSISTENCE_KEY, self._update_layout) - - ################################################## - # Axes Options - ################################################## - control_box.AddStretchSpacer() - 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) - axes_options_box.Add(scope_mode_box, 0, wx.EXPAND) - #x axis divs - 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 = 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 = 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) - 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) - axes_options_box.Add(xy_mode_box, 0, wx.EXPAND) - #x div controls - 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 = 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 = 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 = 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 - 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.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) - 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_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() - 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() - 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_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 - 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 = 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 - 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 = 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 == wxgui.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_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() - 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() - 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() - 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 - 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 - def on_mouse_wheel(event): - if not parent[XY_MODE_KEY]: - if event.GetWheelRotation() < 0: self._on_incr_t_divs(event) - else: self._on_decr_t_divs(event) - parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel) - - ################################################## - # Event handlers - ################################################## - #trigger level - def _on_incr_trigger_level(self, event): - self.parent[TRIGGER_LEVEL_KEY] += self.parent[Y_PER_DIV_KEY]/3. - def _on_decr_trigger_level(self, event): - self.parent[TRIGGER_LEVEL_KEY] -= self.parent[Y_PER_DIV_KEY]/3. - #incr/decr divs - def _on_incr_t_divs(self, event): - self.parent[T_PER_DIV_KEY] = common.get_clean_incr(self.parent[T_PER_DIV_KEY]) - def _on_decr_t_divs(self, event): - self.parent[T_PER_DIV_KEY] = common.get_clean_decr(self.parent[T_PER_DIV_KEY]) - def _on_incr_x_divs(self, event): - self.parent[X_PER_DIV_KEY] = common.get_clean_incr(self.parent[X_PER_DIV_KEY]) - def _on_decr_x_divs(self, event): - self.parent[X_PER_DIV_KEY] = common.get_clean_decr(self.parent[X_PER_DIV_KEY]) - def _on_incr_y_divs(self, event): - self.parent[Y_PER_DIV_KEY] = common.get_clean_incr(self.parent[Y_PER_DIV_KEY]) - def _on_decr_y_divs(self, event): - self.parent[Y_PER_DIV_KEY] = common.get_clean_decr(self.parent[Y_PER_DIV_KEY]) - #incr/decr offset - def _on_incr_x_off(self, event): - self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY] - def _on_decr_x_off(self, event): - self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY] - def _on_incr_y_off(self, event): - self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY] - def _on_decr_y_off(self, event): - self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY] - - ################################################## - # subscriber handlers - ################################################## - def _update_layout(self,key): - # Just ignore the key value we get - # we only need to now that the visability or size of something has changed - self.parent.Layout() - #self.parent.Fit() - -################################################## -# Scope window with plotter and control panel -################################################## -class scope_window(wx.Panel, pubsub.pubsub): - def __init__( - self, - parent, - controller, - size, - title, - frame_rate, - num_inputs, - sample_rate_key, - t_scale, - v_scale, - v_offset, - xy_mode, - ac_couple_key, - trigger_level_key, - trigger_mode_key, - trigger_slope_key, - trigger_channel_key, - decimation_key, - msg_key, - use_persistence, - persist_alpha, - trig_mode, - y_axis_label, - ): - pubsub.pubsub.__init__(self) - #check num inputs - assert num_inputs <= len(CHANNEL_COLOR_SPECS) - #setup - self.sampleses = None - self.num_inputs = num_inputs - autorange = not v_scale - self.autorange_ts = 0 - v_scale = v_scale or 1 - self.frame_rate_ts = 0 - #proxy the keys - self.proxy(MSG_KEY, controller, msg_key) - self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) - self.proxy(TRIGGER_LEVEL_KEY, controller, trigger_level_key) - self.proxy(TRIGGER_MODE_KEY, controller, trigger_mode_key) - 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) - #initialize values - self[RUNNING_KEY] = True - 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 - self[AUTORANGE_KEY] = autorange - self[T_PER_DIV_KEY] = t_scale - self[X_PER_DIV_KEY] = v_scale - self[Y_PER_DIV_KEY] = v_scale - self[T_OFF_KEY] = 0 - self[X_OFF_KEY] = v_offset - self[Y_OFF_KEY] = v_offset - self[T_DIVS_KEY] = 8 - self[X_DIVS_KEY] = 8 - self[Y_DIVS_KEY] = 8 - self[Y_AXIS_LABEL] = y_axis_label - self[FRAME_RATE_KEY] = frame_rate - self[TRIGGER_LEVEL_KEY] = 0 - self[TRIGGER_CHANNEL_KEY] = 0 - self[TRIGGER_MODE_KEY] = trig_mode - - self[TRIGGER_SLOPE_KEY] = wxgui.TRIG_SLOPE_POS - self[T_FRAC_OFF_KEY] = 0.5 - self[USE_PERSISTENCE_KEY] = use_persistence - self[PERSIST_ALPHA_KEY] = persist_alpha - - if self[TRIGGER_MODE_KEY] == wxgui.TRIG_MODE_STRIPCHART: - self[T_FRAC_OFF_KEY] = 0.0 - - 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.SetSizeHints(*size) - self.plotter.set_title(title) - self.plotter.enable_legend(True) - self.plotter.enable_point_label(True) - self.plotter.enable_grid_lines(True) - self.plotter.set_use_persistence(use_persistence) - self.plotter.set_persist_alpha(persist_alpha) - #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 - for key in [common.index_key(MARKER_KEY, i) for i in range(self.num_inputs)] + [ - TRIGGER_LEVEL_KEY, TRIGGER_MODE_KEY, - T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY, - T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY, - T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY, - XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY, - TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY, - ]: self.subscribe(key, self.update_grid) - #register events for plotter settings - self.subscribe(USE_PERSISTENCE_KEY, self.plotter.set_use_persistence) - self.subscribe(PERSIST_ALPHA_KEY, self.plotter.set_persist_alpha) - #initial update - self.update_grid() - - def handle_msg(self, msg): - """ - Handle the message from the scope sink message queue. - Plot the list of arrays of samples onto the grid. - Each samples array gets its own channel. - - Args: - msg: the time domain data as a character array - """ - if not self[RUNNING_KEY]: return - #check time elapsed - if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return - #convert to floating point numbers - samples = numpy.fromstring(msg, numpy.float32) - #extract the trigger offset - self.trigger_offset = samples[-1] - samples = samples[:-1] - samps_per_ch = len(samples)/self.num_inputs - self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)] - #handle samples - self.handle_samples() - self.frame_rate_ts = time.time() - - def set_auto_trigger_level(self, *args): - """ - Use the current trigger channel and samples to calculate the 50% level. - """ - if not self.sampleses: return - samples = self.sampleses[self[TRIGGER_CHANNEL_KEY]] - self[TRIGGER_LEVEL_KEY] = (numpy.max(samples)+numpy.min(samples))/2 - - def handle_samples(self): - """ - Handle the cached samples from the scope input. - Perform ac coupling, triggering, and auto ranging. - """ - if not self.sampleses: return - sampleses = self.sampleses - if self[XY_MODE_KEY]: - self[DECIMATION_KEY] = 1 - x_samples = sampleses[self[X_CHANNEL_KEY]] - y_samples = sampleses[self[Y_CHANNEL_KEY]] - #autorange - if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: - x_min, x_max = common.get_min_max(x_samples) - y_min, y_max = common.get_min_max(y_samples) - #adjust the x per div - x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY]) - if x_per_div != self[X_PER_DIV_KEY]: self[X_PER_DIV_KEY] = x_per_div; return - #adjust the x offset - x_off = x_per_div*round((x_max+x_min)/2/x_per_div) - if x_off != self[X_OFF_KEY]: self[X_OFF_KEY] = x_off; return - #adjust the y per div - y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY]) - if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return - #adjust the y offset - y_off = y_per_div*round((y_max+y_min)/2/y_per_div) - if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return - self.autorange_ts = time.time() - #plot xy channel - self.plotter.set_waveform( - channel='XY', - samples=(x_samples, y_samples), - color_spec=CHANNEL_COLOR_SPECS[0], - marker=self[XY_MARKER_KEY], - ) - #turn off each waveform - for i, samples in enumerate(sampleses): - self.plotter.clear_waveform(channel='Ch%d'%(i+1)) - else: - #autorange - if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: - bounds = [common.get_min_max(samples) for samples in sampleses] - y_min = numpy.min([bound[0] for bound in bounds]) - y_max = numpy.max([bound[1] for bound in bounds]) - #adjust the y per div - y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY]) - if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return - #adjust the y offset - y_off = y_per_div*round((y_max+y_min)/2/y_per_div) - if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return - self.autorange_ts = time.time() - #number of samples to scale to the screen - actual_rate = self.get_actual_rate() - time_span = self[T_PER_DIV_KEY]*self[T_DIVS_KEY] - num_samps = int(round(time_span*actual_rate)) - #handle the time offset - t_off = self[T_FRAC_OFF_KEY]*(len(sampleses[0])/actual_rate - time_span) - if t_off != self[T_OFF_KEY]: self[T_OFF_KEY] = t_off; return - samps_off = int(round(actual_rate*self[T_OFF_KEY])) - #adjust the decim so that we use about half the samps - self[DECIMATION_KEY] = int(round( - time_span*self[SAMPLE_RATE_KEY]/(0.5*len(sampleses[0])) - ) - ) - #num samps too small, auto increment the time - if num_samps < 2: self[T_PER_DIV_KEY] = common.get_clean_incr(self[T_PER_DIV_KEY]) - #num samps in bounds, plot each waveform - elif num_samps <= len(sampleses[0]): - for i, samples in enumerate(sampleses): - #plot samples - self.plotter.set_waveform( - channel='Ch%d'%(i+1), - samples=samples[samps_off:num_samps+samps_off], - color_spec=CHANNEL_COLOR_SPECS[i], - marker=self[common.index_key(MARKER_KEY, i)], - trig_off=self.trigger_offset, - ) - #turn XY channel off - self.plotter.clear_waveform(channel='XY') - #keep trigger level within range - if self[TRIGGER_LEVEL_KEY] > self.get_y_max(): - self[TRIGGER_LEVEL_KEY] = self.get_y_max(); return - if self[TRIGGER_LEVEL_KEY] < self.get_y_min(): - self[TRIGGER_LEVEL_KEY] = self.get_y_min(); return - #disable the trigger channel - if not self[TRIGGER_SHOW_KEY] or self[XY_MODE_KEY] or self[TRIGGER_MODE_KEY] == wxgui.TRIG_MODE_FREE: - self.plotter.clear_waveform(channel='Trig') - else: #show trigger channel - trigger_level = self[TRIGGER_LEVEL_KEY] - trigger_point = (len(self.sampleses[0])-1)/self.get_actual_rate()/2.0 - self.plotter.set_waveform( - channel='Trig', - samples=( - [self.get_t_min(), trigger_point, trigger_point, trigger_point, trigger_point, self.get_t_max()], - [trigger_level, trigger_level, self.get_y_max(), self.get_y_min(), trigger_level, trigger_level] - ), - color_spec=TRIGGER_COLOR_SPEC, - ) - #update the plotter - self.plotter.update() - - def get_actual_rate(self): return 1.0*self[SAMPLE_RATE_KEY]/self[DECIMATION_KEY] - def get_t_min(self): return self[T_OFF_KEY] - def get_t_max(self): return self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + self[T_OFF_KEY] - def get_x_min(self): return -1*self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY] - def get_x_max(self): return self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY] - def get_y_min(self): return -1*self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY] - def get_y_max(self): return self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY] - - def update_grid(self, *args): - """ - Update the grid to reflect the current settings: - xy divisions, xy offset, xy mode setting - """ - if self[T_FRAC_OFF_KEY] < 0: self[T_FRAC_OFF_KEY] = 0; return - if self[T_FRAC_OFF_KEY] > 1: self[T_FRAC_OFF_KEY] = 1; return - if self[XY_MODE_KEY]: - #update the x axis - self.plotter.set_x_label('Ch%d'%(self[X_CHANNEL_KEY]+1)) - self.plotter.set_x_grid(self.get_x_min(), self.get_x_max(), self[X_PER_DIV_KEY]) - #update the y axis - self.plotter.set_y_label('Ch%d'%(self[Y_CHANNEL_KEY]+1)) - self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) - else: - #update the t axis - self.plotter.set_x_label('Time', 's') - self.plotter.set_x_grid(self.get_t_min(), self.get_t_max(), self[T_PER_DIV_KEY], True) - #update the y axis - self.plotter.set_y_label(self[Y_AXIS_LABEL]) - self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) - #redraw current sample - self.handle_samples() - diff --git a/gr-wxgui/python/wxgui/scopesink2.py b/gr-wxgui/python/wxgui/scopesink2.py deleted file mode 100644 index 99e268895a..0000000000 --- a/gr-wxgui/python/wxgui/scopesink2.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 2008,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 gr - -p = gr.prefs() -style = p.get_string('wxgui', 'style', 'auto') - -if style == 'auto' or style == 'gl': - try: - import wx.glcanvas - from OpenGL.GL import * - from scopesink_gl import scope_sink_f, scope_sink_c - except ImportError: - if style == 'gl': - raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - else: - # Fall backto non-gl sinks - from scopesink_nongl import scope_sink_f, scope_sink_c -elif style == 'nongl': - from scopesink_nongl import scope_sink_f, scope_sink_c -else: - raise RuntimeError("Unknown wxgui style") diff --git a/gr-wxgui/python/wxgui/scopesink_gl.py b/gr-wxgui/python/wxgui/scopesink_gl.py deleted file mode 100644 index b2d5670c77..0000000000 --- a/gr-wxgui/python/wxgui/scopesink_gl.py +++ /dev/null @@ -1,239 +0,0 @@ -# -# Copyright 2008,2010,2012 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. -# - -################################################## -# Imports -################################################## -import scope_window -import common -from gnuradio import gr, filter -from gnuradio import blocks -from gnuradio import analog -from gnuradio import wxgui -from pubsub import pubsub -from constants import * -import math - -class ac_couple_block(gr.hier_block2): - """ - AC couple the incoming stream by subtracting out the low pass signal. - Mute the low pass filter to disable ac coupling. - """ - - def __init__(self, controller, ac_couple_key, sample_rate_key): - gr.hier_block2.__init__( - self, - "ac_couple", - gr.io_signature(1, 1, gr.sizeof_float), - gr.io_signature(1, 1, gr.sizeof_float), - ) - #blocks - lpf = filter.single_pole_iir_filter_ff(0.0) - sub = blocks.sub_ff() - mute = blocks.mute_ff() - #connect - self.connect(self, sub, self) - self.connect(self, lpf, mute, (sub, 1)) - #subscribe - controller.subscribe(ac_couple_key, lambda x: mute.set_mute(not x)) - controller.subscribe(sample_rate_key, lambda x: lpf.set_taps(0.05)) - #initialize - controller[ac_couple_key] = controller[ac_couple_key] - controller[sample_rate_key] = controller[sample_rate_key] - -################################################## -# Scope sink block (wrapper for old wxgui) -################################################## -class _scope_sink_base(gr.hier_block2, common.wxgui_hb): - """ - A scope block with a gui window. - """ - - def __init__( - self, - parent, - title='', - sample_rate=1, - size=scope_window.DEFAULT_WIN_SIZE, - v_scale=0, - t_scale=0, - v_offset=0, - xy_mode=False, - ac_couple=False, - num_inputs=1, - trig_mode=scope_window.DEFAULT_TRIG_MODE, - y_axis_label='Counts', - frame_rate=scope_window.DEFAULT_FRAME_RATE, - use_persistence=False, - persist_alpha=None, - **kwargs #do not end with a comma - ): - #ensure analog alpha - if persist_alpha is None: - actual_frame_rate=float(frame_rate) - analog_cutoff_freq=0.5 # Hertz - #calculate alpha from wanted cutoff freq - persist_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_frame_rate) - - if not t_scale: t_scale = 10.0/sample_rate - #init - gr.hier_block2.__init__( - self, - "scope_sink", - gr.io_signature(num_inputs, num_inputs, self._item_size), - gr.io_signature(0, 0, 0), - ) - #scope - msgq = gr.msg_queue(2) - scope = wxgui.oscope_sink_f(sample_rate, msgq) - #controller - self.controller = pubsub() - self.controller.subscribe(SAMPLE_RATE_KEY, scope.set_sample_rate) - self.controller.publish(SAMPLE_RATE_KEY, scope.sample_rate) - self.controller.subscribe(DECIMATION_KEY, scope.set_decimation_count) - self.controller.publish(DECIMATION_KEY, scope.get_decimation_count) - self.controller.subscribe(TRIGGER_LEVEL_KEY, scope.set_trigger_level) - self.controller.publish(TRIGGER_LEVEL_KEY, scope.get_trigger_level) - self.controller.subscribe(TRIGGER_MODE_KEY, scope.set_trigger_mode) - self.controller.publish(TRIGGER_MODE_KEY, scope.get_trigger_mode) - self.controller.subscribe(TRIGGER_SLOPE_KEY, scope.set_trigger_slope) - self.controller.publish(TRIGGER_SLOPE_KEY, scope.get_trigger_slope) - self.controller.subscribe(TRIGGER_CHANNEL_KEY, scope.set_trigger_channel) - self.controller.publish(TRIGGER_CHANNEL_KEY, scope.get_trigger_channel) - actual_num_inputs = self._real and num_inputs or num_inputs*2 - #init ac couple - for i in range(actual_num_inputs): - self.controller[common.index_key(AC_COUPLE_KEY, i)] = ac_couple - #start input watcher - common.input_watcher(msgq, self.controller, MSG_KEY) - #create window - self.win = scope_window.scope_window( - parent=parent, - controller=self.controller, - size=size, - title=title, - frame_rate=frame_rate, - num_inputs=actual_num_inputs, - sample_rate_key=SAMPLE_RATE_KEY, - t_scale=t_scale, - v_scale=v_scale, - v_offset=v_offset, - xy_mode=xy_mode, - trig_mode=trig_mode, - y_axis_label=y_axis_label, - ac_couple_key=AC_COUPLE_KEY, - trigger_level_key=TRIGGER_LEVEL_KEY, - trigger_mode_key=TRIGGER_MODE_KEY, - trigger_slope_key=TRIGGER_SLOPE_KEY, - trigger_channel_key=TRIGGER_CHANNEL_KEY, - decimation_key=DECIMATION_KEY, - msg_key=MSG_KEY, - use_persistence=use_persistence, - persist_alpha=persist_alpha, - ) - common.register_access_methods(self, self.win) - #connect - if self._real: - for i in range(num_inputs): - self.wxgui_connect( - (self, i), - ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, i), SAMPLE_RATE_KEY), - (scope, i), - ) - else: - for i in range(num_inputs): - c2f = blocks.complex_to_float() - self.wxgui_connect((self, i), c2f) - for j in range(2): - self.connect( - (c2f, j), - ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, 2*i+j), SAMPLE_RATE_KEY), - (scope, 2*i+j), - ) - -class scope_sink_f(_scope_sink_base): - _item_size = gr.sizeof_float - _real = True - -class scope_sink_c(_scope_sink_base): - _item_size = gr.sizeof_gr_complex - _real = False - -# ---------------------------------------------------------------- -# Stand-alone test application -# ---------------------------------------------------------------- - -import wx -from gnuradio.wxgui import stdgui2 - -class test_top_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - default_input_rate = 1e6 - if len(argv) > 1: - input_rate = int(argv[1]) - else: - input_rate = default_input_rate - - if len(argv) > 2: - v_scale = float(argv[2]) # start up at this v_scale value - else: - v_scale = None # start up in autorange mode, default - - if len(argv) > 3: - t_scale = float(argv[3]) # start up at this t_scale value - else: - t_scale = .00003*default_input_rate/input_rate # old behavior - - print "input rate %s v_scale %s t_scale %s" % (input_rate,v_scale,t_scale) - - - # Generate a complex sinusoid - ampl=1.0e3 - self.src0 = analog.sig_source_c(input_rate, analog.GR_SIN_WAVE, - 25.1e3*input_rate/default_input_rate, ampl) - self.noise = analog.sig_source_c(input_rate, analog.GR_SIN_WAVE, - 11.1*25.1e3*input_rate/default_input_rate, - ampl/10) - #self.noise = analog.noise_source_c(analog.GR_GAUSSIAN, ampl/10) - self.combine = blocks.add_cc() - - # We add this throttle block so that this demo doesn't suck down - # all the CPU available. You normally wouldn't use it... - self.thr = blocks.throttle(gr.sizeof_gr_complex, input_rate) - - scope = scope_sink_c(panel,"Secret Data",sample_rate=input_rate, - v_scale=v_scale, t_scale=t_scale) - vbox.Add(scope.win, 1, wx.EXPAND) - - # Ultimately this will be - # self.connect("src0 throttle scope") - self.connect(self.src0,(self.combine,0)) - self.connect(self.noise,(self.combine,1)) - self.connect(self.combine, self.thr, scope) - -def main (): - app = stdgui2.stdapp(test_top_block, "O'Scope Test App") - app.MainLoop() - -if __name__ == '__main__': - main() diff --git a/gr-wxgui/python/wxgui/scopesink_nongl.py b/gr-wxgui/python/wxgui/scopesink_nongl.py deleted file mode 100644 index 28a473860f..0000000000 --- a/gr-wxgui/python/wxgui/scopesink_nongl.py +++ /dev/null @@ -1,654 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2003,2004,2006,2007,2012 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 gr, gru, eng_notation -from gnuradio import analog -from gnuradio import blocks -from gnuradio import wxgui -from gnuradio.wxgui import stdgui2 -import wx -import gnuradio.wxgui.plot as plot -import numpy -import struct - -default_scopesink_size = (640, 240) -default_v_scale = 1000 -default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) - -class scope_sink_f(gr.hier_block2): - def __init__(self, parent, title='', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1, **kwargs): - - gr.hier_block2.__init__(self, "scope_sink_f", - gr.io_signature(num_inputs, num_inputs, gr.sizeof_float), - gr.io_signature(0,0,0)) - - msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - self.guts = wxgui.oscope_sink_f(sample_rate, msgq) - for i in range(num_inputs): - self.connect((self, i), (self.guts, i)) - - self.win = scope_window(win_info(msgq, sample_rate, frame_decim, - v_scale, t_scale, self.guts, title), parent) - - def set_sample_rate(self, sample_rate): - self.guts.set_sample_rate(sample_rate) - self.win.info.set_sample_rate(sample_rate) - -class scope_sink_c(gr.hier_block2): - def __init__(self, parent, title='', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1, xy_mode=False, **kwargs): - - gr.hier_block2.__init__(self, "scope_sink_c", - gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex), - gr.io_signature(0,0,0)) - - msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - self.guts = wxgui.oscope_sink_f(sample_rate, msgq) - for i in range(num_inputs): - c2f = blocks.complex_to_float() - self.connect((self, i), c2f) - self.connect((c2f, 0),(self.guts, 2*i+0)) - self.connect((c2f, 1),(self.guts, 2*i+1)) - - self.win = scope_window(win_info(msgq, sample_rate, frame_decim, - v_scale, t_scale, self.guts, title), parent) - self.win.info.xy = xy_mode - - def set_sample_rate(self, sample_rate): - self.guts.set_sample_rate(sample_rate) - self.win.info.set_sample_rate(sample_rate) - -class constellation_sink(scope_sink_c): - def __init__(self, parent, title='Constellation', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim): - scope_sink_c.__init__(self, parent=parent, title=title, sample_rate=sample_rate, - size=size, frame_decim=frame_decim) - self.win.info.xy = True #constellation mode - -# ======================================================================== - - -time_base_list = [ # time / division - 1.0e-7, # 100ns / div - 2.5e-7, - 5.0e-7, - 1.0e-6, # 1us / div - 2.5e-6, - 5.0e-6, - 1.0e-5, # 10us / div - 2.5e-5, - 5.0e-5, - 1.0e-4, # 100us / div - 2.5e-4, - 5.0e-4, - 1.0e-3, # 1ms / div - 2.5e-3, - 5.0e-3, - 1.0e-2, # 10ms / div - 2.5e-2, - 5.0e-2 - ] - -v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER - 2.0e-3, # 2m / div, don't call it V/div it's actually counts/div - 5.0e-3, - 1.0e-2, - 2.0e-2, - 5.0e-2, - 1.0e-1, - 2.0e-1, - 5.0e-1, - 1.0e+0, - 2.0e+0, - 5.0e+0, - 1.0e+1, - 2.0e+1, - 5.0e+1, - 1.0e+2, - 2.0e+2, - 5.0e+2, - 1.0e+3, - 2.0e+3, - 5.0e+3, - 1.0e+4 # 10000 /div, USRP full scale is -/+ 32767 - ] - - -wxDATA_EVENT = wx.NewEventType() - -def EVT_DATA_EVENT(win, func): - win.Connect(-1, -1, wxDATA_EVENT, func) - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType(wxDATA_EVENT) - self.data = data - - def Clone(self): - self.__class__(self.GetId()) - - -class win_info(object): - __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', - 'scopesink', 'title', - 'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy', - 'autorange', 'running'] - - def __init__(self, msgq, sample_rate, frame_decim, v_scale, t_scale, - scopesink, title = "Oscilloscope", xy=False): - self.msgq = msgq - self.sample_rate = sample_rate - self.frame_decim = frame_decim - self.scopesink = scopesink - self.title = title; - - self.time_scale_cursor = gru.seq_with_cursor(time_base_list, initial_value = t_scale) - self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale) - - self.marker = 'line' - self.xy = xy - self.autorange = not v_scale - self.running = True - - def get_time_per_div(self): - return self.time_scale_cursor.current() - - def get_volts_per_div(self): - return self.v_scale_cursor.current() - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - - def get_sample_rate(self): - return self.sample_rate - - def get_decimation_rate(self): - return 1.0 - - def set_marker(self, s): - self.marker = s - - def get_marker(self): - return self.marker - - -class input_watcher(gru.msgq_runner): - def __init__(self, msgq, event_receiver, frame_decim, **kwds): - self.event_receiver = event_receiver - self.frame_decim = frame_decim - self.iscan = 0 - gru.msgq_runner.__init__(self, msgq, self.handle_msg) - - def handle_msg(self, msg): - if self.iscan == 0: # only display at frame_decim - self.iscan = self.frame_decim - - nchan = int(msg.arg1()) # number of channels of data in msg - nsamples = int(msg.arg2()) # number of samples in each channel - - s = msg.to_string() # get the body of the msg as a string - - bytes_per_chan = nsamples * gr.sizeof_float - - records = [] - for ch in range(nchan): - - start = ch * bytes_per_chan - chan_data = s[start:start+bytes_per_chan] - rec = numpy.fromstring(chan_data, numpy.float32) - records.append(rec) - - # print "nrecords = %d, reclen = %d" % (len (records),nsamples) - - de = DataEvent(records) - wx.PostEvent(self.event_receiver, de) - records = [] - del de - - self.iscan -= 1 - - -class scope_window(wx.Panel): - - def __init__(self, info, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""): - wx.Panel.__init__(self, parent, -1) - self.info = info - - vbox = wx.BoxSizer(wx.VERTICAL) - - self.graph = graph_window(info, self, -1) - - vbox.Add(self.graph, 1, wx.EXPAND) - vbox.Add(self.make_control_box(), 0, wx.EXPAND) - vbox.Add(self.make_control2_box(), 0, wx.EXPAND) - - self.sizer = vbox - self.SetSizer(self.sizer) - self.SetAutoLayout(True) - self.sizer.Fit(self) - self.set_autorange(self.info.autorange) - - - # second row of control buttons etc. appears BELOW control_box - def make_control2_box(self): - ctrlbox = wx.BoxSizer(wx.HORIZONTAL) - - self.inc_v_button = wx.Button(self, 1101, " < ", style=wx.BU_EXACTFIT) - self.inc_v_button.SetToolTipString("Increase vertical range") - wx.EVT_BUTTON(self, 1101, self.incr_v_scale) # ID matches button ID above - - self.dec_v_button = wx.Button(self, 1100, " > ", style=wx.BU_EXACTFIT) - self.dec_v_button.SetToolTipString("Decrease vertical range") - wx.EVT_BUTTON(self, 1100, self.decr_v_scale) - - self.v_scale_label = wx.StaticText(self, 1002, "None") # vertical /div - self.update_v_scale_label() - - self.autorange_checkbox = wx.CheckBox(self, 1102, "Autorange") - self.autorange_checkbox.SetToolTipString("Select autorange on/off") - wx.EVT_CHECKBOX(self, 1102, self.autorange_checkbox_event) - - ctrlbox.Add((5,0) ,0) # left margin space - ctrlbox.Add(self.inc_v_button, 0, wx.EXPAND) - ctrlbox.Add(self.dec_v_button, 0, wx.EXPAND) - ctrlbox.Add(self.v_scale_label, 0, wx.ALIGN_CENTER) - ctrlbox.Add((20,0) ,0) # spacer - ctrlbox.Add(self.autorange_checkbox, 0, wx.ALIGN_CENTER) - - return ctrlbox - - def make_control_box(self): - ctrlbox = wx.BoxSizer(wx.HORIZONTAL) - - tb_left = wx.Button(self, 1001, " < ", style=wx.BU_EXACTFIT) - tb_left.SetToolTipString("Increase time base") - wx.EVT_BUTTON(self, 1001, self.incr_timebase) - - - tb_right = wx.Button(self, 1000, " > ", style=wx.BU_EXACTFIT) - tb_right.SetToolTipString("Decrease time base") - wx.EVT_BUTTON(self, 1000, self.decr_timebase) - - self.time_base_label = wx.StaticText(self, 1002, "") - self.update_timebase_label() - - ctrlbox.Add((5,0) ,0) - # ctrlbox.Add(wx.StaticText(self, -1, "Horiz Scale: "), 0, wx.ALIGN_CENTER) - ctrlbox.Add(tb_left, 0, wx.EXPAND) - ctrlbox.Add(tb_right, 0, wx.EXPAND) - ctrlbox.Add(self.time_base_label, 0, wx.ALIGN_CENTER) - - ctrlbox.Add((10,0) ,1) # stretchy space - - ctrlbox.Add(wx.StaticText(self, -1, "Trig: "), 0, wx.ALIGN_CENTER) - self.trig_chan_choice = wx.Choice(self, 1004, - choices = ['Ch1', 'Ch2', 'Ch3', 'Ch4']) - self.trig_chan_choice.SetToolTipString("Select channel for trigger") - wx.EVT_CHOICE(self, 1004, self.trig_chan_choice_event) - ctrlbox.Add(self.trig_chan_choice, 0, wx.ALIGN_CENTER) - - self.trig_mode_choice = wx.Choice(self, 1005, - choices = ['Free', 'Auto', 'Norm']) - self.trig_mode_choice.SetSelection(1) - self.trig_mode_choice.SetToolTipString("Select trigger slope or Auto (untriggered roll)") - wx.EVT_CHOICE(self, 1005, self.trig_mode_choice_event) - ctrlbox.Add(self.trig_mode_choice, 0, wx.ALIGN_CENTER) - - trig_level50 = wx.Button(self, 1006, "50%") - trig_level50.SetToolTipString("Set trigger level to 50%") - wx.EVT_BUTTON(self, 1006, self.set_trig_level50) - ctrlbox.Add(trig_level50, 0, wx.EXPAND) - - run_stop = wx.Button(self, 1007, "Run/Stop") - run_stop.SetToolTipString("Toggle Run/Stop mode") - wx.EVT_BUTTON(self, 1007, self.run_stop) - ctrlbox.Add(run_stop, 0, wx.EXPAND) - - ctrlbox.Add((10, 0) ,1) # stretchy space - - ctrlbox.Add(wx.StaticText(self, -1, "Fmt: "), 0, wx.ALIGN_CENTER) - self.marker_choice = wx.Choice(self, 1002, choices = self._marker_choices) - self.marker_choice.SetToolTipString("Select plotting with lines, pluses or dots") - wx.EVT_CHOICE(self, 1002, self.marker_choice_event) - ctrlbox.Add(self.marker_choice, 0, wx.ALIGN_CENTER) - - self.xy_choice = wx.Choice(self, 1003, choices = ['X:t', 'X:Y']) - self.xy_choice.SetToolTipString("Select X vs time or X vs Y display") - wx.EVT_CHOICE(self, 1003, self.xy_choice_event) - ctrlbox.Add(self.xy_choice, 0, wx.ALIGN_CENTER) - - return ctrlbox - - _marker_choices = ['line', 'plus', 'dot'] - - def update_timebase_label(self): - time_per_div = self.info.get_time_per_div() - s = ' ' + eng_notation.num_to_str(time_per_div) + 's/div' - self.time_base_label.SetLabel(s) - - def decr_timebase(self, evt): - self.info.time_scale_cursor.prev() - self.update_timebase_label() - - def incr_timebase(self, evt): - self.info.time_scale_cursor.next() - self.update_timebase_label() - - def update_v_scale_label(self): - volts_per_div = self.info.get_volts_per_div() - s = ' ' + eng_notation.num_to_str(volts_per_div) + '/div' # Not V/div - self.v_scale_label.SetLabel(s) - - def decr_v_scale(self, evt): - self.info.v_scale_cursor.prev() - self.update_v_scale_label() - - def incr_v_scale(self, evt): - self.info.v_scale_cursor.next() - self.update_v_scale_label() - - def marker_choice_event(self, evt): - s = evt.GetString() - self.set_marker(s) - - def set_autorange(self, on): - if on: - self.v_scale_label.SetLabel(" (auto)") - self.info.autorange = True - self.autorange_checkbox.SetValue(True) - self.inc_v_button.Enable(False) - self.dec_v_button.Enable(False) - else: - if self.graph.y_range: - (l,u) = self.graph.y_range # found by autorange - self.info.v_scale_cursor.set_index_by_value((u-l)/8.0) - self.update_v_scale_label() - self.info.autorange = False - self.autorange_checkbox.SetValue(False) - self.inc_v_button.Enable(True) - self.dec_v_button.Enable(True) - - def autorange_checkbox_event(self, evt): - if evt.Checked(): - self.set_autorange(True) - else: - self.set_autorange(False) - - def set_marker(self, s): - self.info.set_marker(s) # set info for drawing routines - i = self.marker_choice.FindString(s) - assert i >= 0, "Hmmm, set_marker problem" - self.marker_choice.SetSelection(i) - - def set_format_line(self): - self.set_marker('line') - - def set_format_dot(self): - self.set_marker('dot') - - def set_format_plus(self): - self.set_marker('plus') - - def xy_choice_event(self, evt): - s = evt.GetString() - self.info.xy = s == 'X:Y' - - def trig_chan_choice_event(self, evt): - s = evt.GetString() - ch = int(s[-1]) - 1 - self.info.scopesink.set_trigger_channel(ch) - - def trig_mode_choice_event(self, evt): - sink = self.info.scopesink - s = evt.GetString() - if s == 'Norm': - sink.set_trigger_mode(wxgui.TRIG_MODE_NORM) - elif s == 'Auto': - sink.set_trigger_mode(wxgui.TRIG_MODE_AUTO) - elif s == 'Free': - sink.set_trigger_mode(wxgui.TRIG_MODE_FREE) - else: - assert 0, "Bad trig_mode_choice string" - - def set_trig_level50(self, evt): - self.info.scopesink.set_trigger_level_auto() - - def run_stop(self, evt): - self.info.running = not self.info.running - - -class graph_window(plot.PlotCanvas): - - channel_colors = ['BLUE', 'RED', - 'CYAN', 'MAGENTA', 'GREEN', 'YELLOW'] - - def __init__(self, info, parent, id = -1, - pos = wx.DefaultPosition, size = (640, 240), - style = wx.DEFAULT_FRAME_STYLE, name = ""): - plot.PlotCanvas.__init__(self, parent, id, pos, size, style, name) - - self.SetXUseScopeTicks(True) - self.SetEnableGrid(True) - self.SetEnableZoom(True) - self.SetEnableLegend(True) - # self.SetBackgroundColour('black') - - self.info = info; - self.y_range = None - self.x_range = None - self.avg_y_min = None - self.avg_y_max = None - self.avg_x_min = None - self.avg_x_max = None - - EVT_DATA_EVENT(self, self.format_data) - - self.input_watcher = input_watcher(info.msgq, self, info.frame_decim) - - def channel_color(self, ch): - return self.channel_colors[ch % len(self.channel_colors)] - - def format_data(self, evt): - if not self.info.running: - return - - if self.info.xy: - self.format_xy_data(evt) - return - - info = self.info - records = evt.data - nchannels = len(records) - npoints = len(records[0]) - - objects = [] - - Ts = 1.0 / (info.get_sample_rate() / info.get_decimation_rate()) - x_vals = Ts * numpy.arange(-npoints/2, npoints/2) - - # preliminary clipping based on time axis here, instead of in graphics code - time_per_window = self.info.get_time_per_div() * 10 - n = int(time_per_window / Ts + 0.5) - n = n & ~0x1 # make even - n = max(2, min(n, npoints)) - - self.SetXUseScopeTicks(True) # use 10 divisions, no labels - - for ch in range(nchannels): - r = records[ch] - - # plot middle n points of record - - lb = npoints/2 - n/2 - ub = npoints/2 + n/2 - # points = zip(x_vals[lb:ub], r[lb:ub]) - points = numpy.zeros((ub-lb, 2), numpy.float64) - points[:,0] = x_vals[lb:ub] - points[:,1] = r[lb:ub] - - m = info.get_marker() - if m == 'line': - objects.append(plot.PolyLine(points, - colour=self.channel_color(ch), - legend=('Ch%d' % (ch+1,)))) - else: - objects.append(plot.PolyMarker(points, - marker=m, - colour=self.channel_color(ch), - legend=('Ch%d' % (ch+1,)))) - - graphics = plot.PlotGraphics(objects, - title=self.info.title, - xLabel = '', yLabel = '') - - time_per_div = info.get_time_per_div() - x_range = (-5.0 * time_per_div, 5.0 * time_per_div) # ranges are tuples! - volts_per_div = info.get_volts_per_div() - if not self.info.autorange: - self.y_range = (-4.0 * volts_per_div, 4.0 * volts_per_div) - self.Draw(graphics, xAxis=x_range, yAxis=self.y_range) - self.update_y_range() # autorange to self.y_range - - - def format_xy_data(self, evt): - info = self.info - records = evt.data - nchannels = len(records) - npoints = len(records[0]) - - if nchannels < 2: - return - - objects = [] - # points = zip(records[0], records[1]) - points = numpy.zeros((len(records[0]), 2), numpy.float32) - points[:,0] = records[0] - points[:,1] = records[1] - - self.SetXUseScopeTicks(False) - - m = info.get_marker() - if m == 'line': - objects.append(plot.PolyLine(points, - colour=self.channel_color(0))) - else: - objects.append(plot.PolyMarker(points, - marker=m, - colour=self.channel_color(0))) - - graphics = plot.PlotGraphics(objects, - title=self.info.title, - xLabel = 'I', yLabel = 'Q') - - self.Draw(graphics, xAxis=self.x_range, yAxis=self.y_range) - self.update_y_range() - self.update_x_range() - - - def update_y_range(self): - alpha = 1.0/25 - graphics = self.last_draw[0] - p1, p2 = graphics.boundingBox() # min, max points of graphics - - if self.avg_y_min: # prevent vertical scale from jumping abruptly --? - self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha) - self.avg_y_max = p2[1] * alpha + self.avg_y_max * (1 - alpha) - else: # initial guess - self.avg_y_min = p1[1] # -500.0 workaround, sometimes p1 is ~ 10^35 - self.avg_y_max = p2[1] # 500.0 - - self.y_range = self._axisInterval('auto', self.avg_y_min, self.avg_y_max) - # print "p1 %s p2 %s y_min %s y_max %s y_range %s" \ - # % (p1, p2, self.avg_y_min, self.avg_y_max, self.y_range) - - - def update_x_range(self): - alpha = 1.0/25 - graphics = self.last_draw[0] - p1, p2 = graphics.boundingBox() # min, max points of graphics - - if self.avg_x_min: - self.avg_x_min = p1[0] * alpha + self.avg_x_min * (1 - alpha) - self.avg_x_max = p2[0] * alpha + self.avg_x_max * (1 - alpha) - else: - self.avg_x_min = p1[0] - self.avg_x_max = p2[0] - - self.x_range = self._axisInterval('auto', self.avg_x_min, self.avg_x_max) - - -# ---------------------------------------------------------------- -# Stand-alone test application -# ---------------------------------------------------------------- - -class test_top_block(stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) - - if len(argv) > 1: - frame_decim = int(argv[1]) - else: - frame_decim = 1 - - if len(argv) > 2: - v_scale = float(argv[2]) # start up at this v_scale value - else: - v_scale = None # start up in autorange mode, default - - if len(argv) > 3: - t_scale = float(argv[3]) # start up at this t_scale value - else: - t_scale = None # old behavior - - print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) - - input_rate = 1e6 - - # Generate a complex sinusoid - self.src0 = analog.sig_source_c(input_rate, analog.GR_SIN_WAVE, 25.1e3, 1e3) - - # We add this throttle block so that this demo doesn't suck down - # all the CPU available. You normally wouldn't use it... - self.thr = blocks.throttle(gr.sizeof_gr_complex, input_rate) - - scope = scope_sink_c(panel,"Secret Data",sample_rate=input_rate, - frame_decim=frame_decim, - v_scale=v_scale, t_scale=t_scale) - vbox.Add(scope.win, 1, wx.EXPAND) - - # Ultimately this will be - # self.connect("src0 throttle scope") - self.connect(self.src0, self.thr, scope) - -def main(): - app = stdgui2.stdapp(test_top_block, "O'Scope Test App") - app.MainLoop() - -if __name__ == '__main__': - main() - -# ---------------------------------------------------------------- diff --git a/gr-wxgui/python/wxgui/slider.py b/gr-wxgui/python/wxgui/slider.py deleted file mode 100644 index 9a8bfff7e7..0000000000 --- a/gr-wxgui/python/wxgui/slider.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python - -import wx - -def slider(parent, min, max, callback): - """ - Return a wx.Slider object. - - Args: - min: minimum slider value (float) - max: maximum slider value (float) - callback: function of one arg invoked when slider moves. - @rtype: wx.Slider - """ - new_id = wx.NewId() - s = wx.Slider(parent, new_id, (max+min)/2, min, max, wx.DefaultPosition, - wx.Size(250,-1), wx.SL_HORIZONTAL | wx.SL_LABELS) - wx.EVT_COMMAND_SCROLL(parent, new_id, - lambda evt : callback(evt.GetInt())) - return s - - -# ---------------------------------------------------------------- -# Demo app -# ---------------------------------------------------------------- -if __name__ == '__main__': - - from gnuradio.wxgui import stdgui2 - - class demo_graph(stdgui2.std_top_block): - - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - vbox.Add(slider(panel, 23, 47, self.my_callback1), 1, wx.ALIGN_CENTER) - vbox.Add(slider(panel, -100, 100, self.my_callback2), 1, wx.ALIGN_CENTER) - - def my_callback1(self, val): - print "cb1 = ", val - - def my_callback2(self, val): - print "cb2 = ", val - - def main (): - app = stdgui2.stdapp (demo_graph, "Slider Demo") - app.MainLoop () - - main () diff --git a/gr-wxgui/python/wxgui/stdgui2.py b/gr-wxgui/python/wxgui/stdgui2.py deleted file mode 100644 index dbd0307195..0000000000 --- a/gr-wxgui/python/wxgui/stdgui2.py +++ /dev/null @@ -1,107 +0,0 @@ -# -# Copyright 2004 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. -# - -'''A simple wx gui for GNU Radio applications''' - -import ctypes -import wx -import sys -from gnuradio import gr - - -class stdapp (wx.App): - def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2, - max_noutput_items=None): - self.top_block_maker = top_block_maker - self.title = title - self._nstatus = nstatus - self._max_noutput_items = max_noutput_items - # If we're on Linux, also enable multi-threading Xlib access - if sys.platform.startswith('linux'): - try: - x11 = ctypes.cdll.LoadLibrary('libX11.so') - x11.XInitThreads() - except: - print "Warning: failed to XInitThreads()" - # All our initialization must come before calling wx.App.__init__. - # OnInit is called from somewhere in the guts of __init__. - wx.App.__init__ (self, redirect=False) - - def OnInit (self): - frame = stdframe (self.top_block_maker, self.title, self._nstatus) - frame.Show (True) - self.SetTopWindow (frame) - - if(self._max_noutput_items is not None): - frame.top_block().start (self._max_noutput_items) - else: - frame.top_block().start () - - return True - - -class stdframe (wx.Frame): - def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2): - # print "stdframe.__init__" - wx.Frame.__init__(self, None, -1, title) - - self.CreateStatusBar (nstatus) - mainmenu = wx.MenuBar () - - menu = wx.Menu () - item = menu.Append (200, 'E&xit', 'Exit') - self.Bind (wx.EVT_MENU, self.OnCloseWindow, item) - mainmenu.Append (menu, "&File") - self.SetMenuBar (mainmenu) - - self.Bind (wx.EVT_CLOSE, self.OnCloseWindow) - self.panel = stdpanel (self, self, top_block_maker) - vbox = wx.BoxSizer(wx.VERTICAL) - vbox.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(vbox) - self.SetAutoLayout(True) - vbox.Fit(self) - - def OnCloseWindow (self, event): - self.top_block().stop() - self.top_block().wait() - self.Destroy () - - def top_block (self): - return self.panel.top_block - -class stdpanel (wx.Panel): - def __init__ (self, parent, frame, top_block_maker): - # print "stdpanel.__init__" - wx.Panel.__init__ (self, parent, -1) - self.frame = frame - - vbox = wx.BoxSizer (wx.VERTICAL) - self.top_block = top_block_maker (frame, self, vbox, sys.argv) - self.SetSizer (vbox) - self.SetAutoLayout (True) - vbox.Fit (self) - -class std_top_block (gr.top_block): - def __init__ (self, parent, panel, vbox, argv): - # Call the hier_block2 constructor - # Top blocks have no inputs and outputs - gr.top_block.__init__(self, "std_top_block") diff --git a/gr-wxgui/python/wxgui/termsink.py b/gr-wxgui/python/wxgui/termsink.py deleted file mode 100644 index a0cfd575d6..0000000000 --- a/gr-wxgui/python/wxgui/termsink.py +++ /dev/null @@ -1,77 +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. -# - -from gnuradio import gru -import wx - -DEFAULT_WIN_SIZE = (600, 300) -APPEND_EVENT = wx.NewEventType() -EVT_APPEND_EVENT = wx.PyEventBinder(APPEND_EVENT, 0) - -class AppendEvent(wx.PyEvent): - def __init__(self, text): - wx.PyEvent.__init__(self) - self.SetEventType(APPEND_EVENT) - self.text = text - - def Clone(self): - self.__class__(self.GetId()) - -class termsink(wx.Panel): - def __init__(self, - parent, - msgq, - size=DEFAULT_WIN_SIZE, - ): - - wx.Panel.__init__(self, - parent, - size=size, - style=wx.SIMPLE_BORDER, - ) - - self.text_ctrl = wx.TextCtrl(self, - wx.ID_ANY, - value="", - size=size, - style=wx.TE_MULTILINE|wx.TE_READONLY, - ) - - main_sizer = wx.BoxSizer(wx.VERTICAL) - main_sizer.Add(self.text_ctrl, 1, wx.EXPAND) - self.SetSizerAndFit(main_sizer) - - EVT_APPEND_EVENT(self, self.evt_append) - self.runner = gru.msgq_runner(msgq, self.handle_msg) - - def handle_msg(self, msg): - # This gets called in the queue runner thread context - # For now, just add whatever the user sends to the text control - text = msg.to_string() - - # Create a wxPython event and post it to the event queue - evt = AppendEvent(text) - wx.PostEvent(self, evt) - del evt - - def evt_append(self, evt): - # This gets called by the wxPython event queue runner - self.text_ctrl.AppendText(evt.text) diff --git a/gr-wxgui/python/wxgui/waterfall_window.py b/gr-wxgui/python/wxgui/waterfall_window.py deleted file mode 100644 index 272af10ffc..0000000000 --- a/gr-wxgui/python/wxgui/waterfall_window.py +++ /dev/null @@ -1,328 +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-1`301, USA. -# - -################################################## -# Imports -################################################## -import plotter -import common -import wx -import numpy -import math -import pubsub -from constants import * -from gnuradio import gr #for gr.prefs -import forms - -################################################## -# Constants -################################################## -SLIDER_STEPS = 100 -AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 -DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'waterfall_rate', 30) -DEFAULT_COLOR_MODE = gr.prefs().get_string('wxgui', 'waterfall_color', 'rgb1') -DEFAULT_WIN_SIZE = (600, 300) -DIV_LEVELS = (1, 2, 5, 10, 20) -MIN_DYNAMIC_RANGE, MAX_DYNAMIC_RANGE = 10, 200 -DYNAMIC_RANGE_STEP = 10. -COLOR_MODES = ( - ('RGB1', 'rgb1'), - ('RGB2', 'rgb2'), - ('RGB3', 'rgb3'), - ('Gray', 'gray'), -) - -################################################## -# Waterfall window control panel -################################################## -class control_panel(wx.Panel): - """ - A control panel with wx widgits to control the plotter and fft block chain. - """ - - def __init__(self, parent): - """ - Create a new control panel. - - Args: - parent: the wx parent window - """ - self.parent = parent - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - parent[SHOW_CONTROL_PANEL_KEY] = True - parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) - control_box = wx.BoxSizer(wx.VERTICAL) - control_box.AddStretchSpacer() - 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() - 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, - ) - #dyanmic range buttons - 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 - 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 - forms.single_button( - parent=self, sizer=axes_box, label='Autoscale', - callback=self.parent.autoscale, - ) - #clear - control_box.AddStretchSpacer() - forms.single_button( - parent=self, sizer=control_box, label='Clear', - callback=self._on_clear_button, - ) - #run/stop - 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) - - ################################################## - # Event handlers - ################################################## - def _on_clear_button(self, event): - self.parent[NUM_LINES_KEY] = self.parent[NUM_LINES_KEY] - def _on_incr_dynamic_range(self, event): - self.parent[DYNAMIC_RANGE_KEY] = min(MAX_DYNAMIC_RANGE, common.get_clean_incr(self.parent[DYNAMIC_RANGE_KEY])) - def _on_decr_dynamic_range(self, event): - self.parent[DYNAMIC_RANGE_KEY] = max(MIN_DYNAMIC_RANGE, common.get_clean_decr(self.parent[DYNAMIC_RANGE_KEY])) - def _on_incr_ref_level(self, event): - self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]/DYNAMIC_RANGE_STEP - def _on_decr_ref_level(self, event): - self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]/DYNAMIC_RANGE_STEP - def _on_incr_time_scale(self, event): - old_rate = self.parent[FRAME_RATE_KEY] - self.parent[FRAME_RATE_KEY] *= 0.75 - if self.parent[FRAME_RATE_KEY] < 1.0: - self.parent[FRAME_RATE_KEY] = 1.0 - - if self.parent[FRAME_RATE_KEY] == old_rate: - self.parent[DECIMATION_KEY] += 1 - def _on_decr_time_scale(self, event): - old_rate = self.parent[FRAME_RATE_KEY] - self.parent[FRAME_RATE_KEY] *= 1.25 - if self.parent[FRAME_RATE_KEY] == old_rate: - self.parent[DECIMATION_KEY] -= 1 - -################################################## -# Waterfall window with plotter and control panel -################################################## -class waterfall_window(wx.Panel, pubsub.pubsub): - def __init__( - self, - parent, - controller, - size, - title, - real, - fft_size, - num_lines, - decimation_key, - baseband_freq, - sample_rate_key, - frame_rate_key, - dynamic_range, - ref_level, - average_key, - avg_alpha_key, - msg_key, - ): - pubsub.pubsub.__init__(self) - #setup - self.samples = list() - self.real = real - self.fft_size = fft_size - #proxy the keys - self.proxy(MSG_KEY, controller, msg_key) - self.proxy(DECIMATION_KEY, controller, decimation_key) - self.proxy(FRAME_RATE_KEY, controller, frame_rate_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) - #init panel and plot - wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) - self.plotter = plotter.waterfall_plotter(self) - self.plotter.SetSize(wx.Size(*size)) - self.plotter.SetSizeHints(*size) - self.plotter.set_title(title) - self.plotter.enable_point_label(True) - self.plotter.enable_grid_lines(False) - #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[DYNAMIC_RANGE_KEY] = dynamic_range - self[NUM_LINES_KEY] = num_lines - self[Y_DIVS_KEY] = 8 - self[X_DIVS_KEY] = 8 #approximate - self[REF_LEVEL_KEY] = ref_level - self[BASEBAND_FREQ_KEY] = baseband_freq - self[COLOR_MODE_KEY] = COLOR_MODES[0][1] - self[COLOR_MODE_KEY] = DEFAULT_COLOR_MODE - 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 ( - DECIMATION_KEY, SAMPLE_RATE_KEY, FRAME_RATE_KEY, - BASEBAND_FREQ_KEY, X_DIVS_KEY, Y_DIVS_KEY, NUM_LINES_KEY, - ): self.subscribe(key, self.update_grid) - #initial update - self.update_grid() - - def set_callback(self,callb): - self.plotter.set_callback(callb) - - def autoscale(self, *args): - """ - Autoscale the waterfall plot to the last frame. - Set the dynamic range and reference level. - Does not affect the current data in the waterfall. - """ - if not len(self.samples): return - min_level, max_level = common.get_min_max_fft(self.samples) - #set the range and level - self[DYNAMIC_RANGE_KEY] = common.get_clean_num(max_level - min_level) - self[REF_LEVEL_KEY] = DYNAMIC_RANGE_STEP*round(.5+max_level/DYNAMIC_RANGE_STEP) - - def handle_msg(self, msg): - """ - Handle the message from the fft sink message queue. - If complex, reorder the fft samples so the negative bins come first. - If real, keep take only the positive bins. - Send the data to the plotter. - - Args: - msg: the fft array as a character array - """ - if not self[RUNNING_KEY]: return - #convert to floating point numbers - self.samples = samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame - num_samps = len(samples) - #reorder fft - if self.real: samples = samples[:(num_samps+1)/2] - else: samples = numpy.concatenate((samples[num_samps/2+1:], samples[:(num_samps+1)/2])) - #plot the fft - self.plotter.set_samples( - samples=samples, - minimum=self[REF_LEVEL_KEY] - self[DYNAMIC_RANGE_KEY], - maximum=self[REF_LEVEL_KEY], - ) - #update the plotter - self.plotter.update() - - def update_grid(self, *args): - """ - Update the plotter grid. - This update method is dependent on the variables below. - Determine the x and y axis grid parameters. - The x axis depends on sample rate, baseband freq, and x divs. - The y axis depends on y per div, y divs, and ref level. - """ - #grid parameters - sample_rate = self[SAMPLE_RATE_KEY] - frame_rate = self[FRAME_RATE_KEY] - if frame_rate < 1.0 : - frame_rate = 1.0 - baseband_freq = self[BASEBAND_FREQ_KEY] - num_lines = self[NUM_LINES_KEY] - y_divs = self[Y_DIVS_KEY] - x_divs = self[X_DIVS_KEY] - #determine best fitting x_per_div - if self.real: x_width = sample_rate/2.0 - else: x_width = sample_rate/1.0 - x_per_div = common.get_clean_num(x_width/x_divs) - #update the x grid - if self.real: - self.plotter.set_x_grid( - baseband_freq, - baseband_freq + sample_rate/2.0, - x_per_div, True, - ) - else: - self.plotter.set_x_grid( - baseband_freq - sample_rate/2.0, - baseband_freq + sample_rate/2.0, - x_per_div, True, - ) - #update x units - self.plotter.set_x_label('Frequency', 'Hz') - #update y grid - duration = float(num_lines)/frame_rate - y_per_div = common.get_clean_num(duration/y_divs) - self.plotter.set_y_grid(0, duration, y_per_div, True) - #update y units - self.plotter.set_y_label('Time', 's') - #update plotter - self.plotter.update() diff --git a/gr-wxgui/python/wxgui/waterfallsink2.py b/gr-wxgui/python/wxgui/waterfallsink2.py deleted file mode 100644 index 0b876fc3e2..0000000000 --- a/gr-wxgui/python/wxgui/waterfallsink2.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 2008,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 gr - -p = gr.prefs() -style = p.get_string('wxgui', 'style', 'auto') - -if style == 'auto' or style == 'gl': - try: - import wx.glcanvas - from OpenGL.GL import * - from waterfallsink_gl import waterfall_sink_f, waterfall_sink_c - except ImportError: - if style == 'gl': - raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - else: - # Fall backto non-gl sinks - from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c -elif style == 'nongl': - from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c -else: - raise RuntimeError("Unknown wxgui style") diff --git a/gr-wxgui/python/wxgui/waterfallsink_gl.py b/gr-wxgui/python/wxgui/waterfallsink_gl.py deleted file mode 100644 index c763d591b2..0000000000 --- a/gr-wxgui/python/wxgui/waterfallsink_gl.py +++ /dev/null @@ -1,176 +0,0 @@ -# -# Copyright 2008,2009,2012 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. -# - -################################################## -# Imports -################################################## -import waterfall_window -import common -from gnuradio import gr, fft -from gnuradio import analog -from gnuradio import blocks -from gnuradio.fft import logpwrfft -from pubsub import pubsub -from constants import * - -################################################## -# Waterfall sink block (wrapper for old wxgui) -################################################## -class _waterfall_sink_base(gr.hier_block2, common.wxgui_hb): - """ - An fft block with real/complex inputs and a gui window. - """ - - def __init__( - self, - parent, - baseband_freq=0, - ref_level=50, - sample_rate=1, - fft_size=512, - fft_rate=waterfall_window.DEFAULT_FRAME_RATE, - average=False, - avg_alpha=None, - title='', - size=waterfall_window.DEFAULT_WIN_SIZE, - ref_scale=2.0, - dynamic_range=80, - num_lines=256, - win=None, - **kwargs #do not end with a comma - ): - #ensure avg alpha - if avg_alpha is None: avg_alpha = 2.0/fft_rate - #init - gr.hier_block2.__init__( - self, - "waterfall_sink", - gr.io_signature(1, 1, self._item_size), - gr.io_signature(0, 0, 0), - ) - #blocks - fft = self._fft_chain( - sample_rate=sample_rate, - fft_size=fft_size, - frame_rate=fft_rate, - ref_scale=ref_scale, - avg_alpha=avg_alpha, - average=average, - win=win, - ) - msgq = gr.msg_queue(2) - sink = blocks.message_sink(gr.sizeof_float*fft_size, msgq, True) - #controller - self.controller = pubsub() - self.controller.subscribe(AVERAGE_KEY, fft.set_average) - self.controller.publish(AVERAGE_KEY, fft.average) - self.controller.subscribe(AVG_ALPHA_KEY, fft.set_avg_alpha) - self.controller.publish(AVG_ALPHA_KEY, fft.avg_alpha) - self.controller.subscribe(SAMPLE_RATE_KEY, fft.set_sample_rate) - self.controller.publish(SAMPLE_RATE_KEY, fft.sample_rate) - self.controller.subscribe(DECIMATION_KEY, fft.set_decimation) - self.controller.publish(DECIMATION_KEY, fft.decimation) - self.controller.subscribe(FRAME_RATE_KEY, fft.set_vec_rate) - self.controller.publish(FRAME_RATE_KEY, fft.frame_rate) - #start input watcher - common.input_watcher(msgq, self.controller, MSG_KEY) - #create window - self.win = waterfall_window.waterfall_window( - parent=parent, - controller=self.controller, - size=size, - title=title, - real=self._real, - fft_size=fft_size, - num_lines=num_lines, - baseband_freq=baseband_freq, - decimation_key=DECIMATION_KEY, - sample_rate_key=SAMPLE_RATE_KEY, - frame_rate_key=FRAME_RATE_KEY, - dynamic_range=dynamic_range, - ref_level=ref_level, - average_key=AVERAGE_KEY, - avg_alpha_key=AVG_ALPHA_KEY, - msg_key=MSG_KEY, - ) - common.register_access_methods(self, self.win) - setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS - #connect - self.wxgui_connect(self, fft, sink) - - def set_callback(self,callb): - self.win.set_callback(callb) - -class waterfall_sink_f(_waterfall_sink_base): - _fft_chain = logpwrfft.logpwrfft_f - _item_size = gr.sizeof_float - _real = True - -class waterfall_sink_c(_waterfall_sink_base): - _fft_chain = logpwrfft.logpwrfft_c - _item_size = gr.sizeof_gr_complex - _real = False - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -import wx -from gnuradio.wxgui import stdgui2 - -class test_top_block(stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) - - fft_size = 512 - - # build our flow graph - input_rate = 20.000e3 - - # Generate a complex sinusoid - self.src1 = analog.sig_source_c(input_rate, analog.GR_SIN_WAVE, 5.75e3, 1000) - #src1 = analog.sig_source_c(input_rate, analog.GR_CONST_WAVE, 5.75e3, 1000) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - self.thr1 = blocks.throttle(gr.sizeof_gr_complex, input_rate) - - sink1 = waterfall_sink_c(panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - self.connect(self.src1, self.thr1, sink1) - vbox.Add(sink1.win, 1, wx.EXPAND) - - # generate a real sinusoid - self.src2 = analog.sig_source_f(input_rate, analog.GR_SIN_WAVE, 5.75e3, 1000) - self.thr2 = blocks.throttle(gr.sizeof_float, input_rate) - sink2 = waterfall_sink_f(panel, title="Real Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - self.connect(self.src2, self.thr2, sink2) - vbox.Add(sink2.win, 1, wx.EXPAND) - - -def main (): - app = stdgui2.stdapp(test_top_block, "Waterfall Sink Test App") - app.MainLoop() - -if __name__ == '__main__': - main () - diff --git a/gr-wxgui/python/wxgui/waterfallsink_nongl.py b/gr-wxgui/python/wxgui/waterfallsink_nongl.py deleted file mode 100644 index 5cfcd24413..0000000000 --- a/gr-wxgui/python/wxgui/waterfallsink_nongl.py +++ /dev/null @@ -1,434 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2003-2005,2007,2008,2013 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 gr, gru, fft, filter -from gnuradio import blocks -from gnuradio import analog -from gnuradio.wxgui import stdgui2 -from gnuradio.filter import window -import wx -import gnuradio.wxgui.plot as plot -import numpy -import os -import math - -default_fftsink_size = (640,240) -default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) - -class waterfall_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, - sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, - average=False, avg_alpha=None, title=''): - - # initialize common attributes - self.baseband_freq = baseband_freq - self.sample_rate = sample_rate - self.fft_size = fft_size - self.fft_rate = fft_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / fft_rate - else: - self.avg_alpha = avg_alpha - self.title = title - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue up to 2 messages - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - else: - self.avg.set_taps(1.0) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_baseband_freq(self, baseband_freq): - self.baseband_freq = baseband_freq - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - self._set_n() - - def _set_n(self): - self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - def set_callback(self, callb): - return - -class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): - def __init__(self, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, **kwargs): - - gr.hier_block2.__init__(self, "waterfall_sink_f", - gr.io_signature(1, 1, gr.sizeof_float), - gr.io_signature(0,0,0)) - - waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title) - - self.s2p = blocks.stream_to_vector(gr.sizeof_float, self.fft_size) - self.one_in_n = blocks.keep_one_in_n(gr.sizeof_float * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = fft.fft_vfc(self.fft_size, True, mywindow) - self.c2mag = blocks.complex_to_mag(self.fft_size) - self.avg = filter.single_pole_iir_filter_ff(1.0, self.fft_size) - self.log = blocks.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - self.sink = blocks.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) - - self.win = waterfall_window(self, parent, size=size) - self.set_average(self.average) - - -class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): - def __init__(self, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, **kwargs): - - gr.hier_block2.__init__(self, "waterfall_sink_f", - gr.io_signature(1, 1, gr.sizeof_gr_complex), - gr.io_signature(0,0,0)) - - waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title) - - self.s2p = blocks.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) - self.one_in_n = blocks.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = fft.fft_vcc(self.fft_size, True, mywindow) - self.c2mag = blocks.complex_to_mag(self.fft_size) - self.avg = filter.single_pole_iir_filter_ff(1.0, self.fft_size) - self.log = blocks.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - self.sink = blocks.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) - - self.win = waterfall_window(self, parent, size=size) - self.set_average(self.average) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - -class input_watcher (gru.msgq_runner): - def __init__ (self, msgq, fft_size, event_receiver, **kwds): - self.fft_size = fft_size - self.event_receiver = event_receiver - gru.msgq_runner.__init__(self, msgq, self.handle_msg) - - def handle_msg(self, msg): - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - -class waterfall_window (wx.Panel): - def __init__ (self, fftsink, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - wx.Panel.__init__(self, parent, id, pos, size, style, name) - self.set_baseband_freq = fftsink.set_baseband_freq - self.fftsink = fftsink - self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1) - - self.scale_factor = 5.0 # FIXME should autoscale, or set this - - dc1 = wx.MemoryDC() - dc1.SelectObject(self.bm) - dc1.Clear() - - self.pens = self.make_pens() - - wx.EVT_PAINT( self, self.OnPaint ) - wx.EVT_CLOSE (self, self.on_close_window) - EVT_DATA_EVENT (self, self.set_data) - - self.build_popup_menu() - - wx.EVT_CLOSE (self, self.on_close_window) - self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - - self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) - - - def on_close_window (self, event): - print "waterfall_window: on_close_window" - self.keep_running = False - - def const_list(self,const,len): - return [const] * len - - def make_colormap(self): - r = [] - r.extend(self.const_list(0,96)) - r.extend(range(0,255,4)) - r.extend(self.const_list(255,64)) - r.extend(range(255,128,-4)) - - g = [] - g.extend(self.const_list(0,32)) - g.extend(range(0,255,4)) - g.extend(self.const_list(255,64)) - g.extend(range(255,0,-4)) - g.extend(self.const_list(0,32)) - - b = range(128,255,4) - b.extend(self.const_list(255,64)) - b.extend(range(255,0,-4)) - b.extend(self.const_list(0,96)) - return (r,g,b) - - def make_pens(self): - (r,g,b) = self.make_colormap() - pens = [] - for i in range(0,256): - colour = wx.Colour(r[i], g[i], b[i]) - pens.append( wx.Pen(colour, 2, wx.SOLID)) - return pens - - def OnPaint(self, event): - dc = wx.PaintDC(self) - self.DoDrawing(dc) - - def DoDrawing(self, dc=None): - if dc is None: - dc = wx.ClientDC(self) - dc.DrawBitmap(self.bm, 0, 0, False ) - - - def const_list(self,const,len): - a = [const] - for i in range(1,len): - a.append(const) - return a - - - def set_data (self, evt): - dB = evt.data - L = len (dB) - - dc1 = wx.MemoryDC() - dc1.SelectObject(self.bm) - dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1) - - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) - if x >= 1e9: - sf = 1e-9 - units = "GHz" - elif x >= 1e6: - sf = 1e-6 - units = "MHz" - else: - sf = 1e-3 - units = "kHz" - - - if self.fftsink.input_is_real: # only plot 1/2 the points - d_max = L/2 - p_width = 2 - else: - d_max = L/2 - p_width = 1 - - scale_factor = self.scale_factor - if self.fftsink.input_is_real: # real fft - for x_pos in range(0, d_max): - value = int(dB[x_pos] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) - else: # complex fft - for x_pos in range(0, d_max): # positive freqs - value = int(dB[x_pos] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2) - for x_pos in range(0 , d_max): # negative freqs - value = int(dB[x_pos+d_max] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) - - del dc1 - self.DoDrawing (None) - - def on_average(self, evt): - # print "on_average" - self.fftsink.set_average(evt.IsChecked()) - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.PopupMenu(menu, event.GetPosition()) - - - def build_popup_menu(self): - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_y_per_div = wx.NewId() - self.id_decr_y_per_div = wx.NewId() - self.id_y_per_div_1 = wx.NewId() - self.id_y_per_div_2 = wx.NewId() - self.id_y_per_div_5 = wx.NewId() - self.id_y_per_div_10 = wx.NewId() - self.id_y_per_div_20 = wx.NewId() - self.id_average = wx.NewId() - - self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) - #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - # menu.Append(self.id_incr_ref_level, "Incr Ref Level") - # menu.Append(self.id_decr_ref_level, "Decr Ref Level") - # menu.Append(self.id_incr_y_per_div, "Incr dB/div") - # menu.Append(self.id_decr_y_per_div, "Decr dB/div") - # menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") - - self.checkmarks = { - self.id_average : lambda : self.fftsink.average - #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, - #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, - #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, - #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, - #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, - } - - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -class test_top_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) - - fft_size = 512 - - # build our flow graph - input_rate = 20.000e3 - - # Generate a complex sinusoid - self.src1 = analog.sig_source_c(input_rate, analog.GR_SIN_WAVE, 5.75e3, 1000) - #src1 = analog.sig_source_c(input_rate, analog.GR_CONST_WAVE, 5.75e3, 1000) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - self.thr1 = blocks.throttle(gr.sizeof_gr_complex, input_rate) - - sink1 = waterfall_sink_c(panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - self.connect(self.src1, self.thr1, sink1) - vbox.Add(sink1.win, 1, wx.EXPAND) - - # generate a real sinusoid - self.src2 = analog.sig_source_f(input_rate, analog.GR_SIN_WAVE, 5.75e3, 1000) - self.thr2 = blocks.throttle(gr.sizeof_float, input_rate) - sink2 = waterfall_sink_f(panel, title="Real Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - self.connect(self.src2, self.thr2, sink2) - vbox.Add(sink2.win, 1, wx.EXPAND) - - -def main (): - app = stdgui2.stdapp(test_top_block, "Waterfall Sink Test App") - app.MainLoop() - -if __name__ == '__main__': - main() |