2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.codeInsight.lookup.impl;
19 import com.intellij.codeInsight.lookup.LookupElement;
20 import com.intellij.codeInsight.lookup.LookupElementPresentation;
21 import com.intellij.codeInsight.lookup.LookupValueWithUIHint;
22 import com.intellij.codeInsight.lookup.RealLookupElementPresentation;
23 import com.intellij.ide.ui.UISettings;
24 import com.intellij.openapi.editor.colors.EditorColorsScheme;
25 import com.intellij.openapi.editor.colors.EditorFontType;
26 import com.intellij.openapi.util.TextRange;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.psi.codeStyle.NameUtil;
29 import com.intellij.ui.Gray;
30 import com.intellij.ui.LayeredIcon;
31 import com.intellij.ui.SimpleColoredComponent;
32 import com.intellij.ui.SimpleTextAttributes;
33 import com.intellij.ui.speedSearch.SpeedSearchUtil;
34 import com.intellij.util.ui.EmptyIcon;
35 import com.intellij.util.ui.UIUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
40 import javax.swing.border.EmptyBorder;
43 public class LookupCellRenderer implements ListCellRenderer {
44 private static final int AFTER_TAIL = 10;
45 private static final int AFTER_TYPE = 6;
46 private Icon myEmptyIcon = EmptyIcon.create(5);
47 private final Font myNormalFont;
48 private final Font myBoldFont;
49 private final FontMetrics myNormalMetrics;
50 private final FontMetrics myBoldMetrics;
52 public static final Color BACKGROUND_COLOR = new Color(235, 244, 254);
53 private static final Color FOREGROUND_COLOR = Color.black;
54 private static final Color GRAYED_FOREGROUND_COLOR = Gray._160;
55 private static final Color SELECTED_BACKGROUND_COLOR = new Color(0, 82, 164);
56 private static final Color SELECTED_FOREGROUND_COLOR = Color.white;
57 private static final Color SELECTED_GRAYED_FOREGROUND_COLOR = Color.white;
59 static final Color PREFIX_FOREGROUND_COLOR = new Color(176, 0, 176);
60 private static final Color SELECTED_PREFIX_FOREGROUND_COLOR = new Color(249, 236, 204);
62 private static final Color EMPTY_ITEM_FOREGROUND_COLOR = FOREGROUND_COLOR;
64 private static final int MAX_LENGTH = 70;
66 private final LookupImpl myLookup;
68 private final SimpleColoredComponent myNameComponent;
69 private final SimpleColoredComponent myTailComponent;
70 private final SimpleColoredComponent myTypeLabel;
71 private final JPanel myPanel;
73 public static final Color PREFERRED_BACKGROUND_COLOR = new Color(220, 245, 220);
74 private static final String ELLIPSIS = "\u2026";
76 public LookupCellRenderer(LookupImpl lookup) {
77 EditorColorsScheme scheme = lookup.getEditor().getColorsScheme();
78 myNormalFont = scheme.getFont(EditorFontType.PLAIN);
79 myBoldFont = scheme.getFont(EditorFontType.BOLD);
82 myNameComponent = new MySimpleColoredComponent();
83 myNameComponent.setIpad(new Insets(0, 0, 0, 0));
85 myTailComponent = new MySimpleColoredComponent();
86 myTailComponent.setIpad(new Insets(0, 0, 0, 0));
87 myTailComponent.setFont(myNormalFont);
89 myTypeLabel = new MySimpleColoredComponent();
90 myTypeLabel.setIpad(new Insets(0, 0, 0, 0));
91 myTypeLabel.setFont(myNormalFont);
93 myPanel = new LookupPanel();
94 myPanel.add(myNameComponent, BorderLayout.WEST);
95 myPanel.add(myTailComponent, BorderLayout.CENTER);
96 myTailComponent.setBorder(new EmptyBorder(0, 0, 0, AFTER_TAIL));
98 myPanel.add(myTypeLabel, BorderLayout.EAST);
99 myTypeLabel.setBorder(new EmptyBorder(0, 0, 0, AFTER_TYPE));
101 myNormalMetrics = myLookup.getEditor().getComponent().getFontMetrics(myNormalFont);
102 myBoldMetrics = myLookup.getEditor().getComponent().getFontMetrics(myBoldFont);
104 UIUtil.removeQuaquaVisualMarginsIn(myPanel);
107 public Component getListCellRendererComponent(
115 if (!myLookup.isFocused()) {
119 final LookupElement item = (LookupElement)value;
120 final Color foreground = isSelected ? SELECTED_FOREGROUND_COLOR : FOREGROUND_COLOR;
121 final Color background = getItemBackground(list, index, isSelected);
123 int allowedWidth = list.getWidth() - AFTER_TAIL - AFTER_TYPE - getIconIndent();
124 final LookupElementPresentation presentation = new RealLookupElementPresentation(allowedWidth, myNormalMetrics, myBoldMetrics, myLookup);
125 if (item.isValid()) {
126 item.renderElement(presentation);
128 presentation.setItemTextForeground(Color.RED);
129 presentation.setItemText("Invalid");
132 myNameComponent.clear();
133 myNameComponent.setIcon(augmentIcon(presentation.getIcon(), myEmptyIcon));
134 myNameComponent.setBackground(background);
135 allowedWidth -= setItemTextLabel(item, isSelected ? SELECTED_FOREGROUND_COLOR : presentation.getItemTextForeground(), isSelected, presentation, allowedWidth);
138 if (allowedWidth > 0) {
139 allowedWidth -= setTypeTextLabel(item, background, foreground, presentation, allowedWidth, isSelected);
142 myTailComponent.clear();
143 myTailComponent.setBackground(background);
144 if (allowedWidth >= 0) {
145 setTailTextLabel(isSelected, presentation, foreground, allowedWidth);
151 private Color getItemBackground(JList list, int index, boolean isSelected) {
152 final int preferredCount = myLookup.getPreferredItemsCount();
153 final boolean isPreferred = index <= preferredCount - 1 && preferredCount < list.getModel().getSize() - 1 && LookupImpl.limitRelevance();
156 return isSelected ? SELECTED_BACKGROUND_COLOR : PREFERRED_BACKGROUND_COLOR;
158 return isSelected ? SELECTED_BACKGROUND_COLOR : BACKGROUND_COLOR;
161 private void setTailTextLabel(boolean isSelected, LookupElementPresentation presentation, Color foreground, int allowedWidth) {
162 final Color fg = getTailTextColor(isSelected, presentation, foreground);
164 final String tailText = StringUtil.notNullize(presentation.getTailText());
166 int style = SimpleTextAttributes.STYLE_PLAIN;
167 if (presentation.isStrikeout()) {
168 style |= SimpleTextAttributes.STYLE_STRIKEOUT;
171 SimpleTextAttributes attributes = new SimpleTextAttributes(style, fg);
172 if (allowedWidth < 0) {
176 myTailComponent.append(trimLabelText(tailText, allowedWidth, myNormalMetrics), attributes);
179 private static String trimLabelText(@Nullable String text, int maxWidth, FontMetrics metrics) {
180 if (text == null || StringUtil.isEmpty(text)) {
184 final int strWidth = RealLookupElementPresentation.getStringWidth(text, metrics);
185 if (strWidth <= maxWidth) {
189 if (RealLookupElementPresentation.getStringWidth(ELLIPSIS, metrics) > maxWidth) {
194 int j = text.length();
196 int mid = (i + j) / 2;
197 final String candidate = text.substring(0, mid) + ELLIPSIS;
198 final int width = RealLookupElementPresentation.getStringWidth(candidate, metrics);
199 if (width <= maxWidth) {
206 return text.substring(0, i) + ELLIPSIS;
209 public static Color getTailTextColor(boolean isSelected, LookupElementPresentation presentation, Color defaultForeground) {
210 if (presentation.isTailGrayed()) {
211 return getGrayedForeground(isSelected);
214 final Color tailForeground = presentation.getTailForeground();
215 if (tailForeground != null) {
216 return tailForeground;
219 return defaultForeground;
222 public static Color getGrayedForeground(boolean isSelected) {
223 return isSelected ? SELECTED_GRAYED_FOREGROUND_COLOR : GRAYED_FOREGROUND_COLOR;
226 private int setItemTextLabel(LookupElement item, final Color foreground, final boolean selected, LookupElementPresentation presentation, int allowedWidth) {
227 boolean bold = presentation.isItemTextBold();
229 myNameComponent.setFont(bold ? myBoldFont : myNormalFont);
231 int style = bold ? SimpleTextAttributes.STYLE_BOLD : SimpleTextAttributes.STYLE_PLAIN;
232 if (presentation.isStrikeout()) {
233 style |= SimpleTextAttributes.STYLE_STRIKEOUT;
235 if (presentation.isItemTextUnderlined()) {
236 style |= SimpleTextAttributes.STYLE_UNDERLINE;
239 final FontMetrics metrics = bold ? myBoldMetrics : myNormalMetrics;
240 final String name = trimLabelText(presentation.getItemText(), allowedWidth, metrics);
241 int used = RealLookupElementPresentation.getStringWidth(name, metrics);
243 renderItemName(item, foreground, selected, style, name, myNameComponent);
247 private void renderItemName(LookupElement item,
250 @SimpleTextAttributes.StyleAttributeConstant int style,
252 final SimpleColoredComponent nameComponent) {
253 final SimpleTextAttributes base = new SimpleTextAttributes(style, foreground);
255 final String prefix = myLookup.itemPattern(item);
256 if (prefix.length() > 0) {
257 Iterable<TextRange> ranges = new NameUtil.MinusculeMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE).matchingFragments(name);
258 if (ranges != null) {
259 SimpleTextAttributes highlighted =
260 new SimpleTextAttributes(style, selected ? SELECTED_PREFIX_FOREGROUND_COLOR : PREFIX_FOREGROUND_COLOR);
261 SpeedSearchUtil.appendColoredFragments(nameComponent, name, ranges, base, highlighted);
265 nameComponent.append(name, base);
268 private int setTypeTextLabel(LookupElement item,
269 final Color background,
271 final LookupElementPresentation presentation,
274 final String givenText = presentation.getTypeText();
275 final String labelText = trimLabelText(StringUtil.isEmpty(givenText) ? "" : " " + givenText, allowedWidth, myNormalMetrics);
277 int used = RealLookupElementPresentation.getStringWidth(labelText, myNormalMetrics);
279 final Icon icon = presentation.getTypeIcon();
281 myTypeLabel.setIcon(icon);
282 used += icon.getIconWidth();
285 Color sampleBackground = background;
287 Object o = item.getObject();
288 if (o instanceof LookupValueWithUIHint && StringUtil.isEmpty(labelText)) {
289 Color proposedBackground = ((LookupValueWithUIHint)o).getColorHint();
290 if (proposedBackground != null) {
291 sampleBackground = proposedBackground;
293 myTypeLabel.append(" ");
294 used += myNormalMetrics.stringWidth("WW");
296 myTypeLabel.append(labelText);
299 myTypeLabel.setBackground(sampleBackground);
300 myTypeLabel.setForeground(presentation.isTypeGrayed() ? getGrayedForeground(selected) : item instanceof EmptyLookupItem ? EMPTY_ITEM_FOREGROUND_COLOR : foreground);
304 public static Icon augmentIcon(@Nullable Icon icon, @NotNull Icon standard) {
309 if (icon.getIconHeight() < standard.getIconHeight() || icon.getIconWidth() < standard.getIconWidth()) {
310 final LayeredIcon layeredIcon = new LayeredIcon(2);
311 layeredIcon.setIcon(icon, 0, 0, (standard.getIconHeight() - icon.getIconHeight()) / 2);
312 layeredIcon.setIcon(standard, 1);
319 public int updateMaximumWidth(final LookupElementPresentation p) {
320 final Icon icon = p.getIcon();
321 if (icon != null && (icon.getIconWidth() > myEmptyIcon.getIconWidth() || icon.getIconHeight() > myEmptyIcon.getIconHeight())) {
322 myEmptyIcon = new EmptyIcon(Math.max(icon.getIconWidth(), myEmptyIcon.getIconWidth()), Math.max(icon.getIconHeight(), myEmptyIcon.getIconHeight()));
325 return RealLookupElementPresentation.calculateWidth(p, myNormalMetrics, myBoldMetrics) + AFTER_TAIL + AFTER_TYPE;
328 public int getIconIndent() {
329 return myNameComponent.getIconTextGap() + myEmptyIcon.getIconWidth();
333 private static class MySimpleColoredComponent extends SimpleColoredComponent {
334 private MySimpleColoredComponent() {
335 setFocusBorderAroundIcon(true);
339 protected void applyAdditionalHints(Graphics g) {
340 UISettings.setupAntialiasing(g);
344 private class LookupPanel extends JPanel {
345 public LookupPanel() {
346 super(new BorderLayout());
349 public void paint(Graphics g){
350 if (!myLookup.isFocused() && myLookup.isCompletion()) {
351 ((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f));