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.
27 class prop_setter(object):
28 def _register_set_prop(self, controller, control_key, *args):
29 def set_method(value): controller[control_key] = value
30 if args: set_method(args[0])
31 setattr(self, 'set_%s'%control_key, set_method)
33 ##################################################
34 # Input Watcher Thread
35 ##################################################
36 class input_watcher(threading.Thread):
38 Input watcher thread runs forever.
39 Read messages from the message queue.
40 Forward messages to the message handler.
42 def __init__ (self, msgq, handle_msg):
43 threading.Thread.__init__(self)
46 self._handle_msg = handle_msg
47 self.keep_running = True
51 while self.keep_running: self._handle_msg(self.msgq.delete_head().to_string())
53 ##################################################
55 ##################################################
56 class LabelText(wx.StaticText):
58 Label text to give the wx plots a uniform look.
59 Get the default label text and set the font bold.
61 def __init__(self, parent, label):
62 wx.StaticText.__init__(self, parent, -1, label)
64 font.SetWeight(wx.FONTWEIGHT_BOLD)
67 class IncrDecrButtons(wx.BoxSizer):
69 A horizontal box sizer with a increment and a decrement button.
71 def __init__(self, parent, on_incr, on_decr):
73 @param parent the parent window
74 @param on_incr the event handler for increment
75 @param on_decr the event handler for decrement
77 wx.BoxSizer.__init__(self, wx.HORIZONTAL)
78 self._incr_button = wx.Button(parent, -1, '+', style=wx.BU_EXACTFIT)
79 self._incr_button.Bind(wx.EVT_BUTTON, on_incr)
80 self.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL)
81 self._decr_button = wx.Button(parent, -1, ' - ', style=wx.BU_EXACTFIT)
82 self._decr_button.Bind(wx.EVT_BUTTON, on_decr)
83 self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL)
85 def Disable(self, disable=True): self.Enable(not disable)
86 def Enable(self, enable=True):
88 self._incr_button.Enable()
89 self._decr_button.Enable()
91 self._incr_button.Disable()
92 self._decr_button.Disable()
94 class ToggleButtonController(wx.Button):
95 def __init__(self, parent, controller, control_key, true_label, false_label):
96 self._controller = controller
97 self._control_key = control_key
98 wx.Button.__init__(self, parent, -1, '', style=wx.BU_EXACTFIT)
99 self.Bind(wx.EVT_BUTTON, self._evt_button)
100 controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label))
102 def _evt_button(self, e):
103 self._controller[self._control_key] = not self._controller[self._control_key]
105 class CheckBoxController(wx.CheckBox):
106 def __init__(self, parent, label, controller, control_key):
107 self._controller = controller
108 self._control_key = control_key
109 wx.CheckBox.__init__(self, parent, style=wx.CHK_2STATE, label=label)
110 self.Bind(wx.EVT_CHECKBOX, self._evt_checkbox)
111 controller.subscribe(control_key, lambda x: self.SetValue(bool(x)))
113 def _evt_checkbox(self, e):
114 self._controller[self._control_key] = bool(e.IsChecked())
116 class LogSliderController(wx.BoxSizer):
118 Log slider controller with display label and slider.
119 Gives logarithmic scaling to slider operation.
121 def __init__(self, parent, label, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x):
122 wx.BoxSizer.__init__(self, wx.VERTICAL)
123 self._label = wx.StaticText(parent, -1, label + formatter(1/3.0))
124 self.Add(self._label, 0, wx.EXPAND)
125 self._slider = wx.Slider(parent, -1, 0, 0, slider_steps, style=wx.SL_HORIZONTAL)
126 self.Add(self._slider, 0, wx.EXPAND)
127 def _on_slider_event(event):
128 controller[control_key] = \
129 10**(float(max_exp-min_exp)*self._slider.GetValue()/slider_steps + min_exp)
130 self._slider.Bind(wx.EVT_SLIDER, _on_slider_event)
131 def _on_controller_set(value):
132 self._label.SetLabel(label + formatter(value))
133 slider_value = slider_steps*(math.log10(value)-min_exp)/(max_exp-min_exp)
134 slider_value = min(max(0, slider_value), slider_steps)
135 if abs(slider_value - self._slider.GetValue()) > 1:
136 self._slider.SetValue(slider_value)
137 controller.subscribe(control_key, _on_controller_set)
139 def Disable(self, disable=True): self.Enable(not disable)
140 def Enable(self, enable=True):
142 self._slider.Enable()
145 self._slider.Disable()
146 self._label.Disable()
148 class DropDownController(wx.BoxSizer):
150 Drop down controller with label and chooser.
151 Srop down selection from a set of choices.
153 def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)):
155 @param parent the parent window
156 @param label the label for the drop down
157 @param choices a list of tuples -> (label, value)
158 @param controller the prop val controller
159 @param control_key the prop key for this control
161 wx.BoxSizer.__init__(self, wx.HORIZONTAL)
162 self._label = wx.StaticText(parent, -1, ' %s '%label)
163 self.Add(self._label, 1, wx.ALIGN_CENTER_VERTICAL)
164 self._chooser = wx.Choice(parent, -1, choices=[c[0] for c in choices], size=size)
165 def _on_chooser_event(event):
166 controller[control_key] = choices[self._chooser.GetSelection()][1]
167 self._chooser.Bind(wx.EVT_CHOICE, _on_chooser_event)
168 self.Add(self._chooser, 0, wx.ALIGN_CENTER_VERTICAL)
169 def _on_controller_set(value):
170 #only set the chooser if the value is a possible choice
171 for i, choice in enumerate(choices):
172 if value == choice[1]: self._chooser.SetSelection(i)
173 controller.subscribe(control_key, _on_controller_set)
175 def Disable(self, disable=True): self.Enable(not disable)
176 def Enable(self, enable=True):
178 self._chooser.Enable()
181 self._chooser.Disable()
182 self._label.Disable()
184 ##################################################
186 ##################################################
189 Get the exponent of the number in base 10.
190 @param num the floating point number
191 @return the exponent as an integer
193 if num == 0: return 0
194 return int(math.floor(math.log10(abs(num))))
196 def get_clean_num(num):
198 Get the closest clean number match to num with bases 1, 2, 5.
199 @param num the number
200 @return the closest number
202 if num == 0: return 0
206 nums = numpy.array((1, 2, 5, 10))*(10**exp)
207 return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))]
209 def get_clean_incr(num):
211 Get the next higher clean number with bases 1, 2, 5.
212 @param num the number
213 @return the next higher number
215 num = get_clean_num(num)
217 coeff = int(round(num/10**exp))
227 def get_clean_decr(num):
229 Get the next lower clean number with bases 1, 2, 5.
230 @param num the number
231 @return the next lower number
233 num = get_clean_num(num)
235 coeff = int(round(num/10**exp))
245 def get_min_max(samples):
247 Get the minimum and maximum bounds for an array of samples.
248 @param samples the array of real values
249 @return a tuple of min, max
252 mean = numpy.average(samples)
253 rms = scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5)
258 def get_si_components(num):
260 Get the SI units for the number.
261 Extract the coeff and exponent of the number.
262 The exponent will be a multiple of 3.
263 @param num the floating point number
264 @return the tuple coeff, exp, prefix
268 exp = min(max(exp, -24), 24) #bounds on SI table below
281 return coeff, exp, prefix
283 def label_format(num):
285 Format a floating point number into a presentable string.
286 If the number has an small enough exponent, use regular decimal.
287 Otherwise, format the number with floating point notation.
288 Exponents are normalized to multiples of 3.
289 In the case where the exponent was found to be -3,
290 it is best to display this as a regular decimal, with a 0 to the left.
291 @param num the number to format
292 @return a label string
294 coeff, exp, prefix = get_si_components(num)
295 if -3 <= exp < 3: return '%g'%num
296 return '%se%d'%('%.3g'%coeff, exp)
298 if __name__ == '__main__':
300 for i in range(-25, 25):
301 num = random.random()*10**i
302 print num, ':', get_si_components(num)