/*
 * @(#)BufferedPanel.java    2.6.0e   19 November 2007
 *
 * Copyright 2007
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;

/**
 * <p>A <code>BufferedPanel</code> combines bit-map-graphics with
 * algorithmic graphics based on objects.</p>
 *
 * <p>A <code>BufferedPanel</code> maintains a base layer onto which
 * the caller may paint graphics of arbitrary complexity; the data
 * in this base layer is stored in a Java <code>BufferedImage</code>
 * that persists until changed by the caller; the panel also holds
 * a <code>PaintableSequence</code> whose objects are painted on top
 * of the base layer; in effect, the paintable sequence collects a
 * sequence of sprites that are painted above the image that is held
 * in the base layer.</p>
 *
 * <p>This design allows a <code>BufferedPanel</code> to combine the
 * flexibility of mutatable graphics objects held in the paintable
 * sequence with the advantages of a persistant background image
 * that is stored in a bit-map.</p>
 *
 * <p>We will now give a brief tutorial on how to use a
 * <code>BufferedPanel</code>.
 * For simplicity, we assume the name of the panel is "window".</p>
 *
 * <p>By default, the
 * <code>BufferedPanel</code> has a background of white paint.
 * To change this initial background if desired, use the calls:</p>
 *
 * <p><code>window.setBufferBackground(<i>the-paint</i>);</code></p>
 * <p><code>window.clearPanel();</code></p>
 *
 * <p>Once the initial background is set, one can make further changes
 * to the bit-map-image of the window.  To do this, one must get the
 * <i>graphics context</i> of the bit-map-image via the call:</p>
 *
 * <p><code>Graphics2D g = window.getBufferGraphics();</code></p>
 *
 * <p>Then, one can make any calls in the Java Graphics2D library or
 * in JPT to act on <code>g</code> and change the bit-map-image. In
 * this way, arbitrary graphics may be drawn on the bit-map-image.
 * Such graphics will persist in the bits until explicitly changed.</p>
 *
 * <p>The caller can decide when to refresh the screen.  To do this,
 * make the call:</p>
 *
 * <p><code>window.repaint();</code></p>
 *
 * <p>A second way to supply graphics in a
 * <code>BufferedPanel</code> is to add one
 * more <code>Paintable</code> objects to a <code>PaintableSequence</code>
 * that is maintained by the panel.  You may think of a
 * <code>Paintable</code> as an
 * object that maintains both graphics state and algorithmics for painting
 * that state.  A <code>Paintable</code>
 * can also be mutated by affine transformations
 * and, in particular, may be dragged on the screen if suitable mouse
 * behavior has been installed in the window.</p>
 *
 * <p>A <code>BufferedPanel</code>
 * has methods to set, add, or remove <code>Paintable</code>s 
 * one-by-one or by using arrays of <code>Paintable</code>s.
 * For convenience, one can supply
 * general objects and this class will make a best effort to convert such
 * objects into <code>Paintable</code>s.
 * The key to this generality is that the method
 * <code>ComponentFactory.makePaintable</code> is invoked when an object is
 * presented to the panel to be added.  If the object is already
 * <code>Paintable</code>
 * then it is added as-is; otherwise it is converted into a
 * <code>Paintable</code> if
 * that is possible.</p>
 *
 * <p>One can obtain the
 * <code>PaintableSequence</code> object and use that object to
 * perform sophisticated actions such as mutation by affine transformations.
 * For convenience, the panel also has a built-in option to use the mouse
 * to drag the <code>Paintable</code> objects.
 * To install this option, make the call:</p>
 *
 * <p><code>window.installSimpleMouseActions(<i>shift</i>);</code></p>
 *
 * <p>If the supplied <code>boolean</code> parameter <code><i>shift</i></code>
 * is <code>true</code> then when a
 * <code>Paintable</code> is clicked by the mouse it will be shifted to be
 * the topmost <code>Paintable</code> in the sequence.</p>
 *
 * <p>It is certainly possible for the caller to create more sophisticated
 * mouse behavior and install such behavior.</p>
 *
 * <p>Finally, the
 * <code>BufferedPanel</code> class provides a "hook" so that a derived
 * class can add additional algorithmic graphics features.  There is a
 * method:</p>
 *
 * <p><code>public void paintOver(Graphics2D g2)</code></p>
 *
 * <p>By default, this method does nothing.  However, a derived class may
 * redefine this method to do anything whatsover.  The order of painting
 * calls in the class is then as follows:</p>
 *
 * <ul>
 *   <li>Paint the buffer</li>
 *   <li>Paint the internal paintable sequence</li>
 *   <li>Call the <code>paintOver</code> method for further graphics</li>
 * </ul>
 *
 * <p>Note that one may install key actions for this panel but there are no
 * default key actions that have been predefined.  This decision is left to
 * the user of this class.</p>
 * 
 * <p>In 2.6.0, the interface <code>Paintable</code> introduced the notions
 * of a background paint and a background tile.  The implication is that
 * the internal paintable sequence of this buffered panel can also have a
 * background paint or a background tile. Thus, the description of how the
 * panel is painted may now be expanded to:</p>
 * 
 * <ul>
 *   <li>Paint the buffer</li>
 *   <li>Paint the background paint of the internal paintable sequence</li>
 *   <li>Paint the background tile  of the internal paintable sequence</li>
 *   <li>Paint the paintables of the internal paintable sequence</li>
 *   <li>Call the <code>paintOver</code> method for further graphics</li>
 * </ul>
 * 
 * <p>To support use of the background paint of the internal paintable
 * sequence, the default bounds of the sequence are explicitly set to be
 * the rectangle for the internal buffer.  Hence, if the background of the
 * paintable sequence is set to an opaque paint, it will entirely cover up
 * what is painted via the buffer.  This can be extremely useful.  For
 * example, if you set the background paint of the sequence to white, then
 * that will hide the buffer; if you later clear this background paint,
 * then the buffer will once again be visible.  Hence, it is unnecessary
 * to copy the buffer to some auxiliary buffer just to hide and restore it.</p>
 * 
 * <p>A number of methods have been added that support setting and clearing
 * of the background paint and background tile of the internal paintable
 * sequence.</p>
 * 
 * <p>Changes in 2.6.0d:</p>
 * 
 * <ul>
 *   <li>
 *   Added the method <code>makeSnapshot</code> that will construct a new
 *   <code>BufferedImage</code> that is the same size as this panel and
 *   that captures the current visual state of this panel.</p>
 *   </li>
 *   
 *   <li>
 *   Changed the method name of <code>setBackgroundTile</code> to
 *   <code>setSequenceBackgroundTile</code> to be consistent with
 *   the naming conventions of the related methods.
 *   </li>
 * 
 *   <li>
 *   Added <code>final</code> to methods that should not be overridden.
 *   </ul>
 * </ul>
 * 
 * <p>Changes in 2.6.0e:</p>
 * 
 * <p>A <code>BufferedPanel</code> is constructed with an inner panel
 * of class <code>Painter</code> that is responsible for painting and
 * for returning the preferred size of the component.  In this release,
 * the body of the methods <code>paint</code> and
 * <code>paintComponent</code> in class <code>Painter</code> have been
 * <code>synchronized</code> on the enclosing <code>BufferedPanel</code>
 * object.  This means that the user of this class has the option of
 * also using <code>synchronized</code> on this <code>BufferedPanel</code>
 * object when making changes to either the internal
 * <code>BufferedImage</code> or to the <code>PaintableSequence</code> of
 * objects painted above the buffer.  If this synchronization is done,
 * then Java repainting will not take place while the data structure is
 * undergoing change.  In practice, this synchronization is needed only
 * when lots of dynamic activity is happening.</p>
 * 
 * @author  Richard Rasala
 * @author  Jeff Raab
 * @version 2.6.0e
 * @since   1.0
 */
public class BufferedPanel extends DisplayPanel {

    ///////////////
    // Constants //
    ///////////////
    
    /** Default background color for a buffered panel. */
    public static final Color DEFAULT_BUFFER_BACKGROUND = Color.white;
    
    
    /////////////////
    // Member Data //
    /////////////////
    
    /** The buffered image that maintains the persistent graphics state. */
    protected BufferedImage buffer = null;
    
    /** The buffer width. */
    private int bufferwidth  = 0;
    
    /** The buffer height. */
    private int bufferheight = 0;
    
    /** The paintable sequence that paints on top of the buffer. */
    protected PaintableSequence paintablesequence = new PaintableSequence();
    
    /** The internal painter panel. */
    protected BufferedPanel.Painter painter = null;
    
    /** The background paint for this buffered panel. */
    protected Paint bufferBackground = DEFAULT_BUFFER_BACKGROUND;
    
    /** The mouse action adapter for this buffered panel. */
    protected MouseActionAdapter mouseActions = null;

    /** The key action adapter for this buffered panel. */
    protected KeyActionAdapter keyActions = null;
    
    /** The mouse x-position. */
    protected int mouseX = 0;
    
    /** The mouse y-position. */
    protected int mouseY = 0;
    
    /** The current paintable being dragged. */
    protected Paintable currentPaintable = null;
    
    /** Whether or not to shift the current paintable to the top. */
    protected boolean autoShiftToTop = false;
    
    /** Whether or not the mouse is in the panel. */
    protected boolean mouseIsInPanel = true;
    
    
    /** The "Simple Mouse Action" moved action. */
    protected MouseAction SMA_Moved_Action = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            SMA_Moved(evt);
        }
    };
    
    /** The "Simple Mouse Action" pressed action. */
    protected MouseAction SMA_Pressed_Action = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            SMA_Pressed(evt);
        }
    };
    
    /** The "Simple Mouse Action" released action. */
    protected MouseAction SMA_Released_Action = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            SMA_Released(evt);
        }
    };
    
    /** The "Simple Mouse Action" clicked action. */
    protected MouseAction SMA_Clicked_Action = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            SMA_Clicked(evt);
        }
    };
    
    /** The "Simple Mouse Action" dragged action. */
    protected MouseAction SMA_Dragged_Action = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            SMA_Dragged(evt);
        }
    };
    
    /** The "Simple Mouse Action" entered panel action. */
    protected MouseAction SMA_Entered_Action = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            SMA_Entered(evt);
        }
    };
    
    /** The "Simple Mouse Action" exited panel action. */
    protected MouseAction SMA_Exited_Action = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            SMA_Exited(evt);
        }
    };
    
    
    //////////////////
    // Constructors //
    //////////////////

    /**
     * <p>Constructs a BufferedPanel containing a buffered image
     * with the given width and height,
     * and a white background.</p>
     *
     * <p>If the given width or height is less than 1 pixel,
     * that value is set to 1 pixel.</p>
     *
     * <p>Though the component itself may grow arbitrarily large,
     * the buffered image painted to the buffer will
     * remain the size specified in this constructor
     * unless the size is reset using the
     * <code>setBufferSize</code> method.</p>
     *
     * @param width  the width  of the buffered image
     * @param height the height of the buffered image
     */
    public BufferedPanel(int width, int height) {
        this(width, height, DEFAULT_BUFFER_BACKGROUND);
    }
	
	
    /**
     * <p>Constructs a BufferedPanel containing a buffered image
     * with the given <code>Dimension</code>,
     * and a white background.</p>
     *
     * <p>If the given <code>Dimension</code> is <code>null</code>,
     * then the buffer width and height are both set to 1 pixel.</p>
     *
     * <p>If the width or height of the given <code>Dimension</code>
     * is less than 1 pixel, that value is set to 1 pixel.</p>
     *
     * <p>Though the component itself may grow arbitrarily large,
     * the buffered image painted to the buffer will
     * remain the size specified in this constructor
     * unless the size is reset using the
     * <code>setBufferSize</code> method.</p>
     *
     * @param dimension the dimension of the buffered image
     */
    public BufferedPanel(Dimension dimension) {
        this(
            (dimension == null) ? 1 : dimension.width,
            (dimension == null) ? 1 : dimension.height,
            DEFAULT_BUFFER_BACKGROUND);
    }
	
	
    /**
     * <p>Constructs a BufferedPanel containing a buffered image
     * with the given width and height, 
     * and the given background color or <code>Paint</code>
     * for the image.</p>
     *
     * <p>If the given width or height is less than 1 pixel,
     * that value is set to 1 pixel.</p>
     *
     * <p>Though the component itself may grow arbitrarily large,
     * the buffered image painted to the buffer will
     * remain the size specified in this constructor
     * unless the size is reset using the
     * <code>setBufferSize</code> method.</p>
     *
     * @param width      the width  of the buffered image
     * @param height     the height of the buffered image
     * @param background the background color or <code>Paint</code>
     *      for the buffered image
     */
    public BufferedPanel(int width, int height, Paint background) {
        super(new CenterLayout());
        setBufferBackground(background);

        // build the buffered image as a side effect
        setBufferSize(width, height);
        
        
        // build the painter panel using the inner Painter class
        makePainterPanelIfNeeded();
        
        
        // install the painter panel
		add(painter);
		
        // build the automatically installed event handlers
        mouseActions = new MouseActionAdapter(painter);
        keyActions = new KeyActionAdapter(painter);
    }
    
	
    /**
     * <p>Constructs a BufferedPanel containing a buffered image
     * with the given <code>Dimension</code>,
     * and the given background color or <code>Paint</code>
     * for the image.</p>
     *
     * <p>If the given <code>Dimension</code> is <code>null</code>,
     * then the buffer width and height are both set to 1 pixel.</p>
     *
     * <p>If the width or height of the given <code>Dimension</code>
     * is less than 1 pixel, that value is set to 1 pixel.</p>
     *
     * <p>Though the component itself may grow arbitrarily large,
     * the buffered image painted to the buffer will
     * remain the size specified in this constructor
     * unless the size is reset using the
     * <code>setBufferSize</code> method.</p>
     *
     * @param dimension the dimension of the buffered image
     * @param background the background color or <code>Paint</code>
     *      for the buffered image
     */
    public BufferedPanel(Dimension dimension, Paint background) {
        this(
            (dimension == null) ? 1 : dimension.width,
            (dimension == null) ? 1 : dimension.height,
            background);
    }
	
    
    private void makePainterPanelIfNeeded() {
        if (painter == null)
            painter = new BufferedPanel.Painter(this);
    }
	
	////////////////
    // Public API //
    ////////////////

    /**
     * <p>Returns a <code>Graphics2D</code> object that permits
     * painting to the internal buffered image for this panel.</p>
     *
     * <p>The user should always use this object to paint to the
     * buffer and thus indirectly modify this buffered panel.</p>
     *
     * <p>To make painting changes to the buffer visible, the
     * <code>repaint()</code> method must explicitly be called.
     * This allows a number of painting operations to be done
     * prior to screen repaint.</p>
     */
    public final Graphics2D getBufferGraphics() {
        return buffer.createGraphics();
    }
	
	
    /**
     * <p>Returns the internal buffered image for this panel.</p>
     */
    public final BufferedImage getBuffer() {
        return buffer;
    }
    
    
    /**
     * <p>Sets the size of the buffered image 
     * to the given <code>Dimension</code>.</p>
     *
     * <p>If the given <code>Dimension</code> is <code>null</code>,
     * the current image size is not changed.</p>
     *
     * <p>If the width or height of the given <code>Dimension</code> 
     * is less than 1 pixel, it is set to 1 pixel.</p>
     *
     * <p>Any image area gained by an size increase in either direction
     * will be painted with the current background color.</p>
     *
     * <p>Any image area lost by a size decrease in either direction
     * will be clipped on the right and/or bottom of the image.</p>
     *
     * <p>For a short time both the image of the previous size 
     * and an image of the new size are maintained in memory.</p>
     *
     * @param size the new size for the image
     */
    public final synchronized void setBufferSize(Dimension size) {
        if (size == null)
            return;
        
        setBufferSize(size.width, size.height);
    }
    
    
    /**
     * <p>Sets the size of the buffered image 
     * to the given width and height.</p>
     *
     * <p>If the given width or height 
     * is less than 1 pixel, it is set to 1 pixel.</p>
     *
     * <p>Any image area gained by an size increase in either direction
     * will be painted with the current background color.</p>
     *
     * <p>Any image area lost by a size decrease in either direction
     * will be clipped on the right and/or bottom of the image.</p>
     *
     * <p>For a short time both the image of the previous size 
     * and an image of the new size are maintained in memory.</p>
     *
     * @param width  the new width  for the image
     * @param height the new height for the image
     */
    public final synchronized void setBufferSize(int width, int height) {
        // ensure positive width and height
        width  = (int)Math.max(width,  1);
        height = (int)Math.max(height, 1);
        
        if (buffer != null) {
            if ((width == bufferwidth) && (height == bufferheight))
                return;
        }
        
        // save current buffer so we can later paint onto new buffer
        BufferedImage oldBuffer = buffer;
        
        // build the new buffered image
        buffer =
            new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        
        bufferwidth  = width;
        bufferheight = height;
        
        // clear the new buffered image
        clearPanel();
        
        // paint the old image to the new buffer if necessary
        if (oldBuffer != null) {
            Graphics2D g2 = getBufferGraphics();
            g2.drawImage(oldBuffer, 0, 0, this);
        }
        
        paintablesequence.setDefaultBounds2D(new XRect(0, 0, width, height));
        
        // refresh the enclosing window to account for the new buffer size
        refreshComponent();
    }
    
    
    /**
     * Returns the width of the buffered image.
     */
    public final int getBufferWidth() {
        return bufferwidth;
    }
    
    
    /**
     * Returns the height of the buffered image.
     */
    public final int getBufferHeight() {
        return bufferheight;
    }
    
    
    /**
     * <p>Construct a <code>BufferedImage</code> object that is
     * the same size as this buffered panel and then paint this
     * new buffered image by simulating the paint operations of
     * this buffered panel.</p>
     * 
     * <p>This method creates a snapshot of the current visual
     * state of this buffered panel.</p>
     * 
     * <p>Insets and borders to the buffered panel are ignored.</p>
     */
    public final synchronized BufferedImage makeSnapshot() {
        BufferedImage snapshot =
            new BufferedImage(bufferwidth, bufferheight, BufferedImage.TYPE_INT_RGB);
        
        Graphics2D g = snapshot.createGraphics();
        
        g.drawImage(buffer, 0, 0, this);
        
        paintablesequence.paint(g);
        
        paintOver(g);
        
        return snapshot;
    }
    
    
    /**
     * <p>Returns the internal panel for this buffered panel,
     * that is, the panel that paints the buffered image
     * and handles the mouse and key adapters.</p>
     *
     * <p>This panel may be used when access to the panel on
     * which the graphics is drawn is needed.</p>
     *
     * <p>Do not set a border on this internal panel.  Set a
     * border on the outer <code>BufferedPanel</code> object.</p>
     *
     * @since 1.1
     */
    public final DisplayPanel getInnerPanel() {
        makePainterPanelIfNeeded();
        
        return painter;
    }
    
    
    /**
     * <p>Repaints the buffered panel directly without a call to the generic
     * <code>repaint</code> method in <code>Component</code>.</p>
     *
     * <p>Instead, this method directly asks the inner panel to paint itself
     * which is done by repainting the screen from the buffer.</p>
     *
     * <p>This method is supplied for performance reasons to support the use
     * of animation in a buffered panel.</p>
     *
     * <p>This method is not 100% robust because Java occasionally
     * loses track of where on the screen it is painting.  Therefore, this
     * method should be used only if performance issues require that it be
     * used.  Using <code>repaint</code> is still the recommended way to
     * repaint the buffered panel.</p>
     *
     * <p>This method does nothing if the buffered panel is not installed in
     * a window or dialog box.</p>
     *
     * @since 2.3
     */
    public final void quickRepaint() {
        DisplayPanel panel = getInnerPanel();
        Graphics graphics = panel.getGraphics();
        
        if (graphics == null)
            return;
            
        panel.paint(graphics);
    }
    
    
    /**
     * <p>Fills this buffered panel
     * with the given color or <code>Paint</code>.</p>
     *
     * <p>If the given color or <code>Paint</code> is <code>null</code>,
     * the buffered panel is not changed.</p>
     *
     * @param fill the color or <code>Paint</code> used to fill
     */
    public final void fillPanel(Paint fill) {
        if (fill == null)
            return;
        
        Graphics2D g2 = getBufferGraphics();
        g2.setPaint(fill);
        g2.fillRect(0, 0, getBufferWidth(), getBufferHeight());
    }
    
    
    /**
     * <p>Fills this buffered panel 
     * with its background color or <code>Paint</code>.</p>
     * 
     * @see #getBufferBackground()
     * @see #setBufferBackground(Paint)
     */
    public final void clearPanel() {
        fillPanel(getBufferBackground());
    }
    
    
    /**
     * <p>Clears the internal paintable sequence;
     * returns an array with the paintables removed.</p>
     *
     * <p>Calls the corresponding method on the internal
     * paintable sequence.</p>
     */
    public final Paintable[] clearSequence() {
        return paintablesequence.clearSequence();
    }
    
    
    /**
     * <p>Fills this buffered panel 
     * with its background color or <code>Paint</code>
     * and clears the internal paintable sequence;
     * returns an array with the paintables removed.</p>
     */
    public final Paintable[] clearPanelAndSequence() {
        clearPanel();
        return paintablesequence.clearSequence();
    }
    
    
    /**
     * <p>Clears all data in the internal paintable sequence,
     * that is, the sequence paintables,
     * the sequence background paint,
     * and the sequence background tile;
     * returns an array with the paintables removed.</p>
     * 
     * <p>Calls <code>clearEverything</code> on the internal
     * paintable sequence.</p>
     */
    public final Paintable[] clearSequenceData() {
        return paintablesequence.clearEverything();
    }
    
    
    /**
     * <p>Fills this buffered panel 
     * with its background color or <code>Paint</code>
     * and clears all data in the internal paintable sequence,
     * that is, the sequence paintables,
     * the sequence background paint,
     * and the sequence background tile;
     * returns an array with the paintables removed.</p>
     * 
     * <p>Calls <code>clearEverything</code> on the internal
     * paintable sequence.</p>
     */
    public final Paintable[] clearPanelAndSequenceData() {
        clearPanel();
        return paintablesequence.clearEverything();
    }
    
    
    /**
     * <p>Sets the background color or <code>Paint</code>
     * to the given color or <code>Paint</code>.</p>
     *
     * <p>If the given color or <code>Paint</code> is <code>null</code>,
     * the current background is not changed.</p>
     *
     * <p>The panel is only filled with the background paint if one of
     * <code>clearPanel...</code> methods is called after setting the
     * background.  The reason for this is that changing the buffer
     * background destroys all current color data in the buffer and
     * the caller should make an explicit decision to do this action.</p>
     *
     * @param background the new background color or <code>Paint</code>
     */
    public final void setBufferBackground(Paint background) {
        if (background == null)
            return;
            
        bufferBackground = background;
    }
    
    
    /**
     * <p>Returns the background color or <code>Paint</code>
     * for this buffered panel.</p>
     */
    public final Paint getBufferBackground() {
        return bufferBackground;
    }
    
    
    /**
     * <p>Sets the internal paintable sequence background paint.</p>
     *
     * <p>The paint may be set to <code>null</code> to eliminate
     * background painting.</p>
     * 
     * @param background the internal paintable sequence background paint
     */
    public final void setSequenceBackgroundPaint(Paint background) {
        paintablesequence.setBackgroundPaint(background);
    }
    
    
    /**
     * Returns the internal paintable sequence background paint.
     *
     * @return the internal paintable sequence background paint.
     */
    public final Paint getSequenceBackgroundPaint() {
        return paintablesequence.getBackgroundPaint();
    }
    
    
    /**
     * <p>Clear the internal paintable sequence background paint.</p>
     */
    public final void clearSequenceBackgroundPaint() {
        paintablesequence.clearBackgroundPaint();
    }
    
    
    /**
     * <p>Sets the internal paintable sequence background tile paintable.
     * The object passed should be a paintable or be convertible to a
     * paintable via the method <code>ComponentFactory.makePaintable</code>.</p>
     *
     * <p>The object may be set to <code>null</code> to eliminate
     * background tiling.</p>
     *
     * @param object the internal paintable sequence background tile object
     */
    public final void setSequenceBackgroundTile(Object object) {
        paintablesequence.setBackgroundTile(object);
    }
    
    
    /**
     * Returns the internal paintable sequence background tile.
     *
     * @return the internal paintable sequence background tile.
     */
    public final Paintable getSequenceBackgroundTile() {
        return paintablesequence.getBackgroundTile();
    }
    
    
    /**
     * <p>Clear the internal paintable sequence background tile.</p>
     */
    public final void clearSequenceBackgroundTile() {
        paintablesequence.clearBackgroundTile();
    }
    
    
    /**
     * <p>Clear the both the internal paintable sequence background paint
     * and the internal paintable sequence background tile.</p>
     */
    public final void clearBothSequenceBackgrounds() {
        paintablesequence.clearBothBackgrounds();
    }
    
    
    /**
     * <p>Draws a grid with spacing equal to the given pixels value and
     * color equal to <code>Colors.lightgray</code>.</p>
     * 
     * <p>Does nothing if pixels &lt;= 0.</p>
     *
     * @param pixels the pixel spacing of the grid
     */
    public void drawGrid(int pixels) {
        drawGrid(pixels, null);
    }
    
    
    /**
     * <p>Draws a grid with spacing equal to the given pixels value and
     * color equal to the given color.</p>
     *
     * <p>If the given color is <code>null</code>, it is set to the
     * value <code>Colors.lightgray</code>.</p>
     *
     * <p>Does nothing if pixels &lt;= 0.</p>
     *
     * @param pixels the pixel spacing of the grid
     * @param color  the grid color
     */
    public void drawGrid(int pixels, Color color) {
        if (pixels <= 0)
            return;
        
        if (color == null)
            color = Colors.lightgray;
        
        Graphics2D g = getBufferGraphics();
        
        Paint oldPaint = g.getPaint();
        
        g.setPaint(color);
        
        XLine2D line = new XLine2D();
        
        int w = getBufferWidth();
        int h = getBufferHeight();
        
        for (int x = pixels; x < w; x += pixels) {
            line.setValue(x, 0, x, h);
            g.draw(line);
        }
        
        for (int y = pixels; y < h; y += pixels) {
            line.setValue(0, y, w, y);
            g.draw(line);
        }
        
        g.setPaint(oldPaint);
    }
    
    
    /**
     * <p>Sets the background color for the panel that wraps the buffer
     * to the given color.</p>
     *
     * @param background the new background color
     */
    public final void setBackground(Color background) {
        if (painter != null)
            painter.setBackground(background);
            
        super.setBackground(background);
    }
    
    
    /**
     * <p>Sets the mouse action adapter for the buffer
     * to the given adapter.</p>
     *
     * <p>If <code>null</code>, the current adapter
     * is not changed.</p>
     *
     * @param adapter the new mouse action adapter
     */
    public final void setMouseActionAdapter(MouseActionAdapter adapter) {
        if (adapter == null)
            return;
            
        painter.removeMouseListener(mouseActions);
        painter.removeMouseMotionListener(mouseActions);

        mouseActions = adapter;

        painter.addMouseListener(mouseActions);
        painter.addMouseMotionListener(mouseActions);
    }
    
    
    /**
     * Returns the mouse action adapter for the buffer.
     */
    public final MouseActionAdapter getMouseActionAdapter() {
        return mouseActions;
    }    
    
    
    /**
     * <p>Sets the key action adapter for the buffer
     * to the given adapter.</p>
     *
     * <p>If <code>null</code>, the current adapter
     * is not changed.</p>
     *
     * @param adapter the new key action adapter
     */
    public final void setKeyActionAdapter(KeyActionAdapter adapter) {
        if (adapter == null)
            return;
            
        painter.removeKeyListener(keyActions);

        keyActions = adapter;

        painter.addKeyListener(keyActions);
    }
    
    
    /**
     * Returns the key action adapter for the buffer.
     */
    public final KeyActionAdapter getKeyActionAdapter() {
        return keyActions;
    }
    
    
    /**
     * <p>Overrides to delegate to the inner panel that is
     * the panel returned by <code>getInnerPanel()</code>.</p>
     */
    public final void setFocusable(boolean focusable) {
        getInnerPanel().setFocusable(focusable);
    }
    
    
    /**
     * <p>Overrides to delegate to the inner panel that is
     * the panel returned by <code>getInnerPanel()</code>.</p>
     */
    public final boolean isFocusable() {
        return getInnerPanel().isFocusable();
    }
    
    
    /**
     * <p>Overrides to delegate to the inner panel that is
     * the panel returned by <code>getInnerPanel()</code>.</p>
     */
    public final boolean isRequestFocusEnabled() {
        return getInnerPanel().isRequestFocusEnabled();
    }
    
    
    /**
     * <p>Overrides to delegate to the inner panel that is
     * the panel returned by <code>getInnerPanel()</code>.</p>
     */
    public final void requestFocus() {
        getInnerPanel().requestFocus();
    }
    
    
    /**
     * <p>Overrides to delegate to the inner panel that is
     * the panel returned by <code>getInnerPanel()</code>.</p>
     */
    public final boolean requestFocusInWindow() {
        return getInnerPanel().requestFocusInWindow();
    }
    
    
    /**
     * <p>Gets the internal <code>PaintableSequence</code> that will
     * be painted after the underlying buffer is painted.</p>
     *
     * <p>This method enables access to the full functionality of the
     * internal paintable sequence.</p>
     */
    public final PaintableSequence getPaintableSequence() {
        return paintablesequence;
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param index the position in the sequence
     * @param o the object convert to a paintable to set at the position
     */
    public final Paintable setPaintable(int index, Object o) {
        return paintablesequence.setPaintable(index, o);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param o the object convert to a paintable to set at the position
     * @param index the position in the sequence
     */
    public final Paintable setPaintable(Object o, int index) {
        return paintablesequence.setPaintable(o, index);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param index the position in the sequence
     */
    public final Paintable getPaintable(int index) {
        return paintablesequence.getPaintable(index);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param paintable the paintable to find
     */
    public final int getIndex(Paintable paintable) {
        return paintablesequence.getIndex(paintable);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param index the position in the sequence
     * @param o the object to convert to a paintable to insert at the position
     */
    public final Paintable addPaintable(int index, Object o) {
        return paintablesequence.addPaintable(index, o);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param o the object to convert to a paintable to insert at the position
     * @param index the position in the sequence
     */
    public final Paintable addPaintable(Object o, int index) {
        return paintablesequence.addPaintable(o, index);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param o the object to convert to a paintable to insert at the top
     */
    public final Paintable addPaintable(Object o) {
        return paintablesequence.addPaintable(o);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param o the object to convert to a paintable to insert at the top
     */
    public final Paintable addPaintableAtTop(Object o) {
        return paintablesequence.addPaintableAtTop(o);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param o the object to convert to a paintable to insert at the bottom
     */
    public final Paintable appendPaintable(Object o) {
        return paintablesequence.appendPaintable(o);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param objects the array of objects to install
     */
    public final Paintable[] setSequence(Object[] objects) {
        return paintablesequence.setSequence(objects);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param index   the position in the sequence at which to add
     * @param objects the array of objects to add
     */
    public final Paintable[] addSequence(int index, Object[] objects) {
        return paintablesequence.addSequence(index, objects);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param objects the array of objects to add
     * @param index   the position in the sequence at which to add
     */
    public final Paintable[] addSequence(Object[] objects, int index) {
        return paintablesequence.addSequence(objects, index);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param objects the array of objects to add at the top
     */
    public final Paintable[] addSequence(Object[] objects) {
        return paintablesequence.addSequence(objects);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param objects the array of objects to add at the top
     */
    public final Paintable[] addSequenceAtTop(Object[] objects) {
        return paintablesequence.addSequenceAtTop(objects);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param objects the array of objects to append
     */
    public final Paintable[] appendSequence(Object[] objects) {
        return paintablesequence.appendSequence(objects);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param index the position of the item to remove
     */
    public final Paintable removePaintable(int index) {
        return paintablesequence.removePaintable(index);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param paintable the item to remove
     */
    public final void removePaintable(Paintable paintable) {
        paintablesequence.removePaintable(paintable);
    }
    
    
    /**
     * <p>Calls the corresponding method on the internal paintable
     * sequence.</p>
     *
     * @param m the starting index inclusive
     * @param n the ending index exclusive
     */
    public final Paintable[] removeSequence(int m, int n) {
        return paintablesequence.removeSequence(m, n);
    }
    
    
    /**
     * <p>Override this <code>paintOver</code> method to add additional
     * painting actions after the default buffer repaint is done during
     * a <code>repaint()</code> call.</p>
     *
     * <p>The intention of this facility is to enable algorithmic
     * painting to be done via the <code>paintOver</code> method on
     * top of the default painting of the buffer image on the panel.
     * This makes the buffer appear to be the background and what is
     * painted via the <code>paintOver</code> method to be painted in
     * the foreground.</p>
     *
     * <p>The default implementation of the <code>paintOver</code>
     * method is to do nothing.  This enables overrides as desired.</p>
     * 
     * <p>As of 2.4.0, this method is called after both the painting of
     * the buffer and the painting of the internal paintable sequence.
     * Given the power inherent in painting both the buffer bitmap and
     * the internal paintable sequence, it is rare that this method
     * will need to be overridden.</p>
     *
     * @param g2 the <code>Graphics2D</code> context for the buffer
     *           repaint operation
     * @since 1.0.1
     */
    public void paintOver(Graphics2D g2) {
        // intentionally left empty to allow for overrides
    }
    
    
    /** The method that implements the "Simple Mouse Action" moved action. */
    protected void SMA_Moved(MouseEvent evt) {
        if (mouseIsInPanel)
            setMousePosition(evt);
    }
    
    
    /** The method that implements the "Simple Mouse Action" pressed action. */
    protected void SMA_Pressed(MouseEvent evt) {
        mouseIsInPanel = true;
        setMousePosition(evt);
        
        int newX = mouseX;
        int newY = mouseY;
        
        if(paintablesequence != null)
            currentPaintable = paintablesequence.hits(newX, newY);
        else
            currentPaintable = null;
        
        if (currentPaintable != null)
            if (autoShiftToTop) {
                paintablesequence.shiftPaintableToTop(currentPaintable);
                repaint();
            }
    }
    
    
    /** The method that implements the "Simple Mouse Action" released action. */
    protected void SMA_Released(MouseEvent evt) {
    
    }
    
    
    /** The method that implements the "Simple Mouse Action" clicked action. */
    protected void SMA_Clicked(MouseEvent evt) {
    
    }
    
    
    /** The method that implements the "Simple Mouse Action" dragged action. */
    protected void SMA_Dragged(MouseEvent evt) {
        int oldX = mouseX;
        int oldY = mouseY;
        
        if (mouseIsInPanel)
            setMousePosition(evt);
        
        int newX = mouseX;
        int newY = mouseY;
        
        if (currentPaintable != null)
            if ((newX != oldX) || (newY != oldY)) {
                int dx = newX - oldX;
                int dy = newY - oldY;
                
                currentPaintable.move(dx, dy);
                repaint();
            }
    }
    
    
    /** The method that implements the "Simple Mouse Action" entered panel action. */
    protected void SMA_Entered(MouseEvent evt) {
        mouseIsInPanel = true;
    }
    
    
    /** The method that implements the "Simple Mouse Action" exited panel action. */
    protected void SMA_Exited(MouseEvent evt) {
        mouseIsInPanel = false;
    }
    
    
    /**
     * <p>Gets the current value of the mouse x-position.</p>
     *
     * <p>Valid only if mouse actions are installed that set this
     * position.</p>
     */
    public final int getMouseX() { return mouseX; }
    
    
    /**
     * <p>Gets the current value of the mouse y-position.</p>
     *
     * <p>Valid only if mouse actions are installed that set this
     * position.</p>
     */
    public final int getMouseY() { return mouseY; }
    
    
    /**
     * <p>Sets the mouse position from the mouse event.</p>
     *
     * <p>May be used as a helper method for defining mouse actions.
     * If used while the mouse is moved or dragged then will make
     * <code>getMouseX</code> and <code>getMouseY</code> valid.</p>
     *
     * @param evt a mouse event
     */
    public final void setMousePosition(MouseEvent evt) {
        if (evt == null)
            return;
        
        mouseX = evt.getX();
        mouseY = evt.getY();
    }
    
    
    /**
     * <p>Sets whether or not a clicked paintable should shift
     * to the top of the paintable sequence.</p>
     *
     * @param shift whether or not to shift a paintable to the top
     */
    public final void setAutoShiftToTop(boolean shift) {
        autoShiftToTop = shift;
    }
    
    
    /**
     * <p>Gets whether or not a clicked paintable should shift
     * to the top of the paintable sequence.</p>
     */
    public final boolean getAutoShiftToTop() {
        return autoShiftToTop;
    }
    
    
    /**
     * <p>Installs a set of simple mouse actions that by default
     * will permit a user to drag the paintables in the internal
     * paintable sequence.</p>
     *
     * <p>These actions will only drag while the mouse is in the
     * panel.  This avoids dragging a paintable completely out
     * of view.  However, if the mouse re-enters the panel, then
     * the paintable will jump to the new mouse position.</p>
     *
     * <p>These mouse actions respect the decision on whether or
     * not a clicked paintable should shift to the top of the
     * internal paintable sequence.</p>
     *
     * <p>These mouse actions are just a useful simple default
     * set of actions.  The caller can change this behavior by
     * either of two methods:</p>
     *
     * <p>1. Obtain the mouse action adapter via the method call
     * <code>getMouseActionAdapter()</code> and install actions
     * directly.</p>
     *
     * <p>2. Define a derived class and override the following
     * seven <code>protected</code> methods:</p>
     *
     * <ul>
     *  <li><code>SMA_Moved(MouseEvent evt)</code></li>
     *  <li><code>SMA_Pressed(MouseEvent evt)</code></li>
     *  <li><code>SMA_Released(MouseEvent evt)</code></li>
     *  <li><code>SMA_Clicked(MouseEvent evt)</code></li>
     *  <li><code>SMA_Dragged(MouseEvent evt)</code></li>
     *  <li><code>SMA_Entered(MouseEvent evt)</code></li>
     *  <li><code>SMA_Exited(MouseEvent evt)</code></li>
     * </ul>
     *
     * <p>Then use <code>installSimpleMouseActions()</code> to
     * install the seven mouse actions you have defined.</p>
     *
     * <p>Technical note: This method call automatically uninstalls
     * any other mouse actions that may have been installed.</p>
     */
    public void installSimpleMouseActions() {
        uninstallAllMouseActions();
        
        mouseActions.addMouseMovedAction   (SMA_Moved_Action);
        mouseActions.addMousePressedAction (SMA_Pressed_Action);
        mouseActions.addMouseReleasedAction(SMA_Released_Action);
        mouseActions.addMouseClickedAction (SMA_Clicked_Action);
        mouseActions.addMouseDraggedAction (SMA_Dragged_Action);
        mouseActions.addMouseEnteredAction (SMA_Entered_Action);
        mouseActions.addMouseExitedAction  (SMA_Exited_Action);
    }
    
    
    /**
     * <p>Convenience method that installs the simple mouse actions
     * and sets the auto-shift-to-top parameter at the same time.</p>
     *
     * <p>Equivalent to:</p>
     *
     * <ul>
     *  <li><code>installSimpleMouseActions()</code></li>
     *  <li><code>setAutoShiftToTop(shift)</code></li>
     * </ul>
     *
     * @param shift whether or not to shift a paintable to the top
     */
    public void installSimpleMouseActions(boolean shift) {
        installSimpleMouseActions();
        setAutoShiftToTop(shift);
    }
    

    /**
     * Uninstalls all mouse actions that have been attached to the
     * mouse action adapter for this panel.
     */
    public final void uninstallAllMouseActions() {
        mouseActions.uninstallAllMouseActions();
    }
    
    
    /**
     * Uninstalls all key actions that have been attached to the
     * key action adapter for this panel.
     */
    public final void uninstallAllKeyActions() {
        keyActions.uninstallAllKeyActions();
    }
    
    
    ///////////////////
    // Inner classes //
    ///////////////////
    
    /**
     * <p>Panel that paints the internal buffered image that
     * maintains the persistent graphics state of the buffered 
     * panel.</p>
     *
     * <p>As of 2.4.0, paints the internal paintable sequence of
     * the buffered panel after painting the buffered image.</p>
     *
     * @author  Jeff Raab
     * @author  Richard Rasala
     * @version 2.4.0
     * @since   1.0
     */
    protected static class Painter extends DisplayPanel {
    
        /**
         * Reference to the <code>BufferedPanel</code> that created this
         * <code>Painter</code>.
         */
        protected BufferedPanel panel = null;
        
        
        /**
         * Contructor that should only be called by a
         * <code>BufferedPanel</code>.
         *
         * @param panel the <code>BufferedPanel</code> used to construct
         *        this <code>Painter</code>
         */
        protected Painter(BufferedPanel panel) {
            this.panel = panel;
        }
        
        
        /**
         * <p>Returns the size of the buffer as the size of this panel.</p>
         *
         * <p>Ignores any calls to <code>setPreferredSize</code>.</p>
         */
        public Dimension getPreferredSize() {
            return new Dimension(panel.getBufferWidth(), panel.getBufferHeight());
        }
        
        
        /**
         * <p>Paints the image buffer of the buffered panel in this panel
         * and then paints the buffered panel paintable sequence.</p>
         * 
         * <p>As of 2.6.0e, is syncrhronized on the enclosing
         * <code>BufferedPanel</code> object.</p>
         * 
         * @param g the standard graphics state for this panel
         */
        protected void paintComponent(Graphics g) {
            synchronized (panel) {
                Insets in = getInsets();
                int x = in.left;
                int y = in.top;
                
                g.drawImage(panel.getBuffer(), x, y, this);
                
                g.translate( x,  y);
                panel.paintablesequence.paint(g);
                g.translate(-x, -y);
            }
        }
        
        
        /**
         * <p>Paints the component and then adds the work done by the
         * paintOver function.</p>
         * 
         * <p>As of 2.6.0e, is syncrhronized on the enclosing
         * <code>BufferedPanel</code> object.</p>
         * 
         * @param g the standard graphics state for this panel
         */
        public void paint(Graphics g) {
            synchronized (panel) {
                // this call will call paintComponent to paint the buffer
                super.paint(g);
                
                Graphics2D g2 = (Graphics2D) g;
    
                Insets in = getInsets();
                int x = in.left;
                int y = in.top;
                
                g2.translate( x,  y);
                panel.paintOver(g2);
                g2.translate(-x, -y);
            }
        }
    }
}
