Mercurial > fife-parpg
view engine/extensions/pychan/events.py @ 166:81a222e7bd25
Fixes for event handling.
author | phoku@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Sat, 18 Oct 2008 06:46:21 +0000 |
parents | 5b04a7d3ded6 |
children | 06dddc96ce54 |
line wrap: on
line source
#coding: utf-8 """ PyChan event handling ===================== Users shouldn't need to use this module directly. L{widgets.Widget.capture} and L{widgets.Widget.mapEvents} provide a convenient API to capture events. Nevertheless to understand how its supposed to work take a look at L{EventMapper} and L{EventListener} Available Events ---------------- """ import fife import exceptions import manager import tools EVENTS = [ "mouseEntered", "mouseExited", "mousePressed", "mouseReleased", "mouseClicked", "mouseMoved", "mouseDragged", "action", "keyPressed", "keyReleased", ] # Add the EVENTS to the docs. __doc__ += "".join([" - %s\n" % event for event in EVENTS]) MOUSE_EVENT, KEY_EVENT, ACTION_EVENT = range(3) def getEventType(name): if "mouse" in name: return MOUSE_EVENT if "key" in name: return KEY_EVENT return ACTION_EVENT CALLBACK_NONE_MESSAGE = """\ You passed None as parameter to %s.capture, which would normally remove a mapped event. But there was no event mapped. Did you accidently call a function instead of passing it? """ class EventListener(fife.GUIEventListener): """ Redirector for event callbacks. Use *only* from L{EventMapper}. This class uses the SWIG director feature - overriden virtual methods are called from C++ to - listen to Guichan events. When the module is first loaded the event handler methods are auto-generated from the list L{EVENTS}. This is effectively the same code as:: def mouseEntered(self,event): self._redirectEvent("mouseEntered",event) This way L{EVENTS} and the actually receivable events are forced to be in sync. """ def __init__(self,debug=True): super(EventListener,self).__init__() self.events = {} self.indent = 0 self.debug = debug self.guimanager = manager.Manager.manager.guimanager def _redirectEvent(self,name,event): self.indent += 4 event = self.translateEvent(getEventType(name), event) if name in self.events: if self.debug: print "-"*self.indent, name for f in self.events[name].itervalues(): f( event ) self.indent -= 4 def translateEvent(self,event_type,event): if event_type == MOUSE_EVENT: return self.guimanager.translateMouseEvent(event) if event_type == KEY_EVENT: return self.guimanager.translateKeyEvent(event) return event def _redirect(name): def redirectorFunc(self,event): self._redirectEvent(name,event) return redirectorFunc for event_name in EVENTS: setattr(EventListener,event_name,_redirect(event_name)) class EventMapper(object): """ Handles events and callbacks for L{widgets.Widget} and derived classes. Every PyChan widget has an L{EventMapper} instance as attribute *event_mapper*. This instance handles all necessary house-keeping. Such an event mapper can be either *attached* or *detached*. In its attached state an L{EventListener} is added to the Guichan widget and will redirect the events to the callbacks. In its detached state no events are received from the real Guichan widget. The event mapper starts in the detached state. When a new event is captured the mapper attaches itself automatically. The widget doesn't need to handle that. """ def __init__(self,widget): super(EventMapper,self).__init__() self.widget = widget self.listener = EventListener() self.is_attached = False self.debug = manager.Manager.manager.debug def __del__(self): self.detach() def __repr__(self): return "EventMapper(%s)" % repr(self.widget) def attach(self): """ Start receiving events. No need to call this manually. """ if self.is_attached: return if not self.listener.events: return if self.debug: print "Attach:",self self.widget.real_widget.addKeyListener( self.listener ) self.widget.real_widget.addMouseListener( self.listener ) self.widget.real_widget.addActionListener( self.listener ) self.is_attached = True def detach(self): """ Stop receiving events. No need to call this manually. """ if not self.is_attached: return if self.debug: print "Detach:",self self.widget.real_widget.removeKeyListener( self.listener ) self.widget.real_widget.removeMouseListener( self.listener ) self.widget.real_widget.removeActionListener( self.listener ) self.is_attached = False def capture(self,event_name,callback,group_name): if event_name not in EVENTS: raise exceptions.RuntimeError("Unknown eventname: " + event_name) if callback is None: if self.isCaptured(event_name,group_name): del self.listener.events[event_name][group_name] if not self.listener.events[event_name]: del self.listener.events[event_name] if not self.listener.events: self.detach() elif self.debug: print CALLBACK_NONE_MESSAGE % str(self.widget) return if not callable(callback): raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) def captured_f(event): tools.applyOnlySuitable(callback,event=event,widget=self.widget) if event_name not in self.listener.events: self.listener.events[event_name] = {group_name : captured_f} else: self.listener.events[event_name][group_name] = captured_f self.attach() def isCaptured(self,event_name,group_name="default"): return event_name in self.listener.events and group_name in self.listener.events[event_name] def getCapturedEvents(self): return self.listener.events.keys() def splitEventDescriptor(name): """ Utility function to split "widgetName/eventName" descriptions into tuples. """ L = name.split("/") if len(L) not in (1,2,3): raise exceptions.RuntimeError("Invalid widgetname / eventname combination: " + name) if len(L) == 1: L = L[0],"action" elif L[1] not in EVENTS: raise exceptions.RuntimeError("Unknown event name: " + name) if len(L) == 2: L = L[0],L[1],"default" return L