| /* | |
| * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package com.apple.laf; | |
| import java.awt.*; | |
| import java.awt.image.*; | |
| import java.util.HashMap; | |
| import com.apple.laf.AquaImageFactory.RecyclableSlicedImageControl; | |
| import com.apple.laf.AquaImageFactory.NineSliceMetrics; | |
| import com.apple.laf.AquaImageFactory.SlicedImageControl; | |
| import sun.awt.image.*; | |
| import sun.java2d.*; | |
| import sun.print.*; | |
| import apple.laf.*; | |
| import apple.laf.JRSUIUtils.NineSliceMetricsProvider; | |
| import sun.awt.image.ImageCache; | |
| abstract class AquaPainter <T extends JRSUIState> { | |
| static <T extends JRSUIState> AquaPainter<T> create(final T state) { | |
| return new AquaSingleImagePainter<>(state); | |
| } | |
| 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) { | |
| return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, true); | |
| } | |
| 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) { | |
| return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, true, true); | |
| } | |
| 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) { | |
| return create(state, new NineSliceMetricsProvider() { | |
| @Override | |
| public NineSliceMetrics getNineSliceMetricsForState(JRSUIState state) { | |
| return new NineSliceMetrics(minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, stretchHorizontally, stretchVertically); | |
| } | |
| }); | |
| } | |
| static <T extends JRSUIState> AquaPainter<T> create(final T state, final NineSliceMetricsProvider metricsProvider) { | |
| return new AquaNineSlicingImagePainter<>(state, metricsProvider); | |
| } | |
| abstract void paint(Graphics2D g, T stateToPaint); | |
| final Rectangle boundsRect = new Rectangle(); | |
| final JRSUIControl control; | |
| T state; | |
| AquaPainter(final JRSUIControl control, final T state) { | |
| this.control = control; | |
| this.state = state; | |
| } | |
| final JRSUIControl getControl() { | |
| control.set(state = state.derive()); | |
| return control; | |
| } | |
| final void paint(final Graphics g, final Component c, final int x, | |
| final int y, final int w, final int h) { | |
| boundsRect.setBounds(x, y, w, h); | |
| final T nextState = state.derive(); | |
| final Graphics2D g2d = getGraphics2D(g); | |
| if (g2d != null) paint(g2d, nextState); | |
| state = nextState; | |
| } | |
| private static class AquaNineSlicingImagePainter<T extends JRSUIState> | |
| extends AquaPainter<T> { | |
| private final HashMap<T, RecyclableJRSUISlicedImageControl> slicedControlImages; | |
| private final NineSliceMetricsProvider metricsProvider; | |
| AquaNineSlicingImagePainter(final T state) { | |
| this(state, null); | |
| } | |
| AquaNineSlicingImagePainter(final T state, final NineSliceMetricsProvider metricsProvider) { | |
| super(new JRSUIControl(false), state); | |
| this.metricsProvider = metricsProvider; | |
| slicedControlImages = new HashMap<>(); | |
| } | |
| @Override | |
| void paint(final Graphics2D g, final T stateToPaint) { | |
| if (metricsProvider == null) { | |
| AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); | |
| return; | |
| } | |
| RecyclableJRSUISlicedImageControl slicesRef = slicedControlImages.get(stateToPaint); | |
| if (slicesRef == null) { | |
| final NineSliceMetrics metrics = metricsProvider.getNineSliceMetricsForState(stateToPaint); | |
| if (metrics == null) { | |
| AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); | |
| return; | |
| } | |
| slicesRef = new RecyclableJRSUISlicedImageControl(control, stateToPaint, metrics); | |
| slicedControlImages.put(stateToPaint, slicesRef); | |
| } | |
| final SlicedImageControl slices = slicesRef.get(); | |
| slices.paint(g, boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); | |
| } | |
| } | |
| private static final class AquaSingleImagePainter<T extends JRSUIState> | |
| extends AquaPainter<T> { | |
| AquaSingleImagePainter(final T state) { | |
| super(new JRSUIControl(false), state); | |
| } | |
| @Override | |
| void paint(final Graphics2D g, final T stateToPaint) { | |
| paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); | |
| } | |
| /** | |
| * Paints a native control, which identified by its size and a set of | |
| * additional arguments using a cached image. | |
| * | |
| * @param g Graphics to draw the control | |
| * @param control the reference to the native control | |
| * @param controlState the state of the native control | |
| * @param bounds the rectangle where the native part should be drawn. | |
| * Note: the focus can/will be drawn outside of this bounds. | |
| */ | |
| static void paintFromSingleCachedImage(final Graphics2D g, | |
| final JRSUIControl control, | |
| final JRSUIState controlState, | |
| final Rectangle bounds) { | |
| if (bounds.width <= 0 || bounds.height <= 0) { | |
| return; | |
| } | |
| int focus = 0; | |
| if (controlState.is(JRSUIConstants.Focused.YES)) { | |
| focus = JRSUIConstants.FOCUS_SIZE; | |
| } | |
| final int imgX = bounds.x - focus; | |
| final int imgY = bounds.y - focus; | |
| final int imgW = bounds.width + (focus << 1); | |
| final int imgH = bounds.height + (focus << 1); | |
| final GraphicsConfiguration config = g.getDeviceConfiguration(); | |
| final ImageCache cache = ImageCache.getInstance(); | |
| final AquaPixelsKey key = new AquaPixelsKey(config, imgW, imgH, | |
| bounds, controlState); | |
| Image img = cache.getImage(key); | |
| if (img == null) { | |
| img = new MultiResolutionCachedImage(imgW, imgH, | |
| (rvWidth, rvHeight) -> createImage(imgX, imgY, | |
| rvWidth, rvHeight, bounds, control, controlState)); | |
| if (!controlState.is(JRSUIConstants.Animating.YES)) { | |
| cache.setImage(key, img); | |
| } | |
| } | |
| g.drawImage(img, imgX, imgY, imgW, imgH, null); | |
| } | |
| private static Image createImage(int imgX, int imgY, int imgW, int imgH, | |
| final Rectangle bounds, | |
| final JRSUIControl control, | |
| JRSUIState controlState) { | |
| BufferedImage img = new BufferedImage(imgW, imgH, | |
| BufferedImage.TYPE_INT_ARGB_PRE); | |
| final WritableRaster raster = img.getRaster(); | |
| final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer(); | |
| control.set(controlState); | |
| control.paint(SunWritableRaster.stealData(buffer, 0), imgW, imgH, | |
| bounds.x - imgX, bounds.y - imgY, bounds.width, | |
| bounds.height); | |
| SunWritableRaster.markDirty(buffer); | |
| return img; | |
| } | |
| } | |
| private static class AquaPixelsKey implements ImageCache.PixelsKey { | |
| private final int pixelCount; | |
| private final int hash; | |
| // key parts | |
| private final GraphicsConfiguration config; | |
| private final int w; | |
| private final int h; | |
| private final Rectangle bounds; | |
| private final JRSUIState state; | |
| AquaPixelsKey(final GraphicsConfiguration config, | |
| final int w, final int h, final Rectangle bounds, | |
| final JRSUIState state) { | |
| this.pixelCount = w * h; | |
| this.config = config; | |
| this.w = w; | |
| this.h = h; | |
| this.bounds = bounds; | |
| this.state = state; | |
| this.hash = hash(); | |
| } | |
| @Override | |
| public int getPixelCount() { | |
| return pixelCount; | |
| } | |
| private int hash() { | |
| int hash = config != null ? config.hashCode() : 0; | |
| hash = 31 * hash + w; | |
| hash = 31 * hash + h; | |
| hash = 31 * hash + bounds.hashCode(); | |
| hash = 31 * hash + state.hashCode(); | |
| return hash; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return hash; | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (obj instanceof AquaPixelsKey) { | |
| AquaPixelsKey key = (AquaPixelsKey) obj; | |
| return config == key.config && w == key.w && h == key.h | |
| && bounds.equals(key.bounds) && state.equals(key.state); | |
| } | |
| return false; | |
| } | |
| } | |
| private static class RecyclableJRSUISlicedImageControl | |
| extends RecyclableSlicedImageControl { | |
| private final JRSUIControl control; | |
| private final JRSUIState state; | |
| RecyclableJRSUISlicedImageControl(final JRSUIControl control, final JRSUIState state, final NineSliceMetrics metrics) { | |
| super(metrics); | |
| this.control = control; | |
| this.state = state; | |
| } | |
| @Override | |
| protected Image createTemplateImage(int width, int height) { | |
| BufferedImage image = new BufferedImage(metrics.minW, metrics.minH, BufferedImage.TYPE_INT_ARGB_PRE); | |
| final WritableRaster raster = image.getRaster(); | |
| final DataBufferInt buffer = (DataBufferInt)raster.getDataBuffer(); | |
| control.set(state); | |
| control.paint(SunWritableRaster.stealData(buffer, 0), metrics.minW, metrics.minH, 0, 0, metrics.minW, metrics.minH); | |
| SunWritableRaster.markDirty(buffer); | |
| return image; | |
| } | |
| } | |
| private Graphics2D getGraphics2D(final Graphics g) { | |
| try { | |
| return (SunGraphics2D)g; // doing a blind try is faster than checking instanceof | |
| } catch (Exception ignored) { | |
| if (g instanceof PeekGraphics) { | |
| // if it is a peek just dirty the region | |
| g.fillRect(boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); | |
| } else if (g instanceof ProxyGraphics2D) { | |
| final ProxyGraphics2D pg = (ProxyGraphics2D)g; | |
| final Graphics2D g2d = pg.getDelegate(); | |
| if (g2d instanceof SunGraphics2D) { | |
| return g2d; | |
| } | |
| } else if (g instanceof Graphics2D) { | |
| return (Graphics2D) g; | |
| } | |
| } | |
| return null; | |
| } | |
| } |