Mercurial > fife-parpg
comparison engine/extensions/pychan/events.py @ 205:54bfd1015b35
* PyChan event handling rework (part I)
** Unified listeners
** ...hopefully more robust attach/detach code.
* Added compat, layout and also the new autopsition feature.
* Documentation
* Minor style fixes in core.
author | phoku@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Sat, 14 Mar 2009 12:13:29 +0000 |
parents | 06dddc96ce54 |
children | e281223a03a6 |
comparison
equal
deleted
inserted
replaced
204:5816ab527da8 | 205:54bfd1015b35 |
---|---|
1 # -*- coding: utf-8 -*- | |
1 #coding: utf-8 | 2 #coding: utf-8 |
2 | 3 |
3 """ | 4 """ |
4 PyChan event handling | 5 PyChan event handling |
5 ===================== | 6 ===================== |
9 a convenient API to capture events. | 10 a convenient API to capture events. |
10 | 11 |
11 Nevertheless to understand how its supposed to work | 12 Nevertheless to understand how its supposed to work |
12 take a look at L{EventMapper} and L{EventListener} | 13 take a look at L{EventMapper} and L{EventListener} |
13 | 14 |
15 Event callbacks | |
16 --------------- | |
17 | |
18 You can either write callbacks yourself or | |
19 use L{tools.callBackWithArguments} or L{tools.attrSetCallback} | |
20 to generate suitable callbacks. | |
21 | |
22 Here's an example callback:: | |
23 def dumpEventInfo(event=0,widget=0): | |
24 print widget, " received the event ", event | |
25 | |
26 Note the signature - C{event} and C{widget} are keyword | |
27 arguments passed to the callback. If doesn't accept either | |
28 C{event} or C{widget} as argument, these are not passed. | |
29 | |
30 This way a simple function which ignores C{event} or C{widget} | |
31 can be used, while they are available if needed. | |
32 | |
33 Currently only one callback can be set per event. In case | |
34 you don't want to write your own callback that dispatches | |
35 to different callbacks you can use L{tools.chainCallbacks}. | |
36 | |
14 Available Events | 37 Available Events |
15 ---------------- | 38 ---------------- |
16 | 39 |
17 """ | 40 """ |
18 | 41 |
19 import fife | 42 from compat import guichan |
43 | |
20 import exceptions | 44 import exceptions |
21 import manager | 45 from internal import get_manager |
22 import tools | 46 import tools |
47 import traceback | |
23 | 48 |
24 EVENTS = [ | 49 EVENTS = [ |
25 "mouseEntered", | 50 "mouseEntered", |
26 "mouseExited", | 51 "mouseExited", |
27 "mousePressed", | 52 "mousePressed", |
35 ] | 60 ] |
36 | 61 |
37 # Add the EVENTS to the docs. | 62 # Add the EVENTS to the docs. |
38 __doc__ += "".join([" - %s\n" % event for event in EVENTS]) | 63 __doc__ += "".join([" - %s\n" % event for event in EVENTS]) |
39 | 64 |
65 # The line before seems to leak the variable event into the global namespace ... remove that! | |
66 # This is a python problem, addressed in python3 | |
67 try: del event | |
68 except:pass | |
69 | |
40 MOUSE_EVENT, KEY_EVENT, ACTION_EVENT = range(3) | 70 MOUSE_EVENT, KEY_EVENT, ACTION_EVENT = range(3) |
41 def getEventType(name): | 71 def getEventType(name): |
42 if "mouse" in name: | 72 if "mouse" in name: |
43 return MOUSE_EVENT | 73 return MOUSE_EVENT |
44 if "key" in name: | 74 if "key" in name: |
48 | 78 |
49 CALLBACK_NONE_MESSAGE = """\ | 79 CALLBACK_NONE_MESSAGE = """\ |
50 You passed None as parameter to %s.capture, which would normally remove a mapped event. | 80 You passed None as parameter to %s.capture, which would normally remove a mapped event. |
51 But there was no event mapped. Did you accidently call a function instead of passing it? | 81 But there was no event mapped. Did you accidently call a function instead of passing it? |
52 """ | 82 """ |
53 | 83 class EventListenerBase(object): |
54 class EventListener(fife.GUIEventListener): | |
55 """ | 84 """ |
56 Redirector for event callbacks. | 85 Redirector for event callbacks. |
57 Use *only* from L{EventMapper}. | 86 Use *only* from L{EventMapper}. |
58 | 87 |
59 This class uses the SWIG director feature - overriden | 88 This class uses the SWIG director feature - overriden |
60 virtual methods are called from C++ to - listen to | 89 virtual methods are called from C++ to - listen to |
61 Guichan events. | 90 Guichan events. |
62 | 91 |
63 When the module is first loaded the event handler | 92 """ |
64 methods are auto-generated from the list L{EVENTS}. | 93 def __init__(self): |
65 This is effectively the same code as:: | 94 super(EventListenerBase,self).__init__() |
66 def mouseEntered(self,event): | |
67 self._redirectEvent("mouseEntered",event) | |
68 | |
69 This way L{EVENTS} and the actually receivable events | |
70 are forced to be in sync. | |
71 """ | |
72 def __init__(self,debug=True): | |
73 super(EventListener,self).__init__() | |
74 self.events = {} | 95 self.events = {} |
75 self.indent = 0 | 96 self.indent = 0 |
76 self.debug = debug | 97 self.debug = 1 |
77 self.guimanager = manager.Manager.manager.guimanager | 98 self.is_attached = False |
99 | |
100 def attach(self,widget): | |
101 """ | |
102 Start receiving events. | |
103 No need to call this manually. | |
104 """ | |
105 | |
106 if self.is_attached: | |
107 return | |
108 if not self.events: | |
109 return | |
110 if self.debug: print "Attach:",self | |
111 self.doAttach(widget.real_widget) | |
112 self.widget = widget | |
113 self.is_attached = True | |
114 | |
115 def detach(self): | |
116 """ | |
117 Stop receiving events. | |
118 No need to call this manually. | |
119 """ | |
120 if not self.is_attached: | |
121 return | |
122 if self.debug: print "Detach:",self | |
123 self.doDetach(self.widget.real_widget) | |
124 self.widget = None | |
125 self.is_attached = False | |
78 | 126 |
79 def _redirectEvent(self,name,event): | 127 def _redirectEvent(self,name,event): |
80 self.indent += 4 | 128 self.indent += 4 |
81 event = self.translateEvent(getEventType(name), event) | 129 try: |
82 if name in self.events: | 130 event = self.translateEvent(getEventType(name), event) |
83 if self.debug: print "-"*self.indent, name | 131 if name in self.events: |
84 for f in self.events[name].itervalues(): | 132 if self.debug: print "-"*self.indent, name |
85 f( event ) | 133 for f in self.events[name].itervalues(): |
86 self.indent -= 4 | 134 f( event ) |
135 | |
136 except: | |
137 print name, event | |
138 traceback.print_exc() | |
139 raise | |
140 | |
141 finally: | |
142 self.indent -= 4 | |
87 | 143 |
88 def translateEvent(self,event_type,event): | 144 def translateEvent(self,event_type,event): |
89 if event_type == MOUSE_EVENT: | 145 if event_type == MOUSE_EVENT: |
90 return self.guimanager.translateMouseEvent(event) | 146 return get_manager().hook.translate_mouse_event(event) |
91 if event_type == KEY_EVENT: | 147 if event_type == KEY_EVENT: |
92 return self.guimanager.translateKeyEvent(event) | 148 return get_manager().hook.translate_key_event(event) |
93 return event | 149 return event |
94 | 150 |
95 def _redirect(name): | 151 class _ActionEventListener(EventListenerBase,guichan.ActionListener): |
96 def redirectorFunc(self,event): | 152 def __init__(self):super(_ActionEventListener,self).__init__() |
97 self._redirectEvent(name,event) | 153 def doAttach(self,real_widget): real_widget.addActionListener(self) |
98 return redirectorFunc | 154 def doDetach(self,real_widget): real_widget.removeActionListener(self) |
99 | 155 |
100 for event_name in EVENTS: | 156 def action(self,e): self._redirectEvent("action",e) |
101 setattr(EventListener,event_name,_redirect(event_name)) | 157 |
158 class _MouseEventListener(EventListenerBase,guichan.MouseListener): | |
159 def __init__(self):super(_MouseEventListener,self).__init__() | |
160 def doAttach(self,real_widget): real_widget.addMouseListener(self) | |
161 def doDetach(self,real_widget): real_widget.removeMouseListener(self) | |
162 | |
163 def mouseEntered(self,e): self._redirectEvent("mouseEntered",e) | |
164 def mouseExited(self,e): self._redirectEvent("mouseExited",e) | |
165 def mousePressed(self,e): self._redirectEvent("mousePressed",e) | |
166 def mouseReleased(self,e): self._redirectEvent("mouseReleased",e) | |
167 def mouseClicked(self,e): self._redirectEvent("mouseClicked",e) | |
168 def mouseMoved(self,e): self._redirectEvent("mouseMoved",e) | |
169 def mouseDragged(self,e): self._redirectEvent("mouseDragged",e) | |
170 | |
171 class _KeyEventListener(EventListenerBase,guichan.KeyListener): | |
172 def __init__(self):super(_KeyEventListener,self).__init__() | |
173 def doAttach(self,real_widget): real_widget.addKeyListener(self) | |
174 def doDetach(self,real_widget): real_widget.removeKeyListener(self) | |
175 | |
176 def keyPressed(self,e): self._redirectEvent("keyPressed",e) | |
177 def keyReleased(self,e): self._redirectEvent("keyReleased",e) | |
102 | 178 |
103 class EventMapper(object): | 179 class EventMapper(object): |
104 """ | 180 """ |
105 Handles events and callbacks for L{widgets.Widget} | 181 Handles events and callbacks for L{widgets.Widget} |
106 and derived classes. | 182 and derived classes. |
107 | 183 |
108 Every PyChan widget has an L{EventMapper} instance | 184 Every PyChan widget has an L{EventMapper} instance |
109 as attribute *event_mapper*. | 185 as attribute B{event_mapper}. |
110 | 186 |
111 This instance handles all necessary house-keeping. | 187 This instance handles all necessary house-keeping. |
112 Such an event mapper can be either *attached* or | 188 Such an event mapper can be either *attached* or |
113 *detached*. In its attached state an L{EventListener} | 189 *detached*. In its attached state an L{EventListener} |
114 is added to the Guichan widget and will redirect | 190 is added to the Guichan widget and will redirect |
122 automatically. The widget doesn't need to handle that. | 198 automatically. The widget doesn't need to handle that. |
123 """ | 199 """ |
124 def __init__(self,widget): | 200 def __init__(self,widget): |
125 super(EventMapper,self).__init__() | 201 super(EventMapper,self).__init__() |
126 self.widget = widget | 202 self.widget = widget |
127 self.listener = EventListener() | 203 self.listener = { |
204 KEY_EVENT : _KeyEventListener(), | |
205 ACTION_EVENT : _ActionEventListener(), | |
206 MOUSE_EVENT : _MouseEventListener(), | |
207 } | |
128 self.is_attached = False | 208 self.is_attached = False |
129 self.debug = manager.Manager.manager.debug | 209 self.debug = get_manager().debug |
130 | 210 |
131 def __del__(self): | |
132 self.detach() | |
133 def __repr__(self): | 211 def __repr__(self): |
134 return "EventMapper(%s)" % repr(self.widget) | 212 return "EventMapper(%s)" % repr(self.widget) |
135 | 213 |
136 def attach(self): | |
137 """ | |
138 Start receiving events. | |
139 No need to call this manually. | |
140 """ | |
141 | |
142 if self.is_attached: | |
143 return | |
144 if not self.listener.events: | |
145 return | |
146 if self.debug: print "Attach:",self | |
147 self.widget.real_widget.addKeyListener( self.listener ) | |
148 self.widget.real_widget.addMouseListener( self.listener ) | |
149 self.widget.real_widget.addActionListener( self.listener ) | |
150 self.is_attached = True | |
151 | |
152 def detach(self): | |
153 """ | |
154 Stop receiving events. | |
155 No need to call this manually. | |
156 """ | |
157 | |
158 if not self.is_attached: | |
159 return | |
160 if self.debug: print "Detach:",self | |
161 self.widget.real_widget.removeKeyListener( self.listener ) | |
162 self.widget.real_widget.removeMouseListener( self.listener ) | |
163 self.widget.real_widget.removeActionListener( self.listener ) | |
164 self.is_attached = False | |
165 | 214 |
166 def capture(self,event_name,callback,group_name): | 215 def capture(self,event_name,callback,group_name): |
167 if event_name not in EVENTS: | 216 if event_name not in EVENTS: |
168 raise exceptions.RuntimeError("Unknown eventname: " + event_name) | 217 raise exceptions.RuntimeError("Unknown eventname: " + event_name) |
169 | 218 |
170 if callback is None: | 219 if callback is None: |
171 if self.isCaptured(event_name,group_name): | 220 if self.isCaptured(event_name,group_name): |
172 del self.listener.events[event_name][group_name] | 221 self.removeEvent(event_name,group_name) |
173 if not self.listener.events[event_name]: | |
174 del self.listener.events[event_name] | |
175 if not self.listener.events: | |
176 self.detach() | |
177 elif self.debug: | 222 elif self.debug: |
178 print CALLBACK_NONE_MESSAGE % str(self.widget) | 223 print CALLBACK_NONE_MESSAGE % str(self.widget) |
179 return | 224 return |
180 | 225 self.addEvent(event_name,callback,group_name) |
226 | |
227 def isCaptured(self,event_name,group_name="default"): | |
228 return ("%s/%s" % (event_name,group_name)) in self.getCapturedEvents() | |
229 | |
230 def getCapturedEvents(self): | |
231 events = [] | |
232 for event_type, listener in self.listener.items(): | |
233 for event_name, group in listener.events.items(): | |
234 for group_name in group.keys(): | |
235 events.append( "%s/%s" % (event_name, group_name) ) | |
236 return events | |
237 | |
238 def getListener(self,event_name): | |
239 return self.listener[getEventType(event_name)] | |
240 | |
241 def removeEvent(self,event_name,group_name): | |
242 listener = self.getListener(event_name) | |
243 del listener.events[event_name][group_name] | |
244 if not listener.events[event_name]: | |
245 del listener.events[event_name] | |
246 if not listener.events: | |
247 listener.detach() | |
248 | |
249 def addEvent(self,event_name,callback,group_name): | |
181 if not callable(callback): | 250 if not callable(callback): |
182 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) | 251 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) |
183 | 252 |
184 def captured_f(event): | 253 def captured_f(event): |
185 tools.applyOnlySuitable(callback,event=event,widget=self.widget) | 254 tools.applyOnlySuitable(callback,event=event,widget=self.widget) |
186 | 255 |
187 if event_name not in self.listener.events: | 256 listener = self.getListener(event_name) |
188 self.listener.events[event_name] = {group_name : captured_f} | 257 |
258 if event_name not in listener.events: | |
259 listener.events[event_name] = {group_name : captured_f} | |
189 else: | 260 else: |
190 self.listener.events[event_name][group_name] = captured_f | 261 listener.events[event_name][group_name] = captured_f |
191 self.attach() | 262 listener.attach(self.widget) |
192 | |
193 def isCaptured(self,event_name,group_name="default"): | |
194 return event_name in self.listener.events and group_name in self.listener.events[event_name] | |
195 | |
196 def getCapturedEvents(self): | |
197 return self.listener.events.keys() | |
198 | 263 |
199 | 264 |
200 def splitEventDescriptor(name): | 265 def splitEventDescriptor(name): |
201 """ Utility function to split "widgetName/eventName" descriptions into tuples. """ | 266 """ Utility function to split "widgetName/eventName" descriptions into tuples. """ |
202 L = name.split("/") | 267 L = name.split("/") |
208 raise exceptions.RuntimeError("Unknown event name: " + name) | 273 raise exceptions.RuntimeError("Unknown event name: " + name) |
209 if len(L) == 2: | 274 if len(L) == 2: |
210 L = L[0],L[1],"default" | 275 L = L[0],L[1],"default" |
211 return L | 276 return L |
212 | 277 |
278 |