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