|
|
@ -8,26 +8,28 @@ |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
package com.fr.design.gui.autocomplete; |
|
|
|
package com.fr.design.gui.autocomplete; |
|
|
|
|
|
|
|
|
|
|
|
import java.awt.*; |
|
|
|
|
|
|
|
import java.awt.event.*; |
|
|
|
|
|
|
|
import java.beans.PropertyChangeEvent; |
|
|
|
|
|
|
|
import java.beans.PropertyChangeListener; |
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
import javax.swing.*; |
|
|
|
import javax.swing.*; |
|
|
|
import javax.swing.event.CaretEvent; |
|
|
|
import javax.swing.event.CaretEvent; |
|
|
|
import javax.swing.event.CaretListener; |
|
|
|
import javax.swing.event.CaretListener; |
|
|
|
import javax.swing.event.DocumentEvent; |
|
|
|
import javax.swing.event.DocumentEvent; |
|
|
|
import javax.swing.event.DocumentListener; |
|
|
|
import javax.swing.event.DocumentListener; |
|
|
|
import javax.swing.text.*; |
|
|
|
import javax.swing.text.*; |
|
|
|
|
|
|
|
import java.awt.*; |
|
|
|
|
|
|
|
import java.awt.event.*; |
|
|
|
|
|
|
|
import java.beans.PropertyChangeEvent; |
|
|
|
|
|
|
|
import java.beans.PropertyChangeListener; |
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import static com.fr.design.gui.syntax.ui.rtextarea.RTADefaultInputMap.DEFAULT_MODIFIER; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Adds auto-completion to a text component. Provides a popup window with a |
|
|
|
* Adds auto-completion to a text component. Provides a popup window with a |
|
|
|
* list of auto-complete choices on a given keystroke, such as Crtrl+Space.<p> |
|
|
|
* list of auto-complete choices on a given keystroke, such as Crtrl+Space.<p> |
|
|
|
* |
|
|
|
* <p> |
|
|
|
* Depending on the {@link CompletionProvider} installed, the following |
|
|
|
* Depending on the {@link CompletionProvider} installed, the following |
|
|
|
* auto-completion features may be enabled: |
|
|
|
* auto-completion features may be enabled: |
|
|
|
* |
|
|
|
* <p> |
|
|
|
* <ul> |
|
|
|
* <ul> |
|
|
|
* <li>An auto-complete choices list made visible via e.g. Ctrl+Space</li> |
|
|
|
* <li>An auto-complete choices list made visible via e.g. Ctrl+Space</li> |
|
|
|
* <li>A "description" window displayed alongside the choices list that |
|
|
|
* <li>A "description" window displayed alongside the choices list that |
|
|
@ -212,7 +214,7 @@ public class AutoCompletion { |
|
|
|
* Stores how to render auto-completion-specific highlights in text |
|
|
|
* Stores how to render auto-completion-specific highlights in text |
|
|
|
* components. |
|
|
|
* components. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private static final AutoCompletionStyleContext styleContext = |
|
|
|
private static final AutoCompletionStyleContext STYLE_CONTEXT = |
|
|
|
new AutoCompletionStyleContext(); |
|
|
|
new AutoCompletionStyleContext(); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -275,7 +277,7 @@ public class AutoCompletion { |
|
|
|
* @return Whether to auto-complete single choices. |
|
|
|
* @return Whether to auto-complete single choices. |
|
|
|
* @see #setAutoCompleteSingleChoices(boolean) |
|
|
|
* @see #setAutoCompleteSingleChoices(boolean) |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public boolean getAutoCompleteSingleChoices() { |
|
|
|
public boolean isAutoCompleteSingleChoices() { |
|
|
|
return autoCompleteSingleChoices; |
|
|
|
return autoCompleteSingleChoices; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -295,7 +297,7 @@ public class AutoCompletion { |
|
|
|
* |
|
|
|
* |
|
|
|
* @return Whether debug is enabled. |
|
|
|
* @return Whether debug is enabled. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
static boolean getDebug() { |
|
|
|
static boolean isDebug() { |
|
|
|
return DEBUG; |
|
|
|
return DEBUG; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -308,8 +310,7 @@ public class AutoCompletion { |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static KeyStroke getDefaultTriggerKey() { |
|
|
|
public static KeyStroke getDefaultTriggerKey() { |
|
|
|
// Default to CTRL, even on Mac, since Ctrl+Space activates Spotlight
|
|
|
|
// Default to CTRL, even on Mac, since Ctrl+Space activates Spotlight
|
|
|
|
int mask = InputEvent.CTRL_MASK; |
|
|
|
return KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, DEFAULT_MODIFIER); |
|
|
|
return KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, mask); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -397,7 +398,7 @@ public class AutoCompletion { |
|
|
|
* @return Whether the description window should be shown. |
|
|
|
* @return Whether the description window should be shown. |
|
|
|
* @see #setShowDescWindow(boolean) |
|
|
|
* @see #setShowDescWindow(boolean) |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public boolean getShowDescWindow() { |
|
|
|
public boolean isShowDescWindow() { |
|
|
|
return showDescWindow; |
|
|
|
return showDescWindow; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -409,7 +410,7 @@ public class AutoCompletion { |
|
|
|
* @return The style context. |
|
|
|
* @return The style context. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static AutoCompletionStyleContext getStyleContext() { |
|
|
|
public static AutoCompletionStyleContext getStyleContext() { |
|
|
|
return styleContext; |
|
|
|
return STYLE_CONTEXT; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -680,60 +681,32 @@ public class AutoCompletion { |
|
|
|
* @return The current line number of the caret. |
|
|
|
* @return The current line number of the caret. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected int refreshPopupWindow() { |
|
|
|
protected int refreshPopupWindow() { |
|
|
|
|
|
|
|
|
|
|
|
// A return value of null => don't suggest completions
|
|
|
|
// A return value of null => don't suggest completions
|
|
|
|
String text = provider.getAlreadyEnteredText(textComponent); |
|
|
|
String text = provider.getAlreadyEnteredText(textComponent); |
|
|
|
if (text == null && !isPopupVisible()) { |
|
|
|
if (text == null && !isPopupVisible()) { |
|
|
|
return getLineOfCaret(); |
|
|
|
return getLineOfCaret(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// If the popup is currently visible, and they type a space (or any
|
|
|
|
// If the popup is currently visible, and they type a space (or any
|
|
|
|
// character that resets the completion list to "all completions"),
|
|
|
|
// character that resets the completion list to "all completions"),
|
|
|
|
// the popup window should be hidden instead of being reset to show
|
|
|
|
// the popup window should be hidden instead of being reset to show
|
|
|
|
// everything.
|
|
|
|
// everything.
|
|
|
|
int textLen = text == null ? 0 : text.length(); |
|
|
|
int textLen = text == null ? 0 : text.length(); |
|
|
|
if (textLen==0) { |
|
|
|
if (textLen == 0 && isPopupVisible()) { |
|
|
|
if (isPopupVisible()) { |
|
|
|
|
|
|
|
hidePopupWindow(); |
|
|
|
hidePopupWindow(); |
|
|
|
return getLineOfCaret(); |
|
|
|
return getLineOfCaret(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
final List<Completion> completions = provider.getCompletions(textComponent); |
|
|
|
|
|
|
|
|
|
|
|
final List<Completion> completions = provider. |
|
|
|
|
|
|
|
getCompletions(textComponent); |
|
|
|
|
|
|
|
int count = completions.size(); |
|
|
|
int count = completions.size(); |
|
|
|
|
|
|
|
if (needSetPopupWindow(count, textLen)) { |
|
|
|
if (count>1 || (count==1 && (isPopupVisible() || textLen==0)) || |
|
|
|
|
|
|
|
(count==1 && !getAutoCompleteSingleChoices())) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (popupWindow == null) { |
|
|
|
if (popupWindow == null) { |
|
|
|
popupWindow = new AutoCompletePopupWindow(parentWindow, this); |
|
|
|
popupWindow = createAutoCompletePopupWindow(); |
|
|
|
// Completion is usually done for code, which is always done
|
|
|
|
|
|
|
|
// LTR, so make completion stuff RTL only if text component is
|
|
|
|
|
|
|
|
// also RTL.
|
|
|
|
|
|
|
|
popupWindow.applyComponentOrientation( |
|
|
|
|
|
|
|
getTextComponentOrientation()); |
|
|
|
|
|
|
|
if (renderer!=null) { |
|
|
|
|
|
|
|
popupWindow.setListCellRenderer(renderer); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (preferredChoicesWindowSize!=null) { |
|
|
|
|
|
|
|
popupWindow.setSize(preferredChoicesWindowSize); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (preferredDescWindowSize!=null) { |
|
|
|
|
|
|
|
popupWindow.setDescriptionWindowSize( |
|
|
|
|
|
|
|
preferredDescWindowSize); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
popupWindow.setCompletions(completions); |
|
|
|
popupWindow.setCompletions(completions); |
|
|
|
|
|
|
|
|
|
|
|
if (!popupWindow.isVisible()) { |
|
|
|
if (!popupWindow.isVisible()) { |
|
|
|
Rectangle r = null; |
|
|
|
Rectangle r = null; |
|
|
|
try { |
|
|
|
try { |
|
|
|
r = textComponent.modelToView(textComponent. |
|
|
|
r = textComponent.modelToView(textComponent.getCaretPosition()); |
|
|
|
getCaretPosition()); |
|
|
|
|
|
|
|
} catch (BadLocationException ble) { |
|
|
|
} catch (BadLocationException ble) { |
|
|
|
|
|
|
|
|
|
|
|
return -1; |
|
|
|
return -1; |
|
|
|
} |
|
|
|
} |
|
|
|
Point p = new Point(r.x, r.y); |
|
|
|
Point p = new Point(r.x, r.y); |
|
|
@ -743,25 +716,43 @@ public class AutoCompletion { |
|
|
|
popupWindow.setLocationRelativeTo(r); |
|
|
|
popupWindow.setLocationRelativeTo(r); |
|
|
|
popupWindow.setVisible(true); |
|
|
|
popupWindow.setVisible(true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} else if (count == 1) { // !isPopupVisible && autoCompleteSingleChoices
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else if (count==1) { // !isPopupVisible && autoCompleteSingleChoices
|
|
|
|
|
|
|
|
SwingUtilities.invokeLater(new Runnable() { |
|
|
|
SwingUtilities.invokeLater(new Runnable() { |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
insertCompletion(completions.get(0)); |
|
|
|
insertCompletion(completions.get(0)); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
hidePopupWindow(); |
|
|
|
hidePopupWindow(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return getLineOfCaret(); |
|
|
|
return getLineOfCaret(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean needSetPopupWindow(int count, int textLen) { |
|
|
|
|
|
|
|
return (count == 1 && (isPopupVisible() || textLen == 0)) |
|
|
|
|
|
|
|
|| (count == 1 && !isAutoCompleteSingleChoices()) |
|
|
|
|
|
|
|
|| count > 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private AutoCompletePopupWindow createAutoCompletePopupWindow() { |
|
|
|
|
|
|
|
AutoCompletePopupWindow popupWindow = new AutoCompletePopupWindow(parentWindow, this); |
|
|
|
|
|
|
|
// Completion is usually done for code, which is always done
|
|
|
|
|
|
|
|
// LTR, so make completion stuff RTL only if text component is
|
|
|
|
|
|
|
|
// also RTL.
|
|
|
|
|
|
|
|
popupWindow.applyComponentOrientation( |
|
|
|
|
|
|
|
getTextComponentOrientation()); |
|
|
|
|
|
|
|
if (renderer != null) { |
|
|
|
|
|
|
|
popupWindow.setListCellRenderer(renderer); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (preferredChoicesWindowSize != null) { |
|
|
|
|
|
|
|
popupWindow.setSize(preferredChoicesWindowSize); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (preferredDescWindowSize != null) { |
|
|
|
|
|
|
|
popupWindow.setDescriptionWindowSize( |
|
|
|
|
|
|
|
preferredDescWindowSize); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return popupWindow; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Sets the delay between when the user types a character and when the |
|
|
|
* Sets the delay between when the user types a character and when the |
|
|
@ -791,8 +782,7 @@ public class AutoCompletion { |
|
|
|
if (textComponent != null) { |
|
|
|
if (textComponent != null) { |
|
|
|
if (autoActivationEnabled) { |
|
|
|
if (autoActivationEnabled) { |
|
|
|
autoActivationListener.addTo(textComponent); |
|
|
|
autoActivationListener.addTo(textComponent); |
|
|
|
} |
|
|
|
} else { |
|
|
|
else { |
|
|
|
|
|
|
|
autoActivationListener.removeFrom(textComponent); |
|
|
|
autoActivationListener.removeFrom(textComponent); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -819,7 +809,7 @@ public class AutoCompletion { |
|
|
|
* be automatically inserted, without displaying the popup menu. |
|
|
|
* be automatically inserted, without displaying the popup menu. |
|
|
|
* |
|
|
|
* |
|
|
|
* @param autoComplete Whether to auto-complete single choices. |
|
|
|
* @param autoComplete Whether to auto-complete single choices. |
|
|
|
* @see #getAutoCompleteSingleChoices() |
|
|
|
* @see #isAutoCompleteSingleChoices() |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setAutoCompleteSingleChoices(boolean autoComplete) { |
|
|
|
public void setAutoCompleteSingleChoices(boolean autoComplete) { |
|
|
|
autoCompleteSingleChoices = autoComplete; |
|
|
|
autoCompleteSingleChoices = autoComplete; |
|
|
@ -956,7 +946,7 @@ public class AutoCompletion { |
|
|
|
* completion window. |
|
|
|
* completion window. |
|
|
|
* |
|
|
|
* |
|
|
|
* @param show Whether to show the description window. |
|
|
|
* @param show Whether to show the description window. |
|
|
|
* @see #getShowDescWindow() |
|
|
|
* @see #isShowDescWindow() |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setShowDescWindow(boolean show) { |
|
|
|
public void setShowDescWindow(boolean show) { |
|
|
|
hidePopupWindow(); // Needed to force it to take effect
|
|
|
|
hidePopupWindow(); // Needed to force it to take effect
|
|
|
@ -1012,8 +1002,7 @@ public class AutoCompletion { |
|
|
|
textComponent.replaceSelection(Character.toString(p.getParameterListStart())); |
|
|
|
textComponent.replaceSelection(Character.toString(p.getParameterListStart())); |
|
|
|
TemplateCompletion tc = new TemplateCompletion(p, null, null, template); |
|
|
|
TemplateCompletion tc = new TemplateCompletion(p, null, null, template); |
|
|
|
pc = tc; |
|
|
|
pc = tc; |
|
|
|
} |
|
|
|
} else { |
|
|
|
else { |
|
|
|
|
|
|
|
text = p.getParameterListStart() + text; |
|
|
|
text = p.getParameterListStart() + text; |
|
|
|
textComponent.replaceSelection(text); |
|
|
|
textComponent.replaceSelection(text); |
|
|
|
return; |
|
|
|
return; |
|
|
@ -1130,8 +1119,7 @@ public class AutoCompletion { |
|
|
|
public void caretUpdate(CaretEvent e) { |
|
|
|
public void caretUpdate(CaretEvent e) { |
|
|
|
if (justInserted) { |
|
|
|
if (justInserted) { |
|
|
|
justInserted = false; |
|
|
|
justInserted = false; |
|
|
|
} |
|
|
|
} else { |
|
|
|
else { |
|
|
|
|
|
|
|
timer.stop(); |
|
|
|
timer.stop(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1153,12 +1141,10 @@ public class AutoCompletion { |
|
|
|
if (provider.isAutoActivateOkay(textComponent)) { |
|
|
|
if (provider.isAutoActivateOkay(textComponent)) { |
|
|
|
timer.restart(); |
|
|
|
timer.restart(); |
|
|
|
justInserted = true; |
|
|
|
justInserted = true; |
|
|
|
} |
|
|
|
} else { |
|
|
|
else { |
|
|
|
|
|
|
|
timer.stop(); |
|
|
|
timer.stop(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
else { |
|
|
|
|
|
|
|
timer.stop(); |
|
|
|
timer.stop(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1187,8 +1173,7 @@ public class AutoCompletion { |
|
|
|
public void actionPerformed(ActionEvent e) { |
|
|
|
public void actionPerformed(ActionEvent e) { |
|
|
|
if (isAutoCompleteEnabled()) { |
|
|
|
if (isAutoCompleteEnabled()) { |
|
|
|
refreshPopupWindow(); |
|
|
|
refreshPopupWindow(); |
|
|
|
} |
|
|
|
} else if (oldTriggerAction != null) { |
|
|
|
else if (oldTriggerAction!=null) { |
|
|
|
|
|
|
|
oldTriggerAction.actionPerformed(e); |
|
|
|
oldTriggerAction.actionPerformed(e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|