]> git.gag.com Git - debian/gnuradio/blob - gr-wxgui/src/python/common.py
4297648825213998a0441d2f42648980b3fdc71a
[debian/gnuradio] / gr-wxgui / src / python / common.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 import threading
23 import numpy
24 import math
25 import wx
26
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)
32
33 ##################################################
34 # Input Watcher Thread
35 ##################################################
36 class input_watcher(threading.Thread):
37         """
38         Input watcher thread runs forever.
39         Read messages from the message queue.
40         Forward messages to the message handler.
41         """
42         def __init__ (self, msgq, handle_msg):
43                 threading.Thread.__init__(self)
44                 self.setDaemon(1)
45                 self.msgq = msgq
46                 self._handle_msg = handle_msg
47                 self.keep_running = True
48                 self.start()
49
50         def run(self):
51                 while self.keep_running: self._handle_msg(self.msgq.delete_head().to_string())
52
53 ##################################################
54 # WX Shared Classes
55 ##################################################
56 class LabelText(wx.StaticText):
57         """
58         Label text to give the wx plots a uniform look.
59         Get the default label text and set the font bold.
60         """
61         def __init__(self, parent, label):
62                 wx.StaticText.__init__(self, parent, -1, label)
63                 font = self.GetFont()
64                 font.SetWeight(wx.FONTWEIGHT_BOLD)
65                 self.SetFont(font)
66
67 class IncrDecrButtons(wx.BoxSizer):
68         """
69         A horizontal box sizer with a increment and a decrement button.
70         """
71         def __init__(self, parent, on_incr, on_decr):
72                 """
73                 @param parent the parent window
74                 @param on_incr the event handler for increment
75                 @param on_decr the event handler for decrement
76                 """
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)
84
85         def Disable(self, disable=True): self.Enable(not disable)
86         def Enable(self, enable=True):
87                 if enable:
88                         self._incr_button.Enable()
89                         self._decr_button.Enable()
90                 else:
91                         self._incr_button.Disable()
92                         self._decr_button.Disable()
93
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))
101
102         def _evt_button(self, e):
103                 self._controller[self._control_key] = not self._controller[self._control_key]
104
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)))
112
113         def _evt_checkbox(self, e):
114                 self._controller[self._control_key] = bool(e.IsChecked())
115
116 class LogSliderController(wx.BoxSizer):
117         """
118         Log slider controller with display label and slider.
119         Gives logarithmic scaling to slider operation.
120         """
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)
138
139         def Disable(self, disable=True): self.Enable(not disable)
140         def Enable(self, enable=True):
141                 if enable:
142                         self._slider.Enable()
143                         self._label.Enable()
144                 else:
145                         self._slider.Disable()
146                         self._label.Disable()
147
148 class DropDownController(wx.BoxSizer):
149         """
150         Drop down controller with label and chooser.
151         Srop down selection from a set of choices.
152         """
153         def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)):
154                 """
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
160                 """
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)
174
175         def Disable(self, disable=True): self.Enable(not disable)
176         def Enable(self, enable=True):
177                 if enable:
178                         self._chooser.Enable()
179                         self._label.Enable()
180                 else:
181                         self._chooser.Disable()
182                         self._label.Disable()
183
184 ##################################################
185 # Shared Functions
186 ##################################################
187 def get_exp(num):
188         """
189         Get the exponent of the number in base 10.
190         @param num the floating point number
191         @return the exponent as an integer
192         """
193         if num == 0: return 0
194         return int(math.floor(math.log10(abs(num))))
195
196 def get_clean_num(num):
197         """
198         Get the closest clean number match to num with bases 1, 2, 5.
199         @param num the number
200         @return the closest number
201         """
202         if num == 0: return 0
203         if num > 0: sign = 1
204         else: sign = -1
205         exp = get_exp(num)
206         nums = numpy.array((1, 2, 5, 10))*(10**exp)
207         return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))]
208
209 def get_clean_incr(num):
210         """
211         Get the next higher clean number with bases 1, 2, 5.
212         @param num the number
213         @return the next higher number
214         """
215         num = get_clean_num(num)
216         exp = get_exp(num)
217         coeff = int(round(num/10**exp))
218         return {
219                 -5: -2,
220                 -2: -1,
221                 -1: -.5,
222                 1: 2,
223                 2: 5,
224                 5: 10,
225         }[coeff]*(10**exp)
226
227 def get_clean_decr(num):
228         """
229         Get the next lower clean number with bases 1, 2, 5.
230         @param num the number
231         @return the next lower number
232         """
233         num = get_clean_num(num)
234         exp = get_exp(num)
235         coeff = int(round(num/10**exp))
236         return {
237                 -5: -10,
238                 -2: -5,
239                 -1: -2,
240                 1: .5,
241                 2: 1,
242                 5: 2,
243         }[coeff]*(10**exp)
244
245 def get_min_max(samples):
246         """
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
250         """
251         scale_factor = 3
252         mean = numpy.average(samples)
253         rms = scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5)
254         min = mean - rms
255         max = mean + rms
256         return min, max
257
258 def get_si_components(num):
259         """
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
265         """
266         exp = get_exp(num)
267         exp -= exp%3
268         exp = min(max(exp, -24), 24) #bounds on SI table below
269         prefix = {
270                 24: 'Y', 21: 'Z',
271                 18: 'E', 15: 'P',
272                 12: 'T', 9: 'G',
273                 6: 'M', 3: 'K',
274                 0: '',
275                 -3: 'm', -6: 'u',
276                 -9: 'n', -12: 'p',
277                 -15: 'f', -18: 'a',
278                 -21: 'z', -24: 'y',
279         }[exp]
280         coeff = num/10**exp
281         return coeff, exp, prefix
282
283 def label_format(num):
284         """
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
293         """
294         coeff, exp, prefix = get_si_components(num)
295         if -3 <= exp < 3: return '%g'%num
296         return '%se%d'%('%.3g'%coeff, exp)
297
298 if __name__ == '__main__':
299         import random
300         for i in range(-25, 25):
301                 num = random.random()*10**i
302                 print num, ':', get_si_components(num)