/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

package org.apache.batik.swing.gvt;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
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.ConcreteImageRendererFactory;
import org.apache.batik.gvt.renderer.ImageRenderer;
import org.apache.batik.gvt.renderer.ImageRendererFactory;
import org.apache.batik.gvt.text.Mark;

/**
 * This class represents a component which can display a GVT tree.
 *
 * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
 * @version $Id: JGVTComponent.java,v 1.43 2003/11/21 13:36:25 deweese Exp $
 */
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 ConcreteImageRendererFactory();

    /**
     * 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 = new AffineTransform();

    /**
     * The transform used for rendering.
     */
    protected AffineTransform renderingTransform = new AffineTransform();

    /**
     * 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 JGVTComponentListener list.
     */
    protected List jgvtListeners = null;

    /**
     * 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 unconditionally 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) {
                    if (updateRenderingTransform())
                        scheduleGVTRendering();
                }
            });

    }

    public void setDisableInteractions(boolean b) {
        disableInteractions = b;
    }

    public boolean getDisableInteractions() {
        return disableInteractions;
    }

    /**
     * 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;
    }


    public void addJGVTComponentListener(JGVTComponentListener listener) {
        if (jgvtListeners == null)
            jgvtListeners = new LinkedList();
        jgvtListeners.add(listener);
    }

    public void removeJGVTComponentListener(JGVTComponentListener listener) {
        if (jgvtListeners == null) return;
        jgvtListeners.remove(listener);
    }

    /**
     * Resets the rendering transform to its initial value.
     */
    public void resetRenderingTransform() {
        setRenderingTransform(initialTransform);
    }

    /**
     * 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);
        initialTransform = new AffineTransform();
        updateRenderingTransform();
        setRenderingTransform(initialTransform, 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);
        }
    }

    /**
     * Initializes the event handling classes.
     */
    protected void initializeEventHandling() {
        if (eventsEnabled) {
            eventDispatcher = new AWTEventDispatcher();
            if (selectableText) {
                textSelectionManager =
                    new TextSelectionManager(this, eventDispatcher);
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // Selection methods
    ////////////////////////////////////////////////////////////////////////

    /**
     * Sets the color of the selection overlay to the specified color.
     *
     * @param color the new color of the selection overlay
     */
    public void setSelectionOverlayColor(Color color) {
        if (textSelectionManager != null) {
            textSelectionManager.setSelectionOverlayColor(color);
        }
    }

    /**
     * Returns the color of the selection overlay.
     */
    public Color getSelectionOverlayColor() {
        if (textSelectionManager != null) {
            return textSelectionManager.getSelectionOverlayColor();
        } else {
            return null;
        }
    }

    /**
     * Sets the color of the outline of the selection overlay to the specified
     * color.
     *
     * @param color the new color of the outline of the selection overlay
     */
    public void setSelectionOverlayStrokeColor(Color color) {
        if (textSelectionManager != null) {
            textSelectionManager.setSelectionOverlayStrokeColor(color);
        }
    }

    /**
     * Returns the color of the outline of the selection overlay.
     */
    public Color getSelectionOverlayStrokeColor() {
        if (textSelectionManager != null) {
            return textSelectionManager.getSelectionOverlayStrokeColor();
        } else {
            return null;
        }
    }

    /**
     * Sets whether or not the selection overlay will be painted in XOR mode,
     * depending on the specified parameter.
     *
     * @param state true implies the selection overlay will be in XOR mode
     */
    public void setSelectionOverlayXORMode(boolean state) {
        if (textSelectionManager != null) {
            textSelectionManager.setSelectionOverlayXORMode(state);
        }
    }

    /**
     * Returns true if the selection overlay is painted in XOR mode, false
     * otherwise.
     */
    public boolean isSelectionOverlayXORMode() {
        if (textSelectionManager != null) {
            return textSelectionManager.isSelectionOverlayXORMode();
        } else {
            return false;
        }
    }

    /**
     * Sets the selection to the specified start and end mark.
     *
     * @param start the mark used to define where the selection starts
     * @param end the mark used to define where the selection ends
     */
    public void select(Mark start, Mark end) {
        if (textSelectionManager != null) {
            textSelectionManager.setSelection(start, end);
        }
    }

    /**
     * Deselects all.
     */
    public void deselectAll() {
        if (textSelectionManager != null) {
            textSelectionManager.clearSelection();
        }
    }

    ////////////////////////////////////////////////////////////////////////
    // Painting methods
    ////////////////////////////////////////////////////////////////////////

    /**
     * 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;
    }

    public Rectangle getRenderRect() {
        Dimension d = getSize();
        return new Rectangle(0, 0, d.width, d.height);
    }

    /**
     * Repaints immediately the component.
     */
    public void immediateRepaint() {
        if (EventQueue.isDispatchThread()) {
            Rectangle visRect = getRenderRect();
            if (doubleBufferedRendering)
                repaint(visRect.x,     visRect.y, 
                        visRect.width, visRect.height);
            else
                paintImmediately(visRect.x,     visRect.y, 
                                 visRect.width, visRect.height);
        } else {
            try {
                EventQueue.invokeAndWait(new Runnable() {
                        public void run() {
                            Rectangle visRect = getRenderRect();
                            if (doubleBufferedRendering)
                                repaint(visRect.x,     visRect.y, 
                                        visRect.width, visRect.height);
                            else
                                paintImmediately(visRect.x,    visRect.y, 
                                                 visRect.width,visRect.height);
                        }
                    });
            } catch (Exception e) {
            }
        }
    }

    /**
     * Paints this component.
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D)g;

        Rectangle visRect = getRenderRect();
        g2d.setComposite(AlphaComposite.SrcOver);
        g2d.setPaint(getBackground());
        g2d.fillRect(visRect.x,     visRect.y, 
                     visRect.width, visRect.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) {
        setRenderingTransform(at, true);
    }

    public void setRenderingTransform(AffineTransform at, 
                                      boolean performRedraw) {
        renderingTransform = at;
        suspendInteractions = true;
        if (eventDispatcher != null) {
            try {
                eventDispatcher.setBaseTransform
                    (renderingTransform.createInverse());
            } catch (NoninvertibleTransformException e) {
                handleException(e);
            }
        }
        if (jgvtListeners != null) {
            Iterator iter = jgvtListeners.iterator();
            ComponentEvent ce = new ComponentEvent
                (this, JGVTComponentListener.COMPONENT_TRANSFORM_CHANGED);
            while (iter.hasNext()) {
                JGVTComponentListener l = (JGVTComponentListener)iter.next();
                l.componentTransformChanged(ce);
            }
        }

        if (performRedraw)
            scheduleGVTRendering();
    }

    /**
     * Returns the initial transform.
     */
    public AffineTransform getInitialTransform() {
        return initialTransform;
    }

    /**
     * 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);
    }

    /**
     * Flush any cached image data (preliminary interface,
     * may be removed or modified in the future).
     */
    public void flush() {
        renderer.flush();
    }

    /**
     * Flush a rectangle of cached image data (preliminary interface,
     * may be removed or modified in the future).
     */
    public void flush(Rectangle r) {
        renderer.flush(r);
    }

    /**
     * Creates a new renderer.
     */
    protected ImageRenderer createImageRenderer() {
        return rendererFactory.createStaticImageRenderer();
    }

    /**
     * Renders the GVT tree.
     */
    protected void renderGVTTree() {
        Rectangle visRect = getRenderRect();
        if (gvtRoot == null || visRect.width <= 0 || visRect.height <= 0) {
            return;
        }

        // Renderer setup.
        if (renderer == null || renderer.getTree() != gvtRoot) {
            renderer = 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(visRect);

        // Rendering thread setup.
        gvtTreeRenderer = new GVTTreeRenderer(renderer, renderingTransform,
                                              doubleBufferedRendering, s,
                                              visRect.width, visRect.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.setEventDispatchEnabled(false);
        }

        gvtTreeRenderer.start();
    }

    /**
     * Computes the initial value of the transform used for rendering.
     * Return true if a repaint is required, otherwise false.
     */
    protected boolean computeRenderingTransform() {
        initialTransform = new AffineTransform();
        if (initialTransform != renderingTransform) {
            setRenderingTransform(initialTransform, false);
            return true;
        }
        return false;
    }

    /**
     * Updates the value of the transform used for rendering.
     * Return true if a repaint is required, otherwise false.
     */
    protected boolean updateRenderingTransform() {
        // Do nothing.
        return false;
    }

    /**
     * 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;
        image = 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 {
        boolean checkClick = false;
        boolean hadDrag = false;
        int startX, startY;
        long startTime;
        int MAX_DISP = 4*4;
        long CLICK_TIME = 200;

        /**
         * 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()) {
                                EventQueue.invokeLater(new Runnable() {
                                    public void run() {
                                        if (progressivePaintThread ==
                                            thisThread) {
                                            Rectangle vRect = getRenderRect();
                                            repaint(vRect.x,     vRect.y, 
                                                    vRect.width, vRect.height);
                                        }
                                    }
                                });
                                sleep(200);
                            }
                        } catch (InterruptedException ie) {
                        } catch (ThreadDeath td) {
                            throw td;
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    }
                };
                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.setEventDispatchEnabled(true);
            }
        }

        /**
         * 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();
            }

            if (eventDispatcher != null) {
                eventDispatcher.setEventDispatchEnabled(true);
            }
        }

        // 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();
            } else if (eventDispatcher != null) {
                dispatchKeyTyped(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchKeyTyped(KeyEvent e) {
            eventDispatcher.keyTyped(e);
        }

        /**
         * 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) {
                dispatchKeyPressed(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchKeyPressed(KeyEvent e) {
            eventDispatcher.keyPressed(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) {
                dispatchKeyReleased(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchKeyReleased(KeyEvent e) {
            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) {
                dispatchMouseClicked(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchMouseClicked(MouseEvent e) {
            eventDispatcher.mouseClicked(e);
        }

        /**
         * Invoked when a mouse button has been pressed on a component.
         */
        public void mousePressed(MouseEvent e) {
            startX = e.getX();
            startY = e.getY();
            startTime = System.currentTimeMillis();
            checkClick = true;

            selectInteractor(e);
            if (interactor != null) {
                interactor.mousePressed(e);
                deselectInteractor();
            } else if (eventDispatcher != null) {
                dispatchMousePressed(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchMousePressed(MouseEvent e) {
            eventDispatcher.mousePressed(e);
        }

        /**
         * Invoked when a mouse button has been released on a component.
         */
        public void mouseReleased(java.awt.event.MouseEvent e) {
            if ((checkClick) && hadDrag) {
                int dx = startX-e.getX();
                int dy = startY-e.getY();
                long cTime = System.currentTimeMillis();
                if ((dx*dx+dy*dy < MAX_DISP) &&
                    (cTime-startTime) < CLICK_TIME) {
                    // our drag was short! dispatch a CLICK event.
                    //
                    MouseEvent click = new MouseEvent
                        (e.getComponent(),
                         MouseEvent.MOUSE_CLICKED,
                         e.getWhen(),
                         e.getModifiers(),		// modifiers
                         e.getX(),
                         e.getY(),
                         e.getClickCount(),
                         e.isPopupTrigger());
							
                    mouseClicked(click);
                }
            }
            checkClick = false;
            hadDrag = false;

            selectInteractor(e);
            if (interactor != null) {
                interactor.mouseReleased(e);
                deselectInteractor();
            } else if (eventDispatcher != null) {
                dispatchMouseReleased(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchMouseReleased(MouseEvent e) {
            eventDispatcher.mouseReleased(e);
        }

        /**
         * Invoked when the mouse enters a component.
         */
        public void mouseEntered(MouseEvent e) {
            // requestFocus();  // This would grab focus every time mouse enters!
            selectInteractor(e);
            if (interactor != null) {
                interactor.mouseEntered(e);
                deselectInteractor();
            } else if (eventDispatcher != null) {
                dispatchMouseEntered(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchMouseEntered(MouseEvent e) {
            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) {
                dispatchMouseExited(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchMouseExited(MouseEvent e) {
            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) {
            hadDrag = true;
            int dx = startX-e.getX();
            int dy = startY-e.getY();
            if (dx*dx+dy*dy > MAX_DISP)
                checkClick = false;

            selectInteractor(e);
            if (interactor != null) {
                interactor.mouseDragged(e);
                deselectInteractor();
            } else if (eventDispatcher != null) {
                dispatchMouseDragged(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchMouseDragged(MouseEvent e) {
            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) {
                dispatchMouseMoved(e);
            }
        }

        /**
         * Dispatches the event to the GVT tree.
         */
        protected void dispatchMouseMoved(MouseEvent e) {
            eventDispatcher.mouseMoved(e);
        }

        /**
         * Selects an interactor, given an input event.
         */
        protected void selectInteractor(InputEvent ie) {
            if (!disableInteractions &&
                !suspendInteractions &&
                interactor == null &&
                gvtRoot != 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;
            }
        }
    }
}
