| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.bridge; |
| |
| import java.awt.Cursor; |
| import java.awt.geom.Dimension2D; |
| import java.io.IOException; |
| import java.io.InterruptedIOException; |
| import java.lang.ref.SoftReference; |
| import java.lang.ref.WeakReference; |
| import java.net.MalformedURLException; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| |
| import org.apache.batik.bridge.svg12.SVG12BridgeContext; |
| import org.apache.batik.bridge.svg12.SVG12BridgeExtension; |
| import org.apache.batik.css.engine.CSSContext; |
| import org.apache.batik.css.engine.CSSEngine; |
| import org.apache.batik.css.engine.CSSEngineEvent; |
| import org.apache.batik.css.engine.CSSEngineListener; |
| import org.apache.batik.css.engine.CSSEngineUserAgent; |
| import org.apache.batik.css.engine.SVGCSSEngine; |
| import org.apache.batik.css.engine.SystemColorSupport; |
| import org.apache.batik.css.engine.value.Value; |
| import org.apache.batik.dom.AbstractNode; |
| import org.apache.batik.dom.events.NodeEventTarget; |
| import org.apache.batik.dom.svg.AnimatedAttributeListener; |
| import org.apache.batik.dom.svg.AnimatedLiveAttributeValue; |
| import org.apache.batik.dom.svg.SVGContext; |
| import org.apache.batik.dom.svg.SVGDOMImplementation; |
| import org.apache.batik.dom.svg.SVGOMDocument; |
| import org.apache.batik.dom.svg.SVGOMElement; |
| import org.apache.batik.dom.svg.SVGStylableElement; |
| import org.apache.batik.dom.xbl.XBLManager; |
| import org.apache.batik.gvt.CompositeGraphicsNode; |
| import org.apache.batik.gvt.GraphicsNode; |
| import org.apache.batik.gvt.TextPainter; |
| import org.apache.batik.gvt.font.FontFamilyResolver; |
| import org.apache.batik.script.Interpreter; |
| import org.apache.batik.script.InterpreterPool; |
| import org.apache.batik.util.CSSConstants; |
| import org.apache.batik.util.CleanerThread; |
| import org.apache.batik.util.ParsedURL; |
| import org.apache.batik.util.SVGConstants; |
| import org.apache.batik.util.Service; |
| import org.apache.batik.util.XMLConstants; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.events.Event; |
| import org.w3c.dom.events.EventListener; |
| import org.w3c.dom.events.EventTarget; |
| import org.w3c.dom.events.MouseEvent; |
| import org.w3c.dom.events.MutationEvent; |
| import org.w3c.dom.svg.SVGDocument; |
| |
| /** |
| * This class represents a context used by the various bridges and the |
| * builder. A bridge context is associated to a particular document |
| * and cannot be reused. |
| * |
| * The context encapsulates the dynamic bindings between DOM elements |
| * and GVT nodes, graphic contexts such as a <code>GraphicsNodeRenderContext</code>, |
| * and the different objects required by the GVT builder to interpret |
| * a SVG DOM tree such as the current viewport or the user agent. |
| * |
| * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a> |
| * @version $Id$ |
| */ |
| public class BridgeContext implements ErrorConstants, CSSContext { |
| |
| /** |
| * The document is bridge context is dedicated to. |
| */ |
| protected Document document; |
| |
| /** |
| * Whether the document is an SVG 1.2 document. |
| */ |
| protected boolean isSVG12; |
| |
| /** |
| * The GVT builder that might be used to create a GVT subtree. |
| */ |
| protected GVTBuilder gvtBuilder; |
| |
| /** |
| * The interpreter cache per document. |
| * key is the language - |
| * value is a Interpreter |
| */ |
| protected Map interpreterMap = new HashMap(7); |
| |
| /** |
| * A Map of all the font families already matched. This is |
| * to reduce the number of instances of GVTFontFamilies and to |
| * hopefully reduce the time taken to search for a matching SVG font. |
| */ |
| private Map fontFamilyMap; |
| |
| /** |
| * The viewports. |
| * key is an Element - |
| * value is a Viewport |
| */ |
| protected Map viewportMap = new WeakHashMap(); |
| |
| /** |
| * The viewport stack. Used in building time. |
| */ |
| protected List viewportStack = new LinkedList(); |
| |
| /** |
| * The user agent. |
| */ |
| protected UserAgent userAgent; |
| |
| /** |
| * Binding Map: |
| * key is an SVG Element - |
| * value is a GraphicsNode |
| */ |
| protected Map elementNodeMap; |
| |
| /** |
| * Binding Map: |
| * key is GraphicsNode - |
| * value is a SVG Element. |
| */ |
| protected Map nodeElementMap; |
| |
| /** |
| * Bridge Map: |
| * Keys are namespace URI - values are HashMap (with keys are local |
| * name and values are a Bridge instance). |
| */ |
| protected Map namespaceURIMap; |
| |
| /** |
| * Default bridge. |
| * When a bridge is requested for an element type that does not have a |
| * bridge, and there is no other bridge for elements in the same namespace, |
| * the default bridge is returned. This is used for custom elements, |
| * which all use the same bridge type. |
| */ |
| protected Bridge defaultBridge; |
| |
| /** |
| * Default bridge reserved namespaces set. |
| * Default bridges will not be created for elements that have a |
| * namespace URI present in this set. |
| */ |
| protected Set reservedNamespaceSet; |
| |
| /** |
| * Element Data Map: |
| * This is a general location for elements to 'cache' |
| * data. Such as the graphics tree for a pattern or |
| * the Gradient arrays. |
| * |
| * This is a weak hash map and the data is referenced |
| * by SoftReference so both must be referenced elsewhere |
| * to stay live. |
| */ |
| protected Map elementDataMap; |
| |
| /** |
| * The interpreter pool used to handle scripts. |
| */ |
| protected InterpreterPool interpreterPool; |
| |
| /** |
| * The document loader used to load/create Document. |
| */ |
| protected DocumentLoader documentLoader; |
| |
| /** |
| * The size of the document. |
| */ |
| protected Dimension2D documentSize; |
| |
| /** |
| * The text painter to use. Typically, you can specify the text painter that |
| * will be used be text nodes. |
| */ |
| protected TextPainter textPainter; |
| |
| /** |
| * Indicates that no DOM listeners should be registered. In this |
| * case the generated GVT tree should be totally independent of |
| * the DOM tree (in practice text holds references to the source |
| * text elements for font resolution). |
| */ |
| public static final int STATIC = 0; |
| |
| /** |
| * Indicates that DOM listeners should be registered to support, |
| * 'interactivity' this includes anchors and cursors, but does not |
| * include support for DOM modifications. |
| */ |
| public static final int INTERACTIVE = 1; |
| |
| /** |
| * Indicates that all DOM listeners should be registered. This supports |
| * 'interactivity' (anchors and cursors), as well as DOM modifications |
| * listeners to update the GVT rendering tree. |
| */ |
| public static final int DYNAMIC = 2; |
| |
| /** |
| * Whether the bridge should support dynamic, or interactive features. |
| */ |
| protected int dynamicStatus = STATIC; |
| |
| /** |
| * The update manager. |
| */ |
| protected UpdateManager updateManager; |
| |
| /** |
| * The XBL manager. |
| */ |
| protected XBLManager xblManager; |
| |
| /** |
| * The bridge context for the primary document, if this is a bridge |
| * context for a resource document. |
| */ |
| protected BridgeContext primaryContext; |
| |
| /** |
| * Set of WeakReferences to child BridgeContexts. |
| */ |
| protected HashSet childContexts = new HashSet(); |
| |
| /** |
| * The animation engine for the document. |
| */ |
| protected SVGAnimationEngine animationEngine; |
| |
| /** |
| * The animation limiting mode. |
| */ |
| protected int animationLimitingMode; |
| |
| /** |
| * The amount of animation limiting. |
| */ |
| protected float animationLimitingAmount; |
| |
| /** |
| * By default we share a unique instance of InterpreterPool. |
| */ |
| private static InterpreterPool sharedPool = new InterpreterPool(); |
| |
| /** |
| * Constructs a new empty bridge context. |
| */ |
| protected BridgeContext() {} |
| |
| public final FontFamilyResolver getFontFamilyResolver() { |
| return userAgent.getFontFamilyResolver(); |
| } |
| |
| /** |
| * Constructs a new bridge context. |
| * @param userAgent the user agent |
| */ |
| public BridgeContext(UserAgent userAgent) { |
| this(userAgent, |
| sharedPool, |
| new DocumentLoader(userAgent)); |
| } |
| |
| /** |
| * Constructs a new bridge context. |
| * @param userAgent the user agent |
| * @param loader document loader |
| */ |
| public BridgeContext(UserAgent userAgent, |
| DocumentLoader loader) { |
| this(userAgent, sharedPool, loader); |
| } |
| |
| /** |
| * Constructs a new bridge context. |
| * @param userAgent the user agent |
| * @param interpreterPool the interpreter pool |
| * @param documentLoader document loader |
| */ |
| public BridgeContext(UserAgent userAgent, |
| InterpreterPool interpreterPool, |
| DocumentLoader documentLoader) { |
| this.userAgent = userAgent; |
| this.viewportMap.put(userAgent, new UserAgentViewport(userAgent)); |
| this.interpreterPool = interpreterPool; |
| this.documentLoader = documentLoader; |
| } |
| |
| /** |
| * Calls dispose on this BridgeContext, if it is a child context. |
| */ |
| protected void finalize() { |
| if (primaryContext != null) { |
| dispose(); |
| } |
| } |
| |
| /** |
| * This function creates a new 'sub' BridgeContext to associated |
| * with 'newDoc' if one currently doesn't exist, otherwise it |
| * returns the BridgeContext currently associated with the |
| * document. |
| * @param newDoc The document to get/create a BridgeContext for. |
| */ |
| public BridgeContext createSubBridgeContext(SVGOMDocument newDoc) { |
| BridgeContext subCtx; |
| |
| CSSEngine eng = newDoc.getCSSEngine(); |
| if (eng != null) { |
| subCtx = (BridgeContext) newDoc.getCSSEngine().getCSSContext(); |
| return subCtx; |
| } |
| |
| subCtx = createBridgeContext(newDoc); |
| subCtx.primaryContext = primaryContext != null ? primaryContext : this; |
| subCtx.primaryContext.childContexts.add(new WeakReference(subCtx)); |
| subCtx.dynamicStatus = dynamicStatus; |
| subCtx.setGVTBuilder(getGVTBuilder()); |
| subCtx.setTextPainter(getTextPainter()); |
| subCtx.setDocument(newDoc); |
| subCtx.initializeDocument(newDoc); |
| if (isInteractive()) |
| subCtx.addUIEventListeners(newDoc); |
| return subCtx; |
| } |
| |
| /** |
| * This function creates a new BridgeContext, it mostly |
| * exists so subclasses can provide an instance of |
| * themselves when a sub BridgeContext is needed. |
| */ |
| public BridgeContext createBridgeContext(SVGOMDocument doc) { |
| if (doc.isSVG12()) { |
| return new SVG12BridgeContext(getUserAgent(), getDocumentLoader()); |
| } |
| return new BridgeContext(getUserAgent(), getDocumentLoader()); |
| } |
| |
| /** |
| * Initializes the given document. |
| */ |
| protected void initializeDocument(Document document) { |
| SVGOMDocument doc = (SVGOMDocument)document; |
| CSSEngine eng = doc.getCSSEngine(); |
| if (eng == null) { |
| SVGDOMImplementation impl; |
| impl = (SVGDOMImplementation)doc.getImplementation(); |
| eng = impl.createCSSEngine(doc, this); |
| eng.setCSSEngineUserAgent(new CSSEngineUserAgentWrapper(userAgent)); |
| doc.setCSSEngine(eng); |
| eng.setMedia(userAgent.getMedia()); |
| String uri = userAgent.getUserStyleSheetURI(); |
| if (uri != null) { |
| try { |
| ParsedURL url = new ParsedURL(uri); |
| eng.setUserAgentStyleSheet |
| (eng.parseStyleSheet(url, "all")); |
| } catch (Exception e) { |
| userAgent.displayError(e); |
| } |
| } |
| eng.setAlternateStyleSheet(userAgent.getAlternateStyleSheet()); |
| } |
| } |
| |
| /** |
| * Returns the CSS engine associated with given element. |
| */ |
| public CSSEngine getCSSEngineForElement(Element e) { |
| SVGOMDocument doc = (SVGOMDocument)e.getOwnerDocument(); |
| return doc.getCSSEngine(); |
| } |
| |
| // properties //////////////////////////////////////////////////////////// |
| |
| /** |
| * Sets the text painter that will be used by text nodes. This attributes |
| * might be used by bridges (especially SVGTextElementBridge) to set the |
| * text painter of each TextNode. |
| * |
| * @param textPainter the text painter for text nodes |
| */ |
| public void setTextPainter(TextPainter textPainter) { |
| this.textPainter = textPainter; |
| } |
| |
| /** |
| * Returns the text painter that will be used be text nodes. |
| */ |
| public TextPainter getTextPainter() { |
| return textPainter; |
| } |
| |
| /** |
| * Returns the document this bridge context is dedicated to. |
| */ |
| public Document getDocument() { |
| return document; |
| } |
| |
| /** |
| * Sets the document this bridge context is dedicated to, to the |
| * specified document. |
| * @param document the document |
| */ |
| protected void setDocument(Document document) { |
| if (this.document != document){ |
| fontFamilyMap = null; |
| } |
| this.document = document; |
| this.isSVG12 = ((SVGOMDocument) document).isSVG12(); |
| registerSVGBridges(); |
| } |
| |
| /** |
| * Returns the map of font families |
| */ |
| public Map getFontFamilyMap(){ |
| if (fontFamilyMap == null){ |
| fontFamilyMap = new HashMap(); |
| } |
| return fontFamilyMap; |
| } |
| |
| /** |
| * Sets the map of font families to the specified value. |
| * |
| *@param fontFamilyMap the map of font families |
| */ |
| protected void setFontFamilyMap(Map fontFamilyMap) { |
| this.fontFamilyMap = fontFamilyMap; |
| } |
| |
| /** |
| * Associates a data object with a node so it can be retrieved later. |
| * This is primarily used for caching the graphics node generated from |
| * a 'pattern' element. A soft reference to the data object is used. |
| */ |
| public void setElementData(Node n, Object data) { |
| if (elementDataMap == null) { |
| elementDataMap = new WeakHashMap(); |
| } |
| elementDataMap.put(n, new SoftReference(data)); |
| } |
| |
| /** |
| * Retrieves a data object associated with the given node. |
| */ |
| public Object getElementData(Node n) { |
| if (elementDataMap == null) |
| return null; |
| Object o = elementDataMap.get(n); |
| if (o == null) return null; |
| SoftReference sr = (SoftReference)o; |
| o = sr.get(); |
| if (o == null) { |
| elementDataMap.remove(n); |
| } |
| return o; |
| } |
| |
| /** |
| * Returns the user agent of this bridge context. |
| */ |
| public UserAgent getUserAgent() { |
| return userAgent; |
| } |
| |
| /** |
| * Sets the user agent to the specified user agent. |
| * @param userAgent the user agent |
| */ |
| protected void setUserAgent(UserAgent userAgent) { |
| this.userAgent = userAgent; |
| } |
| |
| /** |
| * Returns the GVT builder that is currently used to build the GVT tree. |
| */ |
| public GVTBuilder getGVTBuilder() { |
| return gvtBuilder; |
| } |
| |
| /** |
| * Sets the GVT builder that uses this context. |
| */ |
| protected void setGVTBuilder(GVTBuilder gvtBuilder) { |
| this.gvtBuilder = gvtBuilder; |
| } |
| |
| /** |
| * Returns the interpreter pool used to handle scripts. |
| */ |
| public InterpreterPool getInterpreterPool() { |
| return interpreterPool; |
| } |
| |
| /** |
| * Returns the focus manager. |
| */ |
| public FocusManager getFocusManager() { |
| return focusManager; |
| } |
| |
| /** |
| * Returns the cursor manager |
| */ |
| public CursorManager getCursorManager() { |
| return cursorManager; |
| } |
| |
| /** |
| * Sets the interpreter pool used to handle scripts to the |
| * specified interpreter pool. |
| * @param interpreterPool the interpreter pool |
| */ |
| protected void setInterpreterPool(InterpreterPool interpreterPool) { |
| this.interpreterPool = interpreterPool; |
| } |
| |
| /** |
| * Returns a Interpreter for the specified language. |
| * |
| * @param language the scripting language |
| */ |
| public Interpreter getInterpreter(String language) { |
| if (document == null) { |
| throw new RuntimeException("Unknown document"); |
| } |
| Interpreter interpreter = (Interpreter)interpreterMap.get(language); |
| if (interpreter == null) { |
| try { |
| interpreter = interpreterPool.createInterpreter(document, |
| language, |
| null); |
| String[] mimeTypes = interpreter.getMimeTypes(); |
| for (int i = 0; i < mimeTypes.length; i++) { |
| interpreterMap.put(mimeTypes[i], interpreter); |
| } |
| } catch (Exception e) { |
| if (userAgent != null) { |
| userAgent.displayError(e); |
| return null; |
| } |
| } |
| } |
| |
| if (interpreter == null) { |
| if (userAgent != null) { |
| userAgent.displayError(new Exception("Unknown language: " + language)); |
| } |
| } |
| |
| return interpreter; |
| } |
| |
| /** |
| * Returns the document loader used to load external documents. |
| */ |
| public DocumentLoader getDocumentLoader() { |
| return documentLoader; |
| } |
| |
| /** |
| * Sets the document loader used to load external documents. |
| * @param newDocumentLoader the new document loader |
| */ |
| protected void setDocumentLoader(DocumentLoader newDocumentLoader) { |
| this.documentLoader = newDocumentLoader; |
| } |
| |
| /** |
| * Returns the actual size of the document or null if the document |
| * has not been built yet. |
| */ |
| public Dimension2D getDocumentSize() { |
| return documentSize; |
| } |
| |
| /** |
| * Sets the size of the document to the specified dimension. |
| * |
| * @param d the actual size of the SVG document |
| */ |
| protected void setDocumentSize(Dimension2D d) { |
| this.documentSize = d; |
| } |
| |
| /** |
| * Returns true if the document is dynamic, false otherwise. |
| */ |
| public boolean isDynamic() { |
| return (dynamicStatus == DYNAMIC); |
| } |
| |
| /** |
| * Returns true if the document is interactive, false otherwise. |
| */ |
| public boolean isInteractive() { |
| return (dynamicStatus != STATIC); |
| } |
| |
| /** |
| * Sets the document as a STATIC, INTERACTIVE or DYNAMIC document. |
| * Call this method before the build phase |
| * (ie. before <code>gvtBuilder.build(...)</code>) |
| * otherwise, that will have no effect. |
| * |
| *@param status the document dynamicStatus |
| */ |
| public void setDynamicState(int status) { |
| dynamicStatus = status; |
| } |
| |
| /** |
| * Sets the document as DYNAMIC if <code>dynamic</code> is true |
| * STATIC otherwise. |
| */ |
| public void setDynamic(boolean dynamic) { |
| if (dynamic) |
| setDynamicState(DYNAMIC); |
| else |
| setDynamicState(STATIC); |
| } |
| |
| /** |
| * Sets the document as INTERACTIVE if <code>interactive</code> is |
| * true STATIC otherwise. |
| */ |
| public void setInteractive(boolean interactive) { |
| if (interactive) |
| setDynamicState(INTERACTIVE); |
| else |
| setDynamicState(STATIC); |
| } |
| |
| /** |
| * Returns the update manager, if the bridge supports dynamic features. |
| */ |
| public UpdateManager getUpdateManager() { |
| return updateManager; |
| } |
| |
| /** |
| * Sets the update manager. |
| */ |
| protected void setUpdateManager(UpdateManager um) { |
| updateManager = um; |
| } |
| |
| /** |
| * Sets the update manager on the given BridgeContext. |
| */ |
| protected void setUpdateManager(BridgeContext ctx, UpdateManager um) { |
| ctx.setUpdateManager(um); |
| } |
| |
| /** |
| * Sets the xblManager variable of the given BridgeContext. |
| */ |
| protected void setXBLManager(BridgeContext ctx, XBLManager xm) { |
| ctx.xblManager = xm; |
| } |
| |
| /** |
| * Returns whether the managed document is an SVG 1.2 document. |
| */ |
| public boolean isSVG12() { |
| return isSVG12; |
| } |
| |
| /** |
| * Returns the primary bridge context. |
| */ |
| public BridgeContext getPrimaryBridgeContext() { |
| if (primaryContext != null) { |
| return primaryContext; |
| } |
| return this; |
| } |
| |
| /** |
| * Returns an array of the child contexts. |
| */ |
| public BridgeContext[] getChildContexts() { |
| BridgeContext[] res = new BridgeContext[childContexts.size()]; |
| Iterator it = childContexts.iterator(); |
| for (int i = 0; i < res.length; i++) { |
| WeakReference wr = (WeakReference) it.next(); |
| res[i] = (BridgeContext) wr.get(); |
| } |
| return res; |
| } |
| |
| /** |
| * Returns the AnimationEngine for the document. Creates one if |
| * it doesn't exist. |
| */ |
| public SVGAnimationEngine getAnimationEngine() { |
| if (animationEngine == null) { |
| animationEngine = new SVGAnimationEngine(document, this); |
| setAnimationLimitingMode(); |
| } |
| return animationEngine; |
| } |
| |
| // reference management ////////////////////////////////////////////////// |
| |
| /** |
| * Returns a new URIResolver object. |
| */ |
| public URIResolver createURIResolver(SVGDocument doc, DocumentLoader dl) { |
| return new URIResolver(doc, dl); |
| } |
| |
| /** |
| * Returns the node referenced by the specified element by the specified |
| * uri. The referenced node can be either an element given by a fragment |
| * ID, or the document node. |
| * @param e the element referencing |
| * @param uri the uri of the referenced node |
| */ |
| public Node getReferencedNode(Element e, String uri) { |
| try { |
| SVGDocument document = (SVGDocument)e.getOwnerDocument(); |
| URIResolver ur = createURIResolver(document, documentLoader); |
| Node ref = ur.getNode(uri, e); |
| if (ref == null) { |
| throw new BridgeException(this, e, ERR_URI_BAD_TARGET, |
| new Object[] {uri}); |
| } else { |
| SVGOMDocument refDoc = |
| (SVGOMDocument) (ref.getNodeType() == Node.DOCUMENT_NODE |
| ? ref |
| : ref.getOwnerDocument()); |
| // This is new rather than attaching this BridgeContext |
| // with the new document we now create a whole new |
| // BridgeContext to go with the new document. |
| // This means that the new document has it's own |
| // world of stuff and it should avoid memory leaks |
| // since the new document isn't 'tied into' this |
| // bridge context. |
| if (refDoc != document) { |
| createSubBridgeContext(refDoc); |
| } |
| return ref; |
| } |
| } catch (MalformedURLException ex) { |
| throw new BridgeException(this, e, ex, ERR_URI_MALFORMED, |
| new Object[] {uri}); |
| } catch (InterruptedIOException ex) { |
| throw new InterruptedBridgeException(); |
| } catch (IOException ex) { |
| //ex.printStackTrace(); |
| throw new BridgeException(this, e, ex, ERR_URI_IO, |
| new Object[] {uri}); |
| } catch (SecurityException ex) { |
| throw new BridgeException(this, e, ex, ERR_URI_UNSECURE, |
| new Object[] {uri}); |
| } |
| } |
| |
| /** |
| * Returns the element referenced by the specified element by the |
| * specified uri. The referenced element can not be a Document. |
| * |
| * @param e the element referencing |
| * @param uri the uri of the referenced element |
| */ |
| public Element getReferencedElement(Element e, String uri) { |
| Node ref = getReferencedNode(e, uri); |
| if (ref != null && ref.getNodeType() != Node.ELEMENT_NODE) { |
| throw new BridgeException(this, e, ERR_URI_REFERENCE_A_DOCUMENT, |
| new Object[] {uri}); |
| } |
| return (Element) ref; |
| } |
| |
| // Viewport ////////////////////////////////////////////////////////////// |
| |
| /** |
| * Returns the viewport of the specified element. |
| * |
| * @param e the element interested in its viewport |
| */ |
| public Viewport getViewport(Element e) { |
| if (viewportStack != null) { |
| // building time |
| if (viewportStack.size() == 0) { |
| // outermost svg element |
| return (Viewport)viewportMap.get(userAgent); |
| } else { |
| // current viewport |
| return (Viewport)viewportStack.get(0); |
| } |
| } else { |
| // search the first parent which has defined a viewport |
| e = SVGUtilities.getParentElement(e); |
| while (e != null) { |
| Viewport viewport = (Viewport)viewportMap.get(e); |
| if (viewport != null) { |
| return viewport; |
| } |
| e = SVGUtilities.getParentElement(e); |
| } |
| return (Viewport)viewportMap.get(userAgent); |
| } |
| } |
| |
| /** |
| * Starts a new viewport from the specified element. |
| * |
| * @param e the element that defines a new viewport |
| * @param viewport the viewport of the element |
| */ |
| public void openViewport(Element e, Viewport viewport) { |
| viewportMap.put(e, viewport); |
| if (viewportStack == null) { |
| viewportStack = new LinkedList(); |
| } |
| viewportStack.add(0, viewport); |
| } |
| |
| public void removeViewport(Element e) { |
| viewportMap.remove(e); |
| } |
| |
| /** |
| * Closes the viewport associated to the specified element. |
| * @param e the element that closes its viewport |
| */ |
| public void closeViewport(Element e) { |
| //viewportMap.remove(e); FIXME: potential memory leak |
| viewportStack.remove(0); |
| if (viewportStack.size() == 0) { |
| viewportStack = null; |
| } |
| } |
| |
| // Bindings ////////////////////////////////////////////////////////////// |
| |
| /** |
| * Binds the specified GraphicsNode to the specified Node. This method |
| * automatically bind the graphics node to the element and the element to |
| * the graphics node. |
| * |
| * @param node the DOM Node to bind to the specified graphics node |
| * @param gn the graphics node to bind to the specified element |
| */ |
| public void bind(Node node, GraphicsNode gn) { |
| if (elementNodeMap == null) { |
| elementNodeMap = new WeakHashMap(); |
| nodeElementMap = new WeakHashMap(); |
| } |
| elementNodeMap.put(node, new SoftReference(gn)); |
| nodeElementMap.put(gn, new SoftReference(node)); |
| } |
| |
| /** |
| * Removes the binding of the specified Node. |
| * |
| * @param node the DOM Node to unbind |
| */ |
| public void unbind(Node node) { |
| if (elementNodeMap == null) { |
| return; |
| } |
| GraphicsNode gn = null; |
| SoftReference sr = (SoftReference)elementNodeMap.get(node); |
| if (sr != null) |
| gn = (GraphicsNode)sr.get(); |
| elementNodeMap.remove(node); |
| if (gn != null) |
| nodeElementMap.remove(gn); |
| } |
| |
| /** |
| * Returns the GraphicsNode associated to the specified Node or |
| * null if any. |
| * |
| * @param node the DOM Node associated to the graphics node to return |
| */ |
| public GraphicsNode getGraphicsNode(Node node) { |
| if (elementNodeMap != null) { |
| SoftReference sr = (SoftReference)elementNodeMap.get(node); |
| if (sr != null) |
| return (GraphicsNode)sr.get(); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the Node associated to the specified GraphicsNode or |
| * null if any. |
| * |
| * @param gn the graphics node associated to the element to return |
| */ |
| public Element getElement(GraphicsNode gn) { |
| if (nodeElementMap != null) { |
| SoftReference sr = (SoftReference)nodeElementMap.get(gn); |
| if (sr != null) { |
| Node n = (Node) sr.get(); |
| if (n.getNodeType() == Node.ELEMENT_NODE) { |
| return (Element) n; |
| } |
| } |
| } |
| return null; |
| } |
| |
| // Bridge management ///////////////////////////////////////////////////// |
| |
| /** |
| * Returns true if the specified element has a GraphicsNodeBridge |
| * associated to it, false otherwise. |
| * |
| * @param element the element |
| */ |
| public boolean hasGraphicsNodeBridge(Element element) { |
| if (namespaceURIMap == null || element == null) { |
| return false; |
| } |
| String localName = element.getLocalName(); |
| String namespaceURI = element.getNamespaceURI(); |
| namespaceURI = ((namespaceURI == null)? "" : namespaceURI); |
| HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI); |
| if (localNameMap == null) { |
| return false; |
| } |
| return (localNameMap.get(localName) instanceof GraphicsNodeBridge); |
| } |
| |
| /** |
| * Returns the bridge for the document node. |
| */ |
| public DocumentBridge getDocumentBridge() { |
| return new SVGDocumentBridge(); |
| } |
| |
| /** |
| * Returns the bridge associated with the specified element. |
| * |
| * @param element the element |
| */ |
| public Bridge getBridge(Element element) { |
| if (namespaceURIMap == null || element == null) { |
| return null; |
| } |
| String localName = element.getLocalName(); |
| String namespaceURI = element.getNamespaceURI(); |
| namespaceURI = ((namespaceURI == null)? "" : namespaceURI); |
| return getBridge(namespaceURI, localName); |
| } |
| |
| /** |
| * Returns the bridge associated with the element type |
| * |
| * @param namespaceURI namespace of the requested element |
| * @param localName element's local name |
| * |
| */ |
| public Bridge getBridge(String namespaceURI, String localName) { |
| Bridge bridge = null; |
| if (namespaceURIMap != null) { |
| HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI); |
| if (localNameMap != null) { |
| bridge = (Bridge)localNameMap.get(localName); |
| } |
| } |
| if (bridge == null |
| && (reservedNamespaceSet == null |
| || !reservedNamespaceSet.contains(namespaceURI))) { |
| bridge = defaultBridge; |
| } |
| if (isDynamic()) { |
| return bridge == null ? null : bridge.getInstance(); |
| } else { |
| return bridge; |
| } |
| } |
| |
| /** |
| * Associates the specified <code>Bridge</code> object with the specified |
| * namespace URI and local name. |
| * @param namespaceURI the namespace URI |
| * @param localName the local name |
| * @param bridge the bridge that manages the element |
| */ |
| public void putBridge(String namespaceURI, String localName, Bridge bridge) { |
| // start assert |
| if (!(namespaceURI.equals(bridge.getNamespaceURI()) |
| && localName.equals(bridge.getLocalName()))) { |
| throw new Error("Invalid Bridge: "+ |
| namespaceURI+"/"+bridge.getNamespaceURI()+" "+ |
| localName+"/"+bridge.getLocalName()+" "+ |
| bridge.getClass()); |
| } |
| // end assert |
| if (namespaceURIMap == null) { |
| namespaceURIMap = new HashMap(); |
| } |
| namespaceURI = ((namespaceURI == null)? "" : namespaceURI); |
| HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI); |
| if (localNameMap == null) { |
| localNameMap = new HashMap(); |
| namespaceURIMap.put(namespaceURI, localNameMap); |
| } |
| localNameMap.put(localName, bridge); |
| } |
| |
| /** |
| * Associates the specified <code>Bridge</code> object with it's |
| * namespace URI and local name. |
| * |
| * @param bridge the bridge that manages the element |
| */ |
| public void putBridge(Bridge bridge) { |
| putBridge(bridge.getNamespaceURI(), bridge.getLocalName(), bridge); |
| } |
| |
| /** |
| * Removes the <code>Bridge</code> object associated to the specified |
| * namespace URI and local name. |
| * |
| * @param namespaceURI the namespace URI |
| * @param localName the local name |
| */ |
| public void removeBridge(String namespaceURI, String localName) { |
| if (namespaceURIMap == null) { |
| return; |
| } |
| namespaceURI = ((namespaceURI == null)? "" : namespaceURI); |
| HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI); |
| if (localNameMap != null) { |
| localNameMap.remove(localName); |
| if (localNameMap.isEmpty()) { |
| namespaceURIMap.remove(namespaceURI); |
| if (namespaceURIMap.isEmpty()) { |
| namespaceURIMap = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sets the <code>Bridge</code> object to be used for foreign |
| * namespace elements. |
| * |
| * @param bridge the bridge that manages the element |
| */ |
| public void setDefaultBridge(Bridge bridge) { |
| defaultBridge = bridge; |
| } |
| |
| /** |
| * Adds a namespace URI to avoid when creating default bridges. |
| */ |
| public void putReservedNamespaceURI(String namespaceURI) { |
| if (namespaceURI == null) { |
| namespaceURI = ""; |
| } |
| if (reservedNamespaceSet == null) { |
| reservedNamespaceSet = new HashSet(); |
| } |
| reservedNamespaceSet.add(namespaceURI); |
| } |
| |
| /** |
| * Removes a namespace URI to avoid when creating default bridges. |
| */ |
| public void removeReservedNamespaceURI(String namespaceURI) { |
| if (namespaceURI == null) { |
| namespaceURI = ""; |
| } |
| if (reservedNamespaceSet != null) { |
| reservedNamespaceSet.remove(namespaceURI); |
| if (reservedNamespaceSet.isEmpty()) { |
| reservedNamespaceSet = null; |
| } |
| } |
| } |
| |
| // dynamic support //////////////////////////////////////////////////////// |
| |
| /** |
| * The list of all EventListener attached by bridges that need to |
| * be removed on a dispose() call. |
| */ |
| protected Set eventListenerSet = new HashSet(); |
| |
| /** |
| * The DOM EventListener to receive 'DOMCharacterDataModified' event. |
| */ |
| protected EventListener domCharacterDataModifiedEventListener; |
| |
| /** |
| * The DOM EventListener to receive 'DOMAttrModified' event. |
| */ |
| protected EventListener domAttrModifiedEventListener; |
| |
| /** |
| * The DOM EventListener to receive 'DOMNodeInserted' event. |
| */ |
| protected EventListener domNodeInsertedEventListener; |
| |
| /** |
| * The DOM EventListener to receive 'DOMNodeRemoved' event. |
| */ |
| protected EventListener domNodeRemovedEventListener; |
| |
| /** |
| * The CSSEngine listener to receive CSSEngineEvent. |
| */ |
| protected CSSEngineListener cssPropertiesChangedListener; |
| |
| /** |
| * The listener to receive notification of animated attribute changes. |
| */ |
| protected AnimatedAttributeListener animatedAttributeListener; |
| |
| /** |
| * The EventListener that is responsible of managing DOM focus event. |
| */ |
| protected FocusManager focusManager; |
| |
| /** |
| * Manages cursors and performs caching when appropriate |
| */ |
| protected CursorManager cursorManager = new CursorManager(this); |
| |
| /** |
| * Adds EventListeners to the input document to handle the cursor |
| * property. |
| * This is not done in the addDOMListeners method because |
| * addDOMListeners is only used for dynamic content whereas |
| * cursor support is provided for all content. |
| * Also note that it is very important that the listeners be |
| * registered for the capture phase as the 'default' behavior |
| * for cursors is handled by the BridgeContext during the |
| * capture phase and the 'custom' behavior (handling of 'auto' |
| * on anchors, for example), is handled during the bubbling phase. |
| */ |
| public void addUIEventListeners(Document doc) { |
| NodeEventTarget evtTarget = (NodeEventTarget)doc.getDocumentElement(); |
| |
| DOMMouseOverEventListener domMouseOverListener = |
| new DOMMouseOverEventListener(); |
| evtTarget.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| SVGConstants.SVG_EVENT_MOUSEOVER, |
| domMouseOverListener, true, null); |
| storeEventListenerNS |
| (evtTarget, |
| XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| SVGConstants.SVG_EVENT_MOUSEOVER, |
| domMouseOverListener, true); |
| |
| DOMMouseOutEventListener domMouseOutListener = |
| new DOMMouseOutEventListener(); |
| evtTarget.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| SVGConstants.SVG_EVENT_MOUSEOUT, |
| domMouseOutListener, true, null); |
| storeEventListenerNS |
| (evtTarget, |
| XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| SVGConstants.SVG_EVENT_MOUSEOUT, |
| domMouseOutListener, true); |
| |
| } |
| |
| public void removeUIEventListeners(Document doc) { |
| EventTarget evtTarget = (EventTarget)doc.getDocumentElement(); |
| synchronized (eventListenerSet) { |
| Iterator i = eventListenerSet.iterator(); |
| while (i.hasNext()) { |
| EventListenerMememto elm = (EventListenerMememto)i.next(); |
| NodeEventTarget et = elm.getTarget(); |
| if (et == evtTarget) { |
| EventListener el = elm.getListener(); |
| boolean uc = elm.getUseCapture(); |
| String t = elm.getEventType(); |
| boolean n = elm.getNamespaced(); |
| if (et == null || el == null || t == null) { |
| continue; |
| } |
| if (n) { |
| String ns = elm.getNamespaceURI(); |
| et.removeEventListenerNS(ns, t, el, uc); |
| } else { |
| et.removeEventListener(t, el, uc); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds EventListeners to the DOM and CSSEngineListener to the |
| * CSSEngine to handle any modifications on the DOM tree or style |
| * properties and update the GVT tree in response. |
| */ |
| public void addDOMListeners() { |
| SVGOMDocument doc = (SVGOMDocument)document; |
| |
| domAttrModifiedEventListener = new DOMAttrModifiedEventListener(); |
| doc.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMAttrModified", |
| domAttrModifiedEventListener, true, null); |
| |
| domNodeInsertedEventListener = new DOMNodeInsertedEventListener(); |
| doc.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeInserted", |
| domNodeInsertedEventListener, true, null); |
| |
| domNodeRemovedEventListener = new DOMNodeRemovedEventListener(); |
| doc.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeRemoved", |
| domNodeRemovedEventListener, true, null); |
| |
| domCharacterDataModifiedEventListener = |
| new DOMCharacterDataModifiedEventListener(); |
| doc.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMCharacterDataModified", |
| domCharacterDataModifiedEventListener, true, null); |
| |
| animatedAttributeListener = new AnimatedAttrListener(); |
| doc.addAnimatedAttributeListener(animatedAttributeListener); |
| |
| focusManager = new FocusManager(document); |
| |
| CSSEngine cssEngine = doc.getCSSEngine(); |
| cssPropertiesChangedListener = new CSSPropertiesChangedListener(); |
| cssEngine.addCSSEngineListener(cssPropertiesChangedListener); |
| } |
| |
| /** |
| * Removes event listeners from the DOM and CSS engine. |
| */ |
| protected void removeDOMListeners() { |
| SVGOMDocument doc = (SVGOMDocument)document; |
| |
| doc.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified", |
| domAttrModifiedEventListener, true); |
| doc.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeInserted", |
| domNodeInsertedEventListener, true); |
| doc.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeRemoved", |
| domNodeRemovedEventListener, true); |
| doc.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMCharacterDataModified", |
| domCharacterDataModifiedEventListener, true); |
| |
| doc.removeAnimatedAttributeListener(animatedAttributeListener); |
| |
| CSSEngine cssEngine = doc.getCSSEngine(); |
| if (cssEngine != null) { |
| cssEngine.removeCSSEngineListener |
| (cssPropertiesChangedListener); |
| cssEngine.dispose(); |
| doc.setCSSEngine(null); |
| } |
| } |
| |
| /** |
| * Adds to the eventListenerSet the specified event listener |
| * registration. |
| */ |
| protected void storeEventListener(EventTarget t, |
| String s, |
| EventListener l, |
| boolean b) { |
| synchronized (eventListenerSet) { |
| eventListenerSet.add(new EventListenerMememto(t, s, l, b, this)); |
| } |
| } |
| |
| /** |
| * Adds to the eventListenerSet the specified event listener |
| * registration. |
| */ |
| protected void storeEventListenerNS(EventTarget t, |
| String n, |
| String s, |
| EventListener l, |
| boolean b) { |
| synchronized (eventListenerSet) { |
| eventListenerSet.add(new EventListenerMememto(t, n, s, l, b, this)); |
| } |
| } |
| |
| public static class SoftReferenceMememto |
| extends CleanerThread.SoftReferenceCleared { |
| Object mememto; |
| Set set; |
| // String refStr; |
| SoftReferenceMememto(Object ref, Object mememto, Set set) { |
| super(ref); |
| // refStr = ref.toString(); |
| this.mememto = mememto; |
| this.set = set; |
| } |
| |
| public void cleared() { |
| synchronized (set) { |
| // System.err.println("SRClear: " + refStr); |
| set.remove(mememto); |
| mememto = null; |
| set = null; |
| } |
| } |
| } |
| |
| /** |
| * A class used to store an EventListener added to the DOM. |
| */ |
| protected static class EventListenerMememto { |
| |
| public SoftReference target; // Soft ref to EventTarget |
| public SoftReference listener; // Soft ref to EventListener |
| public boolean useCapture; |
| public String namespaceURI; |
| public String eventType; |
| public boolean namespaced; |
| |
| public EventListenerMememto(EventTarget t, |
| String s, |
| EventListener l, |
| boolean b, |
| BridgeContext ctx) { |
| Set set = ctx.eventListenerSet; |
| target = new SoftReferenceMememto(t, this, set); |
| listener = new SoftReferenceMememto(l, this, set); |
| eventType = s; |
| useCapture = b; |
| } |
| |
| public EventListenerMememto(EventTarget t, |
| String n, |
| String s, |
| EventListener l, |
| boolean b, |
| BridgeContext ctx) { |
| this(t, s, l, b, ctx); |
| namespaceURI = n; |
| namespaced = true; |
| } |
| |
| public EventListener getListener() { |
| return (EventListener)listener.get(); |
| } |
| public NodeEventTarget getTarget() { |
| return (NodeEventTarget)target.get(); |
| } |
| public boolean getUseCapture() { |
| return useCapture; |
| } |
| public String getNamespaceURI() { |
| return namespaceURI; |
| } |
| public String getEventType() { |
| return eventType; |
| } |
| public boolean getNamespaced() { |
| return namespaced; |
| } |
| } |
| |
| /** |
| * Adds the GVT listener for AWT event support. |
| */ |
| public void addGVTListener(Document doc) { |
| BridgeEventSupport.addGVTListener(this, doc); |
| } |
| |
| /** |
| * Clears the list of child BridgeContexts and disposes them if there are |
| * no more references to them. |
| */ |
| protected void clearChildContexts() { |
| childContexts.clear(); |
| } |
| |
| /** |
| * Disposes this BridgeContext. |
| */ |
| public void dispose() { |
| clearChildContexts(); |
| |
| synchronized (eventListenerSet) { |
| // remove all listeners added by Bridges |
| Iterator iter = eventListenerSet.iterator(); |
| while (iter.hasNext()) { |
| EventListenerMememto m = (EventListenerMememto)iter.next(); |
| NodeEventTarget et = m.getTarget(); |
| EventListener el = m.getListener(); |
| boolean uc = m.getUseCapture(); |
| String t = m.getEventType(); |
| boolean n = m.getNamespaced(); |
| if (et == null || el == null || t == null) { |
| continue; |
| } |
| if (n) { |
| String ns = m.getNamespaceURI(); |
| et.removeEventListenerNS(ns, t, el, uc); |
| } else { |
| et.removeEventListener(t, el, uc); |
| } |
| } |
| } |
| |
| if (document != null) { |
| removeDOMListeners(); |
| AbstractGraphicsNodeBridge.disposeTree(document); |
| } |
| |
| if (animationEngine != null) { |
| animationEngine.dispose(); |
| animationEngine = null; |
| } |
| |
| Iterator iter = interpreterMap.values().iterator(); |
| while (iter.hasNext()) { |
| Interpreter interpreter = (Interpreter)iter.next(); |
| if (interpreter != null) |
| interpreter.dispose(); |
| } |
| interpreterMap.clear(); |
| |
| if (focusManager != null) { |
| focusManager.dispose(); |
| } |
| if (elementDataMap != null) { |
| elementDataMap.clear(); |
| } |
| if (nodeElementMap != null) { |
| nodeElementMap.clear(); |
| } |
| if (elementNodeMap != null) { |
| elementNodeMap.clear(); |
| } |
| } |
| |
| /** |
| * Returns the SVGContext associated to the specified Node or null if |
| * there is none. |
| */ |
| protected static SVGContext getSVGContext(Node node) { |
| if (node instanceof SVGOMElement) { |
| return ((SVGOMElement) node).getSVGContext(); |
| } else if (node instanceof SVGOMDocument) { |
| return ((SVGOMDocument) node).getSVGContext(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the BridgeUpdateHandler associated to the specified Node |
| * or null if there is none. |
| */ |
| protected static BridgeUpdateHandler getBridgeUpdateHandler(Node node) { |
| SVGContext ctx = getSVGContext(node); |
| return (ctx == null) ? null : (BridgeUpdateHandler)ctx; |
| } |
| |
| /** |
| * The DOM EventListener invoked when an attribute is modified. |
| */ |
| protected class DOMAttrModifiedEventListener implements EventListener { |
| |
| /** |
| * Creates a new DOMAttrModifiedEventListener. |
| */ |
| public DOMAttrModifiedEventListener() { |
| } |
| |
| /** |
| * Handles 'DOMAttrModified' event type. |
| */ |
| public void handleEvent(Event evt) { |
| Node node = (Node)evt.getTarget(); |
| BridgeUpdateHandler h = getBridgeUpdateHandler(node); |
| if (h != null) { |
| try { |
| h.handleDOMAttrModifiedEvent((MutationEvent)evt); |
| } catch (Exception e) { |
| userAgent.displayError(e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when the mouse exits an element |
| */ |
| protected class DOMMouseOutEventListener implements EventListener { |
| |
| /** |
| * Creates a new DOMMouseOutEventListener. |
| */ |
| public DOMMouseOutEventListener() { |
| } |
| |
| /** |
| * Handles 'mouseout' MouseEvent event type. |
| */ |
| public void handleEvent(Event evt) { |
| MouseEvent me = (MouseEvent)evt; |
| Element newTarget = (Element)me.getRelatedTarget(); |
| Cursor cursor = CursorManager.DEFAULT_CURSOR; |
| if (newTarget != null) |
| cursor = CSSUtilities.convertCursor |
| (newTarget, BridgeContext.this); |
| if (cursor == null) |
| cursor = CursorManager.DEFAULT_CURSOR; |
| |
| userAgent.setSVGCursor(cursor); |
| } |
| } |
| |
| |
| /** |
| * The DOM EventListener invoked when the mouse mouves over a new |
| * element. |
| * |
| * Here is how cursors are handled: |
| * |
| */ |
| protected class DOMMouseOverEventListener implements EventListener { |
| |
| /** |
| * Creates a new DOMMouseOverEventListener. |
| */ |
| public DOMMouseOverEventListener() { |
| } |
| |
| /** |
| * Handles 'mouseover' MouseEvent event type. |
| */ |
| public void handleEvent(Event evt) { |
| Element target = (Element)evt.getTarget(); |
| Cursor cursor = CSSUtilities.convertCursor(target, BridgeContext.this); |
| |
| if (cursor != null) { |
| userAgent.setSVGCursor(cursor); |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a node is added. |
| */ |
| protected class DOMNodeInsertedEventListener implements EventListener { |
| |
| /** |
| * Creates a new DOMNodeInsertedEventListener. |
| */ |
| public DOMNodeInsertedEventListener() { |
| } |
| |
| /** |
| * Handles 'DOMNodeInserted' event type. |
| */ |
| public void handleEvent(Event evt) { |
| MutationEvent me = (MutationEvent)evt; |
| BridgeUpdateHandler h = |
| getBridgeUpdateHandler(me.getRelatedNode()); |
| if (h != null) { |
| try { |
| h.handleDOMNodeInsertedEvent(me); |
| } catch (InterruptedBridgeException ibe) { |
| /* do nothing */ |
| } catch (Exception e) { |
| userAgent.displayError(e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a node is removed. |
| */ |
| protected class DOMNodeRemovedEventListener implements EventListener { |
| |
| /** |
| * Creates a new DOMNodeRemovedEventListener. |
| */ |
| public DOMNodeRemovedEventListener() { |
| } |
| |
| /** |
| * Handles 'DOMNodeRemoved' event type. |
| */ |
| public void handleEvent(Event evt) { |
| Node node = (Node)evt.getTarget(); |
| BridgeUpdateHandler h = getBridgeUpdateHandler(node); |
| if (h != null) { |
| try { |
| h.handleDOMNodeRemovedEvent((MutationEvent)evt); |
| } catch (Exception e) { |
| userAgent.displayError(e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a character data is changed. |
| */ |
| protected class DOMCharacterDataModifiedEventListener |
| implements EventListener { |
| |
| /** |
| * Creates a new DOMCharacterDataModifiedEventListener. |
| */ |
| public DOMCharacterDataModifiedEventListener() { |
| } |
| |
| /** |
| * Handles 'DOMCharacterDataModified' event type. |
| */ |
| public void handleEvent(Event evt) { |
| Node node = (Node)evt.getTarget(); |
| while (node != null && !(node instanceof SVGOMElement)) { |
| node = (Node) ((AbstractNode) node).getParentNodeEventTarget(); |
| } |
| BridgeUpdateHandler h = getBridgeUpdateHandler(node); |
| if (h != null) { |
| try { |
| h.handleDOMCharacterDataModified((MutationEvent)evt); |
| } catch (Exception e) { |
| userAgent.displayError(e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * The CSSEngineListener invoked when CSS properties are modified |
| * on a particular element. |
| */ |
| protected class CSSPropertiesChangedListener implements CSSEngineListener { |
| |
| /** |
| * Creates a new CSSPropertiesChangedListener. |
| */ |
| public CSSPropertiesChangedListener() { |
| } |
| |
| /** |
| * Handles CSSEngineEvent that describes the CSS properties |
| * that have changed on a particular element. |
| */ |
| public void propertiesChanged(CSSEngineEvent evt) { |
| Element elem = evt.getElement(); |
| SVGContext ctx = getSVGContext(elem); |
| if (ctx == null) { |
| GraphicsNode pgn = getGraphicsNode(elem.getParentNode()); |
| if ((pgn == null) || !(pgn instanceof CompositeGraphicsNode)) { |
| // Something changed in this element but we really don't |
| // care since its parent isn't displayed either. |
| return; |
| } |
| CompositeGraphicsNode parent = (CompositeGraphicsNode)pgn; |
| // Check if 'display' changed on this element. |
| |
| int [] properties = evt.getProperties(); |
| for (int i=0; i < properties.length; ++i) { |
| if (properties[i] == SVGCSSEngine.DISPLAY_INDEX) { |
| if (!CSSUtilities.convertDisplay(elem)) { |
| // (Still) Not displayed |
| break; |
| } |
| // build the graphics node |
| GVTBuilder builder = getGVTBuilder(); |
| GraphicsNode childNode = builder.build |
| (BridgeContext.this, elem); |
| if (childNode == null) { |
| // the added element is not a graphic element? |
| break; |
| } |
| int idx = -1; |
| for(Node ps = elem.getPreviousSibling(); ps != null; |
| ps = ps.getPreviousSibling()) { |
| if (ps.getNodeType() != Node.ELEMENT_NODE) |
| continue; |
| Element pse = (Element)ps; |
| GraphicsNode gn = getGraphicsNode(pse); |
| if (gn == null) |
| continue; |
| idx = parent.indexOf(gn); |
| if (idx == -1) |
| continue; |
| break; |
| } |
| // insert after prevSibling, if |
| // it was -1 this becomes 0 (first slot) |
| idx++; |
| parent.add(idx, childNode); |
| break; |
| } |
| } |
| } if (ctx != null && (ctx instanceof BridgeUpdateHandler)) { |
| ((BridgeUpdateHandler)ctx).handleCSSEngineEvent(evt); |
| } |
| } |
| } |
| |
| /** |
| * A listener class for changes to animated attributes in the document. |
| */ |
| protected class AnimatedAttrListener |
| implements AnimatedAttributeListener { |
| |
| /** |
| * Creates a new AnimatedAttributeListener. |
| */ |
| public AnimatedAttrListener() { |
| } |
| |
| /** |
| * Called to notify an object of a change to the animated value of |
| * an animated XML attribute. |
| * @param e the owner element of the changed animated attribute |
| * @param alav the AnimatedLiveAttributeValue that changed |
| */ |
| public void animatedAttributeChanged(Element e, |
| AnimatedLiveAttributeValue alav) { |
| BridgeUpdateHandler h = getBridgeUpdateHandler(e); |
| if (h != null) { |
| try { |
| h.handleAnimatedAttributeChanged(alav); |
| } catch (Exception ex) { |
| userAgent.displayError(ex); |
| } |
| } |
| } |
| |
| /** |
| * Called to notify an object of a change to the value of an 'other' |
| * animation. |
| * @param e the element being animated |
| * @param type the type of animation whose value changed |
| */ |
| public void otherAnimationChanged(Element e, String type) { |
| BridgeUpdateHandler h = getBridgeUpdateHandler(e); |
| if (h != null) { |
| try { |
| h.handleOtherAnimationChanged(type); |
| } catch (Exception ex) { |
| userAgent.displayError(ex); |
| } |
| } |
| } |
| } |
| |
| // CSS context //////////////////////////////////////////////////////////// |
| |
| /** |
| * Returns the Value corresponding to the given system color. |
| */ |
| public Value getSystemColor(String ident) { |
| return SystemColorSupport.getSystemColor(ident); |
| } |
| |
| /** |
| * Returns the value corresponding to the default font. |
| */ |
| public Value getDefaultFontFamily() { |
| // No cache needed since the default font family is asked only |
| // one time on the root element (only if it does not have its |
| // own font-family). |
| SVGOMDocument doc = (SVGOMDocument)document; |
| SVGStylableElement root = (SVGStylableElement)doc.getRootElement(); |
| String str = userAgent.getDefaultFontFamily(); |
| return doc.getCSSEngine().parsePropertyValue |
| (root,SVGConstants.CSS_FONT_FAMILY_PROPERTY, str); |
| } |
| |
| /** |
| * Returns a lighter font-weight. |
| */ |
| public float getLighterFontWeight(float f) { |
| return userAgent.getLighterFontWeight(f); |
| } |
| |
| /** |
| * Returns a bolder font-weight. |
| */ |
| public float getBolderFontWeight(float f) { |
| return userAgent.getBolderFontWeight(f); |
| } |
| |
| /** |
| * Returns the size of a px CSS unit in millimeters. |
| */ |
| public float getPixelUnitToMillimeter() { |
| return userAgent.getPixelUnitToMillimeter(); |
| } |
| |
| /** |
| * Returns the size of a px CSS unit in millimeters. |
| * This will be removed after next release. |
| * @see #getPixelUnitToMillimeter() |
| */ |
| public float getPixelToMillimeter() { |
| return getPixelUnitToMillimeter(); |
| |
| } |
| |
| /** |
| * Returns the medium font size. |
| */ |
| public float getMediumFontSize() { |
| return userAgent.getMediumFontSize(); |
| } |
| |
| /** |
| * Returns the width of the block which directly contains the |
| * given element. |
| */ |
| public float getBlockWidth(Element elt) { |
| return getViewport(elt).getWidth(); |
| } |
| |
| /** |
| * Returns the height of the block which directly contains the |
| * given element. |
| */ |
| public float getBlockHeight(Element elt) { |
| return getViewport(elt).getHeight(); |
| } |
| |
| /** |
| * This method throws a SecurityException if the resource |
| * found at url and referenced from docURL |
| * should not be loaded. |
| * |
| * This is a convenience method to call checkLoadExternalResource |
| * on the ExternalResourceSecurity strategy returned by |
| * getExternalResourceSecurity. |
| * |
| * @param resourceURL url for the script, as defined in |
| * the resource's xlink:href attribute. If that |
| * attribute was empty, then this parameter should |
| * be null |
| * @param docURL url for the document into which the |
| * resource was found. |
| */ |
| public void |
| checkLoadExternalResource(ParsedURL resourceURL, |
| ParsedURL docURL) throws SecurityException { |
| userAgent.checkLoadExternalResource(resourceURL, |
| docURL); |
| } |
| |
| |
| /** |
| * Tells whether the given SVG document is dynamic. |
| */ |
| public boolean isDynamicDocument(Document doc) { |
| return BaseScriptingEnvironment.isDynamicDocument(this, doc); |
| } |
| |
| /** |
| * Tells whether the given SVG document is Interactive. |
| * We say it is, if it has any <title>, <desc>, or <a> elements, |
| * of if the 'cursor' property is anything but Auto on any element. |
| */ |
| public boolean isInteractiveDocument(Document doc) { |
| |
| Element root = ((SVGDocument)doc).getRootElement(); |
| if (!SVGConstants.SVG_NAMESPACE_URI.equals(root.getNamespaceURI())) |
| return false; |
| |
| return checkInteractiveElement(root); |
| } |
| |
| /** |
| * used by isInteractiveDocument to check if document |
| * contains any 'interactive' elements. |
| */ |
| public boolean checkInteractiveElement(Element e) { |
| return checkInteractiveElement |
| ((SVGDocument)e.getOwnerDocument(), e); |
| } |
| |
| /** |
| * used by isInteractiveDocument to check if document |
| * contains any 'interactive' elements. |
| */ |
| public boolean checkInteractiveElement(SVGDocument doc, |
| Element e) { |
| String tag = e.getLocalName(); |
| |
| // Check if it's one of our important element. |
| if (SVGConstants.SVG_A_TAG.equals(tag)) |
| return true; |
| |
| // This is a bit of a hack but don't count |
| // title and desc as children of root SVG since |
| // we don't show tool tips for them anyways. |
| if (SVGConstants.SVG_TITLE_TAG.equals(tag)) { |
| return (e.getParentNode() != doc.getRootElement()); |
| } |
| if (SVGConstants.SVG_DESC_TAG.equals(tag)) { |
| return (e.getParentNode() != doc.getRootElement()); |
| } |
| if (SVGConstants.SVG_CURSOR_TAG.equals(tag)) |
| return true; |
| |
| // I am well aware that this is not 100% accurate but it's |
| // the best I can do w/o booting the CSSEngine. |
| if (e.getAttribute(CSSConstants.CSS_CURSOR_PROPERTY).length() >0) |
| return true; |
| |
| /* We would like to do this but the CSS Engine isn't setup when |
| we want to do this. |
| |
| // Check if cursor property is set to something other than 'auto'. |
| Value cursorValue = CSSUtilities.getComputedStyle |
| (e, SVGCSSEngine.CURSOR_INDEX); |
| if ((cursorValue != null) && |
| (cursorValue.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE) && |
| (cursorValue.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) && |
| (SVGConstants.SVG_AUTO_VALUE.equals(cursorValue.getStringValue()))) |
| return true; |
| */ |
| |
| // Check all the child elements for any of the above. |
| final String svg_ns = SVGConstants.SVG_NAMESPACE_URI; |
| for (Node n = e.getFirstChild(); |
| n != null; |
| n = n.getNextSibling()) { |
| if (n.getNodeType() == Node.ELEMENT_NODE) { |
| Element child = (Element)n; |
| if (svg_ns.equals(child.getNamespaceURI())) |
| if (checkInteractiveElement(child)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Sets the animation limiting mode to "none". |
| */ |
| public void setAnimationLimitingNone() { |
| animationLimitingMode = 0; |
| if (animationEngine != null) { |
| setAnimationLimitingMode(); |
| } |
| } |
| |
| /** |
| * Sets the animation limiting mode to a percentage of CPU. |
| * @param pc the maximum percentage of CPU to use (0 < pc ≤ 1) |
| */ |
| public void setAnimationLimitingCPU(float pc) { |
| animationLimitingMode = 1; |
| animationLimitingAmount = pc; |
| if (animationEngine != null) { |
| setAnimationLimitingMode(); |
| } |
| } |
| |
| /** |
| * Sets the animation limiting mode to a number of frames per second. |
| * @param fps the maximum number of frames per second (fps > 0) |
| */ |
| public void setAnimationLimitingFPS(float fps) { |
| animationLimitingMode = 2; |
| animationLimitingAmount = fps; |
| if (animationEngine != null) { |
| setAnimationLimitingMode(); |
| } |
| } |
| |
| /** |
| * Set the animationg limiting mode on the animation engine. |
| */ |
| protected void setAnimationLimitingMode() { |
| switch (animationLimitingMode) { |
| case 0: // unlimited |
| animationEngine.setAnimationLimitingNone(); |
| break; |
| case 1: // %cpu |
| animationEngine.setAnimationLimitingCPU |
| (animationLimitingAmount); |
| break; |
| case 2: // fps |
| animationEngine.setAnimationLimitingFPS |
| (animationLimitingAmount); |
| break; |
| } |
| } |
| |
| // bridge extensions support ////////////////////////////////////////////// |
| |
| protected List extensions = null; |
| |
| /** |
| * Registers the bridges to handle SVG 1.0 elements. |
| */ |
| public void registerSVGBridges() { |
| UserAgent ua = getUserAgent(); |
| List ext = getBridgeExtensions(document); |
| Iterator iter = ext.iterator(); |
| |
| while(iter.hasNext()) { |
| BridgeExtension be = (BridgeExtension)iter.next(); |
| be.registerTags(this); |
| ua.registerExtension(be); |
| } |
| } |
| |
| public List getBridgeExtensions(Document doc) { |
| Element root = ((SVGOMDocument)doc).getRootElement(); |
| String ver = root.getAttributeNS |
| (null, SVGConstants.SVG_VERSION_ATTRIBUTE); |
| BridgeExtension svgBE; |
| if ((ver.length()==0) || ver.equals("1.0") || ver.equals("1.1")) |
| svgBE = new SVGBridgeExtension(); |
| else |
| svgBE = new SVG12BridgeExtension(); |
| |
| float priority = svgBE.getPriority(); |
| extensions = new LinkedList(getGlobalBridgeExtensions()); |
| |
| ListIterator li = extensions.listIterator(); |
| for (;;) { |
| if (!li.hasNext()) { |
| li.add(svgBE); |
| break; |
| } |
| BridgeExtension lbe = (BridgeExtension)li.next(); |
| if (lbe.getPriority() > priority) { |
| li.previous(); |
| li.add(svgBE); |
| break; |
| } |
| } |
| |
| return extensions; |
| } |
| |
| /** |
| * Returns the extensions supported by this bridge context. |
| */ |
| protected static List globalExtensions = null; |
| |
| public static synchronized List getGlobalBridgeExtensions() { |
| if (globalExtensions != null) { |
| return globalExtensions; |
| } |
| globalExtensions = new LinkedList(); |
| |
| Iterator iter = Service.providers(BridgeExtension.class); |
| |
| while (iter.hasNext()) { |
| BridgeExtension be = (BridgeExtension)iter.next(); |
| float priority = be.getPriority(); |
| ListIterator li = globalExtensions.listIterator(); |
| for (;;) { |
| if (!li.hasNext()) { |
| li.add(be); |
| break; |
| } |
| BridgeExtension lbe = (BridgeExtension)li.next(); |
| if (lbe.getPriority() > priority) { |
| li.previous(); |
| li.add(be); |
| break; |
| } |
| } |
| } |
| return globalExtensions; |
| } |
| |
| public static class CSSEngineUserAgentWrapper implements CSSEngineUserAgent { |
| UserAgent ua; |
| CSSEngineUserAgentWrapper(UserAgent ua) { |
| this.ua = ua; |
| } |
| |
| /** |
| * Displays an error resulting from the specified Exception. |
| */ |
| public void displayError(Exception ex) { ua.displayError(ex); } |
| |
| /** |
| * Displays a message in the User Agent interface. |
| */ |
| public void displayMessage(String message) { ua.displayMessage(message); } |
| } |
| |
| } |
| |