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