/*
* @(#)CachedPainter.java 1.2 04/02/15
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.swing.plaf.metal;
import java.awt.*;
import java.awt.image.*;
import javax.swing.Icon;
import java.lang.ref.SoftReference;
import java.util.*;
/**
* A base class used for icons or images that are expensive to paint.
* A subclass will do the following:
* <ol>
* <li>Invoke <code>paint</code> when you want to paint the image,
* if you are implementing <code>Icon</code> you'll invoke this from
* <code>paintIcon</code>.
* The args argument is useful when additional state is needed.
* <li>Override <code>paintToImage</code> to render the image. The code that
* lives here is equivalent to what previously would go in
* <code>paintIcon</code>, for an <code>Icon</code>.
* </ol>
*
* @version @(#)CachedPainter.java 1.2 04/02/15
*/
abstract class CachedPainter {
// CacheMap maps from class to Cache.
private static final Map<Object,Cache> cacheMap =
new HashMap<Object,Cache>();
private static Cache getCache(Object key) {
synchronized(cacheMap) {
Cache cache = cacheMap.get(key);
if (cache == null) {
cache = new Cache(1);
cacheMap.put(key, cache);
}
return cache;
}
}
/**
* Creates an instance of <code>CachedPainter</code> that will cache up
* to <code>cacheCount</code> images of this class.
*
* @param cacheCount Max number of images to cache
*/
public CachedPainter(int cacheCount) {
getCache(getClass()).setMaxCount(cacheCount);
}
/**
* Renders the cached image to the the passed in <code>Graphic</code>.
* If there is no cached image <code>paintToImage</code> will be invoked.
* <code>paintImage</code> is invoked to paint the cached image.
*/
protected void paint(Component c, Graphics g, int x,
int y, int w, int h, Object... args) {
if (w <= 0 || h <= 0) {
return;
}
Object key = getClass();
GraphicsConfiguration config = c.getGraphicsConfiguration();
Cache cache = getCache(key);
Image image = cache.getImage(key, config, w, h, args);
int attempts = 0;
do {
boolean draw = false;
if (image instanceof VolatileImage) {
// See if we need to recreate the image
switch (((VolatileImage)image).validate(config)) {
case VolatileImage.IMAGE_INCOMPATIBLE:
((VolatileImage)image).flush();
image = null;
break;
case VolatileImage.IMAGE_RESTORED:
draw = true;
break;
}
}
if (image == null) {
// Recreate the image
image = createImage(c, w, h, config);
cache.setImage(key, config, w, h, args, image);
draw = true;
}
if (draw) {
// Render to the Image
Graphics g2 = image.getGraphics();
paintToImage(c, g2, w, h, args);
g2.dispose();
}
// Render to the passed in Graphics
paintImage(c, g, x, y, w, h, image, args);
// If we did this 3 times and the contents are still lost
// assume we're painting to a VolatileImage that is bogus and
// give up. Presumably we'll be called again to paint.
} while ((image instanceof VolatileImage) &&
((VolatileImage)image).contentsLost() && ++attempts < 3);
}
/**
* Paints the representation to cache to the supplied Graphics.
*
* @param c Component painting to
* @param g Graphics to paint to
* @param w Width to paint to
* @param h Height to paint to
* @param args Arguments supplied to <code>paint</code>
*/
protected abstract void paintToImage(Component c, Graphics g,
int w, int h, Object[] args);
/**
* Paints the image to the specified location.
*
* @param c Component painting to
* @param g Graphics to paint to
* @param x X coordinate to paint to
* @param y Y coordinate to paint to
* @param w Width to paint to
* @param h Height to paint to
* @param image Image to paint
* @param args Arguments supplied to <code>paint</code>
*/
protected void paintImage(Component c, Graphics g,
int x, int y, int w, int h, Image image,
Object[] args) {
g.drawImage(image, x, y, null);
}
/**
* Creates the image to cache. This returns an opaque image, subclasses
* that require translucency or transparency will need to override this
* method.
*
* @param c Component painting to
* @param w Width of image to create
* @param h Height to image to create
* @param config GraphicsConfiguration that will be
* rendered to, this may be null.
*/
protected Image createImage(Component c, int w, int h,
GraphicsConfiguration config) {
if (config == null) {
return new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
}
return config.createCompatibleVolatileImage(w, h);
}
/**
* Cache is used to cache an image based on a set of arguments.
*/
private static class Cache {
// Maximum number of entries to cache
private int maxCount;
// The entries.
private java.util.List<SoftReference<Entry>> entries;
Cache(int maxCount) {
this.maxCount = maxCount;
entries = new ArrayList<SoftReference<Entry>>(maxCount);
}
void setMaxCount(int maxCount) {
this.maxCount = maxCount;
}
private Entry getEntry(Object key, GraphicsConfiguration config,
int w, int h, Object[] args) {
synchronized(this) {
Entry entry;
for (int counter = entries.size() - 1; counter >= 0;counter--){
entry = entries.get(counter).get();
if (entry == null) {
// SoftReference was invalidated, remove the entry
entries.remove(counter);
}
else if (entry.equals(config, w, h, args)) {
// Found the entry, return it.
return entry;
}
}
// Entry doesn't exist
entry = new Entry(config, w, h, args);
if (entries.size() == maxCount) {
entries.remove(0);
}
entries.add(new SoftReference<Entry>(entry));
return entry;
}
}
/**
* Returns the cached Image, or null, for the specified arguments.
*/
public Image getImage(Object key, GraphicsConfiguration config,
int w, int h, Object[] args) {
Entry entry = getEntry(key, config, w, h, args);
return entry.getImage();
}
/**
* Sets the cached image for the specified constraints.
*/
public void setImage(Object key, GraphicsConfiguration config,
int w, int h, Object[] args, Image image) {
Entry entry = getEntry(key, config, w, h, args);
entry.setImage(image);
}
/**
* Caches set of arguments and Image.
*/
private static class Entry {
private GraphicsConfiguration config;
private Object[] args;
private Image image;
private int w;
private int h;
Entry(GraphicsConfiguration config, int w, int h, Object[] args) {
this.config = config;
this.args = args;
this.w = w;
this.h = h;
}
public void setImage(Image image) {
this.image = image;
}
public Image getImage() {
return image;
}
public String toString() {
String value = super.toString() +
"[ graphicsConfig=" + config +
", image=" + image +
", w=" + w + ", h=" + h;
if (args != null) {
for (int counter = 0; counter < args.length; counter++) {
value += ", " + args[counter];
}
}
value += "]";
return value;
}
public boolean equals(GraphicsConfiguration config, int w, int h,
Object[] args){
if (this.w == w && this.h == h &&
((this.config != null && this.config.equals(config)) ||
(this.config == null && config == null))) {
if (this.args == null && args == null) {
return true;
}
if (this.args != null && args != null &&
this.args.length == args.length) {
for (int counter = args.length - 1; counter >= 0;
counter--) {
if (!this.args[counter].equals(args[counter])) {
return false;
}
}
return true;
}
}
return false;
}
}
}
}
|