From 5201b9563ac813022b29d21e41a07da968d22368 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 30 Sep 2022 19:36:00 -0400 Subject: [PATCH] GP-2616 - fixed NPE in text field --- .../autocomplete/TextFieldAutocompleter.java | 122 ++++++++++-------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java index fef873e6a6..d45552dfef 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java @@ -36,7 +36,7 @@ import ghidra.util.task.SwingUpdateManager; /** * An autocompleter that may be attached to one or more {@link JTextField}. - * + * *

* Each autocompleter instance has one associated window (displaying the list of suggestions) and * one associated model (generating the list of suggestions). Thus, the list can only be active on @@ -44,7 +44,7 @@ import ghidra.util.task.SwingUpdateManager; * for one autocompleter to be reused on many fields. Behavior is undefined when multiple * autocompleters are attached to the same text field. More likely, you should implement a composite * model if you wish to present completions from multiple models on a single text field. - * + * *

* By default, the autocompleter is activated when the user presses CTRL-SPACE, at which point, the * model is queried for possible suggestions. The completer gives the model all the text preceding @@ -56,7 +56,7 @@ import ghidra.util.task.SwingUpdateManager; * positioning behavior can be modified by overriding the {@link #getCompletionWindowPosition()} * method. As a convenience, the {@link #getCaretPositionOnScreen(JTextField)} method is available * to compute the default position. - * + * *

* Whether or not the list is currently displayed, when the user presses CTRL-SPACE, if only one * completion is possible, it is automatically activated. This logic is applied again and again, @@ -65,20 +65,20 @@ import ghidra.util.task.SwingUpdateManager; * by overriding the {@link #getCompletionCanDefault(Object) getCompletionCanDefault(T)} method. * This same behavior can be activated by calling the {@link #startCompletion(JTextField)} method, * which may be useful, e.g., to bind a different key sequence to start autocompletion. - * + * *

* The appearance of each item in the suggestion list can be modified by overriding the various * {@code getCompletion...} methods. Note that it's possible for an item to be displayed one way, * but cause the insertion of different text. In any case, it is best to ensure any modification * produces an intuitive behavior. - * + * *

* The simplest use case is to create a text field, create an autocompleter with a custom model, and * then attach and show. - * + * *

  * JTextField field = new JTextField();
- * 
+ *
  * AutocompletionModel model = new AutocompletionModel() {
  *     @Override
  *     public Collection computeCompletions(String text) {
@@ -88,9 +88,9 @@ import ghidra.util.task.SwingUpdateManager;
  * TextFieldAutocompleter completer = new TextFieldAutocompleter(model);
  * completer.attachTo(field);
  * // ... Add the field to, e.g., a dialog, and show.
- * 
+ *
  * 
- * + * * @param the type of suggestions presented by this autocompleter. */ public class TextFieldAutocompleter { @@ -267,7 +267,7 @@ public class TextFieldAutocompleter { /** * Create a new autocompleter associated with the given model. - * + * * @param model the model giving the suggestions. */ public TextFieldAutocompleter(AutocompletionModel model) { @@ -276,7 +276,7 @@ public class TextFieldAutocompleter { /** * Recompute the display location and move with list window. - * + * *

* This is useful, e.g., when the window containing the associated text field(s) moves. */ @@ -288,7 +288,7 @@ public class TextFieldAutocompleter { /** * Update the contents of the suggestion list. - * + * *

* This entails taking the prefix, querying the model, and rendering the list. */ @@ -336,6 +336,11 @@ public class TextFieldAutocompleter { * Build the completion window, parented to the attached field that last had focus. */ protected void buildCompletionWindow() { + if (focus == null) { + // shouldn't happen; likely some off focus issue + return; + } + completionWindow = new JWindow(WindowUtilities.windowForComponent(focus)); completionWindow.add(content); content.setVisible(true); @@ -352,7 +357,7 @@ public class TextFieldAutocompleter { /** * Show or hide the completion list window - * + * * @param visible true to show, false to hide */ public void setCompletionListVisible(boolean visible) { @@ -373,10 +378,10 @@ public class TextFieldAutocompleter { /** * Check if the completion list window is visible. - * + * *

* If it is visible, this implies that the user is actively using the autocompleter. - * + * * @return true if shown, false if hidden. */ public boolean isCompletionListVisible() { @@ -388,12 +393,14 @@ public class TextFieldAutocompleter { */ private void doUpdateDisplayLocation() { Point p = getCompletionWindowPosition(); - completionWindow.setLocation(p); + if (p != null) { + completionWindow.setLocation(p); + } } /** * Gets the prefix from the given text field, used to query the model. - * + * * @param field an attached field, usually the one with focus. * @return the prefix to use as the query. */ @@ -408,13 +415,13 @@ public class TextFieldAutocompleter { /** * Get the preferred location (on screen) of the completion list window. - * + * *

* Typically, this is a location near the focused field. Ideally, it is positioned such that the * displayed suggestions coincide with the applicable text in the focused field. For example, if * the suggestions display some portion of the prefix, the window could be positioned such that * the portion in the suggestion appears directly below the same portion in the field. - * + * * @return the point giving the top-left corner of the completion window */ protected Point getCompletionWindowPosition() { @@ -423,24 +430,31 @@ public class TextFieldAutocompleter { /** * Get the preferred dimensions of the completion list window. - * + * *

* Typically, this is the width of the focused field. - * + * * @return the dimension giving the preferred height and width. A value can be -1 to indicate no * preference. */ protected Dimension getDefaultCompletionWindowDimension() { + if (focus == null) { + return new Dimension(-1, -1); + } return new Dimension(focus.getWidth(), -1); } /** * A convenience function that returns the bottom on-screen position of the given field's caret. - * + * * @param field the field, typically the one having focus - * @return the on-screen position of the caret's bottom. + * @return the on-screen position of the caret's bottom; null if the given field is null */ protected Point getCaretPositionOnScreen(JTextField field) { + if (focus == null) { + return null; + } + FontMetrics metrics = field.getFontMetrics(field.getFont()); Caret c = field.getCaret(); Point p = c.getMagicCaretPosition(); // returns a shared reference @@ -457,14 +471,14 @@ public class TextFieldAutocompleter { /** * Builds the list cell renderer for the autocompletion list. - * + * *

* A programmer may override this if the various {@code getCompletion...} methods prove * insufficient for customizing the display of the suggestions. Please remember that * {@link JLabel}s can render HTML, so {@link #getCompletionDisplay(Object) * getCompletionDisplay(T)} is quite powerful with the default * {@link AutocompletionCellRenderer}. - * + * * @return a list cell renderer for the completion list. */ protected ListCellRenderer buildListCellRenderer() { @@ -473,10 +487,10 @@ public class TextFieldAutocompleter { /** * Attach the autocompleter to the given text field. - * + * *

* If this method is never called, then the autocompleter can never appear. - * + * * @param field the field that will gain this autocompletion feature * @return true, if this field is not already attached */ @@ -502,7 +516,7 @@ public class TextFieldAutocompleter { /** * Deprive the given field of this autocompleter. - * + * * @param field the field that will lose this autocompletion feature * @return true, if this field was actually attached */ @@ -520,7 +534,7 @@ public class TextFieldAutocompleter { /** * Cause the currently-selected suggestion to be activated. - * + * *

* By default, this is called when the user presses ENTER or clicks a suggestion. */ @@ -535,12 +549,12 @@ public class TextFieldAutocompleter { /** * Fire the registered autocompletion listeners on the given event. - * + * *

* Each registered listener is invoked in order of registration. If any listener consumes the * event, then later-registered listeners will not be notified of the event. If any listener * cancels the event, then the suggested text will not be inserted. - * + * * @param ev the event * @return true, if no listener cancelled the event */ @@ -555,14 +569,18 @@ public class TextFieldAutocompleter { } private void completionActivated(T sel) { + if (focus == null) { + // shouldn't happen; likely some off focus issue + return; + } + AutocompletionEvent ev = new AutocompletionEvent<>(sel, focus); if (!fireAutocompletionListeners(ev)) { return; } try { focus.getDocument() - .insertString(focus.getCaretPosition(), getCompletionText(sel), - null); + .insertString(focus.getCaretPosition(), getCompletionText(sel), null); } catch (BadLocationException e) { throw new AssertionError("INTERNAL: Should not be here", e); @@ -571,7 +589,7 @@ public class TextFieldAutocompleter { /** * Register the given auto-completion listener - * + * * @param l the listener to register */ public void addAutocompletionListener(AutocompletionListener l) { @@ -580,7 +598,7 @@ public class TextFieldAutocompleter { /** * Unregister the given auto-completion listener - * + * * @param l the listener to unregister */ public void removeAutocompletionListener(AutocompletionListener l) { @@ -589,7 +607,7 @@ public class TextFieldAutocompleter { /** * Get all the registered auto-completion listeners - * + * * @return an array of registered listeners */ @SuppressWarnings("unchecked") @@ -599,7 +617,7 @@ public class TextFieldAutocompleter { /** * Get all registered listeners of the given type - * + * * @param listenerType the type of listeners to get * @return an array of registered listeners */ @@ -613,7 +631,7 @@ public class TextFieldAutocompleter { /** * Get the text to insert when the given suggestion is activated - * + * * @param sel the activated suggestion * @return the text to insert */ @@ -623,7 +641,7 @@ public class TextFieldAutocompleter { /** * Get the (possibly HTML) text to display for the given suggestion in the list - * + * * @param sel the suggestion to display * @return the text or HTML representing the suggestion */ @@ -633,7 +651,7 @@ public class TextFieldAutocompleter { /** * Get the foreground color to display for the given suggestion in the list - * + * * @param sel the suggestion to display * @param isSelected true if the suggestion is currently selected * @param cellHasFocus true if the suggestion currently has focus @@ -645,7 +663,7 @@ public class TextFieldAutocompleter { /** * Get the background color to display for the given suggestion in the list - * + * * @param sel the suggestion to display * @param isSelected true if the suggestion is currently selected * @param cellHasFocus true if the suggestion currently has focus @@ -657,7 +675,7 @@ public class TextFieldAutocompleter { /** * Get the icon to display with the given suggestion in the list - * + * * @param sel the suggestion to display * @param isSelected true if the suggestion is currently selected * @param cellHasFocus true if the suggestion currently has focus @@ -669,7 +687,7 @@ public class TextFieldAutocompleter { /** * Get the font for the given suggestion in the list - * + * * @param sel the suggestion to display * @param isSelected true if the suggestion is currently selected * @param cellHasFocus true if the suggestion currently has focus @@ -684,7 +702,7 @@ public class TextFieldAutocompleter { /** * Decide whether the given suggestion can be automatically activated. - * + * *

* When autocompletion is started (via {@link #startCompletion(JTextField)}) or when the user * presses CTRL-SPACE, if there is only a single suggestion, it is taken automatically, and the @@ -692,7 +710,7 @@ public class TextFieldAutocompleter { * it calls this method. If it returns false, the single suggestion is displayed in a 1-long * list instead. This is useful to prevent consequential actions from being automatically * activated by the autocompleter. - * + * * @param sel the potentially auto-activated suggestion. * @return true to permit auto-activation, false to prevent it. */ @@ -702,17 +720,17 @@ public class TextFieldAutocompleter { /** * Starts the autocompleter on the given text field. - * + * *

* First, this repeatedly attempts auto-activation. When there are many suggestions, or when * auto-activation is prevented (see {@link #getCompletionCanDefault(Object) * getCompletionCanDefault(T)}), a list is displayed (usually below the caret) containing the * suggestions given the fields current contents. The list remains open until either the user * cancels it (usually via ESC) or the user activates a suggestion. - * + * *

* NOTE: The text field must already be attached. - * + * * @param field the field on which to start autocompletion. */ public void startCompletion(JTextField field) { @@ -765,7 +783,7 @@ public class TextFieldAutocompleter { /** * Cause the suggestion at the given index to be selected - * + * * @param index the index of the selection */ public void select(int index) { @@ -852,7 +870,7 @@ public class TextFieldAutocompleter { /** * Get the list of suggestions as ordered on screen - * + * * @return an immutable copy of the list */ public List getSuggestions() { @@ -1012,7 +1030,7 @@ public class TextFieldAutocompleter { /** * A demonstration of the autocompleter on a single text field. - * + * *

* The autocompleter offers the tails from a list of strings that start with the text before the * caret. @@ -1056,7 +1074,7 @@ public class TextFieldAutocompleter { /** * A demonstration of the autocompleter on two linked text fields. - * + * *

* This demo was designed to test whether the autocompleter and the {@link TextFieldLinker} * could be composed correctly.