farewell green items in completion
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / lookup / impl / LookupCellRenderer.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.codeInsight.lookup.impl;
18
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;
38
39 import javax.swing.*;
40 import javax.swing.border.EmptyBorder;
41 import java.awt.*;
42
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;
51
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;
58
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);
61
62   private static final Color EMPTY_ITEM_FOREGROUND_COLOR = FOREGROUND_COLOR;
63
64   private static final int MAX_LENGTH = 70;
65
66   private final LookupImpl myLookup;
67
68   private final SimpleColoredComponent myNameComponent;
69   private final SimpleColoredComponent myTailComponent;
70   private final SimpleColoredComponent myTypeLabel;
71   private final JPanel myPanel;
72
73   public static final Color PREFERRED_BACKGROUND_COLOR = new Color(220, 245, 220);
74   private static final String ELLIPSIS = "\u2026";
75
76   public LookupCellRenderer(LookupImpl lookup) {
77     EditorColorsScheme scheme = lookup.getEditor().getColorsScheme();
78     myNormalFont = scheme.getFont(EditorFontType.PLAIN);
79     myBoldFont = scheme.getFont(EditorFontType.BOLD);
80
81     myLookup = lookup;
82     myNameComponent = new MySimpleColoredComponent();
83     myNameComponent.setIpad(new Insets(0, 0, 0, 0));
84
85     myTailComponent = new MySimpleColoredComponent();
86     myTailComponent.setIpad(new Insets(0, 0, 0, 0));
87     myTailComponent.setFont(myNormalFont);
88
89     myTypeLabel = new MySimpleColoredComponent();
90     myTypeLabel.setIpad(new Insets(0, 0, 0, 0));
91     myTypeLabel.setFont(myNormalFont);
92
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));
97
98     myPanel.add(myTypeLabel, BorderLayout.EAST);
99     myTypeLabel.setBorder(new EmptyBorder(0, 0, 0, AFTER_TYPE));
100
101     myNormalMetrics = myLookup.getEditor().getComponent().getFontMetrics(myNormalFont);
102     myBoldMetrics = myLookup.getEditor().getComponent().getFontMetrics(myBoldFont);
103
104     UIUtil.removeQuaquaVisualMarginsIn(myPanel);
105   }
106
107   public Component getListCellRendererComponent(
108       final JList list,
109       Object value,
110       int index,
111       boolean isSelected,
112       boolean hasFocus) {
113
114
115     if (!myLookup.isFocused()) {
116       isSelected = false;
117     }
118
119     final LookupElement item = (LookupElement)value;
120     final Color foreground = isSelected ? SELECTED_FOREGROUND_COLOR : FOREGROUND_COLOR;
121     final Color background = getItemBackground(list, index, isSelected);
122
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);
127     } else {
128       presentation.setItemTextForeground(Color.RED);
129       presentation.setItemText("Invalid");
130     }
131
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);
136
137     myTypeLabel.clear();
138     if (allowedWidth > 0) {
139       allowedWidth -= setTypeTextLabel(item, background, foreground, presentation, allowedWidth, isSelected);
140     }
141
142     myTailComponent.clear();
143     myTailComponent.setBackground(background);
144     if (allowedWidth >= 0) {
145       setTailTextLabel(isSelected, presentation, foreground, allowedWidth);
146     }
147
148     return myPanel;
149   }
150
151   private Color getItemBackground(JList list, int index, boolean isSelected) {
152     return isSelected ? SELECTED_BACKGROUND_COLOR : BACKGROUND_COLOR;
153   }
154
155   private void setTailTextLabel(boolean isSelected, LookupElementPresentation presentation, Color foreground, int allowedWidth) {
156     final Color fg = getTailTextColor(isSelected, presentation, foreground);
157
158     final String tailText = StringUtil.notNullize(presentation.getTailText());
159
160     int style = SimpleTextAttributes.STYLE_PLAIN;
161     if (presentation.isStrikeout()) {
162       style |= SimpleTextAttributes.STYLE_STRIKEOUT;
163     }
164
165     SimpleTextAttributes attributes = new SimpleTextAttributes(style, fg);
166     if (allowedWidth < 0) {
167       return;
168     }
169
170     myTailComponent.append(trimLabelText(tailText, allowedWidth, myNormalMetrics), attributes);
171   }
172
173   private static String trimLabelText(@Nullable String text, int maxWidth, FontMetrics metrics) {
174     if (text == null || StringUtil.isEmpty(text)) {
175       return "";
176     }
177
178     final int strWidth = RealLookupElementPresentation.getStringWidth(text, metrics);
179     if (strWidth <= maxWidth) {
180       return text;
181     }
182
183     if (RealLookupElementPresentation.getStringWidth(ELLIPSIS, metrics) > maxWidth) {
184       return "";
185     }
186
187     int i = 0;
188     int j = text.length();
189     while (i + 1 < j) {
190       int mid = (i + j) / 2;
191       final String candidate = text.substring(0, mid) + ELLIPSIS;
192       final int width = RealLookupElementPresentation.getStringWidth(candidate, metrics);
193       if (width <= maxWidth) {
194         i = mid;
195       } else {
196         j = mid;
197       }
198     }
199
200     return text.substring(0, i) + ELLIPSIS;
201   }
202
203   public static Color getTailTextColor(boolean isSelected, LookupElementPresentation presentation, Color defaultForeground) {
204     if (presentation.isTailGrayed()) {
205       return getGrayedForeground(isSelected);
206     }
207
208     final Color tailForeground = presentation.getTailForeground();
209     if (tailForeground != null) {
210       return tailForeground;
211     }
212
213     return defaultForeground;
214   }
215
216   public static Color getGrayedForeground(boolean isSelected) {
217     return isSelected ? SELECTED_GRAYED_FOREGROUND_COLOR : GRAYED_FOREGROUND_COLOR;
218   }
219
220   private int setItemTextLabel(LookupElement item, final Color foreground, final boolean selected, LookupElementPresentation presentation, int allowedWidth) {
221     boolean bold = presentation.isItemTextBold();
222
223     myNameComponent.setFont(bold ? myBoldFont : myNormalFont);
224
225     int style = bold ? SimpleTextAttributes.STYLE_BOLD : SimpleTextAttributes.STYLE_PLAIN;
226     if (presentation.isStrikeout()) {
227       style |= SimpleTextAttributes.STYLE_STRIKEOUT;
228     }
229     if (presentation.isItemTextUnderlined()) {
230       style |= SimpleTextAttributes.STYLE_UNDERLINE;
231     }
232
233     final FontMetrics metrics = bold ? myBoldMetrics : myNormalMetrics;
234     final String name = trimLabelText(presentation.getItemText(), allowedWidth, metrics);
235     int used = RealLookupElementPresentation.getStringWidth(name, metrics);
236
237     renderItemName(item, foreground, selected, style, name, myNameComponent);
238     return used;
239   }
240
241   private void renderItemName(LookupElement item,
242                       Color foreground,
243                       boolean selected,
244                       @SimpleTextAttributes.StyleAttributeConstant int style,
245                       String name,
246                       final SimpleColoredComponent nameComponent) {
247     final SimpleTextAttributes base = new SimpleTextAttributes(style, foreground);
248
249     final String prefix = myLookup.itemPattern(item);
250     if (prefix.length() > 0) {
251       Iterable<TextRange> ranges = new NameUtil.MinusculeMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE).matchingFragments(name);
252       if (ranges != null) {
253         SimpleTextAttributes highlighted =
254           new SimpleTextAttributes(style, selected ? SELECTED_PREFIX_FOREGROUND_COLOR : PREFIX_FOREGROUND_COLOR);
255         SpeedSearchUtil.appendColoredFragments(nameComponent, name, ranges, base, highlighted);
256         return;
257       }
258     }
259     nameComponent.append(name, base);
260   }
261
262   private int setTypeTextLabel(LookupElement item,
263                                final Color background,
264                                Color foreground,
265                                final LookupElementPresentation presentation,
266                                int allowedWidth,
267                                boolean selected) {
268     final String givenText = presentation.getTypeText();
269     final String labelText = trimLabelText(StringUtil.isEmpty(givenText) ? "" : " " + givenText, allowedWidth, myNormalMetrics);
270
271     int used = RealLookupElementPresentation.getStringWidth(labelText, myNormalMetrics);
272
273     final Icon icon = presentation.getTypeIcon();
274     if (icon != null) {
275       myTypeLabel.setIcon(icon);
276       used += icon.getIconWidth();
277     }
278
279     Color sampleBackground = background;
280
281     Object o = item.getObject();
282     if (o instanceof LookupValueWithUIHint && StringUtil.isEmpty(labelText)) {
283       Color proposedBackground = ((LookupValueWithUIHint)o).getColorHint();
284       if (proposedBackground != null) {
285         sampleBackground = proposedBackground;
286       }
287       myTypeLabel.append("  ");
288       used += myNormalMetrics.stringWidth("WW");
289     } else {
290       myTypeLabel.append(labelText);
291     }
292
293     myTypeLabel.setBackground(sampleBackground);
294     myTypeLabel.setForeground(presentation.isTypeGrayed() ? getGrayedForeground(selected) : item instanceof EmptyLookupItem ? EMPTY_ITEM_FOREGROUND_COLOR : foreground);
295     return used;
296   }
297
298   public static Icon augmentIcon(@Nullable Icon icon, @NotNull Icon standard) {
299     if (icon == null) {
300       return standard;
301     }
302
303     if (icon.getIconHeight() < standard.getIconHeight() || icon.getIconWidth() < standard.getIconWidth()) {
304       final LayeredIcon layeredIcon = new LayeredIcon(2);
305       layeredIcon.setIcon(icon, 0, 0, (standard.getIconHeight() - icon.getIconHeight()) / 2);
306       layeredIcon.setIcon(standard, 1);
307       return layeredIcon;
308     }
309
310     return icon;
311   }
312
313   public int updateMaximumWidth(final LookupElementPresentation p) {
314     final Icon icon = p.getIcon();
315     if (icon != null && (icon.getIconWidth() > myEmptyIcon.getIconWidth() || icon.getIconHeight() > myEmptyIcon.getIconHeight())) {
316       myEmptyIcon = new EmptyIcon(Math.max(icon.getIconWidth(), myEmptyIcon.getIconWidth()), Math.max(icon.getIconHeight(), myEmptyIcon.getIconHeight()));
317     }
318
319     return RealLookupElementPresentation.calculateWidth(p, myNormalMetrics, myBoldMetrics) + AFTER_TAIL + AFTER_TYPE;
320   }
321
322   public int getIconIndent() {
323     return myNameComponent.getIconTextGap() + myEmptyIcon.getIconWidth();
324   }
325
326
327   private static class MySimpleColoredComponent extends SimpleColoredComponent {
328     private MySimpleColoredComponent() {
329       setFocusBorderAroundIcon(true);
330     }
331
332     @Override
333     protected void applyAdditionalHints(Graphics g) {
334       UISettings.setupAntialiasing(g);
335     }
336   }
337
338   private class LookupPanel extends JPanel {
339     public LookupPanel() {
340       super(new BorderLayout());
341     }
342
343     public void paint(Graphics g){
344       if (!myLookup.isFocused() && myLookup.isCompletion()) {
345         ((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f));
346       }
347       super.paint(g);
348     }
349   }
350 }