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 ##################################################
35 ##################################################
36 EVT_DATA = wx.PyEventBinder(wx.NewEventType())
37 class DataEvent(wx.PyEvent):
38 def __init__(self, data):
39 wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId)
42 ##################################################
43 # Input Watcher Thread
44 ##################################################
45 class input_watcher(threading.Thread):
47 Input watcher thread runs forever.
48 Read messages from the message queue.
49 Forward messages to the message handler.
51 def __init__ (self, msgq, handle_msg):
52 threading.Thread.__init__(self)
55 self._handle_msg = handle_msg
56 self.keep_running = True
60 while self.keep_running: self._handle_msg(self.msgq.delete_head().to_string())
62 ##################################################
64 ##################################################
65 class LabelText(wx.StaticText):
67 Label text to give the wx plots a uniform look.
68 Get the default label text and set the font bold.
70 def __init__(self, parent, label):
71 wx.StaticText.__init__(self, parent, -1, label)
73 font.SetWeight(wx.FONTWEIGHT_BOLD)
76 class IncrDecrButtons(wx.BoxSizer):
78 A horizontal box sizer with a increment and a decrement button.
80 def __init__(self, parent, on_incr, on_decr):
82 @param parent the parent window
83 @param on_incr the event handler for increment
84 @param on_decr the event handler for decrement
86 wx.BoxSizer.__init__(self, wx.HORIZONTAL)
87 self._incr_button = wx.Button(parent, -1, '+', style=wx.BU_EXACTFIT)
88 self._incr_button.Bind(wx.EVT_BUTTON, on_incr)
89 self.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL)
90 self._decr_button = wx.Button(parent, -1, ' - ', style=wx.BU_EXACTFIT)
91 self._decr_button.Bind(wx.EVT_BUTTON, on_decr)
92 self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL)
94 def Disable(self, disable=True): self.Enable(not disable)
95 def Enable(self, enable=True):
97 self._incr_button.Enable()
98 self._decr_button.Enable()
100 self._incr_button.Disable()
101 self._decr_button.Disable()
103 class ToggleButtonController(wx.Button):
104 def __init__(self, parent, controller, control_key, true_label, false_label):
105 self._controller = controller
106 self._control_key = control_key
107 wx.Button.__init__(self, parent, -1, '', style=wx.BU_EXACTFIT)
108 self.Bind(wx.EVT_BUTTON, self._evt_button)
109 controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label))
111 def _evt_button(self, e):
112 self._controller[self._control_key] = not self._controller[self._control_key]
114 class CheckBoxController(wx.CheckBox):
115 def __init__(self, parent, label, controller, control_key):
116 self._controller = controller
117 self._control_key = control_key
118 wx.CheckBox.__init__(self, parent, style=wx.CHK_2STATE, label=label)
119 self.Bind(wx.EVT_CHECKBOX, self._evt_checkbox)
120 controller.subscribe(control_key, lambda x: self.SetValue(bool(x)))
122 def _evt_checkbox(self, e):
123 self._controller[self._control_key] = bool(e.IsChecked())
125 class LogSliderController(wx.BoxSizer):
127 Log slider controller with display label and slider.
128 Gives logarithmic scaling to slider operation.
130 def __init__(self, parent, label, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x):
131 wx.BoxSizer.__init__(self, wx.VERTICAL)
132 self._label = wx.StaticText(parent, -1, label + formatter(1/3.0))
133 self.Add(self._label, 0, wx.EXPAND)
134 self._slider = wx.Slider(parent, -1, 0, 0, slider_steps, style=wx.SL_HORIZONTAL)
135 self.Add(self._slider, 0, wx.EXPAND)
136 def _on_slider_event(event):
137 controller[control_key] = \
138 10**(float(max_exp-min_exp)*self._slider.GetValue()/slider_steps + min_exp)
139 self._slider.Bind(wx.EVT_SLIDER, _on_slider_event)
140 def _on_controller_set(value):
141 self._label.SetLabel(label + formatter(value))
142 slider_value = slider_steps*(math.log10(value)-min_exp)/(max_exp-min_exp)
143 slider_value = min(max(0, slider_value), slider_steps)
144 if abs(slider_value - self._slider.GetValue()) > 1:
145 self._slider.SetValue(slider_value)
146 controller.subscribe(control_key, _on_controller_set)
148 def Disable(self, disable=True): self.Enable(not disable)
149 def Enable(self, enable=True):
151 self._slider.Enable()
154 self._slider.Disable()
155 self._label.Disable()
157 class DropDownController(wx.BoxSizer):
159 Drop down controller with label and chooser.
160 Srop down selection from a set of choices.
162 def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)):
164 @param parent the parent window
165 @param label the label for the drop down
166 @param choices a list of tuples -> (label, value)
167 @param controller the prop val controller
168 @param control_key the prop key for this control
170 wx.BoxSizer.__init__(self, wx.HORIZONTAL)
171 self._label = wx.StaticText(parent, -1, ' %s '%label)
172 self.Add(self._label, 1, wx.ALIGN_CENTER_VERTICAL)
173 self._chooser = wx.Choice(parent, -1, choices=[c[0] for c in choices], size=size)
174 def _on_chooser_event(event):
175 controller[control_key] = choices[self._chooser.GetSelection()][1]
176 self._chooser.Bind(wx.EVT_CHOICE, _on_chooser_event)
177 self.Add(self._chooser, 0, wx.ALIGN_CENTER_VERTICAL)
178 def _on_controller_set(value):
179 #only set the chooser if the value is a possible choice
180 for i, choice in enumerate(choices):
181 if value == choice[1]: self._chooser.SetSelection(i)
182 controller.subscribe(control_key, _on_controller_set)
184 def Disable(self, disable=True): self.Enable(not disable)
185 def Enable(self, enable=True):
187 self._chooser.Enable()
190 self._chooser.Disable()
191 self._label.Disable()
193 ##################################################
195 ##################################################
198 Get the exponent of the number in base 10.
199 @param num the floating point number
200 @return the exponent as an integer
202 if num == 0: return 0
203 return int(math.floor(math.log10(abs(num))))
205 def get_clean_num(num):
207 Get the closest clean number match to num with bases 1, 2, 5.
208 @param num the number
209 @return the closest number
211 if num == 0: return 0
215 nums = numpy.array((1, 2, 5, 10))*(10**exp)
216 return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))]
218 def get_clean_incr(num):
220 Get the next higher clean number with bases 1, 2, 5.
221 @param num the number
222 @return the next higher number
224 num = get_clean_num(num)
226 coeff = int(round(num/10**exp))
236 def get_clean_decr(num):
238 Get the next lower clean number with bases 1, 2, 5.
239 @param num the number
240 @return the next lower number
242 num = get_clean_num(num)
244 coeff = int(round(num/10**exp))
254 def get_min_max(samples):
256 Get the minimum and maximum bounds for an array of samples.
257 @param samples the array of real values
258 @return a tuple of min, max
261 mean = numpy.average(samples)
262 rms = numpy.max([scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5), .1])
267 def get_si_components(num):
269 Get the SI units for the number.
270 Extract the coeff and exponent of the number.
271 The exponent will be a multiple of 3.
272 @param num the floating point number
273 @return the tuple coeff, exp, prefix
277 exp = min(max(exp, -24), 24) #bounds on SI table below
290 return coeff, exp, prefix
292 def label_format(num):
294 Format a floating point number into a presentable string.
295 If the number has an small enough exponent, use regular decimal.
296 Otherwise, format the number with floating point notation.
297 Exponents are normalized to multiples of 3.
298 In the case where the exponent was found to be -3,
299 it is best to display this as a regular decimal, with a 0 to the left.
300 @param num the number to format
301 @return a label string
303 coeff, exp, prefix = get_si_components(num)
304 if -3 <= exp < 3: return '%g'%num
305 return '%se%d'%('%.3g'%coeff, exp)
307 if __name__ == '__main__':
309 for i in range(-25, 25):
310 num = random.random()*10**i
311 print num, ':', get_si_components(num)