]> git.gag.com Git - debian/gnuradio/blob - gr-wxgui/src/python/common.py
e7d08891d36b9e1224bd6d9fc0f100d8f05c4c53
[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 # Custom Data Event
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)
40                 self.data = data
41
42 ##################################################
43 # Input Watcher Thread
44 ##################################################
45 class input_watcher(threading.Thread):
46         """
47         Input watcher thread runs forever.
48         Read messages from the message queue.
49         Forward messages to the message handler.
50         """
51         def __init__ (self, msgq, handle_msg):
52                 threading.Thread.__init__(self)
53                 self.setDaemon(1)
54                 self.msgq = msgq
55                 self._handle_msg = handle_msg
56                 self.keep_running = True
57                 self.start()
58
59         def run(self):
60                 while self.keep_running: self._handle_msg(self.msgq.delete_head().to_string())
61
62 ##################################################
63 # WX Shared Classes
64 ##################################################
65 class LabelText(wx.StaticText):
66         """
67         Label text to give the wx plots a uniform look.
68         Get the default label text and set the font bold.
69         """
70         def __init__(self, parent, label):
71                 wx.StaticText.__init__(self, parent, -1, label)
72                 font = self.GetFont()
73                 font.SetWeight(wx.FONTWEIGHT_BOLD)
74                 self.SetFont(font)
75
76 class IncrDecrButtons(wx.BoxSizer):
77         """
78         A horizontal box sizer with a increment and a decrement button.
79         """
80         def __init__(self, parent, on_incr, on_decr):
81                 """
82                 @param parent the parent window
83                 @param on_incr the event handler for increment
84                 @param on_decr the event handler for decrement
85                 """
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)
93
94         def Disable(self, disable=True): self.Enable(not disable)
95         def Enable(self, enable=True):
96                 if enable:
97                         self._incr_button.Enable()
98                         self._decr_button.Enable()
99                 else:
100                         self._incr_button.Disable()
101                         self._decr_button.Disable()
102
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))
110
111         def _evt_button(self, e):
112                 self._controller[self._control_key] = not self._controller[self._control_key]
113
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)))
121
122         def _evt_checkbox(self, e):
123                 self._controller[self._control_key] = bool(e.IsChecked())
124
125 class LogSliderController(wx.BoxSizer):
126         """
127         Log slider controller with display label and slider.
128         Gives logarithmic scaling to slider operation.
129         """
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)
147
148         def Disable(self, disable=True): self.Enable(not disable)
149         def Enable(self, enable=True):
150                 if enable:
151                         self._slider.Enable()
152                         self._label.Enable()
153                 else:
154                         self._slider.Disable()
155                         self._label.Disable()
156
157 class DropDownController(wx.BoxSizer):
158         """
159         Drop down controller with label and chooser.
160         Srop down selection from a set of choices.
161         """
162         def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)):
163                 """
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
169                 """
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)
183
184         def Disable(self, disable=True): self.Enable(not disable)
185         def Enable(self, enable=True):
186                 if enable:
187                         self._chooser.Enable()
188                         self._label.Enable()
189                 else:
190                         self._chooser.Disable()
191                         self._label.Disable()
192
193 ##################################################
194 # Shared Functions
195 ##################################################
196 def get_exp(num):
197         """
198         Get the exponent of the number in base 10.
199         @param num the floating point number
200         @return the exponent as an integer
201         """
202         if num == 0: return 0
203         return int(math.floor(math.log10(abs(num))))
204
205 def get_clean_num(num):
206         """
207         Get the closest clean number match to num with bases 1, 2, 5.
208         @param num the number
209         @return the closest number
210         """
211         if num == 0: return 0
212         if num > 0: sign = 1
213         else: sign = -1
214         exp = get_exp(num)
215         nums = numpy.array((1, 2, 5, 10))*(10**exp)
216         return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))]
217
218 def get_clean_incr(num):
219         """
220         Get the next higher clean number with bases 1, 2, 5.
221         @param num the number
222         @return the next higher number
223         """
224         num = get_clean_num(num)
225         exp = get_exp(num)
226         coeff = int(round(num/10**exp))
227         return {
228                 -5: -2,
229                 -2: -1,
230                 -1: -.5,
231                 1: 2,
232                 2: 5,
233                 5: 10,
234         }[coeff]*(10**exp)
235
236 def get_clean_decr(num):
237         """
238         Get the next lower clean number with bases 1, 2, 5.
239         @param num the number
240         @return the next lower number
241         """
242         num = get_clean_num(num)
243         exp = get_exp(num)
244         coeff = int(round(num/10**exp))
245         return {
246                 -5: -10,
247                 -2: -5,
248                 -1: -2,
249                 1: .5,
250                 2: 1,
251                 5: 2,
252         }[coeff]*(10**exp)
253
254 def get_min_max(samples):
255         """
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
259         """
260         scale_factor = 3
261         mean = numpy.average(samples)
262         rms = numpy.max([scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5), .1])
263         min = mean - rms
264         max = mean + rms
265         return min, max
266
267 def get_si_components(num):
268         """
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
274         """
275         exp = get_exp(num)
276         exp -= exp%3
277         exp = min(max(exp, -24), 24) #bounds on SI table below
278         prefix = {
279                 24: 'Y', 21: 'Z',
280                 18: 'E', 15: 'P',
281                 12: 'T', 9: 'G',
282                 6: 'M', 3: 'K',
283                 0: '',
284                 -3: 'm', -6: 'u',
285                 -9: 'n', -12: 'p',
286                 -15: 'f', -18: 'a',
287                 -21: 'z', -24: 'y',
288         }[exp]
289         coeff = num/10**exp
290         return coeff, exp, prefix
291
292 def label_format(num):
293         """
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
302         """
303         coeff, exp, prefix = get_si_components(num)
304         if -3 <= exp < 3: return '%g'%num
305         return '%se%d'%('%.3g'%coeff, exp)
306
307 if __name__ == '__main__':
308         import random
309         for i in range(-25, 25):
310                 num = random.random()*10**i
311                 print num, ':', get_si_components(num)