View Javadoc

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 }