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