From 50d3bbeb80b8ab8292f097343df7236b753959ac Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:48:33 -0400 Subject: [PATCH] GP-6889 improving accessibility for show actions dialog --- .../java/docking/DialogComponentProvider.java | 3 ++ .../actions/dialog/ActionChooserDialog.java | 49 +++++++++++++++---- .../widgets/searchlist/SearchList.java | 15 +++--- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java index ae45829b42..a8626d2b0d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java @@ -682,6 +682,9 @@ public class DialogComponentProvider */ public void setAccessibleDescription(String description) { this.accessibleDescription = description; + if (dialog != null) { + dialog.getAccessibleContext().setAccessibleDescription(description); + } } private void doSetStatusText(String text, MessageType type, boolean alert) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/dialog/ActionChooserDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/dialog/ActionChooserDialog.java index e80488f620..8493db20a1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/dialog/ActionChooserDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/dialog/ActionChooserDialog.java @@ -4,9 +4,9 @@ * Licensed 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. @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; +import javax.accessibility.AccessibleContext; import javax.swing.*; import docking.*; @@ -59,10 +60,6 @@ public class ActionChooserDialog extends DialogComponentProvider { addOKButton(); addCancelButton(); updateTitle(); - setAccessibleDescription( - "This dialog initialy shows only locally relevant actions. Repeat initial keybinding " + - "to show More. Use up down arrows to scroll through list of actions and press" + - " enter to invoke selected action. Type text to filter list."); setOkEnabled(false); } @@ -115,6 +112,11 @@ public class ActionChooserDialog extends DialogComponentProvider { public void setActionDisplayLevel(ActionDisplayLevel level) { model.setDisplayLevel(level); updateTitle(); + updateAccessibleDescription(); + // put the focus to the filter to help the screen reader report the current display level. + // The focus is probably already in the filter or it was moved to the tree, but the tree + // contents is totally changing, so going back to the filter should not be annoying. + searchList.getTextField().requestFocus(); } @Override @@ -137,8 +139,30 @@ public class ActionChooserDialog extends DialogComponentProvider { setTitle("All Local and Global Actions (" + model.getSize() + ")"); break; } - } + + setAccessibleDescription( + "This dialog initially shows only locally relevant actions. Repeat initial keybinding " + + "to show more. Use up down arrows to scroll through list of actions and press" + + " enter to invoke selected action. Type text to filter list."); + } + private void updateAccessibleDescription() { + JTextField textField = searchList.getTextField(); + String msg = "Type to filter actions by name. List currently contains "; + switch (model.getActionDisplayLevel()) { + case LOCAL: + msg += "only the most relevant actions"; + break; + case GLOBAL: + msg += "all valid local and global actions"; + break; + case ALL: + msg += "all local and global actions"; + break; + } + textField.getAccessibleContext().setAccessibleDescription(msg); + } + private JComponent buildMainPanel() { JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createEmptyBorder(5, 2, 0, 2)); @@ -152,8 +176,15 @@ public class ActionChooserDialog extends DialogComponentProvider { searchList.setSelectionCallback(this::itemSelected); searchList.setInitialSelection(); // update selection after adding our listener searchList.setItemRenderer(new ActionRenderer()); - searchList.setDisplayNameFunction( - (t, c) -> getActionDisplayName(t, c) + " " + getKeyBindingString(t)); + searchList.setDisplayNameFunction((t, c) -> { + String name = getActionDisplayName(t, c) + " " + getKeyBindingString(t); + ActionContext context = model.getContext(); + if (context != null && !(t.isValidContext(context) && t.isEnabledForContext(context))) { + name += " (Disabled)"; + } + return name; + + }); panel.add(searchList); return panel; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/SearchList.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/SearchList.java index 8e3906ca3b..5bd52396ee 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/SearchList.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/searchlist/SearchList.java @@ -4,9 +4,9 @@ * Licensed 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. @@ -20,12 +20,14 @@ import java.awt.event.*; import java.util.List; import java.util.function.*; +import javax.accessibility.AccessibleContext; import javax.swing.*; import javax.swing.border.Border; import javax.swing.event.*; import docking.event.mouse.GMouseListenerAdapter; import docking.widgets.list.GListCellRenderer; +import ghidra.util.Msg; import utility.function.Dummy; /** @@ -219,6 +221,8 @@ public class SearchList extends JPanel { private Component buildFilterField() { JPanel panel = new JPanel(new BorderLayout()); textField = new JTextField(); + textField.getAccessibleContext().setAccessibleDescription("Enter text to filter list"); + textField.getAccessibleContext().setAccessibleName("Filter text field."); panel.add(textField, BorderLayout.CENTER); textField.addKeyListener(new TextFieldKeyListener()); textField.getDocument().addDocumentListener(new SearchListDocumentListener()); @@ -272,7 +276,7 @@ public class SearchList extends JPanel { return new DefaultFilter(text); } - JTextField getTextField() { + public JTextField getTextField() { return textField; } @@ -324,8 +328,8 @@ public class SearchList extends JPanel { categoryLabel.setOpaque(true); categoryLabel.setBackground(background); categoryLabel.setForeground(itemRendererComp.getForeground()); - panel.getAccessibleContext() - .setAccessibleName(getDisplayName(value.value(), value.category())); + String txt = getDisplayName(value.value(), value.category()); + panel.getAccessibleContext().setAccessibleName(txt); return panel; } } @@ -430,5 +434,4 @@ public class SearchList extends JPanel { public JTextField getFilterField() { return textField; } - }