X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8b9a4190f70909de9568f45389e7aa3ecbc66b8a..d0ee33f5c6908b4ac5e1364381f0ef00942e3936:/wxPython/wx/py/dispatcher.py diff --git a/wxPython/wx/py/dispatcher.py b/wxPython/wx/py/dispatcher.py index e2dee16487..4242179884 100644 --- a/wxPython/wx/py/dispatcher.py +++ b/wxPython/wx/py/dispatcher.py @@ -1,8 +1,260 @@ +"""Provides global signal dispatching services.""" -"""Renamer stub: provides a way to drop the wx prefix from wxPython objects.""" +__author__ = "Patrick K. O'Brien " +__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 '' % (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