diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /toolkit/test/accessibility/AccessibilityTreeModel.java | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/test/accessibility/AccessibilityTreeModel.java')
-rw-r--r-- | toolkit/test/accessibility/AccessibilityTreeModel.java | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/toolkit/test/accessibility/AccessibilityTreeModel.java b/toolkit/test/accessibility/AccessibilityTreeModel.java new file mode 100644 index 000000000..b021746bc --- /dev/null +++ b/toolkit/test/accessibility/AccessibilityTreeModel.java @@ -0,0 +1,471 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +import java.util.ArrayList; + +import javax.swing.event.TreeModelEvent; +import javax.swing.tree.TreePath; + +import com.sun.star.accessibility.XAccessible; +import com.sun.star.accessibility.XAccessibleContext; +import com.sun.star.accessibility.XAccessibleEventBroadcaster; +import com.sun.star.accessibility.XAccessibleEventListener; +import com.sun.star.uno.UnoRuntime; + +public class AccessibilityTreeModel + extends AccessibilityTreeModelBase +{ + private boolean mbVerbose = false; + + public AccessibilityTreeModel (AccessibleTreeNode aRoot) + { + // create default node (unless we have a 'proper' node) + setRoot (aRoot); + + maNodeMap = new NodeMap(); + + mxListener = new QueuedListener(new EventListener (this)); + } + + 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()))); + } + + + @Override + public synchronized void setRoot (AccessibleTreeNode aRoot) + { + if (getRoot() == null) + super.setRoot (aRoot); + else + { + lock (); + maNodeMap.ForEach (new NodeMapCallback () { + @Override + public void Apply (AccTreeNode aNode) + { + if (maCanvas != null) + maCanvas.removeNode (aNode); + removeAccListener (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. + */ + @Override + public synchronized 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; + } + + /** Remove a node (and all children) from the tree model. + */ + private 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 a new child to a parent. + @return + Returns the new or existing representation of the specified + accessible object. + */ + private AccessibleTreeNode addChild (AccTreeNode aParentNode, XAccessible xNewChild) + { + AccessibleTreeNode aChildNode = null; + try + { + // 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 (createChildUpdateEvent (xParent)); + 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. + */ + private 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 + */ + private Object[] createPath (AccessibleTreeNode aNode) + { + ArrayList<AccessibleTreeNode> aPath = new ArrayList<AccessibleTreeNode>(); + 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. + + + private void fireTreeNodesChanged(TreeModelEvent e) + { + for(int i = 0; i < maTMListeners.size(); i++) + { + maTMListeners.get(i).treeNodesChanged(e); + } + } + + protected void fireTreeNodesInserted(final TreeModelEvent e) + { + for(int i = 0; i < maTMListeners.size(); i++) + { + maTMListeners.get(i).treeNodesInserted(e); + } + } + + private void fireTreeNodesRemoved(final TreeModelEvent e) + { + for(int i = 0; i < maTMListeners.size(); i++) + { + maTMListeners.get(i).treeNodesRemoved(e); + } + } + + private void fireTreeStructureChanged(final TreeModelEvent e) + { + for(int i = 0; i < maTMListeners.size(); i++) + { + maTMListeners.get(i).treeStructureChanged(e); + } + } + + /** Create a TreeModelEvent object that informs listeners that one child + has been removed from or inserted into its parent. + */ + private TreeModelEvent createChildUpdateEvent (XAccessibleContext xParent) + { + AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent); + return createEvent (aParentNode, xParent); + } + + private 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. + */ + private TreeModelEvent createChangeEvent (AccTreeNode aNode, java.util.List<Integer> 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 = aChildIndices.get(i); + aChildObjects[i] = aNode.getChild (nIndex); + nChildIndices[i] = nIndex; + } + + return new TreeModelEvent (this, + createPath(aNode), + nChildIndices, + aChildObjects); + } + + + + private XAccessibleEventBroadcaster getBroadcaster (Object aObject) + { + if (aObject instanceof AccTreeNode) + return UnoRuntime.queryInterface ( + XAccessibleEventBroadcaster.class, ((AccTreeNode)aObject).getContext()); + else + return null; + } + + private void registerAccListener( Object aObject ) + { + // register this as listener for XAccessibleEventBroadcaster + // implementations + XAccessibleEventBroadcaster xBroadcaster = getBroadcaster( aObject ); + if (xBroadcaster != null) + { + xBroadcaster.addAccessibleEventListener( mxListener ); + } + } + + private void removeAccListener( Object aObject ) + { + XAccessibleEventBroadcaster xBroadcaster = getBroadcaster( aObject ); + if (xBroadcaster != null) + { + xBroadcaster.removeAccessibleEventListener( 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<AccessibleExtendedComponentHandler> 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. + java.util.List<Integer> 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 final NodeMap maNodeMap; + + // If the lock count is higher then zero, then no events are processed. + private int mnLockCount; + + private Canvas maCanvas; +} |