Mercurial > fife-parpg
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/python/fife/extensions/pychan/events.py Mon Jan 11 23:34:52 2010 +0000 @@ -0,0 +1,330 @@ +# -*- coding: utf-8 -*- + +# #################################################################### +# Copyright (C) 2005-2009 by the FIFE team +# http://www.fifengine.de +# This file is part of FIFE. +# +# FIFE is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# #################################################################### + +"""\ +PyChan event handling (internal). +================================= + +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{EventListenerBase} + +Event callbacks +--------------- + +You can either write callbacks yourself or +use L{tools.callBackWithArguments} or L{tools.attrSetCallback} +to generate suitable callbacks. + +Here's an example callback:: + def dumpEventInfo(event=0,widget=0): + print widget, " received the event ", event + +Note the signature - C{event} and C{widget} are keyword +arguments passed to the callback. If doesn't accept either +C{event} or C{widget} as argument, these are not passed. + +This way a simple function which ignores C{event} or C{widget} +can be used, while they are available if needed. + +Currently only one callback can be set per event. In case +you don't want to write your own callback that dispatches +to different callbacks you can use L{tools.chainCallbacks}. + +Available Events +---------------- + +""" + +from compat import guichan +import widgets + +import exceptions +from internal import get_manager +import tools +import traceback +import weakref +from fife.extensions import fife_timer as timer + +EVENTS = [ + "mouseEntered", + "mouseExited", + "mousePressed", + "mouseReleased", + "mouseClicked", + "mouseMoved", + "mouseWheelMovedUp", + "mouseWheelMovedDown", + "mouseDragged", + "action", + "keyPressed", + "keyReleased", +] + +# Add the EVENTS to the docs. +__doc__ += "".join([" - %s\n" % event for event in EVENTS]) + +# The line before seems to leak the variable event into the global namespace ... remove that! +# This is a python problem, addressed in python3 +try: del event +except:pass + +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 EventListenerBase(object): + """ + 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. + + """ + def __init__(self): + super(EventListenerBase,self).__init__() + self.events = {} + self.indent = 0 + self.debug = get_manager().debug + self.is_attached = False + + def attach(self,widget): + """ + Start receiving events. + No need to call this manually. + """ + + if self.is_attached: + return + if not self.events: + return + if self.debug: print "Attach:",self + self.doAttach(widget.real_widget) + self.widget_ref = weakref.ref(widget) + 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.is_attached = False + + def _redirectEvent(self,name,event): + self.indent += 4 + try: + 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(): + def delayed_f(): + f( event ) + timer.delayCall(0,delayed_f) + + except: + print name, repr(event) + traceback.print_exc() + raise + + finally: + self.indent -= 4 + + def translateEvent(self,event_type,event): + if event_type == MOUSE_EVENT: + return get_manager().hook.translate_mouse_event(event) + if event_type == KEY_EVENT: + return get_manager().hook.translate_key_event(event) + return event + +class _ActionEventListener(EventListenerBase,guichan.ActionListener): + def __init__(self):super(_ActionEventListener,self).__init__() + def doAttach(self,real_widget): real_widget.addActionListener(self) + def doDetach(self,real_widget): real_widget.removeActionListener(self) + + def action(self,e): self._redirectEvent("action",e) + +class _MouseEventListener(EventListenerBase,guichan.MouseListener): + def __init__(self):super(_MouseEventListener,self).__init__() + def doAttach(self,real_widget): real_widget.addMouseListener(self) + def doDetach(self,real_widget): real_widget.removeMouseListener(self) + + def mouseEntered(self,e): self._redirectEvent("mouseEntered",e) + def mouseExited(self,e): self._redirectEvent("mouseExited",e) + def mousePressed(self,e): self._redirectEvent("mousePressed",e) + def mouseReleased(self,e): self._redirectEvent("mouseReleased",e) + def mouseClicked(self,e): self._redirectEvent("mouseClicked",e) + def mouseMoved(self,e): self._redirectEvent("mouseMoved",e) + def mouseWheelMovedUp(self,e): self._redirectEvent("mouseWheelMovedUp",e) + def mouseWheelMovedDown(self,e): self._redirectEvent("mouseWheelMovedDown",e) + def mouseDragged(self,e): self._redirectEvent("mouseDragged",e) + +class _KeyEventListener(EventListenerBase,guichan.KeyListener): + def __init__(self):super(_KeyEventListener,self).__init__() + def doAttach(self,real_widget): real_widget.addKeyListener(self) + def doDetach(self,real_widget): real_widget.removeKeyListener(self) + + def keyPressed(self,e): self._redirectEvent("keyPressed",e) + def keyReleased(self,e): self._redirectEvent("keyReleased",e) + +class EventMapper(object): + """ + Handles events and callbacks for L{widgets.Widget} + and derived classes. + + Every PyChan widget has an L{EventMapper} instance + as attribute B{event_mapper}. + + This instance handles all necessary house-keeping. + Such an event mapper can be either C{attached} or + C{detached}. In its attached state an L{EventListenerBase} + 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_ref = weakref.ref(widget) + self.callbacks = {} + self.listener = { + KEY_EVENT : _KeyEventListener(), + ACTION_EVENT : _ActionEventListener(), + MOUSE_EVENT : _MouseEventListener(), + } + self.is_attached = False + self.debug = get_manager().debug + + def __repr__(self): + return "EventMapper(%s)" % repr(self.widget_ref()) + + def attach(self): + for listener in self.listener.values(): + listener.attach() + + def detach(self): + for listener in self.listener.values(): + listener.detach() + + + 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): + self.removeEvent(event_name,group_name) + elif self.debug: + print CALLBACK_NONE_MESSAGE % str(self.widget_ref()) + return + self.addEvent(event_name,callback,group_name) + + def isCaptured(self,event_name,group_name="default"): + return ("%s/%s" % (event_name,group_name)) in self.getCapturedEvents() + + def getCapturedEvents(self): + events = [] + for event_type, listener in self.listener.items(): + for event_name, group in listener.events.items(): + for group_name in group.keys(): + events.append( "%s/%s" % (event_name, group_name) ) + return events + + def getListener(self,event_name): + return self.listener[getEventType(event_name)] + + def removeEvent(self,event_name,group_name): + listener = self.getListener(event_name) + del listener.events[event_name][group_name] + + if not listener.events[event_name]: + del listener.events[event_name] + if not listener.events: + listener.detach() + + del self.callbacks[group_name][event_name] + if len(self.callbacks[group_name]) <= 0: + del self.callbacks[group_name] + + def addEvent(self,event_name,callback,group_name): + if not callable(callback): + raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) + # The closure self needs to keep a weak ref. + # Otherwise the GC has problems. + self_ref = weakref.ref(self) + + # Set up callback dictionary. This should fix some GC issues + if not self.callbacks.has_key(group_name): + self.callbacks[group_name] = {} + + if not self.callbacks[group_name].has_key(event_name): + self.callbacks[group_name][event_name] = {} + + self.callbacks[group_name][event_name] = callback + + def captured_f(event): + if self_ref() is not None: + tools.applyOnlySuitable(self_ref().callbacks[group_name][event_name],event=event,widget=self_ref().widget_ref()) + + listener = self.getListener(event_name) + + if event_name not in listener.events: + listener.events[event_name] = {group_name : captured_f} + else: + listener.events[event_name][group_name] = captured_f + listener.attach(self.widget_ref()) + + +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 \ No newline at end of file