| /************************************************************** |
| * |
| * 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 javax.swing.event.TreeModelEvent; |
| import javax.swing.event.TreeModelListener; |
| import javax.swing.tree.TreePath; |
| |
| |
| import java.util.Vector; |
| import java.util.HashMap; |
| import java.util.Enumeration; |
| |
| import com.sun.star.accessibility.*; |
| |
| import com.sun.star.uno.UnoRuntime; |
| import com.sun.star.uno.XInterface; |
| import com.sun.star.uno.Any; |
| import com.sun.star.lang.EventObject; |
| import com.sun.star.lang.XServiceInfo; |
| import com.sun.star.lang.XServiceName; |
| |
| public class AccessibilityTreeModel |
| extends AccessibilityTreeModelBase |
| { |
| public boolean mbVerbose = false; |
| |
| public AccessibilityTreeModel (AccessibleTreeNode aRoot) |
| { |
| // create default node (unless we have a 'proper' node) |
| if( ! (aRoot instanceof AccessibleTreeNode) ) |
| aRoot = new StringNode ("Root", null); |
| setRoot (aRoot); |
| |
| maNodeMap = new NodeMap(); |
| |
| maEventListener = new EventListener (this); |
| mxListener = new QueuedListener (maEventListener); |
| } |
| |
| public void clear () |
| { |
| maNodeMap.Clear(); |
| } |
| |
| /** Lock the tree. While the tree is locked, events from the outside are |
| not processed. Lock the tree when you change its internal structure. |
| */ |
| public void lock () |
| { |
| mnLockCount += 1; |
| } |
| |
| /** Unlock the tree. After unlocking the tree as many times as locking |
| it, a treeStructureChange event is sent to the event listeners. |
| @param aNodeHint |
| If not null and treeStructureChange events are thrown then this |
| node is used as root of the modified subtree. |
| */ |
| public void unlock (AccessibleTreeNode aNodeHint) |
| { |
| mnLockCount -= 1; |
| if (mnLockCount == 0) |
| fireTreeStructureChanged ( |
| new TreeModelEvent (this, |
| new TreePath (aNodeHint.createPath()))); |
| } |
| |
| |
| |
| |
| /** Inform all listeners (especially the renderer) of a change of the |
| tree's structure. |
| @param aNode This node specifies the sub tree in which all changes |
| take place. |
| */ |
| public void FireTreeStructureChanged (AccessibleTreeNode aNode) |
| { |
| } |
| |
| |
| |
| |
| |
| public synchronized void setRoot (AccessibleTreeNode aRoot) |
| { |
| if (getRoot() == null) |
| super.setRoot (aRoot); |
| else |
| { |
| lock (); |
| maNodeMap.ForEach (new NodeMapCallback () { |
| public void Apply (AccTreeNode aNode) |
| { |
| if (maCanvas != null) |
| maCanvas.removeNode (aNode); |
| removeAccListener ((AccTreeNode)aNode); |
| } |
| }); |
| maNodeMap.Clear (); |
| |
| setRoot (aRoot); |
| unlock (aRoot); |
| } |
| } |
| |
| |
| // |
| // child management: |
| // |
| |
| |
| |
| /** Delegate the request to the parent and then register listeners at |
| the child and add the child to the canvas. |
| */ |
| public Object getChild (Object aParent, int nIndex) |
| { |
| AccessibleTreeNode aChild = (AccessibleTreeNode)super.getChild (aParent, nIndex); |
| |
| if (aChild == null) |
| System.out.println ("getChild: child not found"); |
| else |
| // Keep translation table up-to-date. |
| addNode (aChild); |
| |
| return aChild; |
| } |
| |
| public Object getChildNoCreate (Object aParent, int nIndex) |
| { |
| AccessibleTreeNode aChild = (AccessibleTreeNode)super.getChildNoCreate (aParent, nIndex); |
| |
| return aChild; |
| } |
| |
| |
| |
| |
| /** Remove a node (and all children) from the tree model. |
| */ |
| protected boolean removeChild (AccessibleTreeNode aNode) |
| { |
| try |
| { |
| if( aNode == null ) |
| { |
| System.out.println ("can't remove null node"); |
| return false; |
| } |
| else |
| { |
| // depth-first removal of children |
| while (aNode.getChildCount() > 0) |
| if ( ! removeChild (aNode.getChildNoCreate (0))) |
| break; |
| |
| // Remove node from its parent. |
| AccessibleTreeNode aParent = aNode.getParent(); |
| if (aParent != null) |
| { |
| int nIndex = aParent.indexOf(aNode); |
| aParent.removeChild (nIndex); |
| } |
| |
| maNodeMap.RemoveNode (aNode); |
| } |
| } |
| catch (Exception e) |
| { |
| System.out.println ("caught exception while removing child " |
| + aNode + " : " + e); |
| e.printStackTrace (); |
| return false; |
| } |
| return true; |
| } |
| |
| public void removeNode (XAccessibleContext xNode) |
| { |
| if (xNode != null) |
| { |
| AccessibleTreeNode aNode = maNodeMap.GetNode (xNode); |
| AccessibleTreeNode aRootNode = (AccessibleTreeNode)getRoot(); |
| TreeModelEvent aEvent = createEvent (aRootNode, aNode); |
| removeChild (aNode); |
| if (mbVerbose) |
| System.out.println (aNode); |
| fireTreeNodesRemoved (aEvent); |
| maCanvas.repaint (); |
| } |
| } |
| |
| |
| /** Add add a new child to a parent. |
| @return |
| Returns the new or existing representation of the specified |
| accessible object. |
| */ |
| protected AccessibleTreeNode addChild (AccTreeNode aParentNode, XAccessible xNewChild) |
| { |
| AccessibleTreeNode aChildNode = null; |
| try |
| { |
| boolean bRet = false; |
| |
| // First make sure that the accessible object does not already have |
| // a representation. |
| aChildNode = maNodeMap.GetNode(xNewChild); |
| if (aChildNode == null) |
| aChildNode = aParentNode.addAccessibleChild (xNewChild); |
| else |
| System.out.println ("node already present"); |
| } |
| catch (Exception e) |
| { |
| System.out.println ("caught exception while adding child " |
| + xNewChild + " to parent " + aParentNode + ": " + e); |
| e.printStackTrace (); |
| } |
| return aChildNode; |
| } |
| |
| public void addChild (XAccessibleContext xParent, XAccessible xChild) |
| { |
| AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent); |
| if (aParentNode instanceof AccTreeNode) |
| { |
| AccessibleTreeNode aChild = addChild ((AccTreeNode)aParentNode, xChild); |
| if (addNode (aChild)) |
| { |
| if (maCanvas != null) |
| maCanvas.updateNode ((AccTreeNode)aParentNode); |
| |
| // A call to fireTreeNodesInserted for xNew |
| // should be sufficient but at least the |
| // StringNode object that contains the number of |
| // children also changes and we do not know its |
| // index relative to its parent. Therefore the |
| // more expensive fireTreeStructureChanged is |
| // necessary. |
| fireTreeNodesInserted (createEvent (xParent, xChild)); |
| updateNode (xParent, AccessibleTreeHandler.class); |
| } |
| maCanvas.repaint (); |
| } |
| } |
| |
| |
| /** Add the child node to the internal tree structure. |
| @param aNode |
| The node to insert into the internal tree structure. |
| */ |
| protected boolean addNode (AccessibleTreeNode aNode) |
| { |
| boolean bRet = false; |
| try |
| { |
| if ( ! maNodeMap.ValueIsMember (aNode)) |
| { |
| if (aNode instanceof AccTreeNode) |
| { |
| AccTreeNode aChild = (AccTreeNode)aNode; |
| XAccessibleContext xChild = aChild.getContext(); |
| registerAccListener (aChild); |
| if (maCanvas != null) |
| maCanvas.addNode (aChild); |
| maNodeMap.InsertNode (xChild, aChild); |
| } |
| bRet = true; |
| } |
| |
| } |
| catch (Exception e) |
| { |
| System.out.println ("caught exception while adding node " |
| + aNode + ": " + e); |
| e.printStackTrace (); |
| } |
| return bRet; |
| } |
| |
| |
| |
| |
| /** create path to node, suitable for TreeModelEvent constructor |
| * @see javax.swing.event.TreeModelEvent#TreeModelEvent |
| */ |
| protected Object[] createPath (AccessibleTreeNode aNode) |
| { |
| Vector aPath = new Vector(); |
| aNode.createPath (aPath); |
| return aPath.toArray(); |
| } |
| |
| // |
| // listeners (and helper methods) |
| // |
| // We are registered with listeners as soon as objects are in the |
| // tree cache, and we should get removed as soon as they are out. |
| // |
| |
| protected void fireTreeNodesChanged(TreeModelEvent e) |
| { |
| for(int i = 0; i < maTMListeners.size(); i++) |
| { |
| ((TreeModelListener)maTMListeners.get(i)).treeNodesChanged(e); |
| } |
| } |
| |
| protected void fireTreeNodesInserted(final TreeModelEvent e) |
| { |
| for(int i = 0; i < maTMListeners.size(); i++) |
| { |
| ((TreeModelListener)maTMListeners.get(i)).treeNodesInserted(e); |
| } |
| } |
| |
| protected void fireTreeNodesRemoved(final TreeModelEvent e) |
| { |
| for(int i = 0; i < maTMListeners.size(); i++) |
| { |
| ((TreeModelListener)maTMListeners.get(i)).treeNodesRemoved(e); |
| } |
| } |
| |
| protected void fireTreeStructureChanged(final TreeModelEvent e) |
| { |
| for(int i = 0; i < maTMListeners.size(); i++) |
| { |
| ((TreeModelListener)maTMListeners.get(i)).treeStructureChanged(e); |
| } |
| } |
| |
| protected TreeModelEvent createEvent (XAccessibleContext xParent) |
| { |
| AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent); |
| return new TreeModelEvent (this, createPath (aParentNode)); |
| } |
| |
| /** Create a TreeModelEvent object that informs listeners that one child |
| has been removed from or inserted into its parent. |
| */ |
| public TreeModelEvent createEvent (XAccessibleContext xParent, XAccessible xChild) |
| { |
| AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent); |
| return createEvent (aParentNode, xParent); |
| } |
| |
| public TreeModelEvent createEvent (AccessibleTreeNode aParentNode, XAccessibleContext xChild) |
| { |
| AccessibleTreeNode aChildNode = null; |
| if (xChild != null) |
| aChildNode = maNodeMap.GetNode (xChild); |
| return createEvent (aParentNode, aChildNode); |
| } |
| |
| |
| |
| protected TreeModelEvent createEvent ( |
| AccessibleTreeNode aParentNode, |
| AccessibleTreeNode aChildNode) |
| { |
| Object[] aPathToParent = createPath (aParentNode); |
| |
| int nIndexInParent = -1; |
| if (aChildNode != null) |
| nIndexInParent = aParentNode.indexOf (aChildNode); |
| if (mbVerbose) |
| System.out.println (aChildNode + " " + nIndexInParent); |
| |
| if (nIndexInParent == -1) |
| // This event may be passed only to treeStructureChanged of the listeners. |
| return new TreeModelEvent (this, |
| aPathToParent); |
| else |
| // General purpose event for removing or inserting known nodes. |
| return new TreeModelEvent (this, |
| aPathToParent, |
| new int[] {nIndexInParent}, |
| new Object[] {aChildNode} ); |
| } |
| |
| |
| |
| |
| /** Create a TreeModelEvent that indicates changes at those children of |
| the specified node with the specified indices. |
| */ |
| protected TreeModelEvent createChangeEvent (AccTreeNode aNode, Vector aChildIndices) |
| { |
| // Build a list of child objects that are indicated by the given indices. |
| int nCount = aChildIndices.size(); |
| Object aChildObjects[] = new Object[nCount]; |
| int nChildIndices[] = new int[nCount]; |
| for (int i=0; i<nCount; i++) |
| { |
| int nIndex = ((Integer)aChildIndices.elementAt(i)).intValue(); |
| aChildObjects[i] = aNode.getChild (nIndex); |
| nChildIndices[i] = nIndex; |
| } |
| |
| return new TreeModelEvent (this, |
| createPath(aNode), |
| nChildIndices, |
| aChildObjects); |
| } |
| |
| |
| |
| /** |
| * broadcast a tree event in a separate Thread |
| * must override fire method |
| */ |
| class EventRunner implements Runnable |
| { |
| public void run() |
| { |
| for(int i = 0; i < maTMListeners.size(); i++) |
| { |
| fire( (TreeModelListener)maTMListeners.get(i) ); |
| } |
| } |
| |
| protected void fire( TreeModelListener l) { } |
| } |
| |
| |
| |
| protected XAccessibleEventBroadcaster getBroadcaster (Object aObject) |
| { |
| if (aObject instanceof AccTreeNode) |
| return (XAccessibleEventBroadcaster) UnoRuntime.queryInterface ( |
| XAccessibleEventBroadcaster.class, ((AccTreeNode)aObject).getContext()); |
| else |
| return null; |
| } |
| |
| protected void registerAccListener( Object aObject ) |
| { |
| // register this as listener for XAccessibleEventBroadcaster |
| // implementations |
| XAccessibleEventBroadcaster xBroadcaster = getBroadcaster( aObject ); |
| if (xBroadcaster != null) |
| { |
| xBroadcaster.addEventListener( mxListener ); |
| } |
| } |
| |
| protected void removeAccListener( Object aObject ) |
| { |
| XAccessibleEventBroadcaster xBroadcaster = getBroadcaster( aObject ); |
| if (xBroadcaster != null) |
| { |
| xBroadcaster.removeEventListener( mxListener ); |
| } |
| } |
| |
| |
| |
| public void setCanvas (Canvas aCanvas) |
| { |
| maCanvas = aCanvas; |
| } |
| |
| public Canvas getCanvas () |
| { |
| return maCanvas; |
| } |
| |
| public void updateNode (XAccessibleContext xSource, java.lang.Class class1) |
| { |
| updateNode (xSource, class1,null); |
| } |
| |
| /** Get a list of children of the node associated with xSource that are |
| affected by the given handlers. Fire events that these children may |
| have changed in the tree view. Update the canvas representation of |
| xSource. |
| */ |
| public AccTreeNode updateNode (XAccessibleContext xSource, |
| java.lang.Class class1, java.lang.Class class2) |
| { |
| AccessibleTreeNode aTreeNode = maNodeMap.GetNode (xSource); |
| AccTreeNode aNode = null; |
| if (mbVerbose) |
| System.out.println ("updating node " + xSource + " " + aTreeNode); |
| if (aTreeNode instanceof AccTreeNode) |
| { |
| aNode = (AccTreeNode) aTreeNode; |
| // Get list of affected children. |
| Vector aChildIndices = (aNode).updateChildren ( |
| class1, class2); |
| // Fire events that these children may have changed. |
| fireTreeNodesChanged ( |
| createChangeEvent (aNode, aChildIndices)); |
| } |
| return aNode; |
| } |
| |
| /** The listener to be registered with the accessible objects. |
| * Could be set to 'this' for same-thread event delivery, or to an |
| * instance of QueuedListener for multi-threaded delivery. May |
| * not be changed, since this would trip the |
| * register/removeAccListener logic. */ |
| private final XAccessibleEventListener mxListener; |
| |
| // Map to translate from accessible object to corresponding tree node. |
| private NodeMap maNodeMap; |
| |
| // If the lock count is higher then zero, then no events are processed. |
| private int mnLockCount; |
| |
| private Canvas maCanvas; |
| |
| private EventListener maEventListener; |
| } |