2 # Copyright 2008 Free Software Foundation, Inc.
4 # This file is part of GNU Radio
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)
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.
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.
22 #A macro to apply an index to a key
23 index_key = lambda key, i: "%s_%d"%(key, i+1)
25 def _register_access_method(destination, controller, key):
27 Helper function for register access methods.
28 This helper creates distinct set and get methods for each key
29 and adds them to the destination object.
31 def set(value): controller[key] = value
32 setattr(destination, 'set_'+key, set)
33 def get(): return controller[key]
34 setattr(destination, 'get_'+key, get)
36 def register_access_methods(destination, controller):
38 Register setter and getter functions in the destination object for all keys in the controller.
39 @param destination the object to get new setter and getter methods
40 @param controller the pubsub controller
42 for key in controller.keys(): _register_access_method(destination, controller, key)
44 ##################################################
45 # Input Watcher Thread
46 ##################################################
49 class input_watcher(threading.Thread):
51 Input watcher thread runs forever.
52 Read messages from the message queue.
53 Forward messages to the message handler.
55 def __init__ (self, msgq, controller, msg_key, arg1_key='', arg2_key=''):
56 threading.Thread.__init__(self)
59 self._controller = controller
60 self._msg_key = msg_key
61 self._arg1_key = arg1_key
62 self._arg2_key = arg2_key
63 self.keep_running = True
67 while self.keep_running:
68 msg = self.msgq.delete_head()
69 if self._arg1_key: self._controller[self._arg1_key] = msg.arg1()
70 if self._arg2_key: self._controller[self._arg2_key] = msg.arg2()
71 self._controller[self._msg_key] = msg.to_string()
73 ##################################################
75 ##################################################
79 EVT_DATA = wx.PyEventBinder(wx.NewEventType())
80 class DataEvent(wx.PyEvent):
81 def __init__(self, data):
82 wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId)
85 class LabelText(wx.StaticText):
87 Label text to give the wx plots a uniform look.
88 Get the default label text and set the font bold.
90 def __init__(self, parent, label):
91 wx.StaticText.__init__(self, parent, label=label)
93 font.SetWeight(wx.FONTWEIGHT_BOLD)
96 class LabelBox(wx.BoxSizer):
97 def __init__(self, parent, label, widget):
98 wx.BoxSizer.__init__(self, wx.HORIZONTAL)
99 self.Add(wx.StaticText(parent, label=' %s '%label), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
100 self.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT)
102 class IncrDecrButtons(wx.BoxSizer):
104 A horizontal box sizer with a increment and a decrement button.
106 def __init__(self, parent, on_incr, on_decr):
108 @param parent the parent window
109 @param on_incr the event handler for increment
110 @param on_decr the event handler for decrement
112 wx.BoxSizer.__init__(self, wx.HORIZONTAL)
113 self._incr_button = wx.Button(parent, label='+', style=wx.BU_EXACTFIT)
114 self._incr_button.Bind(wx.EVT_BUTTON, on_incr)
115 self.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL)
116 self._decr_button = wx.Button(parent, label=' - ', style=wx.BU_EXACTFIT)
117 self._decr_button.Bind(wx.EVT_BUTTON, on_decr)
118 self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL)
120 def Disable(self): self.Enable(False)
121 def Enable(self, enable=True):
123 self._incr_button.Enable()
124 self._decr_button.Enable()
126 self._incr_button.Disable()
127 self._decr_button.Disable()
129 class ToggleButtonController(wx.Button):
130 def __init__(self, parent, controller, control_key, true_label, false_label):
131 self._controller = controller
132 self._control_key = control_key
133 wx.Button.__init__(self, parent, style=wx.BU_EXACTFIT)
134 self.Bind(wx.EVT_BUTTON, self._evt_button)
135 controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label))
137 def _evt_button(self, e):
138 self._controller[self._control_key] = not self._controller[self._control_key]
140 class CheckBoxController(wx.CheckBox):
141 def __init__(self, parent, label, controller, control_key):
142 self._controller = controller
143 self._control_key = control_key
144 wx.CheckBox.__init__(self, parent, style=wx.CHK_2STATE, label=label)
145 self.Bind(wx.EVT_CHECKBOX, self._evt_checkbox)
146 controller.subscribe(control_key, lambda x: self.SetValue(bool(x)))
148 def _evt_checkbox(self, e):
149 self._controller[self._control_key] = bool(e.IsChecked())
151 from gnuradio import eng_notation
153 class TextBoxController(wx.TextCtrl):
154 def __init__(self, parent, controller, control_key, cast=float):
155 self._controller = controller
156 self._control_key = control_key
158 wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER)
159 self.Bind(wx.EVT_TEXT_ENTER, self._evt_enter)
160 controller.subscribe(control_key, lambda x: self.SetValue(eng_notation.num_to_str(x)))
162 def _evt_enter(self, e):
163 try: self._controller[self._control_key] = self._cast(eng_notation.str_to_num(self.GetValue()))
164 except: self._controller[self._control_key] = self._controller[self._control_key]
166 class LogSliderController(wx.BoxSizer):
168 Log slider controller with display label and slider.
169 Gives logarithmic scaling to slider operation.
171 def __init__(self, parent, prefix, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x):
172 self._prefix = prefix
173 self._min_exp = min_exp
174 self._max_exp = max_exp
175 self._controller = controller
176 self._control_key = control_key
177 self._formatter = formatter
178 wx.BoxSizer.__init__(self, wx.VERTICAL)
179 self._label = wx.StaticText(parent, label=prefix + formatter(1/3.0))
180 self.Add(self._label, 0, wx.EXPAND)
181 self._slider = wx.Slider(parent, minValue=0, maxValue=slider_steps, style=wx.SL_HORIZONTAL)
182 self.Add(self._slider, 0, wx.EXPAND)
183 self._slider.Bind(wx.EVT_SLIDER, self._on_slider_event)
184 controller.subscribe(control_key, self._on_controller_set)
186 def _get_slope(self):
187 return float(self._max_exp-self._min_exp)/self._slider.GetMax()
189 def _on_slider_event(self, e):
190 self._controller[self._control_key] = 10**(self._get_slope()*self._slider.GetValue() + self._min_exp)
192 def _on_controller_set(self, value):
193 self._label.SetLabel(self._prefix + self._formatter(value))
194 slider_value = (math.log10(value)-self._min_exp)/self._get_slope()
195 slider_value = min(max(self._slider.GetMin(), slider_value), self._slider.GetMax())
196 if abs(slider_value - self._slider.GetValue()) > 1:
197 self._slider.SetValue(slider_value)
199 def Disable(self): self.Enable(False)
200 def Enable(self, enable=True):
202 self._slider.Enable()
205 self._slider.Disable()
206 self._label.Disable()
208 class DropDownController(wx.Choice):
210 Drop down controller with label and chooser.
211 Srop down selection from a set of choices.
213 def __init__(self, parent, choices, controller, control_key, size=(-1, -1)):
215 @param parent the parent window
216 @param choices a list of tuples -> (label, value)
217 @param controller the prop val controller
218 @param control_key the prop key for this control
220 self._controller = controller
221 self._control_key = control_key
222 self._choices = choices
223 wx.Choice.__init__(self, parent, choices=[c[0] for c in choices], size=size)
224 self.Bind(wx.EVT_CHOICE, self._on_chooser_event)
225 controller.subscribe(control_key, self._on_controller_set)
227 def _on_chooser_event(self, e):
228 self._controller[self._control_key] = self._choices[self.GetSelection()][1]
230 def _on_controller_set(self, value):
231 #only set the chooser if the value is a possible choice
232 for i, choice in enumerate(self._choices):
233 if value == choice[1]: self.SetSelection(i)
235 ##################################################
237 ##################################################
243 Get the exponent of the number in base 10.
244 @param num the floating point number
245 @return the exponent as an integer
247 if num == 0: return 0
248 return int(math.floor(math.log10(abs(num))))
250 def get_clean_num(num):
252 Get the closest clean number match to num with bases 1, 2, 5.
253 @param num the number
254 @return the closest number
256 if num == 0: return 0
257 sign = num > 0 and 1 or -1
259 nums = numpy.array((1, 2, 5, 10))*(10**exp)
260 return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))]
262 def get_clean_incr(num):
264 Get the next higher clean number with bases 1, 2, 5.
265 @param num the number
266 @return the next higher number
268 num = get_clean_num(num)
270 coeff = int(round(num/10**exp))
280 def get_clean_decr(num):
282 Get the next lower clean number with bases 1, 2, 5.
283 @param num the number
284 @return the next lower number
286 num = get_clean_num(num)
288 coeff = int(round(num/10**exp))
298 def get_min_max(samples):
300 Get the minimum and maximum bounds for an array of samples.
301 @param samples the array of real values
302 @return a tuple of min, max
305 mean = numpy.average(samples)
306 rms = numpy.max([scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5), .1])