Mercurial > fife-parpg
comparison engine/extensions/pychan/events.py @ 232:f10a35efebc0
Memory leak fix for pychan
Thanks to phoku for doing much of this patch
author | cheesesucker@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Mon, 23 Mar 2009 02:06:14 +0000 |
parents | 6d09d310943b |
children | e4dfdf4c11fd |
comparison
equal
deleted
inserted
replaced
231:c62ed457e954 | 232:f10a35efebc0 |
---|---|
43 | 43 |
44 import exceptions | 44 import exceptions |
45 from internal import get_manager | 45 from internal import get_manager |
46 import tools | 46 import tools |
47 import traceback | 47 import traceback |
48 import weakref | |
48 | 49 |
49 EVENTS = [ | 50 EVENTS = [ |
50 "mouseEntered", | 51 "mouseEntered", |
51 "mouseExited", | 52 "mouseExited", |
52 "mousePressed", | 53 "mousePressed", |
109 return | 110 return |
110 if not self.events: | 111 if not self.events: |
111 return | 112 return |
112 if self.debug: print "Attach:",self | 113 if self.debug: print "Attach:",self |
113 self.doAttach(widget.real_widget) | 114 self.doAttach(widget.real_widget) |
114 self.widget = widget | 115 self.widget_ref = weakref.ref(widget) |
115 self.is_attached = True | 116 self.is_attached = True |
116 | 117 |
117 def detach(self): | 118 def detach(self): |
118 """ | 119 """ |
119 Stop receiving events. | 120 Stop receiving events. |
120 No need to call this manually. | 121 No need to call this manually. |
121 """ | 122 """ |
122 if not self.is_attached: | 123 if not self.is_attached: |
123 return | 124 return |
124 if self.debug: print "Detach:",self | 125 if self.debug: print "Detach:",self |
125 self.doDetach(self.widget.real_widget) | |
126 self.widget = None | |
127 self.is_attached = False | 126 self.is_attached = False |
128 | 127 |
129 def _redirectEvent(self,name,event): | 128 def _redirectEvent(self,name,event): |
130 self.indent += 4 | 129 self.indent += 4 |
131 try: | 130 try: |
150 return get_manager().hook.translate_key_event(event) | 149 return get_manager().hook.translate_key_event(event) |
151 return event | 150 return event |
152 | 151 |
153 class _ActionEventListener(EventListenerBase,guichan.ActionListener): | 152 class _ActionEventListener(EventListenerBase,guichan.ActionListener): |
154 def __init__(self):super(_ActionEventListener,self).__init__() | 153 def __init__(self):super(_ActionEventListener,self).__init__() |
155 def doAttach(self,real_widget): real_widget.addActionListener(self) | 154 def doAttach(self,real_widget): real_widget.addActionListener(self) |
156 def doDetach(self,real_widget): real_widget.removeActionListener(self) | 155 def doDetach(self,real_widget): real_widget.removeActionListener(self) |
157 | 156 |
158 def action(self,e): self._redirectEvent("action",e) | 157 def action(self,e): self._redirectEvent("action",e) |
159 | 158 |
160 class _MouseEventListener(EventListenerBase,guichan.MouseListener): | 159 class _MouseEventListener(EventListenerBase,guichan.MouseListener): |
161 def __init__(self):super(_MouseEventListener,self).__init__() | 160 def __init__(self):super(_MouseEventListener,self).__init__() |
162 def doAttach(self,real_widget): real_widget.addMouseListener(self) | 161 def doAttach(self,real_widget): real_widget.addMouseListener(self) |
163 def doDetach(self,real_widget): real_widget.removeMouseListener(self) | 162 def doDetach(self,real_widget): real_widget.removeMouseListener(self) |
164 | 163 |
165 def mouseEntered(self,e): self._redirectEvent("mouseEntered",e) | 164 def mouseEntered(self,e): self._redirectEvent("mouseEntered",e) |
166 def mouseExited(self,e): self._redirectEvent("mouseExited",e) | 165 def mouseExited(self,e): self._redirectEvent("mouseExited",e) |
167 def mousePressed(self,e): self._redirectEvent("mousePressed",e) | 166 def mousePressed(self,e): self._redirectEvent("mousePressed",e) |
172 def mouseWheelMovedDown(self,e): self._redirectEvent("mouseWheelMovedDown",e) | 171 def mouseWheelMovedDown(self,e): self._redirectEvent("mouseWheelMovedDown",e) |
173 def mouseDragged(self,e): self._redirectEvent("mouseDragged",e) | 172 def mouseDragged(self,e): self._redirectEvent("mouseDragged",e) |
174 | 173 |
175 class _KeyEventListener(EventListenerBase,guichan.KeyListener): | 174 class _KeyEventListener(EventListenerBase,guichan.KeyListener): |
176 def __init__(self):super(_KeyEventListener,self).__init__() | 175 def __init__(self):super(_KeyEventListener,self).__init__() |
177 def doAttach(self,real_widget): real_widget.addKeyListener(self) | 176 def doAttach(self,real_widget): real_widget.addKeyListener(self) |
178 def doDetach(self,real_widget): real_widget.removeKeyListener(self) | 177 def doDetach(self,real_widget): real_widget.removeKeyListener(self) |
179 | 178 |
180 def keyPressed(self,e): self._redirectEvent("keyPressed",e) | 179 def keyPressed(self,e): self._redirectEvent("keyPressed",e) |
181 def keyReleased(self,e): self._redirectEvent("keyReleased",e) | 180 def keyReleased(self,e): self._redirectEvent("keyReleased",e) |
182 | 181 |
201 When a new event is captured the mapper attaches itself | 200 When a new event is captured the mapper attaches itself |
202 automatically. The widget doesn't need to handle that. | 201 automatically. The widget doesn't need to handle that. |
203 """ | 202 """ |
204 def __init__(self,widget): | 203 def __init__(self,widget): |
205 super(EventMapper,self).__init__() | 204 super(EventMapper,self).__init__() |
206 self.widget = widget | 205 self.widget_ref = weakref.ref(widget) |
206 self.callbacks = {} | |
207 self.listener = { | 207 self.listener = { |
208 KEY_EVENT : _KeyEventListener(), | 208 KEY_EVENT : _KeyEventListener(), |
209 ACTION_EVENT : _ActionEventListener(), | 209 ACTION_EVENT : _ActionEventListener(), |
210 MOUSE_EVENT : _MouseEventListener(), | 210 MOUSE_EVENT : _MouseEventListener(), |
211 } | 211 } |
212 self.is_attached = False | 212 self.is_attached = False |
213 self.debug = get_manager().debug | 213 self.debug = get_manager().debug |
214 | 214 |
215 def __repr__(self): | 215 def __repr__(self): |
216 return "EventMapper(%s)" % repr(self.widget) | 216 return "EventMapper(%s)" % repr(self.widget_ref()) |
217 | 217 |
218 def attach(self): | |
219 for listener in self.listener.values(): | |
220 listener.attach() | |
221 | |
222 def detach(self): | |
223 for listener in self.listener.values(): | |
224 listener.detach() | |
225 | |
218 | 226 |
219 def capture(self,event_name,callback,group_name): | 227 def capture(self,event_name,callback,group_name): |
220 if event_name not in EVENTS: | 228 if event_name not in EVENTS: |
221 raise exceptions.RuntimeError("Unknown eventname: " + event_name) | 229 raise exceptions.RuntimeError("Unknown eventname: " + event_name) |
222 | 230 |
223 if callback is None: | 231 if callback is None: |
224 if self.isCaptured(event_name,group_name): | 232 if self.isCaptured(event_name,group_name): |
225 self.removeEvent(event_name,group_name) | 233 self.removeEvent(event_name,group_name) |
226 elif self.debug: | 234 elif self.debug: |
227 print CALLBACK_NONE_MESSAGE % str(self.widget) | 235 print CALLBACK_NONE_MESSAGE % str(self.widget_ref()) |
228 return | 236 return |
229 self.addEvent(event_name,callback,group_name) | 237 self.addEvent(event_name,callback,group_name) |
230 | 238 |
231 def isCaptured(self,event_name,group_name="default"): | 239 def isCaptured(self,event_name,group_name="default"): |
232 return ("%s/%s" % (event_name,group_name)) in self.getCapturedEvents() | 240 return ("%s/%s" % (event_name,group_name)) in self.getCapturedEvents() |
243 return self.listener[getEventType(event_name)] | 251 return self.listener[getEventType(event_name)] |
244 | 252 |
245 def removeEvent(self,event_name,group_name): | 253 def removeEvent(self,event_name,group_name): |
246 listener = self.getListener(event_name) | 254 listener = self.getListener(event_name) |
247 del listener.events[event_name][group_name] | 255 del listener.events[event_name][group_name] |
256 | |
248 if not listener.events[event_name]: | 257 if not listener.events[event_name]: |
249 del listener.events[event_name] | 258 del listener.events[event_name] |
250 if not listener.events: | 259 if not listener.events: |
251 listener.detach() | 260 listener.detach() |
261 | |
262 del self.callbacks[group_name][event_name] | |
263 if len(self.callbacks[group_name]) <= 0: | |
264 del self.callbacks[group_name] | |
252 | 265 |
253 def addEvent(self,event_name,callback,group_name): | 266 def addEvent(self,event_name,callback,group_name): |
254 if not callable(callback): | 267 if not callable(callback): |
255 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) | 268 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) |
256 | 269 # The closure self needs to keep a weak ref. |
270 # Otherwise the GC has problems. | |
271 self_ref = weakref.ref(self) | |
272 | |
273 # Set up callback dictionary. This should fix some GC issues | |
274 if not self.callbacks.has_key(group_name): | |
275 self.callbacks[group_name] = {} | |
276 | |
277 if not self.callbacks[group_name].has_key(event_name): | |
278 self.callbacks[group_name][event_name] = {} | |
279 | |
280 self.callbacks[group_name][event_name] = callback | |
281 | |
282 | |
257 def captured_f(event): | 283 def captured_f(event): |
258 tools.applyOnlySuitable(callback,event=event,widget=self.widget) | 284 tools.applyOnlySuitable(self_ref().callbacks[group_name][event_name],event=event,widget=self_ref().widget_ref()) |
259 | 285 |
260 listener = self.getListener(event_name) | 286 listener = self.getListener(event_name) |
261 | 287 |
262 if event_name not in listener.events: | 288 if event_name not in listener.events: |
263 listener.events[event_name] = {group_name : captured_f} | 289 listener.events[event_name] = {group_name : captured_f} |
264 else: | 290 else: |
265 listener.events[event_name][group_name] = captured_f | 291 listener.events[event_name][group_name] = captured_f |
266 listener.attach(self.widget) | 292 listener.attach(self.widget_ref()) |
267 | 293 |
268 | 294 |
269 def splitEventDescriptor(name): | 295 def splitEventDescriptor(name): |
270 """ Utility function to split "widgetName/eventName" descriptions into tuples. """ | 296 """ Utility function to split "widgetName/eventName" descriptions into tuples. """ |
271 L = name.split("/") | 297 L = name.split("/") |
276 elif L[1] not in EVENTS: | 302 elif L[1] not in EVENTS: |
277 raise exceptions.RuntimeError("Unknown event name: " + name) | 303 raise exceptions.RuntimeError("Unknown event name: " + name) |
278 if len(L) == 2: | 304 if len(L) == 2: |
279 L = L[0],L[1],"default" | 305 L = L[0],L[1],"default" |
280 return L | 306 return L |
281 | |
282 |