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