mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-1685 - Fixed the Find Dialog incorrectly matching history entries
when the selected item was programmatically set
This commit is contained in:
parent
e9b4aed8fe
commit
f0d0dd532d
7 changed files with 161 additions and 78 deletions
|
@ -19,7 +19,6 @@ import java.awt.BorderLayout;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
@ -49,18 +48,18 @@ class AnnotationHandlerDialog extends DialogComponentProvider {
|
||||||
addCancelButton();
|
addCancelButton();
|
||||||
setOkEnabled(true);
|
setOkEnabled(true);
|
||||||
setHelpLocation(new HelpLocation(HelpTopics.DATA_MANAGER, "Export_To"));
|
setHelpLocation(new HelpLocation(HelpTopics.DATA_MANAGER, "Export_To"));
|
||||||
setRememberSize( false );
|
setRememberSize(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cancelCallback() {
|
protected void cancelCallback() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void okCallback() {
|
protected void okCallback() {
|
||||||
Object [] objs = handlerComboBox.getSelectedObjects();
|
Object[] objs = handlerComboBox.getSelectedObjects();
|
||||||
if (objs != null && objs.length > 0) {
|
if (objs != null && objs.length > 0) {
|
||||||
handler = (AnnotationHandler) objs[0];
|
handler = (AnnotationHandler) objs[0];
|
||||||
}
|
}
|
||||||
|
@ -71,8 +70,9 @@ class AnnotationHandlerDialog extends DialogComponentProvider {
|
||||||
JPanel create() {
|
JPanel create() {
|
||||||
JPanel outerPanel = new JPanel(new BorderLayout());
|
JPanel outerPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
handlerComboBox = new GhidraComboBox<>(new Vector<AnnotationHandler>(handlerList));
|
handlerComboBox = new GhidraComboBox<>(handlerList);
|
||||||
handlerComboBox.addActionListener(new ActionListener() {
|
handlerComboBox.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
okCallback();
|
okCallback();
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,11 @@ class AnnotationHandlerDialog extends DialogComponentProvider {
|
||||||
return outerPanel;
|
return outerPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AnnotationHandler getHandler() { return handler; }
|
public AnnotationHandler getHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean wasSuccessful() { return success; }
|
public boolean wasSuccessful() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,7 +285,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||||
private Component buildFormatChooser() {
|
private Component buildFormatChooser() {
|
||||||
|
|
||||||
List<Exporter> exporters = getApplicableExporters();
|
List<Exporter> exporters = getApplicableExporters();
|
||||||
comboBox = new GhidraComboBox<>(new Vector<>(exporters));
|
comboBox = new GhidraComboBox<>(exporters);
|
||||||
|
|
||||||
Exporter defaultExporter = getDefaultExporter(exporters);
|
Exporter defaultExporter = getDefaultExporter(exporters);
|
||||||
if (defaultExporter != null) {
|
if (defaultExporter != null) {
|
||||||
|
|
|
@ -260,7 +260,7 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||||
set.add(loader);
|
set.add(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loaderComboBox = new GhidraComboBox<>(new Vector<>(set));
|
loaderComboBox = new GhidraComboBox<>(set);
|
||||||
loaderComboBox.addItemListener(e -> selectedLoaderChanged());
|
loaderComboBox.addItemListener(e -> selectedLoaderChanged());
|
||||||
loaderComboBox.setEnterKeyForwarding(true);
|
loaderComboBox.setEnterKeyForwarding(true);
|
||||||
loaderComboBox.setRenderer(
|
loaderComboBox.setRenderer(
|
||||||
|
|
|
@ -45,14 +45,13 @@ public class FindAction extends AbstractDecompilerAction {
|
||||||
if (findDialog == null) {
|
if (findDialog == null) {
|
||||||
findDialog =
|
findDialog =
|
||||||
new FindDialog("Decompiler Find Text", new DecompilerSearcher(decompilerPanel)) {
|
new FindDialog("Decompiler Find Text", new DecompilerSearcher(decompilerPanel)) {
|
||||||
@Override
|
@Override
|
||||||
protected void dialogClosed() {
|
protected void dialogClosed() {
|
||||||
// clear the search results when the dialog is closed
|
// clear the search results when the dialog is closed
|
||||||
decompilerPanel.setSearchResults(null);
|
decompilerPanel.setSearchResults(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
findDialog
|
findDialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||||
.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
|
||||||
}
|
}
|
||||||
return findDialog;
|
return findDialog;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package docking.widgets;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
|
@ -218,9 +219,15 @@ public class FindDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
public void setSearchText(String text) {
|
public void setSearchText(String text) {
|
||||||
String searchText = text == null ? textField.getText() : text;
|
String searchText = text == null ? textField.getText() : text;
|
||||||
textField.setText(searchText);
|
comboBox.setSelectedItem(searchText);
|
||||||
textField.setSelectionStart(0);
|
}
|
||||||
textField.setSelectionEnd(searchText.length());
|
|
||||||
|
public String getSearchText() {
|
||||||
|
return textField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHistory(List<String> history) {
|
||||||
|
history.forEach(comboBox::addToModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeSearchText(String text) {
|
private void storeSearchText(String text) {
|
||||||
|
|
|
@ -31,30 +31,32 @@ import docking.widgets.GComponent;
|
||||||
/**
|
/**
|
||||||
* GhidraComboBox adds the following features:
|
* GhidraComboBox adds the following features:
|
||||||
*
|
*
|
||||||
* 1) ActionListeners are only invoked when the <Enter> key
|
* <p>
|
||||||
* is pressed within the text-field of the combo-box.
|
* 1) ActionListeners are only invoked when the <Enter> key is pressed within the text-field
|
||||||
* In normal JComboBox case, the ActionListeners are notified
|
* of the combo-box. In normal JComboBox case, the ActionListeners are notified when an item is
|
||||||
* when an item is selected from the list.
|
* selected from the list.
|
||||||
*
|
*
|
||||||
* 2) Adds the auto-completion feature. As a user
|
* <p>
|
||||||
* types in the field, the combo box suggest the nearest matching
|
* 2) Adds the auto-completion feature. As a user types in the field, the combo box suggest the
|
||||||
* entry in the combo box model.
|
* nearest matching entry in the combo box model.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* It also fixes the following bug:
|
* It also fixes the following bug:
|
||||||
*
|
*
|
||||||
* A normal JComboBox has a problem (feature?)
|
* <p>
|
||||||
* that if you have a dialog with a button
|
* A normal JComboBox has a problem (feature?) that if you have a dialog with a button and
|
||||||
* and JComboBox and you edit the comboText field and
|
* JComboBox and you edit the comboText field and then hit the button, the button sometimes does
|
||||||
* then hit the button, the button sometimes does not work.
|
* not work.
|
||||||
*
|
*
|
||||||
* When the combobox loses focus,
|
* <p>
|
||||||
* and its text has changed, it generates an actionPerformed event as
|
* When the combobox loses focus, and its text has changed, it generates an actionPerformed event
|
||||||
* though the user pressed <Enter> in the combo text field. This
|
* as though the user pressed <Enter> in the combo text field. This has a bizarre effect if
|
||||||
* has a bizarre effect if you have added an actionPerformed listener
|
* you have added an actionPerformed listener to the combobox and in your callback you adjust the
|
||||||
* to the combobox and in your callback you adjust the enablement state
|
* enablement state of the button that you pressed (which caused the text field to lose focus) in
|
||||||
* of the button that you pressed (which caused the text field to lose
|
* that you end up changing the button's internal state(by calling setEnabled(true or false)) in
|
||||||
* focus) in that you end up changing the button's internal state(by calling
|
* the middle of the button press.
|
||||||
* setEnabled(true or false)) in the middle of the button press.
|
*
|
||||||
|
* @param <E> the item type
|
||||||
*/
|
*/
|
||||||
public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
private ArrayList<ActionListener> listeners = new ArrayList<>();
|
private ArrayList<ActionListener> listeners = new ArrayList<>();
|
||||||
|
@ -74,17 +76,16 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new GhidraComboBox using the given model.
|
* Construct a new GhidraComboBox using the given model.
|
||||||
* @see javax.swing.JComboBox#JComboBox(ComboBoxModel)
|
* @param model the model
|
||||||
*/
|
*/
|
||||||
public GhidraComboBox(ComboBoxModel<E> aModel) {
|
public GhidraComboBox(ComboBoxModel<E> model) {
|
||||||
super(aModel);
|
super(model);
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new GhidraComboBox and populate a default model
|
* Construct a new GhidraComboBox and populate a default model with the given items.
|
||||||
* with the given items.
|
* @param items the items
|
||||||
* @see javax.swing.JComboBox#JComboBox(Object[])
|
|
||||||
*/
|
*/
|
||||||
public GhidraComboBox(E[] items) {
|
public GhidraComboBox(E[] items) {
|
||||||
super(items);
|
super(items);
|
||||||
|
@ -92,12 +93,11 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new GhidraComboBox and populate a default model with
|
* Construct a new GhidraComboBox and populate a default model with the given items.
|
||||||
* the given Vector of items.
|
* @param items the items
|
||||||
* @see javax.swing.JComboBox#JComboBox(Vector)
|
|
||||||
*/
|
*/
|
||||||
public GhidraComboBox(Vector<E> items) {
|
public GhidraComboBox(Collection<E> items) {
|
||||||
super(items);
|
super(new Vector<>(items));
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,10 +155,10 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HACK ALERT: By default, the JComboBoxUI forwards the <Enter> key actions to the root pane
|
* HACK ALERT: By default, the JComboBoxUI forwards the <Enter> key actions to the root
|
||||||
* of the JComboBox's container (which is used primarily by any installed 'default button').
|
* pane of the JComboBox's container (which is used primarily by any installed 'default
|
||||||
* The problem is that the forwarding does not happen always. In the case that the <Enter>
|
* button'). The problem is that the forwarding does not happen always. In the case that the
|
||||||
* key will trigger a selection in the combo box, the action is NOT forwarded.
|
* <Enter> key will trigger a selection in the combo box, the action is NOT forwarded.
|
||||||
* <p>
|
* <p>
|
||||||
* By default Ghidra disables the forwarding altogether, since most users of
|
* By default Ghidra disables the forwarding altogether, since most users of
|
||||||
* {@link GhidraComboBox} will add an action listener to handle <Enter> actions.
|
* {@link GhidraComboBox} will add an action listener to handle <Enter> actions.
|
||||||
|
@ -211,14 +211,17 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fix for the following series of events:
|
* A fix for the following series of events:
|
||||||
* -The user selects an item
|
* <ol>
|
||||||
* -The user deletes the text
|
* <li>The user selects an item</li>
|
||||||
* -setSelectedItem(Object) method is called with the same item
|
* <li>The user deletes the text</li>
|
||||||
|
* <li>setSelectedItem(Object) method is called with the same item</li>
|
||||||
|
* </ol>
|
||||||
*
|
*
|
||||||
* In that above series of steps, the text will still be empty, as the user deleted it *and*
|
* In that above series of steps, the text will still be empty, as the user deleted it *and*
|
||||||
* the call to setSelectedItem(Object) had no effect because the base class assumed that the
|
* the call to setSelectedItem(Object) had no effect because the base class assumed that the
|
||||||
* item is already selected.
|
* item is already selected.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* This method exists to make sure, in that case, that the text of the field matches the
|
* This method exists to make sure, in that case, that the text of the field matches the
|
||||||
* selected item.
|
* selected item.
|
||||||
*/
|
*/
|
||||||
|
@ -243,9 +246,6 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all entries in the drop down list
|
|
||||||
*/
|
|
||||||
public void clearModel() {
|
public void clearModel() {
|
||||||
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
|
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
|
||||||
model.removeAllElements();
|
model.removeAllElements();
|
||||||
|
@ -325,15 +325,15 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Document the valid user input on the fly.
|
* Custom Document to perform matching of items as the user types
|
||||||
*/
|
*/
|
||||||
public class InterceptedInputDocument extends DefaultStyledDocument {
|
public class InterceptedInputDocument extends DefaultStyledDocument {
|
||||||
|
|
||||||
private boolean automated = false;
|
private boolean automated = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before new user input is inserted into the entry text field. The super
|
* Called before new user input is inserted into the entry text field. The super method is
|
||||||
* method is called if the input is accepted.
|
* called if the input is accepted.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
|
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package docking.widgets;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import generic.test.AbstractGenericTest;
|
||||||
|
|
||||||
|
public class FindDialogTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetSelectedValueDoesNotTriggerMatch() {
|
||||||
|
FindDialogSearcher searcher = new DummySearcher();
|
||||||
|
FindDialog findDialog = new FindDialog("Title", searcher);
|
||||||
|
findDialog.setHistory(List.of("search1"));
|
||||||
|
|
||||||
|
String searchText = "search"; // a prefix of an existing history entry
|
||||||
|
findDialog.setSearchText(searchText);
|
||||||
|
assertEquals(searchText, AbstractGenericTest.runSwing(() -> findDialog.getSearchText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DummySearcher implements FindDialogSearcher {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CursorPosition getCursorPosition() {
|
||||||
|
return new CursorPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCursorPosition(CursorPosition position) {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CursorPosition getStart() {
|
||||||
|
return new CursorPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CursorPosition getEnd() {
|
||||||
|
return new CursorPosition(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void highlightSearchResults(SearchLocation location) {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchLocation search(String text, CursorPosition cursorPosition,
|
||||||
|
boolean searchForward, boolean useRegex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue