Mercurial > fife-parpg
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 |