src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java
changeset 47216 71c04702a3d5
parent 42218 35e0972b2533
child 52242 7bd9745e8e15
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     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