comparison engine/python/fife/extensions/pychan/events.py @ 378:64738befdf3b

bringing in the changes from the build_system_rework branch in preparation for the 0.3.0 release. This commit will require the Jan2010 devkit. Clients will also need to be modified to the new way to import fife.
author vtchill@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 11 Jan 2010 23:34:52 +0000
parents
children eab690c748a3
comparison
equal deleted inserted replaced
377:fe6fb0e0ed23 378:64738befdf3b
1 # -*- coding: utf-8 -*-
2
3 # ####################################################################
4 # Copyright (C) 2005-2009 by the FIFE team
5 # http://www.fifengine.de
6 # This file is part of FIFE.
7 #
8 # FIFE is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the
20 # Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 # ####################################################################
23
24 """\
25 PyChan event handling (internal).
26 =================================
27
28 Users shouldn't need to use this module directly.
29 L{widgets.Widget.capture} and L{widgets.Widget.mapEvents} provide
30 a convenient API to capture events.
31
32 Nevertheless to understand how its supposed to work
33 take a look at L{EventMapper} and L{EventListenerBase}
34
35 Event callbacks
36 ---------------
37
38 You can either write callbacks yourself or
39 use L{tools.callBackWithArguments} or L{tools.attrSetCallback}
40 to generate suitable callbacks.
41
42 Here's an example callback::
43 def dumpEventInfo(event=0,widget=0):
44 print widget, " received the event ", event
45
46 Note the signature - C{event} and C{widget} are keyword
47 arguments passed to the callback. If doesn't accept either
48 C{event} or C{widget} as argument, these are not passed.
49
50 This way a simple function which ignores C{event} or C{widget}
51 can be used, while they are available if needed.
52
53 Currently only one callback can be set per event. In case
54 you don't want to write your own callback that dispatches
55 to different callbacks you can use L{tools.chainCallbacks}.
56
57 Available Events
58 ----------------
59
60 """
61
62 from compat import guichan
63 import widgets
64
65 import exceptions
66 from internal import get_manager
67 import tools
68 import traceback
69 import weakref
70 from fife.extensions import fife_timer as timer
71
72 EVENTS = [
73 "mouseEntered",
74 "mouseExited",
75 "mousePressed",
76 "mouseReleased",
77 "mouseClicked",
78 "mouseMoved",
79 "mouseWheelMovedUp",
80 "mouseWheelMovedDown",
81 "mouseDragged",
82 "action",
83 "keyPressed",
84 "keyReleased",
85 ]
86
87 # Add the EVENTS to the docs.
88 __doc__ += "".join([" - %s\n" % event for event in EVENTS])
89
90 # The line before seems to leak the variable event into the global namespace ... remove that!
91 # This is a python problem, addressed in python3
92 try: del event
93 except:pass
94
95 MOUSE_EVENT, KEY_EVENT, ACTION_EVENT = range(3)
96 def getEventType(name):
97 if "mouse" in name:
98 return MOUSE_EVENT
99 if "key" in name:
100 return KEY_EVENT
101 return ACTION_EVENT
102
103
104 CALLBACK_NONE_MESSAGE = """\
105 You passed None as parameter to %s.capture, which would normally remove a mapped event.
106 But there was no event mapped. Did you accidently call a function instead of passing it?
107 """
108 class EventListenerBase(object):
109 """
110 Redirector for event callbacks.
111 Use *only* from L{EventMapper}.
112
113 This class uses the SWIG director feature - overriden
114 virtual methods are called from C++ to - listen to
115 Guichan events.
116
117 """
118 def __init__(self):
119 super(EventListenerBase,self).__init__()
120 self.events = {}
121 self.indent = 0
122 self.debug = get_manager().debug
123 self.is_attached = False
124
125 def attach(self,widget):
126 """
127 Start receiving events.
128 No need to call this manually.
129 """
130
131 if self.is_attached:
132 return
133 if not self.events:
134 return
135 if self.debug: print "Attach:",self
136 self.doAttach(widget.real_widget)
137 self.widget_ref = weakref.ref(widget)
138 self.is_attached = True
139
140 def detach(self):
141 """
142 Stop receiving events.
143 No need to call this manually.
144 """
145 if not self.is_attached:
146 return
147 if self.debug: print "Detach:",self
148 self.is_attached = False
149
150 def _redirectEvent(self,name,event):
151 self.indent += 4
152 try:
153 event = self.translateEvent(getEventType(name), event)
154 if name in self.events:
155 if self.debug: print "-"*self.indent, name
156 for f in self.events[name].itervalues():
157 def delayed_f():
158 f( event )
159 timer.delayCall(0,delayed_f)
160
161 except:
162 print name, repr(event)
163 traceback.print_exc()
164 raise
165
166 finally:
167 self.indent -= 4
168
169 def translateEvent(self,event_type,event):
170 if event_type == MOUSE_EVENT:
171 return get_manager().hook.translate_mouse_event(event)
172 if event_type == KEY_EVENT:
173 return get_manager().hook.translate_key_event(event)
174 return event
175
176 class _ActionEventListener(EventListenerBase,guichan.ActionListener):
177 def __init__(self):super(_ActionEventListener,self).__init__()
178 def doAttach(self,real_widget): real_widget.addActionListener(self)
179 def doDetach(self,real_widget): real_widget.removeActionListener(self)
180
181 def action(self,e): self._redirectEvent("action",e)
182
183 class _MouseEventListener(EventListenerBase,guichan.MouseListener):
184 def __init__(self):super(_MouseEventListener,self).__init__()
185 def doAttach(self,real_widget): real_widget.addMouseListener(self)
186 def doDetach(self,real_widget): real_widget.removeMouseListener(self)
187
188 def mouseEntered(self,e): self._redirectEvent("mouseEntered",e)
189 def mouseExited(self,e): self._redirectEvent("mouseExited",e)
190 def mousePressed(self,e): self._redirectEvent("mousePressed",e)
191 def mouseReleased(self,e): self._redirectEvent("mouseReleased",e)
192 def mouseClicked(self,e): self._redirectEvent("mouseClicked",e)
193 def mouseMoved(self,e): self._redirectEvent("mouseMoved",e)
194 def mouseWheelMovedUp(self,e): self._redirectEvent("mouseWheelMovedUp",e)
195 def mouseWheelMovedDown(self,e): self._redirectEvent("mouseWheelMovedDown",e)
196 def mouseDragged(self,e): self._redirectEvent("mouseDragged",e)
197
198 class _KeyEventListener(EventListenerBase,guichan.KeyListener):
199 def __init__(self):super(_KeyEventListener,self).__init__()
200 def doAttach(self,real_widget): real_widget.addKeyListener(self)
201 def doDetach(self,real_widget): real_widget.removeKeyListener(self)
202
203 def keyPressed(self,e): self._redirectEvent("keyPressed",e)
204 def keyReleased(self,e): self._redirectEvent("keyReleased",e)
205
206 class EventMapper(object):
207 """
208 Handles events and callbacks for L{widgets.Widget}
209 and derived classes.
210
211 Every PyChan widget has an L{EventMapper} instance
212 as attribute B{event_mapper}.
213
214 This instance handles all necessary house-keeping.
215 Such an event mapper can be either C{attached} or
216 C{detached}. In its attached state an L{EventListenerBase}
217 is added to the Guichan widget and will redirect
218 the events to the callbacks.
219
220 In its detached state no events are received from the
221 real Guichan widget.
222
223 The event mapper starts in the detached state.
224 When a new event is captured the mapper attaches itself
225 automatically. The widget doesn't need to handle that.
226 """
227 def __init__(self,widget):
228 super(EventMapper,self).__init__()
229 self.widget_ref = weakref.ref(widget)
230 self.callbacks = {}
231 self.listener = {
232 KEY_EVENT : _KeyEventListener(),
233 ACTION_EVENT : _ActionEventListener(),
234 MOUSE_EVENT : _MouseEventListener(),
235 }
236 self.is_attached = False
237 self.debug = get_manager().debug
238
239 def __repr__(self):
240 return "EventMapper(%s)" % repr(self.widget_ref())
241
242 def attach(self):
243 for listener in self.listener.values():
244 listener.attach()
245
246 def detach(self):
247 for listener in self.listener.values():
248 listener.detach()
249
250
251 def capture(self,event_name,callback,group_name):
252 if event_name not in EVENTS:
253 raise exceptions.RuntimeError("Unknown eventname: " + event_name)
254
255 if callback is None:
256 if self.isCaptured(event_name,group_name):
257 self.removeEvent(event_name,group_name)
258 elif self.debug:
259 print CALLBACK_NONE_MESSAGE % str(self.widget_ref())
260 return
261 self.addEvent(event_name,callback,group_name)
262
263 def isCaptured(self,event_name,group_name="default"):
264 return ("%s/%s" % (event_name,group_name)) in self.getCapturedEvents()
265
266 def getCapturedEvents(self):
267 events = []
268 for event_type, listener in self.listener.items():
269 for event_name, group in listener.events.items():
270 for group_name in group.keys():
271 events.append( "%s/%s" % (event_name, group_name) )
272 return events
273
274 def getListener(self,event_name):
275 return self.listener[getEventType(event_name)]
276
277 def removeEvent(self,event_name,group_name):
278 listener = self.getListener(event_name)
279 del listener.events[event_name][group_name]
280
281 if not listener.events[event_name]:
282 del listener.events[event_name]
283 if not listener.events:
284 listener.detach()
285
286 del self.callbacks[group_name][event_name]
287 if len(self.callbacks[group_name]) <= 0:
288 del self.callbacks[group_name]
289
290 def addEvent(self,event_name,callback,group_name):
291 if not callable(callback):
292 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback))
293 # The closure self needs to keep a weak ref.
294 # Otherwise the GC has problems.
295 self_ref = weakref.ref(self)
296
297 # Set up callback dictionary. This should fix some GC issues
298 if not self.callbacks.has_key(group_name):
299 self.callbacks[group_name] = {}
300
301 if not self.callbacks[group_name].has_key(event_name):
302 self.callbacks[group_name][event_name] = {}
303
304 self.callbacks[group_name][event_name] = callback
305
306 def captured_f(event):
307 if self_ref() is not None:
308 tools.applyOnlySuitable(self_ref().callbacks[group_name][event_name],event=event,widget=self_ref().widget_ref())
309
310 listener = self.getListener(event_name)
311
312 if event_name not in listener.events:
313 listener.events[event_name] = {group_name : captured_f}
314 else:
315 listener.events[event_name][group_name] = captured_f
316 listener.attach(self.widget_ref())
317
318
319 def splitEventDescriptor(name):
320 """ Utility function to split "widgetName/eventName" descriptions into tuples. """
321 L = name.split("/")
322 if len(L) not in (1,2,3):
323 raise exceptions.RuntimeError("Invalid widgetname / eventname combination: " + name)
324 if len(L) == 1:
325 L = L[0],"action"
326 elif L[1] not in EVENTS:
327 raise exceptions.RuntimeError("Unknown event name: " + name)
328 if len(L) == 2:
329 L = L[0],L[1],"default"
330 return L