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 sun.awt.AppContext;
41 
42 import sun.lwawt.macosx.CImage;
43 import sun.lwawt.macosx.CImage.Creator;
44 import sun.lwawt.macosx.CPlatformWindow;
45 import sun.misc.Launcher;
46 import sun.reflect.misc.ReflectUtil;
47 import sun.security.action.GetPropertyAction;
48 import sun.swing.SwingUtilities2;
49 
50 import com.apple.laf.AquaImageFactory.SlicedImageControl;
51 import sun.awt.image.MultiResolutionCachedImage;
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      */
AquaUtils()60     private AquaUtils() {
61     }
62 
63     /**
64      * Convenience function for determining ComponentOrientation.  Helps us
65      * avoid having Munge directives throughout the code.
66      */
isLeftToRight(final Component c)67     static boolean isLeftToRight(final Component c) {
68         return c.getComponentOrientation().isLeftToRight();
69     }
70 
enforceComponentOrientation(final Component c, final ComponentOrientation orientation)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 
getCImageCreatorInternal()80     private static Creator getCImageCreatorInternal() {
81         return AccessController.doPrivileged(new PrivilegedAction<Creator>() {
82             @Override
83             public Creator run() {
84                 try {
85                     final Method getCreatorMethod = CImage.class.getDeclaredMethod("getCreator", new Class[] {});
86                     getCreatorMethod.setAccessible(true);
87                     return (Creator)getCreatorMethod.invoke(null, new Object[] {});
88                 } catch (final Exception ignored) {
89                     return null;
90                 }
91             }
92         });
93     }
94 
95     private static final RecyclableSingleton<Creator> cImageCreator = new RecyclableSingleton<Creator>() {
96         @Override
97         protected Creator getInstance() {
98             return getCImageCreatorInternal();
99         }
100     };
101     static Creator getCImageCreator() {
102         return cImageCreator.get();
103     }
104 
105     static Image generateSelectedDarkImage(final Image image) {
106         final ImageProducer prod = new FilteredImageSource(image.getSource(), new IconImageFilter() {
107             @Override
108             int getGreyFor(final int gray) {
109                 return gray * 75 / 100;
110             }
111         });
112         return Toolkit.getDefaultToolkit().createImage(prod);
113     }
114 
115     static Image generateDisabledImage(final Image image) {
116         final ImageProducer prod = new FilteredImageSource(image.getSource(), new IconImageFilter() {
117             @Override
118             int getGreyFor(final int gray) {
119                 return 255 - ((255 - gray) * 65 / 100);
120             }
121         });
122         return Toolkit.getDefaultToolkit().createImage(prod);
123     }
124 
125     static Image generateLightenedImage(final Image image, final int percent) {
126         final GrayFilter filter = new GrayFilter(true, percent);
127         return (image instanceof MultiResolutionCachedImage)
128                 ? ((MultiResolutionCachedImage) image).map(
129                         rv -> generateLightenedImage(rv, filter))
130                 : generateLightenedImage(image, filter);
131     }
132 
133     static Image generateLightenedImage(Image image, ImageFilter filter) {
134         final ImageProducer prod = new FilteredImageSource(image.getSource(), filter);
135         return Toolkit.getDefaultToolkit().createImage(prod);
136     }
137 
138     private abstract static class IconImageFilter extends RGBImageFilter {
139         IconImageFilter() {
140             super();
141             canFilterIndexColorModel = true;
142         }
143 
144         @Override
145         public final int filterRGB(final int x, final int y, final int rgb) {
146             final int red = (rgb >> 16) & 0xff;
147             final int green = (rgb >> 8) & 0xff;
148             final int blue = rgb & 0xff;
149             final int gray = getGreyFor((int)((0.30 * red + 0.59 * green + 0.11 * blue) / 3));
150 
151             return (rgb & 0xff000000) | (grayTransform(red, gray) << 16) | (grayTransform(green, gray) << 8) | (grayTransform(blue, gray) << 0);
152         }
153 
154         private static int grayTransform(final int color, final int gray) {
155             int result = color - gray;
156             if (result < 0) result = 0;
157             if (result > 255) result = 255;
158             return result;
159         }
160 
161         abstract int getGreyFor(int gray);
162     }
163 
164     abstract static class RecyclableObject<T> {
165         private SoftReference<T> objectRef;
166 
167         T get() {
168             T referent;
169             if (objectRef != null && (referent = objectRef.get()) != null) return referent;
170             referent = create();
171             objectRef = new SoftReference<T>(referent);
172             return referent;
173         }
174 
175         protected abstract T create();
176     }
177 
178     abstract static class RecyclableSingleton<T> {
179         final T get() {
180             return AppContext.getSoftReferenceValue(this, () -> getInstance());
181         }
182 
183         void reset() {
184             AppContext.getAppContext().remove(this);
185         }
186 
187         abstract T getInstance();
188     }
189 
190     static class RecyclableSingletonFromDefaultConstructor<T> extends RecyclableSingleton<T> {
191         private final Class<T> clazz;
192 
193         RecyclableSingletonFromDefaultConstructor(final Class<T> clazz) {
194             this.clazz = clazz;
195         }
196 
197         @Override
198         T getInstance() {
199             try {
200                 ReflectUtil.checkPackageAccess(clazz);
201                 return clazz.newInstance();
202             } catch (InstantiationException | IllegalAccessException ignored) {
203             }
204             return null;
205         }
206     }
207 
208     abstract static class LazyKeyedSingleton<K, V> {
209         private Map<K, V> refs;
210 
211         V get(final K key) {
212             if (refs == null) refs = new HashMap<>();
213 
214             final V cachedValue = refs.get(key);
215             if (cachedValue != null) return cachedValue;
216 
217             final V value = getInstance(key);
218             refs.put(key, value);
219             return value;
220         }
221 
222         protected abstract V getInstance(K key);
223     }
224 
225     private static final RecyclableSingleton<Boolean> enableAnimations = new RecyclableSingleton<Boolean>() {
226         @Override
227         protected Boolean getInstance() {
228             final String sizeProperty = (String) AccessController.doPrivileged((PrivilegedAction<?>)new GetPropertyAction(
229                     ANIMATIONS_PROPERTY));
230             return !"false".equals(sizeProperty); // should be true by default
231         }
232     };
233     private static boolean animationsEnabled() {
234         return enableAnimations.get();
235     }
236 
237     private static final int MENU_BLINK_DELAY = 50; // 50ms == 3/60 sec, according to the spec
238     static void blinkMenu(final Selectable selectable) {
239         if (!animationsEnabled()) return;
240         try {
241             selectable.paintSelected(false);
242             Thread.sleep(MENU_BLINK_DELAY);
243             selectable.paintSelected(true);
244             Thread.sleep(MENU_BLINK_DELAY);
245         } catch (final InterruptedException ignored) { }
246     }
247 
248     interface Selectable {
249         void paintSelected(boolean selected);
250     }
251 
252     interface JComponentPainter {
253         void paint(JComponent c, Graphics g, int x, int y, int w, int h);
254     }
255 
256     interface Painter {
257         void paint(Graphics g, int x, int y, int w, int h);
258     }
259 
260     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) {
261         g.setFont(font);
262         g.setColor(shadowColor);
263         SwingUtilities2.drawString(c, g, text, x + offsetX, y + offsetY + metrics.getAscent());
264         g.setColor(textColor);
265         SwingUtilities2.drawString(c, g, text, x, y + metrics.getAscent());
266     }
267 
268     static class ShadowBorder implements Border {
269         private final Painter prePainter;
270         private final Painter postPainter;
271 
272         private final int offsetX;
273         private final int offsetY;
274         private final float distance;
275         private final int blur;
276         private final Insets insets;
277         private final ConvolveOp blurOp;
278 
279         ShadowBorder(final Painter prePainter, final Painter postPainter, final int offsetX, final int offsetY, final float distance, final float intensity, final int blur) {
280             this.prePainter = prePainter; this.postPainter = postPainter;
281             this.offsetX = offsetX; this.offsetY = offsetY; this.distance = distance; this.blur = blur;
282             final int halfBlur = blur / 2;
283             insets = new Insets(halfBlur - offsetY, halfBlur - offsetX, halfBlur + offsetY, halfBlur + offsetX);
284 
285             final float blurry = intensity / (blur * blur);
286             final float[] blurKernel = new float[blur * blur];
287             for (int i = 0; i < blurKernel.length; i++) blurKernel[i] = blurry;
288             blurOp = new ConvolveOp(new Kernel(blur, blur, blurKernel));
289         }
290 
291         @Override
292         public final boolean isBorderOpaque() {
293             return false;
294         }
295 
296         @Override
297         public final Insets getBorderInsets(final Component c) {
298             return insets;
299         }
300 
301         @Override
302         public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
303             final BufferedImage img = new BufferedImage(width + blur * 2, height + blur * 2, BufferedImage.TYPE_INT_ARGB_PRE);
304             paintToImage(img, x, y, width, height);
305 //            debugFrame("border", img);
306             g.drawImage(img, -blur, -blur, null);
307         }
308 
309         private void paintToImage(final BufferedImage img, final int x, final int y, final int width, final int height) {
310             // clear the prior image
311             Graphics2D imgG = (Graphics2D)img.getGraphics();
312             imgG.setComposite(AlphaComposite.Clear);
313             imgG.setColor(Color.black);
314             imgG.fillRect(0, 0, width + blur * 2, height + blur * 2);
315 
316             final int adjX = (int)(x + blur + offsetX + (insets.left * distance));
317             final int adjY = (int)(y + blur + offsetY + (insets.top * distance));
318             final int adjW = (int)(width - (insets.left + insets.right) * distance);
319             final int adjH = (int)(height - (insets.top + insets.bottom) * distance);
320 
321             // let the delegate paint whatever they want to be blurred
322             imgG.setComposite(AlphaComposite.DstAtop);
323             if (prePainter != null) prePainter.paint(imgG, adjX, adjY, adjW, adjH);
324             imgG.dispose();
325 
326             // blur the prior image back into the same pixels
327             imgG = (Graphics2D)img.getGraphics();
328             imgG.setComposite(AlphaComposite.DstAtop);
329             imgG.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
330             imgG.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
331             imgG.drawImage(img, blurOp, 0, 0);
332 
333             if (postPainter != null) postPainter.paint(imgG, adjX, adjY, adjW, adjH);
334             imgG.dispose();
335         }
336     }
337 
338     static class SlicedShadowBorder extends ShadowBorder {
339         private final SlicedImageControl slices;
340 
341         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) {
342             super(prePainter, postPainter, offsetX, offsetY, distance, intensity, blur);
343 
344             final BufferedImage i = new BufferedImage(templateWidth, templateHeight, BufferedImage.TYPE_INT_ARGB_PRE);
345             super.paintBorder(null, i.getGraphics(), 0, 0, templateWidth, templateHeight);
346 //            debugFrame("slices", i);
347             slices = new SlicedImageControl(i, leftCut, topCut, rightCut, bottomCut, false);
348         }
349 
350         @Override
351         public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
352             slices.paint(g, x, y, width, height);
353         }
354     }
355 
356 //    static void debugFrame(String name, Image image) {
357 //        JFrame f = new JFrame(name);
358 //        f.setContentPane(new JLabel(new ImageIcon(image)));
359 //        f.pack();
360 //        f.setVisible(true);
361 //    }
362 
363     // special casing naughty applications, like InstallAnywhere
364     // <rdar://problem/4851533> REGR: JButton: Myst IV: the buttons of 1.0.3 updater have redraw issue
365     static boolean shouldUseOpaqueButtons() {
366         final ClassLoader launcherClassLoader = Launcher.getLauncher().getClassLoader();
367         if (classExists(launcherClassLoader, "com.installshield.wizard.platform.macosx.MacOSXUtils")) return true;
368         return false;
369     }
370 
371     private static boolean classExists(final ClassLoader classLoader, final String clazzName) {
372         try {
373             return Class.forName(clazzName, false, classLoader) != null;
374         } catch (final Throwable ignored) { }
375         return false;
376     }
377 
378     private static final RecyclableSingleton<Method> getJComponentGetFlagMethod = new RecyclableSingleton<Method>() {
379         @Override
380         protected Method getInstance() {
381             return AccessController.doPrivileged(
382                 new PrivilegedAction<Method>() {
383                     @Override
384                     public Method run() {
385                         try {
386                             final Method method = JComponent.class.getDeclaredMethod("getFlag", new Class[] { int.class });
387                             method.setAccessible(true);
388                             return method;
389                         } catch (final Throwable ignored) {
390                             return null;
391                         }
392                     }
393                 }
394             );
395         }
396     };
397 
398     private static final Integer OPAQUE_SET_FLAG = 24; // private int JComponent.OPAQUE_SET
399     static boolean hasOpaqueBeenExplicitlySet(final JComponent c) {
400         final Method method = getJComponentGetFlagMethod.get();
401         if (method == null) return false;
402         try {
403             return Boolean.TRUE.equals(method.invoke(c, OPAQUE_SET_FLAG));
404         } catch (final Throwable ignored) {
405             return false;
406         }
407     }
408 
409     private static boolean isWindowTextured(final Component c) {
410         if (!(c instanceof JComponent)) {
411             return false;
412         }
413         final JRootPane pane = ((JComponent) c).getRootPane();
414         if (pane == null) {
415             return false;
416         }
417         Object prop = pane.getClientProperty(
418                 CPlatformWindow.WINDOW_BRUSH_METAL_LOOK);
419         if (prop != null) {
420             return Boolean.parseBoolean(prop.toString());
421         }
422         prop = pane.getClientProperty(CPlatformWindow.WINDOW_STYLE);
423         return prop != null && "textured".equals(prop);
424     }
425 
426     private static Color resetAlpha(final Color color) {
427         return new Color(color.getRed(), color.getGreen(), color.getBlue(), 0);
428     }
429 
430     static void fillRect(final Graphics g, final Component c) {
431         fillRect(g, c, c.getBackground(), 0, 0, c.getWidth(), c.getHeight());
432     }
433 
434     static void fillRect(final Graphics g, final Component c, final Color color,
435                          final int x, final int y, final int w, final int h) {
436         if (!(g instanceof Graphics2D)) {
437             return;
438         }
439         final Graphics2D cg = (Graphics2D) g.create();
440         try {
441             if (color instanceof UIResource && isWindowTextured(c)
442                     && color.equals(SystemColor.window)) {
443                 cg.setComposite(AlphaComposite.Src);
444                 cg.setColor(resetAlpha(color));
445             } else {
446                 cg.setColor(color);
447             }
448             cg.fillRect(x, y, w, h);
449         } finally {
450             cg.dispose();
451         }
452     }
453 }
454 
455