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