mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
442 lines
16 KiB
Java
442 lines
16 KiB
Java
/*
|
|
* $Id: AbstractPainter.java 4082 2011-11-15 18:39:43Z kschaefe $
|
|
*
|
|
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
|
|
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package org.jdesktop.swingx.painter;
|
|
|
|
import java.awt.AlphaComposite;
|
|
import java.awt.Composite;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.RenderingHints;
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.BufferedImageOp;
|
|
import java.lang.ref.SoftReference;
|
|
|
|
import org.jdesktop.beans.AbstractBean;
|
|
import org.jdesktop.swingx.util.GraphicsUtilities;
|
|
|
|
/**
|
|
* <p>A convenient base class from which concrete {@link Painter} implementations may
|
|
* extend. It extends {@link org.jdesktop.beans.AbstractBean} as a convenience for
|
|
* adding property change notification support. In addition, <code>AbstractPainter</code>
|
|
* provides subclasses with the ability to cacheable painting operations, configure the
|
|
* drawing surface with common settings (such as antialiasing and interpolation), and
|
|
* toggle whether a subclass paints or not via the <code>visibility</code> property.</p>
|
|
*
|
|
* <p>Subclasses of <code>AbstractPainter</code> generally need only override the
|
|
* {@link #doPaint(Graphics2D, Object, int, int)} method. If a subclass requires more control
|
|
* over whether caching is enabled, or for configuring the graphics state, then it
|
|
* may override the appropriate protected methods to interpose its own behavior.</p>
|
|
*
|
|
* <p>For example, here is the doPaint method of a simple <code>Painter</code> that
|
|
* paints an opaque rectangle:
|
|
* <pre><code>
|
|
* public void doPaint(Graphics2D g, T obj, int width, int height) {
|
|
* g.setPaint(Color.BLUE);
|
|
* g.fillRect(0, 0, width, height);
|
|
* }
|
|
* </code></pre></p>
|
|
*
|
|
* @author rbair
|
|
* @param <T> an optional configuration parameter
|
|
*/
|
|
@SuppressWarnings("nls")
|
|
public abstract class AbstractPainter<T> extends AbstractBean implements Painter<T> {
|
|
/**
|
|
* An enum representing the possible interpolation values of Bicubic, Bilinear, and
|
|
* Nearest Neighbor. These ua.in.smartjava.map to the underlying RenderingHints,
|
|
* but are easier to use and serialization safe.
|
|
*/
|
|
public enum Interpolation {
|
|
/**
|
|
* use bicubic interpolation
|
|
*/
|
|
Bicubic(RenderingHints.VALUE_INTERPOLATION_BICUBIC),
|
|
/**
|
|
* use bilinear interpolation
|
|
*/
|
|
Bilinear(RenderingHints.VALUE_INTERPOLATION_BILINEAR),
|
|
/**
|
|
* use nearest neighbor interpolation
|
|
*/
|
|
NearestNeighbor(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
|
|
|
private Object value;
|
|
|
|
Interpolation(Object value) {
|
|
this.value = value;
|
|
}
|
|
|
|
private void configureGraphics(Graphics2D g) {
|
|
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, value);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------- Instance Variables
|
|
/**
|
|
* The cached image, if shouldUseCache() returns true
|
|
*/
|
|
private transient SoftReference<BufferedImage> cachedImage;
|
|
private boolean cacheCleared = true;
|
|
private boolean cacheable = false;
|
|
private boolean dirty = false;
|
|
private BufferedImageOp[] filters = new BufferedImageOp[0];
|
|
private boolean antialiasing = true;
|
|
private Interpolation interpolation = Interpolation.NearestNeighbor;
|
|
private boolean visible = true;
|
|
private boolean inPaintContext;
|
|
|
|
/**
|
|
* Creates a new instance of AbstractPainter.
|
|
*/
|
|
public AbstractPainter() { }
|
|
|
|
/**
|
|
* Creates a new instance of AbstractPainter.
|
|
* @param cacheable indicates if this painter should be cacheable
|
|
*/
|
|
public AbstractPainter(boolean cacheable) {
|
|
setCacheable(cacheable);
|
|
}
|
|
|
|
/**
|
|
* A defensive copy of the Effects to apply to the results
|
|
* of the AbstractPainter's painting operation. The array may
|
|
* be empty but it will never be null.
|
|
* @return the array of filters applied to this painter
|
|
*/
|
|
public final BufferedImageOp[] getFilters() {
|
|
BufferedImageOp[] results = new BufferedImageOp[filters.length];
|
|
System.arraycopy(filters, 0, results, 0, results.length);
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* <p>A convenience method for specifying the filters to use based on
|
|
* BufferedImageOps. These will each be individually wrapped by an ImageFilter
|
|
* and then setFilters(Effect... filters) will be called with the resulting
|
|
* array</p>
|
|
*
|
|
*
|
|
* @param effects the BufferedImageOps to wrap as filters
|
|
*/
|
|
public void setFilters(BufferedImageOp ... effects) {
|
|
if (effects == null) effects = new BufferedImageOp[0];
|
|
BufferedImageOp[] old = getFilters();
|
|
this.filters = new BufferedImageOp[effects.length];
|
|
System.arraycopy(effects, 0, this.filters, 0, this.filters.length);
|
|
setDirty(true);
|
|
firePropertyChange("filters", old, getFilters());
|
|
}
|
|
|
|
/**
|
|
* Returns if antialiasing is turned on or not. The default value is true.
|
|
* This is a bound property.
|
|
* @return the current antialiasing setting
|
|
*/
|
|
public boolean isAntialiasing() {
|
|
return antialiasing;
|
|
}
|
|
/**
|
|
* Sets the antialiasing setting. This is a bound property.
|
|
* @param value the new antialiasing setting
|
|
*/
|
|
public void setAntialiasing(boolean value) {
|
|
boolean old = isAntialiasing();
|
|
antialiasing = value;
|
|
if (old != value) setDirty(true);
|
|
firePropertyChange("antialiasing", old, isAntialiasing());
|
|
}
|
|
|
|
/**
|
|
* Gets the current interpolation setting. This property determines if interpolation will
|
|
* be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION.
|
|
* @return the current interpolation setting
|
|
*/
|
|
public Interpolation getInterpolation() {
|
|
return interpolation;
|
|
}
|
|
|
|
/**
|
|
* Sets a new value for the interpolation setting. This setting determines if interpolation
|
|
* should be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION.
|
|
* @param value the new interpolation setting
|
|
*/
|
|
public void setInterpolation(Interpolation value) {
|
|
Object old = getInterpolation();
|
|
this.interpolation = value == null ? Interpolation.NearestNeighbor : value;
|
|
if (old != value) setDirty(true);
|
|
firePropertyChange("interpolation", old, getInterpolation());
|
|
}
|
|
|
|
/**
|
|
* Gets the visible property. This controls if the painter should
|
|
* paint itself. It is true by default. Setting visible to false
|
|
* is good when you want to temporarily turn off a painter. An example
|
|
* of this is a painter that you only use when a button is highlighted.
|
|
*
|
|
* @return current value of visible property
|
|
*/
|
|
public boolean isVisible() {
|
|
return this.visible;
|
|
}
|
|
|
|
/**
|
|
* <p>Sets the visible property. This controls if the painter should
|
|
* paint itself. It is true by default. Setting visible to false
|
|
* is good when you want to temporarily turn off a painter. An example
|
|
* of this is a painter that you only use when a button is highlighted.</p>
|
|
*
|
|
* @param visible New value of visible property.
|
|
*/
|
|
public void setVisible(boolean visible) {
|
|
boolean old = isVisible();
|
|
this.visible = visible;
|
|
if (old != visible) setDirty(true); //not the most efficient, but I must do this otherwise a CompoundPainter
|
|
//or other aggregate painter won't know that it is now invalid
|
|
//there might be a tricky solution but that is a performance optimization
|
|
firePropertyChange("visible", old, isVisible());
|
|
}
|
|
|
|
/**
|
|
* <p>Gets whether this <code>AbstractPainter</code> can be cached as an image.
|
|
* If caching is enabled, then it is the responsibility of the developer to
|
|
* invalidate the painter (via {@link #clearCache}) if external state has
|
|
* changed in such a way that the painter is invalidated and needs to be
|
|
* repainted.</p>
|
|
*
|
|
* @return whether this is cacheable
|
|
*/
|
|
public boolean isCacheable() {
|
|
return cacheable;
|
|
}
|
|
|
|
/**
|
|
* <p>Sets whether this <code>AbstractPainter</code> can be cached as an image.
|
|
* If true, this is treated as a hint. That is, a cacheable may or may not be used.
|
|
* The {@link #shouldUseCache} method actually determines whether the cacheable is used.
|
|
* However, if false, then this is treated as an absolute value. That is, no
|
|
* cacheable will be used.</p>
|
|
*
|
|
* <p>If set to false, then #clearCache is called to free system resources.</p>
|
|
*
|
|
* @param cacheable the cache flag
|
|
*/
|
|
public void setCacheable(boolean cacheable) {
|
|
boolean old = isCacheable();
|
|
this.cacheable = cacheable;
|
|
firePropertyChange("cacheable", old, isCacheable());
|
|
if (!isCacheable()) {
|
|
clearCache();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <p>Call this method to clear the cacheable. This may be called whether there is
|
|
* a cacheable being used or not. If cleared, on the next call to <code>paint</code>,
|
|
* the painting routines will be called.</p>
|
|
*
|
|
* <p><strong>Subclasses</strong>If overridden in subclasses, you
|
|
* <strong>must</strong> call super.clearCache, or physical
|
|
* resources (such as an Image) may leak.</p>
|
|
*/
|
|
public void clearCache() {
|
|
BufferedImage cache = cachedImage == null ? null : cachedImage.get();
|
|
if (cache != null) {
|
|
cache.flush();
|
|
}
|
|
cacheCleared = true;
|
|
if (!isCacheable()) {
|
|
cachedImage = null;
|
|
}
|
|
}
|
|
|
|
// /**
|
|
// * Only made package private for testing. Don't call this method outside
|
|
// * of this class! This is NOT a bound property
|
|
// */
|
|
// private boolean isCacheCleared() {
|
|
// return cacheCleared;
|
|
// }
|
|
|
|
/**
|
|
* <p>Called to allow <code>Painter</code> subclasses a chance to see if any state
|
|
* in the given object has changed from the last paint operation. If it has, then
|
|
* the <code>Painter</code> has a chance to mark itself as dirty, thus causing a
|
|
* repaint, even if cached.</p>
|
|
*
|
|
* @param object the object to validate
|
|
*/
|
|
protected void validate(T object) { /* do nothing */ }
|
|
|
|
/**
|
|
* Ye olde dirty bit. If true, then the painter is considered dirty and in need of
|
|
* being repainted. This is a bound property.
|
|
*
|
|
* @return true if the painter state has changed and the painter needs to be
|
|
* repainted.
|
|
*/
|
|
protected boolean isDirty() {
|
|
return dirty;
|
|
}
|
|
|
|
/**
|
|
* Sets the dirty bit. If true, then the painter is considered dirty, and the cache
|
|
* will be cleared. This property is bound.
|
|
*
|
|
* @param d whether this <code>Painter</code> is dirty.
|
|
*/
|
|
protected void setDirty(boolean d) {
|
|
boolean old = isDirty();
|
|
this.dirty = d;
|
|
firePropertyChange("dirty", old, isDirty());
|
|
if (isDirty()) {
|
|
clearCache();
|
|
}
|
|
}
|
|
|
|
boolean isInPaintContext() {
|
|
return inPaintContext;
|
|
}
|
|
|
|
void setInPaintContext(boolean inPaintContext) {
|
|
this.inPaintContext = inPaintContext;
|
|
}
|
|
|
|
/**
|
|
* <p>Returns true if the painter should use caching. This method allows subclasses to
|
|
* specify the heuristics regarding whether to cache or not. If a <code>Painter</code>
|
|
* has intelligent rules regarding painting times, and can more accurately indicate
|
|
* whether it should be cached, it could implement that logic in this method.</p>
|
|
*
|
|
* @return whether or not a cache should be used
|
|
*/
|
|
protected boolean shouldUseCache() {
|
|
return isCacheable() || filters.length > 0; //NOTE, I can only do this because getFilters() is final
|
|
}
|
|
|
|
/**
|
|
* <p>This method is called by the <code>paint</code> method prior to
|
|
* any drawing operations to configure the drawing surface. The default
|
|
* implementation sets the rendering hints that have been specified for
|
|
* this <code>AbstractPainter</code>.</p>
|
|
*
|
|
* <p>This method can be overridden by subclasses to modify the drawing
|
|
* surface before any painting happens.</p>
|
|
*
|
|
* @param g the graphics surface to configure. This will never be null.
|
|
* @see #paint(Graphics2D, Object, int, int)
|
|
*/
|
|
protected void configureGraphics(Graphics2D g) {
|
|
//configure antialiasing
|
|
if(isAntialiasing()) {
|
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
|
RenderingHints.VALUE_ANTIALIAS_ON);
|
|
} else {
|
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
|
RenderingHints.VALUE_ANTIALIAS_OFF);
|
|
}
|
|
|
|
getInterpolation().configureGraphics(g);
|
|
}
|
|
|
|
/**
|
|
* Subclasses must implement this method and perform custom painting operations
|
|
* here.
|
|
* @param width the width
|
|
* @param height the height
|
|
* @param g The Graphics2D object in which to paint
|
|
* @param object an optional configuration parameter
|
|
*/
|
|
protected abstract void doPaint(Graphics2D g, T object, int width, int height);
|
|
|
|
@Override
|
|
public final void paint(Graphics2D g, T obj, int width, int height) {
|
|
if (g == null) {
|
|
throw new NullPointerException("The Graphics2D must be supplied");
|
|
}
|
|
|
|
if(!isVisible() || width < 1 || height < 1) {
|
|
return;
|
|
}
|
|
|
|
configureGraphics(g);
|
|
|
|
//paint to a temporary image if I'm caching, or if there are filters to apply
|
|
if (shouldUseCache() || filters.length > 0) {
|
|
validate(obj);
|
|
BufferedImage cache = cachedImage == null ? null : cachedImage.get();
|
|
boolean invalidCache = null == cache ||
|
|
cache.getWidth() != width ||
|
|
cache.getHeight() != height;
|
|
|
|
if (cacheCleared || invalidCache || isDirty()) {
|
|
//rebuild the cacheable. I do this both if a cacheable is needed, and if any
|
|
//filters exist. I only *save* the resulting image if caching is turned on
|
|
if (invalidCache) {
|
|
cache = GraphicsUtilities.createCompatibleTranslucentImage(width, height);
|
|
}
|
|
if (cache != null) {
|
|
Graphics2D gfx = cache.createGraphics();
|
|
|
|
try {
|
|
gfx.setClip(0, 0, width, height);
|
|
|
|
if (!invalidCache) {
|
|
// If we are doing a repaint, but we didn't have to
|
|
// recreate the image, we need to clear it back
|
|
// to a fully transparent background.
|
|
Composite composite = gfx.getComposite();
|
|
gfx.setComposite(AlphaComposite.Clear);
|
|
gfx.fillRect(0, 0, width, height);
|
|
gfx.setComposite(composite);
|
|
}
|
|
|
|
configureGraphics(gfx);
|
|
doPaint(gfx, obj, width, height);
|
|
} finally {
|
|
gfx.dispose();
|
|
}
|
|
|
|
if (!isInPaintContext()) {
|
|
for (BufferedImageOp f : getFilters()) {
|
|
cache = f.filter(cache, null);
|
|
}
|
|
}
|
|
|
|
//only save the temporary image as the cacheable if I'm caching
|
|
if (shouldUseCache()) {
|
|
cachedImage = new SoftReference<BufferedImage>(cache);
|
|
cacheCleared = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
g.drawImage(cache, 0, 0, null);
|
|
} else {
|
|
//can't use the cacheable, so just paint
|
|
doPaint(g, obj, width, height);
|
|
}
|
|
|
|
//painting has occured, so restore the dirty bit to false
|
|
setDirty(false);
|
|
}
|
|
}
|