blob: 237df74963f0eb1e19f0337f0b31aa54f4045dce [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.gvt;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JComponent;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.event.AWTEventDispatcher;
import org.apache.batik.gvt.renderer.DynamicRendererFactory;
import org.apache.batik.gvt.renderer.ImageRenderer;
import org.apache.batik.gvt.renderer.ImageRendererFactory;
/**
* This class represents a component which can display a GVT tree.
*
* @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
* @version $Id$
*/
public class JGVTComponent extends JComponent {
/**
* The listener.
*/
protected Listener listener;
/**
* The GVT tree renderer.
*/
protected GVTTreeRenderer gvtTreeRenderer;
/**
* The GVT tree root.
*/
protected GraphicsNode gvtRoot;
/**
* The renderer factory.
*/
protected ImageRendererFactory rendererFactory = new DynamicRendererFactory();
/**
* The current renderer.
*/
protected ImageRenderer renderer;
/**
* The GVT tree renderer listeners.
*/
protected List gvtTreeRendererListeners =
Collections.synchronizedList(new LinkedList());
/**
* Whether a render was requested.
*/
protected boolean needRender;
/**
* Whether to allow progressive paint.
*/
protected boolean progressivePaint;
/**
* The progressive paint thread.
*/
protected Thread progressivePaintThread;
/**
* The image to paint.
*/
protected BufferedImage image;
/**
* The initial rendering transform.
*/
protected AffineTransform initialTransform;
/**
* The transform used for rendering.
*/
protected AffineTransform renderingTransform;
/**
* The transform used for painting.
*/
protected AffineTransform paintingTransform;
/**
* The interactor list.
*/
protected List interactors = new LinkedList();
/**
* The current interactor.
*/
protected Interactor interactor;
/**
* The overlays.
*/
protected List overlays = new LinkedList();
/**
* The event dispatcher.
*/
protected AWTEventDispatcher eventDispatcher;
/**
* The text selection manager.
*/
protected TextSelectionManager textSelectionManager;
/**
* Whether the double buffering is enabled.
*/
protected boolean doubleBufferedRendering;
/**
* Whether the GVT tree should be reactive to mouse and key events.
*/
protected boolean eventsEnabled;
/**
* Whether the text should be selectable if eventEnabled is false,
* this flag is ignored.
*/
protected boolean selectableText;
/**
* Whether to suspend interactions.
*/
protected boolean suspendInteractions;
/**
* Whether to inconditionally disable interactions.
*/
protected boolean disableInteractions;
/**
* Creates a new JGVTComponent.
*/
public JGVTComponent() {
this(false, false);
}
/**
* Creates a new JGVTComponent.
* @param eventEnabled Whether the GVT tree should be reactive
* to mouse and key events.
* @param selectableText Whether the text should be selectable.
* if eventEnabled is false, this flag is ignored.
*/
public JGVTComponent(boolean eventsEnabled, boolean selectableText) {
setBackground(Color.white);
this.eventsEnabled = eventsEnabled;
this.selectableText = selectableText;
listener = createListener();
addKeyListener(listener);
addMouseListener(listener);
addMouseMotionListener(listener);
addGVTTreeRendererListener(listener);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
updateRenderingTransform();
scheduleGVTRendering();
}
});
}
/**
* Returns the interactor list.
*/
public List getInteractors() {
return interactors;
}
/**
* Returns the overlay list.
*/
public List getOverlays() {
return overlays;
}
/**
* Returns the off-screen image, if any.
*/
public BufferedImage getOffScreen() {
return image;
}
/**
* Stops the processing of the current tree.
*/
public void stopProcessing() {
if (gvtTreeRenderer != null) {
needRender = false;
gvtTreeRenderer.interrupt();
interruptProgressivePaintThread();
}
}
/**
* Returns the root of the GVT tree displayed by this component, if any.
*/
public GraphicsNode getGraphicsNode() {
return gvtRoot;
}
/**
* Sets the GVT tree to display.
*/
public void setGraphicsNode(GraphicsNode gn) {
setGraphicsNode(gn, true);
}
/**
* Sets the GVT tree to display.
*/
protected void setGraphicsNode(GraphicsNode gn, boolean createDispatcher) {
gvtRoot = gn;
if (gn != null && createDispatcher) {
initializeEventHandling();
}
if (eventDispatcher != null) {
eventDispatcher.setRootNode(gn);
}
computeRenderingTransform();
}
/**
* Initializes the event handling classes.
*/
protected void initializeEventHandling() {
if (eventsEnabled) {
eventDispatcher =
new AWTEventDispatcher(rendererFactory.getRenderContext());
if (selectableText) {
textSelectionManager =
new TextSelectionManager(this, rendererFactory.getRenderContext(),
eventDispatcher);
}
}
}
/**
* Whether to enable the progressive paint.
*/
public void setProgressivePaint(boolean b) {
if (progressivePaint != b) {
progressivePaint = b;
interruptProgressivePaintThread();
}
}
/**
* Tells whether the progressive paint is enabled.
*/
public boolean getProgressivePaint() {
return progressivePaint;
}
/**
* Repaints immediately the component.
*/
public void immediateRepaint() {
if (java.awt.EventQueue.isDispatchThread()) {
Dimension dim = getSize();
paintImmediately(0, 0, dim.width, dim.height);
} else {
try {
java.awt.EventQueue.invokeAndWait(new Runnable() {
public void run() {
Dimension dim = getSize();
paintImmediately(0, 0, dim.width, dim.height);
}
});
} catch (Exception e) {
}
}
}
/**
* Paints this component.
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
Dimension d = getSize();
g2d.setComposite(AlphaComposite.SrcOver);
g2d.setPaint(getBackground());
g2d.fillRect(0, 0, d.width, d.height);
if (image != null) {
if (paintingTransform != null) {
g2d.transform(paintingTransform);
}
g2d.drawRenderedImage(image, null);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
Iterator it = overlays.iterator();
while (it.hasNext()) {
((Overlay)it.next()).paint(g);
}
}
}
/**
* Sets the painting transform. A null transform is the same as
* an identity transform.
* The next repaint will use the given transform.
*/
public void setPaintingTransform(AffineTransform at) {
paintingTransform = at;
immediateRepaint();
}
/**
* Returns the current painting transform.
*/
public AffineTransform getPaintingTransform() {
return paintingTransform;
}
/**
* Sets the rendering transform.
* Calling this method causes a rendering to be performed.
*/
public void setRenderingTransform(AffineTransform at) {
renderingTransform = at;
suspendInteractions = true;
if (eventDispatcher != null) {
try {
eventDispatcher.setBaseTransform(renderingTransform.createInverse());
} catch (NoninvertibleTransformException e) {
handleException(e);
}
}
scheduleGVTRendering();
}
/**
* Returns the current rendering transform.
*/
public AffineTransform getRenderingTransform() {
return renderingTransform;
}
/**
* Sets whether this component should use double buffering to render
* SVG documents. The change will be effective during the next
* rendering.
*/
public void setDoubleBufferedRendering(boolean b) {
doubleBufferedRendering = b;
}
/**
* Tells whether this component use double buffering to render
* SVG documents.
*/
public boolean getDoubleBufferedRendering() {
return doubleBufferedRendering;
}
/**
* Adds a GVTTreeRendererListener to this component.
*/
public void addGVTTreeRendererListener(GVTTreeRendererListener l) {
gvtTreeRendererListeners.add(l);
}
/**
* Removes a GVTTreeRendererListener from this component.
*/
public void removeGVTTreeRendererListener(GVTTreeRendererListener l) {
gvtTreeRendererListeners.remove(l);
}
/**
* Renders the GVT tree. Used for the initial rendering and resize only.
*/
protected void renderGVTTree() {
Dimension d = getSize();
if (gvtRoot == null || d.width <= 0 || d.height <= 0) {
return;
}
// Renderer setup.
if (renderer == null || renderer.getTree() != gvtRoot) {
renderer = rendererFactory.createImageRenderer();
renderer.setTree(gvtRoot);
}
// Area of interest computation.
AffineTransform inv;
try {
inv = renderingTransform.createInverse();
} catch (NoninvertibleTransformException e) {
throw new InternalError(e.getMessage());
}
Shape s = inv.createTransformedShape(new Rectangle(0, 0, d.width, d.height));
// Rendering thread setup.
gvtTreeRenderer = new GVTTreeRenderer(renderer, renderingTransform,
doubleBufferedRendering,
s, d.width, d.height);
gvtTreeRenderer.setPriority(Thread.MIN_PRIORITY);
Iterator it = gvtTreeRendererListeners.iterator();
while (it.hasNext()) {
gvtTreeRenderer.addGVTTreeRendererListener
((GVTTreeRendererListener)it.next());
}
// Disable the dispatch during the rendering
// to avoid concurrent access to the GVT tree.
if (eventDispatcher != null) {
eventDispatcher.setRootNode(null);
}
gvtTreeRenderer.start();
}
/**
* Computes the initial value of the transform used for rendering.
*/
protected void computeRenderingTransform() {
initialTransform = new AffineTransform();
setRenderingTransform(initialTransform);
}
/**
* Updates the value of the transform used for rendering.
*/
protected void updateRenderingTransform() {
// Do nothing.
}
/**
* Handles an exception.
*/
protected void handleException(Exception e) {
// Do nothing.
}
/**
* Releases the references to the rendering resources,
*/
protected void releaseRenderingReferences() {
eventDispatcher = null;
if (textSelectionManager != null) {
overlays.remove(textSelectionManager.getSelectionOverlay());
textSelectionManager = null;
}
renderer = null;
gvtRoot = null;
}
/**
* Schedules a new GVT rendering.
*/
protected void scheduleGVTRendering() {
if (gvtTreeRenderer != null) {
needRender = true;
gvtTreeRenderer.interrupt();
} else {
renderGVTTree();
}
}
private void interruptProgressivePaintThread() {
if (progressivePaintThread != null) {
progressivePaintThread.interrupt();
progressivePaintThread = null;
}
}
/**
* Creates an instance of Listener.
*/
protected Listener createListener() {
return new Listener();
}
/**
* To hide the listener methods.
*/
protected class Listener
implements GVTTreeRendererListener,
KeyListener,
MouseListener,
MouseMotionListener {
/**
* Creates a new Listener.
*/
protected Listener() {
}
// GVTTreeRendererListener /////////////////////////////////////////////
/**
* Called when a rendering is in its preparing phase.
*/
public void gvtRenderingPrepare(GVTTreeRendererEvent e) {
suspendInteractions = true;
if (!progressivePaint && !doubleBufferedRendering) {
image = null;
immediateRepaint();
}
}
/**
* Called when a rendering started.
*/
public void gvtRenderingStarted(GVTTreeRendererEvent e) {
paintingTransform = null;
if (progressivePaint && !doubleBufferedRendering) {
image = e.getImage();
progressivePaintThread = new Thread() {
public void run() {
final Thread thisThread = this;
try {
while (!isInterrupted()) {
java.awt.EventQueue.invokeAndWait(new Runnable() {
public void run() {
if (progressivePaintThread == thisThread) {
Dimension dim = getSize();
paintImmediately(0, 0,
dim.width, dim.height);
}
}
});
sleep(200);
}
} catch (Exception e) {
}
}
};
progressivePaintThread.setPriority(Thread.MIN_PRIORITY + 1);
progressivePaintThread.start();
}
if (!doubleBufferedRendering) {
suspendInteractions = false;
}
}
/**
* Called when a rendering was completed.
*/
public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
interruptProgressivePaintThread();
if (doubleBufferedRendering) {
suspendInteractions = false;
}
gvtTreeRenderer = null;
if (needRender) {
renderGVTTree();
needRender = false;
} else {
image = e.getImage();
immediateRepaint();
}
if (eventDispatcher != null) {
eventDispatcher.setRootNode(gvtRoot);
}
}
/**
* Called when a rendering was cancelled.
*/
public void gvtRenderingCancelled(GVTTreeRendererEvent e) {
renderingStopped();
}
/**
* Called when a rendering failed.
*/
public void gvtRenderingFailed(GVTTreeRendererEvent e) {
renderingStopped();
}
/**
* The actual implementation of gvtRenderingCancelled() and
* gvtRenderingFailed().
*/
private void renderingStopped() {
interruptProgressivePaintThread();
if (doubleBufferedRendering) {
suspendInteractions = false;
}
gvtTreeRenderer = null;
if (needRender) {
renderGVTTree();
needRender = false;
} else {
immediateRepaint();
}
}
// KeyListener //////////////////////////////////////////////////////////
/**
* Invoked when a key has been typed.
* This event occurs when a key press is followed by a key release.
*/
public void keyTyped(KeyEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.keyTyped(e);
deselectInteractor();
}
}
/**
* Invoked when a key has been pressed.
*/
public void keyPressed(KeyEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.keyPressed(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.keyReleased(e);
}
}
/**
* Invoked when a key has been released.
*/
public void keyReleased(KeyEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.keyReleased(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.keyReleased(e);
}
}
// MouseListener ///////////////////////////////////////////////////////
/**
* Invoked when the mouse has been clicked on a component.
*/
public void mouseClicked(MouseEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.mouseClicked(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.mouseClicked(e);
}
}
/**
* Invoked when a mouse button has been pressed on a component.
*/
public void mousePressed(MouseEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.mousePressed(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.mousePressed(e);
}
}
/**
* Invoked when a mouse button has been released on a component.
*/
public void mouseReleased(MouseEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.mouseReleased(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.mouseReleased(e);
}
}
/**
* Invoked when the mouse enters a component.
*/
public void mouseEntered(MouseEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.mouseEntered(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.mouseEntered(e);
}
}
/**
* Invoked when the mouse exits a component.
*/
public void mouseExited(MouseEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.mouseExited(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.mouseExited(e);
}
}
// MouseMotionListener /////////////////////////////////////////////////
/**
* Invoked when a mouse button is pressed on a component and then
* dragged. Mouse drag events will continue to be delivered to
* the component where the first originated until the mouse button is
* released (regardless of whether the mouse position is within the
* bounds of the component).
*/
public void mouseDragged(MouseEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.mouseDragged(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.mouseDragged(e);
}
}
/**
* Invoked when the mouse button has been moved on a component
* (with no buttons no down).
*/
public void mouseMoved(MouseEvent e) {
selectInteractor(e);
if (interactor != null) {
interactor.mouseMoved(e);
deselectInteractor();
} else if (eventDispatcher != null) {
eventDispatcher.mouseMoved(e);
}
}
/**
* Selects an interactor, given an input event.
*/
protected void selectInteractor(InputEvent ie) {
if (!disableInteractions &&
!suspendInteractions &&
interactor == null) {
Iterator it = interactors.iterator();
while (it.hasNext()) {
Interactor i = (Interactor)it.next();
if (i.startInteraction(ie)) {
interactor = i;
break;
}
}
}
}
/**
* Deselects an interactor, if the interaction has finished.
*/
protected void deselectInteractor() {
if (interactor.endInteraction()) {
interactor = null;
}
}
}
}