Skip to content

Commit

Permalink
JBR-6808 Don't create AccessibleJTreeNode for the tree root if it's n…
Browse files Browse the repository at this point in the history
…ot visible

* This fixes an issue with AccessibleJTreeNode#getBounds, which adjusts the node's bounds according to the parent node. For nodes whose parent is the invisible root, getBounds was returning null, and it caused issues with assistive technology like macOS Accessibility Zoom.
* Additionally, NVDA will now report correct tree depth levels because the root node won't add to the levels count (JDK-8249806).

(cherry picked from commit f7c47bf)
  • Loading branch information
dmitrii-drobotov authored and jbrbot committed May 12, 2024
1 parent 14b289a commit 487c23b
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 6 deletions.
12 changes: 9 additions & 3 deletions src/java.desktop/share/classes/javax/swing/JTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -4996,9 +4996,15 @@ public Accessible getAccessibleParent() {
if (treeModel != null) {
index = treeModel.getIndexOfChild(objParent, obj);
}
accessibleParent = new AccessibleJTreeNode(tree,
parentPath,
null);
// If the root is not visible and the parent is the root,
// don't create an accessible node for it.
if (!isRootVisible() && parentPath.getParentPath() == null) {
accessibleParent = tree;
} else {
accessibleParent = new AccessibleJTreeNode(tree,
parentPath,
null);
}
this.setAccessibleParent(accessibleParent);
} else if (treeModel != null) {
accessibleParent = tree; // we're the top!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6476,9 +6476,15 @@ public Accessible getAccessibleParent() {
java.lang.System.arraycopy(objPath, 0, objParentPath,
0, objPath.length-1);
TreePath parentPath = new TreePath(objParentPath);
accessibleParent = new AccessibleJTreeNode(tree,
parentPath,
null);
// If the root is not visible and the parent is the root,
// don't create an accessible node for it.
if (!tree.isRootVisible() && parentPath.getParentPath() == null) {
accessibleParent = tree;
} else {
accessibleParent = new AccessibleJTreeNode(tree,
parentPath,
null);
}
this.setAccessibleParent(accessibleParent);
} else if (treeModel != null) {
accessibleParent = tree; // we're the top!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8249806
* @summary Tests that JTree's root is not set as accessible parent of top-level tree nodes (direct children of root)
* when the JTree has setRootVisible(false).
* @run main AccessibleJTreeNodeAccessibleParentTest
*/

import java.awt.Robot;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class AccessibleJTreeNodeAccessibleParentTest {
private static JTree jTree;
private static JFrame jFrame;

private static void createGUI() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
DefaultMutableTreeNode node = new DefaultMutableTreeNode("node");
root.add(node);

jTree = new JTree(root);
jTree.setRootVisible(false);

jFrame = new JFrame();
jFrame.setBounds(100, 100, 300, 300);
jFrame.getContentPane().add(jTree);
jFrame.setVisible(true);
}

private static void doTest() throws Exception {
try {
SwingUtilities.invokeAndWait(() -> createGUI());
Robot robot = new Robot();
robot.waitForIdle();

AtomicBoolean accessibleNodeInitialized = new AtomicBoolean(false);

SwingUtilities.invokeAndWait(() -> {
jTree.getAccessibleContext().addPropertyChangeListener(evt -> {
// When an AccessibleJTreeNode is created for the active descendant change event,
// its parent is not set on initialization but calculated on the first access.
// This imitates the way assistive tools obtain AccessibleJTreeNode objects.
if (AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY.equals(evt.getPropertyName()) &&
evt.getNewValue() instanceof Accessible accessibleNode) {
// Check that the parent of the top-level node is the tree itself instead of the invisible root.
if (!jTree.equals(accessibleNode.getAccessibleContext().getAccessibleParent())) {
throw new RuntimeException("Accessible parent of the top-level node is not the tree.");
}
accessibleNodeInitialized.set(true);
}
});

jTree.setSelectionRow(0);
});
robot.waitForIdle();

if (!accessibleNodeInitialized.get()) {
throw new RuntimeException("The active descendant property change event wasn't fired, " +
"or the accessible node wasn't initialized properly.");
}
} finally {
SwingUtilities.invokeAndWait(() -> jFrame.dispose());
}
}

public static void main(String[] args) throws Exception {
doTest();
System.out.println("Test Passed");
}
}

0 comments on commit 487c23b

Please sign in to comment.