1 /* 2 * Copyright (c) 2011, 2014, 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 /** 145 * Paints a native control, which identified by its size and a set of 146 * additional arguments using a cached image. 147 * 148 * @param g Graphics to draw the control 149 * @param control the reference to the native control 150 * @param controlState the state of the native control 151 * @param bounds the rectangle where the native part should be drawn. 152 * Note: the focus can/will be drawn outside of this bounds. 153 */ 154 static void paintFromSingleCachedImage(final Graphics2D g, 155 final JRSUIControl control, 156 final JRSUIState controlState, 157 final Rectangle bounds) { 158 if (bounds.width <= 0 || bounds.height <= 0) { 159 return; 160 } 161 162 int focus = 0; 163 if (controlState.is(JRSUIConstants.Focused.YES)) { 164 focus = JRSUIConstants.FOCUS_SIZE; 165 } 166 167 final int imgX = bounds.x - focus; 168 final int imgY = bounds.y - focus; 169 final int imgW = bounds.width + (focus << 1); 170 final int imgH = bounds.height + (focus << 1); 171 final GraphicsConfiguration config = g.getDeviceConfiguration(); 172 final ImageCache cache = ImageCache.getInstance(); 173 final AquaPixelsKey key = new AquaPixelsKey(config, imgW, imgH, 174 bounds, controlState); 175 Image img = cache.getImage(key); 176 if (img == null) { 177 178 Image baseImage = createImage(imgX, imgY, imgW, imgH, bounds, 179 control, controlState); 180 181 img = new MultiResolutionBufferedImage(baseImage, 182 (rvWidth, rvHeight) -> createImage(imgX, imgY, 183 rvWidth, rvHeight, bounds, control, controlState)); 184 185 if (!controlState.is(JRSUIConstants.Animating.YES)) { 186 cache.setImage(key, img); 187 } 188 } 189 190 g.drawImage(img, imgX, imgY, imgW, imgH, null); 191 } 192 193 private static Image createImage(int imgX, int imgY, int imgW, int imgH, 194 final Rectangle bounds, 195 final JRSUIControl control, 196 JRSUIState controlState) { 197 BufferedImage img = new BufferedImage(imgW, imgH, 198 BufferedImage.TYPE_INT_ARGB_PRE); 199 200 final WritableRaster raster = img.getRaster(); 201 final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer(); 202 203 control.set(controlState); 204 control.paint(SunWritableRaster.stealData(buffer, 0), imgW, imgH, 205 bounds.x - imgX, bounds.y - imgY, bounds.width, 206 bounds.height); 207 SunWritableRaster.markDirty(buffer); 208 return img; 209 } 210 } 211 212 private static class AquaPixelsKey implements ImageCache.PixelsKey { 213 214 private final int pixelCount; 215 private final int hash; 216 217 // key parts 218 private final GraphicsConfiguration config; 219 private final int w; 220 private final int h; 221 private final Rectangle bounds; 222 private final JRSUIState state; 223 224 AquaPixelsKey(final GraphicsConfiguration config, 225 final int w, final int h, final Rectangle bounds, 226 final JRSUIState state) { 227 this.pixelCount = w * h; 228 this.config = config; 229 this.w = w; 230 this.h = h; 231 this.bounds = bounds; 232 this.state = state; 233 this.hash = hash(); 234 } 235 236 @Override 237 public int getPixelCount() { 238 return pixelCount; 239 } 240 241 private int hash() { 242 int hash = config != null ? config.hashCode() : 0; 243 hash = 31 * hash + w; 244 hash = 31 * hash + h; 245 hash = 31 * hash + bounds.hashCode(); 246 hash = 31 * hash + state.hashCode(); 247 return hash; 248 } 249 250 @Override 251 public int hashCode() { 252 return hash; 253 } 254 255 @Override 256 public boolean equals(Object obj) { 257 if (obj instanceof AquaPixelsKey) { 258 AquaPixelsKey key = (AquaPixelsKey) obj; 259 return config == key.config && w == key.w && h == key.h 260 && bounds.equals(key.bounds) && state.equals(key.state); 261 } 262 return false; 263 } 264 } 265 266 private static class RecyclableJRSUISlicedImageControl 267 extends RecyclableSlicedImageControl { 268 269 private final JRSUIControl control; 270 private final JRSUIState state; 271 272 RecyclableJRSUISlicedImageControl(final JRSUIControl control, final JRSUIState state, final NineSliceMetrics metrics) { 273 super(metrics); 274 this.control = control; 275 this.state = state; 276 } 277 278 @Override 279 protected Image createTemplateImage(int width, int height) { 280 BufferedImage image = new BufferedImage(metrics.minW, metrics.minH, BufferedImage.TYPE_INT_ARGB_PRE); 281 282 final WritableRaster raster = image.getRaster(); 283 final DataBufferInt buffer = (DataBufferInt)raster.getDataBuffer(); 284 285 control.set(state); 286 control.paint(SunWritableRaster.stealData(buffer, 0), metrics.minW, metrics.minH, 0, 0, metrics.minW, metrics.minH); 287 288 SunWritableRaster.markDirty(buffer); 289 290 return image; 291 } 292 } 293 294 private Graphics2D getGraphics2D(final Graphics g) { 295 try { 296 return (SunGraphics2D)g; // doing a blind try is faster than checking instanceof 297 } catch (Exception ignored) { 298 if (g instanceof PeekGraphics) { 299 // if it is a peek just dirty the region 300 g.fillRect(boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); 301 } else if (g instanceof ProxyGraphics2D) { 302 final ProxyGraphics2D pg = (ProxyGraphics2D)g; 303 final Graphics2D g2d = pg.getDelegate(); 304 if (g2d instanceof SunGraphics2D) { 305 return g2d; 306 } 307 } else if (g instanceof Graphics2D) { 308 return (Graphics2D) g; 309 } 310 } 311 312 return null; 313 } 314 }