Merged r10463:10658 from jblum/gui_guts into trunk. Trunk passes distcheck.
[debian/gnuradio] / gr-wxgui / src / python / number_window.py
1 #
2 # Copyright 2008 Free Software Foundation, Inc.
3 #
4 # This file is part of GNU Radio
5 #
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
9 # any later version.
10 #
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING.  If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
20 #
21
22 ##################################################
23 # Imports
24 ##################################################
25 import common
26 import numpy
27 import wx
28 import pubsub
29 from constants import *
30 from gnuradio import gr #for gr.prefs
31
32 ##################################################
33 # Constants
34 ##################################################
35 NEG_INF = float('-inf')
36 SLIDER_STEPS = 100
37 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0
38 DEFAULT_NUMBER_RATE = gr.prefs().get_long('wxgui', 'number_rate', 5)
39 DEFAULT_WIN_SIZE = (300, 300)
40 DEFAULT_GAUGE_RANGE = 1000
41
42 ##################################################
43 # Number window control panel
44 ##################################################
45 class control_panel(wx.Panel):
46         """
47         A control panel with wx widgits to control the averaging.
48         """
49
50         def __init__(self, parent):
51                 """
52                 Create a new control panel.
53                 @param parent the wx parent window
54                 """
55                 self.parent = parent
56                 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
57                 control_box = wx.BoxSizer(wx.VERTICAL)
58                 #checkboxes for average and peak hold
59                 control_box.AddStretchSpacer()
60                 control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER)
61                 self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY)
62                 control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND)
63                 self.average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY)
64                 control_box.Add(self.average_check_box, 0, wx.EXPAND)
65                 control_box.AddSpacer(2)
66                 self.avg_alpha_slider = common.LogSliderController(
67                         self, 'Avg Alpha',
68                         AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS,
69                         parent, AVG_ALPHA_KEY,
70                         formatter=lambda x: ': %.4f'%x,
71                 )
72                 parent.subscribe(AVERAGE_KEY, self.avg_alpha_slider.Enable)
73                 control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND)
74                 #run/stop
75                 control_box.AddStretchSpacer()
76                 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
77                 control_box.Add(self.run_button, 0, wx.EXPAND)
78                 #set sizer
79                 self.SetSizerAndFit(control_box)
80
81 ##################################################
82 # Numbersink window with label and gauges
83 ##################################################
84 class number_window(wx.Panel, pubsub.pubsub):
85         def __init__(
86                 self,
87                 parent,
88                 controller,
89                 size,
90                 title,
91                 units,
92                 show_gauge,
93                 real,
94                 minval,
95                 maxval,
96                 decimal_places,
97                 average_key,
98                 avg_alpha_key,
99                 peak_hold,
100                 msg_key,
101                 sample_rate_key,
102         ):
103                 pubsub.pubsub.__init__(self)
104                 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
105                 #setup
106                 self.peak_val_real = NEG_INF
107                 self.peak_val_imag = NEG_INF
108                 self.real = real
109                 self.units = units
110                 self.minval = minval
111                 self.maxval = maxval
112                 self.decimal_places = decimal_places
113                 #proxy the keys
114                 self.proxy(MSG_KEY, controller, msg_key)
115                 self.proxy(AVERAGE_KEY, controller, average_key)
116                 self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key)
117                 self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key)
118                 #setup the box with display and controls
119                 self.control_panel = control_panel(self)
120                 main_box = wx.BoxSizer(wx.HORIZONTAL)
121                 sizer = wx.BoxSizer(wx.VERTICAL)
122                 main_box.Add(sizer, 1, wx.EXPAND)
123                 main_box.Add(self.control_panel, 0, wx.EXPAND)
124                 sizer.Add(common.LabelText(self, title), 1, wx.ALIGN_CENTER)
125                 self.text = wx.StaticText(self, size=(size[0], -1))
126                 sizer.Add(self.text, 1, wx.EXPAND)
127                 self.gauge_real = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL)
128                 self.gauge_imag = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL)
129                 #hide/show gauges
130                 self.show_gauges(show_gauge)
131                 sizer.Add(self.gauge_real, 1, wx.EXPAND)
132                 sizer.Add(self.gauge_imag, 1, wx.EXPAND)
133                 self.SetSizerAndFit(main_box)
134                 #initialize values
135                 self[PEAK_HOLD_KEY] = peak_hold
136                 self[RUNNING_KEY] = True
137                 self[AVERAGE_KEY] = self[AVERAGE_KEY]
138                 self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY]
139                 #register events
140                 self.subscribe(MSG_KEY, self.handle_msg)
141                 self.Bind(common.EVT_DATA, self.update)
142
143         def show_gauges(self, show_gauge):
144                 """
145                 Show or hide the gauges.
146                 If this is real, never show the imaginary gauge.
147                 @param show_gauge true to show
148                 """
149                 if show_gauge: self.gauge_real.Show()
150                 else: self.gauge_real.Hide()
151                 if show_gauge and not self.real: self.gauge_imag.Show()
152                 else: self.gauge_imag.Hide()
153
154         def handle_msg(self, msg):
155                 """
156                 Post this message into a data event.
157                 Allow wx to handle the event to avoid threading issues.
158                 @param msg the incoming numbersink data
159                 """
160                 wx.PostEvent(self, common.DataEvent(msg))
161
162         def update(self, event):
163                 """
164                 Handle a message from the message queue.
165                 Convert the string based message into a float or complex.
166                 If more than one number was read, only take the last number.
167                 Perform peak hold operations, set the gauges and display.
168                 @param event event.data is the number sample as a character array
169                 """
170                 if not self[RUNNING_KEY]: return
171                 #set gauge
172                 def set_gauge_value(gauge, value):
173                         gauge_val = DEFAULT_GAUGE_RANGE*(value-self.minval)/(self.maxval-self.minval)
174                         gauge_val = max(0, gauge_val) #clip
175                         gauge_val = min(DEFAULT_GAUGE_RANGE, gauge_val) #clip
176                         gauge.SetValue(gauge_val)
177                 format_string = "%%.%df"%self.decimal_places
178                 if self.real:
179                         sample = numpy.fromstring(event.data, numpy.float32)[-1]
180                         if self[PEAK_HOLD_KEY]: sample = self.peak_val_real = max(self.peak_val_real, sample)
181                         label_text = "%s %s"%(format_string%sample, self.units)
182                         set_gauge_value(self.gauge_real, sample)
183                 else:
184                         sample = numpy.fromstring(event.data, numpy.complex64)[-1]
185                         if self[PEAK_HOLD_KEY]:
186                                 self.peak_val_real = max(self.peak_val_real, sample.real)
187                                 self.peak_val_imag = max(self.peak_val_imag, sample.imag)
188                                 sample = self.peak_val_real + self.peak_val_imag*1j
189                         label_text = "%s + %sj %s"%(format_string%sample.real, format_string%sample.imag, self.units)
190                         set_gauge_value(self.gauge_real, sample.real)
191                         set_gauge_value(self.gauge_imag, sample.imag)
192                 #set label text
193                 self.text.SetLabel(label_text)
194                 #clear peak hold
195                 if not self[PEAK_HOLD_KEY]:
196                         self.peak_val_real = NEG_INF
197                         self.peak_val_imag = NEG_INF