fb3401e9e61458edacf88f3cd6cc76dd314d6961
[idea/community.git] / platform / core-api / src / com / intellij / util / IconUtil.java
1 /*
2  * Copyright 2000-2015 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 package com.intellij.util;
17
18 import com.intellij.ide.FileIconPatcher;
19 import com.intellij.ide.FileIconProvider;
20 import com.intellij.ide.presentation.VirtualFilePresentation;
21 import com.intellij.openapi.extensions.Extensions;
22 import com.intellij.openapi.project.DumbService;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.IconLoader;
25 import com.intellij.openapi.util.Iconable;
26 import com.intellij.openapi.util.Key;
27 import com.intellij.openapi.util.SystemInfo;
28 import com.intellij.openapi.vfs.VFileProperty;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.openapi.vfs.WritingAccessProvider;
31 import com.intellij.ui.IconDeferrer;
32 import com.intellij.ui.JBColor;
33 import com.intellij.ui.LayeredIcon;
34 import com.intellij.ui.RowIcon;
35 import com.intellij.util.ui.*;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import javax.swing.*;
40 import java.awt.*;
41 import java.awt.geom.AffineTransform;
42 import java.awt.image.BufferedImage;
43
44
45 /**
46  * @author max
47  * @author Konstantin Bulenkov
48  */
49 public class IconUtil {
50   private static final Key<Boolean> PROJECT_WAS_EVER_INITIALIZED = Key.create("iconDeferrer:projectWasEverInitialized");
51
52   private static boolean wasEverInitialized(@NotNull Project project) {
53     Boolean was = project.getUserData(PROJECT_WAS_EVER_INITIALIZED);
54     if (was == null) {
55       if (project.isInitialized()) {
56         was = true;
57         project.putUserData(PROJECT_WAS_EVER_INITIALIZED, was);
58       }
59       else {
60         was = false;
61       }
62     }
63
64     return was.booleanValue();
65   }
66
67   @NotNull
68   public static Icon cropIcon(@NotNull Icon icon, int maxWidth, int maxHeight) {
69     if (icon.getIconHeight() <= maxHeight && icon.getIconWidth() <= maxWidth) {
70       return icon;
71     }
72
73     final int w = Math.min(icon.getIconWidth(), maxWidth);
74     final int h = Math.min(icon.getIconHeight(), maxHeight);
75
76     final BufferedImage image = GraphicsEnvironment
77       .getLocalGraphicsEnvironment()
78       .getDefaultScreenDevice()
79       .getDefaultConfiguration()
80       .createCompatibleImage(icon.getIconWidth(), icon.getIconHeight(), Transparency.TRANSLUCENT);
81     final Graphics2D g = image.createGraphics();
82     icon.paintIcon(new JPanel(), g, 0, 0);
83     g.dispose();
84
85     final BufferedImage img = UIUtil.createImage(w, h, Transparency.TRANSLUCENT);
86     final int offX = icon.getIconWidth() > maxWidth ? (icon.getIconWidth() - maxWidth) / 2 : 0;
87     final int offY = icon.getIconHeight() > maxHeight ? (icon.getIconHeight() - maxHeight) / 2 : 0;
88     for (int col = 0; col < w; col++) {
89       for (int row = 0; row < h; row++) {
90         img.setRGB(col, row, image.getRGB(col + offX, row + offY));
91       }
92     }
93
94     return new ImageIcon(img);
95   }
96
97   @NotNull
98   public static Icon cropIcon(@NotNull Icon icon, @NotNull Rectangle area) {
99     if (!new Rectangle(icon.getIconWidth(), icon.getIconHeight()).contains(area)) {
100       return icon;
101     }
102     return new CropIcon(icon, area);
103   }
104
105   @NotNull
106   public static Icon flip(@NotNull final Icon icon, final boolean horizontal) {
107     return new Icon() {
108       @Override
109       public void paintIcon(Component c, Graphics g, int x, int y) {
110         Graphics2D g2d = (Graphics2D)g.create();
111         try {
112           AffineTransform transform =
113             AffineTransform.getTranslateInstance(horizontal ? x + getIconWidth() : x, horizontal ? y : y + getIconHeight());
114           transform.concatenate(AffineTransform.getScaleInstance(horizontal ? -1 : 1, horizontal ? 1 : -1));
115           transform.preConcatenate(g2d.getTransform());
116           g2d.setTransform(transform);
117           icon.paintIcon(c, g2d, 0, 0);
118         }
119         finally {
120           g2d.dispose();
121         }
122       }
123
124       @Override
125       public int getIconWidth() {
126         return icon.getIconWidth();
127       }
128
129       @Override
130       public int getIconHeight() {
131         return icon.getIconHeight();
132       }
133     };
134   }
135
136   private static final NullableFunction<FileIconKey, Icon> ICON_NULLABLE_FUNCTION = new NullableFunction<FileIconKey, Icon>() {
137     @Override
138     public Icon fun(final FileIconKey key) {
139       final VirtualFile file = key.getFile();
140       final int flags = key.getFlags();
141       final Project project = key.getProject();
142
143       if (!file.isValid() || project != null && (project.isDisposed() || !wasEverInitialized(project))) return null;
144
145       final Icon providersIcon = getProvidersIcon(file, flags, project);
146       Icon icon = providersIcon == null ? VirtualFilePresentation.getIconImpl(file) : providersIcon;
147
148       final boolean dumb = project != null && DumbService.getInstance(project).isDumb();
149       for (FileIconPatcher patcher : getPatchers()) {
150         if (dumb && !DumbService.isDumbAware(patcher)) {
151           continue;
152         }
153
154         // render without locked icon patch since we are going to apply it later anyway
155         icon = patcher.patchIcon(icon, file, flags & ~Iconable.ICON_FLAG_READ_STATUS, project);
156       }
157
158       if (file.is(VFileProperty.SYMLINK)) {
159         icon = new LayeredIcon(icon, PlatformIcons.SYMLINK_ICON);
160       }
161       if (BitUtil.isSet(flags, Iconable.ICON_FLAG_READ_STATUS) &&
162           (!file.isWritable() || !WritingAccessProvider.isPotentiallyWritable(file, project))) {
163         icon = new LayeredIcon(icon, PlatformIcons.LOCKED_ICON);
164       }
165
166       Iconable.LastComputedIcon.put(file, icon, flags);
167
168       return icon;
169     }
170   };
171
172   public static Icon getIcon(@NotNull final VirtualFile file, @Iconable.IconFlags final int flags, @Nullable final Project project) {
173     Icon lastIcon = Iconable.LastComputedIcon.get(file, flags);
174
175     final Icon base = lastIcon != null ? lastIcon : VirtualFilePresentation.getIconImpl(file);
176     return IconDeferrer.getInstance().defer(base, new FileIconKey(file, project, flags), ICON_NULLABLE_FUNCTION);
177   }
178
179   @Nullable
180   private static Icon getProvidersIcon(@NotNull VirtualFile file, @Iconable.IconFlags int flags, Project project) {
181     for (FileIconProvider provider : getProviders()) {
182       final Icon icon = provider.getIcon(file, flags, project);
183       if (icon != null) return icon;
184     }
185     return null;
186   }
187
188   @NotNull
189   public static Icon getEmptyIcon(boolean showVisibility) {
190     RowIcon baseIcon = new RowIcon(2);
191     baseIcon.setIcon(createEmptyIconLike(PlatformIcons.CLASS_ICON_PATH), 0);
192     if (showVisibility) {
193       baseIcon.setIcon(createEmptyIconLike(PlatformIcons.PUBLIC_ICON_PATH), 1);
194     }
195     return baseIcon;
196   }
197
198   @NotNull
199   private static Icon createEmptyIconLike(@NotNull String baseIconPath) {
200     Icon baseIcon = IconLoader.findIcon(baseIconPath);
201     if (baseIcon == null) {
202       return EmptyIcon.ICON_16;
203     }
204     return new EmptyIcon(baseIcon.getIconWidth(), baseIcon.getIconHeight());
205   }
206
207   private static class FileIconProviderHolder {
208     private static final FileIconProvider[] myProviders = Extensions.getExtensions(FileIconProvider.EP_NAME);
209   }
210
211   private static FileIconProvider[] getProviders() {
212     return FileIconProviderHolder.myProviders;
213   }
214
215   private static class FileIconPatcherHolder {
216     private static final FileIconPatcher[] ourPatchers = Extensions.getExtensions(FileIconPatcher.EP_NAME);
217   }
218
219   private static FileIconPatcher[] getPatchers() {
220     return FileIconPatcherHolder.ourPatchers;
221   }
222
223   public static Image toImage(@NotNull Icon icon) {
224     if (icon instanceof ImageIcon) {
225       return ((ImageIcon)icon).getImage();
226     }
227     else {
228       final int w = icon.getIconWidth();
229       final int h = icon.getIconHeight();
230       final BufferedImage image = GraphicsEnvironment.getLocalGraphicsEnvironment()
231         .getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(w, h, Transparency.TRANSLUCENT);
232       final Graphics2D g = image.createGraphics();
233       icon.paintIcon(null, g, 0, 0);
234       g.dispose();
235       return image;
236     }
237   }
238
239   @NotNull
240   public static Icon getAddIcon() {
241     return getToolbarDecoratorIcon("add.png");
242   }
243
244   @NotNull
245   public static Icon getRemoveIcon() {
246     return getToolbarDecoratorIcon("remove.png");
247   }
248
249   @NotNull
250   public static Icon getMoveUpIcon() {
251     return getToolbarDecoratorIcon("moveUp.png");
252   }
253
254   @NotNull
255   public static Icon getMoveDownIcon() {
256     return getToolbarDecoratorIcon("moveDown.png");
257   }
258
259   @NotNull
260   public static Icon getEditIcon() {
261     return getToolbarDecoratorIcon("edit.png");
262   }
263
264   @NotNull
265   public static Icon getAddClassIcon() {
266     return getToolbarDecoratorIcon("addClass.png");
267   }
268
269   @NotNull
270   public static Icon getAddPatternIcon() {
271     return getToolbarDecoratorIcon("addPattern.png");
272   }
273
274   @NotNull
275   public static Icon getAddJiraPatternIcon() {
276     return getToolbarDecoratorIcon("addJira.png");
277   }
278
279   @NotNull
280   public static Icon getAddYouTrackPatternIcon() {
281     return getToolbarDecoratorIcon("addYouTrack.png");
282   }
283
284   @NotNull
285   public static Icon getAddBlankLineIcon() {
286     return getToolbarDecoratorIcon("addBlankLine.png");
287   }
288
289   @NotNull
290   public static Icon getAddPackageIcon() {
291     return getToolbarDecoratorIcon("addPackage.png");
292   }
293
294   @NotNull
295   public static Icon getAddLinkIcon() {
296     return getToolbarDecoratorIcon("addLink.png");
297   }
298
299   @NotNull
300   public static Icon getAddFolderIcon() {
301     return getToolbarDecoratorIcon("addFolder.png");
302   }
303
304   @NotNull
305   public static Icon getAnalyzeIcon() {
306     return getToolbarDecoratorIcon("analyze.png");
307   }
308
309   public static void paintInCenterOf(@NotNull Component c, @NotNull Graphics g, @NotNull Icon icon) {
310     final int x = (c.getWidth() - icon.getIconWidth()) / 2;
311     final int y = (c.getHeight() - icon.getIconHeight()) / 2;
312     icon.paintIcon(c, g, x, y);
313   }
314
315   @NotNull
316   private static Icon getToolbarDecoratorIcon(@NotNull String name) {
317     return IconLoader.getIcon(getToolbarDecoratorIconsFolder() + name);
318   }
319
320   @NotNull
321   private static String getToolbarDecoratorIconsFolder() {
322     return "/toolbarDecorator/" + (SystemInfo.isMac ? "mac/" : "");
323   }
324
325   /**
326    * Result icons look like original but have equal (maximum) size
327    */
328   @NotNull
329   public static Icon[] getEqualSizedIcons(@NotNull Icon... icons) {
330     Icon[] result = new Icon[icons.length];
331     int width = 0;
332     int height = 0;
333     for (Icon icon : icons) {
334       width = Math.max(width, icon.getIconWidth());
335       height = Math.max(height, icon.getIconHeight());
336     }
337     for (int i = 0; i < icons.length; i++) {
338       result[i] = new IconSizeWrapper(icons[i], width, height);
339     }
340     return result;
341   }
342
343   @NotNull
344   public static Icon toSize(@NotNull Icon icon, int width, int height) {
345     return new IconSizeWrapper(icon, width, height);
346   }
347
348   private static class IconSizeWrapper implements Icon {
349     private final Icon myIcon;
350     private final int myWidth;
351     private final int myHeight;
352
353     private IconSizeWrapper(@NotNull Icon icon, int width, int height) {
354       myIcon = icon;
355       myWidth = width;
356       myHeight = height;
357     }
358
359     @Override
360     public void paintIcon(Component c, Graphics g, int x, int y) {
361       x += (myWidth - myIcon.getIconWidth()) / 2;
362       y += (myHeight - myIcon.getIconHeight()) / 2;
363       myIcon.paintIcon(c, g, x, y);
364     }
365
366     @Override
367     public int getIconWidth() {
368       return myWidth;
369     }
370
371     @Override
372     public int getIconHeight() {
373       return myHeight;
374     }
375   }
376
377   private static class CropIcon implements Icon {
378     private final Icon mySrc;
379     private final Rectangle myCrop;
380
381     private CropIcon(@NotNull Icon src, @NotNull Rectangle crop) {
382       mySrc = src;
383       myCrop = crop;
384     }
385
386     @Override
387     public void paintIcon(Component c, Graphics g, int x, int y) {
388       mySrc.paintIcon(c, g, x - myCrop.x, y - myCrop.y);
389     }
390
391     @Override
392     public int getIconWidth() {
393       return myCrop.width;
394     }
395
396     @Override
397     public int getIconHeight() {
398       return myCrop.height;
399     }
400   }
401
402   @NotNull
403   public static Icon scale(@NotNull final Icon source, double _scale) {
404     final int hiDPIscale;
405     if (source instanceof ImageIcon) {
406       Image image = ((ImageIcon)source).getImage();
407       hiDPIscale = RetinaImage.isAppleHiDPIScaledImage(image) || image instanceof JBHiDPIScaledImage ? 2 : 1;
408     }
409     else {
410       hiDPIscale = 1;
411     }
412     final double scale = Math.min(32, Math.max(.1, _scale));
413     return new Icon() {
414       @Override
415       public void paintIcon(Component c, Graphics g, int x, int y) {
416         Graphics2D g2d = (Graphics2D)g.create();
417         try {
418           g2d.translate(x, y);
419           AffineTransform transform = AffineTransform.getScaleInstance(scale, scale);
420           transform.preConcatenate(g2d.getTransform());
421           g2d.setTransform(transform);
422           g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
423           source.paintIcon(c, g2d, 0, 0);
424         }
425         finally {
426           g2d.dispose();
427         }
428       }
429
430       @Override
431       public int getIconWidth() {
432         return (int)(source.getIconWidth() * scale) / hiDPIscale;
433       }
434
435       @Override
436       public int getIconHeight() {
437         return (int)(source.getIconHeight() * scale) / hiDPIscale;
438       }
439     };
440   }
441
442   @NotNull
443   public static Icon colorize(@NotNull final Icon source, @NotNull Color color) {
444     return colorize(source, color, false);
445   }
446
447   @NotNull
448   public static Icon colorize(@NotNull final Icon source, @NotNull Color color, boolean keepGray) {
449     float[] base = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
450
451     final BufferedImage image = UIUtil.createImage(source.getIconWidth(), source.getIconHeight(), Transparency.TRANSLUCENT);
452     final Graphics2D g = image.createGraphics();
453     source.paintIcon(null, g, 0, 0);
454     g.dispose();
455
456     final BufferedImage img = UIUtil.createImage(source.getIconWidth(), source.getIconHeight(), Transparency.TRANSLUCENT);
457     int[] rgba = new int[4];
458     float[] hsb = new float[3];
459     for (int y = 0; y < image.getRaster().getHeight(); y++) {
460       for (int x = 0; x < image.getRaster().getWidth(); x++) {
461         image.getRaster().getPixel(x, y, rgba);
462         if (rgba[3] != 0) {
463           Color.RGBtoHSB(rgba[0], rgba[1], rgba[2], hsb);
464           int rgb = Color.HSBtoRGB(base[0], base[1] * (keepGray ? hsb[1] : 1f), base[2] * hsb[2]);
465           img.getRaster().setPixel(x, y, new int[]{rgb >> 16 & 0xff, rgb >> 8 & 0xff, rgb & 0xff, rgba[3]});
466         }
467       }
468     }
469
470     return createImageIcon(img);
471   }
472
473   @NotNull
474   public static JBImageIcon createImageIcon(@NotNull final BufferedImage img) {
475     return new JBImageIcon(img) {
476       @Override
477       public int getIconWidth() {
478         return getImage() instanceof JBHiDPIScaledImage ? super.getIconWidth() / 2 : super.getIconWidth();
479       }
480
481       @Override
482       public int getIconHeight() {
483         return getImage() instanceof JBHiDPIScaledImage ? super.getIconHeight() / 2: super.getIconHeight();
484       }
485     };
486   }
487
488   @NotNull
489   public static Icon textToIcon(@NotNull final String text, @NotNull final Component component, final float fontSize) {
490     final Font font = JBFont.create(JBUI.Fonts.label().deriveFont(fontSize));
491     FontMetrics metrics = component.getFontMetrics(font);
492     final int width = metrics.stringWidth(text) + JBUI.scale(4);
493     final int height = metrics.getHeight();
494
495     return new Icon() {
496       @Override
497       public void paintIcon(Component c, Graphics g, int x, int y) {
498         g = g.create();
499         try {
500           GraphicsUtil.setupAntialiasing(g);
501           g.setFont(font);
502           UIUtil.drawStringWithHighlighting(g, text, x + JBUI.scale(2), y + height - JBUI.scale(1), JBColor.foreground(), JBColor.background());
503         }
504         finally {
505           g.dispose();
506         }
507       }
508
509       @Override
510       public int getIconWidth() {
511         return width;
512       }
513
514       @Override
515       public int getIconHeight() {
516         return height;
517       }
518     };
519   }
520
521   @NotNull
522   public static Icon addText(@NotNull Icon base, @NotNull String text) {
523     LayeredIcon icon = new LayeredIcon(2);
524     icon.setIcon(base, 0);
525     icon.setIcon(textToIcon(text, new JLabel(), JBUI.scale(6f)), 1, SwingConstants.SOUTH_EAST);
526     return icon;
527   }
528 }