Imported Upstream version 3.2.2
[debian/gnuradio] / gr-wxgui / src / python / form.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2005 Free Software Foundation, Inc.
4
5 # This file is part of GNU Radio
6
7 # GNU Radio is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3, or (at your option)
10 # any later version.
11
12 # GNU Radio is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Radio; see the file COPYING.  If not, write to
19 # the Free Software Foundation, Inc., 51 Franklin Street,
20 # Boston, MA 02110-1301, USA.
21
22
23 import wx
24 from gnuradio import eng_notation
25
26 # ----------------------------------------------------------------
27 #                   Wrappers for certain widgets
28 # ----------------------------------------------------------------
29
30 def button_with_callback(parent, label, callback):
31     new_id = wx.NewId()
32     btn = wx.Button(parent, new_id, label)
33     wx.EVT_BUTTON(parent, new_id, lambda evt: callback())
34     return btn
35     
36
37 # ----------------------------------------------------------------
38 #                        Format converters
39 # ----------------------------------------------------------------
40
41 class abstract_converter(object):
42     def value_to_prim(self, v):
43         """
44         Convert from user specified value to value acceptable to underlying primitive.
45         The underlying primitive usually expects strings.
46         """
47         raise NotImplementedError
48     def prim_to_value(self, s):
49         """
50         Convert from underlying primitive value to user specified value.
51         The underlying primitive usually expects strings.
52         """
53         raise NotImplementedError
54     def help(self):
55         return "Any string is acceptable"
56
57 class identity_converter(abstract_converter):
58     def value_to_prim(self,v):
59         return v
60     def prim_to_value(self, s):
61         return s
62
63 class int_converter(abstract_converter):
64     def value_to_prim(self, v):
65         return str(v)
66     def prim_to_value(self, s):
67         return int(s, 0)
68     def help(self):
69         return "Enter an integer.  Leading 0x indicates hex"
70
71 class float_converter(abstract_converter):
72     def value_to_prim(self, v):
73         return eng_notation.num_to_str(v)
74     def prim_to_value(self, s):
75         return eng_notation.str_to_num(s)
76     def help(self):
77         return "Enter a float with optional scale suffix.  E.g., 100.1M"
78
79
80 # ----------------------------------------------------------------
81 #               Various types of data entry fields
82 # ----------------------------------------------------------------
83
84 class field(object):
85     """
86     A field in a form.
87     """
88     def __init__(self, converter, value):
89         self.converter = converter
90         if value is not None:
91             self.set_value(value)
92
93     def set_value(self, v):
94         self._set_prim_value(self.converter.value_to_prim(v))
95
96     def get_value(self):
97         return self.converter.prim_to_value(self._get_prim_value())
98
99     def get_value_with_check(self):
100         """
101         Returns (value, error_msg), where error_msg is not None if there was problem
102         """
103         try:
104             return (self.get_value(), None)
105         except:
106             return (None, self._error_msg())
107
108     def _set_prim_value(self, v):
109         raise NotImplementedError
110
111     def _get_prim_value(self):
112         raise NotImplementedError
113
114     def _pair_with_label(self, widget, parent=None, sizer=None, label=None, weight=1):
115         self.label = label
116         if label is None:
117             sizer.Add (widget, weight, wx.EXPAND)
118             return widget
119         elif 0:
120             hbox = wx.BoxSizer(wx.HORIZONTAL)
121             label_widget = wx.StaticText(parent, -1, label + ': ')
122             hbox.Add(label_widget, 0, wx.EXPAND)
123             hbox.Add(widget, 1, wx.EXPAND)
124             sizer.Add(hbox, weight, wx.EXPAND)
125             return widget
126         else:
127             label_widget = wx.StaticText(parent, -1, label + ': ')
128             sizer.Add(label_widget, 0, wx.EXPAND)
129             sizer.Add(widget, weight, wx.EXPAND)
130             return widget
131     
132     def _error_msg(self):
133         prefix = ''
134         if self.label:
135             prefix = self.label + ': '
136         return "%s%s is invalid. %s" % (prefix, self._get_prim_value(),
137                                         self.converter.help())
138
139 # static (display-only) text fields
140
141 class static_text_field(field):
142     def __init__(self, parent=None, sizer=None, label=None, value=None,
143                  converter=identity_converter(), weight=0):
144         self.f = self._pair_with_label(wx.StaticText(parent, -1, ""),
145                                        parent=parent, sizer=sizer, label=label, weight=weight)
146         field.__init__(self, converter, value)
147
148     def _get_prim_value(self):
149         return self.f.GetLabel()
150
151     def _set_prim_value(self, v):
152         self.f.SetLabel(v)
153
154
155 class static_int_field(static_text_field):
156     def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0):
157         static_text_field.__init__(self, parent, sizer, label, value, int_converter(), weight)
158
159 class static_float_field(static_text_field):
160     def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0):
161         static_text_field.__init__(self, parent, sizer, label, value, float_converter(), weight)
162
163
164 # editable text fields
165
166 class text_field(field):
167     def __init__(self, parent=None, sizer=None, label=None, value=None,
168                  converter=identity_converter(), callback=None, weight=1):
169         style = 0
170         if callback:
171             style = wx.TE_PROCESS_ENTER
172
173         new_id = wx.NewId()
174         w = wx.TextCtrl(parent, new_id, "", style=style)
175         self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight)
176         if callback:
177             wx.EVT_TEXT_ENTER(w, new_id, lambda evt: callback())
178         field.__init__(self, converter, value)
179
180     def _get_prim_value(self):
181         return self.f.GetValue()
182
183     def _set_prim_value(self, v):
184         self.f.SetValue(v)
185
186
187 class int_field(text_field):
188     def __init__(self, parent=None, sizer=None, label=None, value=None,
189                  callback=None, weight=1):
190         text_field.__init__(self, parent, sizer, label, value, int_converter(), callback, weight)
191
192 class float_field(text_field):
193     def __init__(self, parent=None, sizer=None, label=None, value=None,
194                  callback=None, weight=1):
195         text_field.__init__(self, parent, sizer, label, value, float_converter(), callback, weight)
196
197 # other fields
198
199 class slider_field(field):
200     def __init__(self, parent=None, sizer=None, label=None, value=None,
201                  converter=identity_converter(), callback=None, min=0, max=100, weight=1):
202         new_id = wx.NewId()
203         w = wx.Slider(parent, new_id, (max+min)/2, min, max,
204                       size=wx.Size(250, -1), style=wx.SL_HORIZONTAL | wx.SL_LABELS)
205         self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight)
206         if callback:
207             wx.EVT_COMMAND_SCROLL(w, new_id, lambda evt: callback(evt.GetInt()))
208         field.__init__(self, converter, value)
209
210     def _get_prim_value(self):
211         return self.f.GetValue()
212
213     def _set_prim_value(self, v):
214         self.f.SetValue(int(v))
215
216 class quantized_slider_field(field):
217     def __init__(self, parent=None, sizer=None, label=None, value=None,
218                  converter=identity_converter(), callback=None, range=None, weight=1):
219         if not isinstance(range, (tuple, list)) or len(range) != 3:
220             raise ValueError, range
221
222         self.min = range[0]
223         self.max = range[1]
224         self.step_size = float(range[2])
225         nsteps = int((self.max-self.min)/self.step_size)
226         
227         new_id = wx.NewId()
228         w = wx.Slider(parent, new_id, 0, 0, nsteps,
229                       size=wx.Size(250, -1), style=wx.SL_HORIZONTAL)
230         self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight)
231         if callback:
232             wx.EVT_COMMAND_SCROLL(w, new_id,
233                                   lambda evt: callback(self._map_out(evt.GetInt())))
234         field.__init__(self, converter, value)
235
236     def _get_prim_value(self):
237         return self._map_out(self.f.GetValue())
238
239     def _set_prim_value(self, v):
240         self.f.SetValue(self._map_in(v))
241
242     def _map_in(self, x):
243         return int((x-self.min) / self.step_size)
244
245     def _map_out(self, x):
246         return x * self.step_size + self.min
247
248 class checkbox_field(field):
249     def __init__(self, parent=None, sizer=None, label=None, value=None,
250                  converter=identity_converter(), callback=None, weight=1):
251         new_id = wx.NewId()
252         w = wx.CheckBox(parent, new_id, label, style=wx.CHK_2STATE)
253         self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight)
254         if callback:
255             wx.EVT_CHECKBOX(w, new_id, lambda evt: callback(evt.GetInt()))
256         field.__init__(self, converter, value)
257
258     def _get_prim_value(self):
259         return self.f.GetValue()
260
261     def _set_prim_value(self, v):
262         self.f.SetValue(int(v))
263
264
265 class radiobox_field(field):
266     def __init__(self, parent=None, sizer=None, label=None, value=None,
267                  converter=identity_converter(), callback=None, weight=1,
268                  choices=None, major_dimension=1, specify_rows=False):
269         new_id = wx.NewId()
270
271         if specify_rows:
272             style=wx.RA_SPECIFY_ROWS | wx.RA_HORIZONTAL
273         else:
274             style=wx.RA_SPECIFY_COLS | wx.RA_HORIZONTAL
275             
276         w = wx.RadioBox(parent, new_id, label=label, style=style, majorDimension=major_dimension,
277                         choices=choices)
278         self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight)
279         if callback:
280             wx.EVT_RADIOBOX(w, new_id, lambda evt: callback(evt.GetString()))
281         field.__init__(self, converter, value)
282
283     def _get_prim_value(self):
284         return self.f.GetStringSelection()
285
286     def _set_prim_value(self, v):
287         self.f.SetStringSelection(str(v))
288
289 # ----------------------------------------------------------------
290 #                         the form class
291 # ----------------------------------------------------------------
292
293 class form(dict):
294     def __init__(self):
295         dict.__init__(self)
296
297     def check_input_for_errors(self):
298         """
299         Returns list of error messages if there's trouble,
300         else empty list.
301         """
302         vals = [f.get_value_with_check() for f in self.values()]
303         return [t[1] for t in vals if t[1] is not None]
304         
305     def get_key_vals(self):
306         d = {}
307         for (key, f) in self.items():
308             d[key] = f.get_value()
309         return d
310
311
312     def _nop(*args): pass
313     
314     def check_input_and_call(self, callback, status_handler=_nop):
315         """
316         Return a function that checks the form for errors, and then if it's OK,
317         invokes the user specified callback, passing it the form key/value dictionary.
318         status_handler is called with a string indicating results.
319         """
320         def doit_callback(*ignore):
321             errors = self.check_input_for_errors()
322             if errors:
323                 status_handler(errors[0])
324                 #print '\n'.join(tuple(errors))
325             else:
326                 kv = self.get_key_vals()
327                 if callback(kv):
328                     status_handler("OK")
329                 else:
330                     status_handler("Failed")
331
332         return doit_callback
333
334
335
336 # ----------------------------------------------------------------
337 #                    Stand-alone example code
338 # ----------------------------------------------------------------
339
340 import sys
341 from gnuradio.wxgui import stdgui2
342
343 class demo_app_flow_graph (stdgui2.std_top_block):
344     def __init__(self, frame, panel, vbox, argv):
345         stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
346
347         self.frame = frame
348         self.panel = panel
349
350         def _print_kv(kv):
351             print "kv =", kv
352             return True
353
354         self.form = form()
355         
356         self.form['static1'] = \
357             static_text_field(parent=panel, sizer=vbox,
358                               label="Static Text",
359                               value="The Static Value")
360
361         self.form['text1'] = \
362             text_field(parent=panel, sizer=vbox,
363                        label="TextCtrl",
364                        value="The Editable Value")
365
366         self.form['int1'] = \
367             int_field(parent=panel, sizer=vbox,
368                       label="Int Field",
369                       value=1234)
370
371         self.form['float1'] = \
372             float_field(parent=panel, sizer=vbox,
373                       label="Float Field",
374                       value=3.14159)
375
376         self.doit = button_with_callback(
377             panel, "Do It!",
378             self.form.check_input_and_call(_print_kv, self._set_status_msg))
379
380         vbox.Add(self.doit, 0, wx.CENTER)
381
382     def _set_status_msg(self, msg):
383         self.frame.GetStatusBar().SetStatusText(msg, 0)
384
385             
386 def main ():
387     app = stdgui2.stdapp(demo_app_flow_graph, "wxgui form demo", nstatus=1)
388     app.MainLoop ()
389
390 if __name__ == '__main__':
391     main ()