cleanup
[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     final int preferredCount = myLookup.getPreferredItemsCount();
153     final boolean isPreferred = index <= preferredCount - 1 && preferredCount < list.getModel().getSize() - 1 && LookupImpl.limitRelevance();
154
155     if (isPreferred) {
156       return isSelected ? SELECTED_BACKGROUND_COLOR : PREFERRED_BACKGROUND_COLOR;
157     }
158     return isSelected ? SELECTED_BACKGROUND_COLOR : BACKGROUND_COLOR;
159   }
160
161   private void setTailTextLabel(boolean isSelected, LookupElementPresentation presentation, Color foreground, int allowedWidth) {
162     final Color fg = getTailTextColor(isSelected, presentation, foreground);
163
164     final String tailText = StringUtil.notNullize(presentation.getTailText());
165
166     int style = SimpleTextAttributes.STYLE_PLAIN;
167     if (presentation.isStrikeout()) {
168       style |= SimpleTextAttributes.STYLE_STRIKEOUT;
169     }
170
171     SimpleTextAttributes attributes = new SimpleTextAttributes(style, fg);
172     if (allowedWidth < 0) {
173       return;
174     }
175
176     myTailComponent.append(trimLabelText(tailText, allowedWidth, myNormalMetrics), attributes);
177   }
178
179   private static String trimLabelText(@Nullable String text, int maxWidth, FontMetrics metrics) {
180     if (text == null || StringUtil.isEmpty(text)) {
181       return "";
182     }
183
184     final int strWidth = RealLookupElementPresentation.getStringWidth(text, metrics);
185     if (strWidth <= maxWidth) {
186       return text;
187     }
188
189     if (RealLookupElementPresentation.getStringWidth(ELLIPSIS, metrics) > maxWidth) {
190       return "";
191     }
192
193     int i = 0;
194     int j = text.length();
195     while (i + 1 < j) {
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) {
200         i = mid;
201       } else {
202         j = mid;
203       }
204     }
205
206     return text.substring(0, i) + ELLIPSIS;
207   }
208
209   public static Color getTailTextColor(boolean isSelected, LookupElementPresentation presentation, Color defaultForeground) {
210     if (presentation.isTailGrayed()) {
211       return getGrayedForeground(isSelected);
212     }
213
214     final Color tailForeground = presentation.getTailForeground();
215     if (tailForeground != null) {
216       return tailForeground;
217     }
218
219     return defaultForeground;
220   }
221
222   public static Color getGrayedForeground(boolean isSelected) {
223     return isSelected ? SELECTED_GRAYED_FOREGROUND_COLOR : GRAYED_FOREGROUND_COLOR;
224   }
225
226   private int setItemTextLabel(LookupElement item, final Color foreground, final boolean selected, LookupElementPresentation presentation, int allowedWidth) {
227     boolean bold = presentation.isItemTextBold();
228
229     myNameComponent.setFont(bold ? myBoldFont : myNormalFont);
230
231     int style = bold ? SimpleTextAttributes.STYLE_BOLD : SimpleTextAttributes.STYLE_PLAIN;
232     if (presentation.isStrikeout()) {
233       style |= SimpleTextAttributes.STYLE_STRIKEOUT;
234     }
235     if (presentation.isItemTextUnderlined()) {
236       style |= SimpleTextAttributes.STYLE_UNDERLINE;
237     }
238
239     final FontMetrics metrics = bold ? myBoldMetrics : myNormalMetrics;
240     final String name = trimLabelText(presentation.getItemText(), allowedWidth, metrics);
241     int used = RealLookupElementPresentation.getStringWidth(name, metrics);
242
243     renderItemName(item, foreground, selected, style, name, myNameComponent);
244     return used;
245   }
246
247   private void renderItemName(LookupElement item,
248                       Color foreground,
249                       boolean selected,
250                       @SimpleTextAttributes.StyleAttributeConstant int style,
251                       String name,
252                       final SimpleColoredComponent nameComponent) {
253     final SimpleTextAttributes base = new SimpleTextAttributes(style, foreground);
254
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);
262         return;
263       }
264     }
265     nameComponent.append(name, base);
266   }
267
268   private int setTypeTextLabel(LookupElement item,
269                                final Color background,
270                                Color foreground,
271                                final LookupElementPresentation presentation,
272                                int allowedWidth,
273                                boolean selected) {
274     final String givenText = presentation.getTypeText();
275     final String labelText = trimLabelText(StringUtil.isEmpty(givenText) ? "" : " " + givenText, allowedWidth, myNormalMetrics);
276
277     int used = RealLookupElementPresentation.getStringWidth(labelText, myNormalMetrics);
278
279     final Icon icon = presentation.getTypeIcon();
280     if (icon != null) {
281       myTypeLabel.setIcon(icon);
282       used += icon.getIconWidth();
283     }
284
285     Color sampleBackground = background;
286
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;
292       }
293       myTypeLabel.append("  ");
294       used += myNormalMetrics.stringWidth("WW");
295     } else {
296       myTypeLabel.append(labelText);
297     }
298
299     myTypeLabel.setBackground(sampleBackground);
300     myTypeLabel.setForeground(presentation.isTypeGrayed() ? getGrayedForeground(selected) : item instanceof EmptyLookupItem ? EMPTY_ITEM_FOREGROUND_COLOR : foreground);
301     return used;
302   }
303
304   public static Icon augmentIcon(@Nullable Icon icon, @NotNull Icon standard) {
305     if (icon == null) {
306       return standard;
307     }
308
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);
313       return layeredIcon;
314     }
315
316     return icon;
317   }
318
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()));
323     }
324
325     return RealLookupElementPresentation.calculateWidth(p, myNormalMetrics, myBoldMetrics) + AFTER_TAIL + AFTER_TYPE;
326   }
327
328   public int getIconIndent() {
329     return myNameComponent.getIconTextGap() + myEmptyIcon.getIconWidth();
330   }
331
332
333   private static class MySimpleColoredComponent extends SimpleColoredComponent {
334     private MySimpleColoredComponent() {
335       setFocusBorderAroundIcon(true);
336     }
337
338     @Override
339     protected void applyAdditionalHints(Graphics g) {
340       UISettings.setupAntialiasing(g);
341     }
342   }
343
344   private class LookupPanel extends JPanel {
345     public LookupPanel() {
346       super(new BorderLayout());
347     }
348
349     public void paint(Graphics g){
350       if (!myLookup.isFocused() && myLookup.isCompletion()) {
351         ((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f));
352       }
353       super.paint(g);
354     }
355   }
356 }