Source code for schrodinger.ui.qt.datamapper
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
MSuper = QtWidgets.QDataWidgetMapper
[docs]class TrackingDataWidgetMapper(MSuper):
"""
This class augments QtWidgets.QDataWidgetMapper with three additional features:
1) Emits a widgetChanged signal whenever any mapped widget is modified by
the user
2) Provides an isDirty() method to determine whether any of the current
widget values differ from the model state.
3) Allows QButtonGroup objects to be mapped
"""
widgetChanged = QtCore.pyqtSignal()
[docs] def __init__(self, *args, **kwargs):
MSuper.__init__(self, *args, **kwargs)
self.all_widgets = []
[docs] def isDirty(self):
"""
Determines whether the values in the widgets match the values in the
model. This will happen if the user has edited widget values but not
submitted the changes.
This method is primarily intended to be used with the submitPolicy
set to ManualSubmit.
If the user edits a value back to its original state, isDirty() will
correctly return False.
"""
if self.currentIndex() == -1:
return False
model = self.model()
if self.orientation() == Qt.Horizontal:
count = model.columnCount()
else:
count = model.rowCount()
for i in range(count):
widget = self.mappedWidgetAt(i)
if not widget:
continue
prop_name = self.mappedPropertyName(widget)
value = widget.property(prop_name)
if self.orientation() == Qt.Horizontal:
row = self.currentIndex()
index = model.index(row, i)
else:
col = self.currentIndex()
index = model.index(i, col)
orig_value = model.data(index, Qt.EditRole)
if value != orig_value:
return True
return False
[docs] def addMapping(self, widget, section):
"""
See parent class for full documentation for this method. In addition,
this method connects the appropriate signal on the widget so that all
widget changes result in the widgetChanged signal being emitted.
This method also allows QButtonGroup to be mapped, which the parent
class does not. Only exclusive button groups will work correctly - non-
exclusive groups will only register the first checked button in the
group.
This override of addMapping also eliminates the optional propertyName
argument.
"""
if isinstance(widget, QtWidgets.QButtonGroup):
widget = MappableButtonGroup(widget)
MSuper.addMapping(self, widget, section, b'currentSelection')
elif isinstance(widget, QtWidgets.QComboBox):
# The default, currentText, does not work correctly, because there
# is no corresponding setter in QComboBox
MSuper.addMapping(self, widget, section, b'currentIndex')
else:
MSuper.addMapping(self, widget, section)
self.all_widgets.append(widget)
signal = self._findWidgetSignal(widget)
signal.connect(self.widgetChanged.emit)
[docs] def removeMapping(self, widget):
# See parent class for documentation
signal = self._findWidgetSignal(widget)
signal.disconnect(self.widgetChanged.emit)
MSuper.removeMapping(self, widget)
def _findWidgetSignal(self, widget):
"""
Returns the appropriate signal for the widget that indicates that the
contents of the widget has changed, using the SIGNAL_MAP.
"""
for widget_class, signal_name in SIGNAL_MAP.items():
if isinstance(widget, widget_class):
return getattr(widget, signal_name)
else:
raise TypeError('Unhandled widget type: %s' % type(widget))
[docs]class MappableButtonGroup(QtWidgets.QWidget):
"""
This widget is a container for a QButtonGroup used to enable button groups
to be mapped by the widget mapper.
"""
selectionChanged = QtCore.pyqtSignal(int)
[docs] def __init__(self, button_group, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self._button_group = button_group
for i, button in enumerate(button_group.buttons()):
self._button_group.setId(button, i)
button.toggled.connect(self.selectionChanged.emit)
currentSelection = QtCore.pyqtProperty(int,
fget=getCurrentSelection,
fset=setCurrentSelection)
# Maps widget types to the name of the signal indicating widget value changed
# Add widget types as needed
SIGNAL_MAP = {
QtWidgets.QLineEdit: 'textChanged',
QtWidgets.QComboBox: 'currentIndexChanged',
QtWidgets.QSpinBox: 'valueChanged',
QtWidgets.QDoubleSpinBox: 'valueChanged',
QtWidgets.QCheckBox: 'toggled',
MappableButtonGroup: 'selectionChanged',
}