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