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