git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@25782 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			261 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Provides global signal dispatching services."""
 | 
						|
 | 
						|
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
 | 
						|
__cvsid__ = "$Id$"
 | 
						|
__revision__ = "$Revision$"[11:-2]
 | 
						|
 | 
						|
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
 |