GP-5964 - File Chooser - Accessibility tweaks

This commit is contained in:
dragonmacher 2025-09-09 15:22:03 -04:00
parent 6eab6693fc
commit dd6561807b
6 changed files with 272 additions and 148 deletions

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -27,7 +27,7 @@ import docking.action.DockingActionIf;
import ghidra.docking.util.LookAndFeelUtils; import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.StringUtilities; import ghidra.util.StringUtilities;
class DockingToolBarUtils { public class DockingToolBarUtils {
private static final String START_KEYBINDING_TEXT = "<BR><HR><CENTER>("; private static final String START_KEYBINDING_TEXT = "<BR><HR><CENTER>(";
private static final String END_KEYBINDNIG_TEXT = ")</CENTER>"; private static final String END_KEYBINDNIG_TEXT = ")</CENTER>";
@ -37,16 +37,25 @@ class DockingToolBarUtils {
* @param button the button * @param button the button
* @param action the action * @param action the action
*/ */
static void setToolTipText(JButton button, DockingActionIf action) { public static void setToolTipText(JButton button, DockingActionIf action) {
String text = createToolTipText(button, action);
button.setToolTipText(text);
}
/**
* Creates tooltip text for the given action and button. This is intended to be used for
* buttons that represent the given action.
* @param button the button that is the target for the text
* @param action the action that is the source of the button
* @return the text
*/
public static String createToolTipText(JButton button, DockingActionIf action) {
String toolTipText = getToolTipText(action); String toolTipText = getToolTipText(action);
String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding()); String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding());
if (keyBindingText != null) { if (keyBindingText != null) {
button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText)); return combingToolTipTextWithKeyBinding(toolTipText, keyBindingText);
}
else {
button.setToolTipText(toolTipText);
} }
return toolTipText;
} }
private static String combingToolTipTextWithKeyBinding(String toolTipText, private static String combingToolTipTextWithKeyBinding(String toolTipText,

View file

@ -130,7 +130,7 @@ public class EmptyBorderButton extends JButton {
@Override @Override
public void setBorder(Border border) { public void setBorder(Border border) {
// To keep UI from installing a non-appropriate border (such as when switching themes), // To keep UI from installing an incorrect border (such as when switching themes),
// only allow borders created by this class to be set. // only allow borders created by this class to be set.
if (border == RAISED_BUTTON_BORDER || border == LOWERED_BUTTON_BORDER || if (border == RAISED_BUTTON_BORDER || border == LOWERED_BUTTON_BORDER ||
border == FOCUSED_BUTTON_BORDER || border == NO_BUTTON_BORDER) { border == FOCUSED_BUTTON_BORDER || border == NO_BUTTON_BORDER) {
@ -171,6 +171,12 @@ public class EmptyBorderButton extends JButton {
setBorder(NO_BUTTON_BORDER); setBorder(NO_BUTTON_BORDER);
} }
@Override
public void setEnabled(boolean b) {
setBorder(NO_BUTTON_BORDER);
super.setEnabled(b);
}
protected void updateBorderBasedOnState() { protected void updateBorderBasedOnState() {
if (!isEnabled()) { if (!isEnabled()) {
return; return;

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,36 +15,45 @@
*/ */
package docking.widgets.filechooser; package docking.widgets.filechooser;
import java.awt.event.MouseAdapter; import java.awt.Color;
import java.awt.event.MouseEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.File; import java.io.File;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.*;
import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import generic.theme.GColor;
import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors;
public class FileChooserToggleButton extends JToggleButton { public class FileChooserToggleButton extends JToggleButton {
private static final long serialVersionUID = 1L;
static final Border RAISED_BORDER = BorderFactory.createCompoundBorder( //
BorderFactory.createRaisedBevelBorder(), // All border sizes are based on trial-and-error, adjusted to prevent the UI from moving as the
BorderFactory.createEmptyBorder(1, 1, 1, 1)); // user hovers and moves around with the keyboard.
//
private static final Border RAISED_BORDER = BorderFactory.createCompoundBorder(
BorderFactory.createRaisedBevelBorder(), BorderFactory.createEmptyBorder(2, 2, 2, 2));
static final Border NO_BORDER = new EmptyBorder(RAISED_BORDER.getBorderInsets(new JButton())); private static final Border LOWERED_BORDER = BorderFactory.createCompoundBorder(
BorderFactory.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(2, 2, 2, 2));
static final Border LOWERED_BORDER = BorderFactory.createCompoundBorder( // The focused border is a blue line with some padding on the outside so it is easy to see when
BorderFactory.createLoweredBevelBorder(), // the button has focus. This is similar to other buttons in the system.
BorderFactory.createEmptyBorder(1, 1, 1, 1)); private static final Color FOCUS_COLOR = new GColor("color.border.button.focused");
private static final Border FOCUSED_BORDER = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(1, 1, 1, 1), BorderFactory.createLineBorder(FOCUS_COLOR));
private static final Border UNFOCUSED_BORDER = BorderFactory.createEmptyBorder(2, 2, 2, 2);
public FileChooserToggleButton(String text) { private static final Border NO_BORDER = new EmptyBorder(4, 4, 4, 4);
private GhidraFileChooser fileChooser;
public FileChooserToggleButton(String text, GhidraFileChooser fileChooser) {
super(text); super(text);
initBorder(); this.fileChooser = fileChooser;
}
public FileChooserToggleButton(Action action) {
super(action);
initBorder(); initBorder();
} }
@ -59,88 +68,92 @@ public class FileChooserToggleButton extends JToggleButton {
setContentAreaFilled(false); setContentAreaFilled(false);
// changes the border on hover and click // changes the border on hover and click
addMouseListener(new ButtonMouseListener()); addChangeListener(new ButtonStateListener());
// works in conjunction with the mouse listener to properly set the border // works in conjunction with the mouse listener to properly set the border
addChangeListener(e -> { addChangeListener(e -> updateBorderBasedOnState());
if (isSelected()) {
setBorder(LOWERED_BORDER);
}
else {
setBorder(NO_BORDER);
}
});
setFocusable(false); // this prevents the focus box from being drawn over the button addFocusListener(new ButtonFocusListener());
updateBorderBasedOnState();
} }
void clearBorder() { @Override
public void setBorder(Border border) {
// To keep UI from installing an incorrect border (such as when switching themes),
// only allow borders created by this class to be set.
if (border == RAISED_BORDER || border == LOWERED_BORDER || border == NO_BORDER ||
border instanceof FocusedBorder) {
super.setBorder(border);
}
}
private void clearBorder() {
setBorder(NO_BORDER); setBorder(NO_BORDER);
} }
/** Returns the directory with which this button is associated. */ /** {@return Returns the directory with which this button is associated.} */
File getFile() { File getFile() {
return null; return null;
} }
private class ButtonMouseListener extends MouseAdapter { private void updateBorderBasedOnState() {
private boolean inside = false; if (!isEnabled()) {
return;
}
private Border defaultBorder; ButtonModel buttonModel = getModel();
boolean pressed = buttonModel.isPressed();
boolean rollover = buttonModel.isRollover();
boolean armed = buttonModel.isArmed();
boolean selected = buttonModel.isSelected();
Border border = NO_BORDER;
if (selected) {
border = LOWERED_BORDER;
}
else if (pressed && (rollover || armed)) {
border = LOWERED_BORDER;
}
else if (rollover) {
border = RAISED_BORDER;
}
border = createFocusedBorder(border, isFocusOwner());
setBorder(border);
}
private Border createFocusedBorder(Border outside, boolean isFocused) {
Border inside = isFocused ? FOCUSED_BORDER : UNFOCUSED_BORDER;
return new FocusedBorder(outside, inside);
}
private class ButtonStateListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
updateBorderBasedOnState();
}
}
private class ButtonFocusListener implements FocusListener {
@Override @Override
public void mouseEntered(MouseEvent me) { public void focusGained(FocusEvent e) {
if (isSelected()) { updateBorderBasedOnState();
return;
}
defaultBorder = getBorder();
setBorder(RAISED_BORDER);
inside = true;
} }
@Override @Override
public void mouseExited(MouseEvent me) { public void focusLost(FocusEvent e) {
if (isSelected()) { updateBorderBasedOnState();
return; fileChooser.updateShortcutPanel();
}
inside = false;
restoreBorder();
} }
}
@Override private class FocusedBorder extends CompoundBorder {
public void mousePressed(MouseEvent e) { FocusedBorder(Border outsideBorder, Border insideBorder) {
if (isSelected()) { super(outsideBorder, insideBorder);
return;
}
if (e.getButton() == MouseEvent.BUTTON1) {
setBorder(LOWERED_BORDER);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (isSelected()) {
return;
}
if (inside) {
setBorder(RAISED_BORDER);
}
else {
restoreBorder();
}
}
private void restoreBorder() {
if (defaultBorder != null) {
setBorder(defaultBorder);
}
else {
setBorder(NO_BORDER);
}
} }
} }
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -19,7 +19,6 @@ import javax.swing.*;
import docking.ReusableDialogComponentProvider; import docking.ReusableDialogComponentProvider;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.label.GLabel;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
@ -47,17 +46,13 @@ class GFileChooserOptionsDialog extends ReusableDialogComponentProvider {
private JComponent buildComponent() { private JComponent buildComponent() {
JPanel panel = new JPanel(new PairLayout()); JPanel panel = new JPanel(new PairLayout());
showDotFilesCheckBox = new GCheckBox(); showDotFilesCheckBox = new GCheckBox("Show '.' files");
showDotFilesCheckBox.setToolTipText("When toggled on the file chooser will show files " +
"with names that begin with a '.' character");
showDotFilesCheckBox.getAccessibleContext().setAccessibleName("Show Dot Files"); showDotFilesCheckBox.getAccessibleContext().setAccessibleName("Show Dot Files");
showDotFilesCheckBox.setSelected(true); showDotFilesCheckBox.setSelected(true);
JLabel label = new GLabel("Show '.' files");
label.getAccessibleContext().setAccessibleName("Show Files");
label.setToolTipText("When toggled on the file chooser will show files " +
"with names that begin with a '.' character");
panel.add(showDotFilesCheckBox); panel.add(showDotFilesCheckBox);
panel.add(label);
panel.getAccessibleContext().setAccessibleName("GFile Chooser Options"); panel.getAccessibleContext().setAccessibleName("GFile Chooser Options");
return panel; return panel;
} }

View file

@ -17,6 +17,8 @@ package docking.widgets.filechooser;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.util.*; import java.util.*;
@ -35,7 +37,11 @@ import javax.swing.text.DefaultFormatterFactory;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import docking.*; import docking.*;
import docking.action.DockingAction;
import docking.action.DockingActionIf;
import docking.action.builder.ActionBuilder;
import docking.actions.KeyBindingUtils; import docking.actions.KeyBindingUtils;
import docking.menu.DockingToolBarUtils;
import docking.widgets.*; import docking.widgets.*;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
@ -166,13 +172,19 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
private Component parent; private Component parent;
private JPanel waitPanel; private JPanel waitPanel;
private EmptyBorderButton backButton; private JButton backButton;
private EmptyBorderButton forwardButton; private JButton forwardButton;
private EmptyBorderButton upLevelButton; private JButton upButton;
private EmptyBorderButton newFolderButton; private JButton newFolderButton;
private EmptyBorderButton refreshButton; private JButton refreshButton;
private EmptyBorderToggleButton detailsButton; private EmptyBorderToggleButton detailsButton;
private DockingAction upAction;
private DockingAction backAction;
private DockingAction forwardAction;
private KeyBindingChangeListener keyBindingChangeListener = new KeyBindingChangeListener();
private JPanel shortcutPanel;
private UnselectableButtonGroup shortCutButtonGroup; private UnselectableButtonGroup shortCutButtonGroup;
private FileChooserToggleButton myComputerButton; private FileChooserToggleButton myComputerButton;
private FileChooserToggleButton desktopButton; private FileChooserToggleButton desktopButton;
@ -261,12 +273,43 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
setPreferredSize(800, 600); setPreferredSize(800, 600);
updateDirOnly(newModel.getHomeDirectory(), true); updateDirOnly(newModel.getHomeDirectory(), true);
createActions();
} }
//================================================================================================== //==================================================================================================
// Setup Methods // Setup Methods
//================================================================================================== //==================================================================================================
private void createActions() {
String owner = getClass().getSimpleName();
upAction = new ActionBuilder("Up One Level", owner)
.keyBinding("Alt Up")
.onAction(c -> goUp())
.build();
backAction = new ActionBuilder("Last Folder Visited", owner)
.keyBinding("Alt Left")
.onAction(c -> goBack())
.build();
forwardAction = new ActionBuilder("Previous Folder Visited", owner)
.keyBinding("Alt Right")
.onAction(c -> goForward())
.build();
upAction.addPropertyChangeListener(keyBindingChangeListener);
backAction.addPropertyChangeListener(keyBindingChangeListener);
forwardAction.addPropertyChangeListener(keyBindingChangeListener);
addAction(upAction);
addAction(backAction);
addAction(forwardAction);
updateNavigationButtonToolTips();
}
private JComponent buildWorkPanel() { private JComponent buildWorkPanel() {
buildWaitPanel(); buildWaitPanel();
@ -302,7 +345,8 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
} }
private JPanel buildShortCutPanel() { private JPanel buildShortCutPanel() {
myComputerButton = new FileChooserToggleButton("My Computer") {
myComputerButton = new FileChooserToggleButton("My Computer", this) {
@Override @Override
File getFile() { File getFile() {
return MY_COMPUTER; return MY_COMPUTER;
@ -314,7 +358,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
myComputerButton.addActionListener(e -> updateMyComputer()); myComputerButton.addActionListener(e -> updateMyComputer());
myComputerButton.setForeground(FOREROUND_COLOR); myComputerButton.setForeground(FOREROUND_COLOR);
desktopButton = new FileChooserToggleButton("Desktop") { desktopButton = new FileChooserToggleButton("Desktop", this) {
@Override @Override
File getFile() { File getFile() {
return fileChooserModel.getDesktopDirectory(); return fileChooserModel.getDesktopDirectory();
@ -327,7 +371,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
desktopButton.setForeground(FOREROUND_COLOR); desktopButton.setForeground(FOREROUND_COLOR);
desktopButton.setEnabled(fileChooserModel.getDesktopDirectory() != null); desktopButton.setEnabled(fileChooserModel.getDesktopDirectory() != null);
homeButton = new FileChooserToggleButton("Home") { homeButton = new FileChooserToggleButton("Home", this) {
@Override @Override
File getFile() { File getFile() {
return fileChooserModel.getHomeDirectory(); return fileChooserModel.getHomeDirectory();
@ -339,7 +383,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
homeButton.addActionListener(e -> updateHome()); homeButton.addActionListener(e -> updateHome());
homeButton.setForeground(FOREROUND_COLOR); homeButton.setForeground(FOREROUND_COLOR);
downloadsButton = new FileChooserToggleButton("Downloads") { downloadsButton = new FileChooserToggleButton("Downloads", this) {
@Override @Override
File getFile() { File getFile() {
return fileChooserModel.getDownloadsDirectory(); return fileChooserModel.getDownloadsDirectory();
@ -350,7 +394,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
downloadsButton.addActionListener(e -> updateDownloads()); downloadsButton.addActionListener(e -> updateDownloads());
downloadsButton.setForeground(FOREROUND_COLOR); downloadsButton.setForeground(FOREROUND_COLOR);
recentButton = new FileChooserToggleButton("Recent") { recentButton = new FileChooserToggleButton("Recent", this) {
@Override @Override
File getFile() { File getFile() {
return RECENT; return RECENT;
@ -369,27 +413,30 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
shortCutButtonGroup.add(downloadsButton); shortCutButtonGroup.add(downloadsButton);
shortCutButtonGroup.add(recentButton); shortCutButtonGroup.add(recentButton);
JPanel shortCutPanel = new JPanel(new GridLayout(0, 1)); shortcutPanel = new JPanel(new GridLayout(0, 1));
shortCutPanel.getAccessibleContext().setAccessibleName("Short Cut"); shortcutPanel.getAccessibleContext().setAccessibleName("Short Cut");
DockingUtils.setTransparent(shortCutPanel); DockingUtils.setTransparent(shortcutPanel);
shortCutPanel.add(myComputerButton); shortcutPanel.add(myComputerButton);
shortCutPanel.add(desktopButton); shortcutPanel.add(desktopButton);
shortCutPanel.add(homeButton); shortcutPanel.add(homeButton);
shortCutPanel.add(downloadsButton); shortcutPanel.add(downloadsButton);
shortCutPanel.add(recentButton); shortcutPanel.add(recentButton);
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createLoweredBevelBorder()); panel.setBorder(BorderFactory.createLoweredBevelBorder());
panel.setBackground(SHORTCUT_BACKGROUND_COLOR); panel.setBackground(SHORTCUT_BACKGROUND_COLOR);
panel.add(shortCutPanel, BorderLayout.NORTH); panel.add(shortcutPanel, BorderLayout.NORTH);
panel.getAccessibleContext().setAccessibleName("Short Cut"); panel.getAccessibleContext().setAccessibleName("Short Cut");
return panel; return panel;
} }
private JPanel buildFileNamePanel() { private JPanel buildFileNamePanel() {
JLabel filenameLabel = new GDLabel("File name:"); JLabel filenameLabel = new GDLabel("Filename:");
FileDropDownSelectionDataModel model = new FileDropDownSelectionDataModel(this); FileDropDownSelectionDataModel model = new FileDropDownSelectionDataModel(this);
filenameTextField = new DropDownSelectionTextField<>(model); filenameTextField = new DropDownSelectionTextField<>(model);
filenameLabel.setLabelFor(filenameTextField);
filenameTextField.setMatchingWindowHeight(200); filenameTextField.setMatchingWindowHeight(200);
filenameTextField.getAccessibleContext().setAccessibleName("Filename"); filenameTextField.getAccessibleContext().setAccessibleName("Filename");
filenameTextField.addCellEditorListener(new CellEditorListener() { filenameTextField.addCellEditorListener(new CellEditorListener() {
@ -427,9 +474,10 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
JLabel filterLabel = new GLabel("Type:"); JLabel filterLabel = new GLabel("Type:");
filterLabel.getAccessibleContext().setAccessibleName("Filter"); filterLabel.getAccessibleContext().setAccessibleName("Filter");
filterCombo = new GComboBox<>(); filterCombo = new GComboBox<>();
filterLabel.setLabelFor(filterCombo);
filterCombo.setRenderer(GListCellRenderer.createDefaultTextRenderer( filterCombo.setRenderer(GListCellRenderer.createDefaultTextRenderer(
fileFilter -> fileFilter != null ? fileFilter.getDescription() : "")); fileFilter -> fileFilter != null ? fileFilter.getDescription() : ""));
filterCombo.getAccessibleContext().setAccessibleName("Filter"); filterCombo.getAccessibleContext().setAccessibleName("File Type Filter");
filterModel = (DefaultComboBoxModel<GhidraFileFilter>) filterCombo.getModel(); filterModel = (DefaultComboBoxModel<GhidraFileFilter>) filterCombo.getModel();
addFileFilter(GhidraFileFilter.ALL); addFileFilter(GhidraFileFilter.ALL);
filterCombo.addItemListener(e -> rescanCurrentDirectory()); filterCombo.addItemListener(e -> rescanCurrentDirectory());
@ -444,22 +492,12 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
return filenamePanel; return filenamePanel;
} }
private class SelectionListener<T> implements DropDownSelectionChoiceListener<File> {
@Override
public void selectionChanged(File file) {
// take the selection and close the dialog
worker.schedule(new SetSelectedFileAndAcceptSelection(file));
}
}
private JPanel buildHeaderPanel() { private JPanel buildHeaderPanel() {
JPanel headerPanel = new JPanel(new GridBagLayout()); JPanel headerPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints(); GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0; gbc.gridx = 0;
// gbc.insets = new Insets(PAD, PAD, PAD, PAD);
JButton[] navButtons = buildNavigationButtons(); JButton[] navButtons = buildNavigationButtons();
for (JButton element : navButtons) { for (JButton element : navButtons) {
headerPanel.add(element, gbc); headerPanel.add(element, gbc);
@ -613,6 +651,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
} }
private JButton[] buildNavigationButtons() { private JButton[] buildNavigationButtons() {
backButton = new EmptyBorderButton(ICON_BACK); backButton = new EmptyBorderButton(ICON_BACK);
backButton.setName("BACK_BUTTON"); backButton.setName("BACK_BUTTON");
backButton.setEnabled(false); backButton.setEnabled(false);
@ -625,12 +664,13 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
forwardButton.setToolTipText("Go to previous folder visited"); forwardButton.setToolTipText("Go to previous folder visited");
forwardButton.addActionListener(e -> goForward()); forwardButton.addActionListener(e -> goForward());
upLevelButton = new EmptyBorderButton(ICON_UP); upButton = new EmptyBorderButton(ICON_UP);
upLevelButton.setName(UP_BUTTON_NAME); upButton.setName(UP_BUTTON_NAME);
upLevelButton.setToolTipText("Up one level"); upButton.setEnabled(false);
upLevelButton.addActionListener(e -> goUpOneDirectoryLevel()); upButton.setToolTipText("Up one level");
upButton.addActionListener(e -> goUp());
return new JButton[] { backButton, forwardButton, upLevelButton }; return new JButton[] { backButton, forwardButton, upButton };
} }
private JButton[] buildNonNavigationButtons() { private JButton[] buildNonNavigationButtons() {
@ -1382,15 +1422,17 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
} }
private void doSetSelectedFileAndUpdateDisplay(File file) { private void doSetSelectedFileAndUpdateDisplay(File file) {
if (lastInputFocus != null) {
lastInputFocus.requestFocusInWindow(); Component toFocus = getRestoreFocusComponent();
if (toFocus != null) {
toFocus.requestFocusInWindow();
} }
if (file == null) { if (file == null) {
return; return;
} }
// SCR 4513 - exception if we don't cancel edits before changing the display // exception if we don't cancel edits before changing the display
cancelEdits(); cancelEdits();
selectedFiles.setFile(file); selectedFiles.setFile(file);
updateTextFieldForFile(file); updateTextFieldForFile(file);
@ -1398,6 +1440,22 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
directoryModel.setSelectedFile(file); // the list or table display directoryModel.setSelectedFile(file); // the list or table display
} }
private Component getRestoreFocusComponent() {
// ensure we transfer focus to the directory or table when the view switches
if (isTableShowing()) {
if (lastInputFocus == directoryList) {
lastInputFocus = directoryTable;
}
}
else {
if (lastInputFocus == directoryTable) {
lastInputFocus = directoryList;
}
}
return lastInputFocus;
}
private void updateTextFieldForFile(File file) { private void updateTextFieldForFile(File file) {
if (file == null) { if (file == null) {
return; return;
@ -1452,7 +1510,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
return false; return false;
} }
private void goUpOneDirectoryLevel() { private void goUp() {
cancelEdits(); cancelEdits();
if (currentDirectory() == null) { if (currentDirectory() == null) {
@ -1521,8 +1579,16 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
updateDirAndSelectFile(currentDir, currentSelectedFile, true, false); updateDirAndSelectFile(currentDir, currentSelectedFile, true, false);
} }
private void updateShortcutPanel() { void updateShortcutPanel() {
// make sure that if one of the shortcut buttons is selected, the directory matches that button
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component focusOwner = kfm.getFocusOwner();
if (focusOwner != null && !SwingUtilities.isDescendingFrom(focusOwner, shortcutPanel)) {
// only synchronize the button state if the user is not interacting with the buttons
return;
}
// make sure the selected button matches the current directory
File currentDirectory = currentDirectory(); File currentDirectory = currentDirectory();
checkShortCutButton(myComputerButton, currentDirectory); checkShortCutButton(myComputerButton, currentDirectory);
checkShortCutButton(homeButton, currentDirectory); checkShortCutButton(homeButton, currentDirectory);
@ -1556,13 +1622,24 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
history.clear(); history.clear();
} }
private void updateNavigationButtonToolTips() {
String tip = DockingToolBarUtils.createToolTipText(backButton, backAction);
backButton.setToolTipText(tip);
tip = DockingToolBarUtils.createToolTipText(forwardButton, forwardAction);
forwardButton.setToolTipText(tip);
tip = DockingToolBarUtils.createToolTipText(upButton, upAction);
upButton.setToolTipText(tip);
}
private void updateNavigationButtons() { private void updateNavigationButtons() {
backButton.setEnabled(history.hasPrevious()); backButton.setEnabled(history.hasPrevious());
forwardButton.setEnabled(history.hasNext()); forwardButton.setEnabled(history.hasNext());
File dir = currentDirectory(); File dir = currentDirectory();
boolean enable = dir != null && dir.getParentFile() != null; boolean enable = dir != null && dir.getParentFile() != null;
upLevelButton.setEnabled(enable); upButton.setEnabled(enable);
} }
private void updateHistoryWithSelectedFiles(HistoryEntry historyEntry) { private void updateHistoryWithSelectedFiles(HistoryEntry historyEntry) {
@ -2235,7 +2312,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
public void runSwing() { public void runSwing() {
setDirectoryList(myComputerFile, roots); setDirectoryList(myComputerFile, roots);
setWaitPanelVisible(false); setWaitPanelVisible(false);
Swing.runLater(() -> doSetSelectedFileAndUpdateDisplay(null)); setSelectedFileAndUpdateDisplay(null);
} }
} }
@ -2254,6 +2331,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
setCurrentDirectoryDisplay(recentFile, addToHistory); setCurrentDirectoryDisplay(recentFile, addToHistory);
List<File> list = CollectionUtils.asList(recentList, File.class); List<File> list = CollectionUtils.asList(recentList, File.class);
setDirectoryList(recentFile, list); setDirectoryList(recentFile, list);
setSelectedFileAndUpdateDisplay(null);
} }
} }
@ -2430,4 +2508,26 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
return parentDir.getName() + selectedFilesText; return parentDir.getName() + selectedFilesText;
} }
} }
private class SelectionListener<T> implements DropDownSelectionChoiceListener<File> {
@Override
public void selectionChanged(File file) {
// take the selection and close the dialog
worker.schedule(new SetSelectedFileAndAcceptSelection(file));
}
}
private class KeyBindingChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if (name.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
updateNavigationButtonToolTips();
}
}
}
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -160,7 +160,8 @@ public class GThemeDefaults {
public static final GColor YELLOW = getColor("yellow"); public static final GColor YELLOW = getColor("yellow");
/** /**
* Returns a new {@link GColor} for the given palette name. * Returns a new {@link GColor} for the given palette name. The name should not include
* the prefix {@code color.palette}.
* <p> * <p>
* For a list of supported palette IDs, see {@code gui.palette.theme.properties}. * For a list of supported palette IDs, see {@code gui.palette.theme.properties}.
* <p> * <p>