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

View file

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

View file

@ -29,6 +29,7 @@ import javax.swing.text.*;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GLabel;
import ghidra.app.util.HelpTopics;
import ghidra.docking.util.LookAndFeelUtils;
import ghidra.features.base.memsearch.bytesource.SearchRegion;
@ -57,7 +58,7 @@ class MemorySearchOptionsPanel extends JPanel {
super(new BorderLayout());
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.
isNimbus = LookAndFeelUtils.isUsingNimbusUI();
@ -93,9 +94,22 @@ class MemorySearchOptionsPanel extends JPanel {
JPanel panel = new JPanel(new VerticalLayout(3));
panel.setBorder(createBorder("Search Region Filter"));
boolean accelerator = true;
List<SearchRegion> choices = model.getMemoryRegionChoices();
for (SearchRegion region : choices) {
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.setSelected(model.isSelectedRegion(region));
checkbox.addItemListener(e -> model.selectRegion(region, checkbox.isSelected()));
@ -109,13 +123,15 @@ class MemorySearchOptionsPanel extends JPanel {
panel.setBorder(createBorder("Decimal Options"));
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");
innerPanel.add(label);
Integer[] decimalSizes = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 16 };
int decimalByteSize = model.getDecimalByteSize();
decimalByteSizeCombo = new GComboBox<>(decimalSizes);
label.setLabelFor(decimalByteSizeCombo);
decimalByteSizeCombo.setSelectedItem(decimalByteSize);
decimalByteSizeCombo.addItemListener(this::byteSizeComboChanged);
decimalByteSizeCombo.setToolTipText("Size of decimal values in bytes");
@ -123,6 +139,7 @@ class MemorySearchOptionsPanel extends JPanel {
panel.add(innerPanel);
decimalUnsignedCheckbox = new GCheckBox("Unsigned");
decimalUnsignedCheckbox.setMnemonic('U');
decimalUnsignedCheckbox.setToolTipText(
"Sets whether decimal values should be interpreted as unsigned values");
decimalUnsignedCheckbox.addActionListener(
@ -145,8 +162,11 @@ class MemorySearchOptionsPanel extends JPanel {
JPanel panel = new JPanel(new VerticalLayout(5));
panel.setBorder(createBorder("Code Type Filter"));
GCheckBox instructionsCheckBox = new GCheckBox("Instructions");
instructionsCheckBox.setMnemonic('I');
GCheckBox definedDataCheckBox = new GCheckBox("Defined Data");
definedDataCheckBox.setMnemonic('D');
GCheckBox undefinedDataCheckBox = new GCheckBox("Undefined Data");
undefinedDataCheckBox.setMnemonic('U');
instructionsCheckBox.setToolTipText(
"If selected, include matches found in instructions");
definedDataCheckBox.setToolTipText(
@ -185,9 +205,15 @@ class MemorySearchOptionsPanel extends JPanel {
alignField.setToolTipText(
"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(new JLabel("Alignment:"));
panel.add(alignmentLabel);
panel.add(alignField);
return panel;
@ -215,19 +241,23 @@ class MemorySearchOptionsPanel extends JPanel {
charsetCombo.setToolTipText("Character encoding for translating strings to bytes");
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");
innerPanel.add(label);
innerPanel.add(charsetCombo);
panel.add(innerPanel);
caseSensitiveCheckbox = new GCheckBox("Case Sensitive");
caseSensitiveCheckbox.setMnemonic('n');
caseSensitiveCheckbox.setSelected(model.isCaseSensitive());
caseSensitiveCheckbox.setToolTipText("Allows for case sensitive searching.");
caseSensitiveCheckbox.addActionListener(
e -> model.setCaseSensitive(caseSensitiveCheckbox.isSelected()));
escapeSequencesCheckbox = new GCheckBox("Escape Sequences");
escapeSequencesCheckbox.setMnemonic('c');
escapeSequencesCheckbox.setSelected(model.useEscapeSequences());
escapeSequencesCheckbox.setToolTipText(
"Allows specifying control characters using escape sequences " +

View file

@ -698,10 +698,18 @@ public class MemorySearchProvider extends ComponentProviderAdapter
}
@Override
protected ActionContext createContext(Component sourceComponent, Object contextObject) {
protected ActionContext createContext(Component focusedComponent, Object contextObject) {
ActionContext context = new NavigatableActionContext(this, navigatable);
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;
}