# The functions in this module are used to connect callback properties to Qt
# widgets.
import math
from qtpy import QtGui, QtWidgets
from qtpy.QtCore import Qt
import numpy as np
from ..core import add_callback, remove_callback
from ..selection import SelectionCallbackProperty, ChoiceSeparator
__all__ = ['connect_checkable_button', 'connect_text', 'connect_combo_data',
'connect_combo_text', 'connect_float_text', 'connect_value',
'connect_combo_selection', 'connect_list_selection',
'BaseConnection']
class UserDataWrapper(object):
def __init__(self, data):
self.data = data
[docs]class BaseConnection(object):
def __init__(self, instance, prop, widget):
self._instance = instance
self._prop = prop
self._widget = widget
[docs]class connect_text(BaseConnection):
"""
Connect a string callback property with a Qt widget containing text.
Parameters
----------
instance : object
The class instance that the callback property is attached to
prop : str
The name of the callback property
widget : QtWidget
The Qt widget to connect. This should implement the ``setText`` and
``text`` methods as well optionally the ``editingFinished`` signal.
"""
def __init__(self, instance, prop, widget):
super(connect_text, self).__init__(instance, prop, widget)
self.connect()
[docs] def update_prop(self):
value = self._widget.text()
setattr(self._instance, self._prop, value)
[docs] def update_widget(self, value):
if hasattr(self._widget, 'editingFinished'):
self._widget.blockSignals(True)
self._widget.setText(value)
self._widget.blockSignals(False)
self._widget.editingFinished.emit()
else:
self._widget.setText(value)
[docs] def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
try:
self._widget.editingFinished.connect(self.update_prop)
except AttributeError:
pass
self.update_widget(getattr(self._instance, self._prop))
[docs] def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
try:
self._widget.editingFinished.disconnect(self.update_prop)
except AttributeError:
pass
[docs]class connect_combo_data(BaseConnection):
"""
Connect a callback property with a QComboBox widget based on the userData.
Parameters
----------
instance : object
The class instance that the callback property is attached to
prop : str
The name of the callback property
widget : QComboBox
The combo box to connect.
See Also
--------
connect_combo_text: connect a callback property with a QComboBox widget based on the text.
"""
def __init__(self, instance, prop, widget):
super(connect_combo_data, self).__init__(instance, prop, widget)
self.connect()
[docs] def update_prop(self, idx):
if idx == -1:
setattr(self._instance, self._prop, None)
else:
data_wrapper = self._widget.itemData(idx)
if data_wrapper is None:
setattr(self._instance, self._prop, None)
else:
setattr(self._instance, self._prop, data_wrapper.data)
[docs] def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
self._widget.currentIndexChanged.connect(self.update_prop)
self.update_widget(getattr(self._instance, self._prop))
[docs] def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
self._widget.currentIndexChanged.disconnect(self.update_prop)
[docs]class connect_combo_text(BaseConnection):
"""
Connect a callback property with a QComboBox widget based on the text.
Parameters
----------
instance : object
The class instance that the callback property is attached to
prop : str
The name of the callback property
widget : QComboBox
The combo box to connect.
See Also
--------
connect_combo_data: connect a callback property with a QComboBox widget based on the userData.
"""
def __init__(self, instance, prop, widget):
super(connect_combo_text, self).__init__(instance, prop, widget)
self.connect()
[docs] def update_widget(self, value):
try:
idx = _find_combo_text(self._widget, value)
except ValueError:
if value is None:
idx = -1
else:
raise
self._widget.setCurrentIndex(idx)
[docs] def update_prop(self, idx):
if idx == -1:
setattr(self._instance, self._prop, None)
else:
setattr(self._instance, self._prop, self._widget.itemText(idx))
[docs] def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
self._widget.currentIndexChanged.connect(self.update_prop)
self.update_widget(getattr(self._instance, self._prop))
[docs] def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
self._widget.currentIndexChanged.disconnect(self.update_prop)
[docs]class connect_float_text(BaseConnection):
"""
Connect a numerical callback property with a Qt widget containing text.
Parameters
----------
instance : object
The class instance that the callback property is attached to
prop : str
The name of the callback property
widget : QtWidget
The Qt widget to connect. This should implement the ``setText`` and
``text`` methods as well optionally the ``editingFinished`` signal.
fmt : str or func
This should be either a format string (in the ``{}`` notation), or a
function that takes a number and returns a string.
"""
def __init__(self, instance, prop, widget, fmt="{:g}"):
super(connect_float_text, self).__init__(instance, prop, widget)
if callable(fmt):
format_func = fmt
else:
def format_func(x):
try:
return fmt.format(x)
except ValueError:
return str(x)
self._format_func = format_func
self.connect()
[docs] def update_prop(self):
value = self._widget.text()
try:
value = float(value)
except ValueError:
try:
value = np.datetime64(value)
except Exception:
value = 0
setattr(self._instance, self._prop, value)
[docs] def update_widget(self, value):
if value is None:
value = 0.
self._widget.setText(self._format_func(value))
[docs] def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
try:
self._widget.editingFinished.connect(self.update_prop)
except AttributeError:
pass
self.update_widget(getattr(self._instance, self._prop))
[docs] def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
try:
self._widget.editingFinished.disconnect(self.update_prop)
except AttributeError:
pass
[docs]class connect_value(BaseConnection):
"""
Connect a numerical callback property with a Qt widget representing a value.
Parameters
----------
instance : object
The class instance that the callback property is attached to
prop : str
The name of the callback property
widget : QtWidget
The Qt widget to connect. This should implement the ``setText`` and
``text`` methods as well optionally the ``editingFinished`` signal.
value_range : iterable, optional
A pair of two values representing the true range of values (since
Qt widgets such as sliders can only have values in certain ranges).
log : bool, optional
Whether the Qt widget value should be mapped to the log of the callback
property.
"""
def __init__(self, instance, prop, widget, value_range=None, log=False):
super(connect_value, self).__init__(instance, prop, widget)
if log:
if value_range is None:
raise ValueError("log option can only be set if value_range is given")
else:
self._value_range = math.log10(value_range[0]), math.log10(value_range[1])
else:
self._value_range = value_range
self._log = log
self.connect()
[docs] def update_prop(self):
value = self._widget.value()
if self._value_range is not None:
imin, imax = self._widget.minimum(), self._widget.maximum()
value = ((value - imin) / (imax - imin)
* (self._value_range[1] - self._value_range[0]) + self._value_range[0])
if self._log:
value = 10 ** value
setattr(self._instance, self._prop, value)
[docs] def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
self._widget.valueChanged.connect(self.update_prop)
self.update_widget(getattr(self._instance, self._prop))
[docs] def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
self._widget.valueChanged.disconnect(self.update_prop)
class connect_button(BaseConnection):
"""
Connect a button with a callback method
Parameters
----------
instance : object
The class instance that the callback method is attached to
prop : str
The name of the callback method
widget : QtWidget
The Qt widget to connect. This should implement the ``clicked`` method
"""
def __init__(self, instance, prop, widget):
super(connect_button, self).__init__(instance, prop, widget)
self.connect()
def connect(self):
self._widget.clicked.connect(getattr(self._instance, self._prop))
def disconnect(self):
self._widget.clicked.disconnect(self.update_prop)
def _find_combo_data(widget, value):
"""
Returns the index in a combo box where itemData == value
Raises a ValueError if data is not found
"""
# Here we check that the result is True, because some classes may overload
# == and return other kinds of objects whether true or false.
for idx in range(widget.count()):
if widget.itemData(idx) is not None:
if isinstance(widget.itemData(idx), UserDataWrapper):
if widget.itemData(idx).data is value or (widget.itemData(idx).data == value) is True:
return idx
else:
if widget.itemData(idx) is value or (widget.itemData(idx) == value) is True:
return idx
else:
raise ValueError("%s not found in combo box" % (value,))
def _find_combo_text(widget, value):
"""
Returns the index in a combo box where text == value
Raises a ValueError if data is not found
"""
i = widget.findText(value)
if i == -1:
raise ValueError("%s not found in combo box" % value)
else:
return i
[docs]class connect_combo_selection(BaseConnection):
def __init__(self, instance, prop, widget):
if not isinstance(getattr(type(instance), prop), SelectionCallbackProperty):
raise TypeError('connect_combo_selection requires a SelectionCallbackProperty')
super(connect_combo_selection, self).__init__(instance, prop, widget)
self.connect()
[docs] def update_prop(self, idx):
if idx == -1:
setattr(self._instance, self._prop, None)
else:
data_wrapper = self._widget.itemData(idx)
if data_wrapper is None:
setattr(self._instance, self._prop, None)
else:
setattr(self._instance, self._prop, data_wrapper.data)
[docs] def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
self._widget.currentIndexChanged.connect(self.update_prop)
self.update_widget(getattr(self._instance, self._prop))
[docs] def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
self._widget.currentIndexChanged.disconnect(self.update_prop)
[docs]class connect_list_selection(BaseConnection):
def __init__(self, instance, prop, widget):
"""
Connect a SelectionCallbackProperty with a QListWidget that supports
single-item selection.
"""
if not isinstance(getattr(type(instance), prop), SelectionCallbackProperty):
raise TypeError('connect_list_selection requires a SelectionCallbackProperty')
super(connect_list_selection, self).__init__(instance, prop, widget)
self.connect()
[docs] def update_prop(self):
if len(self._widget.selectedItems()) == 0:
setattr(self._instance, self._prop, None)
else:
data_wrapper = self._widget.selectedItems()[0].data(Qt.UserRole)
if data_wrapper is None:
setattr(self._instance, self._prop, None)
else:
setattr(self._instance, self._prop, data_wrapper.data)
[docs] def connect(self):
add_callback(self._instance, self._prop, self.update_widget)
self._widget.itemSelectionChanged.connect(self.update_prop)
self.update_widget(getattr(self._instance, self._prop))
[docs] def disconnect(self):
remove_callback(self._instance, self._prop, self.update_widget)
self._widget.itemSelectionChanged.disconnect(self.update_prop)