]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/py/dispatcher.py
Assert correction.
[wxWidgets.git] / wxPython / wx / py / dispatcher.py
1 """Provides global signal dispatching services."""
2
3 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
4 __cvsid__ = "$Id$"
5 __revision__ = "$Revision$"[11:-2]
6
7 import exceptions
8 import types
9 import weakref
10
11
12 class DispatcherError(exceptions.Exception):
13 def __init__(self, args=None):
14 self.args = args
15
16
17 class Parameter:
18 """Used to represent default parameter values."""
19 def __repr__(self):
20 return self.__class__.__name__
21
22 class Any(Parameter): pass
23 Any = Any()
24
25 class Anonymous(Parameter): pass
26 Anonymous = Anonymous()
27
28
29 connections = {}
30 senders = {}
31 _boundMethods = weakref.WeakKeyDictionary()
32
33
34 def connect(receiver, signal=Any, sender=Any, weak=True):
35 """Connect receiver to sender for signal.
36
37 If sender is Any, receiver will receive signal from any sender.
38 If signal is Any, receiver will receive any signal from sender.
39 If sender is None, receiver will receive signal from Anonymous.
40 If signal is Any and sender is None, receiver will receive any
41 signal from Anonymous.
42 If signal is Any and sender is Any, receiver will receive any
43 signal from any sender.
44 If weak is true, weak references will be used."""
45 if signal is None:
46 raise DispatcherError, 'signal cannot be None'
47 if weak:
48 receiver = safeRef(receiver)
49 senderkey = id(sender)
50 signals = {}
51 if connections.has_key(senderkey):
52 signals = connections[senderkey]
53 else:
54 connections[senderkey] = signals
55 # Keep track of senders for cleanup.
56 if sender not in (None, Any):
57 def remove(object, senderkey=senderkey):
58 _removeSender(senderkey=senderkey)
59 # Skip objects that can not be weakly referenced, which means
60 # they won't be automatically cleaned up, but that's too bad.
61 try:
62 weakSender = weakref.ref(sender, remove)
63 senders[senderkey] = weakSender
64 except:
65 pass
66 receivers = []
67 if signals.has_key(signal):
68 receivers = signals[signal]
69 else:
70 signals[signal] = receivers
71 try:
72 receivers.remove(receiver)
73 except ValueError:
74 pass
75 receivers.append(receiver)
76
77 def disconnect(receiver, signal=Any, sender=Any, weak=True):
78 """Disconnect receiver from sender for signal.
79
80 Disconnecting is not required. The use of disconnect is the same as for
81 connect, only in reverse. Think of it as undoing a previous connection."""
82 if signal is None:
83 raise DispatcherError, 'signal cannot be None'
84 if weak:
85 receiver = safeRef(receiver)
86 senderkey = id(sender)
87 try:
88 receivers = connections[senderkey][signal]
89 except KeyError:
90 raise DispatcherError, \
91 'No receivers for signal %r from sender %s' % (signal, sender)
92 try:
93 receivers.remove(receiver)
94 except ValueError:
95 raise DispatcherError, \
96 'No connection to receiver %s for signal %r from sender %s' % \
97 (receiver, signal, sender)
98 _cleanupConnections(senderkey, signal)
99
100 def send(signal, sender=Anonymous, **kwds):
101 """Send signal from sender to all connected receivers.
102
103 Return a list of tuple pairs [(receiver, response), ... ].
104 If sender is not specified, signal is sent anonymously."""
105 senderkey = id(sender)
106 anykey = id(Any)
107 # Get receivers that receive *this* signal from *this* sender.
108 receivers = []
109 try:
110 receivers.extend(connections[senderkey][signal])
111 except KeyError:
112 pass
113 # Add receivers that receive *any* signal from *this* sender.
114 anyreceivers = []
115 try:
116 anyreceivers = connections[senderkey][Any]
117 except KeyError:
118 pass
119 for receiver in anyreceivers:
120 if receivers.count(receiver) == 0:
121 receivers.append(receiver)
122 # Add receivers that receive *this* signal from *any* sender.
123 anyreceivers = []
124 try:
125 anyreceivers = connections[anykey][signal]
126 except KeyError:
127 pass
128 for receiver in anyreceivers:
129 if receivers.count(receiver) == 0:
130 receivers.append(receiver)
131 # Add receivers that receive *any* signal from *any* sender.
132 anyreceivers = []
133 try:
134 anyreceivers = connections[anykey][Any]
135 except KeyError:
136 pass
137 for receiver in anyreceivers:
138 if receivers.count(receiver) == 0:
139 receivers.append(receiver)
140 # Call each receiver with whatever arguments it can accept.
141 # Return a list of tuple pairs [(receiver, response), ... ].
142 responses = []
143 for receiver in receivers:
144 if type(receiver) is weakref.ReferenceType \
145 or isinstance(receiver, BoundMethodWeakref):
146 # Dereference the weak reference.
147 receiver = receiver()
148 if receiver is None:
149 # This receiver is dead, so skip it.
150 continue
151 response = _call(receiver, signal=signal, sender=sender, **kwds)
152 responses += [(receiver, response)]
153 return responses
154
155 def _call(receiver, **kwds):
156 """Call receiver with only arguments it can accept."""
157 ## if type(receiver) is types.InstanceType:
158 if hasattr(receiver, '__call__') and \
159 (hasattr(receiver.__call__, 'im_func') or hasattr(receiver.__call__, 'im_code')):
160 # receiver is a class instance; assume it is callable.
161 # Reassign receiver to the actual method that will be called.
162 receiver = receiver.__call__
163 if hasattr(receiver, 'im_func'):
164 # receiver is a method. Drop the first argument, usually 'self'.
165 fc = receiver.im_func.func_code
166 acceptable = fc.co_varnames[1:fc.co_argcount]
167 elif hasattr(receiver, 'func_code'):
168 # receiver is a function.
169 fc = receiver.func_code
170 acceptable = fc.co_varnames[0:fc.co_argcount]
171 else:
172 raise DispatcherError, 'Unknown receiver %s of type %s' % (receiver, type(receiver))
173 if not (fc.co_flags & 8):
174 # fc does not have a **kwds type parameter, therefore
175 # remove unacceptable arguments.
176 for arg in kwds.keys():
177 if arg not in acceptable:
178 del kwds[arg]
179 return receiver(**kwds)
180
181
182 def safeRef(object):
183 """Return a *safe* weak reference to a callable object."""
184 if hasattr(object, 'im_self'):
185 if object.im_self is not None:
186 # Turn a bound method into a BoundMethodWeakref instance.
187 # Keep track of these instances for lookup by disconnect().
188 selfkey = object.im_self
189 funckey = object.im_func
190 if not _boundMethods.has_key(selfkey):
191 _boundMethods[selfkey] = weakref.WeakKeyDictionary()
192 if not _boundMethods[selfkey].has_key(funckey):
193 _boundMethods[selfkey][funckey] = \
194 BoundMethodWeakref(boundMethod=object)
195 return _boundMethods[selfkey][funckey]
196 return weakref.ref(object, _removeReceiver)
197
198
199 class BoundMethodWeakref:
200 """BoundMethodWeakref class."""
201
202 def __init__(self, boundMethod):
203 """Return a weak-reference-like instance for a bound method."""
204 self.isDead = 0
205 def remove(object, self=self):
206 """Set self.isDead to true when method or instance is destroyed."""
207 self.isDead = 1
208 _removeReceiver(receiver=self)
209 self.weakSelf = weakref.ref(boundMethod.im_self, remove)
210 self.weakFunc = weakref.ref(boundMethod.im_func, remove)
211
212 def __repr__(self):
213 """Return the closest representation."""
214 return '<bound method weakref for %s.%s>' % (self.weakSelf, self.weakFunc)
215
216 def __call__(self):
217 """Return a strong reference to the bound method."""
218 if self.isDead:
219 return None
220 else:
221 object = self.weakSelf()
222 method = self.weakFunc().__name__
223 try: # wxPython hack to handle wxDead objects.
224 return getattr(object, method)
225 except AttributeError:
226 ## _removeReceiver(receiver=self)
227 return None
228
229
230 def _removeReceiver(receiver):
231 """Remove receiver from connections."""
232 for senderkey in connections.keys():
233 for signal in connections[senderkey].keys():
234 receivers = connections[senderkey][signal]
235 try:
236 receivers.remove(receiver)
237 except:
238 pass
239 _cleanupConnections(senderkey, signal)
240
241 def _cleanupConnections(senderkey, signal):
242 """Delete any empty signals for senderkey. Delete senderkey if empty."""
243 receivers = connections[senderkey][signal]
244 if not receivers:
245 # No more connected receivers. Therefore, remove the signal.
246 signals = connections[senderkey]
247 del signals[signal]
248 if not signals:
249 # No more signal connections. Therefore, remove the sender.
250 _removeSender(senderkey)
251
252 def _removeSender(senderkey):
253 """Remove senderkey from connections."""
254 del connections[senderkey]
255 # Senderkey will only be in senders dictionary if sender
256 # could be weakly referenced.
257 try:
258 del senders[senderkey]
259 except:
260 pass