"""
The classes in this module provide a property-like interface
to widget instance variables in a class. These properties translate
essential pieces of widget state into more convenient python objects
(for example, the check state of a button to a bool).
Example Use::
class Foo(object):
bar = ButtonProperty('_button')
def __init__(self):
self._button = QtGui.QCheckBox()
f = Foo()
f.bar = True # equivalent to f._button.setChecked(True)
assert f.bar == True
"""
from __future__ import absolute_import, division, print_function
import math
from functools import partial
from glue.logger import logger
from glue.external.six.moves import reduce
from glue.external.qt import QtGui
from glue.external.echo import add_callback
from glue.utils.array import pretty_number
__all__ = ['WidgetProperty', 'CurrentComboDataProperty',
'CurrentComboTextProperty', 'CurrentTabProperty', 'TextProperty',
'ButtonProperty', 'FloatLineProperty', 'ValueProperty',
'connect_bool_button', 'connect_current_combo',
'connect_float_edit', 'connect_int_spin']
[docs]class CurrentComboDataProperty(WidgetProperty):
"""
Wrapper around the data in QComboBox.
"""
[docs] def getter(self, widget):
"""
Return the itemData stored in the currently-selected item
"""
if widget.currentIndex() == -1:
return None
else:
return widget.itemData(widget.currentIndex())
[docs] def setter(self, widget, value):
"""
Update the currently selected item to the one which stores value in
its itemData
"""
# Note, we don't use findData here because it doesn't work
# well with non-str data
try:
idx = _find_combo_data(widget, value)
except ValueError:
if value is None:
idx = -1
else:
raise ValueError("Cannot find data '{0}' in combo box".format(value))
widget.setCurrentIndex(idx)
CurrentComboProperty = CurrentComboDataProperty
[docs]class CurrentComboTextProperty(WidgetProperty):
"""
Wrapper around the text in QComboBox.
"""
[docs] def getter(self, widget):
"""
Return the itemData stored in the currently-selected item
"""
if widget.currentIndex() == -1:
return None
else:
return widget.itemText(widget.currentIndex())
[docs] def setter(self, widget, value):
"""
Update the currently selected item to the one which stores value in
its itemData
"""
idx = widget.findText(value)
if idx == -1:
if value is not None:
raise ValueError("Cannot find text '{0}' in combo box".format(value))
widget.setCurrentIndex(idx)
[docs]class CurrentTabProperty(WidgetProperty):
"""
Wrapper around QTabWidget.
"""
[docs] def getter(self, widget):
"""
Return the itemData stored in the currently-selected item
"""
return widget.tabText(widget.currentIndex())
[docs] def setter(self, widget, value):
"""
Update the currently selected item to the one which stores value in
its itemData
"""
for idx in range(widget.count()):
if widget.tabText(idx) == value:
break
else:
raise ValueError("Cannot find value '{0}' in tabs".format(value))
widget.setCurrentIndex(idx)
[docs]class TextProperty(WidgetProperty):
"""
Wrapper around the text() and setText() methods for QLabel etc
"""
[docs] def getter(self, widget):
return widget.text()
[docs] def setter(self, widget, value):
widget.setText(value)
[docs]class FloatLineProperty(WidgetProperty):
"""
Wrapper around the text state for QLineEdit widgets.
Assumes that the text is a floating-point number
"""
[docs] def getter(self, widget):
try:
return float(widget.text())
except ValueError:
return 0
[docs] def setter(self, widget, value):
widget.setText(pretty_number(value))
widget.editingFinished.emit()
[docs]class ValueProperty(WidgetProperty):
"""
Wrapper around widgets with value() and setValue()
Parameters
----------
att : str
The location, within a class instance, of the widget to wrap around.
If the widget is nested inside another variable, normal '.' syntax
can be used (e.g. 'sub_window.button')
docstring : str, optional
Optional short summary for the property. Used by sphinx. Should be 1
sentence or less.
value_range : tuple, optional
If set, the slider values are mapped to this range.
log : bool, optional
If `True`, the mapping is assumed to be logarithmic instead of
linear.
"""
def __init__(self, att, docstring='',value_range=None, log=False):
super(ValueProperty, self).__init__(att, docstring=docstring)
if log:
if value_range is None:
raise ValueError("log option can only be set if value_range is given")
else:
value_range = math.log10(value_range[0]), math.log10(value_range[1])
self.log = log
self.value_range = value_range
[docs] def getter(self, widget):
val = widget.value()
if self.value_range is not None:
imin, imax = widget.minimum(), widget.maximum()
vmin, vmax = self.value_range
val = (val - imin) / (imax - imin) * (vmax - vmin) + vmin
if self.log:
val = 10 ** val
return val
[docs] def setter(self, widget, val):
if self.log:
val = math.log10(val)
if self.value_range is not None:
imin, imax = widget.minimum(), widget.maximum()
vmin, vmax = self.value_range
val = (val - vmin) / (vmax - vmin) * (imax - imin) + imin
widget.setValue(val)
[docs]def connect_current_combo(client, prop, widget):
"""
Connect widget.currentIndexChanged and client.prop
client.prop should be a callback property
"""
def update_widget(value):
try:
idx = _find_combo_data(widget, value)
except ValueError:
if value is None:
idx = -1
else:
raise
widget.setCurrentIndex(idx)
def update_prop(idx):
if idx == -1:
setattr(client, prop, None)
else:
setattr(client, prop, widget.itemData(idx))
add_callback(client, prop, update_widget)
widget.currentIndexChanged.connect(update_prop)
update_widget(getattr(client, prop))
[docs]def connect_float_edit(client, prop, widget):
"""
Connect widget.setText and client.prop
Also pretty-print the number
client.prop should be a callback property
"""
v = QtGui.QDoubleValidator(None)
v.setDecimals(4)
widget.setValidator(v)
def update_prop():
val = widget.text()
try:
setattr(client, prop, float(val))
except ValueError:
setattr(client, prop, 0)
def update_widget(val):
if val is None:
val = 0.
widget.setText(pretty_number(val))
add_callback(client, prop, update_widget)
widget.editingFinished.connect(update_prop)
update_widget(getattr(client, prop))
def connect_value(client, prop, widget, value_range=None, log=False):
"""
Connect client.prop to widget.valueChanged
client.prop should be a callback property
If ``value_range`` is set, the slider values are mapped to that range. If
``log`` is set, the mapping is assumed to be logarithmic instead of linear.
"""
if log:
if value_range is None:
raise ValueError("log option can only be set if value_range is given")
else:
value_range = math.log10(value_range[0]), math.log10(value_range[1])
def update_prop():
val = widget.value()
if value_range is not None:
imin, imax = widget.minimum(), widget.maximum()
val = (val - imin) / (imax - imin) * (value_range[1] - value_range[0]) + value_range[0]
if log:
val = 10 ** val
setattr(client, prop, val)
def update_widget(val):
if val is None:
widget.setValue(0)
return
if log:
val = math.log10(val)
if value_range is not None:
imin, imax = widget.minimum(), widget.maximum()
val = (val - value_range[0]) / (value_range[1] - value_range[0]) * (imax - imin) + imin
widget.setValue(val)
add_callback(client, prop, update_widget)
widget.valueChanged.connect(update_prop)
update_widget(getattr(client, prop))
connect_int_spin = connect_value
def _find_combo_data(widget, value):
"""
Returns the index in a combo box where itemData == value
Raises a ValueError if data is not found
"""
i = widget.findData(value)
if i == -1:
raise ValueError("%s not found in combo box" % value)
else:
return i