if True: from PySide import QtGui from PySide import QtCore import threading from collections import defaultdict # resizing a window too fast seems to allow for the window manager to drop some requests. # for experimentation, we resize a label within a fixed-size window. w = QtGui.QLabel() w.show() w.setGeometry(QtCore.QRect(100,100,400,400)) propagators = { 'width': lambda src, dest: dest.__self__.setGeometry(QtCore.QRect(dest.__self__.x(), dest.__self__.y(), src.value, dest.__self__.height())), 'height': lambda src, dest: dest.__self__.setGeometry(QtCore.QRect(dest.__self__.x(), dest.__self__.y(), dest.__self__.width(), src.value)) } def propagate(src, dest): propagators[dest.__name__](src, dest) def connect(src, dest): src.register('set', lambda _: propagate(src, dest)) class Model: def __init__(self): self._lock = threading.Lock() self._eventTypeToCallbacks = defaultdict(dict) # key: string i.e. event type, value: {callback} self._eventTypeToCurrent = dict() self._nextId = 0 def notify(self, eventType, info): with self._lock: self._eventTypeToCurrent[eventType] = info # avoid race conditions by caching the list of callbacks, that way if # one of the callbacks modifies the list of callbacks, it won't cause # issues. Also, it prevents deadlocks if one of the callbacks tries to # perform an operation which would try to acquire the lock. cachedCallbacks = [c for c in self._eventTypeToCallbacks[eventType].values()] for c in cachedCallbacks: c(info) def register(self, eventType, callback): with self._lock: callbackId = self._nextId self._nextId = self._nextId + 1 self._eventTypeToCallbacks[eventType][callbackId] = callback cachedHasCurrent = False if eventType in self._eventTypeToCurrent: cachedCurrent = self._eventTypeToCurrent[eventType] cachedHasCurrent = True if cachedHasCurrent: callback(cachedCurrent) return callbackId def unregister(self, event, callbackId): with self._lock: del self._eventTypeToCallbacks[eventType][callbackId] class Connection: def __init__(self, src, dest): self.src = src self.dest = dest class ModelFragment(Model): def __init__(self, name, v): super(ModelFragment, self).__init__() self.name = name if isinstance(v, ModelFragment): self.value = v.value else: self.value = v self.notify('set', self.value) def setValue(self, v): self.value = v self.notify('set', self.value) def __rshift__(self, other): return Connection(self, other) class ModelPropertySet(Model): def _getter(self, p): def real_getter(self): return self._properties[p] return real_getter def _setter(self, p): def real_setter(self, v): if isinstance(v, Connection): connect(v.src, v.dest) else: self._properties[p].setValue(v) return real_setter def __init__(self, theClass, properties): super(ModelPropertySet, self).__init__() self._properties = {} for (p, v) in properties.items(): self._properties[p] = ModelFragment(p, v) setattr(theClass, p, property(self._getter(p), self._setter(p))) class ModelNode(ModelPropertySet): def __init__(self): super(ModelNode, self).__init__(ModelNode, { 'width': 200, 'height': 200 }) m = ModelNode() l = QtGui.QLabel() l.setStyleSheet("QLabel { background-color : rgba(0,0,255,100%); color : black; }") l.setParent(w) l.show() l.setGeometry(QtCore.QRect(100,100,100,100)) m.width >>= l.width m.height >>= l.height m.width = 50