// Charles A. Loomis, Jr., and University of California, Santa Cruz, // Copyright (c) 2000 package org.freehep.swing.graphics; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.image.BufferedImage; import javax.swing.JPanel; import javax.swing.border.Border; /** * This class extends JPanel by adding a backing store. This is * intended to be used in situations in which redrawing the contents * of the panel is extremely expensive. * * To keep things simple, this component does NOT support borders. If * a border is desired, then this component should be embedded within * another container which has one. * * Likewise, children components should NOT be added to this * component. * * @author Charles Loomis * @author Mark Donszelmann * @version $Id: BackedPanel.java 8584 2006-08-10 23:06:37Z duns $ */ public class BackedPanel extends JPanel implements java.io.Serializable { /** * The graphics context of the backing image. */ private Graphics backingGraphics = null; /** * The backing image itself. */ protected BufferedImage backingImage = null; /** * An object to act as a lock for the backing image. */ private Object lock = new Object(); /** * The old dimensions of this panel. */ private Dimension oldDimension = new Dimension(); /** * Object to hold a temporary dimension value. */ private Dimension dim = new Dimension(); /** * Rectangle to hold the clipping bounds. */ protected Rectangle clip = new Rectangle(); /** * Error string when user attempts to set a non-null border. */ private final static String NON_NULL_BORDER_ERROR = "BackedPanel does not support borders."; /** * Creates a new BackedPanel with a width and height set to zero, * and the backing image and graphics object to null. By default, * the Swing double buffering is turned off (since we're writing * to a backing image anyway). The caller of this constructor * selects whether this is a transparent or opaque panel. * * @param opaque transparent panel */ public BackedPanel(boolean opaque) { // First turn off the Swing double buffering. super(false); // Make this either opaque or transparent. setOpaque(opaque); } /** * Return the graphics object for the backing image. All drawing * goes to this image first. Only on a repaint() is the image * flushed to the screen. * * @return Graphics of backing image */ public Graphics getGraphics() { return backingGraphics; } /** * Get the object which acts as a lock on the backing image. * Drawing to the graphics context supplied by getGraphics() or * the image returned from getBackingImage() should be * synchronized to this object. * * @return lock for the backing image */ public Object getLock() { return lock; } /** * Paint this panel by flushing the backing image to the screen. * * @param g Graphics object to draw backing store into */ public void paintComponent(Graphics g) { // Allow the parent to do any custom painting. // FIXME: do not..., since it will also paint. // super.paintComponent(g); // Actually paint this component. if (g!=null && backingImage!=null) { // Despite what the API documentation says, // getClipBounds() returns the current dirty region. This // can then be used to speedup the drawing of the // this BackedPanel. clip = g.getClipBounds(clip); synchronized (getLock()) { BufferedImage subimage = ((BufferedImage)backingImage).getSubimage(clip.x,clip.y, clip.width,clip.height); g.drawImage(subimage,clip.x,clip.y,this); } } } /** * Since this component does not support borders, override this * method to do nothing. * * @param g ignored Graphics context */ public void paintBorder(Graphics g) { } /** * Since this component should not contain children, override this * method to do nothing. * * @param g ignored Graphics context */ public void paintChildren(Graphics g) { } /** * Printing should be handled directly by the component which * paints into the backing store. Hence, this method does * nothing. * * @param g ignored Graphics context */ public void printComponent(Graphics g) { } /** * Since this component does not support borders, override this * method to do nothing. * * @param g ignored Graphics context */ public void printBorder(Graphics g) { } /** * Since this component should not contain children, override this * method to do nothing. * * @param g ignored Graphics context */ public void printChildren(Graphics g) { } /** * This component does not support borders. If this method is * called with any non-null argument, then an * IllegalArgumentException is thrown. If a border is desired, * then this component should be embedded within another container * which has one. * * @param border must be null */ public final void setBorder(Border border) { if (border!=null) throw new IllegalArgumentException(NON_NULL_BORDER_ERROR); } /** * Resize and move a component. */ public void setBounds(int x, int y, int w, int h) { // Make sure that the parent's method is called first; // otherwise, the resize never happens and new images are NOT // made. super.setBounds(x,y,w,h); // Make the backing image. makeImage(); } /** * Make the backing image for this panel. Check to see if the * size has changed before doing anything. */ private void makeImage() { // Get the full size of the panel. dim = getSize(dim); int w = dim.width; int h = dim.height; // Check that the current size is positive and that the new // dimension is not equal to the old one. if (w>0 && h>0) { if (!oldDimension.equals(dim)) { synchronized (getLock()) { // Make the actual backing image and get the // graphics context for this image. backingImage = (isOpaque()) ? (BufferedImage)super.createImage(w,h) : new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB); backingGraphics = backingImage.getGraphics(); // Reset the old size of this panel. oldDimension.setSize(dim); } } } else { synchronized (getLock()) { backingImage = null; backingGraphics = null; } } } }