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