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