Merged r10463:10658 from jblum/gui_guts into trunk. Trunk passes distcheck.
[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 #A macro to apply an index to a key
23 index_key = lambda key, i: "%s_%d"%(key, i+1)
24
25 def _register_access_method(destination, controller, key):
26         """
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.
30         """
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) 
35
36 def register_access_methods(destination, controller):
37         """
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
41         """
42         for key in controller.keys(): _register_access_method(destination, controller, key)
43
44 ##################################################
45 # Input Watcher Thread
46 ##################################################
47 import threading
48
49 class input_watcher(threading.Thread):
50         """
51         Input watcher thread runs forever.
52         Read messages from the message queue.
53         Forward messages to the message handler.
54         """
55         def __init__ (self, msgq, controller, msg_key, arg1_key='', arg2_key=''):
56                 threading.Thread.__init__(self)
57                 self.setDaemon(1)
58                 self.msgq = msgq
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
64                 self.start()
65
66         def run(self):
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()
72
73 ##################################################
74 # WX Shared Classes
75 ##################################################
76 import math
77 import wx
78
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)
83                 self.data = data
84
85 class LabelText(wx.StaticText):
86         """
87         Label text to give the wx plots a uniform look.
88         Get the default label text and set the font bold.
89         """
90         def __init__(self, parent, label):
91                 wx.StaticText.__init__(self, parent, label=label)
92                 font = self.GetFont()
93                 font.SetWeight(wx.FONTWEIGHT_BOLD)
94                 self.SetFont(font)
95
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)
101
102 class IncrDecrButtons(wx.BoxSizer):
103         """
104         A horizontal box sizer with a increment and a decrement button.
105         """
106         def __init__(self, parent, on_incr, on_decr):
107                 """
108                 @param parent the parent window
109                 @param on_incr the event handler for increment
110                 @param on_decr the event handler for decrement
111                 """
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)
119
120         def Disable(self): self.Enable(False)
121         def Enable(self, enable=True):
122                 if enable:
123                         self._incr_button.Enable()
124                         self._decr_button.Enable()
125                 else:
126                         self._incr_button.Disable()
127                         self._decr_button.Disable()
128
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))
136
137         def _evt_button(self, e):
138                 self._controller[self._control_key] = not self._controller[self._control_key]
139
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)))
147
148         def _evt_checkbox(self, e):
149                 self._controller[self._control_key] = bool(e.IsChecked())
150
151 from gnuradio import eng_notation
152
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
157                 self._cast = cast
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)))
161
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]
165
166 class LogSliderController(wx.BoxSizer):
167         """
168         Log slider controller with display label and slider.
169         Gives logarithmic scaling to slider operation.
170         """
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)
185
186         def _get_slope(self):
187                 return float(self._max_exp-self._min_exp)/self._slider.GetMax()
188
189         def _on_slider_event(self, e):
190                 self._controller[self._control_key] = 10**(self._get_slope()*self._slider.GetValue() + self._min_exp)
191
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)
198
199         def Disable(self): self.Enable(False)
200         def Enable(self, enable=True):
201                 if enable:
202                         self._slider.Enable()
203                         self._label.Enable()
204                 else:
205                         self._slider.Disable()
206                         self._label.Disable()
207
208 class DropDownController(wx.Choice):
209         """
210         Drop down controller with label and chooser.
211         Srop down selection from a set of choices.
212         """
213         def __init__(self, parent, choices, controller, control_key, size=(-1, -1)):
214                 """
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
219                 """
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)
226
227         def _on_chooser_event(self, e):
228                 self._controller[self._control_key] = self._choices[self.GetSelection()][1]
229
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)
234
235 ##################################################
236 # Shared Functions
237 ##################################################
238 import numpy
239 import math
240
241 def get_exp(num):
242         """
243         Get the exponent of the number in base 10.
244         @param num the floating point number
245         @return the exponent as an integer
246         """
247         if num == 0: return 0
248         return int(math.floor(math.log10(abs(num))))
249
250 def get_clean_num(num):
251         """
252         Get the closest clean number match to num with bases 1, 2, 5.
253         @param num the number
254         @return the closest number
255         """
256         if num == 0: return 0
257         sign = num > 0 and 1 or -1
258         exp = get_exp(num)
259         nums = numpy.array((1, 2, 5, 10))*(10**exp)
260         return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))]
261
262 def get_clean_incr(num):
263         """
264         Get the next higher clean number with bases 1, 2, 5.
265         @param num the number
266         @return the next higher number
267         """
268         num = get_clean_num(num)
269         exp = get_exp(num)
270         coeff = int(round(num/10**exp))
271         return {
272                 -5: -2,
273                 -2: -1,
274                 -1: -.5,
275                 1: 2,
276                 2: 5,
277                 5: 10,
278         }[coeff]*(10**exp)
279
280 def get_clean_decr(num):
281         """
282         Get the next lower clean number with bases 1, 2, 5.
283         @param num the number
284         @return the next lower number
285         """
286         num = get_clean_num(num)
287         exp = get_exp(num)
288         coeff = int(round(num/10**exp))
289         return {
290                 -5: -10,
291                 -2: -5,
292                 -1: -2,
293                 1: .5,
294                 2: 1,
295                 5: 2,
296         }[coeff]*(10**exp)
297
298 def get_min_max(samples):
299         """
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
303         """
304         scale_factor = 3
305         mean = numpy.average(samples)
306         rms = numpy.max([scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5), .1])
307         min = mean - rms
308         max = mean + rms
309         return min, max