diff options
author | Josh Blum | 2010-12-06 22:46:39 -0500 |
---|---|---|
committer | Josh Blum | 2011-03-01 17:02:06 -0800 |
commit | 9ae8997e5f682bc16e58c0a0bba21794dbd705c5 (patch) | |
tree | 8ffd4f98e92a1e9261f88574d0c2679b11cabaec /gr-qtgui/python | |
parent | 0e076c5835ea624c25e7be964e946d9f6425f414 (diff) | |
download | gnuradio-9ae8997e5f682bc16e58c0a0bba21794dbd705c5.tar.gz gnuradio-9ae8997e5f682bc16e58c0a0bba21794dbd705c5.tar.bz2 gnuradio-9ae8997e5f682bc16e58c0a0bba21794dbd705c5.zip |
qtgui: created a forms module with a slider and text box, implemented in grc
Diffstat (limited to 'gr-qtgui/python')
-rw-r--r-- | gr-qtgui/python/.gitignore | 2 | ||||
-rw-r--r-- | gr-qtgui/python/Makefile.am | 29 | ||||
-rw-r--r-- | gr-qtgui/python/forms/__init__.py | 32 | ||||
-rw-r--r-- | gr-qtgui/python/forms/converters.py | 154 | ||||
-rw-r--r-- | gr-qtgui/python/forms/forms.py | 140 |
5 files changed, 357 insertions, 0 deletions
diff --git a/gr-qtgui/python/.gitignore b/gr-qtgui/python/.gitignore new file mode 100644 index 000000000..b336cc7ce --- /dev/null +++ b/gr-qtgui/python/.gitignore @@ -0,0 +1,2 @@ +/Makefile +/Makefile.in diff --git a/gr-qtgui/python/Makefile.am b/gr-qtgui/python/Makefile.am new file mode 100644 index 000000000..5f6d22b17 --- /dev/null +++ b/gr-qtgui/python/Makefile.am @@ -0,0 +1,29 @@ +# +# Copyright 2010 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/Makefile.common + +qtguiformspythondir = $(grpythondir)/qtgui/forms + +qtguiformspython_PYTHON = \ + forms/__init__.py \ + forms/converters.py \ + forms/forms.py diff --git a/gr-qtgui/python/forms/__init__.py b/gr-qtgui/python/forms/__init__.py new file mode 100644 index 000000000..0f4391bb8 --- /dev/null +++ b/gr-qtgui/python/forms/__init__.py @@ -0,0 +1,32 @@ +# +# Copyright 2010 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +######################################################################## +# External Converters +######################################################################## +from converters import \ + eval_converter, str_converter, \ + float_converter, int_converter + +######################################################################## +# External Forms +######################################################################## +from forms import slider, text_box diff --git a/gr-qtgui/python/forms/converters.py b/gr-qtgui/python/forms/converters.py new file mode 100644 index 000000000..53c966d32 --- /dev/null +++ b/gr-qtgui/python/forms/converters.py @@ -0,0 +1,154 @@ +# +# Copyright 2009 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import eng_notation +import math + +class abstract_converter(object): + def external_to_internal(self, v): + """ + Convert from user specified value to value acceptable to underlying primitive. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def internal_to_external(self, s): + """ + Convert from underlying primitive value to user specified value. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def help(self): + return "Any string is acceptable" + +class identity_converter(abstract_converter): + def external_to_internal(self,v): + return v + def internal_to_external(self, s): + return s + +######################################################################## +# Commonly used converters +######################################################################## +class chooser_converter(abstract_converter): + """ + Convert between a set of possible choices and an index. + Used in the chooser base and all sub-classes. + """ + def __init__(self, choices): + #choices must be a list because tuple does not have .index() in python2.5 + self._choices = list(choices) + def external_to_internal(self, choice): + return self._choices.index(choice) + def internal_to_external(self, index): + return self._choices[index] + def help(self): + return 'Enter a possible value in choices: "%s"'%str(self._choices) + +class bool_converter(abstract_converter): + """ + The internal representation is boolean. + The external representation is specified. + Used in the check box form. + """ + def __init__(self, true, false): + self._true = true + self._false = false + def external_to_internal(self, v): + if v == self._true: return True + if v == self._false: return False + raise Exception, 'Value "%s" is not a possible option.'%v + def internal_to_external(self, v): + if v: return self._true + else: return self._false + def help(self): + return "Value must be in (%s, %s)."%(self._true, self._false) + +class eval_converter(abstract_converter): + """ + A catchall converter when int and float are not enough. + Evaluate the internal representation with python's eval(). + Possible uses, set a complex number, constellation points. + Used in text box. + """ + def __init__(self, formatter=lambda x: '%s'%(x)): + self._formatter = formatter + def external_to_internal(self, v): + return self._formatter(v) + def internal_to_external(self, s): + return eval(s) + def help(self): + return "Value must be evaluatable by python's eval." + +class str_converter(abstract_converter): + def __init__(self, formatter=lambda x: '%s'%(x)): + self._formatter = formatter + def external_to_internal(self, v): + return self._formatter(v) + def internal_to_external(self, s): + return str(s) + +class int_converter(abstract_converter): + def __init__(self, formatter=lambda x: '%d'%round(x)): + self._formatter = formatter + def external_to_internal(self, v): + return self._formatter(v) + def internal_to_external(self, s): + return int(s, 0) + def help(self): + return "Enter an integer. Leading 0x indicates hex" + +class float_converter(abstract_converter): + def __init__(self, formatter=eng_notation.num_to_str): + self._formatter = formatter + def external_to_internal(self, v): + return self._formatter(v) + def internal_to_external(self, s): + return eng_notation.str_to_num(s) + def help(self): + return "Enter a float with optional scale suffix. E.g., 100.1M" + +class slider_converter(abstract_converter): + """ + Scale values to and from the slider. + """ + def __init__(self, start, stop, num_steps, cast): + assert start < stop + assert num_steps > 0 + self._offset = start + self._scaler = float(stop - start)/num_steps + self._cast = cast + def external_to_internal(self, v): + return (v - self._offset)/self._scaler + def internal_to_external(self, v): + return self._cast(v*self._scaler + self._offset) + def help(self): + return "Value should be within slider range" + +class log_slider_converter(slider_converter): + def __init__(self, min_exp, max_exp, num_steps, base): + assert min_exp < max_exp + assert num_steps > 0 + self._base = base + slider_converter.__init__(self, start=min_exp, stop=max_exp, num_steps=num_steps, cast=float) + def external_to_internal(self, v): + return slider_converter.external_to_internal(self, math.log(v, self._base)) + def internal_to_external(self, v): + return self._base**slider_converter.internal_to_external(self, v) diff --git a/gr-qtgui/python/forms/forms.py b/gr-qtgui/python/forms/forms.py new file mode 100644 index 000000000..56ee72499 --- /dev/null +++ b/gr-qtgui/python/forms/forms.py @@ -0,0 +1,140 @@ +# +# Copyright 2010 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from PyQt4 import QtGui +from PyQt4.QtCore import Qt + +import converters + +######################################################################## +# Base class for all forms +######################################################################## +class _form_base(QtGui.QWidget): + def __init__(self, parent=None, converter=None, callback=None, value=None): + QtGui.QWidget.__init__(self, parent) + self._layout = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight, self) + self._converter = converter + self._callback = callback + self._value = value + + def _add_widget(self, widget, label): + if label: + label_widget = QtGui.QLabel(self) + label_widget.setText('%s: '%label) + self._layout.addWidget(label_widget, True) + self._layout.addWidget(widget, stretch=1) + #disable callback, update, re-enable + callback = self._callback + self._callback = None + self.set_value(self.get_value()) + self._callback = callback + + def get_value(self): + return self._value + + def set_value(self, value): + self._value = value + self._base_update(self._converter.external_to_internal(value)) + + def _base_update(self, int_val): + self._update(int_val) + + def _base_handle(self, int_val): + self._value = self._converter.internal_to_external(int_val) + if self._callback: self._callback(self._value) + +######################################################################## +# Slider base class, shared by log and linear sliders +######################################################################## +class _slider_base(_form_base): + def __init__(self, label='', length=-1, num_steps=100, orient=Qt.Horizontal, **kwargs): + _form_base.__init__(self, **kwargs) + self._slider = QtGui.QSlider(parent=self) + self._slider.setOrientation(orient) + self._slider.setRange(0, num_steps) + if length > 0: + if orient == Qt.Horizontal: + slider_size = self._slider.setWidth(length) + if orient == Qt.Vertical: + slider_size = self._slider.setHeight(length) + self._add_widget(self._slider, label) + self._slider.valueChanged.connect(self._handle) + + def _handle(self, event): + value = self._slider.value() + if self._cache != value: self._base_handle(value) + def _update(self, value): + self._cache = int(round(value)) + self._slider.setValue(self._cache) + +######################################################################## +# Linear slider form +######################################################################## +class slider(_slider_base): + """ + A generic linear slider. + @param parent the parent widget + @param value the default value + @param label title label for this widget (optional) + @param length the length of the slider in px (optional) + @param orient Qt.Horizontal Veritcal (default=horizontal) + @param start the start value + @param stop the stop value + @param num_steps the number of slider steps (or specify step_size) + @param step_size the step between slider jumps (or specify num_steps) + @param cast a cast function, int, or float (default=float) + """ + def __init__(self, start, stop, step=None, num_steps=100, cast=float, **kwargs): + assert step or num_steps + if step is not None: num_steps = (stop - start)/step + converter = converters.slider_converter(start=start, stop=stop, num_steps=num_steps, cast=cast) + _slider_base.__init__(self, converter=converter, num_steps=num_steps, **kwargs) + +######################################################################## +# Text Box Form +######################################################################## +class text_box(_form_base): + """ + A text box form. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param value the default value + @param label title label for this widget (optional) + @param converter forms.str_converter(), int_converter(), float_converter()... + """ + def __init__(self, label='', converter=converters.eval_converter(), **kwargs): + _form_base.__init__(self, converter=converter, **kwargs) + self._text_box = QtGui.QLineEdit(self) + self._default_style_sheet = self._text_box.styleSheet() + self._text_box.textChanged.connect(self._set_color_changed) + self._text_box.returnPressed.connect(self._handle) + self._add_widget(self._text_box, label) + + def _set_color_default(self): + self._text_box.setStyleSheet(self._default_style_sheet) + + def _set_color_changed(self, *args): + self._text_box.setStyleSheet("QWidget { background-color: #EEDDDD }") + + def _handle(self): self._base_handle(str(self._text_box.text())) + def _update(self, value): + self._text_box.setText(value) + self._set_color_default() |