diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProvider.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProvider.png index 86aa4931eb..0bb8d69c1a 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProvider.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProvider.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png index 87bf8867af..0b57358cbf 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png index 94a13ce3ba..3290880690 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryRegex.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryRegex.png index 0b77e07997..75f1cc6eac 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryRegex.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchMemoryRegex.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java index 82e8522eb6..eac955f614 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemoryScanControlPanel.java @@ -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()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java index 4a6e8691e9..cf6c1180d3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchControlPanel.java @@ -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(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,23 +150,53 @@ 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("HexSequenceField"); + hexSearchSequenceField.setName("Hex Sequence Field"); Border outerBorder = BorderFactory.createLoweredBevelBorder(); Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4); 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); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java index 4b5585254b..489f68d27e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java @@ -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 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 " + diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java index 95060011d9..a572739c9b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchProvider.java @@ -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; }