Merge branch

'GP-5979_dragonmacher-search-memory-accessibility--SQUASHED' (Closes #8264)
This commit is contained in:
Ryan Kurtz 2025-09-15 09:53:17 -04:00
commit 6dd00be368
8 changed files with 114 additions and 28 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.features.base.memsearch.gui; package ghidra.features.base.memsearch.gui;
import java.awt.BorderLayout;
import java.awt.FlowLayout; import java.awt.FlowLayout;
import javax.swing.*; import javax.swing.*;
@ -38,20 +37,30 @@ public class MemoryScanControlPanel extends JPanel {
private JButton scanButton; private JButton scanButton;
MemoryScanControlPanel(MemorySearchProvider provider) { MemoryScanControlPanel(MemorySearchProvider provider) {
super(new BorderLayout()); super();
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
add(buildButtonPanel(), BorderLayout.CENTER);
scanButton = new JButton("Scan Values"); scanButton = new JButton("Scan Values");
scanButton.setMnemonic('V');
scanButton.setEnabled(false);
scanButton.setToolTipText("Refreshes byte values of current results and eliminates " + scanButton.setToolTipText("Refreshes byte values of current results and eliminates " +
"those that don't meet the selected change criteria"); "those that don't meet the selected change criteria");
add(scanButton);
add(Box.createHorizontalStrut(20));
add(buildButtonPanel());
HelpService helpService = Help.getHelpService(); HelpService helpService = Help.getHelpService();
helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Scan_Controls")); helpService.registerHelp(this, new HelpLocation(HelpTopics.SEARCH, "Scan_Controls"));
add(scanButton, BorderLayout.WEST);
scanButton.addActionListener(e -> provider.scan(selectedScanner)); scanButton.addActionListener(e -> provider.scan(selectedScanner));
} }
private JComponent buildButtonPanel() { private JComponent buildButtonPanel() {
JPanel panel = new JPanel(new FlowLayout()); JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
ButtonGroup buttonGroup = new ButtonGroup(); ButtonGroup buttonGroup = new ButtonGroup();
for (Scanner scanner : Scanner.values()) { for (Scanner scanner : Scanner.values()) {
GRadioButton button = new GRadioButton(scanner.getName()); GRadioButton button = new GRadioButton(scanner.getName());

View file

@ -24,14 +24,17 @@ import java.util.List;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.text.*; import javax.swing.text.*;
import docking.DockingUtils; import docking.DockingUtils;
import docking.menu.ButtonState; import docking.menu.ButtonState;
import docking.menu.MultiStateButton; import docking.menu.MultiStateButton;
import docking.widgets.PopupWindow; import docking.widgets.PopupWindow;
import docking.widgets.combobox.GComboBox;
import docking.widgets.combobox.GhidraComboBox; import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import docking.widgets.list.GComboBoxCellRenderer; import docking.widgets.list.GComboBoxCellRenderer;
import generic.theme.GThemeDefaults.Colors.Messages; import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.features.base.memsearch.combiner.Combiner; import ghidra.features.base.memsearch.combiner.Combiner;
@ -89,9 +92,11 @@ class MemorySearchControlPanel extends JPanel {
searchButton = new MultiStateButton<Combiner>(initialSearchButtonStates); searchButton = new MultiStateButton<Combiner>(initialSearchButtonStates);
searchButton searchButton
.setStateChangedListener(state -> model.setMatchCombiner(state.getClientData())); .setStateChangedListener(state -> model.setMatchCombiner(state.getClientData()));
searchButton.setMnemonic('S');
searchButton.addActionListener(e -> search()); searchButton.addActionListener(e -> search());
panel.add(searchButton, BorderLayout.WEST); panel.add(searchButton, BorderLayout.WEST);
selectionCheckbox = new JCheckBox("Selection Only"); selectionCheckbox = new JCheckBox("Selection Only");
selectionCheckbox.setMnemonic('O');
selectionCheckbox.setSelected(model.isSearchSelectionOnly()); selectionCheckbox.setSelected(model.isSearchSelectionOnly());
selectionCheckbox.setEnabled(model.hasSelection()); selectionCheckbox.setEnabled(model.hasSelection());
selectionCheckbox selectionCheckbox
@ -132,9 +137,10 @@ class MemorySearchControlPanel extends JPanel {
if (!formatComboBox.getSelectedItem().equals(searchFormat)) { if (!formatComboBox.getSelectedItem().equals(searchFormat)) {
formatComboBox.setSelectedItem(searchFormat); formatComboBox.setSelectedItem(searchFormat);
} }
selectionCheckbox.setSelected(model.isSearchSelectionOnly()); selectionCheckbox.setSelected(model.isSearchSelectionOnly());
selectionCheckbox.setEnabled(model.hasSelection()); selectionCheckbox.setEnabled(model.hasSelection());
searchInputField.setToolTipText(searchFormat.getToolTip()); searchInputField.setToolTipText("Search Text: " + searchFormat.getToolTip());
String text = searchInputField.getText(); String text = searchInputField.getText();
String convertedText = searchFormat.convertText(text, oldSettings, model.getSettings()); String convertedText = searchFormat.convertText(text, oldSettings, model.getSettings());
@ -144,23 +150,53 @@ class MemorySearchControlPanel extends JPanel {
} }
private JComponent buildLeftSearchInputPanel() { private JComponent buildLeftSearchInputPanel() {
createSearchInputField();
JPanel searchInputPanel = createSearchInputPanel();
GLabel searchLabel = new GLabel("Search Text:");
searchLabel.setDisplayedMnemonic('T');
searchLabel.setLabelFor(searchInputField);
JLabel bytesLabel = new GLabel("Byte Sequence:", SwingConstants.RIGHT);
bytesLabel.setToolTipText("The byte sequence that will be searched (if applicable)");
JPanel bytesPanel = createBytesPanel();
JPanel panel = new JPanel(new PairLayout(2, 10));
// row 1
panel.add(searchLabel);
panel.add(searchInputPanel);
// row 2
panel.add(bytesLabel);
panel.add(bytesPanel);
return panel;
}
private JPanel createBytesPanel() {
JPanel panel = new JPanel(new BorderLayout());
hexSearchSequenceField = new GDLabel(); hexSearchSequenceField = new GDLabel();
hexSearchSequenceField.setName("HexSequenceField"); hexSearchSequenceField.setName("Hex Sequence Field");
Border outerBorder = BorderFactory.createLoweredBevelBorder(); Border outerBorder = BorderFactory.createLoweredBevelBorder();
Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4); Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4);
Border border = BorderFactory.createCompoundBorder(outerBorder, innerBorder); Border border = BorderFactory.createCompoundBorder(outerBorder, innerBorder);
hexSearchSequenceField.setBorder(border); hexSearchSequenceField.setBorder(border);
JPanel panel = new JPanel(new PairLayout(2, 10)); panel.add(hexSearchSequenceField, BorderLayout.CENTER);
panel.add(buildSearchFormatCombo()); int spaceWidth = formatComboBox.getPreferredSize().width;
panel.add(searchInputField); panel.add(Box.createHorizontalStrut(spaceWidth), BorderLayout.EAST);
JLabel byteSequenceLabel = new JLabel("Byte Sequence:", SwingConstants.RIGHT);
byteSequenceLabel.setToolTipText( return panel;
"This field shows the byte sequence that will be search (if applicable)"); }
private JPanel createSearchInputPanel() {
createSearchInputField();
JPanel panel = new JPanel(new BorderLayout());
panel.add(searchInputField, BorderLayout.CENTER);
panel.add(buildSearchFormatCombo(), BorderLayout.EAST);
panel.add(byteSequenceLabel);
panel.add(hexSearchSequenceField);
return panel; return panel;
} }
@ -181,7 +217,7 @@ class MemorySearchControlPanel extends JPanel {
updateCombo(); updateCombo();
searchInputField.setAutoCompleteEnabled(false); // this interferes with validation searchInputField.setAutoCompleteEnabled(false); // this interferes with validation
searchInputField.setEditable(true); searchInputField.setEditable(true);
searchInputField.setToolTipText(model.getSearchFormat().getToolTip()); searchInputField.setToolTipText("Search Text: " + model.getSearchFormat().getToolTip());
searchInputField.setDocument(new RestrictedInputDocument()); searchInputField.setDocument(new RestrictedInputDocument());
searchInputField.addActionListener(ev -> search()); searchInputField.addActionListener(ev -> search());
JTextField searchTextField = searchInputField.getTextField(); JTextField searchTextField = searchInputField.getTextField();
@ -220,11 +256,14 @@ class MemorySearchControlPanel extends JPanel {
} }
private JComponent buildSearchFormatCombo() { private JComponent buildSearchFormatCombo() {
formatComboBox = new JComboBox<>(SearchFormat.ALL); formatComboBox = new GComboBox<>(SearchFormat.ALL);
formatComboBox.setSelectedItem(model.getSearchFormat()); formatComboBox.setSelectedItem(model.getSearchFormat());
formatComboBox.addItemListener(this::formatComboChanged); formatComboBox.addItemListener(this::formatComboChanged);
formatComboBox.setToolTipText("The selected format will determine how to " + formatComboBox.setToolTipText("Search Format: how to interpret search text");
"interpret text typed into the input field"); Border inside = formatComboBox.getBorder();
CompoundBorder paddingBorder =
BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0), inside);
formatComboBox.setBorder(paddingBorder);
return formatComboBox; return formatComboBox;
} }
@ -252,7 +291,7 @@ class MemorySearchControlPanel extends JPanel {
currentMatcher = byteMatcher; currentMatcher = byteMatcher;
String text = currentMatcher.getDescription(); String text = currentMatcher.getDescription();
hexSearchSequenceField.setText(text); hexSearchSequenceField.setText(text);
hexSearchSequenceField.setToolTipText(currentMatcher.getToolTip()); hexSearchSequenceField.setToolTipText("Search as hex: " + currentMatcher.getToolTip());
updateSearchButton(); updateSearchButton();
provider.setByteMatcher(byteMatcher); provider.setByteMatcher(byteMatcher);
} }

View file

@ -29,6 +29,7 @@ import javax.swing.text.*;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GLabel;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.docking.util.LookAndFeelUtils; import ghidra.docking.util.LookAndFeelUtils;
import ghidra.features.base.memsearch.bytesource.SearchRegion; import ghidra.features.base.memsearch.bytesource.SearchRegion;
@ -57,7 +58,7 @@ class MemorySearchOptionsPanel extends JPanel {
super(new BorderLayout()); super(new BorderLayout());
this.model = model; this.model = model;
// if the look and feel is Nimbus, the spaceing it too big, so we use less spacing // if the look and feel is Nimbus, the spacing it too big, so we use less spacing
// between elements. // between elements.
isNimbus = LookAndFeelUtils.isUsingNimbusUI(); isNimbus = LookAndFeelUtils.isUsingNimbusUI();
@ -93,9 +94,22 @@ class MemorySearchOptionsPanel extends JPanel {
JPanel panel = new JPanel(new VerticalLayout(3)); JPanel panel = new JPanel(new VerticalLayout(3));
panel.setBorder(createBorder("Search Region Filter")); panel.setBorder(createBorder("Search Region Filter"));
boolean accelerator = true;
List<SearchRegion> choices = model.getMemoryRegionChoices(); List<SearchRegion> choices = model.getMemoryRegionChoices();
for (SearchRegion region : choices) { for (SearchRegion region : choices) {
GCheckBox checkbox = new GCheckBox(region.getName()); GCheckBox checkbox = new GCheckBox(region.getName());
if (accelerator) {
// The text for the checkbox is dynamic. If the first letter is taken by a menu,
// then the accelerator may not work. At the time of writing, the first letter of
// the first option seems not to conflict with the other accelerators in the parent
// dialog.
String name = region.getName();
char c = name.charAt(0);
checkbox.setMnemonic(c);
accelerator = false;
}
checkbox.setToolTipText(region.getDescription()); checkbox.setToolTipText(region.getDescription());
checkbox.setSelected(model.isSelectedRegion(region)); checkbox.setSelected(model.isSelectedRegion(region));
checkbox.addItemListener(e -> model.selectRegion(region, checkbox.isSelected())); checkbox.addItemListener(e -> model.selectRegion(region, checkbox.isSelected()));
@ -109,13 +123,15 @@ class MemorySearchOptionsPanel extends JPanel {
panel.setBorder(createBorder("Decimal Options")); panel.setBorder(createBorder("Decimal Options"));
JPanel innerPanel = new JPanel(new PairLayout(5, 5)); JPanel innerPanel = new JPanel(new PairLayout(5, 5));
JLabel label = new JLabel("Size:"); GLabel label = new GLabel("Size:");
label.setDisplayedMnemonic('z');
label.setToolTipText("Size of decimal values in bytes"); label.setToolTipText("Size of decimal values in bytes");
innerPanel.add(label); innerPanel.add(label);
Integer[] decimalSizes = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 16 }; Integer[] decimalSizes = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 16 };
int decimalByteSize = model.getDecimalByteSize(); int decimalByteSize = model.getDecimalByteSize();
decimalByteSizeCombo = new GComboBox<>(decimalSizes); decimalByteSizeCombo = new GComboBox<>(decimalSizes);
label.setLabelFor(decimalByteSizeCombo);
decimalByteSizeCombo.setSelectedItem(decimalByteSize); decimalByteSizeCombo.setSelectedItem(decimalByteSize);
decimalByteSizeCombo.addItemListener(this::byteSizeComboChanged); decimalByteSizeCombo.addItemListener(this::byteSizeComboChanged);
decimalByteSizeCombo.setToolTipText("Size of decimal values in bytes"); decimalByteSizeCombo.setToolTipText("Size of decimal values in bytes");
@ -123,6 +139,7 @@ class MemorySearchOptionsPanel extends JPanel {
panel.add(innerPanel); panel.add(innerPanel);
decimalUnsignedCheckbox = new GCheckBox("Unsigned"); decimalUnsignedCheckbox = new GCheckBox("Unsigned");
decimalUnsignedCheckbox.setMnemonic('U');
decimalUnsignedCheckbox.setToolTipText( decimalUnsignedCheckbox.setToolTipText(
"Sets whether decimal values should be interpreted as unsigned values"); "Sets whether decimal values should be interpreted as unsigned values");
decimalUnsignedCheckbox.addActionListener( decimalUnsignedCheckbox.addActionListener(
@ -145,8 +162,11 @@ class MemorySearchOptionsPanel extends JPanel {
JPanel panel = new JPanel(new VerticalLayout(5)); JPanel panel = new JPanel(new VerticalLayout(5));
panel.setBorder(createBorder("Code Type Filter")); panel.setBorder(createBorder("Code Type Filter"));
GCheckBox instructionsCheckBox = new GCheckBox("Instructions"); GCheckBox instructionsCheckBox = new GCheckBox("Instructions");
instructionsCheckBox.setMnemonic('I');
GCheckBox definedDataCheckBox = new GCheckBox("Defined Data"); GCheckBox definedDataCheckBox = new GCheckBox("Defined Data");
definedDataCheckBox.setMnemonic('D');
GCheckBox undefinedDataCheckBox = new GCheckBox("Undefined Data"); GCheckBox undefinedDataCheckBox = new GCheckBox("Undefined Data");
undefinedDataCheckBox.setMnemonic('U');
instructionsCheckBox.setToolTipText( instructionsCheckBox.setToolTipText(
"If selected, include matches found in instructions"); "If selected, include matches found in instructions");
definedDataCheckBox.setToolTipText( definedDataCheckBox.setToolTipText(
@ -185,9 +205,15 @@ class MemorySearchOptionsPanel extends JPanel {
alignField.setToolTipText( alignField.setToolTipText(
"Filters out matches whose address is not divisible by the alignment value"); "Filters out matches whose address is not divisible by the alignment value");
panel.add(new JLabel("Endianess:")); GLabel endianessLabel = new GLabel("Endianess:");
endianessLabel.setLabelFor(endianessCombo);
endianessLabel.setDisplayedMnemonic('n');
GLabel alignmentLabel = new GLabel("Alignment:");
alignmentLabel.setDisplayedMnemonic('A');
alignmentLabel.setLabelFor(alignField);
panel.add(endianessLabel);
panel.add(endianessCombo); panel.add(endianessCombo);
panel.add(new JLabel("Alignment:")); panel.add(alignmentLabel);
panel.add(alignField); panel.add(alignField);
return panel; return panel;
@ -215,19 +241,23 @@ class MemorySearchOptionsPanel extends JPanel {
charsetCombo.setToolTipText("Character encoding for translating strings to bytes"); charsetCombo.setToolTipText("Character encoding for translating strings to bytes");
JPanel innerPanel = new JPanel(new PairLayout(5, 5)); JPanel innerPanel = new JPanel(new PairLayout(5, 5));
JLabel label = new JLabel("Encoding:"); GLabel label = new GLabel("Encoding:");
label.setDisplayedMnemonic('c');
label.setLabelFor(charsetCombo);
label.setToolTipText("Character encoding for translating strings to bytes"); label.setToolTipText("Character encoding for translating strings to bytes");
innerPanel.add(label); innerPanel.add(label);
innerPanel.add(charsetCombo); innerPanel.add(charsetCombo);
panel.add(innerPanel); panel.add(innerPanel);
caseSensitiveCheckbox = new GCheckBox("Case Sensitive"); caseSensitiveCheckbox = new GCheckBox("Case Sensitive");
caseSensitiveCheckbox.setMnemonic('n');
caseSensitiveCheckbox.setSelected(model.isCaseSensitive()); caseSensitiveCheckbox.setSelected(model.isCaseSensitive());
caseSensitiveCheckbox.setToolTipText("Allows for case sensitive searching."); caseSensitiveCheckbox.setToolTipText("Allows for case sensitive searching.");
caseSensitiveCheckbox.addActionListener( caseSensitiveCheckbox.addActionListener(
e -> model.setCaseSensitive(caseSensitiveCheckbox.isSelected())); e -> model.setCaseSensitive(caseSensitiveCheckbox.isSelected()));
escapeSequencesCheckbox = new GCheckBox("Escape Sequences"); escapeSequencesCheckbox = new GCheckBox("Escape Sequences");
escapeSequencesCheckbox.setMnemonic('c');
escapeSequencesCheckbox.setSelected(model.useEscapeSequences()); escapeSequencesCheckbox.setSelected(model.useEscapeSequences());
escapeSequencesCheckbox.setToolTipText( escapeSequencesCheckbox.setToolTipText(
"Allows specifying control characters using escape sequences " + "Allows specifying control characters using escape sequences " +

View file

@ -698,10 +698,18 @@ public class MemorySearchProvider extends ComponentProviderAdapter
} }
@Override @Override
protected ActionContext createContext(Component sourceComponent, Object contextObject) { protected ActionContext createContext(Component focusedComponent, Object contextObject) {
ActionContext context = new NavigatableActionContext(this, navigatable); ActionContext context = new NavigatableActionContext(this, navigatable);
context.setContextObject(contextObject); context.setContextObject(contextObject);
context.setSourceComponent(sourceComponent);
// the 'sourceComponent' will be the focused item if the focus owner is in our provider,
// otherwise it will be the main component
context.setSourceObject(focusedComponent);
// we make the source component be the table so that the 'activate filter' action works
// from anywhere in this provider
GhidraTable table = resultsPanel.getTable();
context.setSourceComponent(table);
return context; return context;
} }