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