Merged r9481:9518 on jblum/grc_reorganize into trunk. Reorganized grc source under...
[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
24 class LabelText(wx.StaticText):
25         """Label text class for uniform labels among all controls."""
26
27         def __init__(self, window, label):
28                 wx.StaticText.__init__(self, window, -1, str(label))
29                 font = self.GetFont()
30                 font.SetWeight(wx.FONTWEIGHT_BOLD)
31                 self.SetFont(font)
32
33 class _control_base(wx.BoxSizer):
34         """Control base class"""
35
36         def __init__(self, window, callback):
37                 self.window = window
38                 self.callback = callback
39                 wx.BoxSizer.__init__(self, wx.VERTICAL)
40
41         def get_window(self): return self.window
42
43         def call(self): return self.callback(self.get_value())
44
45         def get_value(self): raise NotImplementedError
46
47 class _chooser_control_base(_control_base):
48         """House a drop down or radio buttons for variable control."""
49
50         def __init__(self, window, callback, label='Label', index=0, choices=[0], labels=[]):
51                 """
52                 Chooser contructor.
53                 Create the slider, text box, and label.
54                 @param window the wx parent window
55                 @param callback call the callback on changes
56                 @param label the label title
57                 @param index the default choice index
58                 @param choices a list of choices
59                 @param labels the choice labels or empty list
60                 """
61                 #initialize
62                 _control_base.__init__(self, window, callback)
63                 label_text = LabelText(self.get_window(), label)
64                 self.Add(label_text, 0, wx.ALIGN_CENTER)
65                 self.index = index
66                 self.choices = choices
67                 self.labels = map(str, labels or choices)
68                 self._init()
69
70         def _handle_changed(self, event=None):
71                 """
72                 A change is detected. Call the callback.
73                 """
74                 try: self.call()
75                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle changed.\n', e
76
77         def get_value(self):
78                 """
79                 Update the chooser.
80                 @return one of the possible choices
81                 """
82                 self._update()
83                 return self.choices[self.index]
84
85 ##############################################################################################
86 # Button Control
87 ##############################################################################################
88 class button_control(_chooser_control_base):
89         """House a button for variable control."""
90
91         def _init(self):
92                 self.button = wx.Button(self.get_window(), -1, self.labels[self.index])
93                 self.button.Bind(wx.EVT_BUTTON, self._handle_changed)
94                 self.Add(self.button, 0, wx.ALIGN_CENTER)
95
96         def _update(self):
97                 self.index = (self.index + 1)%len(self.choices) #circularly increment index
98                 self.button.SetLabel(self.labels[self.index])
99
100 ##############################################################################################
101 # Drop Down Control
102 ##############################################################################################
103 class drop_down_control(_chooser_control_base):
104         """House a drop down for variable control."""
105
106         def _init(self):
107                 self.drop_down = wx.Choice(self.get_window(), -1, choices=self.labels)
108                 self.Add(self.drop_down, 0, wx.ALIGN_CENTER)
109                 self.drop_down.Bind(wx.EVT_CHOICE, self._handle_changed)
110                 self.drop_down.SetSelection(self.index)
111
112         def _update(self):
113                 self.index = self.drop_down.GetSelection()
114
115 ##############################################################################################
116 # Radio Buttons Control
117 ##############################################################################################
118 class _radio_buttons_control_base(_chooser_control_base):
119         """House radio buttons for variable control."""
120
121         def _init(self):
122                 #create box for radio buttons
123                 radio_box = wx.BoxSizer(self.radio_box_orientation)
124                 panel = wx.Panel(self.get_window(), -1)
125                 panel.SetSizer(radio_box)
126                 self.Add(panel, 0, wx.ALIGN_CENTER)
127                 #create radio buttons
128                 self.radio_buttons = list()
129                 for label in self.labels:
130                         radio_button = wx.RadioButton(panel, -1, label)
131                         radio_button.SetValue(False)
132                         self.radio_buttons.append(radio_button)
133                         radio_box.Add(radio_button, 0, self.radio_button_align)
134                         radio_button.Bind(wx.EVT_RADIOBUTTON, self._handle_changed)
135                 #set one radio button active
136                 self.radio_buttons[self.index].SetValue(True)
137
138         def _update(self):
139                 selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0]
140                 self.index = self.radio_buttons.index(selected_radio_button)
141
142 class radio_buttons_horizontal_control(_radio_buttons_control_base):
143         radio_box_orientation = wx.HORIZONTAL
144         radio_button_align = wx.ALIGN_CENTER
145 class radio_buttons_vertical_control(_radio_buttons_control_base):
146         radio_box_orientation = wx.VERTICAL
147         radio_button_align = wx.ALIGN_LEFT
148
149 ##############################################################################################
150 # Slider Control
151 ##############################################################################################
152 class _slider_control_base(_control_base):
153         """House a Slider and a Text Box for variable control."""
154
155         def __init__(self, window, callback, label='Label', value=50, min=0, max=100, num_steps=100):
156                 """
157                 Slider contructor.
158                 Create the slider, text box, and label.
159                 @param window the wx parent window
160                 @param callback call the callback on changes
161                 @param label the label title
162                 @param value the default value
163                 @param min the min
164                 @param max the max
165                 @param num_steps the number of steps
166                 """
167                 #initialize
168                 _control_base.__init__(self, window, callback)
169                 self.min = float(min)
170                 self.max = float(max)
171                 self.num_steps = int(num_steps)
172                 #create gui elements
173                 label_text_sizer = wx.BoxSizer(self.label_text_orientation) #label and text box container
174                 label_text = LabelText(self.get_window(), '%s: '%str(label))
175                 self.text_box = text_box = wx.TextCtrl(self.get_window(), -1, str(value), style=wx.TE_PROCESS_ENTER)
176                 text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event
177                 for obj in (label_text, text_box): #fill the container with label and text entry box
178                         label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
179                 self.Add(label_text_sizer, 0, wx.ALIGN_CENTER)
180                 #make the slider
181                 self.slider = slider = wx.Slider(self.get_window(), -1, size=wx.Size(*self.slider_size), style=self.slider_style)
182                 try: slider.SetRange(0, num_steps)
183                 except Exception, e:
184                         print >> sys.stderr, 'Error in set slider range: "%s".'%e
185                         sys.exit(-1)
186                 slider.Bind(wx.EVT_SCROLL, self._handle_scroll) #bind the scrolling event
187                 self.Add(slider, 0, wx.ALIGN_CENTER)
188                 #init slider and text box
189                 self._value = value
190                 self._set_slider_value(self._value) #sets the slider's value
191                 self.text_box.SetValue(str(self._value))
192
193         def get_value(self):
194                 """
195                 Get the current set value.
196                 @return the value (float)
197                 """
198                 return self._value
199
200         def _set_slider_value(self, real_value):
201                 """
202                 Translate the real numerical value into a slider value and,
203                 write the value to the slider.
204                 @param real_value the numeric value the slider should represent
205                 """
206                 slider_value = (float(real_value) - self.min)*self.num_steps/(self.max - self.min)
207                 self.slider.SetValue(slider_value)
208
209         def _handle_scroll(self, event=None):
210                 """
211                 A scroll event is detected. Read the slider, call the callback.
212                 """
213                 slider_value = self.slider.GetValue()
214                 new_value = slider_value*(self.max - self.min)/self.num_steps + self.min
215                 self.text_box.SetValue(str(new_value))
216                 self._value = new_value
217                 try: self.call()
218                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle scroll.\n', e
219
220         def _handle_enter(self, event=None):
221                 """
222                 An enter key was pressed. Read the text box, call the callback.
223                 """
224                 new_value = float(self.text_box.GetValue())
225                 self._set_slider_value(new_value)
226                 self._value = new_value
227                 try: self.call()
228                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e
229
230 class slider_horizontal_control(_slider_control_base):
231         label_text_orientation = wx.HORIZONTAL
232         slider_style = wx.SL_HORIZONTAL
233         slider_size = 200, 20
234 class slider_vertical_control(_slider_control_base):
235         label_text_orientation = wx.VERTICAL
236         slider_style = wx.SL_VERTICAL
237         slider_size = 20, 200
238
239 ##############################################################################################
240 # Text Box Control
241 ##############################################################################################
242 class text_box_control(_control_base):
243         """House a Text Box for variable control."""
244
245         def __init__(self, window, callback, label='Label', value=50):
246                 """
247                 Text box contructor.
248                 Create the text box, and label.
249                 @param window the wx parent window
250                 @param callback call the callback on changes
251                 @param label the label title
252                 @param value the default value
253                 """
254                 #initialize
255                 _control_base.__init__(self, window, callback)
256                 #create gui elements
257                 label_text_sizer = wx.BoxSizer(wx.HORIZONTAL) #label and text box container
258                 label_text = LabelText(self.get_window(), '%s: '%str(label))
259                 self.text_box = text_box = wx.TextCtrl(self.get_window(), -1, str(value), style=wx.TE_PROCESS_ENTER)
260                 text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event
261                 for obj in (label_text, text_box): #fill the container with label and text entry box
262                         label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
263                 self.Add(label_text_sizer, 0, wx.ALIGN_CENTER)
264                 self.text_box.SetValue(str(value))
265
266         def get_value(self):
267                 """
268                 Get the current set value.
269                 @return the value (float)
270                 """
271                 return self._value
272
273         def _handle_enter(self, event=None):
274                 """
275                 An enter key was pressed. Read the text box, call the callback.
276                 If the text cannot be evaluated, do not try callback.
277                 """
278                 try: self._value = eval(self.text_box.GetValue())
279                 except: return
280                 try: self.call()
281                 except Exception, e: print >> sys.stderr, 'Error in exec callback from handle enter.\n', e