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.util.HashMap; 31 32 import com.apple.laf.AquaImageFactory.RecyclableSlicedImageControl; 33 import com.apple.laf.AquaImageFactory.NineSliceMetrics; 34 import com.apple.laf.AquaImageFactory.SlicedImageControl; 35 36 import sun.awt.image.*; 37 import sun.java2d.*; 38 import sun.print.*; 39 import apple.laf.*; 40 import apple.laf.JRSUIUtils.NineSliceMetricsProvider; 41 import sun.awt.image.ImageCache; 42 43 abstract class AquaPainter <T extends JRSUIState> { 44 static <T extends JRSUIState> AquaPainter<T> create(final T state) { 45 return new AquaSingleImagePainter<>(state); 46 } 47 48 static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut) { 49 return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, true); 50 } 51 52 static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle) { 53 return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, true, true); 54 } 55 56 static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle, final boolean stretchHorizontally, final boolean stretchVertically) { 57 return create(state, new NineSliceMetricsProvider() { 58 @Override 59 public NineSliceMetrics getNineSliceMetricsForState(JRSUIState state) { 60 return new NineSliceMetrics(minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, stretchHorizontally, stretchVertically); 61 } 62 }); 63 } 64 65 static <T extends JRSUIState> AquaPainter<T> create(final T state, final NineSliceMetricsProvider metricsProvider) { 66 return new AquaNineSlicingImagePainter<>(state, metricsProvider); 67 } 68 69 abstract void paint(Graphics2D g, T stateToPaint); 70 71 final Rectangle boundsRect = new Rectangle(); 72 final JRSUIControl control; 73 T state; 74 AquaPainter(final JRSUIControl control, final T state) { 75 this.control = control; 76 this.state = state; 77 } 78 79 final JRSUIControl getControl() { 80 control.set(state = state.derive()); 81 return control; 82 } 83 84 final void paint(final Graphics g, final Component c, final int x, 85 final int y, final int w, final int h) { 86 boundsRect.setBounds(x, y, w, h); 87 88 final T nextState = state.derive(); 89 final Graphics2D g2d = getGraphics2D(g); 90 if (g2d != null) paint(g2d, nextState); 91 state = nextState; 92 } 93 94 private static class AquaNineSlicingImagePainter<T extends JRSUIState> 95 extends AquaPainter<T> { 96 97 private final HashMap<T, RecyclableJRSUISlicedImageControl> slicedControlImages; 98 private final NineSliceMetricsProvider metricsProvider; 99 100 AquaNineSlicingImagePainter(final T state) { 101 this(state, null); 102 } 103 104 AquaNineSlicingImagePainter(final T state, final NineSliceMetricsProvider metricsProvider) { 105 super(new JRSUIControl(false), state); 106 this.metricsProvider = metricsProvider; 107 slicedControlImages = new HashMap<>(); 108 } 109 110 @Override 111 void paint(final Graphics2D g, final T stateToPaint) { 112 if (metricsProvider == null) { 113 AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); 114 return; 115 } 116 117 RecyclableJRSUISlicedImageControl slicesRef = slicedControlImages.get(stateToPaint); 118 if (slicesRef == null) { 119 final NineSliceMetrics metrics = metricsProvider.getNineSliceMetricsForState(stateToPaint); 120 if (metrics == null) { 121 AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); 122 return; 123 } 124 slicesRef = new RecyclableJRSUISlicedImageControl(control, stateToPaint, metrics); 125 slicedControlImages.put(stateToPaint, slicesRef); 126 } 127 final SlicedImageControl slices = slicesRef.get(); 128 slices.paint(g, boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); 129 } 130 } 131 132 private static final class AquaSingleImagePainter<T extends JRSUIState> 133 extends AquaPainter<T> { 134 135 AquaSingleImagePainter(final T state) { 136 super(new JRSUIControl(false), state); 137 } 138 139 @Override 140 void paint(final Graphics2D g, final T stateToPaint) { 141 paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); 142 } 143 144 static void paintFromSingleCachedImage(final Graphics2D g, 145 final JRSUIControl control, final JRSUIState controlState, 146 final Rectangle bounds) { 147 if (bounds.width <= 0 || bounds.height <= 0) { 148 return; 149 } 150 151 int scale = 1; 152 if (g instanceof SunGraphics2D) { 153 scale = ((SunGraphics2D) g).surfaceData.getDefaultScale(); 154 } 155 final GraphicsConfiguration config = g.getDeviceConfiguration(); 156 final ImageCache cache = ImageCache.getInstance(); 157 final int imgW = bounds.width * scale; 158 final int imgH = bounds.height * scale; 159 AquaPixelsKey key = new AquaPixelsKey(config, 160 imgW, imgH, scale, controlState); 161 BufferedImage img = (BufferedImage) cache.getImage(key); 162 if (img == null) { 163 img = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB_PRE); 164 if (!controlState.is(JRSUIConstants.Animating.YES)) { 165 cache.setImage(key, img); 166 } 167 168 final WritableRaster raster = img.getRaster(); 169 final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer(); 170 171 control.set(controlState); 172 control.paint(SunWritableRaster.stealData(buffer, 0), 173 imgW, imgH, 0, 0, bounds.width, bounds.height); 174 SunWritableRaster.markDirty(buffer); 175 } 176 177 g.drawImage(img, bounds.x, bounds.y, bounds.width, bounds.height, null); 178 } 179 } 180 181 private static class AquaPixelsKey implements ImageCache.PixelsKey { 182 183 private final int pixelCount; 184 private final int hash; 185 186 // key parts 187 private final GraphicsConfiguration config; 188 private final int w; 189 private final int h; 190 private final int scale; 191 private final JRSUIState state; 192 193 AquaPixelsKey(final GraphicsConfiguration config, 194 final int w, final int h, final int scale, 195 final JRSUIState state) { 196 this.pixelCount = w * h; 197 this.config = config; 198 this.w = w; 199 this.h = h; 200 this.scale = scale; 201 this.state = state; 202 this.hash = hash(); 203 } 204 205 public int getPixelCount() { 206 return pixelCount; 207 } 208 209 private int hash() { 210 int hash = config != null ? config.hashCode() : 0; 211 hash = 31 * hash + w; 212 hash = 31 * hash + h; 213 hash = 31 * hash + scale; 214 hash = 31 * hash + state.hashCode(); 215 return hash; 216 } 217 218 @Override 219 public int hashCode() { 220 return hash; 221 } 222 223 @Override 224 public boolean equals(Object obj) { 225 if (obj instanceof AquaPixelsKey) { 226 AquaPixelsKey key = (AquaPixelsKey) obj; 227 return config == key.config && w == key.w && h == key.h 228 && scale == key.scale && state.equals(key.state); 229 } 230 return false; 231 } 232 } 233 234 private static class RecyclableJRSUISlicedImageControl 235 extends RecyclableSlicedImageControl { 236 237 private final JRSUIControl control; 238 private final JRSUIState state; 239 240 RecyclableJRSUISlicedImageControl(final JRSUIControl control, final JRSUIState state, final NineSliceMetrics metrics) { 241 super(metrics); 242 this.control = control; 243 this.state = state; 244 } 245 246 @Override 247 protected Image createTemplateImage(int width, int height) { 248 BufferedImage image = new BufferedImage(metrics.minW, metrics.minH, BufferedImage.TYPE_INT_ARGB_PRE); 249 250 final WritableRaster raster = image.getRaster(); 251 final DataBufferInt buffer = (DataBufferInt)raster.getDataBuffer(); 252 253 control.set(state); 254 control.paint(SunWritableRaster.stealData(buffer, 0), metrics.minW, metrics.minH, 0, 0, metrics.minW, metrics.minH); 255 256 SunWritableRaster.markDirty(buffer); 257 258 return image; 259 } 260 } 261 262 private Graphics2D getGraphics2D(final Graphics g) { 263 try { 264 return (SunGraphics2D)g; // doing a blind try is faster than checking instanceof 265 } catch (Exception ignored) { 266 if (g instanceof PeekGraphics) { 267 // if it is a peek just dirty the region 268 g.fillRect(boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); 269 } else if (g instanceof ProxyGraphics2D) { 270 final ProxyGraphics2D pg = (ProxyGraphics2D)g; 271 final Graphics2D g2d = pg.getDelegate(); 272 if (g2d instanceof SunGraphics2D) { 273 return g2d; 274 } 275 } else if (g instanceof Graphics2D) { 276 return (Graphics2D) g; 277 } 278 } 279 280 return null; 281 } 282 }