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