]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/py/dispatcher.py
"wxWindows" --> "wxWidgets"
[wxWidgets.git] / wxPython / wx / py / dispatcher.py
index 9ce9935b8ed5fe70c47047517d7362b0d9aef685..4242179884c2dc6863735b3157029885ecf05f93 100644 (file)
+"""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