1 /**********************************************
2 * Copyright (C) 2010 Lukas Laag
3 * This file is part of lib-gwt-svg.
4 *
5 * libgwtsvg is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * libgwtsvg is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with libgwtsvg. If not, see http://www.gnu.org/licenses/
17 **********************************************/
18 package org.vectomatic.dom.svg.impl;
19
20 import org.vectomatic.dom.svg.OMNode;
21 import org.vectomatic.dom.svg.OMSVGElement;
22 import org.vectomatic.dom.svg.utils.XPathPrefixResolver;
23
24 import com.google.gwt.core.client.JavaScriptObject;
25 import com.google.gwt.dom.client.Element;
26 import com.google.gwt.dom.client.NativeEvent;
27 import com.google.gwt.dom.client.Node;
28 import com.google.gwt.event.dom.client.LoseCaptureEvent;
29 import com.google.gwt.event.dom.client.LoseCaptureHandler;
30 import com.google.gwt.event.shared.HandlerRegistration;
31
32 /**
33 * Implementation class for low-level GWT integration
34 * (mostly event dispatching)
35 * @author laaglu
36 */
37 public class DOMHelperImpl {
38 protected static boolean eventsInitialized;
39 protected static final int EVT_FOCUSIN = 0x00001;
40 protected static final int EVT_FOCUSOUT = 0x00002;
41 protected static final int EVT_MOUSEDOWN = 0x00004;
42 protected static final int EVT_MOUSEUP = 0x00008;
43 protected static final int EVT_MOUSEOVER = 0x00010;
44 protected static final int EVT_MOUSEOUT = 0x00020;
45 protected static final int EVT_MOUSEMOVE = 0x00040;
46 protected static final int EVT_ACTIVATE = 0x00080;
47 protected static final int EVT_CLICK = 0x00100;
48 protected static final int EVT_LOAD = 0x00200;
49 protected static final int EVT_BEGIN = 0x00400;
50 protected static final int EVT_END = 0x00800;
51 protected static final int EVT_REPEAT = 0x01000;
52 protected static final int EVT_UNLOAD = 0x02000;
53 protected static final int EVT_ABORT = 0x04000;
54 protected static final int EVT_ERROR = 0x08000;
55 protected static final int EVT_RESIZE = 0x10000;
56 protected static final int EVT_SCROLL = 0x20000;
57 protected static final int EVT_ZOOM = 0x40000;
58 protected static final int EVT_LOOSECAPTURE = 0x80000;
59 protected static final int EVT_TOUCHSTART = 0x100000;
60 protected static final int EVT_TOUCHEND = 0x200000;
61 protected static final int EVT_TOUCHMOVE = 0x400000;
62 protected static final int EVT_TOUCHCANCEL = 0x800000;
63 protected static final int EVT_GESTURESTART = 0x1000000;
64 protected static final int EVT_GESTURECHANGE = 0x2000000;
65 protected static final int EVT_GESTUREEND = 0x4000000;
66 protected OMSVGElement captureElt;
67
68 /**
69 * Initializes the event system. Positions event handlers
70 * on the window so that they can capture events early
71 * if necessary.
72 */
73 protected native void initEventSystem() /*-{
74 $wnd.__helperImpl = this;
75 $wnd.__svgDispatch = function(evt) {
76 $wnd.__helperImpl.@org.vectomatic.dom.svg.impl.DOMHelperImpl::dispatch(Lcom/google/gwt/dom/client/NativeEvent;Lorg/vectomatic/dom/svg/OMNode;Lcom/google/gwt/dom/client/Element;)(evt, evt.currentTarget.__wrapper, evt.currentTarget);
77 };
78
79 $wnd.__svgCapture = function(evt) {
80 $wnd.__helperImpl.@org.vectomatic.dom.svg.impl.DOMHelperImpl::dispatchCapturedEvent(Lcom/google/gwt/dom/client/NativeEvent;Lcom/google/gwt/dom/client/Element;)(evt, evt.currentTarget);
81 };
82
83 $wnd.addEventListener('mousedown', $wnd.__svgCapture, true);
84 $wnd.addEventListener('mouseup', $wnd.__svgCapture, true);
85 $wnd.addEventListener('mousemove', $wnd.__svgCapture, true);
86 $wnd.addEventListener('mouseover', $wnd.__svgCapture, true);
87 $wnd.addEventListener('mouseout', $wnd.__svgCapture, true);
88 $wnd.addEventListener('mousewheel', $wnd.__svgCapture, true);
89 $wnd.addEventListener('click', $wnd.__svgCapture, true);
90 $wnd.addEventListener('focusin', $wnd.__svgCapture, true);
91 $wnd.addEventListener('focusout', $wnd.__svgCapture, true);
92 $wnd.addEventListener('activate', $wnd.__svgCapture, true);
93 $wnd.addEventListener('touchstart', $wnd.__svgCapture, true);
94 $wnd.addEventListener('touchend', $wnd.__svgCapture, true);
95 $wnd.addEventListener('touchmove', $wnd.__svgCapture, true);
96 $wnd.addEventListener('touchcancel', $wnd.__svgCapture, true);
97 $wnd.addEventListener('gesturestart', $wnd.__svgCapture, true);
98 $wnd.addEventListener('gesturechange', $wnd.__svgCapture, true);
99 $wnd.addEventListener('gestureend', $wnd.__svgCapture, true);
100 }-*/;
101
102 /**
103 * Returns the bit mask which corresponds to
104 * the specified event type
105 * @param eventType The event type
106 * @return The bit mask associated to this event type
107 */
108 public native int eventGetTypeInt(String eventType) /*-{
109 switch (eventType) {
110 case "focusin": return 0x00001;
111 case "focusinout": return 0x00002;
112 case "mousedown": return 0x00004;
113 case "mouseup": return 0x00008;
114 case "mouseover": return 0x00010;
115 case "mouseout": return 0x00020;
116 case "mousemove": return 0x00040;
117 case "activate": return 0x00080;
118 case "click": return 0x00100;
119 case "load": return 0x00200;
120 case "begin": return 0x00400;
121 case "end": return 0x00800;
122 case "repeat": return 0x01000;
123 case "unload": return 0x02000;
124 case "abort": return 0x04000;
125 case "error": return 0x08000;
126 case "resize": return 0x10000;
127 case "scroll": return 0x20000;
128 case "zoom": return 0x40000;
129 case "losecapture": return 0x80000;
130 case "touchstart": return 0x100000;
131 case "touchend": return 0x200000;
132 case "touchmove": return 0x400000;
133 case "touchcancel": return 0x800000;
134 case "gesturestart": return 0x1000000;
135 case "gesturechange": return 0x2000000;
136 case "gestureend": return 0x4000000;
137 default: return 0;
138 }
139 }-*/;
140
141 protected void init() {
142 if (!eventsInitialized) {
143 eventsInitialized = true;
144 initEventSystem();
145 }
146 }
147 /**
148 * Makes a node sink the events emitted by the specified element
149 * @param elem The element emitting the events
150 * @param eventName The event name
151 */
152 public void bindEventListener(Element elem, String eventName) {
153 init();
154 sinkEvents(elem, eventGetTypeInt(eventName) | getEventsSunk(elem));
155 }
156
157 /**
158 * Returns the element which currently captures all the
159 * events after a call to {@link org.vectomatic.dom.svg.impl.DOMHelperImpl#setCaptureElement(OMSVGElement, LoseCaptureHandler)}
160 * or null if element is set to capture events
161 * @return The event capturing element
162 */
163 public OMSVGElement getCaptureElement() {
164 init();
165 return captureElt;
166 }
167
168 /**
169 * Makes the specified element capture all the events, until
170 * a call to {@link org.vectomatic.dom.svg.impl.DOMHelperImpl#releaseCaptureElement()}
171 * terminates the capture
172 * @param captureElt The capturing element
173 * @param loseCaptureHandler A handler which will be invoked
174 * if the element loses capture
175 * @return {@link HandlerRegistration} used to remove this handler
176 */
177 public HandlerRegistration setCaptureElement(OMSVGElement captureElt, LoseCaptureHandler loseCaptureHandler) {
178 init();
179 this.captureElt = captureElt;
180 HandlerRegistration registration = null;
181 if (loseCaptureHandler != null) {
182 registration = captureElt.addHandler(loseCaptureHandler, LoseCaptureEvent.getType());
183 }
184 return registration;
185 }
186
187 /**
188 * Stops the forwarding of all events to the capturing element
189 * specified by {@link org.vectomatic.dom.svg.impl.DOMHelperImpl#setCaptureElement(OMSVGElement, LoseCaptureHandler)}
190 */
191 public void releaseCaptureElement() {
192 init();
193 captureElt = null;
194 }
195
196 /**
197 * Returns the event mask for the specified element
198 * @param elem The element
199 * @return The event mask for the specified element
200 */
201 public native int getEventsSunk(Element elem) /*-{
202 return elem.__eventMask || 0;
203 }-*/;
204
205 /**
206 * Changes the event mask and activates the handler
207 * for the specified element
208 * @param elem The object which emits events
209 * @param bits The event mask
210 */
211 protected native void sinkEvents(Element elem, int bits) /*-{
212 var chMask = (elem.__eventMask || 0) ^ bits;
213 elem.__eventMask = bits;
214 if (!chMask) return;
215 if (chMask & 0x00001) elem.onfocusin = (bits & 0x00001) ?
216 $wnd.__svgDispatch : null;
217 if (chMask & 0x00002) elem.onfocusout = (bits & 0x00002) ?
218 $wnd.__svgDispatch : null;
219 if (chMask & 0x00004) elem.onmousedown = (bits & 0x00004) ?
220 $wnd.__svgDispatch : null;
221 if (chMask & 0x00008) elem.onmouseup = (bits & 0x00008) ?
222 $wnd.__svgDispatch : null;
223 if (chMask & 0x00010) elem.onmouseover = (bits & 0x00010) ?
224 $wnd.__svgDispatch : null;
225 if (chMask & 0x00020) elem.onmouseout = (bits & 0x00020) ?
226 $wnd.__svgDispatch : null;
227 if (chMask & 0x00040) elem.onmousemove = (bits & 0x00040) ?
228 $wnd.__svgDispatch : null;
229 if (chMask & 0x00080) elem.onactivate = (bits & 0x00080) ?
230 $wnd.__svgDispatch : null;
231 if (chMask & 0x00100) elem.onclick = (bits & 0x00100) ?
232 $wnd.__svgDispatch : null;
233 if (chMask & 0x00200) elem.onload = (bits & 0x00200) ?
234 $wnd.__svgDispatch : null;
235 if (chMask & 0x00400) {
236 if (bits & 0x00400) {
237 //elem.addEventListener('begin', $wnd.__svgDispatch, false);
238 elem.setAttribute('onbegin', 'window.__svgDispatch(evt);');
239 } else {
240 //elem.removeEventListener('begin', $wnd.__svgDispatch, false);
241 elem.removeAttribute('onbegin');
242 }
243 }
244 if (chMask & 0x00800) {
245 if (bits & 0x00800) {
246 //elem.addEventListener('end', $wnd.__svgDispatch, false);
247 elem.setAttribute('onend', 'window.__svgDispatch(evt);');
248 } else {
249 //elem.removeEventListener('end', $wnd.__svgDispatch, false);
250 elem.removeAttribute('onend');
251 }
252 }
253 if (chMask & 0x01000) {
254 if (bits & 0x01000) {
255 //elem.addEventListener('repeat', $wnd.__svgDispatch, false);
256 elem.setAttribute('onrepeat', 'window.__svgDispatch(evt);');
257 } else {
258 //elem.removeEventListener('repeat', $wnd.__svgDispatch, false);
259 elem.removeAttribute('onrepeat');
260 }
261 }
262 if (chMask & 0x02000) elem.onunload = (bits & 0x02000) ?
263 $wnd.__svgDispatch : null;
264 if (chMask & 0x04000) elem.onabort = (bits & 0x04000) ?
265 $wnd.__svgDispatch : null;
266 if (chMask & 0x08000) elem.onerror = (bits & 0x08000) ?
267 $wnd.__svgDispatch : null;
268 if (chMask & 0x10000) elem.onresize = (bits & 0x10000) ?
269 $wnd.__svgDispatch : null;
270 if (chMask & 0x20000) elem.onscroll = (bits & 0x20000) ?
271 $wnd.__svgDispatch : null;
272 if (chMask & 0x40000) elem.onzoom = (bits & 0x40000) ?
273 $wnd.__svgDispatch : null;
274 if (chMask & 0x100000) elem.ontouchstart = (bits & 0x100000) ?
275 $wnd.__svgDispatch : null;
276 if (chMask & 0x200000) elem.ontouchend = (bits & 0x200000) ?
277 $wnd.__svgDispatch : null;
278 if (chMask & 0x400000) elem.ontouchmove = (bits & 0x400000) ?
279 $wnd.__svgDispatch : null;
280 if (chMask & 0x800000) elem.ontouchcancel = (bits & 0x800000) ?
281 $wnd.__svgDispatch : null;
282 if (chMask & 0x1000000) elem.ongesturestart = (bits & 0x1000000) ?
283 $wnd.__svgDispatch : null;
284 if (chMask & 0x2000000) elem.ongesturechange = (bits & 0x2000000) ?
285 $wnd.__svgDispatch : null;
286 if (chMask & 0x4000000) elem.ongestureend = (bits & 0x4000000) ?
287 $wnd.__svgDispatch : null;
288 }-*/;
289
290 /**
291 * Central dispatching function for events emitted by DOM objects
292 * @param event The DOM event
293 * @param node The object processing the event
294 * @param elem The object emitting the event
295 */
296 public void dispatch(NativeEvent event, OMNode node, Element elem) {
297 //Window.alert("type=" + event.getType());
298 switch(eventGetTypeInt(event.getType())) {
299 // Mouseover and mouseout deserve special treatment
300 // to solve issues described in:
301 // http://www.quirksmode.org/js/events_mouse.html
302 // For SVG, it seems better to test against the tree rooted at
303 // evt.currentTarget than againt the subtree rooted at evt.target
304 case EVT_MOUSEOVER:
305 case EVT_MOUSEOUT:
306 if (isChildOf((Node)event.getCurrentEventTarget().cast(), (Node)event.getRelatedEventTarget().cast())) {
307 return;
308 }
309 break;
310 }
311 node.dispatch(event);
312 }
313
314 /**
315 * Dispatching function for events which result from a call
316 * to {@link #setCaptureElement(OMSVGElement, LoseCaptureHandler)}
317 * @param event The DOM event
318 * @param elem The object emitting the event
319 */
320 public void dispatchCapturedEvent(NativeEvent event, Element elem) {
321 if (captureElt != null) {
322 if (eventGetTypeInt(event.getType()) == EVT_LOOSECAPTURE) {
323 captureElt = null;
324 }
325 dispatch(event, captureElt, elem);
326 event.stopPropagation();
327 }
328 }
329
330 /**
331 * Tests if a node is part of a DOM subtree.
332 * @param root
333 * The subtree root
334 * @param node
335 * The node to be tested
336 * @return
337 * True if the node is part of the subtree, false otherwise
338 */
339 protected native boolean isChildOf(Node root, Node node) /*-{
340 while (node != null && node != root && node.nodeName != 'BODY') {
341 node= node.parentNode
342 }
343 if (node === root) {
344 return true;
345 }
346 return false;
347 }-*/;
348
349 ///////////////////////////////////////////////////////////////
350 // XPath support
351 ///////////////////////////////////////////////////////////////
352
353 public native JavaScriptObject evaluateNodeListXPath_(Element svgElement, String expr, XPathPrefixResolver resolver) /*-{
354 var result = svgElement.ownerDocument.evaluate(expr, svgElement, resolver ? function(prefix) { return resolver.@org.vectomatic.dom.svg.utils.XPathPrefixResolver::resolvePrefix(Ljava/lang/String;)(prefix); } : null, XPathResult.ORDERED_NODE_ITERATOR_TYPE , null);
355 return result;
356 }-*/;
357
358 public native Node evaluateNodeXPath_(Element svgElement, String expr, XPathPrefixResolver resolver) /*-{
359 var result = svgElement.ownerDocument.evaluate(expr, svgElement, resolver ? function(prefix) { return resolver.@org.vectomatic.dom.svg.utils.XPathPrefixResolver::resolvePrefix(Ljava/lang/String;)(prefix); } : null, XPathResult.ANY_UNORDERED_NODE_TYPE , null);
360 return result.singleNodeValue;
361 }-*/;
362
363 public native String evaluateStringXPath_(Element svgElement, String expr, XPathPrefixResolver resolver) /*-{
364 var result = svgElement.ownerDocument.evaluate(expr, svgElement, resolver ? function(prefix) { return resolver.@org.vectomatic.dom.svg.utils.XPathPrefixResolver::resolvePrefix(Ljava/lang/String;)(prefix); } : null, XPathResult.STRING_TYPE , null);
365 return result.stringValue;
366 }-*/;
367
368 public native float evaluateNumberXPath_(Element svgElement, String expr, XPathPrefixResolver resolver) /*-{
369 var result = svgElement.ownerDocument.evaluate(expr, svgElement, resolver ? function(prefix) { return resolver.@org.vectomatic.dom.svg.utils.XPathPrefixResolver::resolvePrefix(Ljava/lang/String;)(prefix); } : null, XPathResult.NUMBER_TYPE , null);
370 return result.numberValue;
371 }-*/;
372
373 public native boolean evaluateBooleanXPath_(Element svgElement, String expr, XPathPrefixResolver resolver) /*-{
374 var result = svgElement.ownerDocument.evaluate(expr, svgElement, resolver ? function(prefix) { return resolver.@org.vectomatic.dom.svg.utils.XPathPrefixResolver::resolvePrefix(Ljava/lang/String;)(prefix); } : null, XPathResult.BOOLEAN_TYPE , null);
375 return result.booleanValue;
376 }-*/;
377
378 }