/*
* 12/23/2008
*
* CompletionCellRenderer.java - Cell renderer that can render the standard
* completion types like Eclipse or NetBeans does.
*
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.autocomplete;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
/**
* A cell renderer that adds some pizazz when rendering the standard
* {@link Completion} types, like Eclipse and NetBeans do. Specifically,
* this renderer handles:
*
* <ul>
* <li>{@link FunctionCompletion}s</li>
* <li>{@link VariableCompletion}s</li>
* <li>{@link MarkupTagCompletion}s</li>
* <li>{@link ShorthandCompletion}s</li>
* <li>{@link TemplateCompletion}s</li>
* </ul>
*
* @author Robert Futrell
* @version 1.0
*/
public class CompletionCellRenderer extends DefaultListCellRenderer {
/**
* The alternating background color, or <code>null</code> if alternating
* row colors should not be used.
*/
private static Color altBG;
/**
* The font to use when rendering items, or <code>null</code> if the
* list's default font should be used.
*/
private Font font;
/**
* Whether to display the types of fields and return types of functions
* in the completion text.
*/
private boolean showTypes;
/**
* The color to use when rendering types in completion text.
*/
private String typeColor;
/**
* During rendering, whether the item being rendered is selected.
*/
private boolean selected;
/**
* During rendering, this is the "real" background color of the item being
* rendered (i.e., what its background color is if it isn't selected).
*/
private Color realBG;
/**
* The color to use for function arguments.
*/
private String paramColor;
/**
* Used in rendering calculations.
*/
private Rectangle paintTextR;
/**
* An optional delegate renderer (primarily for Substance).
*/
private DefaultListCellRenderer delegate;
private static final String SUBSTANCE_RENDERER_CLASS_NAME =
"org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer";
/**
* Keeps the HTML descriptions from "wrapping" in the list, which cuts off
* words.
*/
private static final String PREFIX = "<html><nobr>";
/**
* Constructor.
*/
public CompletionCellRenderer() {
init();
}
/**
* Constructor. This is primarily a hook for Substance, or any other
* Look and Feel whose renderers look drastically different than standard
* <code>DefaultListCellRenderer</code>s. Everything except for the text
* rendering will be done by the delegate. In almost all scenarios, you
* will want to use the no-argument constructor instead of this one.
*
* @param delegate The delegate renderer.
* @see #delegateToSubstanceRenderer()
*/
public CompletionCellRenderer(DefaultListCellRenderer delegate) {
setDelegateRenderer(delegate);
init();
}
/**
* Returns a decent "parameter" color based on the current default
* foreground color.
*
* @return The parameter color to use.
*/
private String createParamColor() {
return Util.isLightForeground(getForeground()) ?
Util.getHexString(Util.getHyperlinkForeground()): "#aa0077";
}
/**
* Returns a decent "type" color based on the current default foreground
* color.
*
* @return The type color to use.
*/
private String createTypeColor() {
return "#808080";
}
/**
* Attempts to delegate rendering to a Substance cell renderer. This
* should only be called if Substance is known to be on the classpath.
*
* @throws Exception If Substance is not on the classpath, or some other
* error occurs creating the Substance cell renderer.
* @see Util#getUseSubstanceRenderers()
* @see #setDelegateRenderer(DefaultListCellRenderer)
*/
public void delegateToSubstanceRenderer() throws Exception {
Class<?> clazz = Class.forName(SUBSTANCE_RENDERER_CLASS_NAME);
DefaultListCellRenderer delegate =
(DefaultListCellRenderer)clazz.newInstance();
setDelegateRenderer(delegate);
}
/**
* Returns the background color to use on alternating lines.
*
* @return The alternate background color. If this is <code>null</code>,
* alternating colors are not used.
* @see #setAlternateBackground(Color)
*/
public static Color getAlternateBackground() {
return altBG;
}
/**
* Returns the delegate renderer, or <code>null</code> if there is none.
*
* @return The delegate renderer.
* @see #setDelegateRenderer(DefaultListCellRenderer)
*/
public DefaultListCellRenderer getDelegateRenderer() {
return delegate;
}
/**
* Returns the font used when rendering completions.
*
* @return The font. If this is <code>null</code>, then the default list
* font is used.
* @see #setDisplayFont(Font)
*/
public Font getDisplayFont() {
return font;
}
/**
* Returns the renderer.
*
* @param list The list of choices being rendered.
* @param value The {@link Completion} being rendered.
* @param index The index into <code>list</code> being rendered.
* @param selected Whether the item is selected.
* @param hasFocus Whether the item has focus.
*/
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean selected, boolean hasFocus) {
super.getListCellRendererComponent(list,value,index,selected,hasFocus);
if (font!=null) {
setFont(font); // Overrides super's setFont(list.getFont()).
}
this.selected = selected;
this.realBG = altBG!=null && (index&1)==1 ? altBG : list.getBackground();
Completion c = (Completion)value;
setIcon(c.getIcon());
if (c instanceof FunctionCompletion) {
FunctionCompletion fc = (FunctionCompletion)value;
prepareForFunctionCompletion(list, fc, index, selected, hasFocus);
}
else if (c instanceof VariableCompletion) {
VariableCompletion vc = (VariableCompletion)value;
prepareForVariableCompletion(list, vc, index, selected, hasFocus);
}
else if (c instanceof TemplateCompletion) {
TemplateCompletion tc = (TemplateCompletion)value;
prepareForTemplateCompletion(list, tc, index, selected, hasFocus);
}
else if (c instanceof MarkupTagCompletion) {
MarkupTagCompletion mtc = (MarkupTagCompletion)value;
prepareForMarkupTagCompletion(list, mtc, index, selected, hasFocus);
}
else {
prepareForOtherCompletion(list, c, index, selected, hasFocus);
}
// A delegate renderer might do its own alternate row striping
// (Substance does).
if (delegate!=null) {
delegate.getListCellRendererComponent(list, getText(), index,
selected, hasFocus);
delegate.setFont(getFont());
delegate.setIcon(getIcon());
return delegate;
}
if (!selected && (index&1)==1 && altBG!=null) {
setBackground(altBG);
}
return this;
}
/**
* Returns whether the types of fields and return types of methods are
* shown in the completion text.
*
* @return Whether to show the types.
* @see #setShowTypes(boolean)
*/
public boolean getShowTypes() {
return showTypes;
}
private void init() {
//setDisplayFont(new Font("Monospaced", Font.PLAIN, 12));
setShowTypes(true);
typeColor = createTypeColor();
paramColor = createParamColor();
paintTextR = new Rectangle();
}
@Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
g.setColor(realBG);
int iconW = 0;
if (getIcon()!=null) {
iconW = getIcon().getIconWidth();
}
if (selected && iconW>0) { // The icon area is never in the "selection"
g.fillRect(0, 0, iconW, getHeight());
g.setColor(getBackground());
g.fillRect(iconW,0, getWidth()-iconW,getHeight());
}
else {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
if (getIcon()!=null) {
getIcon().paintIcon(this, g, 0, 0);
}
String text = getText();
if (text != null) {
paintTextR.setBounds(iconW,0, getWidth()-iconW,getHeight());
paintTextR.x += 3; // Force a slight margin
int space = paintTextR.height - g.getFontMetrics().getHeight();
View v = (View)getClientProperty(BasicHTML.propertyKey);
if (v != null) {
// HTML rendering doesn't auto-center vertically, for some
// reason
paintTextR.y += space/2;
paintTextR.height -= space;
v.paint(g, paintTextR);
}
else {
int textX = paintTextR.x;
int textY = paintTextR.y;// + g.getFontMetrics().getAscent();
//System.out.println(g.getFontMetrics().getAscent());
g.drawString(text, textX, textY);
}
}
}
/**
* Prepares this renderer to display a function completion.
*
* @param list The list of choices being rendered.
* @param fc The completion to render.
* @param index The index into <code>list</code> being rendered.
* @param selected Whether the item is selected.
* @param hasFocus Whether the item has focus.
*/
protected void prepareForFunctionCompletion(JList list,
FunctionCompletion fc, int index, boolean selected, boolean hasFocus) {
StringBuilder sb = new StringBuilder(PREFIX);
sb.append(fc.getName());
char paramListStart = fc.getProvider().getParameterListStart();
if (paramListStart!=0) { // 0 => no start char
sb.append(paramListStart);
}
int paramCount = fc.getParamCount();
for (int i=0; i<paramCount; i++) {
FunctionCompletion.Parameter param = fc.getParam(i);
String type = param.getType();
String name = param.getName();
if (type!=null) {
if (!selected) {
sb.append("<font color='").append(paramColor).append("'>");
}
sb.append(type);
if (!selected) {
sb.append("</font>");
}
if (name!=null) {
sb.append(' ');
}
}
if (name!=null) {
sb.append(name);
}
if (i<paramCount-1) {
sb.append(fc.getProvider().getParameterListSeparator());
}
}
char paramListEnd = fc.getProvider().getParameterListEnd();
if (paramListEnd!=0) { // 0 => No parameter list end char
sb.append(paramListEnd);
}
if (getShowTypes() && fc.getType()!=null) {
sb.append(" : ");
if (!selected) {
sb.append("<font color='").append(typeColor).append("'>");
}
sb.append(fc.getType());
if (!selected) {
sb.append("</font>");
}
}
setText(sb.toString());
}
/**
* Prepares this renderer to display a markup tag completion.
*
* @param list The list of choices being rendered.
* @param mc The completion to render.
* @param index The index into <code>list</code> being rendered.
* @param selected Whether the item is selected.
* @param hasFocus Whether the item has focus.
*/
protected void prepareForMarkupTagCompletion(JList list,
MarkupTagCompletion mc, int index, boolean selected, boolean hasFocus) {
StringBuilder sb = new StringBuilder(PREFIX);
sb.append(mc.getName());
setText(sb.toString());
}
/**
* Prepares this renderer to display a completion not specifically handled
* elsewhere.
*
* @param list The list of choices being rendered.
* @param c The completion to render.
* @param index The index into <code>list</code> being rendered.
* @param selected Whether the item is selected.
* @param hasFocus Whether the item has focus.
*/
protected void prepareForOtherCompletion(JList list,
Completion c, int index, boolean selected, boolean hasFocus) {
StringBuilder sb = new StringBuilder(PREFIX);
sb.append(c.getInputText());
if (c instanceof BasicCompletion) {
String definition = ((BasicCompletion)c).getShortDescription();
if (definition!=null) {
sb.append(" - ");
if (!selected) {
sb.append("<font color='").append(typeColor).append("'>");
}
sb.append(definition);
if (!selected) {
sb.append("</font>");
}
}
}
setText(sb.toString());
}
/**
* Prepares this renderer to display a template completion.
*
* @param list The list of choices being rendered.
* @param tc The completion to render.
* @param index The index into <code>list</code> being rendered.
* @param selected Whether the item is selected.
* @param hasFocus Whether the item has focus.
*/
protected void prepareForTemplateCompletion(JList list,
TemplateCompletion tc, int index, boolean selected, boolean hasFocus) {
StringBuilder sb = new StringBuilder(PREFIX);
sb.append(tc.getInputText());
String definition = tc.getShortDescription();
if (definition!=null) {
sb.append(" - ");
if (!selected) {
sb.append("<font color='").append(typeColor).append("'>");
}
sb.append(definition);
if (!selected) {
sb.append("</font>");
}
}
setText(sb.toString());
}
/**
* Prepares this renderer to display a variable completion.
*
* @param list The list of choices being rendered.
* @param vc The completion to render.
* @param index The index into <code>list</code> being rendered.
* @param selected Whether the item is selected.
* @param hasFocus Whether the item has focus.
*/
protected void prepareForVariableCompletion(JList list,
VariableCompletion vc, int index, boolean selected, boolean hasFocus) {
StringBuilder sb = new StringBuilder(PREFIX);
sb.append(vc.getName());
if (getShowTypes() && vc.getType()!=null) {
sb.append(" : ");
if (!selected) {
sb.append("<font color='").append(typeColor).append("'>");
}
sb.append(vc.getType());
if (!selected) {
sb.append("</font>");
}
}
setText(sb.toString());
}
/**
* Sets the background color to use on alternating lines.
*
* @param altBG The new alternate background color. If this is
* <code>null</code>, alternating lines will not use different
* background colors.
* @see #getAlternateBackground()
*/
public static void setAlternateBackground(Color altBG) {
CompletionCellRenderer.altBG = altBG;
}
/**
* Sets the delegate renderer. Most users will never use this method; it
* is primarily a hook for Substance and other Look and Feels whose
* renderers look drastically different from the standard
* <code>DefaultListCellRenderer</code>.
*
* @param delegate The new delegate renderer. If this is <code>null</code>,
* the default rendering of this component is used.
* @see #getDelegateRenderer()
* @see #delegateToSubstanceRenderer()
*/
public void setDelegateRenderer(DefaultListCellRenderer delegate) {
this.delegate = delegate;
}
/**
* Sets the font to use when rendering completion items.
*
* @param font The font to use. If this is <code>null</code>, then
* the default list font is used.
* @see #getDisplayFont()
*/
public void setDisplayFont(Font font) {
this.font = font;
}
/**
* Sets the icon to display based off of a completion, falling back to a
* default icon if the completion has no icon.
*
* @param completion The completion to check.
* @param defaultIcon The icon to use if <code>completion</code> does not
* specify an icon.
*/
protected void setIconWithDefault(Completion completion, Icon defaultIcon) {
Icon icon = completion.getIcon();
setIcon(icon!=null ? icon : defaultIcon);
}
/**
* Sets the color to use for function arguments.
*
* @param color The color to use. This is ignored if <code>null</code>.
* @see #setTypeColor(Color)
*/
public void setParamColor(Color color) {
if (color!=null) {
paramColor = Util.getHexString(color);
}
}
/**
* Sets whether the types of fields and return types of methods are
* shown in the completion text.
*
* @param show Whether to show the types.
* @see #getShowTypes()
*/
public void setShowTypes(boolean show) {
this.showTypes = show;
}
/**
* Sets the color to use for function/field types. Note that if
* {@link #getShowTypes()} returns <code>false</code>, this property
* effectively does nothing.
*
* @param color The color to use for types. This is ignored if
* <code>null</code>.
* @see #setShowTypes(boolean)
* @see #setParamColor(Color)
*/
public void setTypeColor(Color color) {
if (color!=null) {
typeColor = Util.getHexString(color);
}
}
/**
* Overridden to update our delegate, if necessary.
*/
@Override
public void updateUI() {
super.updateUI();
if (delegate!=null) {
SwingUtilities.updateComponentTreeUI(delegate);
}
paramColor = createParamColor();
}
}