2003-11-12 16:34:20 -05:00
|
|
|
"""Provides global signal dispatching services."""
|
2003-07-02 19:13:10 -04:00
|
|
|
|
2003-11-12 16:34:20 -05:00
|
|
|
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
|
|
|
|
__cvsid__ = "$Id$"
|
|
|
|
__revision__ = "$Revision$"[11:-2]
|
2003-07-02 19:13:10 -04:00
|
|
|
|
2003-11-12 16:34:20 -05:00
|
|
|
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):
|
2006-06-10 20:13:05 -04:00
|
|
|
"""
|
|
|
|
Connect receiver to sender for signal.
|
2003-11-12 16:34:20 -05:00
|
|
|
|
2006-06-10 20:13:05 -04:00
|
|
|
* 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.
|
|
|
|
"""
|
2003-11-12 16:34:20 -05:00
|
|
|
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
|