/**************************************************************
 *
 * 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.
 *
 *************************************************************/

import com.sun.star.accessibility.*;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.IndexOutOfBoundsException;
import com.sun.star.uno.UnoRuntime;

import java.util.Vector;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;



/** This is the tree component that is responsible for displaying the
    contents of the tree model on the screen.
*/
public class AccessibilityTree
    implements TreeExpansionListener, TreeWillExpandListener
{
    /** Create a new accessibility tree.  Use the specified message display
        for displaying messages and the specified canvas to draw the
        graphical representations of accessible objects on.
    */
    public AccessibilityTree ()
    {
        maTree = new JTree ();

        AccessibilityTreeModel aModel =
            new AccessibilityTreeModel (
                new StringNode ("Please press Update button", null));
        maTree.setModel (aModel);

        maCellRenderer = new AccessibleTreeCellRenderer();
        //        setCellRenderer (maCellRenderer);

        // allow editing of XAccessibleText interfaces
        //        setEditable (true);
        //        maTreeModel.addTreeModelListener( new TextUpdateListener() );

        maTree.addMouseListener (new MouseListener (this));

        // Listen to expansions and collapses to change the mouse cursor.
        mnExpandLevel = 0;
        maTree.addTreeWillExpandListener (this);
        maTree.addTreeExpansionListener (this);
    }

    public JTree getComponent ()
    {
        return maTree;
    }

    // Change cursor during expansions to show the user that this is a
    // lengthy operation.
    public void treeWillExpand (TreeExpansionEvent e)
    {
        if (mnExpandLevel == 0)
        {
            maTree.setCursor (new Cursor (Cursor.WAIT_CURSOR));
        }
        mnExpandLevel += 1;
    }
    public void treeWillCollapse (TreeExpansionEvent e)
    {
        if (mnExpandLevel == 0)
        {
            maTree.setCursor (new Cursor (Cursor.WAIT_CURSOR));
        }
        mnExpandLevel += 1;
    }
    public void treeExpanded (TreeExpansionEvent e)
    {
        mnExpandLevel -= 1;
        if (mnExpandLevel == 0)
        {
            maTree.setCursor (new Cursor (Cursor.DEFAULT_CURSOR));
        }
    }
    public void treeCollapsed (TreeExpansionEvent e)
    {
        mnExpandLevel -= 1;
        if (mnExpandLevel == 0)
        {
            maTree.setCursor (new Cursor (Cursor.DEFAULT_CURSOR));
        }
    }



    public void SetCanvas (Canvas aCanvas)
    {
        maCanvas = aCanvas;
        ((AccessibilityTreeModel)maTree.getModel()).setCanvas (maCanvas);
    }

    /** Expand the nodes in the subtree rooted in aNode according to the the
        specified expander.  The tree is locked during the expansion.
    */
    protected void expandTree (AccessibleTreeNode aNode, Expander aExpander)
    {
        if (mnExpandLevel == 0)
        {
            maTree.setEnabled (false);
        }
        mnExpandLevel += 1;

        ((AccessibilityTreeModel)maTree.getModel()).lock ();

        try
        {
            expandTree (new TreePath (aNode.createPath()), aExpander);
        }
        catch (Exception e)
        {
            // Ignore
        }

        mnExpandLevel -= 1;
        if (mnExpandLevel == 0)
        {
            maTree.setEnabled (true);
            ((AccessibilityTreeModel)maTree.getModel()).unlock (aNode);
        }
    }

    private TreePath expandTree( TreePath aPath, Expander aExpander )
    {
        // return first expanded object
        TreePath aFirst = null;

        //        System.out.print ("e");

        try
        {
            // get 'our' object
            Object aObj = aPath.getLastPathComponent();

            // expand this object, if the Expander tells us so
            if( aExpander.expand( aObj ) )
            {
                maTree.expandPath (aPath);
                if( aFirst == null )
                    aFirst = aPath;
            }

            // visit all children
            if (aObj instanceof AccessibleTreeNode)
            {
                AccessibleTreeNode aNode = (AccessibleTreeNode)aObj;
                int nLength = aNode.getChildCount();
                for( int i = 0; i < nLength; i++ )
                {
                    TreePath aRet = expandTree(
                        aPath.pathByAddingChild( aNode.getChild( i ) ),
                        aExpander );
                    if( aFirst == null )
                        aFirst = aRet;
                }
            }
        }
        catch (Exception e)
        {
            System.out.println ("caught exception while expanding tree path "
                + aPath + ": " + e);
            e.printStackTrace ();
        }

        return aFirst;
    }


    /** Expand all nodes and their subtrees that represent shapes.  Call
     *  this method from the outside. */
    public void expandShapes ()
    {
        expandShapes ((AccessibleTreeNode)maTree.getModel().getRoot());
    }
    public void expandShapes (AccessibleTreeNode aNode)
    {
        expandTree (aNode, new ShapeExpander());
    }

    /** Expand all nodes */
    public void expandAll ()
    {
        expandAll ((AccessibleTreeNode)maTree.getModel().getRoot());
    }
    public void expandAll (AccessibleTreeNode aNode)
    {
        expandTree (aNode, new AllExpander());
    }



    public void disposing (com.sun.star.lang.EventObject e)
    {
        System.out.println ("disposing " + e);
    }

    /*
    public Dimension getPreferredSize ()
    {
        Dimension aPreferredSize = super.getPreferredSize();
        Dimension aMinimumSize = super.getMinimumSize();
        if (aPreferredSize.width < aMinimumSize.width)
            aPreferredSize.width = aMinimumSize.width;
        return aPreferredSize;
    }
    */

    class MouseListener extends MouseAdapter
    {
        public MouseListener (AccessibilityTree aTree)
        {
            maTree=aTree;
        }
        public void mousePressed(MouseEvent e) { popupTrigger(e); }
        public void mouseClicked(MouseEvent e) { popupTrigger(e); }
        public void mouseEntered(MouseEvent e) { popupTrigger(e); }
        public void mouseExited(MouseEvent e) { popupTrigger(e); }
        public void mouseReleased(MouseEvent e) { popupTrigger(e); }

        public boolean popupTrigger( MouseEvent e )
        {
            boolean bIsPopup = e.isPopupTrigger();
            if( bIsPopup )
            {
                int selRow = maTree.getComponent().getRowForLocation(e.getX(), e.getY());
                if (selRow != -1)
                {
                    TreePath aPath = maTree.getComponent().getPathForLocation(e.getX(), e.getY());

                    // check for actions
                    Object aObject = aPath.getLastPathComponent();
                    JPopupMenu aMenu = new JPopupMenu();
                    if( aObject instanceof AccTreeNode )
                    {
                        AccTreeNode aNode = (AccTreeNode)aObject;

                        Vector aActions = new Vector();
                        aMenu.add (new AccessibilityTree.ShapeExpandAction(maTree, aNode));
                        aMenu.add (new AccessibilityTree.SubtreeExpandAction(maTree, aNode));

                        aNode.getActions(aActions);
                        for( int i = 0; i < aActions.size(); i++ )
                        {
                            aMenu.add( new NodeAction(
                                           aActions.elementAt(i).toString(),
                                           aNode, i ) );
                        }
                    }
                    else if (aObject instanceof AccessibleTreeNode)
                    {
                        AccessibleTreeNode aNode = (AccessibleTreeNode)aObject;
                        String[] aActionNames = aNode.getActions();
                        int nCount=aActionNames.length;
                        if (nCount > 0)
                        {
                            for (int i=0; i<nCount; i++)
                                aMenu.add( new NodeAction(
                                    aActionNames[i],
                                    aNode,
                                    i));
                        }
                        else
                            aMenu = null;
                    }
                    if (aMenu != null)
                        aMenu.show (maTree.getComponent(),
                            e.getX(), e.getY());
                }
            }

            return bIsPopup;
        }

        private AccessibilityTree maTree;
    }

    class NodeAction extends AbstractAction
    {
        private int mnIndex;
        private AccessibleTreeNode maNode;

        public NodeAction( String aName, AccessibleTreeNode aNode, int nIndex )
        {
            super( aName );
            maNode = aNode;
            mnIndex = nIndex;
        }

        public void actionPerformed(ActionEvent e)
        {
            maNode.performAction(mnIndex);
        }
    }

    // This action expands all shapes in the subtree rooted in the specified node.
    class ShapeExpandAction extends AbstractAction
    {
        private AccessibilityTree maTree;
        private AccTreeNode maNode;
        public ShapeExpandAction (AccessibilityTree aTree, AccTreeNode aNode)
        {
            super ("Expand Shapes");
            maTree = aTree;
            maNode = aNode;
        }
        public void actionPerformed (ActionEvent e)
        {
            maTree.expandShapes (maNode);
        }
    }

    // This action expands all nodes in the subtree rooted in the specified node.
    class SubtreeExpandAction extends AbstractAction
    {
        private AccessibilityTree maTree;
        private AccTreeNode maNode;
        public SubtreeExpandAction (AccessibilityTree aTree, AccTreeNode aNode)
        {
            super ("Expand Subtree");
            maTree = aTree;
            maNode = aNode;
        }
        public void actionPerformed (ActionEvent e)
        {
            maTree.expandAll (maNode);
        }
    }

    /** Predicate class to determine whether a node should be expanded
     * For use with expandTree method */
    abstract class Expander
    {
        abstract public boolean expand (Object aObject);
    }

    /** expand all nodes */
    class AllExpander extends Expander
    {
        public boolean expand(Object aObject) { return true; }
    }

    /** expand all nodes with accessibility roles > 100 */
    class ShapeExpander extends Expander
    {
        public boolean expand (Object aObject)
        {
            if (aObject instanceof AccTreeNode)
            {
                AccTreeNode aNode = (AccTreeNode)aObject;
                XAccessibleContext xContext = aNode.getContext();
                if (xContext != null)
                    if (xContext.getAccessibleRole() >= 100)
                        return true;
            }
            return false;
        }
    }



    protected AccessibleTreeCellRenderer
        maCellRenderer;


    private JTree
        maTree;
    private Canvas
        maCanvas;
    private boolean
        mbFirstShapeSeen;
    private int
        mnExpandLevel;
}