[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / platform / core-api / src / com / intellij / ui / LayeredIcon.java
1 /*
2  * Copyright 2000-2016 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.ui;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.IconLoader;
20 import com.intellij.openapi.util.IconLoader.DarkIconProvider;
21 import com.intellij.util.ArrayUtil;
22 import com.intellij.util.IconUtil;
23 import com.intellij.util.ui.JBCachingScalableIcon;
24 import org.intellij.lang.annotations.MagicConstant;
25 import org.jetbrains.annotations.NotNull;
26
27 import javax.swing.*;
28 import java.awt.*;
29 import java.util.Arrays;
30
31 import static com.intellij.util.ui.JBUIScale.ScaleType.OBJ_SCALE;
32 import static com.intellij.util.ui.JBUIScale.ScaleType.USR_SCALE;
33
34 public class LayeredIcon extends JBCachingScalableIcon<LayeredIcon> implements DarkIconProvider, CompositeIcon {
35   private static final Logger LOG = Logger.getInstance("#com.intellij.ui.LayeredIcon");
36   private final Icon[] myIcons;
37   private Icon[] myScaledIcons;
38   private final boolean[] myDisabledLayers;
39   private final int[] myHShifts;
40   private final int[] myVShifts;
41
42   private int myXShift;
43   private int myYShift;
44
45   private int myWidth;
46   private int myHeight;
47
48   {
49     getScaleContext().addUpdateListener(this::updateSize);
50     setAutoUpdateScaleContext(false);
51   }
52
53   public LayeredIcon(int layerCount) {
54     myIcons = new Icon[layerCount];
55     myDisabledLayers = new boolean[layerCount];
56     myHShifts = new int[layerCount];
57     myVShifts = new int[layerCount];
58   }
59
60   public LayeredIcon(@NotNull Icon... icons) {
61     this(icons.length);
62     for (int i = 0; i < icons.length; i++) {
63       setIcon(icons[i], i);
64     }
65   }
66
67   protected LayeredIcon(LayeredIcon icon) {
68     super(icon);
69     myIcons = ArrayUtil.copyOf(icon.myIcons);
70     myScaledIcons = null;
71     myDisabledLayers = ArrayUtil.copyOf(icon.myDisabledLayers);
72     myHShifts = ArrayUtil.copyOf(icon.myHShifts);
73     myVShifts = ArrayUtil.copyOf(icon.myVShifts);
74     myXShift = icon.myXShift;
75     myYShift = icon.myYShift;
76     myWidth = icon.myWidth;
77     myHeight = icon.myHeight;
78   }
79
80   @NotNull
81   @Override
82   public LayeredIcon copy() {
83     return new LayeredIcon(this);
84   }
85
86   @NotNull
87   @Override
88   public LayeredIcon deepCopy() {
89     LayeredIcon icon = new LayeredIcon(this);
90     for (int i = 0; i < icon.myIcons.length; i++) {
91       icon.myIcons[i] = IconUtil.copy(icon.myIcons[i], null);
92     }
93     return icon;
94   }
95
96   @NotNull
97   private Icon[] myScaledIcons() {
98     if (myScaledIcons != null) {
99       return myScaledIcons;
100     }
101     return myScaledIcons = RowIcon.scaleIcons(myIcons, getScale());
102   }
103
104   @NotNull
105   @Override
106   public LayeredIcon withIconPreScaled(boolean preScaled) {
107     super.withIconPreScaled(preScaled);
108     updateSize();
109     return this;
110   }
111
112   @Override
113   public boolean equals(Object o) {
114     if (this == o) return true;
115     if (!(o instanceof LayeredIcon)) return false;
116
117     final LayeredIcon icon = (LayeredIcon)o;
118
119     if (myHeight != icon.myHeight) return false;
120     if (myWidth != icon.myWidth) return false;
121     if (myXShift != icon.myXShift) return false;
122     if (myYShift != icon.myYShift) return false;
123     if (!Arrays.equals(myHShifts, icon.myHShifts)) return false;
124     if (!Arrays.equals(myIcons, icon.myIcons)) return false;
125     if (!Arrays.equals(myVShifts, icon.myVShifts)) return false;
126
127     return true;
128   }
129
130   @Override
131   public int hashCode() {
132     return 0;
133   }
134
135   public void setIcon(Icon icon, int layer) {
136     setIcon(icon, layer, 0, 0);
137   }
138
139   @Override
140   public Icon getIcon(int layer) {
141     return myIcons[layer];
142   }
143
144   @Override
145   public int getIconCount() {
146     return myIcons.length;
147   }
148
149   @NotNull
150   public Icon[] getAllLayers() {
151     return myIcons;
152   }
153
154   public void setIcon(Icon icon, int layer, int hShift, int vShift) {
155     if (icon instanceof LayeredIcon) {
156       ((LayeredIcon)icon).checkIHaventIconInsideMe(this);
157     }
158     myIcons[layer] = icon;
159     myScaledIcons = null;
160     myHShifts[layer] = hShift;
161     myVShifts[layer] = vShift;
162     updateSize();
163   }
164
165   /**
166    *
167    * @param constraint is expected to be one of compass-directions or CENTER
168    */
169   public void setIcon(Icon icon, int layer, @MagicConstant(valuesFromClass = SwingConstants.class) int constraint) {
170     int width = getIconWidth();
171     int height = getIconHeight();
172     int w = icon.getIconWidth();
173     int h = icon.getIconHeight();
174     if (width <= 1 || height <= 1) {
175       setIcon(icon, layer);
176       return;
177     }
178     int x;
179     int y;
180     switch (constraint) {
181       case SwingConstants.CENTER:
182         x = (width - w) / 2;
183         y = (height - h) /2;
184         break;
185       case SwingConstants.NORTH:
186         x = (width - w) / 2;
187         y = 0;
188         break;
189       case SwingConstants.NORTH_EAST:
190         x = width - w;
191         y = 0;
192         break;
193       case SwingConstants.EAST:
194         x = width - w;
195         y = (height - h) / 2;
196         break;
197       case SwingConstants.SOUTH_EAST:
198         x = width - w;
199         y = height - h;
200         break;
201       case SwingConstants.SOUTH:
202         x = (width - w) / 2;
203         y = height - h;
204         break;
205       case SwingConstants.SOUTH_WEST:
206         x = 0;
207         y = height - h;
208         break;
209       case SwingConstants.WEST:
210         x = 0;
211         y = (height - h) / 2;
212         break;
213       case SwingConstants.NORTH_WEST:
214         x = 0;
215         y = 0;
216         break;
217       default:
218         throw new IllegalArgumentException(
219           "The constraint should be one of SwingConstants' compass-directions [1..8] or CENTER [0], actual value is " + constraint);
220     }
221     setIcon(icon, layer, x, y);
222   }
223
224   private void checkIHaventIconInsideMe(Icon icon) {
225     LOG.assertTrue(icon != this);
226     for (Icon child : myIcons) {
227       if (child instanceof LayeredIcon) ((LayeredIcon)child).checkIHaventIconInsideMe(icon);
228     }
229   }
230
231   @Override
232   public void paintIcon(Component c, Graphics g, int x, int y) {
233     getScaleContext().update();
234     Icon[] icons = myScaledIcons();
235     for (int i = 0; i < icons.length; i++) {
236       Icon icon = icons[i];
237       if (icon == null || myDisabledLayers[i]) continue;
238       int xOffset = (int)Math.floor(x + scaleVal(myXShift + myHShifts(i), OBJ_SCALE));
239       int yOffset = (int)Math.floor(y + scaleVal(myYShift + myVShifts(i), OBJ_SCALE));
240       icon.paintIcon(c, g, xOffset, yOffset);
241     }
242   }
243
244   public boolean isLayerEnabled(int layer) {
245     return !myDisabledLayers[layer];
246   }
247
248   public void setLayerEnabled(int layer, boolean enabled) {
249     myDisabledLayers[layer] = !enabled;
250   }
251
252   @Override
253   public int getIconWidth() {
254     getScaleContext().update();
255     if (myWidth <= 1) updateSize();
256
257     return (int)Math.ceil(scaleVal(myWidth, OBJ_SCALE));
258   }
259
260   @Override
261   public int getIconHeight() {
262     getScaleContext().update();
263     if (myHeight <= 1) updateSize();
264
265     return (int)Math.ceil(scaleVal(myHeight, OBJ_SCALE));
266   }
267
268   private int myHShifts(int i) {
269     return (int)Math.floor(scaleVal(myHShifts[i], USR_SCALE));
270   }
271
272   private int myVShifts(int i) {
273     return (int)Math.floor(scaleVal(myVShifts[i], USR_SCALE));
274   }
275
276   protected void updateSize() {
277     int minX = Integer.MAX_VALUE;
278     int maxX = Integer.MIN_VALUE;
279     int minY = Integer.MAX_VALUE;
280     int maxY = Integer.MIN_VALUE;
281     boolean allIconsAreNull = true;
282     for (int i = 0; i < myIcons.length; i++) {
283       Icon icon = myIcons[i];
284       if (icon == null) continue;
285       allIconsAreNull = false;
286       int hShift = myHShifts(i);
287       int vShift = myVShifts(i);
288       minX = Math.min(minX, hShift);
289       maxX = Math.max(maxX, hShift + icon.getIconWidth());
290       minY = Math.min(minY, vShift);
291       maxY = Math.max(maxY, vShift + icon.getIconHeight());
292     }
293     if (allIconsAreNull) return;
294     myWidth = maxX - minX;
295     myHeight = maxY - minY;
296
297     if (myIcons.length > 1) {
298       myXShift = -minX;
299       myYShift = -minY;
300     }
301   }
302
303   @Override
304   public Icon getDarkIcon(boolean isDark) {
305     LayeredIcon newIcon = copy();
306     for (int i=0; i<newIcon.myIcons.length; i++) {
307       newIcon.myIcons[i] = IconLoader.getDarkIcon(newIcon.myIcons[i], isDark);
308     }
309     return newIcon;
310   }
311
312   public static Icon create(final Icon backgroundIcon, final Icon foregroundIcon) {
313     final LayeredIcon layeredIcon = new LayeredIcon(2);
314     layeredIcon.setIcon(backgroundIcon, 0);
315     layeredIcon.setIcon(foregroundIcon, 1);
316     return layeredIcon;
317   }
318
319   @Override
320   public String toString() {
321     return "Layered icon "+getIconWidth()+"x"+getIconHeight()+". myIcons=" + Arrays.asList(myIcons);
322   }
323 }