|
1 /* |
|
2 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package com.apple.laf; |
|
27 |
|
28 import java.awt.*; |
|
29 import java.awt.image.*; |
|
30 import java.lang.ref.SoftReference; |
|
31 import java.lang.reflect.Method; |
|
32 import java.security.AccessController; |
|
33 import java.security.PrivilegedAction; |
|
34 import java.util.*; |
|
35 |
|
36 import javax.swing.*; |
|
37 import javax.swing.border.Border; |
|
38 import javax.swing.plaf.UIResource; |
|
39 |
|
40 import jdk.internal.loader.ClassLoaders; |
|
41 |
|
42 import sun.awt.AppContext; |
|
43 |
|
44 import sun.lwawt.macosx.CPlatformWindow; |
|
45 import sun.reflect.misc.ReflectUtil; |
|
46 import sun.security.action.GetPropertyAction; |
|
47 import sun.swing.SwingUtilities2; |
|
48 |
|
49 import com.apple.laf.AquaImageFactory.SlicedImageControl; |
|
50 import sun.awt.image.MultiResolutionCachedImage; |
|
51 import sun.swing.SwingAccessor; |
|
52 |
|
53 final class AquaUtils { |
|
54 |
|
55 private static final String ANIMATIONS_PROPERTY = "swing.enableAnimations"; |
|
56 |
|
57 /** |
|
58 * Suppresses default constructor, ensuring non-instantiability. |
|
59 */ |
|
60 private AquaUtils() { |
|
61 } |
|
62 |
|
63 /** |
|
64 * Convenience function for determining ComponentOrientation. Helps us |
|
65 * avoid having Munge directives throughout the code. |
|
66 */ |
|
67 static boolean isLeftToRight(final Component c) { |
|
68 return c.getComponentOrientation().isLeftToRight(); |
|
69 } |
|
70 |
|
71 static void enforceComponentOrientation(final Component c, final ComponentOrientation orientation) { |
|
72 c.setComponentOrientation(orientation); |
|
73 if (c instanceof Container) { |
|
74 for (final Component child : ((Container)c).getComponents()) { |
|
75 enforceComponentOrientation(child, orientation); |
|
76 } |
|
77 } |
|
78 } |
|
79 |
|
80 static Image generateSelectedDarkImage(final Image image) { |
|
81 final ImageFilter filter = new IconImageFilter() { |
|
82 @Override |
|
83 int getGreyFor(final int gray) { |
|
84 return gray * 75 / 100; |
|
85 } |
|
86 }; |
|
87 return map(image, filter); |
|
88 } |
|
89 |
|
90 static Image generateDisabledImage(final Image image) { |
|
91 final ImageFilter filter = new IconImageFilter() { |
|
92 @Override |
|
93 int getGreyFor(final int gray) { |
|
94 return 255 - ((255 - gray) * 65 / 100); |
|
95 } |
|
96 }; |
|
97 return map(image, filter); |
|
98 } |
|
99 |
|
100 static Image generateLightenedImage(final Image image, final int percent) { |
|
101 final GrayFilter filter = new GrayFilter(true, percent); |
|
102 return map(image, filter); |
|
103 } |
|
104 |
|
105 static Image generateFilteredImage(Image image, ImageFilter filter) { |
|
106 final ImageProducer prod = new FilteredImageSource(image.getSource(), filter); |
|
107 return Toolkit.getDefaultToolkit().createImage(prod); |
|
108 } |
|
109 |
|
110 private static Image map(Image image, ImageFilter filter) { |
|
111 if (image instanceof MultiResolutionImage) { |
|
112 return MultiResolutionCachedImage |
|
113 .map((MultiResolutionImage) image, |
|
114 (img) -> generateFilteredImage(img, filter)); |
|
115 } |
|
116 return generateFilteredImage(image, filter); |
|
117 } |
|
118 |
|
119 private abstract static class IconImageFilter extends RGBImageFilter { |
|
120 IconImageFilter() { |
|
121 super(); |
|
122 canFilterIndexColorModel = true; |
|
123 } |
|
124 |
|
125 @Override |
|
126 public final int filterRGB(final int x, final int y, final int rgb) { |
|
127 final int red = (rgb >> 16) & 0xff; |
|
128 final int green = (rgb >> 8) & 0xff; |
|
129 final int blue = rgb & 0xff; |
|
130 final int gray = getGreyFor((int)((0.30 * red + 0.59 * green + 0.11 * blue) / 3)); |
|
131 |
|
132 return (rgb & 0xff000000) | (grayTransform(red, gray) << 16) | (grayTransform(green, gray) << 8) | (grayTransform(blue, gray) << 0); |
|
133 } |
|
134 |
|
135 private static int grayTransform(final int color, final int gray) { |
|
136 int result = color - gray; |
|
137 if (result < 0) result = 0; |
|
138 if (result > 255) result = 255; |
|
139 return result; |
|
140 } |
|
141 |
|
142 abstract int getGreyFor(int gray); |
|
143 } |
|
144 |
|
145 abstract static class RecyclableObject<T> { |
|
146 private SoftReference<T> objectRef; |
|
147 |
|
148 T get() { |
|
149 T referent; |
|
150 if (objectRef != null && (referent = objectRef.get()) != null) return referent; |
|
151 referent = create(); |
|
152 objectRef = new SoftReference<T>(referent); |
|
153 return referent; |
|
154 } |
|
155 |
|
156 protected abstract T create(); |
|
157 } |
|
158 |
|
159 abstract static class RecyclableSingleton<T> { |
|
160 final T get() { |
|
161 return AppContext.getSoftReferenceValue(this, () -> getInstance()); |
|
162 } |
|
163 |
|
164 void reset() { |
|
165 AppContext.getAppContext().remove(this); |
|
166 } |
|
167 |
|
168 abstract T getInstance(); |
|
169 } |
|
170 |
|
171 static class RecyclableSingletonFromDefaultConstructor<T> extends RecyclableSingleton<T> { |
|
172 private final Class<T> clazz; |
|
173 |
|
174 RecyclableSingletonFromDefaultConstructor(final Class<T> clazz) { |
|
175 this.clazz = clazz; |
|
176 } |
|
177 |
|
178 @Override |
|
179 @SuppressWarnings("deprecation") |
|
180 T getInstance() { |
|
181 try { |
|
182 ReflectUtil.checkPackageAccess(clazz); |
|
183 return clazz.newInstance(); |
|
184 } catch (ReflectiveOperationException ignored) { |
|
185 } |
|
186 return null; |
|
187 } |
|
188 } |
|
189 |
|
190 abstract static class LazyKeyedSingleton<K, V> { |
|
191 private Map<K, V> refs; |
|
192 |
|
193 V get(final K key) { |
|
194 if (refs == null) refs = new HashMap<>(); |
|
195 |
|
196 final V cachedValue = refs.get(key); |
|
197 if (cachedValue != null) return cachedValue; |
|
198 |
|
199 final V value = getInstance(key); |
|
200 refs.put(key, value); |
|
201 return value; |
|
202 } |
|
203 |
|
204 protected abstract V getInstance(K key); |
|
205 } |
|
206 |
|
207 private static final RecyclableSingleton<Boolean> enableAnimations = new RecyclableSingleton<Boolean>() { |
|
208 @Override |
|
209 protected Boolean getInstance() { |
|
210 final String sizeProperty = (String) AccessController.doPrivileged((PrivilegedAction<?>)new GetPropertyAction( |
|
211 ANIMATIONS_PROPERTY)); |
|
212 return !"false".equals(sizeProperty); // should be true by default |
|
213 } |
|
214 }; |
|
215 private static boolean animationsEnabled() { |
|
216 return enableAnimations.get(); |
|
217 } |
|
218 |
|
219 private static final int MENU_BLINK_DELAY = 50; // 50ms == 3/60 sec, according to the spec |
|
220 static void blinkMenu(final Selectable selectable) { |
|
221 if (!animationsEnabled()) return; |
|
222 try { |
|
223 selectable.paintSelected(false); |
|
224 Thread.sleep(MENU_BLINK_DELAY); |
|
225 selectable.paintSelected(true); |
|
226 Thread.sleep(MENU_BLINK_DELAY); |
|
227 } catch (final InterruptedException ignored) { } |
|
228 } |
|
229 |
|
230 interface Selectable { |
|
231 void paintSelected(boolean selected); |
|
232 } |
|
233 |
|
234 interface JComponentPainter { |
|
235 void paint(JComponent c, Graphics g, int x, int y, int w, int h); |
|
236 } |
|
237 |
|
238 interface Painter { |
|
239 void paint(Graphics g, int x, int y, int w, int h); |
|
240 } |
|
241 |
|
242 static void paintDropShadowText(final Graphics g, final JComponent c, final Font font, final FontMetrics metrics, final int x, final int y, final int offsetX, final int offsetY, final Color textColor, final Color shadowColor, final String text) { |
|
243 g.setFont(font); |
|
244 g.setColor(shadowColor); |
|
245 SwingUtilities2.drawString(c, g, text, x + offsetX, y + offsetY + metrics.getAscent()); |
|
246 g.setColor(textColor); |
|
247 SwingUtilities2.drawString(c, g, text, x, y + metrics.getAscent()); |
|
248 } |
|
249 |
|
250 static class ShadowBorder implements Border { |
|
251 private final Painter prePainter; |
|
252 private final Painter postPainter; |
|
253 |
|
254 private final int offsetX; |
|
255 private final int offsetY; |
|
256 private final float distance; |
|
257 private final int blur; |
|
258 private final Insets insets; |
|
259 private final ConvolveOp blurOp; |
|
260 |
|
261 ShadowBorder(final Painter prePainter, final Painter postPainter, final int offsetX, final int offsetY, final float distance, final float intensity, final int blur) { |
|
262 this.prePainter = prePainter; this.postPainter = postPainter; |
|
263 this.offsetX = offsetX; this.offsetY = offsetY; this.distance = distance; this.blur = blur; |
|
264 final int halfBlur = blur / 2; |
|
265 insets = new Insets(halfBlur - offsetY, halfBlur - offsetX, halfBlur + offsetY, halfBlur + offsetX); |
|
266 |
|
267 final float blurry = intensity / (blur * blur); |
|
268 final float[] blurKernel = new float[blur * blur]; |
|
269 for (int i = 0; i < blurKernel.length; i++) blurKernel[i] = blurry; |
|
270 blurOp = new ConvolveOp(new Kernel(blur, blur, blurKernel)); |
|
271 } |
|
272 |
|
273 @Override |
|
274 public final boolean isBorderOpaque() { |
|
275 return false; |
|
276 } |
|
277 |
|
278 @Override |
|
279 public final Insets getBorderInsets(final Component c) { |
|
280 return insets; |
|
281 } |
|
282 |
|
283 @Override |
|
284 public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { |
|
285 final BufferedImage img = new BufferedImage(width + blur * 2, height + blur * 2, BufferedImage.TYPE_INT_ARGB_PRE); |
|
286 paintToImage(img, x, y, width, height); |
|
287 // debugFrame("border", img); |
|
288 g.drawImage(img, -blur, -blur, null); |
|
289 } |
|
290 |
|
291 private void paintToImage(final BufferedImage img, final int x, final int y, final int width, final int height) { |
|
292 // clear the prior image |
|
293 Graphics2D imgG = (Graphics2D)img.getGraphics(); |
|
294 imgG.setComposite(AlphaComposite.Clear); |
|
295 imgG.setColor(Color.black); |
|
296 imgG.fillRect(0, 0, width + blur * 2, height + blur * 2); |
|
297 |
|
298 final int adjX = (int)(x + blur + offsetX + (insets.left * distance)); |
|
299 final int adjY = (int)(y + blur + offsetY + (insets.top * distance)); |
|
300 final int adjW = (int)(width - (insets.left + insets.right) * distance); |
|
301 final int adjH = (int)(height - (insets.top + insets.bottom) * distance); |
|
302 |
|
303 // let the delegate paint whatever they want to be blurred |
|
304 imgG.setComposite(AlphaComposite.DstAtop); |
|
305 if (prePainter != null) prePainter.paint(imgG, adjX, adjY, adjW, adjH); |
|
306 imgG.dispose(); |
|
307 |
|
308 // blur the prior image back into the same pixels |
|
309 imgG = (Graphics2D)img.getGraphics(); |
|
310 imgG.setComposite(AlphaComposite.DstAtop); |
|
311 imgG.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); |
|
312 imgG.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); |
|
313 imgG.drawImage(img, blurOp, 0, 0); |
|
314 |
|
315 if (postPainter != null) postPainter.paint(imgG, adjX, adjY, adjW, adjH); |
|
316 imgG.dispose(); |
|
317 } |
|
318 } |
|
319 |
|
320 static class SlicedShadowBorder extends ShadowBorder { |
|
321 private final SlicedImageControl slices; |
|
322 |
|
323 SlicedShadowBorder(final Painter prePainter, final Painter postPainter, final int offsetX, final int offsetY, final float distance, final float intensity, final int blur, final int templateWidth, final int templateHeight, final int leftCut, final int topCut, final int rightCut, final int bottomCut) { |
|
324 super(prePainter, postPainter, offsetX, offsetY, distance, intensity, blur); |
|
325 |
|
326 final BufferedImage i = new BufferedImage(templateWidth, templateHeight, BufferedImage.TYPE_INT_ARGB_PRE); |
|
327 super.paintBorder(null, i.getGraphics(), 0, 0, templateWidth, templateHeight); |
|
328 // debugFrame("slices", i); |
|
329 slices = new SlicedImageControl(i, leftCut, topCut, rightCut, bottomCut, false); |
|
330 } |
|
331 |
|
332 @Override |
|
333 public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { |
|
334 slices.paint(g, x, y, width, height); |
|
335 } |
|
336 } |
|
337 |
|
338 // static void debugFrame(String name, Image image) { |
|
339 // JFrame f = new JFrame(name); |
|
340 // f.setContentPane(new JLabel(new ImageIcon(image))); |
|
341 // f.pack(); |
|
342 // f.setVisible(true); |
|
343 // } |
|
344 |
|
345 // special casing naughty applications, like InstallAnywhere |
|
346 // <rdar://problem/4851533> REGR: JButton: Myst IV: the buttons of 1.0.3 updater have redraw issue |
|
347 static boolean shouldUseOpaqueButtons() { |
|
348 // can we use ClassLoader.getSystemClassLoader here? |
|
349 final ClassLoader launcherClassLoader = ClassLoaders.appClassLoader(); |
|
350 if (classExists(launcherClassLoader, "com.installshield.wizard.platform.macosx.MacOSXUtils")) return true; |
|
351 return false; |
|
352 } |
|
353 |
|
354 private static boolean classExists(final ClassLoader classLoader, final String clazzName) { |
|
355 try { |
|
356 return Class.forName(clazzName, false, classLoader) != null; |
|
357 } catch (final Throwable ignored) { } |
|
358 return false; |
|
359 } |
|
360 |
|
361 private static final RecyclableSingleton<Method> getJComponentGetFlagMethod = new RecyclableSingleton<Method>() { |
|
362 @Override |
|
363 protected Method getInstance() { |
|
364 return AccessController.doPrivileged( |
|
365 new PrivilegedAction<Method>() { |
|
366 @Override |
|
367 public Method run() { |
|
368 try { |
|
369 final Method method = JComponent.class.getDeclaredMethod( |
|
370 "getFlag", new Class<?>[] { int.class }); |
|
371 method.setAccessible(true); |
|
372 return method; |
|
373 } catch (final Throwable ignored) { |
|
374 return null; |
|
375 } |
|
376 } |
|
377 } |
|
378 ); |
|
379 } |
|
380 }; |
|
381 |
|
382 private static final int OPAQUE_SET_FLAG = 24; // private int JComponent.OPAQUE_SET |
|
383 static boolean hasOpaqueBeenExplicitlySet(final JComponent c) { |
|
384 return SwingAccessor.getJComponentAccessor().getFlag(c, OPAQUE_SET_FLAG); |
|
385 } |
|
386 |
|
387 private static boolean isWindowTextured(final Component c) { |
|
388 if (!(c instanceof JComponent)) { |
|
389 return false; |
|
390 } |
|
391 final JRootPane pane = ((JComponent) c).getRootPane(); |
|
392 if (pane == null) { |
|
393 return false; |
|
394 } |
|
395 Object prop = pane.getClientProperty( |
|
396 CPlatformWindow.WINDOW_BRUSH_METAL_LOOK); |
|
397 if (prop != null) { |
|
398 return Boolean.parseBoolean(prop.toString()); |
|
399 } |
|
400 prop = pane.getClientProperty(CPlatformWindow.WINDOW_STYLE); |
|
401 return prop != null && "textured".equals(prop); |
|
402 } |
|
403 |
|
404 private static Color resetAlpha(final Color color) { |
|
405 return new Color(color.getRed(), color.getGreen(), color.getBlue(), 0); |
|
406 } |
|
407 |
|
408 static void fillRect(final Graphics g, final Component c) { |
|
409 fillRect(g, c, c.getBackground(), 0, 0, c.getWidth(), c.getHeight()); |
|
410 } |
|
411 |
|
412 static void fillRect(final Graphics g, final Component c, final Color color, |
|
413 final int x, final int y, final int w, final int h) { |
|
414 if (!(g instanceof Graphics2D)) { |
|
415 return; |
|
416 } |
|
417 final Graphics2D cg = (Graphics2D) g.create(); |
|
418 try { |
|
419 if (color instanceof UIResource && isWindowTextured(c) |
|
420 && color.equals(SystemColor.window)) { |
|
421 cg.setComposite(AlphaComposite.Src); |
|
422 cg.setColor(resetAlpha(color)); |
|
423 } else { |
|
424 cg.setColor(color); |
|
425 } |
|
426 cg.fillRect(x, y, w, h); |
|
427 } finally { |
|
428 cg.dispose(); |
|
429 } |
|
430 } |
|
431 } |
|
432 |