1 #!/usr/bin/env python 2 3 """ 4 DOM Level 3 Events support, with SVG Tiny 1.2 implementation additions. 5 See: http://www.w3.org/TR/DOM-Level-3-Events/events.html 6 See: http://www.w3.org/TR/xml-events/ 7 8 Copyright (C) 2007 Paul Boddie <paul@boddie.org.uk> 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Lesser General Public 12 License as published by the Free Software Foundation; either 13 version 2.1 of the License, or (at your option) any later version. 14 15 This library is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Lesser General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public 21 License along with this library; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 23 """ 24 25 import xml.dom 26 import time 27 28 XML_EVENTS_NAMESPACE = "http://www.w3.org/2001/xml-events" 29 30 class EventException(Exception): 31 32 UNSPECIFIED_EVENT_TYPE_ERR = 0 33 DISPATCH_REQUEST_ERR = 1 34 35 class DocumentEvent: 36 37 """ 38 An event interface supportable by documents. 39 See: http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-DocumentEvent 40 """ 41 42 def canDispatch(self, namespaceURI, type): 43 return namespaceURI is None and event_types.has_key(type) 44 45 def createEvent(self, eventType): 46 try: 47 return event_types[eventType]() 48 except KeyError: 49 raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR) 50 51 class Event: 52 53 """ 54 An event class. 55 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#events__Event 56 See: http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-Event 57 """ 58 59 CAPTURING_PHASE = 1 60 AT_TARGET = 2 61 BUBBLING_PHASE = 3 62 63 def __init__(self): 64 65 "Initialise the event." 66 67 # Initialised later: 68 69 self.target = None 70 self.currentTarget = None 71 self.defaultPrevented = 0 72 self.type = None 73 self.namespaceURI = None 74 75 # DOM Level 3 Events: 76 77 self.bubbles = 1 78 self.eventPhase = self.AT_TARGET # permits direct invocation of dispatchEvent 79 self.timeStamp = time.time() 80 81 # Propagation flags: 82 83 self.stop = 0 84 self.stop_now = 0 85 86 def initEvent(self, eventTypeArg, canBubbleArg, cancelableArg): 87 self.initEventNS(None, eventTypeArg, canBubbleArg, cancelableArg) 88 89 def initEventNS(self, namespaceURIArg, eventTypeArg, canBubbleArg, cancelableArg): 90 self.namespaceURI = namespaceURIArg 91 self.type = eventTypeArg 92 self.bubbles = canBubbleArg 93 self.cancelable = cancelableArg 94 95 def preventDefault(self): 96 self.defaultPrevented = 1 97 98 def stopPropagation(self): 99 self.stop = 1 100 101 def stopImmediatePropagation(self): 102 self.stop = 1 103 self.stop_now = 1 104 105 class UIEvent(Event): 106 107 "A user interface event." 108 109 def __init__(self): 110 Event.__init__(self) 111 self.detail = None 112 113 class MouseEvent(UIEvent): 114 115 "A mouse-related event." 116 117 def __init__(self): 118 Event.__init__(self) 119 self.screenX, self.screenY, self.clientX, self.clientY, self.button = None, None, None, None, None 120 121 # Event types registry. 122 123 event_types = { 124 "Event" : Event, 125 "UIEvent" : UIEvent, 126 "MouseEvent" : MouseEvent 127 } 128 129 class EventTarget: 130 131 """ 132 An event target class. 133 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#events__EventTarget 134 See: http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-EventTarget 135 136 The listeners for a node are accessed through the global object. This common 137 collection is consequently accessed by all nodes in a document, meaning that 138 distinct objects representing the same node can still obtain the set of 139 listeners registered for that node. In contrast, any attempt to directly 140 store listeners on particular objects would result in the specific object 141 which registered the listeners holding the record of such objects, whereas 142 other objects obtained independently for the same node would hold no such 143 record. 144 """ 145 146 def addEventListener(self, type, listener, useCapture): 147 148 """ 149 For the given event 'type', register the given 'listener' for events in 150 the capture phase if 'useCapture' is a true value, or for events in the 151 target and bubble phases otherwise. 152 """ 153 154 self.addEventListenerNS(None, type, listener, useCapture) 155 156 def addEventListenerNS(self, namespaceURI, type, listener, useCapture, group=None): # NOTE: group ignored 157 158 """ 159 For the given 'namespaceURI' and event 'type', register the given 160 'listener' for events in the capture phase if 'useCapture' is a true 161 value, or for events in the target and bubble phases otherwise. 162 """ 163 164 listeners = self.ownerDocument.global_.listeners 165 if not listeners.has_key(self): 166 listeners[self] = {} 167 if not listeners[self].has_key((namespaceURI, type)): 168 listeners[self][(namespaceURI, type)] = [] 169 listeners[self][(namespaceURI, type)].append((listener, useCapture)) 170 171 def dispatchEvent(self, evt): 172 173 "For this node, dispatch event 'evt' to the registered listeners." 174 175 listeners = self.ownerDocument.global_.listeners 176 if not evt.type: 177 raise EventException(EventException.UNSPECIFIED_EVENT_TYPE_ERR) 178 179 # Determine the phase and define the current target (this node) for the 180 # use of listeners. 181 182 capturing = evt.eventPhase == evt.CAPTURING_PHASE 183 evt.currentTarget = self 184 185 # Dispatch on namespaceURI, type. 186 187 for listener, useCapture in listeners.get(self, {}).get((evt.namespaceURI, evt.type), []): 188 189 # Detect requests to stop propagation immediately. 190 191 if evt.stop_now: 192 break 193 194 # Dispatch the event to the appropriate listeners according to the 195 # phase. 196 197 if capturing and useCapture or not capturing and not useCapture: 198 listener.handleEvent(evt) 199 200 return evt.defaultPrevented 201 202 def removeEventListener(self, type, listener, useCapture): 203 204 """ 205 For the given event 'type', deregister the given 'listener' for events 206 in the capture phase if 'useCapture' is a true value, or for events in 207 the target and bubble phases otherwise. 208 """ 209 210 self.removeEventListenerNS(None, type, listener, useCapture) 211 212 def removeEventListenerNS(self, namespaceURI, type, listener, useCapture): 213 214 """ 215 For the given 'namespaceURI' and event 'type', deregister the given 216 'listener' for events in the capture phase if 'useCapture' is a true 217 value, or for events in the target and bubble phases otherwise. 218 """ 219 220 listeners = self.ownerDocument.global_.listeners 221 if listeners.has_key(self) and listeners[self].has_key((namespaceURI, type)): 222 try: 223 listeners[self][(namespaceURI, type)].remove((listener, useCapture)) 224 except ValueError: 225 pass 226 227 # NOTE: The specification doesn't say much about the event system, but we will 228 # NOTE: provide a class to manage the different phases. This is mixed into the 229 # NOTE: SVGDocument class (and potentially other classes in future). 230 231 class EventSystem: 232 233 "An event system which manages the different DOM event flow phases." 234 235 def sendEventToTarget(self, evt, target): 236 237 "Send event 'evt' to the specified 'target' element." 238 239 # Determine the path of the event. 240 241 bubble_route = target.xpath("ancestor::*") 242 capture_route = bubble_route[:] 243 capture_route.reverse() 244 245 # Initialise the target and execute the capture phase. 246 247 evt.target = target 248 evt.eventPhase = evt.CAPTURING_PHASE 249 for element in capture_route: 250 if evt.stop: 251 break 252 element.dispatchEvent(evt) 253 254 # Execute the target phase. 255 256 evt.eventPhase = evt.AT_TARGET 257 if not evt.stop: 258 target.dispatchEvent(evt) 259 260 # Execute the bubble phase, if appropriate. 261 262 evt.eventPhase = evt.BUBBLING_PHASE 263 if evt.bubbles: 264 for element in bubble_route: 265 if evt.stop: 266 break 267 element.dispatchEvent(evt) 268 269 # vim: tabstop=4 expandtab shiftwidth=4