From 6707be3768011e470c27c5cf8a4924b00c969ba7 Mon Sep 17 00:00:00 2001 From: bobbylight Date: Sun, 16 Aug 2026 19:33:52 -0400 Subject: [PATCH] Add DescWindowVisibility (ALWAYS/ON_DEMAND/NEVER) for the description popup Replaces the boolean setShowDescWindow()/getShowDescWindow() API with a DescWindowVisibility enum, adding an ON_DEMAND mode where the description window is only shown when explicitly toggled. The toggle keystroke is customizable via the new setDescWindowToggleKey()/getDescWindowToggleKey() wrapper around the text component's InputMap/ActionMap. Also fixes a bug where a disposed description window could be silently resurrected by the JDK: java.awt.Window#hide() cascades to owned windows still visible at the moment their owner is hidden, hiding them too and flagging them (via a package-private showWithParent field) to be automatically re-shown the next time the owner is shown again - even if that owned window was dispose()d in the meantime. Since AutoCompletePopupWindow hid itself before explicitly hiding the description window, a description window that was visible when the choices popup lost focus would get this flag set; disposing it afterward (e.g. by switching to NEVER) didn't clear it, so the next time the choices popup reopened, the JDK silently recreated the disposed window's native peer and showed it again with stale content, bypassing all of our own visibility logic. Fixed by hiding the description window before hiding the choices popup, so it's already invisible by the time the JDK's cascade runs. Co-Authored-By: Claude Sonnet 5 --- .../autocomplete/AutoCompletePopupWindow.java | 142 +++++++++- .../fife/ui/autocomplete/AutoCompletion.java | 88 ++++-- .../ui/autocomplete/DescWindowVisibility.java | 38 +++ .../ui/autocomplete/AutoCompletionTest.java | 265 +++++++++++++++++- .../ui/autocomplete/demo/DemoRootPane.java | 4 +- config/checkstyle/checkstyle.xml | 2 +- gradle.properties | 2 +- 7 files changed, 514 insertions(+), 27 deletions(-) create mode 100644 AutoComplete/src/main/java/org/fife/ui/autocomplete/DescWindowVisibility.java diff --git a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java index 3876266..3d3d21d 100644 --- a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java +++ b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java @@ -74,6 +74,14 @@ class AutoCompletePopupWindow extends JWindow implements CaretListener, */ private AutoCompleteDescWindow descWindow; + /** + * Whether the description window is currently toggled "on" when + * {@link AutoCompletion#getDescWindowVisibility()} is + * {@link DescWindowVisibility#ON_DEMAND}. Ignored for other visibility + * settings. + */ + private boolean descWindowVisibleOnDemand; + /** * The preferred size of the optional description window. This field * only exists because the user may (and usually will) set the size of @@ -271,6 +279,115 @@ protected void doAutocomplete() { } + /** + * Returns whether the description window should currently be displayed, + * per {@link AutoCompletion#getDescWindowVisibility()}. + * + * @return Whether the description window should currently be displayed. + * @see #toggleDescriptionWindow() + */ + private boolean shouldShowDescWindow() { + switch (ac.getDescWindowVisibility()) { + case ALWAYS: + return true; + case ON_DEMAND: + return descWindowVisibleOnDemand; + case NEVER: + default: + return false; + } + } + + + /** + * Toggles whether the description window is displayed, when + * {@link AutoCompletion#getDescWindowVisibility()} is + * {@link DescWindowVisibility#ON_DEMAND}. Does nothing otherwise, or if + * this popup window is not currently visible. + */ + void toggleDescriptionWindow() { + + if (ac.getDescWindowVisibility() != DescWindowVisibility.ON_DEMAND || !isVisible()) { + return; + } + + descWindowVisibleOnDemand = !descWindowVisibleOnDemand; + + if (descWindowVisibleOnDemand) { + if (descWindow == null) { + descWindow = createDescriptionWindow(); + } + Completion c = list.getSelectedValue(); + if (c != null) { + descWindow.setDescriptionFor(c); + } + positionDescWindow(); + descWindow.setVisible(true); + } + else if (descWindow != null) { + descWindow.setVisible(false); + } + + } + + + /** + * The key used in the input map for the description window toggle action. + */ + private static final String DESC_WINDOW_TOGGLE_KEY = "AutoCompletion.ToggleDescWindow"; + + + /** + * Installs a "description window toggle key" action onto a text component. + * + * @param ac The auto-completion instance the text component is installed on. + * @param tc The text component. + * @param ks The keystroke that should toggle the description window's visibility. + * @see #uninstallDescWindowToggleKey(JTextComponent, KeyStroke) + */ + static void installDescWindowToggleKey(AutoCompletion ac, JTextComponent tc, KeyStroke ks) { + InputMap im = tc.getInputMap(); + im.put(ks, DESC_WINDOW_TOGGLE_KEY); + ActionMap am = tc.getActionMap(); + am.put(DESC_WINDOW_TOGGLE_KEY, new ToggleDescWindowAction(ac)); + } + + + /** + * Removes a previously-installed "description window toggle key" action from a text component. + * + * @param tc The text component. + * @param ks The keystroke previously passed to {@link #installDescWindowToggleKey}. + */ + static void uninstallDescWindowToggleKey(JTextComponent tc, KeyStroke ks) { + tc.getInputMap().remove(ks); + tc.getActionMap().remove(DESC_WINDOW_TOGGLE_KEY); + } + + + /** + * Toggles the description window's visibility when triggered while {@link DescWindowVisibility#ON_DEMAND} + * is active; a no-op otherwise. + */ + private static final class ToggleDescWindowAction extends AbstractAction { + + private final AutoCompletion ac; + + ToggleDescWindowAction(AutoCompletion ac) { + this.ac = ac; + } + + @Override + public void actionPerformed(ActionEvent e) { + AutoCompletePopupWindow popupWindow = ac.getPopupWindow(); + if (popupWindow != null) { + popupWindow.toggleDescriptionWindow(); + } + } + + } + + /** * Returns the copy keystroke to use for this platform. * @@ -316,7 +433,7 @@ AutoCompleteDescWindow getDescWindow() { * that never gets un-mapped or repainted. Disposing of the native peer * avoids that. * - * @see AutoCompletion#setShowDescWindow(boolean) + * @see AutoCompletion#setDescWindowVisibility(DescWindowVisibility) */ void disposeDescWindow() { if (descWindow != null) { @@ -452,7 +569,7 @@ public void mouseReleased(MouseEvent e) { */ private void positionDescWindow() { - boolean showDescWindow = descWindow!=null && ac.getShowDescWindow(); + boolean showDescWindow = descWindow!=null && shouldShowDescWindow(); if (!showDescWindow) { return; } @@ -703,7 +820,7 @@ public void setLocationRelativeTo(Rectangle r) { Rectangle screenBounds = Util.getScreenBoundsForPoint(r.x, r.y); //Dimension screenSize = getToolkit().getScreenSize(); - boolean showDescWindow = descWindow!=null && ac.getShowDescWindow(); + boolean showDescWindow = descWindow!=null && shouldShowDescWindow(); int totalH = getHeight(); if (showDescWindow) { totalH = Math.max(totalH, descWindow.getHeight()); @@ -755,7 +872,9 @@ public void setVisible(boolean visible) { installKeyBindings(); lastLine = ac.getLineOfCaret(); selectFirstItem(); - if (descWindow==null && ac.getShowDescWindow()) { + // ON_DEMAND starts back off each time the popup is (re)shown. + descWindowVisibleOnDemand = false; + if (descWindow==null && shouldShowDescWindow()) { descWindow = createDescriptionWindow(); positionDescWindow(); } @@ -771,6 +890,19 @@ public void setVisible(boolean visible) { } else { uninstallKeyBindings(); + // Explicitly hide the desc window *before* hiding ourselves. + // java.awt.Window#hide() cascades to any owned window that is + // still visible at that moment, hiding it too and flagging it + // to be automatically re-shown (via Window#show()'s internal + // "showWithParent" bookkeeping) the next time we're shown + // again - even if that desc window gets disposed in the + // meantime. Hiding it first ensures it's already invisible + // when our own super.setVisible(false) cascades below, so the + // JDK never sets that flag and can't resurrect a disposed + // desc window behind our back. + if (descWindow != null) { + descWindow.setVisible(false); + } } super.setVisible(visible); @@ -797,7 +929,7 @@ public void setVisible(boolean visible) { // because of the way child JWindows' visibility is handled - in // some ways it's dependent on the parent, in other ways it's not. if (descWindow!=null) { - descWindow.setVisible(visible && ac.getShowDescWindow()); + descWindow.setVisible(visible && shouldShowDescWindow()); } } diff --git a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java index a2a7032..318741e 100644 --- a/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java +++ b/AutoComplete/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java @@ -107,10 +107,10 @@ public class AutoCompletion { private static LinkRedirector linkRedirector; /** - * Whether the description window should be displayed along with the + * Whether/when the description window should be displayed along with the * completion choice window. */ - private boolean showDescWindow; + private DescWindowVisibility descWindowVisibility; /** * Whether auto-complete is enabled. @@ -176,6 +176,12 @@ public class AutoCompletion { */ private Action oldParenAction; + /** + * The keystroke that toggles the description window's visibility in + * {@link DescWindowVisibility#ON_DEMAND} mode, or null. + */ + private KeyStroke descWindowToggleKey; + /** * Listens for events in the parent window that affect the visibility of the * popup windows. @@ -268,7 +274,8 @@ public AutoCompletion(CompletionProvider provider) { setAutoCompleteEnabled(true); setAutoCompleteSingleChoices(true); setAutoActivationEnabled(false); - setShowDescWindow(false); + setDescWindowVisibility(DescWindowVisibility.NEVER); + setDescWindowToggleKey(getDefaultDescWindowToggleKey()); setHideOnCompletionProviderChange(true); setHideOnNoText(true); setParameterDescriptionTruncateThreshold(300); @@ -384,6 +391,18 @@ public static KeyStroke getDefaultTriggerKey() { } + /** + * Returns the default desc window toggle keystroke ({@link DescWindowVisibility#ON_DEMAND}). + * + * @return The default keystroke. + * @see #setDescWindowToggleKey(KeyStroke) + */ + public static KeyStroke getDefaultDescWindowToggleKey() { + int mask = InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK; + return KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, mask); + } + + /** * Returns the handler to use when an external URL is clicked in the * description window. @@ -462,14 +481,24 @@ protected String getReplacementText(Completion c, Document doc, int start, /** - * Returns whether the "description window" should be shown alongside the - * completion window. + * Returns whether/when the "description window" should be shown alongside the completion choices window. * - * @return Whether the description window should be shown. - * @see #setShowDescWindow(boolean) + * @return Whether/when the description window should be shown. + * @see #setDescWindowVisibility(DescWindowVisibility) */ - public boolean getShowDescWindow() { - return showDescWindow; + public DescWindowVisibility getDescWindowVisibility() { + return descWindowVisibility; + } + + + /** + * Returns the desc window toggle keystroke ({@link DescWindowVisibility#ON_DEMAND}). + * + * @return The keystroke, or null if none is installed. + * @see #setDescWindowToggleKey(KeyStroke) + */ + public KeyStroke getDescWindowToggleKey() { + return descWindowToggleKey; } @@ -665,6 +694,9 @@ public void install(JTextComponent c) { this.textComponent = c; installTriggerKey(getTriggerKey()); + if (descWindowToggleKey != null) { + AutoCompletePopupWindow.installDescWindowToggleKey(this, textComponent, descWindowToggleKey); + } // Install the function completion key, if there is one. // NOTE: We cannot do this if the start char is ' ' (e.g. just a space @@ -1148,20 +1180,20 @@ protected void setPopupVisible(boolean visible) { /** - * Sets whether the "description window" should be shown beside the - * completion window. + * Sets whether/when the "description window" should be shown beside the completion choices window. * - * @param show Whether to show the description window. - * @see #getShowDescWindow() + * @param visibility Whether/when to show the description window. + * @see #getDescWindowVisibility() */ - public void setShowDescWindow(boolean show) { + public void setDescWindowVisibility(DescWindowVisibility visibility) { + Objects.requireNonNull(visibility, "visibility cannot be null"); hidePopupWindow(); // Needed to force it to take effect - if (!show && popupWindow != null) { + if (visibility != DescWindowVisibility.ALWAYS && popupWindow != null) { // Dispose (rather than hide) the desc window on toggle-off, to avoid a // Linux/X11 "ghost" window bug when hiding it instead; see issue #84. popupWindow.disposeDescWindow(); } - showDescWindow = show; + descWindowVisibility = visibility; } @@ -1186,6 +1218,27 @@ public void setTriggerKey(KeyStroke ks) { } + /** + * Sets the desc window toggle keystroke ({@link DescWindowVisibility#ON_DEMAND}). + * + * @param ks The keystroke, or {@code null} to remove any previously installed toggle keystroke. + * @see #getDescWindowToggleKey() + */ + public void setDescWindowToggleKey(KeyStroke ks) { + if (!Objects.equals(ks, descWindowToggleKey)) { + if (textComponent != null) { + if (descWindowToggleKey != null) { + AutoCompletePopupWindow.uninstallDescWindowToggleKey(textComponent, descWindowToggleKey); + } + if (ks != null) { + AutoCompletePopupWindow.installDescWindowToggleKey(this, textComponent, ks); + } + } + descWindowToggleKey = ks; + } + } + + /** * Displays a "tool tip" detailing the inputs to the function just entered. * @@ -1238,6 +1291,9 @@ public void uninstall() { hidePopupWindow(); // Unregisters listeners, actions, etc. uninstallTriggerKey(); + if (descWindowToggleKey != null) { + AutoCompletePopupWindow.uninstallDescWindowToggleKey(textComponent, descWindowToggleKey); + } // Uninstall the function completion key. char start = provider.getParameterListStart(); diff --git a/AutoComplete/src/main/java/org/fife/ui/autocomplete/DescWindowVisibility.java b/AutoComplete/src/main/java/org/fife/ui/autocomplete/DescWindowVisibility.java new file mode 100644 index 0000000..575a191 --- /dev/null +++ b/AutoComplete/src/main/java/org/fife/ui/autocomplete/DescWindowVisibility.java @@ -0,0 +1,38 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE.md file for details. + */ +package org.fife.ui.autocomplete; + + +/** + * Controls when the "description" window (the popup that shows documentation + * for the currently selected completion choice) is displayed alongside the + * completion choices window. + * + * @author Robert Futrell + * @version 1.0 + * @see AutoCompletion#setDescWindowVisibility(DescWindowVisibility) + */ +public enum DescWindowVisibility { + + /** + * The description window is shown automatically whenever the completion + * choices window is showing and a description is available. This is the + * default (legacy) behavior. + */ + ALWAYS, + + /** + * The description window is only shown when the user explicitly requests + * it, via the keystroke configured by + * {@link AutoCompletion#setDescWindowToggleKey(javax.swing.KeyStroke)}. + */ + ON_DEMAND, + + /** + * The description window is never shown. + */ + NEVER + +} diff --git a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java index 003fffb..ad7d098 100644 --- a/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java +++ b/AutoComplete/src/test/java/org/fife/ui/autocomplete/AutoCompletionTest.java @@ -5,8 +5,13 @@ package org.fife.ui.autocomplete; import java.awt.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; import javax.swing.JFrame; import javax.swing.JTextArea; +import javax.swing.KeyStroke; import javax.swing.text.DefaultCaret; import javax.swing.text.JTextComponent; @@ -55,7 +60,7 @@ void setShowDescWindow_false_disposesExistingDescWindow() { provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); AutoCompletion ac = new AutoCompletion(provider); - ac.setShowDescWindow(true); + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); ac.setAutoCompleteEnabled(true); JTextArea textArea = new JTextArea(); @@ -79,7 +84,7 @@ void setShowDescWindow_false_disposesExistingDescWindow() { Assertions.assertTrue(descWindow.isDisplayable(), "Description window's native peer should exist while showing"); - ac.setShowDescWindow(false); + ac.setDescWindowVisibility(DescWindowVisibility.NEVER); Assertions.assertNull(popupWindow.getDescWindow(), "Description window should be disposed and discarded, not just hidden"); @@ -89,6 +94,262 @@ void setShowDescWindow_false_disposesExistingDescWindow() { } + @Test + void getShowDescWindow_defaultsToNever() { + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + Assertions.assertEquals(DescWindowVisibility.NEVER, ac.getDescWindowVisibility()); + } + + + /** + * Regression test for a bug where a disposed description window could be + * silently resurrected by the JDK. {@code java.awt.Window#hide()} has an + * internal cascade: any owned window that's still visible when its owner + * is hidden gets hidden too and flagged (via a package-private + * {@code showWithParent} field) to be automatically re-shown - by calling + * {@code show()} directly, bypassing all of our code - the next time the + * owner is shown again, even if that owned window was disposed of in the + * meantime. If the description window is still visible at the moment the + * choices popup is hidden (e.g. the text component loses focus), that + * flag gets set; disposing the description window afterward (by + * switching to {@code NEVER}) doesn't clear it, so the next time the + * choices popup reopens, the JDK recreates the disposed window's native + * peer and shows it again with its last (now stale) content - even + * though {@code AutoCompletePopupWindow}'s own {@code descWindow} field + * is {@code null}. The fix hides the description window *before* hiding + * the choices popup, so it's already invisible by the time the JDK's + * cascade runs and never sets that flag. + */ + @Test + void showDescWindow_disposedWhileHidden_doesNotReappearOnNextShow() { + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.setAutoCompleteEnabled(true); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + AutoCompletePopupWindow popupWindow = ac.getPopupWindow(); + AutoCompleteDescWindow descWindow = popupWindow.getDescWindow(); + Assertions.assertNotNull(descWindow); + Assertions.assertTrue(descWindow.isVisible()); + + // Hide the choices popup while the desc window is still visible, as + // happens e.g. when the text component loses focus. This is what + // causes the JDK to internally flag the desc window to be + // auto-restored the next time the popup is shown again. + popupWindow.setVisible(false); + + // Now dispose the desc window entirely, as setDescWindowVisibility() + // does when switching away from ALWAYS. + ac.setDescWindowVisibility(DescWindowVisibility.NEVER); + Assertions.assertNull(popupWindow.getDescWindow()); + Assertions.assertFalse(descWindow.isDisplayable()); + + // Re-trigger completion; the choices popup reopens, but the disposed + // desc window must not be silently resurrected by the JDK. + ac.doCompletion(); + + Assertions.assertNull(popupWindow.getDescWindow(), + "No new description window should have been created (NEVER)"); + Assertions.assertFalse(descWindow.isVisible(), + "The disposed description window must not be resurrected by the JDK"); + Assertions.assertFalse(descWindow.isDisplayable(), + "The disposed description window's peer must not be silently recreated"); + + } + + + @Test + void showDescWindow_never_descWindowNeverCreated() { + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setDescWindowVisibility(DescWindowVisibility.NEVER); + ac.setAutoCompleteEnabled(true); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + AutoCompletePopupWindow popupWindow = ac.getPopupWindow(); + Assertions.assertNotNull(popupWindow, "Choices popup window should still be shown"); + Assertions.assertNull(popupWindow.getDescWindow(), + "Description window should never be created when visibility is NEVER"); + + } + + + @Test + void showDescWindow_onDemand_toggleKeyShowsAndHidesDescWindow() { + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setDescWindowVisibility(DescWindowVisibility.ON_DEMAND); + ac.setAutoCompleteEnabled(true); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + AutoCompletePopupWindow popupWindow = ac.getPopupWindow(); + Assertions.assertNotNull(popupWindow); + Assertions.assertNull(popupWindow.getDescWindow(), + "Description window should not be created until toggled on, in ON_DEMAND mode"); + + fireKeyAction(textArea, ac.getDescWindowToggleKey()); + AutoCompleteDescWindow descWindow = popupWindow.getDescWindow(); + Assertions.assertNotNull(descWindow, + "Description window should be created the first time it's toggled on"); + Assertions.assertTrue(descWindow.isVisible(), + "Description window should be visible after toggling on"); + + fireKeyAction(textArea, ac.getDescWindowToggleKey()); + Assertions.assertFalse(descWindow.isVisible(), + "Description window should be hidden after toggling back off"); + + } + + + @Test + void showDescWindow_onDemand_toggleKeyIgnoredWhenNotOnDemand() { + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); + ac.setAutoCompleteEnabled(true); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + AutoCompletePopupWindow popupWindow = ac.getPopupWindow(); + AutoCompleteDescWindow descWindow = popupWindow.getDescWindow(); + Assertions.assertNotNull(descWindow, "Description window should already be shown (ALWAYS)"); + + fireKeyAction(textArea, ac.getDescWindowToggleKey()); + Assertions.assertTrue(descWindow.isVisible(), + "Toggle key should have no effect when visibility isn't ON_DEMAND"); + + } + + + @Test + void setDescWindowToggleKey_customKeystrokeIsInstalledAndUsable() { + + DefaultCompletionProvider provider = new DefaultCompletionProvider(); + provider.addCompletion(new BasicCompletion(provider, "foo", "foo's summary")); + provider.addCompletion(new BasicCompletion(provider, "foobar", "foobar's summary")); + + AutoCompletion ac = new AutoCompletion(provider); + ac.setDescWindowVisibility(DescWindowVisibility.ON_DEMAND); + ac.setAutoCompleteEnabled(true); + + KeyStroke customKey = KeyStroke.getKeyStroke("ctrl alt D"); + ac.setDescWindowToggleKey(customKey); + + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + frame = new JFrame(); + frame.add(textArea); + frame.pack(); + frame.setVisible(true); + + textArea.setText("foo"); + textArea.setCaretPosition(textArea.getText().length()); + ac.doCompletion(); + + AutoCompletePopupWindow popupWindow = ac.getPopupWindow(); + Assertions.assertNull(popupWindow.getDescWindow()); + + fireKeyAction(textArea, customKey); + Assertions.assertNotNull(popupWindow.getDescWindow(), + "Description window should toggle on via the custom keystroke"); + + } + + + @Test + void setDescWindowToggleKey_null_removesInstalledKeystroke() { + + AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider()); + JTextArea textArea = new JTextArea(); + ac.install(textArea); + + KeyStroke defaultKey = AutoCompletion.getDefaultDescWindowToggleKey(); + InputMap im = textArea.getInputMap(); + Assertions.assertNotNull(im.get(defaultKey), + "Default toggle key should be installed on install()"); + + ac.setDescWindowToggleKey(null); + Assertions.assertNull(ac.getDescWindowToggleKey()); + + } + + + /** + * Fires the action bound to {@code ks} in {@code textArea}'s input/action maps, + * simulating the user pressing that keystroke. + */ + private static void fireKeyAction(JTextArea textArea, KeyStroke ks) { + InputMap im = textArea.getInputMap(); + ActionMap am = textArea.getActionMap(); + Object key = im.get(ks); + Assertions.assertNotNull(key, "No action bound to keystroke " + ks); + Action action = am.get(key); + Assertions.assertNotNull(action, "No action found for key " + key); + action.actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, "")); + } + + /** * Regression test for Issue #77 - * {@code isAutoActivateOkay()} used to look at {@code JTextComponent#getCaretPosition()} diff --git a/AutoCompleteDemo/src/main/java/org/fife/ui/autocomplete/demo/DemoRootPane.java b/AutoCompleteDemo/src/main/java/org/fife/ui/autocomplete/demo/DemoRootPane.java index 9d7889b..dc691e4 100644 --- a/AutoCompleteDemo/src/main/java/org/fife/ui/autocomplete/demo/DemoRootPane.java +++ b/AutoCompleteDemo/src/main/java/org/fife/ui/autocomplete/demo/DemoRootPane.java @@ -90,7 +90,7 @@ class DemoRootPane extends JRootPane { // Install auto-completion onto our text area. ac = new AutoCompletion(provider); ac.setListCellRenderer(new CCellRenderer()); - ac.setShowDescWindow(true); + ac.setDescWindowVisibility(DescWindowVisibility.ALWAYS); ac.setParameterAssistanceEnabled(true); ac.setAutoCompleteEnabled(true); @@ -449,7 +449,7 @@ private class ShowDescWindowAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { boolean show = showDescWindowItem.isSelected(); - ac.setShowDescWindow(show); + ac.setDescWindowVisibility(show ? DescWindowVisibility.ALWAYS : DescWindowVisibility.NEVER); } } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index b5fbb99..503627a 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -64,7 +64,7 @@ - + diff --git a/gradle.properties b/gradle.properties index 4b1635a..4f708d1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Note that Maven- and signing-related properties are in /gradle.properties javaLanguageVersion=11 -version=3.3.4-SNAPSHOT +version=4.0.0-SNAPSHOT # Ugh, see https://github.com/gradle/gradle/issues/11308 systemProp.org.gradle.internal.publish.checksums.insecure=true