IDEA-122894 License dialog: License key: provide precise diagnostic for rejected key
[idea/community.git] / platform / util / src / com / intellij / openapi / util / IconLoader.java
1 /*
2  * Copyright 2000-2014 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.openapi.util;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.text.StringUtil;
20 import com.intellij.reference.SoftReference;
21 import com.intellij.util.ConcurrencyUtil;
22 import com.intellij.util.ImageLoader;
23 import com.intellij.util.ReflectionUtil;
24 import com.intellij.util.RetinaImage;
25 import com.intellij.util.containers.ContainerUtil;
26 import com.intellij.util.containers.WeakHashMap;
27 import com.intellij.util.ui.JBImageIcon;
28 import com.intellij.util.ui.UIUtil;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import javax.swing.*;
34 import java.awt.*;
35 import java.awt.image.BufferedImage;
36 import java.awt.image.FilteredImageSource;
37 import java.awt.image.ImageProducer;
38 import java.lang.ref.Reference;
39 import java.lang.reflect.Field;
40 import java.net.URL;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.concurrent.ConcurrentMap;
45
46 public final class IconLoader {
47   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.IconLoader");
48   public static boolean STRICT = false;
49   private static boolean USE_DARK_ICONS = UIUtil.isUnderDarcula();
50
51   @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
52   private static final ConcurrentMap<URL, CachedImageIcon> ourIconsCache = ContainerUtil.newConcurrentMap(100, 0.9f, 2);
53
54   /**
55    * This cache contains mapping between icons and disabled icons.
56    */
57   private static final Map<Icon, Icon> ourIcon2DisabledIcon = new WeakHashMap<Icon, Icon>(200);
58   @NonNls private static final Map<String, String> ourDeprecatedIconsReplacements = new HashMap<String, String>();
59
60   static {
61     ourDeprecatedIconsReplacements.put("/general/toolWindowDebugger.png", "AllIcons.Toolwindows.ToolWindowDebugger");
62     ourDeprecatedIconsReplacements.put("/general/toolWindowChanges.png", "AllIcons.Toolwindows.ToolWindowChanges");
63
64     ourDeprecatedIconsReplacements.put("/actions/showSettings.png", "AllIcons.General.ProjectSettings");
65     ourDeprecatedIconsReplacements.put("/general/ideOptions.png", "AllIcons.General.Settings");
66     ourDeprecatedIconsReplacements.put("/general/applicationSettings.png", "AllIcons.General.Settings");
67     ourDeprecatedIconsReplacements.put("/toolbarDecorator/add.png", "AllIcons.General.Add");
68     ourDeprecatedIconsReplacements.put("/vcs/customizeView.png", "AllIcons.General.Settings");
69
70     ourDeprecatedIconsReplacements.put("/vcs/refresh.png", "AllIcons.Actions.Refresh");
71     ourDeprecatedIconsReplacements.put("/actions/sync.png", "AllIcons.Actions.Refresh");
72     ourDeprecatedIconsReplacements.put("/actions/refreshUsages.png", "AllIcons.Actions.Rerun");
73
74     ourDeprecatedIconsReplacements.put("/compiler/error.png", "AllIcons.General.Error");
75     ourDeprecatedIconsReplacements.put("/compiler/hideWarnings.png", "AllIcons.General.HideWarnings");
76     ourDeprecatedIconsReplacements.put("/compiler/information.png", "AllIcons.General.Information");
77     ourDeprecatedIconsReplacements.put("/compiler/warning.png", "AllIcons.General.Warning");
78     ourDeprecatedIconsReplacements.put("/ide/errorSign.png", "AllIcons.General.Error");
79
80     ourDeprecatedIconsReplacements.put("/ant/filter.png", "AllIcons.General.Filter");
81     ourDeprecatedIconsReplacements.put("/inspector/useFilter.png", "AllIcons.General.Filter");
82
83     ourDeprecatedIconsReplacements.put("/actions/showSource.png", "AllIcons.Actions.Preview");
84     ourDeprecatedIconsReplacements.put("/actions/consoleHistory.png", "AllIcons.General.MessageHistory");
85     ourDeprecatedIconsReplacements.put("/vcs/messageHistory.png", "AllIcons.General.MessageHistory");
86   }
87
88   private static final ImageIcon EMPTY_ICON = new ImageIcon(UIUtil.createImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)) {
89     @NonNls
90     public String toString() {
91       return "Empty icon " + super.toString();
92     }
93   };
94
95   private static boolean ourIsActivated = false;
96
97   private IconLoader() { }
98
99   @Deprecated
100   public static Icon getIcon(@NotNull final Image image) {
101     return new JBImageIcon(image);
102   }
103
104   public static void setUseDarkIcons(boolean useDarkIcons) {
105     USE_DARK_ICONS = useDarkIcons;
106     ourIconsCache.clear();
107     ourIcon2DisabledIcon.clear();
108   }
109
110
111   //TODO[kb] support iconsets
112   //public static Icon getIcon(@NotNull final String path, @NotNull final String darkVariantPath) {
113   //  return new InvariantIcon(getIcon(path), getIcon(darkVariantPath));
114   //}
115
116   @NotNull
117   public static Icon getIcon(@NonNls @NotNull final String path) {
118     Class callerClass = ReflectionUtil.getGrandCallerClass();
119
120     assert callerClass != null : path;
121     return getIcon(path, callerClass);
122   }
123
124   @Nullable
125   private static Icon getReflectiveIcon(@NotNull String path, ClassLoader classLoader) {
126     try {
127       @NonNls String pckg = path.startsWith("AllIcons.") ? "com.intellij.icons." : "icons.";
128       Class cur = Class.forName(pckg + path.substring(0, path.lastIndexOf('.')).replace('.', '$'), true, classLoader);
129       Field field = cur.getField(path.substring(path.lastIndexOf('.') + 1));
130
131       return (Icon)field.get(null);
132     }
133     catch (Exception e) {
134       return null;
135     }
136   }
137
138   @Nullable
139   /**
140    * Might return null if icon was not found.
141    * Use only if you expected null return value, otherwise see {@link IconLoader#getIcon(java.lang.String)}
142    */
143   public static Icon findIcon(@NonNls @NotNull String path) {
144     Class callerClass = ReflectionUtil.getGrandCallerClass();
145     if (callerClass == null) return null;
146     return findIcon(path, callerClass);
147   }
148
149   @NotNull
150   public static Icon getIcon(@NotNull String path, @NotNull final Class aClass) {
151     final Icon icon = findIcon(path, aClass);
152     if (icon == null) {
153       LOG.error("Icon cannot be found in '" + path + "', aClass='" + aClass + "'");
154     }
155     return icon;
156   }
157
158   public static void activate() {
159     ourIsActivated = true;
160   }
161
162   private static boolean isLoaderDisabled() {
163     return !ourIsActivated;
164   }
165
166   /**
167    * Might return null if icon was not found.
168    * Use only if you expected null return value, otherwise see {@link IconLoader#getIcon(java.lang.String, java.lang.Class)}
169    */
170   @Nullable
171   public static Icon findIcon(@NotNull final String path, @NotNull final Class aClass) {
172     return findIcon(path, aClass, false);
173   }
174
175   @Nullable
176   public static Icon findIcon(@NotNull String path, @NotNull final Class aClass, boolean computeNow) {
177     path = undeprecate(path);
178     if (isReflectivePath(path)) return getReflectiveIcon(path, aClass.getClassLoader());
179
180     URL myURL = aClass.getResource(path);
181     if (myURL == null) {
182       if (STRICT) throw new RuntimeException("Can't find icon in '" + path + "' near "+aClass);
183       return null;
184     }
185     return findIcon(myURL);
186   }
187
188   @NotNull
189   private static String undeprecate(@NotNull String path) {
190     String replacement = ourDeprecatedIconsReplacements.get(path);
191     return replacement == null ? path : replacement;
192   }
193
194   private static boolean isReflectivePath(@NotNull String path) {
195     List<String> paths = StringUtil.split(path, ".");
196     return paths.size() > 1 && paths.get(0).endsWith("Icons");
197   }
198
199   @Nullable
200   public static Icon findIcon(URL url) {
201     return findIcon(url, true);
202   }
203
204   @Nullable
205   public static Icon findIcon(URL url, boolean useCache) {
206     if (url == null) {
207       return null;
208     }
209     CachedImageIcon icon = ourIconsCache.get(url);
210     if (icon == null) {
211       icon = new CachedImageIcon(url);
212       if (useCache) {
213         icon = ConcurrencyUtil.cacheOrGet(ourIconsCache, url, icon);
214       }
215     }
216     return icon;
217   }
218
219   @Nullable
220   public static Icon findIcon(@NotNull String path, @NotNull ClassLoader classLoader) {
221     path = undeprecate(path);
222     if (isReflectivePath(path)) return getReflectiveIcon(path, classLoader);
223     if (!StringUtil.startsWithChar(path, '/')) return null;
224
225     final URL url = classLoader.getResource(path.substring(1));
226     return findIcon(url);
227   }
228
229   @Nullable
230   private static Icon checkIcon(final Image image, @NotNull URL url) {
231     if (image == null || image.getHeight(LabelHolder.ourFakeComponent) < 1) { // image wasn't loaded or broken
232       return null;
233     }
234
235     final Icon icon = getIcon(image);
236     if (icon != null && !isGoodSize(icon)) {
237       LOG.error("Invalid icon: " + url); // # 22481
238       return EMPTY_ICON;
239     }
240     return icon;
241   }
242
243   public static boolean isGoodSize(@NotNull final Icon icon) {
244     return icon.getIconWidth() > 0 && icon.getIconHeight() > 0;
245   }
246
247   /**
248    * Gets (creates if necessary) disabled icon based on the passed one.
249    *
250    * @return <code>ImageIcon</code> constructed from disabled image of passed icon.
251    */
252   @Nullable
253   public static Icon getDisabledIcon(Icon icon) {
254     if (icon instanceof LazyIcon) icon = ((LazyIcon)icon).getOrComputeIcon();
255     if (icon == null) return null;
256
257     Icon disabledIcon = ourIcon2DisabledIcon.get(icon);
258     if (disabledIcon == null) {
259       if (!isGoodSize(icon)) {
260         LOG.error(icon); // # 22481
261         return EMPTY_ICON;
262       }
263       final int scale = UIUtil.isRetina() ? 2 : 1;
264       @SuppressWarnings("UndesirableClassUsage")
265       BufferedImage image = new BufferedImage(scale*icon.getIconWidth(), scale*icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
266       final Graphics2D graphics = image.createGraphics();
267
268       graphics.setColor(UIUtil.TRANSPARENT_COLOR);
269       graphics.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight());
270       graphics.scale(scale, scale);
271       icon.paintIcon(LabelHolder.ourFakeComponent, graphics, 0, 0);
272
273       graphics.dispose();
274
275       Image img = createDisabled(image);
276       if (UIUtil.isRetina()) img = RetinaImage.createFrom(img, 2, ImageLoader.ourComponent);
277
278       disabledIcon = new JBImageIcon(img);
279       ourIcon2DisabledIcon.put(icon, disabledIcon);
280     }
281     return disabledIcon;
282   }
283
284   private static Image createDisabled(BufferedImage image) {
285     final GrayFilter filter = UIUtil.getGrayFilter();
286     final ImageProducer prod = new FilteredImageSource(image.getSource(), filter);
287     return Toolkit.getDefaultToolkit().createImage(prod);
288   }
289
290   public static Icon getTransparentIcon(@NotNull final Icon icon) {
291     return getTransparentIcon(icon, 0.5f);
292   }
293
294   public static Icon getTransparentIcon(@NotNull final Icon icon, final float alpha) {
295     return new Icon() {
296       @Override
297       public int getIconHeight() {
298         return icon.getIconHeight();
299       }
300
301       @Override
302       public int getIconWidth() {
303         return icon.getIconWidth();
304       }
305
306       @Override
307       public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
308         final Graphics2D g2 = (Graphics2D)g;
309         final Composite saveComposite = g2.getComposite();
310         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
311         icon.paintIcon(c, g2, x, y);
312         g2.setComposite(saveComposite);
313       }
314     };
315   }
316
317   private static final class CachedImageIcon implements Icon {
318     private Object myRealIcon;
319     @NotNull
320     private final URL myUrl;
321     private boolean dark;
322
323     public CachedImageIcon(@NotNull URL url) {
324       myUrl = url;
325       dark = USE_DARK_ICONS;
326     }
327
328     @NotNull
329     private synchronized Icon getRealIcon() {
330       if (isLoaderDisabled() && (myRealIcon == null || dark != USE_DARK_ICONS)) return EMPTY_ICON;
331
332       if (dark != USE_DARK_ICONS) {
333         myRealIcon = null;
334         dark = USE_DARK_ICONS;
335       }
336       Object realIcon = myRealIcon;
337       if (realIcon instanceof Icon) return (Icon)realIcon;
338
339       Icon icon;
340       if (realIcon instanceof Reference) {
341         icon = ((Reference<Icon>)realIcon).get();
342         if (icon != null) return icon;
343       }
344
345       Image image = ImageLoader.loadFromUrl(myUrl);
346       icon = checkIcon(image, myUrl);
347
348       if (icon != null) {
349         if (icon.getIconWidth() < 50 && icon.getIconHeight() < 50) {
350           realIcon = icon;
351         }
352         else {
353           realIcon = new SoftReference<Icon>(icon);
354         }
355         myRealIcon = realIcon;
356       }
357
358       return icon == null ? EMPTY_ICON : icon;
359     }
360
361     @Override
362     public void paintIcon(Component c, Graphics g, int x, int y) {
363       getRealIcon().paintIcon(c, g, x, y);
364     }
365
366     @Override
367     public int getIconWidth() {
368       return getRealIcon().getIconWidth();
369     }
370
371     @Override
372     public int getIconHeight() {
373       return getRealIcon().getIconHeight();
374     }
375
376     @Override
377     public String toString() {
378       return myUrl.toString();
379     }
380   }
381
382   public abstract static class LazyIcon implements Icon {
383     private boolean myWasComputed;
384     private Icon myIcon;
385     private boolean isDarkVariant = USE_DARK_ICONS;
386
387     @Override
388     public void paintIcon(Component c, Graphics g, int x, int y) {
389       final Icon icon = getOrComputeIcon();
390       if (icon != null) {
391         icon.paintIcon(c, g, x, y);
392       }
393     }
394
395     @Override
396     public int getIconWidth() {
397       final Icon icon = getOrComputeIcon();
398       return icon != null ? icon.getIconWidth() : 0;
399     }
400
401     @Override
402     public int getIconHeight() {
403       final Icon icon = getOrComputeIcon();
404       return icon != null ? icon.getIconHeight() : 0;
405     }
406
407     protected final synchronized Icon getOrComputeIcon() {
408       if (!myWasComputed || isDarkVariant != USE_DARK_ICONS) {
409         isDarkVariant = USE_DARK_ICONS;
410         myWasComputed = true;
411         myIcon = compute();
412       }
413
414       return myIcon;
415     }
416
417     public final void load() {
418       getIconWidth();
419     }
420
421     protected abstract Icon compute();
422   }
423
424   private static class LabelHolder {
425     /**
426      * To get disabled icon with paint it into the image. Some icons require
427      * not null component to paint.
428      */
429     private static final JComponent ourFakeComponent = new JLabel();
430   }
431 }