
org.fife.ui.autocomplete.AutoCompletePopupWindow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of autocomplete Show documentation
Show all versions of autocomplete Show documentation
AutoComplete is a library allowing you to add IDE-like auto-completion (aka "code completion" or "Intellisense") to any Swing JTextComponent. Special integration is added for RSyntaxTextArea, since this feature is commonly needed when editing source code. Features include: Drop-down completion choice list. Optional companion "description" window, complete with full HTML support and navigable with hyperlinks. Optional parameter completion assistance for functions and methods, ala Eclipse and NetBeans. Completion information is typically specified in an XML file, but can even be dynamic.
/* * 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 * LICENSE.md file for details. */ package org.fife.ui.autocomplete; import java.awt.*; 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 */ @SuppressWarnings("checkstyle:MultipleVariableDeclarations") class AutoCompletePopupWindow extends JWindow implements CaretListener, ListSelectionListener, MouseListener { /** * The parent AutoCompletion instance. */ private AutoCompletion ac; /** * The list of completion choices. */ private PopupList 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; /** * The background color of the description window. */ private Color descWindowColor; 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. */ 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); if (descWindowColor != null) { dw.setBackground(descWindowColor); } else { descWindowColor = dw.getBackground(); } 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 KeyStroke getCopyKeyStroke() { int key = KeyEvent.VK_C; int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); return KeyStroke.getKeyStroke(key, mask); } /** * Returns the background color of the description window. * * @return The background color, or {@code null} if it has not been created yet. * @see #setDescriptionWindowColor(Color) */ public Color getDescriptionWindowColor() { if (descWindow != null) { return descWindow.getBackground(); } return descWindowColor; } /** * 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
. * @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(Listnull
if nothing is selected. * * @return The selected value. */ public Completion getSelection() { return isShowing() ? 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 (xkey completions) { model.setContents(completions); selectFirstItem(); } /** * Sets the size of the description window. * * @param size The new size. This cannot be null
. */ public void setDescriptionWindowSize(Dimension size) { if (descWindow!=null) { descWindow.setSize(size); } else { preferredDescWindowSize = size; } } /** * Sets the color of the description window. * * @param color The new color. This cannot benull
. * @see #getDescriptionWindowColor() */ public void setDescriptionWindowColor(Color color) { if (descWindow!=null) { descWindow.setBackground(color); } else { descWindowColor = color; } } /** * 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 isnull
, * a default renderer is used. * @see #getListCellRenderer() */ public void setListCellRenderer(ListCellRenderer
© 2015 - 2025 Weber Informatics LLC | Privacy Policy