comparison engine/extensions/pychan/events.py @ 157:bb9902910067

input_rework merged! Bad features: * Broken DND for zero-projekt. * Design short-comings.
author phoku@33b003aa-7bff-0310-803a-e67f0ece8222
date Tue, 14 Oct 2008 07:41:48 +0000
parents
children 5b04a7d3ded6
comparison
equal deleted inserted replaced
156:376b8afc9a18 157:bb9902910067
1 #coding: utf-8
2
3 """
4 PyChan event handling
5 =====================
6
7 Users shouldn't need to use this module directly.
8 L{widgets.Widget.capture} and L{widgets.Widget.mapEvents} provide
9 a convenient API to capture events.
10
11 Nevertheless to understand how its supposed to work
12 take a look at L{EventMapper} and L{EventListener}
13
14 Available Events
15 ----------------
16
17 """
18
19 import fife
20 import exceptions
21 import manager
22 import tools
23
24 EVENTS = [
25 "mouseEntered",
26 "mouseExited",
27 "mousePressed",
28 "mouseReleased",
29 "mouseClicked",
30 "mouseMoved",
31 "mouseDragged",
32 "action",
33 "keyPressed",
34 "keyReleased",
35 ]
36
37 # Add the EVENTS to the docs.
38 __doc__ += "".join([" - %s\n" % event for event in EVENTS])
39
40 MOUSE_EVENT, KEY_EVENT, ACTION_EVENT = range(3)
41 def getEventType(name):
42 if "mouse" in name:
43 return MOUSE_EVENT
44 if "key" in name:
45 return MOUSE_EVENT
46 return ACTION_EVENT
47
48
49 CALLBACK_NONE_MESSAGE = """\
50 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?
52 """
53
54 class EventListener(fife.GUIEventListener):
55 """
56 Redirector for event callbacks.
57 Use *only* from L{EventMapper}.
58
59 This class uses the SWIG director feature - overriden
60 virtual methods are called from C++ to - listen to
61 Guichan events.
62
63 When the module is first loaded the event handler
64 methods are auto-generated from the list L{EVENTS}.
65 This is effectively the same code as::
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 = {}
75 self.indent = 0
76 self.debug = debug
77 self.guimanager = manager.Manager.manager.guimanager
78
79 def _redirectEvent(self,name,event):
80 self.indent += 4
81 event = self.translateEvent(getEventType(name), event)
82 if name in self.events:
83 if self.debug: print "-"*self.indent, name
84 self.events[name]( event )
85 self.indent -= 4
86
87 def translateEvent(self,event_type,event):
88 if event_type == MOUSE_EVENT:
89 return self.guimanager.translateMouseEvent(event)
90 if event_type == KEY_EVENT:
91 return self.guimanager.translateKeyEvent(event)
92 return event
93
94 def _redirect(name):
95 def redirectorFunc(self,event):
96 self._redirectEvent(name,event)
97 return redirectorFunc
98
99 for event_name in EVENTS:
100 setattr(EventListener,event_name,_redirect(event_name))
101
102 class EventMapper(object):
103 """
104 Handles events and callbacks for L{widgets.Widget}
105 and derived classes.
106
107 Every PyChan widget has an L{EventMapper} instance
108 as attribute *event_mapper*.
109
110 This instance handles all necessary house-keeping.
111 Such an event mapper can be either *attached* or
112 *detached*. In its attached state an L{EventListener}
113 is added to the Guichan widget and will redirect
114 the events to the callbacks.
115
116 In its detached state no events are received from the
117 real Guichan widget.
118
119 The event mapper starts in the detached state.
120 When a new event is captured the mapper attaches itself
121 automatically. The widget doesn't need to handle that.
122 """
123 def __init__(self,widget):
124 super(EventMapper,self).__init__()
125 self.widget = widget
126 self.listener = EventListener()
127 self.is_attached = False
128 self.debug = manager.Manager.manager.debug
129
130 def __del__(self):
131 self.detach()
132 def __repr__(self):
133 return "EventMapper(%s)" % repr(self.widget)
134
135 def attach(self):
136 """
137 Start receiving events.
138 No need to call this manually.
139 """
140
141 if self.is_attached:
142 return
143 if not self.listener.events:
144 return
145 if self.debug: print "Attach:",self
146 self.widget.real_widget.addMouseListener( self.listener )
147 self.widget.real_widget.addActionListener( self.listener )
148 self.is_attached = True
149
150 def detach(self):
151 """
152 Stop receiving events.
153 No need to call this manually.
154 """
155
156 if not self.is_attached:
157 return
158 if self.debug: print "Detach:",self
159 self.widget.real_widget.removeMouseListener( self.listener )
160 self.widget.real_widget.removeActionListener( self.listener )
161 self.is_attached = False
162
163 def capture(self,event_name,callback):
164 if event_name not in EVENTS:
165 raise exceptions.RuntimeError("Unknown eventname: " + event_name)
166
167 if callback is None and not self.isCaptured(event_name):
168 if self.debug:
169 print CALLBACK_NONE_MESSAGE % str(self.widget)
170 return
171
172 if callback is None:
173 del self.listener.events[event_name]
174 if not self.listener.events:
175 self.detach()
176 return
177
178 if not callable(callback):
179 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback))
180
181 def captured_f(event):
182 tools.applyOnlySuitable(callback,event=event,widget=self.widget)
183
184 self.listener.events[event_name] = captured_f
185 self.attach()
186
187 def isCaptured(self,event_name):
188 return event_name in self.listener.events
189
190 def getCapturedEvents(self):
191 return self.listener.events.keys()
192
193
194 def splitEventDescriptor(name):
195 """ Utility function to split "widgetName/eventName" descriptions into tuples. """
196 L = name.split("/")
197 if len(L) == 1:
198 L = L[0],"action"
199 if len(L) != 2:
200 raise exceptions.RuntimeError("Invalid widgetname / eventname combination: " + name)
201 if L[1] not in EVENTS:
202 raise exceptions.RuntimeError("Unknown event name: " + name)
203 return L
204