Package fife :: Package extensions :: Package pychan :: Module events
[hide private]
[frames] | no frames]

Source Code for Module fife.extensions.pychan.events

  1  # -*- coding: utf-8 -*- 
  2   
  3  # #################################################################### 
  4  #  Copyright (C) 2005-2010 by the FIFE team 
  5  #  http://www.fifengine.net 
  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.fife_timer import Timer 
 71  from fife.extensions.pychan.tools import callbackWithArguments as cbwa 
 72   
 73  EVENTS = [ 
 74          "mouseEntered", 
 75          "mouseExited", 
 76          "mousePressed", 
 77          "mouseReleased", 
 78          "mouseClicked", 
 79          "mouseMoved", 
 80          "mouseWheelMovedUp", 
 81          "mouseWheelMovedDown", 
 82          "mouseDragged", 
 83          "action", 
 84          "keyPressed", 
 85          "keyReleased", 
 86  ] 
 87   
 88  # Add the EVENTS to the docs. 
 89  __doc__ += "".join([" - %s\n" % event for event in EVENTS]) 
 90   
 91  # The line before seems to leak the variable event into the global namespace ... remove that! 
 92  # This is a python problem, addressed in python3 
 93  try: del event 
 94  except:pass 
 95   
 96  MOUSE_EVENT, KEY_EVENT, ACTION_EVENT = range(3) 
97 -def getEventType(name):
98 if "mouse" in name: 99 return MOUSE_EVENT 100 if "key" in name: 101 return KEY_EVENT 102 return ACTION_EVENT
103 104 105 CALLBACK_NONE_MESSAGE = """\ 106 You passed None as parameter to %s.capture, which would normally remove a mapped event. 107 But there was no event mapped. Did you accidently call a function instead of passing it? 108 """ 109
110 -class EventListenerBase(object):
111 """ 112 Redirector for event callbacks. 113 Use *only* from L{EventMapper}. 114 115 This class uses the SWIG director feature - overriden 116 virtual methods are called from C++ to - listen to 117 Guichan events. 118 119 """
120 - def __init__(self):
121 super(EventListenerBase,self).__init__() 122 self.events = {} 123 self.indent = 0 124 self.debug = get_manager().debug 125 self.is_attached = False 126 127 self._timers = [] 128 self._deadtimers = []
129
130 - def attach(self,widget):
131 """ 132 Start receiving events. 133 No need to call this manually. 134 """ 135 136 if self.is_attached: 137 return 138 if not self.events: 139 return 140 if self.debug: print "Attach:",self 141 self.doAttach(widget.real_widget) 142 self.widget_ref = weakref.ref(widget) 143 self.is_attached = True
144
145 - def detach(self):
146 """ 147 Stop receiving events. 148 No need to call this manually. 149 """ 150 if not self.is_attached: 151 return 152 if self.debug: print "Detach:",self 153 self.is_attached = False
154
155 - def _redirectEvent(self,name,event):
156 self.indent += 4 157 try: 158 event = self.translateEvent(getEventType(name), event) 159 if name in self.events: 160 if self.debug: print "-"*self.indent, name 161 for f in self.events[name].itervalues(): 162 def delayed_f(timer, f=f): # bind f during loop 163 n_timer = timer() 164 f( event ) 165 166 #FIXME: figure out a way to get rid of the dead timer list 167 del self._deadtimers[:] 168 169 if n_timer in self._timers: 170 self._deadtimers.append(n_timer) 171 self._timers.remove(n_timer)
172 173 174 timer = Timer(repeat=1) 175 timer._callback = cbwa(delayed_f, weakref.ref(timer)) 176 timer.start() 177 178 self._timers.append(timer) 179 180 except: 181 print name, repr(event) 182 traceback.print_exc() 183 raise 184 185 finally: 186 self.indent -= 4
187
188 - def translateEvent(self,event_type,event):
189 if event_type == MOUSE_EVENT: 190 return get_manager().hook.translate_mouse_event(event) 191 if event_type == KEY_EVENT: 192 return get_manager().hook.translate_key_event(event) 193 return event
194
195 -class _ActionEventListener(EventListenerBase,guichan.ActionListener):
196 - def __init__(self):super(_ActionEventListener,self).__init__()
197 - def doAttach(self,real_widget): real_widget.addActionListener(self)
198 - def doDetach(self,real_widget): real_widget.removeActionListener(self)
199
200 - def action(self,e): self._redirectEvent("action",e)
201
202 -class _MouseEventListener(EventListenerBase,guichan.MouseListener):
203 - def __init__(self):super(_MouseEventListener,self).__init__()
204 - def doAttach(self,real_widget): real_widget.addMouseListener(self)
205 - def doDetach(self,real_widget): real_widget.removeMouseListener(self)
206
207 - def mouseEntered(self,e): self._redirectEvent("mouseEntered",e)
208 - def mouseExited(self,e): self._redirectEvent("mouseExited",e)
209 - def mousePressed(self,e): self._redirectEvent("mousePressed",e)
210 - def mouseReleased(self,e): self._redirectEvent("mouseReleased",e)
211 - def mouseClicked(self,e): self._redirectEvent("mouseClicked",e)
212 - def mouseMoved(self,e): self._redirectEvent("mouseMoved",e)
213 - def mouseWheelMovedUp(self,e): self._redirectEvent("mouseWheelMovedUp",e)
214 - def mouseWheelMovedDown(self,e): self._redirectEvent("mouseWheelMovedDown",e)
215 - def mouseDragged(self,e): self._redirectEvent("mouseDragged",e)
216
217 -class _KeyEventListener(EventListenerBase,guichan.KeyListener):
218 - def __init__(self):super(_KeyEventListener,self).__init__()
219 - def doAttach(self,real_widget): real_widget.addKeyListener(self)
220 - def doDetach(self,real_widget): real_widget.removeKeyListener(self)
221
222 - def keyPressed(self,e): self._redirectEvent("keyPressed",e)
223 - def keyReleased(self,e): self._redirectEvent("keyReleased",e)
224
225 -class EventMapper(object):
226 """ 227 Handles events and callbacks for L{widgets.Widget} 228 and derived classes. 229 230 Every PyChan widget has an L{EventMapper} instance 231 as attribute B{event_mapper}. 232 233 This instance handles all necessary house-keeping. 234 Such an event mapper can be either C{attached} or 235 C{detached}. In its attached state an L{EventListenerBase} 236 is added to the Guichan widget and will redirect 237 the events to the callbacks. 238 239 In its detached state no events are received from the 240 real Guichan widget. 241 242 The event mapper starts in the detached state. 243 When a new event is captured the mapper attaches itself 244 automatically. The widget doesn't need to handle that. 245 """
246 - def __init__(self,widget):
247 super(EventMapper,self).__init__() 248 self.widget_ref = weakref.ref(widget) 249 self.callbacks = {} 250 self.listener = { 251 KEY_EVENT : _KeyEventListener(), 252 ACTION_EVENT : _ActionEventListener(), 253 MOUSE_EVENT : _MouseEventListener(), 254 } 255 self.is_attached = False 256 self.debug = get_manager().debug
257
258 - def __repr__(self):
259 return "EventMapper(%s)" % repr(self.widget_ref())
260
261 - def attach(self):
262 for listener in self.listener.values(): 263 listener.attach()
264
265 - def detach(self):
266 for listener in self.listener.values(): 267 listener.detach()
268 269
270 - def capture(self,event_name,callback,group_name):
271 if event_name not in EVENTS: 272 raise exceptions.RuntimeError("Unknown eventname: " + event_name) 273 274 if callback is None: 275 if self.isCaptured(event_name,group_name): 276 self.removeEvent(event_name,group_name) 277 elif self.debug: 278 print CALLBACK_NONE_MESSAGE % str(self.widget_ref()) 279 return 280 self.addEvent(event_name,callback,group_name)
281
282 - def isCaptured(self,event_name,group_name="default"):
283 return ("%s/%s" % (event_name,group_name)) in self.getCapturedEvents()
284
285 - def getCapturedEvents(self):
286 events = [] 287 for event_type, listener in self.listener.items(): 288 for event_name, group in listener.events.items(): 289 for group_name in group.keys(): 290 events.append( "%s/%s" % (event_name, group_name) ) 291 return events
292
293 - def getListener(self,event_name):
294 return self.listener[getEventType(event_name)]
295
296 - def removeEvent(self,event_name,group_name):
297 listener = self.getListener(event_name) 298 del listener.events[event_name][group_name] 299 300 if not listener.events[event_name]: 301 del listener.events[event_name] 302 if not listener.events: 303 listener.detach() 304 305 del self.callbacks[group_name][event_name] 306 if len(self.callbacks[group_name]) <= 0: 307 del self.callbacks[group_name]
308
309 - def addEvent(self,event_name,callback,group_name):
310 if not callable(callback): 311 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) 312 # The closure self needs to keep a weak ref. 313 # Otherwise the GC has problems. 314 self_ref = weakref.ref(self) 315 316 # Set up callback dictionary. This should fix some GC issues 317 if not self.callbacks.has_key(group_name): 318 self.callbacks[group_name] = {} 319 320 if not self.callbacks[group_name].has_key(event_name): 321 self.callbacks[group_name][event_name] = {} 322 323 self.callbacks[group_name][event_name] = callback 324 325 def captured_f(event): 326 if self_ref() is not None: 327 tools.applyOnlySuitable(self_ref().callbacks[group_name][event_name],event=event,widget=self_ref().widget_ref())
328 329 listener = self.getListener(event_name) 330 331 if event_name not in listener.events: 332 listener.events[event_name] = {group_name : captured_f} 333 else: 334 listener.events[event_name][group_name] = captured_f 335 listener.attach(self.widget_ref())
336 337
338 -def splitEventDescriptor(name):
339 """ Utility function to split "widgetName/eventName" descriptions into tuples. """ 340 L = name.split("/") 341 if len(L) not in (1,2,3): 342 raise exceptions.RuntimeError("Invalid widgetname / eventname combination: " + name) 343 if len(L) == 1: 344 L = L[0],"action" 345 elif L[1] not in EVENTS: 346 raise exceptions.RuntimeError("Unknown event name: " + name) 347 if len(L) == 2: 348 L = L[0],L[1],"default" 349 return L
350