Source code for schrodinger.ui.qt.xsignals

"""
XSignal is a drop-in replacement for pyqtSignals that provides slot information,
slot reordering, and pre-emit validation.

Copyright Schrodinger, LLC. All rights reserved.
"""
from schrodinger.models import parameters
from schrodinger.Qt import QtCore


[docs]def is_xsignal(obj): """ Check whether an object is an xsignal (more specifically, a BoundXSignal). """ try: return obj.__class__.__name__ == 'BoundXSignal' except AttributeError: return False
[docs]class XSignal:
[docs] def __init__(self, *args): super().__init__() class BoundXSignal(parameters.CompoundParam): """ The bound signal class must be created during init so that the underlying pyqtSignal can be declared with the same arguments as the XSignal instance. For example, fooSignal = XSignal(int, list) -> pyqtSignals(int, list). """ signal = QtCore.pyqtSignal(*args) # *args from XSignal.__init__ _slots = parameters.ListParam() def __init__(self, signal_name, owner): super().__init__() self._slots.mutated.connect(self._on_slotsMutated) self._validation_funcs = [] self._signal_name = signal_name self._owner_class = owner def _on_slotsMutated(self, current, previous): """ Respond to changes in the _slots list. :param current: the current state of the slots list :param previous: the previous state of the slots list """ if previous: self.signal.disconnect() for slot in current: self.signal.connect(slot) def connect(self, slot): """ Wraps the normal pyqtSignal.connect """ self._slots.append(slot) def connectBefore(self, existing_slot, new_slot): """ Connects a new slot to be called before an existing slot. :param existing_slot: the new slot will go before this one """ idx = self._slots.index(existing_slot) self._slots.insert(idx, new_slot) def connectAfter(self, existing_slot, new_slot): """ Connects a new slot to be called after an existing slot. :param existing_slot: the new slot will go after this one """ idx = self._slots.index(existing_slot) self._slots.insert(idx + 1, new_slot) def connectAt(self, index, new_slot): """ Connects a new slot inserted at a specified index. :param index: the insertion index """ self._slots.insert(index, new_slot) def disconnect(self, slot=None): """ Wraps the normal pyqtSignal.connect """ if slot is None: self._slots = [] else: self._slots.remove(slot) def getSlots(self): return list(self._slots) def addValidationFunc(self, func): """ Add a function that will be called prior to emitting the underlying pyqtSignal. If the function explicitly returns False, the signal will not actually be emitted. Despite the name, the function does not necessarily need to do any validation, and can be used for any logic that needs to be executed prior to emitting the signal. :param func: a function that optionally returns False """ self._validation_funcs.append(func) def removeValidationFunc(self, func): self._validation_funcs.remove(func) def emit(self, *args): """ Call any validation functions and if none of them explicitly returns False, emit the signal. """ for func in self._validation_funcs: if func() is False: return False self.signal.emit(*args) return True def __str__(self): return self.__repr__() def __repr__(self): return '<bound XSignal %s of %s>' % (self._signal_name, self._owner_class) self.BoundXSignal = BoundXSignal
def __set_name__(self, owner, name): self._signal_name = name self.instance_attr_name = name + '_bound_xsignal' def __get__(self, instance, owner_class): if instance is None: return self bound_xsignal = getattr(instance, self.instance_attr_name, None) if bound_xsignal is None: bound_xsignal = self.BoundXSignal(self._signal_name, owner_class) setattr(instance, self.instance_attr_name, bound_xsignal) return bound_xsignal