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