+"""Provides global signal dispatching services."""
-"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
+__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
+__cvsid__ = "$Id$"
+__revision__ = "$Revision$"[11:-2]
-from wx import _rename
-from wxPython.py import dispatcher
-_rename(globals(), dispatcher.__dict__, modulename='py.dispatcher')
-del dispatcher
-del _rename
+import exceptions
+import types
+import weakref
+
+
+class DispatcherError(exceptions.Exception):
+ def __init__(self, args=None):
+ self.args = args
+
+
+class Parameter:
+ """Used to represent default parameter values."""
+ def __repr__(self):
+ return self.__class__.__name__
+
+class Any(Parameter): pass
+Any = Any()
+
+class Anonymous(Parameter): pass
+Anonymous = Anonymous()
+
+
+connections = {}
+senders = {}
+_boundMethods = weakref.WeakKeyDictionary()
+
+
+def connect(receiver, signal=Any, sender=Any, weak=True):
+ """Connect receiver to sender for signal.
+
+ If sender is Any, receiver will receive signal from any sender.
+ If signal is Any, receiver will receive any signal from sender.
+ If sender is None, receiver will receive signal from Anonymous.
+ If signal is Any and sender is None, receiver will receive any
+ signal from Anonymous.
+ If signal is Any and sender is Any, receiver will receive any
+ signal from any sender.
+ If weak is true, weak references will be used."""
+ if signal is None:
+ raise DispatcherError, 'signal cannot be None'
+ if weak:
+ receiver = safeRef(receiver)
+ senderkey = id(sender)
+ signals = {}
+ if connections.has_key(senderkey):
+ signals = connections[senderkey]
+ else:
+ connections[senderkey] = signals
+ # Keep track of senders for cleanup.
+ if sender not in (None, Any):
+ def remove(object, senderkey=senderkey):
+ _removeSender(senderkey=senderkey)
+ # Skip objects that can not be weakly referenced, which means
+ # they won't be automatically cleaned up, but that's too bad.
+ try:
+ weakSender = weakref.ref(sender, remove)
+ senders[senderkey] = weakSender
+ except:
+ pass
+ receivers = []
+ if signals.has_key(signal):
+ receivers = signals[signal]
+ else:
+ signals[signal] = receivers
+ try:
+ receivers.remove(receiver)
+ except ValueError:
+ pass
+ receivers.append(receiver)
+
+def disconnect(receiver, signal=Any, sender=Any, weak=True):
+ """Disconnect receiver from sender for signal.
+
+ Disconnecting is not required. The use of disconnect is the same as for
+ connect, only in reverse. Think of it as undoing a previous connection."""
+ if signal is None:
+ raise DispatcherError, 'signal cannot be None'
+ if weak:
+ receiver = safeRef(receiver)
+ senderkey = id(sender)
+ try:
+ receivers = connections[senderkey][signal]
+ except KeyError:
+ raise DispatcherError, \
+ 'No receivers for signal %r from sender %s' % (signal, sender)
+ try:
+ receivers.remove(receiver)
+ except ValueError:
+ raise DispatcherError, \
+ 'No connection to receiver %s for signal %r from sender %s' % \
+ (receiver, signal, sender)
+ _cleanupConnections(senderkey, signal)
+
+def send(signal, sender=Anonymous, **kwds):
+ """Send signal from sender to all connected receivers.
+
+ Return a list of tuple pairs [(receiver, response), ... ].
+ If sender is not specified, signal is sent anonymously."""
+ senderkey = id(sender)
+ anykey = id(Any)
+ # Get receivers that receive *this* signal from *this* sender.
+ receivers = []
+ try:
+ receivers.extend(connections[senderkey][signal])
+ except KeyError:
+ pass
+ # Add receivers that receive *any* signal from *this* sender.
+ anyreceivers = []
+ try:
+ anyreceivers = connections[senderkey][Any]
+ except KeyError:
+ pass
+ for receiver in anyreceivers:
+ if receivers.count(receiver) == 0:
+ receivers.append(receiver)
+ # Add receivers that receive *this* signal from *any* sender.
+ anyreceivers = []
+ try:
+ anyreceivers = connections[anykey][signal]
+ except KeyError:
+ pass
+ for receiver in anyreceivers:
+ if receivers.count(receiver) == 0:
+ receivers.append(receiver)
+ # Add receivers that receive *any* signal from *any* sender.
+ anyreceivers = []
+ try:
+ anyreceivers = connections[anykey][Any]
+ except KeyError:
+ pass
+ for receiver in anyreceivers:
+ if receivers.count(receiver) == 0:
+ receivers.append(receiver)
+ # Call each receiver with whatever arguments it can accept.
+ # Return a list of tuple pairs [(receiver, response), ... ].
+ responses = []
+ for receiver in receivers:
+ if type(receiver) is weakref.ReferenceType \
+ or isinstance(receiver, BoundMethodWeakref):
+ # Dereference the weak reference.
+ receiver = receiver()
+ if receiver is None:
+ # This receiver is dead, so skip it.
+ continue
+ response = _call(receiver, signal=signal, sender=sender, **kwds)
+ responses += [(receiver, response)]
+ return responses
+
+def _call(receiver, **kwds):
+ """Call receiver with only arguments it can accept."""
+## if type(receiver) is types.InstanceType:
+ if hasattr(receiver, '__call__') and \
+ (hasattr(receiver.__call__, 'im_func') or hasattr(receiver.__call__, 'im_code')):
+ # receiver is a class instance; assume it is callable.
+ # Reassign receiver to the actual method that will be called.
+ receiver = receiver.__call__
+ if hasattr(receiver, 'im_func'):
+ # receiver is a method. Drop the first argument, usually 'self'.
+ fc = receiver.im_func.func_code
+ acceptable = fc.co_varnames[1:fc.co_argcount]
+ elif hasattr(receiver, 'func_code'):
+ # receiver is a function.
+ fc = receiver.func_code
+ acceptable = fc.co_varnames[0:fc.co_argcount]
+ else:
+ raise DispatcherError, 'Unknown receiver %s of type %s' % (receiver, type(receiver))
+ if not (fc.co_flags & 8):
+ # fc does not have a **kwds type parameter, therefore
+ # remove unacceptable arguments.
+ for arg in kwds.keys():
+ if arg not in acceptable:
+ del kwds[arg]
+ return receiver(**kwds)
+
+
+def safeRef(object):
+ """Return a *safe* weak reference to a callable object."""
+ if hasattr(object, 'im_self'):
+ if object.im_self is not None:
+ # Turn a bound method into a BoundMethodWeakref instance.
+ # Keep track of these instances for lookup by disconnect().
+ selfkey = object.im_self
+ funckey = object.im_func
+ if not _boundMethods.has_key(selfkey):
+ _boundMethods[selfkey] = weakref.WeakKeyDictionary()
+ if not _boundMethods[selfkey].has_key(funckey):
+ _boundMethods[selfkey][funckey] = \
+ BoundMethodWeakref(boundMethod=object)
+ return _boundMethods[selfkey][funckey]
+ return weakref.ref(object, _removeReceiver)
+
+
+class BoundMethodWeakref:
+ """BoundMethodWeakref class."""
+
+ def __init__(self, boundMethod):
+ """Return a weak-reference-like instance for a bound method."""
+ self.isDead = 0
+ def remove(object, self=self):
+ """Set self.isDead to true when method or instance is destroyed."""
+ self.isDead = 1
+ _removeReceiver(receiver=self)
+ self.weakSelf = weakref.ref(boundMethod.im_self, remove)
+ self.weakFunc = weakref.ref(boundMethod.im_func, remove)
+
+ def __repr__(self):
+ """Return the closest representation."""
+ return '<bound method weakref for %s.%s>' % (self.weakSelf, self.weakFunc)
+
+ def __call__(self):
+ """Return a strong reference to the bound method."""
+ if self.isDead:
+ return None
+ else:
+ object = self.weakSelf()
+ method = self.weakFunc().__name__
+ try: # wxPython hack to handle wxDead objects.
+ return getattr(object, method)
+ except AttributeError:
+## _removeReceiver(receiver=self)
+ return None
+
+
+def _removeReceiver(receiver):
+ """Remove receiver from connections."""
+ for senderkey in connections.keys():
+ for signal in connections[senderkey].keys():
+ receivers = connections[senderkey][signal]
+ try:
+ receivers.remove(receiver)
+ except:
+ pass
+ _cleanupConnections(senderkey, signal)
+
+def _cleanupConnections(senderkey, signal):
+ """Delete any empty signals for senderkey. Delete senderkey if empty."""
+ receivers = connections[senderkey][signal]
+ if not receivers:
+ # No more connected receivers. Therefore, remove the signal.
+ signals = connections[senderkey]
+ del signals[signal]
+ if not signals:
+ # No more signal connections. Therefore, remove the sender.
+ _removeSender(senderkey)
+
+def _removeSender(senderkey):
+ """Remove senderkey from connections."""
+ del connections[senderkey]
+ # Senderkey will only be in senders dictionary if sender
+ # could be weakly referenced.
+ try:
+ del senders[senderkey]
+ except:
+ pass