Source code: org/apache/batik/bridge/BridgeEventSupport.java
1 /*
2
3 Copyright 2001-2004 The Apache Software Foundation
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 */
18 package org.apache.batik.bridge;
19
20 import java.awt.Point;
21 import java.awt.event.KeyEvent;
22 import java.awt.geom.NoninvertibleTransformException;
23 import java.awt.geom.Point2D;
24 import java.awt.geom.Rectangle2D;
25 import java.text.AttributedCharacterIterator;
26 import java.util.List;
27
28 import org.apache.batik.dom.events.DOMKeyEvent;
29 import org.apache.batik.gvt.GraphicsNode;
30 import org.apache.batik.gvt.TextNode;
31 import org.apache.batik.gvt.event.EventDispatcher;
32 import org.apache.batik.gvt.event.GraphicsNodeKeyEvent;
33 import org.apache.batik.gvt.event.GraphicsNodeKeyListener;
34 import org.apache.batik.gvt.event.GraphicsNodeMouseEvent;
35 import org.apache.batik.gvt.event.GraphicsNodeMouseListener;
36 import org.apache.batik.gvt.renderer.StrokingTextPainter;
37 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
38 import org.apache.batik.gvt.text.TextHit;
39 import org.apache.batik.gvt.text.TextSpanLayout;
40 import org.apache.batik.util.SVGConstants;
41 import org.w3c.dom.Document;
42 import org.w3c.dom.Element;
43 import org.w3c.dom.events.DocumentEvent;
44 import org.w3c.dom.events.Event;
45 import org.w3c.dom.events.EventListener;
46 import org.w3c.dom.events.EventTarget;
47 import org.w3c.dom.events.MouseEvent;
48
49 /**
50 * This class is responsible of tracking GraphicsNodeMouseEvent and
51 * fowarding them to the DOM as regular DOM MouseEvent.
52 *
53 * @author <a href="mailto:tkormann@ilog.fr">Thierry Kormann</a>
54 * @version $Id: BridgeEventSupport.java,v 1.59 2005/03/27 08:58:30 cam Exp $
55 */
56 public class BridgeEventSupport implements SVGConstants {
57
58 private BridgeEventSupport() {}
59
60 /**
61 * Is called only for the root element in order to dispatch GVT
62 * events to the DOM.
63 */
64 public static void addGVTListener(BridgeContext ctx, Document doc) {
65 UserAgent ua = ctx.getUserAgent();
66 if (ua != null) {
67 EventDispatcher dispatcher = ua.getEventDispatcher();
68 if (dispatcher != null) {
69 final Listener listener = new Listener(ctx, ua);
70 dispatcher.addGraphicsNodeMouseListener(listener);
71 dispatcher.addGraphicsNodeKeyListener(listener);
72 // add an unload listener on the SVGDocument to remove
73 // that listener for dispatching events
74 EventListener l = new GVTUnloadListener(dispatcher, listener);
75 EventTarget target = (EventTarget)doc;
76 target.addEventListener("SVGUnload", l, false);
77 ctx.storeEventListener(target, "SVGUnload", l, false);
78 }
79 }
80 }
81
82 protected static class GVTUnloadListener implements EventListener {
83
84 protected EventDispatcher dispatcher;
85 protected Listener listener;
86
87 public GVTUnloadListener(EventDispatcher dispatcher,
88 Listener listener) {
89 this.dispatcher = dispatcher;
90 this.listener = listener;
91 }
92
93 public void handleEvent(Event evt) {
94 dispatcher.removeGraphicsNodeMouseListener(listener);
95 dispatcher.removeGraphicsNodeKeyListener(listener);
96 evt.getTarget().removeEventListener
97 (SVGConstants.SVG_SVGUNLOAD_EVENT_TYPE, this, false);
98 }
99 }
100
101 /**
102 * A GraphicsNodeMouseListener that dispatch DOM events accordingly.
103 */
104 protected static class Listener
105 implements GraphicsNodeMouseListener, GraphicsNodeKeyListener {
106
107 protected BridgeContext context;
108 protected UserAgent ua;
109 protected Element lastTargetElement;
110 protected boolean isDown;
111
112 public Listener(BridgeContext ctx, UserAgent u) {
113 context = ctx;
114 ua = u;
115 }
116
117 // Key -------------------------------------------------------------
118
119 /**
120 * Invoked when a key has been pressed.
121 * @param evt the graphics node key event
122 */
123 public void keyPressed(GraphicsNodeKeyEvent evt) {
124 if (!isDown) {
125 isDown = true;
126 dispatchKeyEvent("keydown", evt);
127 }
128 if (evt.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
129 // We will not get a KEY_TYPED event for this char
130 // so generate a keypress event here.
131 dispatchKeyEvent("keypress", evt);
132 }
133 }
134
135 /**
136 * Invoked when a key has been released.
137 * @param evt the graphics node key event
138 */
139 public void keyReleased(GraphicsNodeKeyEvent evt) {
140 dispatchKeyEvent("keyup", evt);
141 isDown = false;
142 }
143
144 /**
145 * Invoked when a key has been typed.
146 * @param evt the graphics node key event
147 */
148 public void keyTyped(GraphicsNodeKeyEvent evt) {
149 dispatchKeyEvent("keypress", evt);
150 }
151
152 protected void dispatchKeyEvent(String eventType,
153 GraphicsNodeKeyEvent evt) {
154 FocusManager fmgr = context.getFocusManager();
155 if (fmgr == null) return;
156
157 Element targetElement = (Element)fmgr.getCurrentEventTarget();
158 if (targetElement == null) {
159 return;
160 }
161 DocumentEvent d = (DocumentEvent)targetElement.getOwnerDocument();
162 DOMKeyEvent keyEvt = (DOMKeyEvent)d.createEvent("KeyEvents");
163 keyEvt.initKeyEvent(eventType,
164 true,
165 true,
166 evt.isControlDown(),
167 evt.isAltDown(),
168 evt.isShiftDown(),
169 evt.isMetaDown(),
170 mapKeyCode(evt.getKeyCode()),
171 evt.getKeyChar(),
172 null);
173
174 try {
175 ((EventTarget)targetElement).dispatchEvent(keyEvt);
176 } catch (RuntimeException e) {
177 ua.displayError(e);
178 }
179 }
180
181 /**
182 * The java KeyEvent keyCodes and the DOMKeyEvent keyCodes
183 * map except for the VK_ENTER code (which has a different value
184 * in DOM and the VK_KANA_LOCK and VK_INPUT_METHOD_ON_OFF which
185 * have no DOM equivalent.
186 */
187 protected final int mapKeyCode(int keyCode) {
188 switch (keyCode) {
189 case KeyEvent.VK_ENTER:
190 return DOMKeyEvent.DOM_VK_ENTER;
191 case KeyEvent.VK_KANA_LOCK:
192 return DOMKeyEvent.DOM_VK_UNDEFINED;
193 case KeyEvent.VK_INPUT_METHOD_ON_OFF:
194 return DOMKeyEvent.DOM_VK_UNDEFINED;
195 default:
196 return keyCode;
197 }
198 }
199
200 // Mouse -----------------------------------------------------------
201
202 public void mouseClicked(GraphicsNodeMouseEvent evt) {
203 dispatchMouseEvent("click", evt, true);
204 }
205
206 public void mousePressed(GraphicsNodeMouseEvent evt) {
207 dispatchMouseEvent("mousedown", evt, true);
208 }
209
210 public void mouseReleased(GraphicsNodeMouseEvent evt) {
211 dispatchMouseEvent("mouseup", evt, true);
212 }
213
214 public void mouseEntered(GraphicsNodeMouseEvent evt) {
215 dispatchMouseEvent("mouseover", evt, true);
216 }
217
218 public void mouseExited(GraphicsNodeMouseEvent evt) {
219 Point clientXY = evt.getClientPoint();
220 // Get the 'new' node for the DOM event.
221 GraphicsNode node = evt.getRelatedNode();
222 Element targetElement = getEventTarget(node, clientXY);
223 if (lastTargetElement != null) {
224 dispatchMouseEvent("mouseout",
225 lastTargetElement, // target
226 targetElement, // relatedTarget
227 clientXY,
228 evt,
229 true);
230 }
231 }
232
233 public void mouseDragged(GraphicsNodeMouseEvent evt) {
234 dispatchMouseEvent("mousemove", evt, false);
235 }
236
237 public void mouseMoved(GraphicsNodeMouseEvent evt) {
238 Point clientXY = evt.getClientPoint();
239 GraphicsNode node = evt.getGraphicsNode();
240 Element targetElement = getEventTarget(node, clientXY);
241 Element holdLTE = lastTargetElement;
242 if (holdLTE != targetElement) {
243 if (holdLTE != null) {
244 dispatchMouseEvent("mouseout",
245 holdLTE, // target
246 targetElement, // relatedTarget
247 clientXY,
248 evt,
249 true);
250 }
251 if (targetElement != null) {
252 dispatchMouseEvent("mouseover",
253 targetElement, // target
254 holdLTE, // relatedTarget
255 clientXY,
256 evt,
257 true);
258 }
259 }
260 dispatchMouseEvent("mousemove",
261 targetElement, // target
262 null, // relatedTarget
263 clientXY,
264 evt,
265 false);
266 }
267
268 /**
269 * Dispatches a DOM MouseEvent according to the specified
270 * parameters.
271 *
272 * @param eventType the event type
273 * @param evt the GVT GraphicsNodeMouseEvent
274 * @param cancelable true means the event is cancelable
275 */
276 protected void dispatchMouseEvent(String eventType,
277 GraphicsNodeMouseEvent evt,
278 boolean cancelable) {
279 Point clientXY = evt.getClientPoint();
280 GraphicsNode node = evt.getGraphicsNode();
281 Element targetElement = getEventTarget
282 (node, new Point2D.Float(evt.getX(), evt.getY()));
283 Element relatedElement = getRelatedElement(evt);
284 dispatchMouseEvent(eventType,
285 targetElement,
286 relatedElement,
287 clientXY,
288 evt,
289 cancelable);
290 }
291
292 /**
293 * Dispatches a DOM MouseEvent according to the specified
294 * parameters.
295 *
296 * @param eventType the event type
297 * @param targetElement the target of the event
298 * @param relatedElement the related target if any
299 * @param clientXY the mouse coordinates in the client space
300 * @param evt the GVT GraphicsNodeMouseEvent
301 * @param cancelable true means the event is cancelable
302 */
303 protected void dispatchMouseEvent(String eventType,
304 Element targetElement,
305 Element relatedElement,
306 Point clientXY,
307 GraphicsNodeMouseEvent evt,
308 boolean cancelable) {
309 if (targetElement == null) {
310 return;
311 }
312 /*
313 if (relatedElement != null) {
314 System.out.println
315 ("dispatching "+eventType+
316 " target:"+targetElement.getLocalName()+
317 " relatedElement:"+relatedElement.getLocalName());
318 } else {
319 System.out.println
320 ("dispatching "+eventType+
321 " target:"+targetElement.getLocalName());
322
323 }
324 */
325 short button = getButton(evt);
326 Point screenXY = evt.getScreenPoint();
327 // create the coresponding DOM MouseEvent
328 DocumentEvent d = (DocumentEvent)targetElement.getOwnerDocument();
329 MouseEvent mouseEvt = (MouseEvent)d.createEvent("MouseEvents");
330 mouseEvt.initMouseEvent(eventType,
331 true,
332 cancelable,
333 null,
334 evt.getClickCount(),
335 screenXY.x,
336 screenXY.y,
337 clientXY.x,
338 clientXY.y,
339 evt.isControlDown(),
340 evt.isAltDown(),
341 evt.isShiftDown(),
342 evt.isMetaDown(),
343 button,
344 (EventTarget)relatedElement);
345
346 try {
347 ((EventTarget)targetElement).dispatchEvent(mouseEvt);
348 } catch (RuntimeException e) {
349 ua.displayError(e);
350 } finally {
351 lastTargetElement = targetElement;
352 }
353 }
354
355 /**
356 * Returns the related element according to the specified event.
357 *
358 * @param evt the GVT GraphicsNodeMouseEvent
359 */
360 protected Element getRelatedElement(GraphicsNodeMouseEvent evt) {
361 GraphicsNode relatedNode = evt.getRelatedNode();
362 Element relatedElement = null;
363 if (relatedNode != null) {
364 relatedElement = context.getElement(relatedNode);
365 }
366 return relatedElement;
367 }
368
369 /**
370 * Returns the mouse event button.
371 *
372 * @param evt the GVT GraphicsNodeMouseEvent
373 */
374 protected short getButton(GraphicsNodeMouseEvent evt) {
375 short button = 1;
376 if ((GraphicsNodeMouseEvent.BUTTON1_MASK & evt.getModifiers()) != 0) {
377 button = 0;
378 } else if ((GraphicsNodeMouseEvent.BUTTON3_MASK & evt.getModifiers()) != 0) {
379 button = 2;
380 }
381 return button;
382 }
383
384 /**
385 * Returns the element that is the target of the specified
386 * event or null if any.
387 *
388 * @param node the graphics node that received the event
389 * @param coords the mouse coordinates in the GVT tree space
390 */
391 protected Element getEventTarget(GraphicsNode node, Point2D coords) {
392 Element target = context.getElement(node);
393 // Lookup inside the text element children to see if the target
394 // is a tspan or textPath
395
396 if (target != null && node instanceof TextNode) {
397 TextNode textNode = (TextNode)node;
398 List list = textNode.getTextRuns();
399 Point2D pt = (Point2D)coords.clone();
400 // place coords in text node coordinate system
401 try {
402 node.getGlobalTransform().createInverse().transform(pt, pt);
403 } catch (NoninvertibleTransformException ex) {
404 }
405 if (list != null){
406 for (int i = 0 ; i < list.size(); i++) {
407 StrokingTextPainter.TextRun run =
408 (StrokingTextPainter.TextRun)list.get(i);
409 AttributedCharacterIterator aci = run.getACI();
410 TextSpanLayout layout = run.getLayout();
411 float x = (float)pt.getX();
412 float y = (float)pt.getY();
413 TextHit textHit = layout.hitTestChar(x, y);
414 Rectangle2D bounds = layout.getBounds2D();
415 if ((textHit != null) &&
416 (bounds != null) && bounds.contains(x, y)) {
417 Object delimiter = aci.getAttribute
418 (GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER);
419 if (delimiter instanceof Element) {
420 return (Element)delimiter;
421 }
422 }
423 }
424 }
425 }
426 return target;
427 }
428 }
429 }