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