This is an automated email from the git hooks/post-receive script. ben pushed a commit to branch master in repository autocomplete.
commit aa4fa8ef4ea7391c9f68506d7140d1999eedd0a0 Author: bobbylight <[email protected]> Date: Fri Jan 9 23:41:35 2009 +0000 Adding parameter support when typing a function or method. Like Eclipse, arguments are pre-filled in and you can tab through them, and there is a tooltip listing the parameters to enter. --- .../autocomplete/AbstractCompletionProvider.java | 46 ++ .../ui/autocomplete/AutoCompleteDescWindow.java | 2 +- src/org/fife/ui/autocomplete/AutoCompletion.java | 81 +++ .../fife/ui/autocomplete/CCompletionProvider.java | 11 + .../ui/autocomplete/CompletionCellRenderer.java | 7 +- .../fife/ui/autocomplete/CompletionProvider.java | 47 ++ .../ui/autocomplete/DefaultCompletionProvider.java | 57 ++ .../fife/ui/autocomplete/FunctionCompletion.java | 136 ++-- .../ui/autocomplete/JarCompletionProvider.java | 8 + .../fife/ui/autocomplete/MarkupTagCompletion.java | 2 +- .../ui/autocomplete/OutlineHighlightPainter.java | 133 ++++ .../ui/autocomplete/ParameterizedCompletion.java | 92 +++ .../ParameterizedCompletionDescriptionToolTip.java | 721 ++++++++++++++++++++ 13 files changed, 1253 insertions(+), 90 deletions(-) diff --git a/src/org/fife/ui/autocomplete/AbstractCompletionProvider.java b/src/org/fife/ui/autocomplete/AbstractCompletionProvider.java index 3d9f5fe..0e930ff 100644 --- a/src/org/fife/ui/autocomplete/AbstractCompletionProvider.java +++ b/src/org/fife/ui/autocomplete/AbstractCompletionProvider.java @@ -28,7 +28,11 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.swing.ListCellRenderer; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; import javax.swing.text.JTextComponent; +import javax.swing.text.Segment; /** @@ -64,6 +68,21 @@ public abstract class AbstractCompletionProvider implements CompletionProvider { */ private Comparator comparator; + /** + * Text that marks the beginning of a parameter list, for example, "(". + */ + private String paramListStart; + + /** + * Text that marks the end of a parameter list, for example, ")". + */ + private String paramListEnd; + + /** + * Text that separates items in a parameter list, for example, ", ". + */ + private String paramListSeparator; + protected static final String EMPTY_STRING = ""; @@ -72,6 +91,9 @@ public abstract class AbstractCompletionProvider implements CompletionProvider { */ public AbstractCompletionProvider() { comparator = new CaseInsensitiveComparator(); + paramListStart = "("; + paramListEnd = ")"; + paramListSeparator = ", "; } @@ -228,6 +250,30 @@ public abstract class AbstractCompletionProvider implements CompletionProvider { /** * {@inheritDoc} */ + public String getParameterListEnd() { + return paramListEnd; + } + + + /** + * {@inheritDoc} + */ + public String getParameterListSeparator() { + return paramListSeparator; + } + + + /** + * {@inheritDoc} + */ + public String getParameterListStart() { + return paramListStart; + } + + + /** + * {@inheritDoc} + */ public CompletionProvider getParent() { return parent; } diff --git a/src/org/fife/ui/autocomplete/AutoCompleteDescWindow.java b/src/org/fife/ui/autocomplete/AutoCompleteDescWindow.java index 17bd894..2e30837 100644 --- a/src/org/fife/ui/autocomplete/AutoCompleteDescWindow.java +++ b/src/org/fife/ui/autocomplete/AutoCompleteDescWindow.java @@ -267,7 +267,7 @@ class AutoCompleteDescWindow extends JWindow implements HyperlinkListener { * @param e The event. */ public void hyperlinkUpdate(HyperlinkEvent e) { -System.out.println(descArea.isEnabled() + ", " + e); + HyperlinkEvent.EventType type = e.getEventType(); if (type.equals(HyperlinkEvent.EventType.ACTIVATED)) { diff --git a/src/org/fife/ui/autocomplete/AutoCompletion.java b/src/org/fife/ui/autocomplete/AutoCompletion.java index e2f3677..a7d7ac9 100644 --- a/src/org/fife/ui/autocomplete/AutoCompletion.java +++ b/src/org/fife/ui/autocomplete/AutoCompletion.java @@ -60,6 +60,11 @@ public class AutoCompletion implements HierarchyListener { private AutoCompletePopupWindow popupWindow; /** + * A "tooltip" describing a function just entered. + */ + private ParameterizedCompletionDescriptionToolTip descToolTip; + + /** * Provides the completion options relevant to the current caret position. */ private CompletionProvider provider; @@ -140,6 +145,47 @@ public class AutoCompletion implements HierarchyListener { /** + * Displays a "tooltip" detailing the inputs to the function just entered. + * + * @param pc The completion. + * @param addParamListStart Whether or not + * {@link CompletionProvider#getParameterListStart()} should be + * added to the text component. + */ + private void displayDescriptionToolTip(ParameterizedCompletion pc, + boolean addParamListStart) { + + // Get rid of the previous tooltip window, if there is one. + hideToolTipWindow(); + + // Don't bother with a tooltip if there are no parameters. + if (pc.getParamCount()==0) { + CompletionProvider p = pc.getProvider(); + textComponent.replaceSelection(p.getParameterListStart() + + p.getParameterListEnd()); + return; + } + + descToolTip = new ParameterizedCompletionDescriptionToolTip( + parentWindow, this, pc); + try { + int dot = textComponent.getCaretPosition(); + Rectangle r = textComponent.modelToView(dot); + Point p = new Point(r.x, r.y); + SwingUtilities.convertPointToScreen(p, textComponent); + r.x = p.x; + r.y = p.y; + descToolTip.setLocationRelativeTo(r); + descToolTip.setVisible(true, addParamListStart); + } catch (BadLocationException ble) { // Should never happen + UIManager.getLookAndFeel().provideErrorFeedback(textComponent); + ble.printStackTrace(); + } + + } + + + /** * Displays the popup window. Hosting applications can call this method * to programmatically begin an auto-completion operation. */ @@ -306,6 +352,14 @@ public class AutoCompletion implements HierarchyListener { } + private void hideToolTipWindow() { + if (descToolTip!=null) { + descToolTip.setVisible(false, false); + descToolTip = null; + } + } + + /** * Inserts a completion. * @@ -327,6 +381,11 @@ public class AutoCompletion implements HierarchyListener { caret.setDot(start); caret.moveDot(dot); textComp.replaceSelection(replacement); + + if (c instanceof ParameterizedCompletion) { + ParameterizedCompletion pc = (ParameterizedCompletion)c; + displayDescriptionToolTip(pc, true); + } /* Document doc = textComp.getDocument(); try { @@ -359,6 +418,24 @@ try { this.textComponent = c; installTriggerKey(getTriggerKey()); + InputMap im = c.getInputMap(); + ActionMap am = c.getActionMap(); + KeyStroke ks = KeyStroke.getKeyStroke('('); + Object oldParenKey = im.get(ks); + im.put(ks, "AutoCompletion.FunctionStart"); + Action oldParenAction = am.get("AutoCompletion.FunctionStart"); + am.put("AutoCompletion.FunctionStart", new javax.swing.AbstractAction() { + public void actionPerformed(java.awt.event.ActionEvent e) { + textComponent.replaceSelection("("); + List completions = provider.getParameterizedCompletionsAt(textComponent); + if (completions!=null && completions.size()>0) { + // TODO: Have tooltip let you select between multiple, like VS + ParameterizedCompletion pc = (ParameterizedCompletion)completions.get(0); + displayDescriptionToolTip(pc, false); + } + } + }); + this.textComponent.addHierarchyListener(this); hierarchyChanged(null); // In case textComponent is already in a window @@ -662,14 +739,17 @@ try { public void componentHidden(ComponentEvent e) { hidePopupWindow(); + hideToolTipWindow(); } public void componentMoved(ComponentEvent e) { hidePopupWindow(); + hideToolTipWindow(); } public void componentResized(ComponentEvent e) { hidePopupWindow(); + hideToolTipWindow(); } public void removeFrom(Window w) { @@ -682,6 +762,7 @@ try { public void windowLostFocus(WindowEvent e) { hidePopupWindow(); + hideToolTipWindow(); } } diff --git a/src/org/fife/ui/autocomplete/CCompletionProvider.java b/src/org/fife/ui/autocomplete/CCompletionProvider.java index 4fcea1a..258422e 100644 --- a/src/org/fife/ui/autocomplete/CCompletionProvider.java +++ b/src/org/fife/ui/autocomplete/CCompletionProvider.java @@ -147,6 +147,16 @@ public class CCompletionProvider extends AbstractCompletionProvider{ } + /** + * {@inheritDoc} + */ + public List getParameterizedCompletionsAt(JTextComponent tc) { + CompletionProvider provider = getProviderFor(tc); + return provider!=null ? provider.getParameterizedCompletionsAt(tc) : + null; + } + + private CompletionProvider getProviderFor(JTextComponent comp) { RSyntaxTextArea rsta = (RSyntaxTextArea)comp; @@ -201,6 +211,7 @@ public class CCompletionProvider extends AbstractCompletionProvider{ return getCommentCompletionProvider(); case Token.COMMENT_DOCUMENTATION: return getDocCommentCompletionProvider(); + case Token.NULL: case Token.WHITESPACE: case Token.IDENTIFIER: case Token.VARIABLE: diff --git a/src/org/fife/ui/autocomplete/CompletionCellRenderer.java b/src/org/fife/ui/autocomplete/CompletionCellRenderer.java index 92401bf..6df1ad1 100644 --- a/src/org/fife/ui/autocomplete/CompletionCellRenderer.java +++ b/src/org/fife/ui/autocomplete/CompletionCellRenderer.java @@ -148,7 +148,7 @@ public class CompletionCellRenderer extends DefaultListCellRenderer { sb.append(fc.getName()); sb.append("</em></b>"); - sb.append('('); + sb.append(fc.getProvider().getParameterListStart()); int paramCount = fc.getParamCount(); for (int i=0; i<paramCount; i++) { FunctionCompletion.Parameter param = fc.getParam(i); @@ -170,10 +170,11 @@ public class CompletionCellRenderer extends DefaultListCellRenderer { sb.append(name); } if (i<paramCount-1) { - sb.append(", "); + sb.append(fc.getProvider().getParameterListSeparator()); } } - sb.append(") : "); + sb.append(fc.getProvider().getParameterListEnd()); + sb.append(" : "); if (!selected) { sb.append("<font color='#a0a0ff'>"); } diff --git a/src/org/fife/ui/autocomplete/CompletionProvider.java b/src/org/fife/ui/autocomplete/CompletionProvider.java index 26b86ea..f5ebcab 100644 --- a/src/org/fife/ui/autocomplete/CompletionProvider.java +++ b/src/org/fife/ui/autocomplete/CompletionProvider.java @@ -74,6 +74,53 @@ public interface CompletionProvider { /** + * Returns a list of parameterized completions that have been entered + * at the current caret position of a text component (and thus can have + * their completion choices displayed). + * + * @param tc The text component. + * @return The list of {@link ParameterizedCompletion}s. If no completions + * are available, this may be <code>null</code>. + */ + public List getParameterizedCompletionsAt(JTextComponent tc); + + + /** + * Returns the text that marks the end of a list of parameters to a + * function or method. + * + * @return The text for a parameter list end, for example, + * "<code>)</code>". + * @see #getParameterListStart() + * @see #getParameterListSeparator() + */ + public String getParameterListEnd(); + + + /** + * Returns the text that separates parameters to a function or method. + * + * @return The text that separates parameters, for example, + * "<code>, </code>". + * @see #getParameterListStart() + * @see #getParameterListEnd() + */ + public String getParameterListSeparator(); + + + /** + * Returns the text that marks the start of a list of parameters to a + * function or method. + * + * @return The text for a parameter list start, for example, + * "<code>(</code>". + * @see #getParameterListEnd() + * @see #getParameterListSeparator() + */ + public String getParameterListStart(); + + + /** * Returns the parent completion provider. * * @return The parent completion provider. diff --git a/src/org/fife/ui/autocomplete/DefaultCompletionProvider.java b/src/org/fife/ui/autocomplete/DefaultCompletionProvider.java index 4a0dfc9..f1737ce 100644 --- a/src/org/fife/ui/autocomplete/DefaultCompletionProvider.java +++ b/src/org/fife/ui/autocomplete/DefaultCompletionProvider.java @@ -119,6 +119,63 @@ public class DefaultCompletionProvider extends AbstractCompletionProvider { /** + * {@inheritDoc} + */ + public List getParameterizedCompletionsAt(JTextComponent tc) { + + List list = null; + + String paramListStart = getParameterListStart(); + if (paramListStart==null) { + return list; // null + } + + int dot = tc.getCaretPosition(); + Segment s = new Segment(); + Document doc = tc.getDocument(); + Element root = doc.getDefaultRootElement(); + int line = root.getElementIndex(dot); + Element elem = root.getElement(line); + int offs = elem.getStartOffset(); + int len = dot - offs - paramListStart.length(); + if (len<=0) { // Not enough chars on line for a method. + return list; // null + } + + try { + + doc.getText(offs, len, s); + offs = s.offset + len - 1; + while (offs>=s.offset && Character.isWhitespace(s.array[offs])) { + offs--; + } + int end = offs; + while (offs>=s.offset && isValidChar(s.array[offs])) { + System.out.println("... Examining '" + s.array[offs] + "'"); + offs--; + } + + String text = new String(s.array, offs+1, end-offs); + System.out.println("... ... \"" + text + "\""); + + Completion c = getCompletionByInputText(text); + if (c instanceof ParameterizedCompletion) { + if (list==null) { + list = new ArrayList(1); + } + list.add(c); + } + + } catch (BadLocationException ble) { + ble.printStackTrace(); // Never happens + } + + return list; + + } + + + /** * Initializes this completion provider. */ protected void init() { diff --git a/src/org/fife/ui/autocomplete/FunctionCompletion.java b/src/org/fife/ui/autocomplete/FunctionCompletion.java index ff5c05d..31ccfaf 100644 --- a/src/org/fife/ui/autocomplete/FunctionCompletion.java +++ b/src/org/fife/ui/autocomplete/FunctionCompletion.java @@ -32,7 +32,8 @@ import java.util.List; * @author Robert Futrell * @version 1.0 */ -public class FunctionCompletion extends VariableCompletion { +public class FunctionCompletion extends VariableCompletion + implements ParameterizedCompletion { /** * Parameters to the function. @@ -54,42 +55,9 @@ public class FunctionCompletion extends VariableCompletion { protected void addDefinitionString(StringBuffer sb) { - sb.append("<html><b>"); - - // Add the return type if applicable (C macros like NULL have no type). - String type = getType(); - if (type!=null) { - appendPossibleDataType(sb, type); - sb.append(' '); - } - - // Add the item being described's name. - sb.append(getName()); - - // Add parameters for functions. - sb.append('('); - for (int i=0; i<getParamCount(); i++) { - Parameter param = getParam(i); - type = param.getType(); - String name = param.getName(); - if (type!=null) { - appendPossibleDataType(sb, type); - if (name!=null) { - sb.append(' '); - } - } - if (name!=null) { - sb.append(name); - } - if (i<params.size()-1) { - sb.append(", "); - } - } - sb.append(')'); - + sb.append(getDefinitionString()); sb.append("</b>"); - } @@ -123,6 +91,54 @@ public class FunctionCompletion extends VariableCompletion { /** + * Returns the "definition string" for this function completion. For + * example, for the C "<code>printf</code>" function, this would return + * "<code>int printf(const char *, ...)</code>". + * + * @return The definition string. + */ + public String getDefinitionString() { + + StringBuffer sb = new StringBuffer(); + + // Add the return type if applicable (C macros like NULL have no type). + String type = getType(); + if (type!=null) { + appendPossibleDataType(sb, type); + sb.append(' '); + } + + // Add the item being described's name. + sb.append(getName()); + + // Add parameters for functions. + CompletionProvider provider = getProvider(); + sb.append(provider.getParameterListStart()); + for (int i=0; i<getParamCount(); i++) { + Parameter param = getParam(i); + type = param.getType(); + String name = param.getName(); + if (type!=null) { + appendPossibleDataType(sb, type); + if (name!=null) { + sb.append(' '); + } + } + if (name!=null) { + sb.append(name); + } + if (i<params.size()-1) { + sb.append(provider.getParameterListSeparator()); + } + } + sb.append(provider.getParameterListEnd()); + + return sb.toString(); + + } + + + /** * Returns the specified {@link Parameter}. * * @param index The index of the parameter to retrieve. @@ -172,54 +188,4 @@ public class FunctionCompletion extends VariableCompletion { } - /** - * A parameter passed to a function. - * - * @author Robert Futrell - * @version 1.0 - */ - public static class Parameter { - - private String name; - private String type; - private String desc; - - public Parameter(String type, String name) { - this.name = name; - this.type = type; - } - - public String getDescription() { - return desc; - } - - public String getName() { - return name; - } - - public String getType() { - return type; - } - - public void setDescription(String desc) { - this.desc = desc; - } - - public String toString() { - StringBuffer sb = new StringBuffer(); - if (type!=null) { - sb.append(type); - } - if (name!=null) { - if (type!=null) { - sb.append(' '); - sb.append(name); - } - } - return sb.toString(); - } - - } - - } \ No newline at end of file diff --git a/src/org/fife/ui/autocomplete/JarCompletionProvider.java b/src/org/fife/ui/autocomplete/JarCompletionProvider.java index 92a6dfd..b6a0839 100644 --- a/src/org/fife/ui/autocomplete/JarCompletionProvider.java +++ b/src/org/fife/ui/autocomplete/JarCompletionProvider.java @@ -149,6 +149,14 @@ public class JarCompletionProvider extends AbstractCompletionProvider { /** + * {@inheritDoc} + */ + public List getParameterizedCompletionsAt(JTextComponent tc) { + return null; // This provider knows no functions or methods. + } + + + /** * Returns whether the specified character is valid in an auto-completion. * * @param ch The character. diff --git a/src/org/fife/ui/autocomplete/MarkupTagCompletion.java b/src/org/fife/ui/autocomplete/MarkupTagCompletion.java index c9ad95b..113c970 100644 --- a/src/org/fife/ui/autocomplete/MarkupTagCompletion.java +++ b/src/org/fife/ui/autocomplete/MarkupTagCompletion.java @@ -26,7 +26,7 @@ package org.fife.ui.autocomplete; import java.util.ArrayList; import java.util.List; -import org.fife.ui.autocomplete.FunctionCompletion.Parameter; +import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter; /** diff --git a/src/org/fife/ui/autocomplete/OutlineHighlightPainter.java b/src/org/fife/ui/autocomplete/OutlineHighlightPainter.java new file mode 100644 index 0000000..1e3b3ee --- /dev/null +++ b/src/org/fife/ui/autocomplete/OutlineHighlightPainter.java @@ -0,0 +1,133 @@ +package org.fife.ui.autocomplete; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; +import java.io.Serializable; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultHighlighter; +import javax.swing.text.JTextComponent; +import javax.swing.text.Position; +import javax.swing.text.View; + + +/** + * Highlight painter that draws an outline around the text. This is used to + * draw bounds around function/method parameters. + * + * @author Robert Futrell + * @version 1.0 + */ +/* + * NOTE: Whenever you see text like "Workaround for Java Highlight issues", + * this is because highlighted text in a JTextComponent gets "pushed" forward + * when the caret is at the Highlight's start, when we need it to instead get + * prepended to. For this reason, the autocomplete package adds its Highlights + * 1 char too long (1 char earlier than where it should really start), but only + * paint the Highlight from the 2nd char on. + */ +class OutlineHighlightPainter extends + DefaultHighlighter.DefaultHighlightPainter implements Serializable { + + /** + * DefaultHighlightPainter doesn't allow changing color, so we must cache + * ours here. + */ + private Color color; + + + /** + * Constructor. + * + * @param color The color to draw the bounding boxes with. This cannot + * be <code>null</code>. + */ + public OutlineHighlightPainter(Color color) { + super(color); + setColor(color); + } + + + /** + * Returns the color to paint bounding boxes with. + * + * @return The color. + * @see #setColor(Color) + */ + public Color getColor() { + return color; + } + + + /** + * {@inheritDoc} + */ + public Shape paintLayer(Graphics g, int p0, int p1, Shape viewBounds, + JTextComponent c, View view) { + + g.setColor(getColor()); + p0++; // Workaround for Java Highlight issues. + + // This special case isn't needed for most standard Swing Views (which + // always return a width of 1 for modelToView() calls), but it is + // needed for RSTA views, which actually return the width of chars for + // modelToView calls. But this should be faster anyway, as we + // short-circuit and do only one modelToView() for one offset. + if (p0==p1) { + try { + Shape s = view.modelToView(p0, viewBounds, + Position.Bias.Forward); + Rectangle r = s.getBounds(); + g.drawLine(r.x, r.y, r.x, r.y+r.height); + return r; + } catch (BadLocationException ble) { + ble.printStackTrace(); // Never happens + return null; + } + } + + if (p0 == view.getStartOffset() && p1 == view.getEndOffset()) { + // Contained in view, can just use bounds. + Rectangle alloc; + if (viewBounds instanceof Rectangle) { + alloc = (Rectangle) viewBounds; + } else { + alloc = viewBounds.getBounds(); + } + g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height - 1); + return alloc; + } + + // Should only render part of View. + try { + // --- determine locations --- + Shape shape = view.modelToView(p0, Position.Bias.Forward, p1, + Position.Bias.Backward, viewBounds); + Rectangle r = (shape instanceof Rectangle) ? (Rectangle) shape + : shape.getBounds(); + g.drawRect(r.x, r.y, r.width - 1, r.height - 1); + return r; + } catch (BadLocationException e) { // Never happens + e.printStackTrace(); + return null; + } + + } + + + /** + * Sets the color to paint the bounding boxes with. + * + * @param color The new color. This cannot be <code>null</code>. + * @see #getColor() + */ + public void setColor(Color color) { + if (color==null) { + throw new IllegalArgumentException("color cannot be null"); + } + this.color = color; + } + + +} \ No newline at end of file diff --git a/src/org/fife/ui/autocomplete/ParameterizedCompletion.java b/src/org/fife/ui/autocomplete/ParameterizedCompletion.java new file mode 100644 index 0000000..30ee1f2 --- /dev/null +++ b/src/org/fife/ui/autocomplete/ParameterizedCompletion.java @@ -0,0 +1,92 @@ +package org.fife.ui.autocomplete; + + +/** + * A completion option that takes parameters, such as a function or method. + * + * @author Robert Futrell + * @version 1.0 + */ +public interface ParameterizedCompletion extends Completion { + + + /** + * Returns the "definition string" for this completion. For examle, + * for the C "<code>printf</code>" function, this would return + * "<code>int printf(const char *, ...)</code>". + * + * @return The definition string. + */ + public String getDefinitionString(); + + + /** + * Returns the specified {@link Parameter}. + * + * @param index The index of the parameter to retrieve. + * @return The parameter. + * @see #getParamCount() + */ + public Parameter getParam(int index); + + + /** + * Returns the number of parameters this completion takes. + * + * @return The number of parameters this completion takes. + * @see #getParam(int) + */ + public int getParamCount(); + + + /** + * A parameter passed to a parameterized {@link Completion}. + * + * @author Robert Futrell + * @version 1.0 + */ + public static class Parameter { + + private String name; + private String type; + private String desc; + + public Parameter(String type, String name) { + this.name = name; + this.type = type; + } + + public String getDescription() { + return desc; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public void setDescription(String desc) { + this.desc = desc; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + if (type!=null) { + sb.append(type); + } + if (name!=null) { + if (type!=null) { + sb.append(' '); + } + sb.append(name); + } + return sb.toString(); + } + + } + + +} \ No newline at end of file diff --git a/src/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java b/src/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java new file mode 100644 index 0000000..9a43069 --- /dev/null +++ b/src/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java @@ -0,0 +1,721 @@ +/* + * 12/21/2008 + * + * AutoCompleteDescWindow.java - A window containing a description of the + * currently selected completion. + * Copyright (C) 2008 Robert Futrell + * robert_futrell at users.sourceforge.net + * http://fifesoft.com/rsyntaxtextarea + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +package org.fife.ui.autocomplete; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.SystemColor; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.InputMap; +import javax.swing.JLabel; +import javax.swing.JWindow; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; +import javax.swing.text.Position; +import javax.swing.text.Highlighter.Highlight; + + +/** + * A "tooltip" that displays information on the function or method currently + * being entered. + * + * @author Robert Futrell + * @version 1.0 + */ +class ParameterizedCompletionDescriptionToolTip { + + /** + * The actual tooltip. + */ + private JWindow tooltip; + + /** + * The painter to paint borders around the variables. + */ + Highlighter.HighlightPainter p; + + /** + * The tags for the highlights around parameters. + */ + List tags; + + /** + * The parent AutoCompletion instance. + */ + private AutoCompletion ac; + + /** + * The label that holds the description. + */ + private JLabel descLabel; + + /** + * The completion being described. + */ + private ParameterizedCompletion pc; + + /** + * Listens for events in the text component while this window is vislble. + */ + private Listener listener; + + /** + * The minimum offset into the document that the caret can move to + * before this tooltip disappears. + */ + private int minPos; + + /** + * The maximum offset into the document that the caret can move to + * before this tooltip disappears. + */ + private Position maxPos; // Moves with text inserted. + + /** + * The currently "selected" parameter in the displayed text. + */ + private int lastSelectedParam; + + private Object oldTabKey; + private Action oldTabAction; + private Object oldShiftTabKey; + private Action oldShiftTabAction; + private Object oldEscapeKey; + private Action oldEscapeAction; + private Object oldClosingKey; + private Action oldClosingAction; + + private static final String IM_KEY_TAB = "ParamCompDescToolTip.Tab"; + private static final String IM_KEY_SHIFT_TAB = "ParamCompDescToolTip.ShiftTab"; + private static final String IM_KEY_ESCAPE = "ParamCompDescToolTip.Escape"; + private static final String IM_KEY_CLOSING = "ParamCompDescToolTip.Closing"; + + + /** + * Constructor. + * + * @param owner The parent window. + * @param ac The parent autocompletion. + * @param pc The completion being described. + */ + public ParameterizedCompletionDescriptionToolTip(Window owner, + AutoCompletion ac, ParameterizedCompletion pc) { + + tooltip = new JWindow(owner); + this.ac = ac; + this.pc = pc; + + descLabel = new JLabel(); + descLabel.setOpaque(true); + descLabel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(Color.BLACK), + BorderFactory.createEmptyBorder(2, 5, 2, 5))); + descLabel.setBackground(getDefaultBackground()); + tooltip.setContentPane(descLabel); + + lastSelectedParam = -1; + updateText(0); + + tooltip.setFocusableWindowState(false); + listener = new Listener(); + + p = new OutlineHighlightPainter(Color.GRAY); + tags = new ArrayList(1); // Usually small + + } + + + /** + * Returns the default background color to use for the description + * window. + * + * @return The default background color. + */ + protected Color getDefaultBackground() { + Color c = UIManager.getColor("ToolTip.background"); + if (c==null) { // Some LookAndFeels like Nimbus + c = UIManager.getColor("info"); // Used by Nimbus (and others) + if (c==null) { + c = SystemColor.infoText; // System default + } + } + return c; + } + + + private List getParameterHighlights() { + List paramHighlights = new ArrayList(1); + JTextComponent tc = ac.getTextComponent(); + Highlight[] highlights = tc.getHighlighter().getHighlights(); + for (int i=0; i<highlights.length; i++) { + if (highlights[i].getPainter()==p) { + paramHighlights.add(highlights[i]); + } + } + return paramHighlights; + } + + + /** + * Installs key bindings on the text component that facilitate the user + * editing this completion's parameters. + * + * @see #uninstallKeyBindings() + */ + private void installKeyBindings() { + + JTextComponent tc = ac.getTextComponent(); + InputMap im = tc.getInputMap(); + ActionMap am = tc.getActionMap(); + + KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); + oldTabKey = im.get(ks); + im.put(ks, IM_KEY_TAB); + oldTabAction = am.get(IM_KEY_TAB); + am.put(IM_KEY_TAB, new NextParamAction()); + + ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK); + oldShiftTabKey = im.get(ks); + im.put(ks, IM_KEY_SHIFT_TAB); + oldShiftTabAction = am.get(IM_KEY_SHIFT_TAB); + am.put(IM_KEY_SHIFT_TAB, new PrevParamAction()); + + ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + oldEscapeKey = im.get(ks); + im.put(ks, IM_KEY_ESCAPE); + oldEscapeAction = am.get(IM_KEY_ESCAPE); + am.put(IM_KEY_ESCAPE, new HideAction()); + + String end = pc.getProvider().getParameterListEnd(); + if (end.length()==1) { // Practically always true, usually ')' + char ch = end.charAt(0); + ks = KeyStroke.getKeyStroke(ch); + oldClosingKey = im.get(ks); + im.put(ks, IM_KEY_CLOSING); + oldClosingAction = am.get(IM_KEY_CLOSING); + am.put(IM_KEY_CLOSING, new ClosingAction()); + } + } + + + /** + * Moves to and selects the next parameter. + * + * @see #moveToPreviousParam() + */ + private void moveToNextParam() { + + JTextComponent tc = ac.getTextComponent(); + int dot = tc.getCaretPosition(); + + int tagCount = tags.size(); + if (tagCount==0) { + tc.setCaretPosition(maxPos.getOffset()); + tooltip.setVisible(false); + } + + Highlight currentNext = null; + int pos = -1; + List highlights = getParameterHighlights(); + for (int i=0; i<highlights.size(); i++) { + Highlight hl = (Highlight)highlights.get(i); + if (currentNext==null || currentNext.getStartOffset()<=dot || + (hl.getStartOffset()>dot && + hl.getStartOffset()<=currentNext.getStartOffset())) { + currentNext = hl; + pos = i; + } + } + + if (currentNext!=null && dot<currentNext.getStartOffset()) { + // "+1" is a workaround for Java Highlight issues. + tc.setSelectionStart(currentNext.getStartOffset()+1); + tc.setSelectionEnd(currentNext.getEndOffset()); + updateText(pos); + } + else { + tc.setCaretPosition(maxPos.getOffset()); + tooltip.setVisible(false); + } + + } + + + /** + * Moves to and selects the previous parameter. + * + * @see #moveToNextParam() + */ + private void moveToPreviousParam() { + + JTextComponent tc = ac.getTextComponent(); + + int tagCount = tags.size(); + if (tagCount==0) { // Should never happen + tc.setCaretPosition(maxPos.getOffset()); + tooltip.setVisible(false); + } + + int dot = tc.getCaretPosition(); + int selStart = tc.getSelectionStart()-1; // Workaround for Java Highlight issues. + Highlight currentPrev = null; + int pos = 0; + Highlighter h = tc.getHighlighter(); + Highlight[] highlights = h.getHighlights(); + for (int i=0; i<highlights.length; i++) { + Highlight hl = highlights[i]; + if (hl.getPainter()==p) { // Only way to identify our own higlights + if (currentPrev==null || currentPrev.getStartOffset()>=dot || + (hl.getStartOffset()<selStart && + hl.getStartOffset()>currentPrev.getStartOffset())) { + currentPrev = hl; + pos = i; + } + } + } + + if (currentPrev!=null && dot>currentPrev.getStartOffset()) { + // "+1" is a workaround for Java Highlight issues. + tc.setSelectionStart(currentPrev.getStartOffset()+1); + tc.setSelectionEnd(currentPrev.getEndOffset()); + updateText(pos); + } + else { + tc.setCaretPosition(maxPos.getOffset()); + tooltip.setVisible(false); + } + + } + + + /** + * Removes the bounding boxes around parameters. + */ + private void removeParameterHighlights() { + JTextComponent tc = ac.getTextComponent(); + Highlighter h = tc.getHighlighter(); + for (int i=0; i<tags.size(); i++) { + h.removeHighlight(tags.get(i)); + } + tags.clear(); + } + + + /** + * Sets the location of this tooltip relative to the given rectangle. + * + * @param r The visual position of the caret (in screen coordinates). + */ + public void setLocationRelativeTo(Rectangle r) { + + Dimension screenSize = tooltip.getToolkit().getScreenSize(); + + // Try putting our stuff "above" the caret first. + int y = r.y - 5 - tooltip.getHeight(); + if (y<0) { + y = r.y + r.height + 5; + } + + // Get x-coordinate of completions. Try to align left edge with the + // caret first. + int x = r.x; + if (x<0) { + x = 0; + } + else if (x+tooltip.getWidth()>screenSize.width) { // completions don't fit + x = screenSize.width - tooltip.getWidth(); + } + + tooltip.setLocation(x, y); + + } + + + /** + * Toggles the visibility of this tooltip. + * + * @param visible Whether the tooltip should be visible. + * @param addParamListStart Whether or not + * {@link CompletionProvider#getParameterListStart()} should be + * added to the text component. If <code>visible</code> is + * <code>false</code>, this parameter is ignored. + */ + public void setVisible(boolean visible, boolean addParamListStart) { + if (visible!=tooltip.isVisible()) { + JTextComponent tc = ac.getTextComponent(); + if (visible) { + listener.install(tc, addParamListStart); + } + else { + listener.uninstall(); + } + tooltip.setVisible(visible); + } + } + + + /** + * Removes the key bindings we installed. + * + * @see #installKeyBindings() + */ + private void uninstallKeyBindings() { + + JTextComponent tc = ac.getTextComponent(); + InputMap im = tc.getInputMap(); + ActionMap am = tc.getActionMap(); + + KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); + im.put(ks, oldTabKey); + am.put(IM_KEY_TAB, oldTabAction); + + ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK); + im.put(ks, oldShiftTabKey); + am.put(IM_KEY_SHIFT_TAB, oldShiftTabAction); + + ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + im.put(ks, oldEscapeKey); + am.put(IM_KEY_ESCAPE, oldEscapeAction); + + String end = pc.getProvider().getParameterListEnd(); + if (end.length()==1) { // Practically always true, usually ')' + char ch = end.charAt(0); + ks = KeyStroke.getKeyStroke(ch); + im.put(ks, oldClosingKey); + am.put(IM_KEY_CLOSING, oldClosingAction); + } + + } + + + /** + * Updates the text in the tooltip to have the current parameter + * disiplayed in bold. The "current parameter" is determined from the + * current caret position. + */ + private void updateText() { + + JTextComponent tc = ac.getTextComponent(); + int dot = tc.getCaretPosition(); + if (dot>0) { + dot--; // Workaround for Java Highlight issues + } + int index = -1; + + List paramHighlights = getParameterHighlights(); + for (int i=0; i<paramHighlights.size(); i++) { + Highlight h = (Highlight)paramHighlights.get(i); + if (dot>=h.getStartOffset() && dot<h.getEndOffset()) { + index = i; + break; + } + } + + updateText(index); + + } + + + /** + * Updates the text in the tooltip to have the current parameter + * displayed in bold. + * + * @param selectedParam The index of the selected parameter. + */ + private void updateText(int selectedParam) { + + // Don't redo everything if they're just using the arrow keys to move + // through each char of a single parameter, for example. + if (selectedParam==lastSelectedParam) { + return; + } + lastSelectedParam = selectedParam; + + StringBuffer sb = new StringBuffer("<html>"); + int paramCount = pc.getParamCount(); + for (int i=0; i<paramCount; i++) { + if (i==selectedParam) { + sb.append("<b>"); + } + sb.append(pc.getParam(i).toString()); + if (i==selectedParam) { + sb.append("</b>"); + } + if (i<paramCount-1) { + sb.append(pc.getProvider().getParameterListSeparator()); + } + } + + descLabel.setText(sb.toString()); + tooltip.pack(); + + } + + + /** + * Called when the user types the character marking the closing of the + * parameter list, such as '<code>)</code>'. + * + * @author Robert Futrell + * @version 1.0 + */ + private class ClosingAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + JTextComponent tc = ac.getTextComponent(); + tc.setCaretPosition(maxPos.getOffset()); + tooltip.setVisible(false); + } + + } + + + /** + * Action performed when the user hits the escape key. + * + * @author Robert Futrell + * @version 1.0 + */ + private class HideAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + tooltip.setVisible(false); + } + + } + + + /** + * Listens for various events in the text component while this tooltip + * is visible. + * + * @author Robert Futrell + * @version 1.0 + */ + private class Listener implements FocusListener, CaretListener { + + /** + * Called when the text component's caret moves. + * + * @param e The event. + */ + public void caretUpdate(CaretEvent e) { + if (maxPos==null) { // Sanity check + tooltip.setVisible(false); + } + int dot = e.getDot(); + if (dot<minPos || dot>maxPos.getOffset()) { + tooltip.setVisible(false); + } + updateText(); + } + + + /** + * Called when the text component gains focus. + * + * @param e The event. + */ + public void focusGained(FocusEvent e) { + // Do nothing + } + + + /** + * Called when the text component loses focus. + * + * @param e The event. + */ + public void focusLost(FocusEvent e) { + tooltip.setVisible(false); + } + + + /** + * Returns the text to insert for a parameter. + * + * @param param The parameter. + * @return The text. + */ + private String getParamText(ParameterizedCompletion.Parameter param) { + String text = param.getName(); + if (text==null) { + text = param.getType(); + if (text==null) { // Shouldn't ever happen + text = "arg"; + } + } + return text; + } + + + /** + * Installs this listener onto a text component. + * + * @param tc The text component to install onto. + * @param addParamListStart Whether or not + * {@link CompletionProvider#getParameterListStart()} should be + * added to the text component. + * @see #uninstall() + */ + public void install(JTextComponent tc, boolean addParamStartList) { + + // Add listeners to the text component. + tc.addCaretListener(this); + tc.addFocusListener(this); + installKeyBindings(); + + StringBuffer sb = new StringBuffer(); + if (addParamStartList) { + sb.append(pc.getProvider().getParameterListStart()); + } + int dot = tc.getCaretPosition() + sb.length(); + int paramCount = pc.getParamCount(); + List paramLocs = null; + if (paramCount>0) { + paramLocs = new ArrayList(paramCount); + } + Highlighter h = tc.getHighlighter(); + + try { + + // Get the range in which the caret can move before we hide + // this tooltip. + minPos = dot; + maxPos = tc.getDocument().createPosition(dot-sb.length()); + int firstParamLen = 0; + + // Create the text to insert (keep it one completion for + // performance and simplicity of undo/redo). + int start = dot; + for (int i=0; i<paramCount; i++) { + FunctionCompletion.Parameter param = pc.getParam(i); + String paramText = getParamText(param); + if (i==0) { + firstParamLen = paramText.length(); + } + sb.append(paramText); + int end = start + paramText.length(); + paramLocs.add(new Point(start, end)); + if (i<paramCount-1) { + sb.append(pc.getProvider().getParameterListSeparator()); + start = end + 2; + } + } + sb.append(pc.getProvider().getParameterListEnd()); + + // Insert the parameter text and add highlights around the + // parameters. + tc.replaceSelection(sb.toString()); + for (int i=0; i<paramCount; i++) { + Point pt = (Point)paramLocs.get(i); + // "-1" is a workaround for Java Highlight issues. + tags.add(h.addHighlight(pt.x-1, pt.y, p)); + } + + // Go back and start at the first parameter. + tc.setCaretPosition(dot); + if (pc.getParamCount()>0) { + tc.moveCaretPosition(dot+firstParamLen); + } + + } catch (BadLocationException ble) { + ble.printStackTrace(); // Never happens + } + + } + + + /** + * Uninstalls this listener from the current text component. + * + */ + public void uninstall() { + + JTextComponent tc = ac.getTextComponent(); + tc.removeCaretListener(this); + tc.removeFocusListener(this); + uninstallKeyBindings(); + + // Remove WeakReferences in javax.swing.text. + maxPos = null; + minPos = -1; + removeParameterHighlights(); + + } + + + } + + + /** + * Action performed when the user hits the tab key. + * + * @author Robert Futrell + * @version 1.0 + */ + private class NextParamAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + moveToNextParam(); + } + + } + + + /** + * Action performed when the user hits shift+tab. + * + * @author Robert Futrell + * @version 1.0 + */ + private class PrevParamAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + moveToPreviousParam(); + } + + } + + +} \ No newline at end of file -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/autocomplete.git _______________________________________________ pkg-java-commits mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-java-commits

