001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.bridge;
020:
021: import java.awt.Point;
022: import java.awt.event.KeyEvent;
023: import java.awt.geom.NoninvertibleTransformException;
024: import java.awt.geom.Point2D;
025: import java.awt.geom.Rectangle2D;
026: import java.lang.ref.SoftReference;
027: import java.text.AttributedCharacterIterator;
028: import java.util.List;
029:
030: import org.apache.batik.dom.events.DOMKeyEvent;
031: import org.apache.batik.dom.events.DOMMouseEvent;
032: import org.apache.batik.dom.events.NodeEventTarget;
033: import org.apache.batik.dom.util.DOMUtilities;
034: import org.apache.batik.gvt.GraphicsNode;
035: import org.apache.batik.gvt.TextNode;
036: import org.apache.batik.gvt.event.EventDispatcher;
037: import org.apache.batik.gvt.event.GraphicsNodeKeyEvent;
038: import org.apache.batik.gvt.event.GraphicsNodeKeyListener;
039: import org.apache.batik.gvt.event.GraphicsNodeMouseEvent;
040: import org.apache.batik.gvt.event.GraphicsNodeMouseListener;
041: import org.apache.batik.gvt.renderer.StrokingTextPainter;
042: import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
043: import org.apache.batik.gvt.text.TextHit;
044: import org.apache.batik.gvt.text.TextSpanLayout;
045: import org.apache.batik.util.SVGConstants;
046: import org.apache.batik.util.XMLConstants;
047:
048: import org.w3c.dom.Document;
049: import org.w3c.dom.Element;
050: import org.w3c.dom.events.DocumentEvent;
051: import org.w3c.dom.events.Event;
052: import org.w3c.dom.events.EventListener;
053: import org.w3c.dom.events.EventTarget;
054:
055: /**
056: * This class is responsible of tracking GraphicsNodeMouseEvent and
057: * fowarding them to the DOM as regular DOM MouseEvent.
058: *
059: * @author <a href="mailto:tkormann@ilog.fr">Thierry Kormann</a>
060: * @version $Id: BridgeEventSupport.java 504819 2007-02-08 08:23:19Z dvholten $
061: */
062: public abstract class BridgeEventSupport implements SVGConstants {
063:
064: public static final AttributedCharacterIterator.Attribute TEXT_COMPOUND_ID = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_ID;
065:
066: protected BridgeEventSupport() {
067: }
068:
069: /**
070: * Is called only for the root element in order to dispatch GVT
071: * events to the DOM.
072: */
073: public static void addGVTListener(BridgeContext ctx, Document doc) {
074: UserAgent ua = ctx.getUserAgent();
075: if (ua != null) {
076: EventDispatcher dispatcher = ua.getEventDispatcher();
077: if (dispatcher != null) {
078: final Listener listener = new Listener(ctx, ua);
079: dispatcher.addGraphicsNodeMouseListener(listener);
080: dispatcher.addGraphicsNodeKeyListener(listener);
081: // add an unload listener on the SVGDocument to remove
082: // that listener for dispatching events
083: EventListener l = new GVTUnloadListener(dispatcher,
084: listener);
085: NodeEventTarget target = (NodeEventTarget) doc;
086: target.addEventListenerNS(
087: XMLConstants.XML_EVENTS_NAMESPACE_URI,
088: "SVGUnload", l, false, null);
089: storeEventListenerNS(ctx, target,
090: XMLConstants.XML_EVENTS_NAMESPACE_URI,
091: "SVGUnload", l, false);
092: }
093: }
094: }
095:
096: /**
097: * Calls storeEventListener on the given BridgeContext.
098: */
099: protected static void storeEventListener(BridgeContext ctx,
100: EventTarget e, String t, EventListener l, boolean c) {
101: ctx.storeEventListener(e, t, l, c);
102: }
103:
104: /**
105: * Calls storeEventListenerNS on the given BridgeContext.
106: */
107: protected static void storeEventListenerNS(BridgeContext ctx,
108: EventTarget e, String n, String t, EventListener l,
109: boolean c) {
110: ctx.storeEventListenerNS(e, n, t, l, c);
111: }
112:
113: protected static class GVTUnloadListener implements EventListener {
114:
115: protected EventDispatcher dispatcher;
116: protected Listener listener;
117:
118: public GVTUnloadListener(EventDispatcher dispatcher,
119: Listener listener) {
120: this .dispatcher = dispatcher;
121: this .listener = listener;
122: }
123:
124: public void handleEvent(Event evt) {
125: dispatcher.removeGraphicsNodeMouseListener(listener);
126: dispatcher.removeGraphicsNodeKeyListener(listener);
127: NodeEventTarget et = (NodeEventTarget) evt.getTarget();
128: et.removeEventListenerNS(
129: XMLConstants.XML_EVENTS_NAMESPACE_URI, "SVGUnload",
130: this , false);
131: }
132: }
133:
134: /**
135: * A GraphicsNodeMouseListener that dispatch DOM events accordingly.
136: */
137: protected static class Listener implements
138: GraphicsNodeMouseListener, GraphicsNodeKeyListener {
139:
140: protected BridgeContext context;
141: protected UserAgent ua;
142: protected Element lastTargetElement;
143: protected boolean isDown;
144:
145: public Listener(BridgeContext ctx, UserAgent u) {
146: context = ctx;
147: ua = u;
148: }
149:
150: // Key -------------------------------------------------------------
151:
152: /**
153: * Invoked when a key has been pressed.
154: * @param evt the graphics node key event
155: */
156: public void keyPressed(GraphicsNodeKeyEvent evt) {
157: // XXX isDown is not preventing key repeats
158: if (!isDown) {
159: isDown = true;
160: dispatchKeyEvent("keydown", evt);
161: }
162: if (evt.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
163: // We will not get a KEY_TYPED event for this char
164: // so generate a keypress event here.
165: dispatchKeyEvent("keypress", evt);
166: }
167: }
168:
169: /**
170: * Invoked when a key has been released.
171: * @param evt the graphics node key event
172: */
173: public void keyReleased(GraphicsNodeKeyEvent evt) {
174: dispatchKeyEvent("keyup", evt);
175: }
176:
177: /**
178: * Invoked when a key has been typed.
179: * @param evt the graphics node key event
180: */
181: public void keyTyped(GraphicsNodeKeyEvent evt) {
182: dispatchKeyEvent("keypress", evt);
183: }
184:
185: /**
186: * Dispatch a DOM 2 Draft Key event.
187: */
188: protected void dispatchKeyEvent(String eventType,
189: GraphicsNodeKeyEvent evt) {
190: FocusManager fmgr = context.getFocusManager();
191: if (fmgr == null)
192: return;
193:
194: Element targetElement = (Element) fmgr
195: .getCurrentEventTarget();
196: if (targetElement == null) {
197: targetElement = context.getDocument()
198: .getDocumentElement();
199: }
200: DocumentEvent d = (DocumentEvent) targetElement
201: .getOwnerDocument();
202: DOMKeyEvent keyEvt = (DOMKeyEvent) d
203: .createEvent("KeyEvents");
204: keyEvt.initKeyEvent(eventType, true, true, evt
205: .isControlDown(), evt.isAltDown(), evt
206: .isShiftDown(), evt.isMetaDown(), mapKeyCode(evt
207: .getKeyCode()), evt.getKeyChar(), null);
208:
209: try {
210: ((EventTarget) targetElement).dispatchEvent(keyEvt);
211: } catch (RuntimeException e) {
212: ua.displayError(e);
213: }
214: }
215:
216: /**
217: * The java KeyEvent keyCodes and the DOMKeyEvent keyCodes
218: * map except for the VK_ENTER code (which has a different value
219: * in DOM and the VK_KANA_LOCK and VK_INPUT_METHOD_ON_OFF which
220: * have no DOM equivalent.
221: */
222: protected final int mapKeyCode(int keyCode) {
223: switch (keyCode) {
224: case KeyEvent.VK_ENTER:
225: return DOMKeyEvent.DOM_VK_ENTER;
226: case KeyEvent.VK_KANA_LOCK:
227: return DOMKeyEvent.DOM_VK_UNDEFINED;
228: case KeyEvent.VK_INPUT_METHOD_ON_OFF:
229: return DOMKeyEvent.DOM_VK_UNDEFINED;
230: default:
231: return keyCode;
232: }
233: }
234:
235: // Mouse -----------------------------------------------------------
236:
237: public void mouseClicked(GraphicsNodeMouseEvent evt) {
238: dispatchMouseEvent("click", evt, true);
239: }
240:
241: public void mousePressed(GraphicsNodeMouseEvent evt) {
242: dispatchMouseEvent("mousedown", evt, true);
243: }
244:
245: public void mouseReleased(GraphicsNodeMouseEvent evt) {
246: dispatchMouseEvent("mouseup", evt, true);
247: }
248:
249: public void mouseEntered(GraphicsNodeMouseEvent evt) {
250: Point clientXY = evt.getClientPoint();
251: GraphicsNode node = evt.getGraphicsNode();
252: Element targetElement = getEventTarget(node,
253: new Point2D.Float(evt.getX(), evt.getY()));
254: Element relatedElement = getRelatedElement(evt);
255: dispatchMouseEvent("mouseover", targetElement,
256: relatedElement, clientXY, evt, true);
257: }
258:
259: public void mouseExited(GraphicsNodeMouseEvent evt) {
260: Point clientXY = evt.getClientPoint();
261: // Get the 'new' node for the DOM event.
262: GraphicsNode node = evt.getRelatedNode();
263: Element targetElement = getEventTarget(node, clientXY);
264: if (lastTargetElement != null) {
265: dispatchMouseEvent("mouseout", lastTargetElement, // target
266: targetElement, // relatedTarget
267: clientXY, evt, true);
268: lastTargetElement = null;
269: }
270: }
271:
272: public void mouseDragged(GraphicsNodeMouseEvent evt) {
273: dispatchMouseEvent("mousemove", evt, false);
274: }
275:
276: public void mouseMoved(GraphicsNodeMouseEvent evt) {
277: Point clientXY = evt.getClientPoint();
278: GraphicsNode node = evt.getGraphicsNode();
279: Element targetElement = getEventTarget(node, clientXY);
280: Element holdLTE = lastTargetElement;
281: if (holdLTE != targetElement) {
282: if (holdLTE != null) {
283: dispatchMouseEvent("mouseout", holdLTE, // target
284: targetElement, // relatedTarget
285: clientXY, evt, true);
286: }
287: if (targetElement != null) {
288: dispatchMouseEvent("mouseover", targetElement, // target
289: holdLTE, // relatedTarget
290: clientXY, evt, true);
291: }
292: }
293: dispatchMouseEvent("mousemove", targetElement, // target
294: null, // relatedTarget
295: clientXY, evt, false);
296: }
297:
298: /**
299: * Dispatches a DOM MouseEvent according to the specified
300: * parameters.
301: *
302: * @param eventType the event type
303: * @param evt the GVT GraphicsNodeMouseEvent
304: * @param cancelable true means the event is cancelable
305: */
306: protected void dispatchMouseEvent(String eventType,
307: GraphicsNodeMouseEvent evt, boolean cancelable) {
308: Point clientXY = evt.getClientPoint();
309: GraphicsNode node = evt.getGraphicsNode();
310: Element targetElement = getEventTarget(node,
311: new Point2D.Float(evt.getX(), evt.getY()));
312: Element relatedElement = getRelatedElement(evt);
313: dispatchMouseEvent(eventType, targetElement,
314: relatedElement, clientXY, evt, cancelable);
315: }
316:
317: /**
318: * Dispatches a DOM MouseEvent according to the specified
319: * parameters.
320: *
321: * @param eventType the event type
322: * @param targetElement the target of the event
323: * @param relatedElement the related target if any
324: * @param clientXY the mouse coordinates in the client space
325: * @param evt the GVT GraphicsNodeMouseEvent
326: * @param cancelable true means the event is cancelable
327: */
328: protected void dispatchMouseEvent(String eventType,
329: Element targetElement, Element relatedElement,
330: Point clientXY, GraphicsNodeMouseEvent evt,
331: boolean cancelable) {
332: if (targetElement == null) {
333: return;
334: }
335: /*
336: if (relatedElement != null) {
337: System.out.println
338: ("dispatching "+eventType+
339: " target:"+targetElement.getLocalName()+
340: " relatedElement:"+relatedElement.getLocalName());
341: } else {
342: System.out.println
343: ("dispatching "+eventType+
344: " target:"+targetElement.getLocalName());
345:
346: }
347: */
348: short button = getButton(evt);
349: Point screenXY = evt.getScreenPoint();
350: // create the coresponding DOM MouseEvent
351: DocumentEvent d = (DocumentEvent) targetElement
352: .getOwnerDocument();
353: DOMMouseEvent mouseEvt = (DOMMouseEvent) d
354: .createEvent("MouseEvents");
355: String modifiers = DOMUtilities.getModifiersList(evt
356: .getLockState(), evt.getModifiers());
357: mouseEvt.initMouseEventNS(
358: XMLConstants.XML_EVENTS_NAMESPACE_URI, eventType,
359: true, cancelable, null, evt.getClickCount(),
360: screenXY.x, screenXY.y, clientXY.x, clientXY.y,
361: button, (EventTarget) relatedElement, modifiers);
362:
363: try {
364: ((EventTarget) targetElement).dispatchEvent(mouseEvt);
365: } catch (RuntimeException e) {
366: ua.displayError(e);
367: } finally {
368: lastTargetElement = targetElement;
369: }
370: }
371:
372: /**
373: * Returns the related element according to the specified event.
374: *
375: * @param evt the GVT GraphicsNodeMouseEvent
376: */
377: protected Element getRelatedElement(GraphicsNodeMouseEvent evt) {
378: GraphicsNode relatedNode = evt.getRelatedNode();
379: Element relatedElement = null;
380: if (relatedNode != null) {
381: relatedElement = context.getElement(relatedNode);
382: }
383: return relatedElement;
384: }
385:
386: /**
387: * Returns the mouse event button.
388: *
389: * @param evt the GVT GraphicsNodeMouseEvent
390: */
391: protected short getButton(GraphicsNodeMouseEvent evt) {
392: short button = 1;
393: if ((GraphicsNodeMouseEvent.BUTTON1_MASK & evt
394: .getModifiers()) != 0) {
395: button = 0;
396: } else if ((GraphicsNodeMouseEvent.BUTTON3_MASK & evt
397: .getModifiers()) != 0) {
398: button = 2;
399: }
400: return button;
401: }
402:
403: /**
404: * Returns the element that is the target of the specified
405: * event or null if any.
406: *
407: * @param node the graphics node that received the event
408: * @param coords the mouse coordinates in the GVT tree space
409: */
410: protected Element getEventTarget(GraphicsNode node,
411: Point2D coords) {
412: Element target = context.getElement(node);
413: // Lookup inside the text element children to see if the target
414: // is a tspan or textPath
415:
416: if (target != null && node instanceof TextNode) {
417: TextNode textNode = (TextNode) node;
418: List list = textNode.getTextRuns();
419: Point2D pt = (Point2D) coords.clone();
420: // place coords in text node coordinate system
421: try {
422: node.getGlobalTransform().createInverse()
423: .transform(pt, pt);
424: } catch (NoninvertibleTransformException ex) {
425: }
426: if (list != null) {
427: for (int i = 0; i < list.size(); i++) {
428: StrokingTextPainter.TextRun run = (StrokingTextPainter.TextRun) list
429: .get(i);
430: AttributedCharacterIterator aci = run.getACI();
431: TextSpanLayout layout = run.getLayout();
432: float x = (float) pt.getX();
433: float y = (float) pt.getY();
434: TextHit textHit = layout.hitTestChar(x, y);
435: Rectangle2D bounds = layout.getBounds2D();
436: if ((textHit != null) && (bounds != null)
437: && bounds.contains(x, y)) {
438: SoftReference sr;
439: sr = (SoftReference) aci
440: .getAttribute(TEXT_COMPOUND_ID);
441: Object delimiter = sr.get();
442: if (delimiter instanceof Element) {
443: return (Element) delimiter;
444: }
445: }
446: }
447: }
448: }
449: return target;
450: }
451: }
452: }
|