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