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