Merge remote-tracking branch

'origin/GP-1685-dragonmacher-decompiler-find-text' into patch (Closes
#3890)
This commit is contained in:
ghidra1 2022-01-21 11:56:31 -05:00
commit ebc9a89860
7 changed files with 161 additions and 78 deletions

View file

@ -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<AnnotationHandler> 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<AnnotationHandler>(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;
}
}

View file

@ -285,7 +285,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private Component buildFormatChooser() {
List<Exporter> exporters = getApplicableExporters();
comboBox = new GhidraComboBox<>(new Vector<>(exporters));
comboBox = new GhidraComboBox<>(exporters);
Exporter defaultExporter = getDefaultExporter(exporters);
if (defaultExporter != null) {

View file

@ -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(

View file

@ -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;
}

View file

@ -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<String> history) {
history.forEach(comboBox::addToModel);
}
private void storeSearchText(String text) {

View file

@ -31,30 +31,32 @@ import docking.widgets.GComponent;
/**
* GhidraComboBox adds the following features:
*
* 1) ActionListeners are only invoked when the &lt;Enter&gt; 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.
* <p>
* 1) ActionListeners are only invoked when the &lt;Enter&gt; 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.
* <p>
* 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.
*
* <p>
* 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.
* <p>
* 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 &lt;Enter&gt; 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.
* <p>
* When the combobox loses focus, and its text has changed, it generates an actionPerformed event
* as though the user pressed &lt;Enter&gt; 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 <E> the item type
*/
public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
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.
* @see javax.swing.JComboBox#JComboBox(ComboBoxModel)
* @param model the model
*/
public GhidraComboBox(ComboBoxModel<E> aModel) {
super(aModel);
public GhidraComboBox(ComboBoxModel<E> 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<E> extends JComboBox<E> 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<E> items) {
super(items);
public GhidraComboBox(Collection<E> items) {
super(new Vector<>(items));
init();
}
@ -155,16 +155,16 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
}
/**
* HACK ALERT: By default, the JComboBoxUI forwards the &lt;Enter&gt; 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 &lt;Enter&gt;
* key will trigger a selection in the combo box, the action is NOT forwarded.
* HACK ALERT: By default, the JComboBoxUI forwards the &lt;Enter&gt; 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
* &lt;Enter&gt; key will trigger a selection in the combo box, the action is NOT forwarded.
* <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 &lt;Enter&gt; actions.
* <p>
* To re-enable the default behavior, set the <code>forwardEnter</code> value to true.
*
*
* @param forwardEnter true to enable default &lt;Enter&gt; key handling.
*/
public void setEnterKeyForwarding(boolean forwardEnter) {
@ -211,14 +211,17 @@ public class GhidraComboBox<E> extends JComboBox<E> 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
* <ol>
* <li>The user selects an item</li>
* <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*
* 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
* 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() {
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
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 {
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 {

View file

@ -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;
}
}
}