diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AnnotationHandlerDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AnnotationHandlerDialog.java index 4064070fcf..47dcc0fe0e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AnnotationHandlerDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AnnotationHandlerDialog.java @@ -19,7 +19,6 @@ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; -import java.util.Vector; import javax.swing.JPanel; @@ -39,28 +38,28 @@ class AnnotationHandlerDialog extends DialogComponentProvider { private AnnotationHandler handler; private boolean success; - + AnnotationHandlerDialog(List handlerList) { super("Export Format"); this.handlerList = handlerList; - + addWorkPanel(create()); addOKButton(); addCancelButton(); setOkEnabled(true); setHelpLocation(new HelpLocation(HelpTopics.DATA_MANAGER, "Export_To")); - setRememberSize( false ); + setRememberSize(false); } - + @Override - protected void cancelCallback() { + protected void cancelCallback() { close(); } @Override - protected void okCallback() { - Object [] objs = handlerComboBox.getSelectedObjects(); + protected void okCallback() { + Object[] objs = handlerComboBox.getSelectedObjects(); if (objs != null && objs.length > 0) { handler = (AnnotationHandler) objs[0]; } @@ -70,9 +69,10 @@ class AnnotationHandlerDialog extends DialogComponentProvider { JPanel create() { JPanel outerPanel = new JPanel(new BorderLayout()); - - handlerComboBox = new GhidraComboBox<>(new Vector(handlerList)); + + handlerComboBox = new GhidraComboBox<>(handlerList); handlerComboBox.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent evt) { okCallback(); } @@ -80,8 +80,12 @@ class AnnotationHandlerDialog extends DialogComponentProvider { outerPanel.add(handlerComboBox, BorderLayout.NORTH); return outerPanel; } - - public AnnotationHandler getHandler() { return handler; } - - public boolean wasSuccessful() { return success; } + + public AnnotationHandler getHandler() { + return handler; + } + + public boolean wasSuccessful() { + return success; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java index e2acc6f3b8..79a56332fc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/exporter/ExporterDialog.java @@ -285,7 +285,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa private Component buildFormatChooser() { List exporters = getApplicableExporters(); - comboBox = new GhidraComboBox<>(new Vector<>(exporters)); + comboBox = new GhidraComboBox<>(exporters); Exporter defaultExporter = getDefaultExporter(exporters); if (defaultExporter != null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java index c49de70c56..9c223f6082 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java @@ -260,7 +260,7 @@ public class ImporterDialog extends DialogComponentProvider { set.add(loader); } } - loaderComboBox = new GhidraComboBox<>(new Vector<>(set)); + loaderComboBox = new GhidraComboBox<>(set); loaderComboBox.addItemListener(e -> selectedLoaderChanged()); loaderComboBox.setEnterKeyForwarding(true); loaderComboBox.setRenderer( diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindAction.java index f042cc7d62..a0ee3c155b 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindAction.java @@ -45,14 +45,13 @@ public class FindAction extends AbstractDecompilerAction { if (findDialog == null) { findDialog = new FindDialog("Decompiler Find Text", new DecompilerSearcher(decompilerPanel)) { - @Override - protected void dialogClosed() { - // clear the search results when the dialog is closed - decompilerPanel.setSearchResults(null); - } - }; - findDialog - .setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind")); + @Override + protected void dialogClosed() { + // clear the search results when the dialog is closed + decompilerPanel.setSearchResults(null); + } + }; + findDialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind")); } return findDialog; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/FindDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/FindDialog.java index 714fd9ea99..e6ea4c7c8a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/FindDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/FindDialog.java @@ -17,6 +17,7 @@ package docking.widgets; import java.awt.BorderLayout; import java.awt.event.KeyEvent; +import java.util.List; import javax.swing.*; import javax.swing.event.DocumentEvent; @@ -149,7 +150,7 @@ public class FindDialog extends DialogComponentProvider { SearchLocation searchLocation = searcher.search(searchText, cursorPosition, forward, useRegex); - // + // // First, just search in the current direction. // if (searchLocation != null) { @@ -157,7 +158,7 @@ public class FindDialog extends DialogComponentProvider { return; } - // + // // Did not find the text in the current direction. Wrap and try one more time. // String wrapMessage; @@ -177,9 +178,9 @@ public class FindDialog extends DialogComponentProvider { return; } - // - // At this point, we wrapped our search and did *not* find a match. This can only - // happen if there is no matching text anywhere in the document, as after wrapping + // + // At this point, we wrapped our search and did *not* find a match. This can only + // happen if there is no matching text anywhere in the document, as after wrapping // will will again find the previous match, if it exists. // notifyUser("Not found"); @@ -218,9 +219,15 @@ public class FindDialog extends DialogComponentProvider { public void setSearchText(String text) { String searchText = text == null ? textField.getText() : text; - textField.setText(searchText); - textField.setSelectionStart(0); - textField.setSelectionEnd(searchText.length()); + comboBox.setSelectedItem(searchText); + } + + public String getSearchText() { + return textField.getText(); + } + + public void setHistory(List history) { + history.forEach(comboBox::addToModel); } private void storeSearchText(String text) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java index dcff25daa1..6610667fc5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java @@ -31,30 +31,32 @@ import docking.widgets.GComponent; /** * GhidraComboBox adds the following features: * - * 1) ActionListeners are only invoked when the <Enter> key - * is pressed within the text-field of the combo-box. - * In normal JComboBox case, the ActionListeners are notified - * when an item is selected from the list. + *

+ * 1) ActionListeners are only invoked when the <Enter> key is pressed within the text-field + * of the combo-box. In normal JComboBox case, the ActionListeners are notified when an item is + * selected from the list. * - * 2) Adds the auto-completion feature. As a user - * types in the field, the combo box suggest the nearest matching - * entry in the combo box model. + *

+ * 2) Adds the auto-completion feature. As a user types in the field, the combo box suggest the + * nearest matching entry in the combo box model. * + *

* It also fixes the following bug: * - * A normal JComboBox has a problem (feature?) - * that if you have a dialog with a button - * and JComboBox and you edit the comboText field and - * then hit the button, the button sometimes does not work. + *

+ * A normal JComboBox has a problem (feature?) that if you have a dialog with a button and + * JComboBox and you edit the comboText field and then hit the button, the button sometimes does + * not work. * - * When the combobox loses focus, - * and its text has changed, it generates an actionPerformed event as - * though the user pressed <Enter> in the combo text field. This - * has a bizarre effect if you have added an actionPerformed listener - * to the combobox and in your callback you adjust the enablement state - * of the button that you pressed (which caused the text field to lose - * focus) in that you end up changing the button's internal state(by calling - * setEnabled(true or false)) in the middle of the button press. + *

+ * When the combobox loses focus, and its text has changed, it generates an actionPerformed event + * as though the user pressed <Enter> in the combo text field. This has a bizarre effect if + * you have added an actionPerformed listener to the combobox and in your callback you adjust the + * enablement state of the button that you pressed (which caused the text field to lose focus) in + * that you end up changing the button's internal state(by calling setEnabled(true or false)) in + * the middle of the button press. + * + * @param the item type */ public class GhidraComboBox extends JComboBox implements GComponent { private ArrayList listeners = new ArrayList<>(); @@ -74,17 +76,16 @@ public class GhidraComboBox extends JComboBox implements GComponent { /** * Construct a new GhidraComboBox using the given model. - * @see javax.swing.JComboBox#JComboBox(ComboBoxModel) + * @param model the model */ - public GhidraComboBox(ComboBoxModel aModel) { - super(aModel); + public GhidraComboBox(ComboBoxModel model) { + super(model); init(); } /** - * Construct a new GhidraComboBox and populate a default model - * with the given items. - * @see javax.swing.JComboBox#JComboBox(Object[]) + * Construct a new GhidraComboBox and populate a default model with the given items. + * @param items the items */ public GhidraComboBox(E[] items) { super(items); @@ -92,12 +93,11 @@ public class GhidraComboBox extends JComboBox implements GComponent { } /** - * Construct a new GhidraComboBox and populate a default model with - * the given Vector of items. - * @see javax.swing.JComboBox#JComboBox(Vector) + * Construct a new GhidraComboBox and populate a default model with the given items. + * @param items the items */ - public GhidraComboBox(Vector items) { - super(items); + public GhidraComboBox(Collection items) { + super(new Vector<>(items)); init(); } @@ -155,16 +155,16 @@ public class GhidraComboBox extends JComboBox implements GComponent { } /** - * HACK ALERT: By default, the JComboBoxUI forwards the <Enter> key actions to the root pane - * of the JComboBox's container (which is used primarily by any installed 'default button'). - * The problem is that the forwarding does not happen always. In the case that the <Enter> - * key will trigger a selection in the combo box, the action is NOT forwarded. + * HACK ALERT: By default, the JComboBoxUI forwards the <Enter> key actions to the root + * pane of the JComboBox's container (which is used primarily by any installed 'default + * button'). The problem is that the forwarding does not happen always. In the case that the + * <Enter> key will trigger a selection in the combo box, the action is NOT forwarded. *

- * 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. *

* To re-enable the default behavior, set the forwardEnter value to true. - * + * * @param forwardEnter true to enable default <Enter> key handling. */ public void setEnterKeyForwarding(boolean forwardEnter) { @@ -211,14 +211,17 @@ public class GhidraComboBox extends JComboBox implements GComponent { /** * A fix for the following series of events: - * -The user selects an item - * -The user deletes the text - * -setSelectedItem(Object) method is called with the same item + *

    + *
  1. The user selects an item
  2. + *
  3. The user deletes the text
  4. + *
  5. setSelectedItem(Object) method is called with the same item
  6. + *
* * 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 - * item is already selected. + * item is already selected. * + *

* This method exists to make sure, in that case, that the text of the field matches the * selected item. */ @@ -243,9 +246,6 @@ public class GhidraComboBox extends JComboBox implements GComponent { } } - /** - * Remove all entries in the drop down list - */ public void clearModel() { DefaultComboBoxModel model = (DefaultComboBoxModel) getModel(); model.removeAllElements(); @@ -325,15 +325,15 @@ public class GhidraComboBox extends JComboBox 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 { private boolean automated = false; /** - * Called before new user input is inserted into the entry text field. The super - * method is called if the input is accepted. + * Called before new user input is inserted into the entry text field. The super method is + * called if the input is accepted. */ @Override public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/FindDialogTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/FindDialogTest.java new file mode 100644 index 0000000000..317f7ddf86 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/FindDialogTest.java @@ -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; + } + + } +}