mirror of
https://github.com/SuspiciousActivity/ByteEdit.git
synced 2026-08-13 17:56:17 +00:00
881 lines
26 KiB
Java
881 lines
26 KiB
Java
/*
|
|
* 12/21/2008
|
|
* AutoCompletePopupWindow.java - A window containing a list of auto-complete
|
|
* choices.
|
|
* This library is distributed under a modified BSD license. See the included
|
|
* AutoComplete.License.txt file for details.
|
|
*/
|
|
package org.fife.ui.autocomplete;
|
|
|
|
import java.awt.BorderLayout;
|
|
import java.awt.ComponentOrientation;
|
|
import java.awt.Dimension;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.awt.Toolkit;
|
|
import java.awt.Window;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.KeyEvent;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.util.List;
|
|
|
|
import javax.swing.AbstractAction;
|
|
import javax.swing.Action;
|
|
import javax.swing.ActionMap;
|
|
import javax.swing.InputMap;
|
|
import javax.swing.JList;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JWindow;
|
|
import javax.swing.KeyStroke;
|
|
import javax.swing.ListCellRenderer;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.event.CaretEvent;
|
|
import javax.swing.event.CaretListener;
|
|
import javax.swing.event.ListSelectionEvent;
|
|
import javax.swing.event.ListSelectionListener;
|
|
import javax.swing.plaf.ListUI;
|
|
import javax.swing.text.Caret;
|
|
import javax.swing.text.JTextComponent;
|
|
|
|
import org.fife.ui.rsyntaxtextarea.PopupWindowDecorator;
|
|
|
|
/**
|
|
* The actual popup window of choices. When visible, this window intercepts
|
|
* certain keystrokes in the parent text component and uses them to navigate the
|
|
* completion choices instead. If Enter or Escape is pressed, the window hides
|
|
* itself and notifies the {@link AutoCompletion} to insert the selected text.
|
|
*
|
|
* @author Robert Futrell
|
|
* @version 1.0
|
|
*/
|
|
class AutoCompletePopupWindow extends JWindow implements CaretListener, ListSelectionListener, MouseListener {
|
|
|
|
/**
|
|
* The parent AutoCompletion instance.
|
|
*/
|
|
private AutoCompletion ac;
|
|
/**
|
|
* The list of completion choices.
|
|
*/
|
|
private JList list;
|
|
/**
|
|
* The contents of {@link #list()}.
|
|
*/
|
|
private CompletionListModel model;
|
|
/**
|
|
* A hack to work around the fact that we clear our completion model (and our
|
|
* selection) when hiding the completion window. This allows us to still know
|
|
* what the user selected after the popup is hidden.
|
|
*/
|
|
private Completion lastSelection;
|
|
/**
|
|
* Optional popup window containing a description of the currently selected
|
|
* completion.
|
|
*/
|
|
private AutoCompleteDescWindow descWindow;
|
|
/**
|
|
* The preferred size of the optional description window. This field only exists
|
|
* because the user may (and usually will) set the size of the description
|
|
* window before it exists (it must be parented to a Window).
|
|
*/
|
|
private Dimension preferredDescWindowSize;
|
|
/**
|
|
* Whether the completion window and the optional description window should be
|
|
* displayed above the current caret position (as opposed to underneath it,
|
|
* which is preferred unless there is not enough space).
|
|
*/
|
|
private boolean aboveCaret;
|
|
private int lastLine;
|
|
private boolean keyBindingsInstalled;
|
|
private KeyActionPair escapeKap;
|
|
private KeyActionPair upKap;
|
|
private KeyActionPair downKap;
|
|
private KeyActionPair leftKap;
|
|
private KeyActionPair rightKap;
|
|
private KeyActionPair enterKap;
|
|
private KeyActionPair tabKap;
|
|
private KeyActionPair homeKap;
|
|
private KeyActionPair endKap;
|
|
private KeyActionPair pageUpKap;
|
|
private KeyActionPair pageDownKap;
|
|
private KeyActionPair ctrlCKap;
|
|
private KeyActionPair oldEscape, oldUp, oldDown, oldLeft, oldRight, oldEnter, oldTab, oldHome, oldEnd, oldPageUp,
|
|
oldPageDown, oldCtrlC;
|
|
/**
|
|
* The space between the caret and the completion popup.
|
|
*/
|
|
private static final int VERTICAL_SPACE = 1;
|
|
/**
|
|
* The class name of the Substance List UI.
|
|
*/
|
|
private static final String SUBSTANCE_LIST_UI = "org.pushingpixels.substance.internal.ui.SubstanceListUI";
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param parent The parent window (hosting the text component).
|
|
* @param ac The auto-completion instance.
|
|
*/
|
|
public AutoCompletePopupWindow(Window parent, final AutoCompletion ac) {
|
|
super(parent);
|
|
ComponentOrientation o = ac.getTextComponentOrientation();
|
|
this.ac = ac;
|
|
model = new CompletionListModel();
|
|
list = new PopupList(model);
|
|
list.setCellRenderer(new DelegatingCellRenderer());
|
|
list.addListSelectionListener(this);
|
|
list.addMouseListener(this);
|
|
JPanel contentPane = new JPanel(new BorderLayout());
|
|
JScrollPane sp = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
|
|
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
|
// In 1.4, JScrollPane.setCorner() has a bug where it won't accept
|
|
// JScrollPane.LOWER_TRAILING_CORNER, even though that constant is
|
|
// defined. So we have to put the logic added in 1.5 to handle it
|
|
// here.
|
|
JPanel corner = new SizeGrip();
|
|
// sp.setCorner(JScrollPane.LOWER_TRAILING_CORNER, corner);
|
|
boolean isLeftToRight = o.isLeftToRight();
|
|
String str = isLeftToRight ? JScrollPane.LOWER_RIGHT_CORNER : JScrollPane.LOWER_LEFT_CORNER;
|
|
sp.setCorner(str, corner);
|
|
contentPane.add(sp);
|
|
setContentPane(contentPane);
|
|
applyComponentOrientation(o);
|
|
// Give apps a chance to decorate us with drop shadows, etc.
|
|
if (Util.getShouldAllowDecoratingMainAutoCompleteWindows()) {
|
|
PopupWindowDecorator decorator = PopupWindowDecorator.get();
|
|
if (decorator != null) {
|
|
decorator.decorate(this);
|
|
}
|
|
}
|
|
pack();
|
|
setFocusableWindowState(false);
|
|
lastLine = -1;
|
|
}
|
|
|
|
@Override
|
|
public void caretUpdate(CaretEvent e) {
|
|
if (isVisible()) { // Should always be true
|
|
int line = ac.getLineOfCaret();
|
|
if (line != lastLine) {
|
|
lastLine = -1;
|
|
setVisible(false);
|
|
} else {
|
|
doAutocomplete();
|
|
}
|
|
} else if (AutoCompletion.getDebug()) {
|
|
Thread.dumpStack();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the description window.
|
|
*
|
|
* @return The description window.
|
|
*/
|
|
private AutoCompleteDescWindow createDescriptionWindow() {
|
|
AutoCompleteDescWindow dw = new AutoCompleteDescWindow(this, ac);
|
|
dw.applyComponentOrientation(ac.getTextComponentOrientation());
|
|
Dimension size = preferredDescWindowSize;
|
|
if (size == null) {
|
|
size = getSize();
|
|
}
|
|
dw.setSize(size);
|
|
return dw;
|
|
}
|
|
|
|
/**
|
|
* Creates the mappings from keys to Actions we'll be putting into the text
|
|
* component's ActionMap and InputMap.
|
|
*/
|
|
private void createKeyActionPairs() {
|
|
// Actions we'll install.
|
|
EnterAction enterAction = new EnterAction();
|
|
escapeKap = new KeyActionPair("Escape", new EscapeAction());
|
|
upKap = new KeyActionPair("Up", new UpAction());
|
|
downKap = new KeyActionPair("Down", new DownAction());
|
|
leftKap = new KeyActionPair("Left", new LeftAction());
|
|
rightKap = new KeyActionPair("Right", new RightAction());
|
|
enterKap = new KeyActionPair("Enter", enterAction);
|
|
tabKap = new KeyActionPair("Tab", enterAction);
|
|
homeKap = new KeyActionPair("Home", new HomeAction());
|
|
endKap = new KeyActionPair("End", new EndAction());
|
|
pageUpKap = new KeyActionPair("PageUp", new PageUpAction());
|
|
pageDownKap = new KeyActionPair("PageDown", new PageDownAction());
|
|
ctrlCKap = new KeyActionPair("CtrlC", new CopyAction());
|
|
// Buffers for the actions we replace.
|
|
oldEscape = new KeyActionPair();
|
|
oldUp = new KeyActionPair();
|
|
oldDown = new KeyActionPair();
|
|
oldLeft = new KeyActionPair();
|
|
oldRight = new KeyActionPair();
|
|
oldEnter = new KeyActionPair();
|
|
oldTab = new KeyActionPair();
|
|
oldHome = new KeyActionPair();
|
|
oldEnd = new KeyActionPair();
|
|
oldPageUp = new KeyActionPair();
|
|
oldPageDown = new KeyActionPair();
|
|
oldCtrlC = new KeyActionPair();
|
|
}
|
|
|
|
protected void doAutocomplete() {
|
|
lastLine = ac.refreshPopupWindow();
|
|
}
|
|
|
|
/**
|
|
* Returns the copy keystroke to use for this platform.
|
|
*
|
|
* @return The copy keystroke.
|
|
*/
|
|
private static final KeyStroke getCopyKeyStroke() {
|
|
int key = KeyEvent.VK_C;
|
|
int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
|
return KeyStroke.getKeyStroke(key, mask);
|
|
}
|
|
|
|
/**
|
|
* Returns the default list cell renderer used when a completion provider does
|
|
* not supply its own.
|
|
*
|
|
* @return The default list cell renderer.
|
|
* @see #setListCellRenderer(ListCellRenderer)
|
|
*/
|
|
public ListCellRenderer getListCellRenderer() {
|
|
DelegatingCellRenderer dcr = (DelegatingCellRenderer) list.getCellRenderer();
|
|
return dcr.getFallbackCellRenderer();
|
|
}
|
|
|
|
/**
|
|
* Returns the selected value, or <code>null</code> if nothing is selected.
|
|
*
|
|
* @return The selected value.
|
|
*/
|
|
public Completion getSelection() {
|
|
return isShowing() ? (Completion) list.getSelectedValue() : lastSelection;
|
|
}
|
|
|
|
/**
|
|
* Inserts the currently selected completion.
|
|
*
|
|
* @see #getSelection()
|
|
*/
|
|
private void insertSelectedCompletion() {
|
|
Completion comp = getSelection();
|
|
ac.insertCompletion(comp);
|
|
}
|
|
|
|
/**
|
|
* Registers keyboard actions to listen for in the text component and intercept.
|
|
*
|
|
* @see #uninstallKeyBindings()
|
|
*/
|
|
private void installKeyBindings() {
|
|
if (AutoCompletion.getDebug()) {
|
|
System.out.println("PopupWindow: Installing keybindings");
|
|
}
|
|
if (keyBindingsInstalled) {
|
|
System.err.println("Error: key bindings were already installed");
|
|
Thread.dumpStack();
|
|
return;
|
|
}
|
|
if (escapeKap == null) { // Lazily create actions.
|
|
createKeyActionPairs();
|
|
}
|
|
JTextComponent comp = ac.getTextComponent();
|
|
InputMap im = comp.getInputMap();
|
|
ActionMap am = comp.getActionMap();
|
|
replaceAction(im, am, KeyEvent.VK_ESCAPE, escapeKap, oldEscape);
|
|
if (AutoCompletion.getDebug() && oldEscape.action == escapeKap.action) {
|
|
Thread.dumpStack();
|
|
}
|
|
replaceAction(im, am, KeyEvent.VK_UP, upKap, oldUp);
|
|
replaceAction(im, am, KeyEvent.VK_LEFT, leftKap, oldLeft);
|
|
replaceAction(im, am, KeyEvent.VK_DOWN, downKap, oldDown);
|
|
replaceAction(im, am, KeyEvent.VK_RIGHT, rightKap, oldRight);
|
|
replaceAction(im, am, KeyEvent.VK_ENTER, enterKap, oldEnter);
|
|
replaceAction(im, am, KeyEvent.VK_TAB, tabKap, oldTab);
|
|
replaceAction(im, am, KeyEvent.VK_HOME, homeKap, oldHome);
|
|
replaceAction(im, am, KeyEvent.VK_END, endKap, oldEnd);
|
|
replaceAction(im, am, KeyEvent.VK_PAGE_UP, pageUpKap, oldPageUp);
|
|
replaceAction(im, am, KeyEvent.VK_PAGE_DOWN, pageDownKap, oldPageDown);
|
|
// Make Ctrl+C copy from description window. This isn't done
|
|
// automagically because the desc. window is not focusable, and copying
|
|
// from text components can only be done from focused components.
|
|
KeyStroke ks = getCopyKeyStroke();
|
|
oldCtrlC.key = im.get(ks);
|
|
im.put(ks, ctrlCKap.key);
|
|
oldCtrlC.action = am.get(ctrlCKap.key);
|
|
am.put(ctrlCKap.key, ctrlCKap.action);
|
|
comp.addCaretListener(this);
|
|
keyBindingsInstalled = true;
|
|
}
|
|
|
|
@Override
|
|
public void mouseClicked(MouseEvent e) {
|
|
if (e.getClickCount() == 2) {
|
|
insertSelectedCompletion();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void mouseEntered(MouseEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void mouseExited(MouseEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void mousePressed(MouseEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void mouseReleased(MouseEvent e) {
|
|
}
|
|
|
|
/**
|
|
* Positions the description window relative to the completion choices window.
|
|
* We assume there is room on one side of the other for this entire window to
|
|
* fit.
|
|
*/
|
|
private void positionDescWindow() {
|
|
boolean showDescWindow = descWindow != null && ac.getShowDescWindow();
|
|
if (!showDescWindow) {
|
|
return;
|
|
}
|
|
// Don't use getLocationOnScreen() as this throws an exception if
|
|
// window isn't visible yet, but getLocation() doesn't, and is in
|
|
// screen coordinates!
|
|
Point p = getLocation();
|
|
Rectangle screenBounds = Util.getScreenBoundsForPoint(p.x, p.y);
|
|
// Dimension screenSize = getToolkit().getScreenSize();
|
|
// int totalH = Math.max(getHeight(), descWindow.getHeight());
|
|
// Try to position to the right first (LTR)
|
|
int x;
|
|
if (ac.getTextComponentOrientation().isLeftToRight()) {
|
|
x = getX() + getWidth() + 5;
|
|
if (x + descWindow.getWidth() > screenBounds.x + screenBounds.width) { // doesn't
|
|
// fit
|
|
x = getX() - 5 - descWindow.getWidth();
|
|
}
|
|
} else { // RTL
|
|
x = getX() - 5 - descWindow.getWidth();
|
|
if (x < screenBounds.x) { // Doesn't fit
|
|
x = getX() + getWidth() + 5;
|
|
}
|
|
}
|
|
int y = getY();
|
|
if (aboveCaret) {
|
|
y = y + getHeight() - descWindow.getHeight();
|
|
}
|
|
if (x != descWindow.getX() || y != descWindow.getY()) {
|
|
descWindow.setLocation(x, y);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* "Puts back" the original key/Action pair for a keystroke. This is used when
|
|
* this popup is hidden.
|
|
*
|
|
* @param im The input map.
|
|
* @param am The action map.
|
|
* @param key The keystroke whose key/Action pair to change.
|
|
* @param kap The (original) key/Action pair.
|
|
* @see #replaceAction(InputMap, ActionMap, int, KeyActionPair, KeyActionPair)
|
|
*/
|
|
private void putBackAction(InputMap im, ActionMap am, int key, KeyActionPair kap) {
|
|
KeyStroke ks = KeyStroke.getKeyStroke(key, 0);
|
|
am.put(im.get(ks), kap.action); // Original action for the "new" key
|
|
im.put(ks, kap.key); // Original key for the keystroke.
|
|
}
|
|
|
|
/**
|
|
* Replaces a key/Action pair in an InputMap and ActionMap with a new pair.
|
|
*
|
|
* @param im The input map.
|
|
* @param am The action map.
|
|
* @param key The keystroke whose information to replace.
|
|
* @param kap The new key/Action pair for <code>key</code>.
|
|
* @param old A buffer in which to place the old key/Action pair.
|
|
* @see #putBackAction(InputMap, ActionMap, int, KeyActionPair)
|
|
*/
|
|
private void replaceAction(InputMap im, ActionMap am, int key, KeyActionPair kap, KeyActionPair old) {
|
|
KeyStroke ks = KeyStroke.getKeyStroke(key, 0);
|
|
old.key = im.get(ks);
|
|
im.put(ks, kap.key);
|
|
old.action = am.get(kap.key);
|
|
am.put(kap.key, kap.action);
|
|
}
|
|
|
|
/**
|
|
* Selects the first item in the completion list.
|
|
*
|
|
* @see #selectLastItem()
|
|
*/
|
|
private void selectFirstItem() {
|
|
if (model.getSize() > 0) {
|
|
list.setSelectedIndex(0);
|
|
list.ensureIndexIsVisible(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects the last item in the completion list.
|
|
*
|
|
* @see #selectFirstItem()
|
|
*/
|
|
private void selectLastItem() {
|
|
int index = model.getSize() - 1;
|
|
if (index > -1) {
|
|
list.setSelectedIndex(index);
|
|
list.ensureIndexIsVisible(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects the next item in the completion list.
|
|
*
|
|
* @see #selectPreviousItem()
|
|
*/
|
|
private void selectNextItem() {
|
|
int index = list.getSelectedIndex();
|
|
if (index > -1) {
|
|
index = (index + 1) % model.getSize();
|
|
list.setSelectedIndex(index);
|
|
list.ensureIndexIsVisible(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects the completion item one "page down" from the currently selected one.
|
|
*
|
|
* @see #selectPageUpItem()
|
|
*/
|
|
private void selectPageDownItem() {
|
|
int visibleRowCount = list.getVisibleRowCount();
|
|
int i = Math.min(list.getModel().getSize() - 1, list.getSelectedIndex() + visibleRowCount);
|
|
list.setSelectedIndex(i);
|
|
list.ensureIndexIsVisible(i);
|
|
}
|
|
|
|
/**
|
|
* Selects the completion item one "page up" from the currently selected one.
|
|
*
|
|
* @see #selectPageDownItem()
|
|
*/
|
|
private void selectPageUpItem() {
|
|
int visibleRowCount = list.getVisibleRowCount();
|
|
int i = Math.max(0, list.getSelectedIndex() - visibleRowCount);
|
|
list.setSelectedIndex(i);
|
|
list.ensureIndexIsVisible(i);
|
|
}
|
|
|
|
/**
|
|
* Selects the previous item in the completion list.
|
|
*
|
|
* @see #selectNextItem()
|
|
*/
|
|
private void selectPreviousItem() {
|
|
int index = list.getSelectedIndex();
|
|
switch (index) {
|
|
case 0:
|
|
index = list.getModel().getSize() - 1;
|
|
break;
|
|
case -1: // Check for an empty list (would be an error)
|
|
index = list.getModel().getSize() - 1;
|
|
if (index == -1) {
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
index = index - 1;
|
|
break;
|
|
}
|
|
list.setSelectedIndex(index);
|
|
list.ensureIndexIsVisible(index);
|
|
}
|
|
|
|
/**
|
|
* Sets the completions to display in the choices list. The first completion is
|
|
* selected.
|
|
*
|
|
* @param completions The completions to display.
|
|
*/
|
|
public void setCompletions(List<Completion> completions) {
|
|
model.setContents(completions);
|
|
selectFirstItem();
|
|
}
|
|
|
|
/**
|
|
* Sets the size of the description window.
|
|
*
|
|
* @param size The new size. This cannot be <code>null</code>.
|
|
*/
|
|
public void setDescriptionWindowSize(Dimension size) {
|
|
if (descWindow != null) {
|
|
descWindow.setSize(size);
|
|
} else {
|
|
preferredDescWindowSize = size;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the default list cell renderer to use when a completion provider does
|
|
* not supply its own.
|
|
*
|
|
* @param renderer The renderer to use. If this is <code>null</code>, a default
|
|
* renderer is used.
|
|
* @see #getListCellRenderer()
|
|
*/
|
|
public void setListCellRenderer(ListCellRenderer renderer) {
|
|
DelegatingCellRenderer dcr = (DelegatingCellRenderer) list.getCellRenderer();
|
|
dcr.setFallbackCellRenderer(renderer);
|
|
}
|
|
|
|
/**
|
|
* Sets the location of this window to be "good" relative to the specified
|
|
* rectangle. That rectangle should be the location of the text component's
|
|
* caret, in screen coordinates.
|
|
*
|
|
* @param r The text component's caret position, in screen coordinates.
|
|
*/
|
|
public void setLocationRelativeTo(Rectangle r) {
|
|
// Multi-monitor support - make sure the completion window (and
|
|
// description window, if applicable) both fit in the same window in
|
|
// a multi-monitor environment. To do this, we decide which monitor
|
|
// the rectangle "r" is in, and use that one (just pick top-left corner
|
|
// as the defining point).
|
|
Rectangle screenBounds = Util.getScreenBoundsForPoint(r.x, r.y);
|
|
// Dimension screenSize = getToolkit().getScreenSize();
|
|
boolean showDescWindow = descWindow != null && ac.getShowDescWindow();
|
|
int totalH = getHeight();
|
|
if (showDescWindow) {
|
|
totalH = Math.max(totalH, descWindow.getHeight());
|
|
}
|
|
// Try putting our stuff "below" the caret first. We assume that the
|
|
// entire height of our stuff fits on the screen one way or the other.
|
|
aboveCaret = false;
|
|
int y = r.y + r.height + VERTICAL_SPACE;
|
|
if (y + totalH > screenBounds.height) {
|
|
y = r.y - VERTICAL_SPACE - getHeight();
|
|
aboveCaret = true;
|
|
}
|
|
// Get x-coordinate of completions. Try to align left edge with the
|
|
// caret first.
|
|
int x = r.x;
|
|
if (!ac.getTextComponentOrientation().isLeftToRight()) {
|
|
x -= getWidth(); // RTL => align right edge
|
|
}
|
|
if (x < screenBounds.x) {
|
|
x = screenBounds.x;
|
|
} else if (x + getWidth() > screenBounds.x + screenBounds.width) { // completions
|
|
// don't
|
|
// fit
|
|
x = screenBounds.x + screenBounds.width - getWidth();
|
|
}
|
|
setLocation(x, y);
|
|
// Position the description window, if necessary.
|
|
if (showDescWindow) {
|
|
positionDescWindow();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggles the visibility of this popup window.
|
|
*
|
|
* @param visible Whether this window should be visible.
|
|
*/
|
|
@Override
|
|
public void setVisible(boolean visible) {
|
|
if (visible != isVisible()) {
|
|
if (visible) {
|
|
installKeyBindings();
|
|
lastLine = ac.getLineOfCaret();
|
|
selectFirstItem();
|
|
if (descWindow == null && ac.getShowDescWindow()) {
|
|
descWindow = createDescriptionWindow();
|
|
positionDescWindow();
|
|
}
|
|
// descWindow needs a kick-start the first time it's displayed.
|
|
// Also, the newly-selected item in the choices list is
|
|
// probably different from the previous one anyway.
|
|
if (descWindow != null) {
|
|
Completion c = (Completion) list.getSelectedValue();
|
|
if (c != null) {
|
|
descWindow.setDescriptionFor(c);
|
|
}
|
|
}
|
|
} else {
|
|
uninstallKeyBindings();
|
|
}
|
|
super.setVisible(visible);
|
|
// Some languages, such as Java, can use quite a lot of memory
|
|
// when displaying hundreds of completion choices. We pro-actively
|
|
// clear our list model here to make them available for GC.
|
|
// Otherwise, they stick around, and consider the following: a
|
|
// user starts code-completion for Java 5 SDK classes, then hides
|
|
// the dialog, then changes the "class path" to use a Java 6 SDK
|
|
// instead. On pressing Ctrl+space, a new array of Completions is
|
|
// created. If this window holds on to the previous Completions,
|
|
// you're getting roughly 2x the necessary Completions in memory
|
|
// until the Completions are actually passed to this window.
|
|
if (!visible) { // Do after super.setVisible(false)
|
|
lastSelection = (Completion) list.getSelectedValue();
|
|
model.clear();
|
|
}
|
|
// Must set descWindow's visibility one way or the other each time,
|
|
// 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());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops intercepting certain keystrokes from the text component.
|
|
*
|
|
* @see #installKeyBindings()
|
|
*/
|
|
private void uninstallKeyBindings() {
|
|
if (AutoCompletion.getDebug()) {
|
|
System.out.println("PopupWindow: Removing keybindings");
|
|
}
|
|
if (!keyBindingsInstalled) {
|
|
return;
|
|
}
|
|
JTextComponent comp = ac.getTextComponent();
|
|
InputMap im = comp.getInputMap();
|
|
ActionMap am = comp.getActionMap();
|
|
putBackAction(im, am, KeyEvent.VK_ESCAPE, oldEscape);
|
|
putBackAction(im, am, KeyEvent.VK_UP, oldUp);
|
|
putBackAction(im, am, KeyEvent.VK_DOWN, oldDown);
|
|
putBackAction(im, am, KeyEvent.VK_LEFT, oldLeft);
|
|
putBackAction(im, am, KeyEvent.VK_RIGHT, oldRight);
|
|
putBackAction(im, am, KeyEvent.VK_ENTER, oldEnter);
|
|
putBackAction(im, am, KeyEvent.VK_TAB, oldTab);
|
|
putBackAction(im, am, KeyEvent.VK_HOME, oldHome);
|
|
putBackAction(im, am, KeyEvent.VK_END, oldEnd);
|
|
putBackAction(im, am, KeyEvent.VK_PAGE_UP, oldPageUp);
|
|
putBackAction(im, am, KeyEvent.VK_PAGE_DOWN, oldPageDown);
|
|
// Ctrl+C
|
|
KeyStroke ks = getCopyKeyStroke();
|
|
am.put(im.get(ks), oldCtrlC.action); // Original action
|
|
im.put(ks, oldCtrlC.key); // Original key
|
|
comp.removeCaretListener(this);
|
|
keyBindingsInstalled = false;
|
|
}
|
|
|
|
/**
|
|
* Updates the <tt>LookAndFeel</tt> of this window and the description window.
|
|
*/
|
|
public void updateUI() {
|
|
SwingUtilities.updateComponentTreeUI(this);
|
|
if (descWindow != null) {
|
|
descWindow.updateUI();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a new item is selected in the popup list.
|
|
*
|
|
* @param e The event.
|
|
*/
|
|
@Override
|
|
public void valueChanged(ListSelectionEvent e) {
|
|
if (!e.getValueIsAdjusting()) {
|
|
Object value = list.getSelectedValue();
|
|
if (value != null && descWindow != null) {
|
|
descWindow.setDescriptionFor((Completion) value);
|
|
positionDescWindow();
|
|
}
|
|
}
|
|
}
|
|
|
|
class CopyAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
boolean doNormalCopy = false;
|
|
if (descWindow != null && descWindow.isVisible()) {
|
|
doNormalCopy = !descWindow.copy();
|
|
}
|
|
if (doNormalCopy) {
|
|
ac.getTextComponent().copy();
|
|
}
|
|
}
|
|
}
|
|
|
|
class DownAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
selectNextItem();
|
|
}
|
|
}
|
|
}
|
|
|
|
class EndAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
selectLastItem();
|
|
}
|
|
}
|
|
}
|
|
|
|
class EnterAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
insertSelectedCompletion();
|
|
}
|
|
}
|
|
}
|
|
|
|
class EscapeAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
setVisible(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
class HomeAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
selectFirstItem();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A mapping from a key (an Object) to an Action.
|
|
*/
|
|
private static class KeyActionPair {
|
|
|
|
public Object key;
|
|
public Action action;
|
|
|
|
public KeyActionPair() {
|
|
}
|
|
|
|
public KeyActionPair(Object key, Action a) {
|
|
this.key = key;
|
|
this.action = a;
|
|
}
|
|
}
|
|
|
|
class LeftAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
JTextComponent comp = ac.getTextComponent();
|
|
Caret c = comp.getCaret();
|
|
int dot = c.getDot();
|
|
if (dot > 0) {
|
|
c.setDot(--dot);
|
|
// Ensure moving left hasn't moved us up a line, thus
|
|
// hiding the popup window.
|
|
if (comp.isVisible()) {
|
|
if (lastLine != -1) {
|
|
doAutocomplete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class PageDownAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
selectPageDownItem();
|
|
}
|
|
}
|
|
}
|
|
|
|
class PageUpAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
selectPageUpItem();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The actual list of completion choices in this popup window.
|
|
*/
|
|
private class PopupList extends JList {
|
|
|
|
public PopupList(CompletionListModel model) {
|
|
super(model);
|
|
}
|
|
|
|
@Override
|
|
public void setUI(ListUI ui) {
|
|
if (Util.getUseSubstanceRenderers() && SUBSTANCE_LIST_UI.equals(ui.getClass().getName())) {
|
|
// Substance requires its special ListUI be installed for
|
|
// its renderers to actually render (!), but long completion
|
|
// lists (e.g. PHPCompletionProvider in RSTALanguageSupport)
|
|
// will simply populate too slowly on initial display (when
|
|
// calculating preferred size of all items), so in this case
|
|
// we give a prototype cell value.
|
|
CompletionProvider p = ac.getCompletionProvider();
|
|
BasicCompletion bc = new BasicCompletion(p, "Hello world");
|
|
setPrototypeCellValue(bc);
|
|
} else {
|
|
// Our custom UI that is faster for long HTML completion lists.
|
|
ui = new FastListUI();
|
|
setPrototypeCellValue(null);
|
|
}
|
|
super.setUI(ui);
|
|
}
|
|
}
|
|
|
|
class RightAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
JTextComponent comp = ac.getTextComponent();
|
|
Caret c = comp.getCaret();
|
|
int dot = c.getDot();
|
|
if (dot < comp.getDocument().getLength()) {
|
|
c.setDot(++dot);
|
|
// Ensure moving right hasn't moved us up a line, thus
|
|
// hiding the popup window.
|
|
if (comp.isVisible()) {
|
|
if (lastLine != -1) {
|
|
doAutocomplete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class UpAction extends AbstractAction {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (isVisible()) {
|
|
selectPreviousItem();
|
|
}
|
|
}
|
|
}
|
|
}
|