blob: 563bcfa2233bdf4c2d7459aaf73b4b1d4a5a3e43 [file] [log] [blame]
/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.swing.svg;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.swing.JComponent;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.BridgeException;
import org.apache.batik.bridge.BridgeMutationEvent;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GraphicsNodeBridge;
import org.apache.batik.bridge.ViewBox;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.svg.SVGOMDocument;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.event.EventDispatcher;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.gvt.JGVTComponent;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGAElement;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGSVGElement;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.MutationEvent;
/**
* This class represents a Swing component which can display SVG.
*
* @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
* @version $Id$
*/
public class JSVGComponent extends JGVTComponent {
/**
* The document loader.
*/
protected SVGDocumentLoader documentLoader;
/**
* The next document loader to run.
*/
protected SVGDocumentLoader nextDocumentLoader;
/**
* The concrete bridge document loader.
*/
protected DocumentLoader loader;
/**
* The GVT tree builder.
*/
protected GVTTreeBuilder gvtTreeBuilder;
/**
* The next GVT tree builder to run.
*/
protected GVTTreeBuilder nextGVTTreeBuilder;
/**
* The current SVG document.
*/
protected SVGDocument svgDocument;
/**
* The document loader listeners.
*/
protected List svgDocumentLoaderListeners = new LinkedList();
/**
* The GVT tree builder listeners.
*/
protected List gvtTreeBuilderListeners = new LinkedList();
/**
* The user agent.
*/
protected UserAgent userAgent;
/**
* The SVG user agent.
*/
protected SVGUserAgent svgUserAgent;
/**
* The current bridge context.
*/
protected BridgeContext bridgeContext;
/**
* The current document fragment identifier.
*/
protected String fragmentIdentifier;
/**
* Creates a new JSVGComponent.
*/
public JSVGComponent() {
this(null, false, false);
}
/**
* Creates a new JSVGComponent.
* @param ua a SVGUserAgent instance or null.
* @param eventEnabled Whether the GVT tree should be reactive
* to mouse and key events.
* @param selectableText Whether the text should be selectable.
*/
public JSVGComponent(SVGUserAgent ua, boolean eventsEnabled,
boolean selectableText) {
super(eventsEnabled, selectableText);
svgUserAgent = ua;
userAgent = createUserAgent();
addSVGDocumentLoaderListener((SVGListener)listener);
addGVTTreeBuilderListener((SVGListener)listener);
}
/**
* Stops the processing of the current document.
*/
public void stopProcessing() {
nextDocumentLoader = null;
nextGVTTreeBuilder = null;
if (documentLoader != null) {
documentLoader.interrupt();
} else if (gvtTreeBuilder != null) {
gvtTreeBuilder.interrupt();
} else {
super.stopProcessing();
}
}
/**
* Loads a SVG document from the given URL.
* <em>Note: Because the loading is multi-threaded, the current
* SVG document is not garanteed to be updated after this method
* returns. The only way to be notified a document has been loaded
* is to listen to the <tt>SVGDocumentLoaderEvent</tt>s.</em>
*/
public void loadSVGDocument(String url) {
stopProcessing();
URL oldURI = null;
if (svgDocument != null) {
oldURI = ((SVGOMDocument)svgDocument).getURLObject();
}
URL newURI = null;
try {
newURI = new URL(oldURI, url);
} catch (MalformedURLException e) {
userAgent.displayError(e);
return;
}
url = newURI.toString();
fragmentIdentifier = newURI.getRef();
loader = new DocumentLoader(userAgent);
nextDocumentLoader = new SVGDocumentLoader(url, loader);
nextDocumentLoader.setPriority(Thread.MIN_PRIORITY);
Iterator it = svgDocumentLoaderListeners.iterator();
while (it.hasNext()) {
nextDocumentLoader.addSVGDocumentLoaderListener
((SVGDocumentLoaderListener)it.next());
}
if (documentLoader == null &&
gvtTreeBuilder == null &&
gvtTreeRenderer == null) {
startDocumentLoader();
}
}
/**
* Starts a loading thread.
*/
private void startDocumentLoader() {
documentLoader = nextDocumentLoader;
nextDocumentLoader = null;
documentLoader.start();
}
/**
* Sets the SVG document to display.
*/
public void setSVGDocument(SVGDocument doc) {
stopProcessing();
if (!(doc.getImplementation() instanceof SVGDOMImplementation)) {
throw new IllegalArgumentException("Invalid DOM implementation.");
}
if (eventsEnabled && svgDocument != null) {
// fire the unload event
Event evt = svgDocument.createEvent("SVGEvents");
evt.initEvent("SVGUnload", false, false);
((EventTarget)(svgDocument.getRootElement())).dispatchEvent(evt);
}
svgDocument = doc;
Element root = doc.getDocumentElement();
String znp = root.getAttributeNS(null, SVGConstants.SVG_ZOOM_AND_PAN_ATTRIBUTE);
disableInteractions = !znp.equals(SVGConstants.SVG_MAGNIFY_VALUE);
bridgeContext = createBridgeContext();
nextGVTTreeBuilder = new GVTTreeBuilder(doc, bridgeContext);
nextGVTTreeBuilder.setPriority(Thread.MIN_PRIORITY);
Iterator it = gvtTreeBuilderListeners.iterator();
while (it.hasNext()) {
nextGVTTreeBuilder.addGVTTreeBuilderListener
((GVTTreeBuilderListener)it.next());
}
releaseRenderingReferences();
initializeEventHandling();
if (gvtTreeBuilder == null &&
documentLoader == null &&
gvtTreeRenderer == null) {
startGVTTreeBuilder();
}
}
/**
* Starts a tree builder.
*/
private void startGVTTreeBuilder() {
gvtTreeBuilder = nextGVTTreeBuilder;
nextGVTTreeBuilder = null;
gvtTreeBuilder.start();
}
/**
* Returns the current SVG document.
*/
public SVGDocument getSVGDocument() {
return svgDocument;
}
/**
* Returns the current's document fragment identifier.
*/
public String getFragmentIdentifier() {
return fragmentIdentifier;
}
/**
* Sets the current fragment identifier.
*/
public void setFragmentIdentifier(String fi) {
fragmentIdentifier = fi;
computeRenderingTransform();
}
/**
* Creates a new bridge context.
*/
protected BridgeContext createBridgeContext() {
if (loader == null) {
loader = new DocumentLoader(userAgent);
}
return new BridgeContext(userAgent,
rendererFactory.getRenderContext(),
loader);
}
/**
* Computes the transform used for rendering.
*/
protected void computeRenderingTransform() {
try {
if (svgDocument != null) {
SVGSVGElement elt = svgDocument.getRootElement();
Dimension d = getSize();
setRenderingTransform(ViewBox.getViewTransform
(fragmentIdentifier, elt, d.width, d.height));
initialTransform = renderingTransform;
}
} catch (BridgeException e) {
userAgent.displayError(e);
}
}
/**
* Updates the value of the transform used for rendering.
*/
protected void updateRenderingTransform() {
if (initialTransform == renderingTransform) {
computeRenderingTransform();
}
}
/**
* Handles an exception.
*/
protected void handleException(Exception e) {
userAgent.displayError(e);
}
/**
* Adds a SVGDocumentLoaderListener to this component.
*/
public void addSVGDocumentLoaderListener(SVGDocumentLoaderListener l) {
svgDocumentLoaderListeners.add(l);
}
/**
* Removes a SVGDocumentLoaderListener from this component.
*/
public void removeSVGDocumentLoaderListener(SVGDocumentLoaderListener l) {
svgDocumentLoaderListeners.remove(l);
}
/**
* Adds a GVTTreeBuilderListener to this component.
*/
public void addGVTTreeBuilderListener(GVTTreeBuilderListener l) {
gvtTreeBuilderListeners.add(l);
}
/**
* Removes a GVTTreeBuilderListener from this component.
*/
public void removeGVTTreeBuilderListener(GVTTreeBuilderListener l) {
gvtTreeBuilderListeners.remove(l);
}
/**
* Creates an instance of Listener.
*/
protected Listener createListener() {
return new SVGListener();
}
/**
* To hide the listener methods.
*/
protected class SVGListener
extends Listener
implements SVGDocumentLoaderListener,
GVTTreeBuilderListener {
/**
* Creates a new SVGListener.
*/
protected SVGListener() {
}
// SVGDocumentLoaderListener ///////////////////////////////////////////
/**
* Called when the loading of a document was started.
*/
public void documentLoadingStarted(SVGDocumentLoaderEvent e) {
}
/**
* Called when the loading of a document was completed.
*/
public void documentLoadingCompleted(SVGDocumentLoaderEvent e) {
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
documentLoader = null;
setSVGDocument(e.getSVGDocument());
}
/**
* Called when the loading of a document was cancelled.
*/
public void documentLoadingCancelled(SVGDocumentLoaderEvent e) {
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
documentLoader = null;
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
}
/**
* Called when the loading of a document has failed.
*/
public void documentLoadingFailed(SVGDocumentLoaderEvent e) {
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
documentLoader = null;
userAgent.displayError(((SVGDocumentLoader)e.getSource()).getException());
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
}
// GVTTreeBuilderListener //////////////////////////////////////////////
/**
* Called when a build started.
* The data of the event is initialized to the old document.
*/
public void gvtBuildStarted(GVTTreeBuilderEvent e) {
computeRenderingTransform();
}
/**
* Called when a build was completed.
*/
public void gvtBuildCompleted(GVTTreeBuilderEvent e) {
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
loader = null;
gvtTreeBuilder = null;
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
setGraphicsNode(e.getGVTRoot(), false);
Dimension2D dim = bridgeContext.getDocumentSize();
setPreferredSize(new Dimension((int)dim.getWidth(), (int)dim.getHeight()));
invalidate();
}
/**
* Called when a build was cancelled.
*/
public void gvtBuildCancelled(GVTTreeBuilderEvent e) {
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
loader = null;
gvtTreeBuilder = null;
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
image = null;
repaint();
}
/**
* Called when a build failed.
*/
public void gvtBuildFailed(GVTTreeBuilderEvent e) {
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
loader = null;
gvtTreeBuilder = null;
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
GraphicsNode gn = e.getGVTRoot();
Dimension2D dim = bridgeContext.getDocumentSize();
if (gn == null || dim == null) {
image = null;
repaint();
} else {
setGraphicsNode(gn, false);
setPreferredSize(new Dimension((int)dim.getWidth(),
(int)dim.getHeight()));
invalidate();
}
userAgent.displayError(((GVTTreeBuilder)e.getSource()).getException());
}
// GVTTreeRendererListener /////////////////////////////////////////////
/**
* Called when a rendering was completed.
*/
public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
super.gvtRenderingCompleted(e);
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
if (eventsEnabled) {
Event evt = svgDocument.createEvent("SVGEvents");
evt.initEvent("SVGLoad", false, false);
((EventTarget)(svgDocument.getRootElement())).dispatchEvent(evt);
((EventTarget)svgDocument).addEventListener("DOMAttrModified",
new MutationListener(bridgeContext), false);
}
}
/**
* Called when a rendering was cancelled.
*/
public void gvtRenderingCancelled(GVTTreeRendererEvent e) {
super.gvtRenderingCancelled(e);
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
}
/**
* Called when a rendering failed.
*/
public void gvtRenderingFailed(GVTTreeRendererEvent e) {
super.gvtRenderingFailed(e);
if (nextGVTTreeBuilder != null) {
startGVTTreeBuilder();
return;
}
if (nextDocumentLoader != null) {
startDocumentLoader();
return;
}
}
/**
* To listener to the DOM mutation events.
*/
protected class MutationListener implements EventListener {
BridgeContext bridgeContext;
public MutationListener(BridgeContext bridgeContext) {
this.bridgeContext = bridgeContext;
}
public void handleEvent(Event evt) {
BridgeMutationEvent bme;
Element target = (Element)evt.getTarget();
bme = new BridgeMutationEvent
(target,
bridgeContext,
BridgeMutationEvent.PROPERTY_MUTATION_TYPE);
MutationEvent me = (MutationEvent)evt;
bme.setAttrName(me.getAttrName());
bme.setAttrNewValue(me.getNewValue());
GraphicsNodeBridge bridge;
bridge = (GraphicsNodeBridge)bridgeContext.getBridge(target);
bridge.update(bme);
}
}
}
/**
* Creates a UserAgent.
*/
protected UserAgent createUserAgent() {
return new BridgeUserAgent();
}
/**
* To hide the user-agent methods.
*/
protected class BridgeUserAgent implements UserAgent {
/**
* Creates a new user agent.
*/
protected BridgeUserAgent() {
}
/**
* Returns the default size of the viewport of this user agent (0, 0).
*/
public Dimension2D getViewportSize() {
return getSize();
}
/**
* Returns the <code>EventDispatcher</code> used by the
* <code>UserAgent</code> to dispatch events on GVT.
*/
public EventDispatcher getEventDispatcher() {
return eventDispatcher;
}
/**
* Displays an error message in the User Agent interface.
*/
public void displayError(String message) {
if (svgUserAgent != null) {
svgUserAgent.displayError(message);
}
}
/**
* Displays an error resulting from the specified Exception.
*/
public void displayError(Exception ex) {
if (svgUserAgent != null) {
svgUserAgent.displayError(ex);
}
}
/**
* Displays a message in the User Agent interface.
*/
public void displayMessage(String message) {
if (svgUserAgent != null) {
svgUserAgent.displayMessage(message);
}
}
/**
* Returns the pixel to mm factor.
*/
public float getPixelToMM() {
if (svgUserAgent != null) {
return svgUserAgent.getPixelToMM();
}
return 0.264583333333333333333f; // 96 dpi
}
/**
* Returns the language settings.
*/
public String getLanguages() {
if (svgUserAgent != null) {
return svgUserAgent.getLanguages();
}
return "en";
}
/**
* Returns the user stylesheet uri.
* @return null if no user style sheet was specified.
*/
public String getUserStyleSheetURI() {
if (svgUserAgent != null) {
return svgUserAgent.getUserStyleSheetURI();
}
return null;
}
/**
* Opens a link.
* @param elt The activated link element.
*/
public void openLink(SVGAElement elt) {
String show = elt.getXlinkShow();
String href = elt.getHref().getBaseVal();
if (show.equals("new")) {
if (svgUserAgent != null) {
URL oldURI = ((SVGOMDocument)svgDocument).getURLObject();
URL newURI = null;
try {
newURI = new URL(oldURI, href);
} catch (MalformedURLException e) {
userAgent.displayError(e);
return;
}
href = newURI.toString();
svgUserAgent.openLink(href);
return;
}
}
// Avoid reloading if possible.
if (svgDocument != null) {
URL oldURI = ((SVGOMDocument)svgDocument).getURLObject();
URL newURI = null;
try {
newURI = new URL(oldURI, href);
} catch (MalformedURLException e) {
userAgent.displayError(e);
return;
}
String s = newURI.getRef();
if (newURI.sameFile(oldURI)) {
if ((fragmentIdentifier == null && s != null) ||
(s == null && fragmentIdentifier != null) ||
(s != null && !s.equals(fragmentIdentifier))) {
fragmentIdentifier = s;
computeRenderingTransform();
}
return;
}
}
JSVGComponent.this.loadSVGDocument(href);
}
/**
* Informs the user agent to change the cursor.
* @param cursor the new cursor
*/
public void setSVGCursor(Cursor cursor) {
JSVGComponent.this.setCursor(cursor);
}
/**
* Returns the class name of the XML parser.
*/
public String getXMLParserClassName() {
if (svgUserAgent != null) {
return svgUserAgent.getXMLParserClassName();
}
return "org.apache.crimson.parser.XMLReaderImpl";
}
/**
* Returns the <code>AffineTransform</code> currently
* applied to the drawing by the UserAgent.
*/
public AffineTransform getTransform() {
return JSVGComponent.this.renderingTransform;
}
/**
* Returns the location on the screen of the
* client area in the UserAgent.
*/
public Point getClientAreaLocationOnScreen() {
return getLocationOnScreen();
}
/**
* Tells whether the given feature is supported by this
* user agent.
*/
public boolean hasFeature(String s) {
return FEATURES.contains(s);
}
/**
* Tells whether the given extension is supported by this
* user agent.
*/
public boolean supportExtension(String s) {
if (svgUserAgent != null) {
return svgUserAgent.supportExtension(s);
}
return false;
}
}
protected final static Set FEATURES = new HashSet();
static {
FEATURES.add(SVGConstants.SVG_ORG_W3C_SVG_FEATURE);
FEATURES.add(SVGConstants.SVG_ORG_W3C_SVG_LANG_FEATURE);
FEATURES.add(SVGConstants.SVG_ORG_W3C_SVG_STATIC_FEATURE);
}
}