Merged r10463:10658 from jblum/gui_guts into trunk. Trunk passes distcheck.
[debian/gnuradio] / grc / src / grc_gnuradio / wxgui / callback_controls.py
1 # Copyright 2008 Free Software Foundation, Inc.
2 #
3 # This file is part of GNU Radio
4 #
5 # GNU Radio is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3, or (at your option)
8 # any later version.
9 #
10 # GNU Radio is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with GNU Radio; see the file COPYING.  If not, write to
17 # the Free Software Foundation, Inc., 51 Franklin Street,
18 # Boston, MA 02110-1301, USA.
19 #
20
21 import wx
22 import sys
23 from gnuradio import eng_notation
24
25 class LabelText(wx.StaticText):
26         """Label text class for uniform labels among all controls."""
27
28         def __init__(self, window, label):
29                 wx.StaticText.__init__(self, window, label=str(label))
30                 font = self.GetFont()
31                 font.SetWeight(wx.FONTWEIGHT_BOLD)
32                 self.SetFont(font)
33
34 class _control_base(wx.BoxSizer):
35         """Control base class"""
36
37         def __init__(self, window, callback):
38                 self.window = window
39                 self.callback = callback
40                 wx.BoxSizer.__init__(self, wx.VERTICAL)
41
42         def get_window(self): return self.window
43
44         def call(self): return self.callback(self.get_value())
45
46         def get_value(self): raise NotImplementedError
47
48         def set_value(self): raise NotImplementedError
49
50 class _chooser_control_base(_control_base):
51         """House a drop down or radio buttons for variable control."""
52
53         def __init__(self, window, callback, label='Label', index=0, choices=[0], labels=[]):
54                 """
55                 Chooser contructor.
56                 Create the slider, text box, and label.
57                 @param window the wx parent window
58                 @param callback call the callback on changes
59                 @param label the label title
60                 @param index the default choice index
61                 @param choices a list of choices
62                 @param labels the choice labels or empty list
63                 """
64                 #initialize
65                 _control_base.__init__(self, window, callback)
66                 label_text = LabelText(self.get_window(), label)
67                 self.Add(label_text, 0, wx.ALIGN_CENTER)
68                 self.index = index
69                 self.choices = choices
70                 self.labels = map(str, labels or choices)
71                 self._init()
72
73         def _handle_changed(self, event=None):
74                 """
75                 A change is detected. Call the callback.
76                 """
77                 try: self.call()
78                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle changed.\n', e
79
80         def get_value(self):
81                 """
82                 Update the chooser.
83                 @return one of the possible choices
84                 """
85                 self._update()
86                 return self.choices[self.index]
87
88 ##############################################################################################
89 # Button Control
90 ##############################################################################################
91 class button_control(_chooser_control_base):
92         """House a button for variable control."""
93
94         def _init(self):
95                 self.button = wx.Button(self.get_window(), label=self.labels[self.index])
96                 self.button.Bind(wx.EVT_BUTTON, self._handle_changed)
97                 self.Add(self.button, 0, wx.ALIGN_CENTER)
98
99         def _update(self):
100                 self.index = (self.index + 1)%len(self.choices) #circularly increment index
101                 self.button.SetLabel(self.labels[self.index])
102
103 ##############################################################################################
104 # Drop Down Control
105 ##############################################################################################
106 class drop_down_control(_chooser_control_base):
107         """House a drop down for variable control."""
108
109         def _init(self):
110                 self.drop_down = wx.Choice(self.get_window(), choices=self.labels)
111                 self.Add(self.drop_down, 0, wx.ALIGN_CENTER)
112                 self.drop_down.Bind(wx.EVT_CHOICE, self._handle_changed)
113                 self.drop_down.SetSelection(self.index)
114
115         def _update(self):
116                 self.index = self.drop_down.GetSelection()
117
118 ##############################################################################################
119 # Radio Buttons Control
120 ##############################################################################################
121 class _radio_buttons_control_base(_chooser_control_base):
122         """House radio buttons for variable control."""
123
124         def _init(self):
125                 #create box for radio buttons
126                 radio_box = wx.BoxSizer(self.radio_box_orientation)
127                 panel = wx.Panel(self.get_window())
128                 panel.SetSizer(radio_box)
129                 self.Add(panel, 0, wx.ALIGN_CENTER)
130                 #create radio buttons
131                 self.radio_buttons = list()
132                 for label in self.labels:
133                         radio_button = wx.RadioButton(panel, label=label)
134                         radio_button.SetValue(False)
135                         self.radio_buttons.append(radio_button)
136                         radio_box.Add(radio_button, 0, self.radio_button_align)
137                         radio_button.Bind(wx.EVT_RADIOBUTTON, self._handle_changed)
138                 #set one radio button active
139                 self.radio_buttons[self.index].SetValue(True)
140
141         def _update(self):
142                 selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0]
143                 self.index = self.radio_buttons.index(selected_radio_button)
144
145 class radio_buttons_horizontal_control(_radio_buttons_control_base):
146         radio_box_orientation = wx.HORIZONTAL
147         radio_button_align = wx.ALIGN_CENTER
148 class radio_buttons_vertical_control(_radio_buttons_control_base):
149         radio_box_orientation = wx.VERTICAL
150         radio_button_align = wx.ALIGN_LEFT
151
152 ##############################################################################################
153 # Slider Control
154 ##############################################################################################
155 class _slider_control_base(_control_base):
156         """House a Slider and a Text Box for variable control."""
157
158         def __init__(self, window, callback, label='Label', value=50, min=0, max=100, num_steps=100, slider_length=200):
159                 """
160                 Slider contructor.
161                 Create the slider, text box, and label.
162                 @param window the wx parent window
163                 @param callback call the callback on changes
164                 @param label the label title
165                 @param value the default value
166                 @param min the min
167                 @param max the max
168                 @param num_steps the number of steps
169                 @param slider_length the length of the slider bar in pixels
170                 """
171                 #initialize
172                 _control_base.__init__(self, window, callback)
173                 self.min = float(min)
174                 self.max = float(max)
175                 self.num_steps = int(num_steps)
176                 self.slider_length = slider_length
177                 #create gui elements
178                 label_text_sizer = wx.BoxSizer(self.label_text_orientation) #label and text box container
179                 label_text = LabelText(self.get_window(), '%s: '%str(label))
180                 self.text_box = text_box = wx.TextCtrl(self.get_window(), style=wx.TE_PROCESS_ENTER)
181                 text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event
182                 for obj in (label_text, text_box): #fill the container with label and text entry box
183                         label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER)
184                 self.Add(label_text_sizer, 0, wx.ALIGN_CENTER)
185                 #make the slider
186                 self.slider = slider = wx.Slider(self.get_window(), size=wx.Size(*self.get_slider_size()), style=self.slider_style)
187                 try: slider.SetRange(0, num_steps)
188                 except Exception, e:
189                         print >> sys.stderr, 'Error in set slider range: "%s".'%e
190                         exit(-1)
191                 slider.Bind(wx.EVT_SCROLL, self._handle_scroll) #bind the scrolling event
192                 self.Add(slider, 0, wx.ALIGN_CENTER)
193                 #init slider and text box
194                 self.set_value(value)
195
196         def get_value(self):
197                 """
198                 Get the current set value.
199                 @return the value (float)
200                 """
201                 return self._value
202
203         def set_value(self, value):
204                 """
205                 Set the current set value.
206                 @param value the value (float)
207                 """
208                 self._value = value
209                 self._update_slider()
210                 self._update_text_box()
211
212         def _update_slider(self):
213                 """
214                 Translate the real numerical value into a slider value.
215                 """
216                 slider_value = (float(self.get_value()) - self.min)*self.num_steps/(self.max - self.min)
217                 self.slider.SetValue(slider_value)
218
219         def _update_text_box(self):
220                 """
221                 Update the text box value.
222                 Convert the value into engineering notation.
223                 """
224                 self.text_box.SetValue(eng_notation.num_to_str(self.get_value()))
225
226         def _handle_scroll(self, event=None):
227                 """
228                 A scroll event is detected. Read the slider, call the callback.
229                 """
230                 slider_value = self.slider.GetValue()
231                 new_value = slider_value*(self.max - self.min)/self.num_steps + self.min
232                 self.set_value(new_value)
233                 try: self.call()
234                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle scroll.\n', e
235
236         def _handle_enter(self, event=None):
237                 """
238                 An enter key was pressed. Read the text box, call the callback.
239                 """
240                 new_value = eng_notation.str_to_num(self.text_box.GetValue())
241                 self.set_value(new_value)
242                 try: self.call()
243                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e
244
245 class slider_horizontal_control(_slider_control_base):
246         label_text_orientation = wx.HORIZONTAL
247         slider_style = wx.SL_HORIZONTAL
248         def get_slider_size(self): return self.slider_length, -1
249 class slider_vertical_control(_slider_control_base):
250         label_text_orientation = wx.VERTICAL
251         slider_style = wx.SL_VERTICAL
252         def get_slider_size(self): return -1, self.slider_length
253
254 ##############################################################################################
255 # Text Box Control
256 ##############################################################################################
257 class text_box_control(_control_base):
258         """House a Text Box for variable control."""
259
260         def __init__(self, window, callback, label='Label', value=50):
261                 """
262                 Text box contructor.
263                 Create the text box, and label.
264                 @param window the wx parent window
265                 @param callback call the callback on changes
266                 @param label the label title
267                 @param value the default value
268                 """
269                 #initialize
270                 _control_base.__init__(self, window, callback)
271                 #create gui elements
272                 label_text_sizer = wx.BoxSizer(wx.HORIZONTAL) #label and text box container
273                 label_text = LabelText(self.get_window(), '%s: '%str(label))
274                 self.text_box = text_box = wx.TextCtrl(self.get_window(), value=str(value), style=wx.TE_PROCESS_ENTER)
275                 text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event
276                 for obj in (label_text, text_box): #fill the container with label and text entry box
277                         label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER)
278                 self.Add(label_text_sizer, 0, wx.ALIGN_CENTER)
279                 #detect string mode
280                 self._string_mode = isinstance(value, str)
281
282         def get_value(self):
283                 """
284                 Get the current set value.
285                 @return the value (float)
286                 """
287                 return self._value
288
289         def _handle_enter(self, event=None):
290                 """
291                 An enter key was pressed. Read the text box, call the callback.
292                 If the text cannot be evaluated, do not try callback.
293                 Do not evaluate the text box value in string mode.
294                 """
295                 if self._string_mode:
296                         self._value = str(self.text_box.GetValue())
297                 else:
298                         try: self._value = eval(self.text_box.GetValue())
299                         except Exception, e:
300                                 print >> sys.stderr, 'Error in evaluate value from handle enter.\n', e
301                                 return
302                 try: self.call()
303                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e