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 T getInstance() { 201 try { 202 ReflectUtil.checkPackageAccess(clazz); 203 return clazz.newInstance(); 204 } catch (InstantiationException | IllegalAccessException ignored) { 205 } 206 return null; 207 } 208 } 209 210 abstract static class LazyKeyedSingleton<K, V> { 211 private Map<K, V> refs; 212 213 V get(final K key) { 214 if (refs == null) refs = new HashMap<>(); 215 216 final V cachedValue = refs.get(key); 217 if (cachedValue != null) return cachedValue; 218 219 final V value = getInstance(key); 220 refs.put(key, value); 221 return value; 222 } 223 224 protected abstract V getInstance(K key); 225 } 226 227 private static final RecyclableSingleton<Boolean> enableAnimations = new RecyclableSingleton<Boolean>() { 228 @Override 229 protected Boolean getInstance() { 230 final String sizeProperty = (String) AccessController.doPrivileged((PrivilegedAction<?>)new GetPropertyAction( 231 ANIMATIONS_PROPERTY)); 232 return !"false".equals(sizeProperty); // should be true by default 233 } 234 }; 235 private static boolean animationsEnabled() { 236 return enableAnimations.get(); 237 } 238 239 private static final int MENU_BLINK_DELAY = 50; // 50ms == 3/60 sec, according to the spec 240 static void blinkMenu(final Selectable selectable) { 241 if (!animationsEnabled()) return; 242 try { 243 selectable.paintSelected(false); 244 Thread.sleep(MENU_BLINK_DELAY); 245 selectable.paintSelected(true); 246 Thread.sleep(MENU_BLINK_DELAY); 247 } catch (final InterruptedException ignored) { } 248 } 249 250 interface Selectable { 251 void paintSelected(boolean selected); 252 } 253 254 interface JComponentPainter { 255 void paint(JComponent c, Graphics g, int x, int y, int w, int h); 256 } 257 258 interface Painter { 259 void paint(Graphics g, int x, int y, int w, int h); 260 } 261 262 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) { 263 g.setFont(font); 264 g.setColor(shadowColor); 265 SwingUtilities2.drawString(c, g, text, x + offsetX, y + offsetY + metrics.getAscent()); 266 g.setColor(textColor); 267 SwingUtilities2.drawString(c, g, text, x, y + metrics.getAscent()); 268 } 269 270 static class ShadowBorder implements Border { 271 private final Painter prePainter; 272 private final Painter postPainter; 273 274 private final int offsetX; 275 private final int offsetY; 276 private final float distance; 277 private final int blur; 278 private final Insets insets; 279 private final ConvolveOp blurOp; 280 281 ShadowBorder(final Painter prePainter, final Painter postPainter, final int offsetX, final int offsetY, final float distance, final float intensity, final int blur) { 282 this.prePainter = prePainter; this.postPainter = postPainter; 283 this.offsetX = offsetX; this.offsetY = offsetY; this.distance = distance; this.blur = blur; 284 final int halfBlur = blur / 2; 285 insets = new Insets(halfBlur - offsetY, halfBlur - offsetX, halfBlur + offsetY, halfBlur + offsetX); 286 287 final float blurry = intensity / (blur * blur); 288 final float[] blurKernel = new float[blur * blur]; 289 for (int i = 0; i < blurKernel.length; i++) blurKernel[i] = blurry; 290 blurOp = new ConvolveOp(new Kernel(blur, blur, blurKernel)); 291 } 292 293 @Override 294 public final boolean isBorderOpaque() { 295 return false; 296 } 297 298 @Override 299 public final Insets getBorderInsets(final Component c) { 300 return insets; 301 } 302 303 @Override 304 public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { 305 final BufferedImage img = new BufferedImage(width + blur * 2, height + blur * 2, BufferedImage.TYPE_INT_ARGB_PRE); 306 paintToImage(img, x, y, width, height); 307 // debugFrame("border", img); 308 g.drawImage(img, -blur, -blur, null); 309 } 310 311 private void paintToImage(final BufferedImage img, final int x, final int y, final int width, final int height) { 312 // clear the prior image 313 Graphics2D imgG = (Graphics2D)img.getGraphics(); 314 imgG.setComposite(AlphaComposite.Clear); 315 imgG.setColor(Color.black); 316 imgG.fillRect(0, 0, width + blur * 2, height + blur * 2); 317 318 final int adjX = (int)(x + blur + offsetX + (insets.left * distance)); 319 final int adjY = (int)(y + blur + offsetY + (insets.top * distance)); 320 final int adjW = (int)(width - (insets.left + insets.right) * distance); 321 final int adjH = (int)(height - (insets.top + insets.bottom) * distance); 322 323 // let the delegate paint whatever they want to be blurred 324 imgG.setComposite(AlphaComposite.DstAtop); 325 if (prePainter != null) prePainter.paint(imgG, adjX, adjY, adjW, adjH); 326 imgG.dispose(); 327 328 // blur the prior image back into the same pixels 329 imgG = (Graphics2D)img.getGraphics(); 330 imgG.setComposite(AlphaComposite.DstAtop); 331 imgG.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); 332 imgG.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 333 imgG.drawImage(img, blurOp, 0, 0); 334 335 if (postPainter != null) postPainter.paint(imgG, adjX, adjY, adjW, adjH); 336 imgG.dispose(); 337 } 338 } 339 340 static class SlicedShadowBorder extends ShadowBorder { 341 private final SlicedImageControl slices; 342 343 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) { 344 super(prePainter, postPainter, offsetX, offsetY, distance, intensity, blur); 345 346 final BufferedImage i = new BufferedImage(templateWidth, templateHeight, BufferedImage.TYPE_INT_ARGB_PRE); 347 super.paintBorder(null, i.getGraphics(), 0, 0, templateWidth, templateHeight); 348 // debugFrame("slices", i); 349 slices = new SlicedImageControl(i, leftCut, topCut, rightCut, bottomCut, false); 350 } 351 352 @Override 353 public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) { 354 slices.paint(g, x, y, width, height); 355 } 356 } 357 358 // static void debugFrame(String name, Image image) { 359 // JFrame f = new JFrame(name); 360 // f.setContentPane(new JLabel(new ImageIcon(image))); 361 // f.pack(); 362 // f.setVisible(true); 363 // } 364 365 // special casing naughty applications, like InstallAnywhere 366 // <rdar://problem/4851533> REGR: JButton: Myst IV: the buttons of 1.0.3 updater have redraw issue 367 static boolean shouldUseOpaqueButtons() { 368 // can we use ClassLoader.getSystemClassLoader here? 369 final ClassLoader launcherClassLoader = ClassLoaders.appClassLoader(); 370 if (classExists(launcherClassLoader, "com.installshield.wizard.platform.macosx.MacOSXUtils")) return true; 371 return false; 372 } 373 374 private static boolean classExists(final ClassLoader classLoader, final String clazzName) { 375 try { 376 return Class.forName(clazzName, false, classLoader) != null; 377 } catch (final Throwable ignored) { } 378 return false; 379 } 380 381 private static final RecyclableSingleton<Method> getJComponentGetFlagMethod = new RecyclableSingleton<Method>() { 382 @Override 383 protected Method getInstance() { 384 return AccessController.doPrivileged( 385 new PrivilegedAction<Method>() { 386 @Override 387 public Method run() { 388 try { 389 final Method method = JComponent.class.getDeclaredMethod( 390 "getFlag", new Class<?>[] { int.class }); 391 method.setAccessible(true); 392 return method; 393 } catch (final Throwable ignored) { 394 return null; 395 } 396 } 397 } 398 ); 399 } 400 }; 401 402 private static final Integer OPAQUE_SET_FLAG = 24; // private int JComponent.OPAQUE_SET 403 static boolean hasOpaqueBeenExplicitlySet(final JComponent c) { 404 final Method method = getJComponentGetFlagMethod.get(); 405 if (method == null) return false; 406 try { 407 return Boolean.TRUE.equals(method.invoke(c, OPAQUE_SET_FLAG)); 408 } catch (final Throwable ignored) { 409 return false; 410 } 411 } 412 413 private static boolean isWindowTextured(final Component c) { 414 if (!(c instanceof JComponent)) { 415 return false; 416 } 417 final JRootPane pane = ((JComponent) c).getRootPane(); 418 if (pane == null) { 419 return false; 420 } 421 Object prop = pane.getClientProperty( 422 CPlatformWindow.WINDOW_BRUSH_METAL_LOOK); 423 if (prop != null) { 424 return Boolean.parseBoolean(prop.toString()); 425 } 426 prop = pane.getClientProperty(CPlatformWindow.WINDOW_STYLE); 427 return prop != null && "textured".equals(prop); 428 } 429 430 private static Color resetAlpha(final Color color) { 431 return new Color(color.getRed(), color.getGreen(), color.getBlue(), 0); 432 } 433 434 static void fillRect(final Graphics g, final Component c) { 435 fillRect(g, c, c.getBackground(), 0, 0, c.getWidth(), c.getHeight()); 436 } 437 438 static void fillRect(final Graphics g, final Component c, final Color color, 439 final int x, final int y, final int w, final int h) { 440 if (!(g instanceof Graphics2D)) { 441 return; 442 } 443 final Graphics2D cg = (Graphics2D) g.create(); 444 try { 445 if (color instanceof UIResource && isWindowTextured(c) 446 && color.equals(SystemColor.window)) { 447 cg.setComposite(AlphaComposite.Src); 448 cg.setColor(resetAlpha(color)); 449 } else { 450 cg.setColor(color); 451 } 452 cg.fillRect(x, y, w, h); 453 } finally { 454 cg.dispose(); 455 } 456 } 457 } 458