import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import java.awt.geom.Rectangle2D;

import com.sun.star.accessibility.XAccessible;
import com.sun.star.accessibility.XAccessibleContext;
import com.sun.star.accessibility.XAccessibleComponent;

/** This canvas displays accessible objects graphically.  Each accessible
    object with graphical representation is represented by an
    CanvasShape object and has to be added by the
    <member>addAccessible</member> member function.

    <p>The canvas listens to selection events of the associated JTree and
    highlights the first selected node of that tree.</p>
*/
class Canvas 
    extends JPanel
    implements MouseListener, MouseMotionListener, TreeSelectionListener//, Scrollable
{
    // This constant can be passed to SetZoomMode to always show the whole screen.
    public static final int WHOLE_SCREEN = -1;

    public Canvas ()
    {
        super (true);
        maObjects = new java.util.HashMap ();
        maNodes = new Vector ();
        maObjectList = new Vector ();
        maContexts = new Vector ();
        addMouseListener (this);
        addMouseMotionListener (this);
        maBoundingBox = new Rectangle (0,0,100,100);
        maTree = null;
        mnHOffset = 0;
        mnVOffset = 0;
        mnScale = 1;
        setShowText(false);
        setShowDescriptions (true);
        setShowNames (true);
        setAntialiasing (true);
        maLastWidgetSize = new Dimension (0,0);
    }

    /** Tell the canvas which tree view to use to highlight accessible
        objects. 
    */
    public void setTree (JTree aTree)
    {
        if (maTree != null)
            maTree.removeTreeSelectionListener (this);
        maTree = aTree;
        if (maTree != null)
            maTree.addTreeSelectionListener (this);
    }




    public void addNode (AccTreeNode aNode)
    {
        if (maNodes.indexOf (aNode) == -1)
        {
            maNodes.add (aNode);
            
            CanvasShape aObject = (CanvasShape) maObjects.get (aNode);
            if (aObject == null)
            {
                aObject = new CanvasShape (aNode);
                // Update bounding box that includes all objects.
                if (maObjects.size() == 0)
                    maBoundingBox = aObject.getBBox();
                else
                    maBoundingBox = maBoundingBox.union (aObject.getBBox());
                
                maObjects.put (aNode, aObject);
                maObjectList.add (aObject);

            }
            repaint ();
        }
    }

    public void removeNode (AccTreeNode aNode)
    {
        int i = maNodes.indexOf (aNode);
        if( i != -1 )
        {
            Object aObject = maObjects.get(aNode);
            maObjectList.remove (aObject);
            maObjects.remove (aObject);
            maNodes.remove (aNode);
            repaint ();
        }
    }

    public void updateNode (AccTreeNode aNode)
    {
        int i = maNodes.indexOf (aNode);
        if (i != -1)
        {
            CanvasShape aObject = (CanvasShape)maObjects.get(aNode);
            if (aObject != null)
                aObject.update();
        }
    }
    
    public void updateNodeGeometry (AccTreeNode aNode)
    {
        CanvasShape aObject = (CanvasShape)maObjects.get(aNode);
        if (aObject != null)
            aObject.updateGeometry();
    }
    
    public void clear ()
    {
        while (maNodes.size() > 0)
            removeNode ((AccTreeNode)maNodes.elementAt(0));

        maNodes.clear();
        maObjects.clear();
        maObjectList.clear();
    }

    public boolean getShowDescriptions ()
    {
        return Options.GetBoolean ("ShowDescriptions");
    }

    public void setShowDescriptions (boolean bNewValue)
    {
        Options.SetBoolean ("ShowDescriptions", bNewValue);
        repaint ();
    }

    public boolean getShowNames ()
    {
        return Options.GetBoolean ("ShowNames");
    }

    public void setShowNames (boolean bNewValue)
    {
        Options.SetBoolean ("ShowNames", bNewValue);
        repaint ();
    }

    public boolean getAntialiasing ()
    {
        return Options.GetBoolean ("Antialiasing");
    }

    public void setAntialiasing (boolean bNewValue)
    {
        Options.SetBoolean ("Antialiasing", bNewValue);
        repaint ();
    }
    
    public boolean getShowText ()
    {
        return Options.GetBoolean ("ShowText");
    }

    public void setShowText (boolean bNewValue)
    {
        Options.SetBoolean ("ShowText", bNewValue);
        repaint ();
    }

    public void setZoomMode (int nZoomMode)
    {
        Options.SetInteger ("ZoomMode", nZoomMode);
        repaint ();
    }

    public int getZoomMode ()
    {
        return Options.GetInteger ("ZoomMode", WHOLE_SCREEN);
    }


    public void paintComponent (Graphics g)
    {
        synchronized (g)
        {
            super.paintComponent (g);

            Graphics2D g2 = (Graphics2D)g;
            if (getAntialiasing())
                g2.setRenderingHint (RenderingHints.KEY_ANTIALIASING, 
                    RenderingHints.VALUE_ANTIALIAS_ON);
            else
                g2.setRenderingHint (RenderingHints.KEY_ANTIALIASING, 
                    RenderingHints.VALUE_ANTIALIAS_OFF);

            setupTransformation ();

            // Draw the screen representation to give a hint of the location of the
            // accessible object on the screen.
            Dimension aScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
            Rectangle2D.Double aScreen = new Rectangle2D.Double (
                mnHOffset,
                mnVOffset,
                mnScale*aScreenSize.getWidth(),
                mnScale*aScreenSize.getHeight());
            // Fill the screen rectangle and draw a frame arround it to increase its visibility.
            g2.setColor (new Color (250,240,230));
            g2.fill (aScreen);
            g2.setColor (Color.BLACK);
            g2.draw (aScreen);
            
            synchronized (maObjectList)
            {
                int nCount = maObjectList.size();
                boolean bShowDescriptions = getShowDescriptions();
                boolean bShowNames = getShowNames();
                boolean bShowText = getShowText();
                for (int i=0; i<nCount; i++)
                {
                    CanvasShape aCanvasShape = (CanvasShape)maObjectList.elementAt(i);
                    aCanvasShape.paint (
                        g2,
                        mnHOffset, mnVOffset, mnScale,
                        bShowDescriptions, bShowNames, bShowText);
                }
            }
            
            // Paint highlighted frame around active object as the last thing.
            if (maActiveObject != null)
                maActiveObject.paint_highlight (
                    g2,
                    mnHOffset, mnVOffset, mnScale);
        }
    }




    /** Set up the transformation so that the graphical display can show a
        centered representation of the whole screen.
    */
    private void setupTransformation ()
    {
        // Turn off scrollbars when showing the whole screen.  Otherwise show them when needed.
        JViewport aViewport = (JViewport)getParent();
        JScrollPane aScrollPane = (JScrollPane)aViewport.getParent();
        int nZoomMode = getZoomMode();
        if (nZoomMode == WHOLE_SCREEN)
        {
            if (aScrollPane.getHorizontalScrollBarPolicy()
                != JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
                aScrollPane.setHorizontalScrollBarPolicy (JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
            if (aScrollPane.getVerticalScrollBarPolicy()
                != JScrollPane.VERTICAL_SCROLLBAR_NEVER)
                aScrollPane.setVerticalScrollBarPolicy (JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        }
        else
        {
            if (aScrollPane.getHorizontalScrollBarPolicy()
                != JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
                aScrollPane.setHorizontalScrollBarPolicy (JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            if (aScrollPane.getVerticalScrollBarPolicy()
                != JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED)
                aScrollPane.setVerticalScrollBarPolicy (JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        }

        Dimension aScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension aWidgetSize = aViewport.getSize();
        {
            if ((aScreenSize.getWidth() > 0) && (aScreenSize.getHeight() > 0))
            {
                if (nZoomMode == WHOLE_SCREEN)
                {
                    // Calculate the scales that would map the screen onto the
                    // widget in both of the coordinate axes and select the
                    // smaller
                    // of the two: it maps the screen onto the widget in both 
                    // axes at the same time.
                    double nHScale = (aWidgetSize.getWidth() - 10) / aScreenSize.getWidth();
                    double nVScale = (aWidgetSize.getHeight() - 10) / aScreenSize.getHeight();
                    if (nHScale < nVScale)
                        mnScale = nHScale;
                    else
                        mnScale = nVScale;
                }
                else
                {
                    mnScale = nZoomMode / 100.0;
                }

                // Calculate offsets that center the scaled screen inside the widget.
                mnHOffset = (aWidgetSize.getWidth() - mnScale*aScreenSize.getWidth()) / 2.0;
                mnVOffset = (aWidgetSize.getHeight() - mnScale*aScreenSize.getHeight()) / 2.0;
                if (mnHOffset < 0)
                    mnHOffset = 0;
                if (mnVOffset < 0)
                    mnVOffset = 0;
            
                setPreferredSize (new Dimension (
                    (int)(2*mnHOffset + mnScale * aScreenSize.getWidth()),
                    (int)(2*mnVOffset + mnScale * aScreenSize.getHeight())));
                revalidate ();
            }
            else
            {
                // In case of a degenerate (not yet initialized?) screen size
                // use some meaningless default values.
                mnScale = 1;
                mnHOffset = 0;
                mnVOffset = 0;
            }
        }
        maLastWidgetSize = aWidgetSize;
    }
    
    

    /**  Call getAccessibleAt to determine accessible object under mouse.
    */
    public void mouseClicked (MouseEvent e)
    {
    }

    public void mousePressed (MouseEvent e)
    {
        CanvasShape aObjectUnderMouse = FindCanvasShapeUnderMouse (e);
        highlightObject (aObjectUnderMouse);
        if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0)
        {
            maTree.expandPath (aObjectUnderMouse.getPath());
        }
    }

    public void mouseReleased (MouseEvent e)
    {
    }

    public void mouseEntered (MouseEvent e)
    {
    }

    public void mouseExited (MouseEvent e)
    {
        // Deselect currently active object.
        if (maActiveObject != null)
        {
            maActiveObject.unhighlight ();
            maActiveObject = null;
            repaint ();
        }
    }

    public void mouseDragged (MouseEvent e)
    {
    }

    public void mouseMoved (MouseEvent e)
    {
        if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0)
            highlightObject (FindCanvasShapeUnderMouse (e));
    }

    protected CanvasShape FindCanvasShapeUnderMouse (MouseEvent e)
    {
        int nObjects = maObjects.size();
        CanvasShape aObjectUnderMouse = null;
        int nCount = maObjectList.size();
        for (int i=nCount-1; i>=0; --i)
        {
            CanvasShape aObject = (CanvasShape)maObjectList.elementAt(i);
            if (aObject != null)
                if (aObject.contains (e.getX(),e.getY()))
                {
                    aObjectUnderMouse = aObject;
                    break;
                }
        }
        return aObjectUnderMouse;
    }

    protected boolean highlightObject (CanvasShape aNewActiveObject)
    {
        if (aNewActiveObject != maActiveObject)
        {
            if (maActiveObject != null)
                maActiveObject.unhighlight();
            
            maActiveObject = aNewActiveObject;
            if (maActiveObject != null)
            {
                if (maTree != null)
                {
                    maTree.scrollPathToVisible (maActiveObject.getPath());
                    maTree.setSelectionPath (maActiveObject.getPath());
                    maTree.repaint ();
                }
                maActiveObject.highlight ();
                repaint ();
            }
            return true;
        }
        else
            return false;
    }
    
    /** Called when the selection of the tree changes.  Highlight the
        corresponding graphical representation of the first selected object.
    */
    public void valueChanged (javax.swing.event.TreeSelectionEvent event)
    {
        TreePath aPath = event.getPath();
        Object aObject = aPath.getLastPathComponent();
        if (aObject instanceof AccTreeNode)
        {
            CanvasShape aCanvasShape = (CanvasShape)maObjects.get ((AccTreeNode)aObject);
            if (highlightObject (aCanvasShape))
                repaint();
        }
    }

    private int 
        mnXAnchor,
        mnYAnchor,
        maResizeFlag;
    private double
        mnHOffset,
        mnVOffset,
        mnScale;
    private CanvasShape 
        maActiveObject;
    private java.util.HashMap
        maObjects;
    private Vector 
        maObjectList,
        maContexts,
        maNodes;
    private Rectangle 
        maBoundingBox;
    private JTree
        maTree;
    // The size of the widget at the last call of setupTransformation()
    private Dimension 
        maLastWidgetSize;
}
