/*
 * @(#)AbstractPaintable.java    2.6.0c   8 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 edu.neu.ccs.util.MathUtilities;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;

import javax.swing.*;
import java.beans.*;

/**
 * <p>The abstract class <code>AbstractPaintable</code> defines objects that
 * implement <code>Paintable</code>.</p>
 *
 * <p>A derived class must implement the following three methods:</p>
 *
 * <ul>
 *   <li><code>public void originalPaint(Graphics g)</code></li>
 *   <li><code>public XRect getActualBounds2D()</code></li>
 *   <li><code>public boolean originalContains(double x, double y)</code></li>
 * </ul>
 *
 * <p>As of 2.4.0, the former interface <code>MutatablePaintable</code> has
 * been merged into <code>Paintable</code>.  Furthermore, the interface
 * <code>MutatablePaintable</code> has been removed from the Java Power
 * Tools as has the class <code>AbstractMutatablePaintable</code>.</p>
 *
 * <p>Much programming experience proved that splitting the design into two
 * interfaces and two abstract classes was more hassle than it was worth.
 * Although splittling the design into two classes was conceptually simple,
 * it made programming much more difficult since the necessary facilities
 * were not all available everywhere.</p>
 *
 * <p>In 2.6.0, the notion of a <i>background paint</i> was refactored from
 * <code>Tile</code> into required &ldquo;set&rdquo; and &ldquo;get&rdquo;
 * methods in the <code>Paintable</code> interface with corresponding
 * implementation in this class.  See the comments on the <code>paint</code>
 * method for further details on how the background paint is applied.</p>
 * 
 * <p>Also, in 2.6.0, support was added to use a paintable as
 * a tile for a rectangle.  Once this facility was in place, we added the
 * option for one paintable to use another paintable as a
 * <i>background tile</i> that will paint the bounding box of the primary
 * paintable before that paintable itself paints.</p>
 * 
 * <p>The order of painting is now:</p>
 * 
 * <ul>
 *   <li>Paint the <i>background paint</i> if any.</li>
 *   <li>Tile with the <i>background tile</i> if any.</li>
 *   <li>Paint the primary content of this paintable.</li>
 * </ul>
 * 
 * <p>Of course, no painting takes place if the paintable is set to be
 * invisible.</p>
 * 
 * <p>In 2.6.0c, added the method <code>makeSnapshot</code>.  This method
 * is implemented via:</p>
 * 
 * <pre>    PaintableTools.makeBufferedImage(this)</pre>
 * 
 * @author  Richard Rasala
 * @version 2.6.0c
 * @since   2.3
 */
public abstract class AbstractPaintable
    implements Paintable
{
    /** Bound property name for set background paint. */
    public static final String SET_BACKGROUND_PAINT = "set.background.paint";
    
    /** Bound property name for set background tile. */
    public static final String SET_BACKGROUND_TILE = "set.background.tile";
    
    
    /** The default Bounds2D rectangle. */
    private XRect    defaultBounds2D = null;
    
    /** The default center. */
    private XPoint2D defaultCenter   = null;
    
    /** The default original Bounds2D rectangle. */
    private XRect    defaultOriginalBounds2D = null;
    
    /** The default original center. */
    private XPoint2D defaultOriginalCenter   = null;
    
    
    /** The visibility property of the paintable. */
    private boolean visible = true;
    
    /** The opacity for paintables that are partially transparent. */
    private float opacity = 1;
    
    /** The background paint if any. */
    private Paint background = null;
    
    /** The background tile if any. */
    private Paintable backgroundTile = null;
    
    
    /** The mutator transform. */
    private AffineTransform mutator = new AffineTransform();
    
    /** The mutator inverse transform. */
    private AffineTransform inverse = new AffineTransform();
    
    
    
    /**
     * <p>The main listener for this AbstractPaintable object to implement
     * the interface <code>SupportsPropertyChange</code>.</p>
     *
     * <p>Note: In this implementation, <code>PropertyChangeSupport</code>
     * is used rather than <code>SwingPropertyChangeSupport</code> to
     * ensure thread safety.</p>
     */
    private final PropertyChangeSupport changeAdapter
        = new PropertyChangeSupport(this);
    
    
    /**
     * <p>The forwarding listener for this AbstractPaintable object to implement
     * the interface <code>SupportsPropertyChange</code>.</p>
     *
     */
    private final PropertyChangeForwardingListener forwardingListener
        = new PropertyChangeForwardingListener(this);
    

    /**
     * <p>Paints onto a graphics context <code>g</code> using information
     * from this object.</p>
     *
     * <p>In this default implementation, the following policies apply.
     * Note the that bounds region refers to the rectangle returned by
     * <code>getBounds2D</code>.</p>
     *
     * <ul>
     *   <li>Does nothing if the graphics context <code>g</code> is
     *       <code>null</code> or this paintable is set to be invisible.</li>
     *   <li>Calls the method <code>getPreparedGraphics2D</code> to obtain
     *       a copy <code>h</code> of the graphics context <code>g</code>
     *       that will be used for all painting. <code>h</code> has the
     *       following settings:</li>
     *       <ul>
     *         <li>The paint area is clipped to the bounds region.</li>
     *         <li>Anti-aliasing is turned on.</li>
     *         <li>The current opacity setting is applied.</li>
     *       </ul>
     *   <li>If the background paint is non-<code>null</code>, then the
     *       bounds region is painted with this background paint.
     *   <li>If the background tile is non-<code>null</code>, then the
     *       bounds region is tiled with this background tile.
     *   <li>The current mutator is then applied to the graphics
     *       context <code>h</code>.</li>
     *   <li>The method <code>originalPaint</code> is called on the now
     *       transformed graphics context <code>h</code>.</li>
     * </ul>
     *
     * <p>When this method call is complete, the internal state of
     * <code>g</code> should be unchanged.  This is the case in this
     * implementation.  Any method that overrides this method must
     * ensure the same invariant regarding <code>g</code>.</p>
     * 
     * @param g the graphics context on which to paint
     */
    public void paint(Graphics g) {
        if ((g == null) || !visible)
            return;
        
        Graphics2D h = getPreparedGraphics2D(g);
        
        if ((background != null) || (backgroundTile != null))
        {
            XRect rect = getBounds2D();
            
            if (background != null) {
                Paint paint = h.getPaint();
                h.setPaint(background);
                h.fill(rect);
                h.setPaint(paint);
            }
            
            if (backgroundTile != null) {
                backgroundTile.paintAsTiles(h, rect);
            }
        }
        
        h.transform(mutator);
        originalPaint(h);
        h.transform(inverse);
    }
    
    
    /**
     * <p>Without changing the paintable or the graphics context, paint
     * the paintable in a translated position specified by the point in
     * coordinates.</p>
     *
     * <p>In particular, <code>paintAt(g,0,0)</code> is equivalent to
     * <code>paint(g)</code>.</p>
     *
     * <p><i>Recommendation:</i> The <code>paintAt</code> methods are
     * intended to be used if a designer wants to define a paintable at
     * or near the origin of coordinates and then independently compute
     * or retrieve the position at which the paintable will be painted.
     * If the <code>paintAt</code> methods are used, it is recommended
     * that the programmer avoid mutating the paintable directly since
     * that will almost certainly lead to confusion and bugs.  Either
     * use mutation or use <code>paintAt</code> but not both.</p>
     *
     * @param g the graphics context on which to paint
     * @param x the x-translation
     * @param y the y-translation
     */
    public final void paintAt(Graphics g, double x, double y) {
        if (g == null)
            return;
        
        Graphics2D h = (Graphics2D) g;
        
        h.translate( x,  y);
        paint(h);
        h.translate(-x, -y);
    }
    
    
    /**
     * <p>Without changing the paintable or the graphics context, paint
     * the paintable in a translated position specified by the point.</p>
     *
     * <p><i>Recommendation:</i> The <code>paintAt</code> methods are
     * intended to be used if a designer wants to define a paintable at
     * or near the origin of coordinates and then independently compute
     * or retrieve the position at which the paintable will be painted.
     * If the <code>paintAt</code> methods are used, it is recommended
     * that the programmer avoid mutating the paintable directly since
     * that will almost certainly lead to confusion and bugs.  Either
     * use mutation or use <code>paintAt</code> but not both.</p>
     *
     * @param g the graphics context on which to paint
     * @param p the translation vector
     */
    public final void paintAt(Graphics g, Point2D p) {
        if (p == null)
            return;
        
        paintAt(g, p.getX(), p.getY());
    }
    
    
    /**
     * <p>Without changing the paintable or the graphics context, paint
     * the paintable in a translated position specified by the point in
     * coordinates and the affine transform T; before painting the
     * transform T should be applied to the point.</p>
     *
     * <p>The application of this facility is to apply a transform to
     * the position of painting that should be applied to the position
     * alone and NOT to the paintable or to the graphics context.</p>
     *
     * <p>For example, if T is a reflection through a horizontal axis
     * that places the origin (0,0) at the lower left of a panel, then
     * it would probably be a very bad idea to apply the reflection
     * to the graphics context (which turns every object upside down)
     * or to the paintable (which turns the paintable upside down).
     * The transform should be applied to the position alone.</p>
     *
     * <p><i>Recommendation:</i> The <code>paintAt</code> methods are
     * intended to be used if a designer wants to define a paintable at
     * or near the origin of coordinates and then independently compute
     * or retrieve the position at which the paintable will be painted.
     * If the <code>paintAt</code> methods are used, it is recommended
     * that the programmer avoid mutating the paintable directly since
     * that will almost certainly lead to confusion and bugs.  Either
     * use mutation or use <code>paintAt</code> but not both.</p>
     *
     * @param g the graphics context on which to paint
     * @param T the affine transform to apply to the translation point
     * @param x the original x-translation before transformation
     * @param y the original y-translation before transformation
     */
    public final void paintAt(Graphics g, AffineTransform T, double x, double y) {
        if ((T == null) || (T.isIdentity())) {
            paintAt(g, x, y);
            return;
        }
        
        Point2D.Double p = new Point2D.Double(x, y);
        
        T.transform(p, p);
        
        paintAt(g, p.getX(), p.getY());
    }
    
    
    /**
     * <p>Without changing the paintable or the graphics context, paint
     * the paintable in a translated position specified by the point in
     * coordinates and the affine transform T; before painting the
     * transform T should be applied to the point.</p>
     *
     * <p>The application of this facility is to apply a transform to
     * the position of painting that should be applied to the position
     * alone and NOT to the paintable or to the graphics context.</p>
     *
     * <p>For example, if T is a reflection through a horizontal axis
     * that places the origin (0,0) at the lower left of a panel, then
     * it would probably be a very bad idea to apply the reflection
     * to the graphics context (which turns every object upside down)
     * or to the paintable (which turns the paintable upside down).
     * The transform should be applied to the position alone.</p>
     *
     * <p><i>Recommendation:</i> The <code>paintAt</code> methods are
     * intended to be used if a designer wants to define a paintable at
     * or near the origin of coordinates and then independently compute
     * or retrieve the position at which the paintable will be painted.
     * If the <code>paintAt</code> methods are used, it is recommended
     * that the programmer avoid mutating the paintable directly since
     * that will almost certainly lead to confusion and bugs.  Either
     * use mutation or use <code>paintAt</code> but not both.</p>
     *
     * @param g the graphics context on which to paint
     * @param T the affine transform to apply to the translation point
     * @param p the original translation point before transformation
     */
    public final void paintAt(Graphics g, AffineTransform T, Point2D p) {
        if (p == null)
            return;
        
        paintAt(g, T, p.getX(), p.getY());
    }
    
    
    /**
     * <p>Repeatedly paints this object as a tile that covers the
     * rectangle <code>(x,y,w,h)</code> in the graphics context
     * <code>g</code>.  The painting is clipped to this rectangle.
     * The graphics context is unchanged when this method is done.</p>
     * 
     * <p>This method works appropriately if the width or height
     * is negative.</p>
     * 
     * <p>However, the method does nothing if:</p>
     * 
     * <ul>
     *   <li>The given graphics context is <code>null</code>.</li>
     *   <li>The given width or height is zero.</li>
     *   <li>This paintable has zero width or height.</li>
     *   <li>This paintable is set to be invisible.</li>
     * </ul>
     * 
     * @param g the graphics context
     * @param x the x-corner of the rectangle to tile
     * @param y the y-corner of the rectangle to tile
     * @param w the width  of the rectangle to tile
     * @param h the height of the rectangle to tile
     */
    public final void paintAsTiles
        (Graphics g, double x, double y, double w, double h)
    {
        if ((g == null) || !visible || (w == 0) || (h == 0))
            return;
        
        if (w < 0) {
            x += w;
            w = -w;
        }
        
        if (h < 0) {
            y += h;
            h = -h;
        }
        
        XRect rect = getBounds2D();
        
        double x0 = rect.getMinX();
        double y0 = rect.getMinY();
        double w0 = rect.getWidth();
        double h0 = rect.getHeight();
        
        if ((w0 <= 0) || (h0 <= 0))
            return;
        
        int[] xLimits = MathUtilities.getTileIndexLimits(x, w, x0, w0);
        
        int[] yLimits = MathUtilities.getTileIndexLimits(y, h, y0, h0);
        
        if ((xLimits != null) && (yLimits != null)) {
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setClip((int) x, (int) y, (int) w, (int) h);
            
            for (int i = xLimits[0]; i < xLimits[1]; i++)
                for (int j = yLimits[0]; j < yLimits[1]; j++)
                    paintAt(g2, i * w0, j * h0);
        }
    }
    
    
    /**
     * <p>Repeatedly paints this object as a tile that covers the
     * rectangle <code>(0,0,w,h)</code> in the graphics context
     * <code>g</code>.  The painting is clipped to this rectangle.
     * The graphics context is unchanged when this method is done.</p>
     * 
     * <p>This method works appropriately if the width or height
     * is negative.</p>
     * 
     * <p>However, the method does nothing if:</p>
     * 
     * <ul>
     *   <li>The given graphics context is <code>null</code>.</li>
     *   <li>The given width or height is zero.</li>
     *   <li>This paintable has zero width or height.</li>
     *   <li>This paintable is set to be invisible.</li>
     * </ul>
     * 
     * @param g the graphics context
     * @param w the width  of the rectangle to tile
     * @param h the height of the rectangle to tile
     */
    public final void paintAsTiles
        (Graphics g, double w, double h)
    {
        paintAsTiles(g, 0, 0, w, h);
    }
    
    
    /**
     * <p>Repeatedly paints this object as a tile that covers the
     * given rectangle in the graphics context <code>g</code>.
     * The painting is clipped to this rectangle.
     * The graphics context is unchanged when this method is done.</p>
     * 
     * <p>The method does nothing if:</p>
     * 
     * <ul>
     *   <li>The given graphics context is <code>null</code>.</li>
     *   <li>The given rectangle is <code>null</code>.</li>
     *   <li>The width or height of the given rectangle is zero.</li>
     *   <li>This paintable has zero width or height.</li>
     *   <li>This paintable is set to be invisible.</li>
     * </ul>
     * 
     * @param g    the graphics context
     * @param rect the rectangle to tile
     */
    public final void paintAsTiles(Graphics g, Rectangle2D rect)
    {
        if ((g == null) || (rect == null))
            return;
        
        double x = rect.getMinX();
        double y = rect.getMinY();
        double w = rect.getWidth();
        double h = rect.getHeight();
        
        paintAsTiles(g, x, y, w, h);
    }
    
    
    /**
     * <p>Returns a copy of the given graphics context after modifying the copy
     * to clip to within the bounds region, to set anti-aliasing on, and to
     * apply the opacity of this paintable if needed.</p>
     *
     * <p>For convenience, the graphics context is returned as a
     * <code>Graphics2D</code> object.</p>
     *
     * @param  g the given graphics context to copy
     * @return a suitably prepared copy of the given graphics context
     */
    public final Graphics2D getPreparedGraphics2D(Graphics g) {
        Graphics2D h = (Graphics2D) g.create();
        
        h.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        
        h.clip(getBounds2D());
        
        applyOpacity(h);
        
        return h;
    }
    
    
    /**
     * <p>Constructs a <code>BufferedImage</code> object that is
     * the same size as the bounding box of this paintable and
     * then paints this paintable onto the buffered image with a
     * suitable translation back to the origin.</p>
     * 
     * <p>This method creates a snapshot of the current visual
     * state of the paintable.</p>
     * 
     * <p>The <code>BufferedImage</code> returned will have size at
     * least 1-by-1 even if this paintable has 0 width or height.</p>
     * 
     * <p>Implemented via:</p>
     * 
     * <pre>    PaintableTools.makeBufferedImage(this)</pre>
     */
    public final BufferedImage makeSnapshot() {
        return PaintableTools.makeBufferedImage(this);
    }
    
    
    /**
     * <p>Returns a copy of the 2-dimensional bounds of the paintable.</p>
     *
     * <p>If the value of <code>getDefaultBounds2D</code> is
     * non-<code>null</code>, then returns this value.</p>
     *
     * <p>Otherwise, returns the the bounds rectangle of the image under 
     * the mutator transform of the original bounds rectangle as computed
     * by <code>getOriginalBounds2D</code>. In particular, if the current
     * mutator is the identity transform, then returns
     * <code>getOriginalBounds2D</code>.</p>
     *
     * <p>This method may be overridden in a derived class if a more exact
     * bounds rectangle may be calculated.</p>
     *
     * <p>This method must not return <code>null</code>.</p>
     *
     * @return a copy of the 2-dimensional bounds of the paintable
     */
    public XRect getBounds2D() {
        // get the default bounds
        XRect bounds = getDefaultBounds2D();
        
        if (bounds != null)
            return bounds;
        
        // get the original bounds
        bounds = getOriginalBounds2D();
        
        if (mutator.isIdentity())
            return bounds;
        
        double x1 = bounds.getMinX();
        double y1 = bounds.getMinY();
        double x2 = bounds.getMaxX();
        double y2 = bounds.getMaxY();
        
        double[] source = new double[] { x1, y1, x2, y1, x2, y2, x1, y2 };
        double[] target = new double[8];
        
        mutator.transform(source, 0, target, 0, 4);
        
        x1 = Math.min(target[0], Math.min(target[2], Math.min(target[4], target[6])));
        y1 = Math.min(target[1], Math.min(target[3], Math.min(target[5], target[7])));
        
        x2 = Math.max(target[0], Math.max(target[2], Math.max(target[4], target[6])));
        y2 = Math.max(target[1], Math.max(target[3], Math.max(target[5], target[7])));
        
        return new XRect(x1, y1, x2 - x1, y2 - y1);
    }
    
    
    /**
     * <p>Returns a copy the center of the paintable.</p>
     *
     * <p>If the value of  <code>getDefaultCenter</code> is
     * non-<code>null</code>, then return this value.</p>
     *
     * <p>Otherwise, returns the image under the mutator transform of
     * <code>getOriginalCenter</code>.  In particular, if the current mutator
     * is the identity transform, then returns <code>getOriginalCenter</code>.</p>
     *
     * <p>This method may be overridden in a derived class if a more exact
     * center may be calculated.</p>
     *
     * <p>This method must not return <code>null</code>.</p>
     *
     * @return a copy the center of the mutated paintable
     */
    public XPoint2D getCenter() {
        // get default center
        XPoint2D center = getDefaultCenter();
        
        if (center != null)
            return center;
        
        // get original center
        center = getOriginalCenter();
        
        if (mutator.isIdentity())
            return center;
        
        return (XPoint2D) mutator.transform(center, center);
    }
    
    
    /**
     * <p>Returns a copy the corner of the paintable.</p>
     *
     * <p>The corner is the top-left corner of the bounds
     * rectangle returned by <code>getBounds2D</code>.</p>
     *
     * @return a copy the corner of the mutated paintable
     */
    public final XPoint2D getCorner() {
        XRect bounds = getBounds2D();
        
        return new XPoint2D(bounds.getMinX(), bounds.getMinY());
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the paintable.</p>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     * @return whether or not a specified point is inside the paintable
     */
    public boolean contains(double x, double y) {
        if (mutator.isIdentity())
            return originalContains(x, y);
        
        XPoint2D p = new XPoint2D(x, y);
        
        inverse.transform(p, p);
        
        return originalContains(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Tests if a specified point is inside the paintable.</p>
     *
     * @param  p a specified point
     * @return whether or not a specified point is inside the paintable
     */
    public final boolean contains(Point2D p) {
        if (p == null)
            return false;
        
        return contains(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Sets the visibility property of this paintable.</p>
     *
     * <p>The default for the visibility property should be <code>true</code></p>.
     *
     * <p>Fires property change: SET_VISIBLE.</p>
     * 
     * @param visible the visibility setting
     */
    public final void setVisible(boolean visible) {
        if (this.visible != visible) {
            this.visible = visible;
            firePropertyChange(SET_VISIBLE, null, null);
        }
    }
    
    
    /**
     * Returns the current visibility property of this paintable.
     */
    public final boolean isVisible() {
        return visible;
    }
    
    
    /**
     * <p>Sets the opacity of this paintable to a value between 0 and 1.</p>
     *
     * <p>Note that an opacity of 0 will make the paintable invisible.  This
     * is not recommended.  Instead use <code>setVisible(false)</code>.</p>
     *
     * <p>Fires property change: SET_OPACITY.</p>
     * 
     * @param opacity the opacity of this paintable
     */
    public final void setOpacity(float opacity) {
        if (opacity < 0)
            opacity = 0;
        
        if (opacity > 1)
            opacity = 1;
        
        if (this.opacity != opacity) {
            this.opacity = opacity;
            firePropertyChange(SET_OPACITY, null, null);
        }
    }
    
    
    /**
     * Returns the opacity value of this paintable between 0 and 1.
     *
     * @return the opacity of this paintable
     */
    public final float getOpacity() {
        return opacity;
    }
    
    
    /**
     * <p>Sets the background paint.</p>
     *
     * <p>The paint may be set to <code>null</code> to
     * eliminate background painting.</p>
     *
     * <p>Fires property change: SET_BACKGROUND_PAINT.</p>
     * 
     * @param background the background paint
     */
    public final void setBackgroundPaint(Paint background) {
        if (background == this.background)
            return;
        
        this.background = background;
        
        firePropertyChange(SET_BACKGROUND_PAINT, null, null);
    }
    
    
    /**
     * Returns the background paint.
     *
     * @return the background paint.
     */
    public final Paint getBackgroundPaint() {
        return background;
    }
    
    
    /**
     * <p>Clear the background paint.</p>
     * 
     * <p>Equivalent to:</p>
     * 
     * <pre>    setBackgroundPaint(null);</pre>
     *
     * <p>Fires property change: SET_BACKGROUND_PAINT.</p>
     */
    public final void clearBackgroundPaint() {
        setBackgroundPaint(null);
    }
    
    
    /**
     * <p>Sets the 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>
     *
     * <p>Fires property change: SET_BACKGROUND_TILE.</p>
     * 
     * @param object the background tile object
     */
    public final void setBackgroundTile(Object object) {
        Paintable tile = ComponentFactory.makePaintable(object);
        
        if (tile == this.backgroundTile)
            return;
        
        this.backgroundTile = tile;
        
        firePropertyChange(SET_BACKGROUND_PAINT, null, null);
    }
    
    
    /**
     * Returns the background tile.
     *
     * @return the background tile.
     */
    public final Paintable getBackgroundTile() {
        return backgroundTile;
    }
    
    
    /**
     * <p>Clear the background tile.</p>
     * 
     * <p>Equivalent to:</p>
     * 
     * <pre>    setBackgroundTile(null);</pre>
     *
     * <p>Fires property change: SET_BACKGROUND_TILE.</p>
     */
    public final void clearBackgroundTile() {
        setBackgroundTile(null);
    }
    
    
    /**
     * <p>Clear the both the background paint and the background tile.</p>
     * 
     * <p>Equivalent to:</p>
     * 
     * <pre>    setBackgroundPaint(null);
     *     setBackgroundTile(null);</pre>
     *
     * <p>Fires property change: SET_BACKGROUND_PAINT.</p>
     * <p>Fires property change: SET_BACKGROUND_TILE.</p>
     */
    public final void clearBothBackgrounds() {
        setBackgroundPaint(null);
        setBackgroundTile(null);
    }
    
    
    /**
     * <p>Sets the mutator transform to the given transform provided that
     * the given transform is invertible
     * and the paintable object supports this operation.</p>
     *
     * <p>If the given transform is not invertible, the method does nothing.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * <p><i>Remarks:</i></p>
     *
     * <p>If the paintable object maintains a mutator object that transforms
     * the paintable, then this method should set that mutator.  Moreover,
     * in this case, the method <code>getMutator</code> should return a copy
     * of that mutator.</p>
     *
     * <p>If the paintable object distributes mutation to internal data then
     * this method may or may not make sense.  The documentation of the
     * implementation should state what, if anything, is done by this method.
     * In particular, it is valid for the method <code>getMutator</code> to
     * return an identity transform if no mutator is maintained explicitly.</p>
     *
     * @param M the invertible affine transform to set as the mutator
     */
    public void setMutator(AffineTransform M) {
        if ((M == null) || (M.equals(mutator)))
            return;
        
        try {
            // test to make sure that the inverse of M exists
            AffineTransform N = M.createInverse();
            
            // if the inverse exists replace the mutator and its inverse
            mutator = M;
            inverse = N;
            
            firePropertyChange(SET_MUTATOR, null, null);
        }
        catch (NoninvertibleTransformException exception) {
            // on error do nothing
            return;
        }
    }
    
    
    /**
     * <p>Composes the current mutator on the right with the given transform
     * provided that
     * the given transform is invertible
     * and the paintable object supports this operation.</p>
     *
     * <p>If the given transform is not invertible, the method does nothing.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * <p><i>Remarks:</i></p>
     *
     * <p>Normally, the action of the new mutator will be to apply
     * the given transform and then to apply the old mutator.</p>
     *
     * <p>If the paintable object distributes mutation to internal data then
     * this method may or may not make sense.  The documentation of the
     * implementation should state what, if anything, is done by this method.</p>
     *
     * @param M the invertible affine transform to compose
     */
    public void addPreMutation(AffineTransform M) {
        if (M == null)
            return;
        
        setMutator(TransformFactory.compose(mutator, M));
    }
    
    
    
    /**
     * <p>Composes the current mutator on the left with the given transform
     * provided that the given transform is invertible.</p>
     *
     * <p>If the given transform is not invertible, the method does nothing.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * <p><i>Remarks:</i></p>
     *
     * <p>Normally, the action of the new mutator will be to apply
     * the old mutator and then to apply the given transform.</p>
     *
     * <p>This method should always have a meaningful interpretation for a
     * paintable, that is, it should always be possible to apply a mutation
     * that follows earlier mutations.  If this method is implemented in a
     * non-standard fashion, the documentation should state what is done.</p>
     *
     * @param M the invertible affine transform to compose
     */
    public void addPostMutation(AffineTransform M) {
        if (M == null)
            return;
        
        setMutator(TransformFactory.compose(M, mutator));
    }
    
    
    /**
     * <p>Applies a <code>Mutator.Strategy</code> object to the paintable
     * by constructing an affine transform using the <i>original</i> center
     * of the paintable
     * and then calling <code>setMutator</code> with this transform as the
     * argument.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * <p>This method may be implemented in a more sophisticated fashion if
     * that is appropriate for a particular paintable.</p>
     * 
     * @param strategy the mutator strategy to apply
     */
    public void setMutator(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        setMutator(strategy.mutator(getOriginalCenter()));
    }
    
    
    /**
     * <p>Applies a <code>Mutator.Strategy</code> object to the paintable
     * by constructing an affine transform using the <i>original</i> center
     * of the paintable
     * and then calling <code>addPreMutation</code> with this transform as the
     * argument.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * <p>This method may be implemented in a more sophisticated fashion if
     * that is appropriate for a particular paintable.</p>
     * 
     * @param strategy the mutator strategy to apply
    */
    public void addPreMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        addPreMutation(strategy.mutator(getOriginalCenter()));
    }
    
    
    /**
     * <p>Applies a <code>Mutator.Strategy</code> object to the paintable
     * by constructing an affine transform using the <i>mutated</i> center
     * of the paintable
     * and then calling <code>addPostMutation</code> with this transform as the
     * argument.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * <p>This method may be implemented in a more sophisticated fashion if
     * that is appropriate for a particular paintable.</p>
     * 
     * @param strategy the mutator strategy to apply
     */
    public void addPostMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        addPostMutation(strategy.mutator(getCenter()));
    }
    
    
    /**
     * <p>Returns a copy of the existing mutator transform.</p>
     *
     * <p>If the implementation other methods are overridden to distribute
     * transforms to sub-objects, then this method may validly always
     * return the identity transform.  This method is final.</p>
     *
     * @return a copy of the existing mutator transform
     */
    public final AffineTransform getMutator() {
        return new AffineTransform(mutator);
    }
    
    
    /**
     * <p>Returns a copy of the existing mutator inverse transform.</p>
     *
     * <p>If the implementation other methods are overridden to distribute
     * transforms to sub-objects, then this method may validly always
     * return the identity transform.  This method is final.</p>
     *
     * @return a copy of the existing mutator inverse transform
     */
    public final AffineTransform getMutatorInverse() {
        return new AffineTransform(inverse);
    }
    
    
    /**
     * <p>This method is implemented to do exactly the same
     * operation as the corresponding method <code>addPostMutation</code>.
     * In other words, this is simply a convenience method with a
     * short name.  This method is final.</p>
     *
     * @param M the invertible affine transform to compose
     * @see #addPostMutation(AffineTransform)
     */
    public final void mutate(AffineTransform M) {
        addPostMutation(M);
    }
    
    
    /**
     * <p>This method is implemented to do exactly the same
     * operation as the corresponding method <code>addPostMutation</code>.
     * In other words, this is simply a convenience method with a
     * short name.  This method is final.</p>
     *
     * @param strategy the mutator strategy to apply
     * @see #addPostMutation(Mutator.Strategy)
     */
    public final void mutate(Mutator.Strategy strategy) {
        addPostMutation(strategy);
    }
    
    
    /**
     * <p>Moves the paintable by a translation using the data in the point
     * specified by coordinates.</p>
     *
     * @param dx the x-coordinate of the translation
     * @param dy the y-coordinate of the translation
     */
    public void move(double dx, double dy) {
        addPostMutation(TransformFactory.translate(dx, dy));
    }
    
    
    /**
     * <p>Moves the paintable by a translation using the data in the point.</p>
     *
     * @param p the translation vector
     */
    public final void move(Point2D p) {
        if (p == null)
            return;
        
        move(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Moves the paintable by a translation in such a way that the
     * paintable center specified by <code>getCenter</code>
     * will move to the given point specified in coordinates.</p>
     *
     * @param x the x-coordinate of the desired center
     * @param y the y-coordinate of the desired center
     */
    public final void moveCenterTo(double x, double y) {
        XPoint2D p = getCenter();
        
        move(x - p.getX(), y - p.getY());
    }
    
    
    /**
     * <p>Moves the paintable by a translation in such a way that the
     * paintable center specified by <code>getCenter</code>
     * will move to the given point.</p>
     *
     * @param p the desired center
     */
    public final void moveCenterTo(Point2D p) {
        if (p == null)
            return;
        
        moveCenterTo(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Moves the paintable by a translation in such a way that the
     * paintable corner specified by <code>getCorner</code>
     * will move to the given point specified in coordinates.</p>
     *
     * @param x the x-coordinate of the desired corner
     * @param y the y-coordinate of the desired corner
     */
    public final void moveCornerTo(double x, double y) {
        XPoint2D p = getCorner();
        
        move(x - p.getX(), y - p.getY());
    }
    
    
    /**
     * <p>Moves the paintable by a translation in such a way that the
     * paintable corner specified by <code>getCorner</code>
     * will move to the given point.</p>
     *
     * @param p the desired corner
     */
    public final void moveCornerTo(Point2D p) {
        if (p == null)
            return;
        
        moveCornerTo(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Rotates the paintable about its center by the given angle
     * in degrees.</p>
     *
     * @param degrees the rotation angle in degrees
     */
    public final void rotate(double degrees) {
        XPoint2D p = getCenter();
        
        addPostMutation
            (TransformFactory.rotate(p.getX(), p.getY(), degrees));
    }
    
    
    /**
     * Reflects the paintable along the line through its center at
     * the given angle in degrees.
     *
     * @param degrees the angle in degrees of the line of reflection
     */
    public final void reflect(double degrees) {
        XPoint2D p = getCenter();
        
        addPostMutation
            (TransformFactory.reflect(p.getX(), p.getY(), degrees));
    }
    
    
    /**
     * Reflects the paintable along a horizontal axis through its center.
     */
    public final void hreflect() {
        reflect(0);
    }
    
    
    /**
     * Reflects the paintable along a vertical axis through its center.
     */
    public final void vreflect() {
        reflect(90);
    }
    
    
    /**
     * <p>Scales the paintable about its center by the given factor
     * s uniformly in all directions.</p>
     *
     * @param s the scale in all directions
     */
    public final void scale(double s) {
        XPoint2D p = getCenter();
        
        addPostMutation
            (TransformFactory.scale(p.getX(), p.getY(), 0, s, s));
    }
    
    
    /**
     * <p>Scales the paintable about its center by the given factor
     * s along the main axis at the given angle in degrees and the
     * given factor t along the perpendicular axis.</p>
     *
     * @param degrees the angle in degrees of the main scaling axis
     * @param s the scale factor along the axis at angle degrees
     * @param t the scale factor along the axis at angle degrees+90
     */
    public final void scale(double degrees, double s, double t) {
        Point2D p = getCenter();
        
        addPostMutation
            (TransformFactory.scale(p.getX(), p.getY(), degrees, s, t));
    }
    
    
    /**
     * <p>Shears the paintable about its center and with shear
     * factor s along the line at the given angle in degrees.</p>
     *
     * <p>In the special case when the center is at (0,0) and the
     * angle in degrees is 0, this corresponds to the transform:
     * (u,v) maps to (u+s*v,v), which fixes the x-axis and shears
     * parallel to that axis.</p>
     *
     * <p>In the general case, the fixed axis for the shear is the
     * line at the given angle degrees through the center.</p>
     *
     * @param degrees the angle in degrees of the fixed axis for shear
     * @param s determines the amount of shear along lines parallel to
     *        the fixed axis for shear
     */
    public final void shear(double degrees, double s) {
        Point2D p = getCenter();
        
        addPostMutation
            (TransformFactory.shear(p.getX(), p.getY(), degrees, s));
    }
    
    
    /**
     * <p>Glides (translates) the paintable along a glide line
     * at the given angle in degrees and the given glide distance.</p>
     *
     * <p>Equivalent to specifing a translation via polar coordinates
     * with the angle in degrees specified first and the distance
     * specified second. The reason for specifying degrees first here
     * is to be consistent with the usage of all other methods that
     * have degrees as a parameter.</p>
     *
     * @param degrees the angle in degrees of the line of glide
     * @param distance the translation distance along the line of glide
     */
    public final void glide(double degrees, double distance) {
        addPostMutation(TransformFactory.glide(degrees, distance));
    }
    
    
    /**
     * Glide reflects the paintable along the line through its center
     * at the given angle in degrees and the given glide distance.
     *
     * @param degrees the angle in degrees of the line of glide reflection
     * @param distance the translation distance along the line of glide reflection
     */
    public final void glidereflect(double degrees, double distance) {
        XPoint2D p = getCenter();
        
        addPostMutation
            (TransformFactory.glidereflect(p.getX(), p.getY(), degrees, distance));
    }
    
    
    /**
     * <p>Apply the linear transform given by the matrix coefficients
     * m00, m10, m01, m11 as if the transform were centered at the
     * center of the paintable.  In other words, before applying the
     * given transform, adjust it so that the center of the paintable
     * remains fixed.</p>
     *
     * <p>Note that the two coefficients of column 0 are given first
     * and the two coefficients of column 1 are given next.</p>
     *
     * @param m00 the matrix coefficient in row 0 column 0
     * @param m10 the matrix coefficient in row 1 column 0
     * @param m01 the matrix coefficient in row 0 column 1
     * @param m11 the matrix coefficient in row 1 column 1
     */ 
    public final void lineartransform
        (double m00, double m10, double m01, double m11)
    {
        XPoint2D p = getCenter();
        
        addPostMutation
            (TransformFactory.centeredTransform
                (p.getX(), p.getY(), m00, m10, m01, m11, 0, 0));
    }
    
    
    /**
     * <p>Apply the affine transform given by the matrix coefficients
     * m00, m10, m01, m11, m02, m12 as if the transform were centered
     * at the center of the paintable.</p>
     *
     * <p>Note that the two coefficients of column 0 are given first,
     * the two coefficients of column 1 are given next, and
     * the two coefficients of column 2 are given last.</p>
     *
     * @param m00 the matrix coefficient in row 0 column 0
     * @param m10 the matrix coefficient in row 1 column 0
     * @param m01 the matrix coefficient in row 0 column 1
     * @param m11 the matrix coefficient in row 1 column 1
     * @param m02 the matrix coefficient in row 0 column 2
     * @param m12 the matrix coefficient in row 1 column 2
     */ 
    public final void affinetransform
        (double m00, double m10, double m01, double m11, double m02, double m12)
    {
        XPoint2D p = getCenter();
        
        addPostMutation
            (TransformFactory.centeredTransform
                (p.getX(), p.getY(), m00, m10, m01, m11, m02, m12));
    }
    
    
    /**
     * <p>Paints onto a <code>Graphics</code> context using information
     * from this object but without the use of the mutator transform.</p>
     *
     * <p>A derived class must supply this method which is the foundation
     * for the <code>paint</code> methods that use the mutator.</p>
     *
     * <p>Normally this method will be called by the <code>paint</code>
     * methods.  Hence the designer of this method may assume that issues
     * such as visibility, opacity, clipping, antialiasing, and mutation
     * will be taken care of by the <code>paint</code> methods.  Therefore,
     * this method need only paint what must be painted with no adjustments.</p>
     *
     * <p>When this method call is complete, the internal state of g should
     * be unchanged.  The override method must ensure this invariant.</p>
     * 
     * @param g the graphics context on which to paint
     */
    public abstract void originalPaint(Graphics g);
    
    
    /**
     * <p>Returns a copy of the 2-dimensional bounds of the original
     * paintable prior to any mutation.</p>
     *
     * <p>If the value of <code>getDefaultOriginalBounds2D</code>
     * is non-<code>null</code>, then returns this value.</p>
     *
     * <p>If the value of <code>getActualBounds2D</code>
     * is non-<code>null</code>, then returns this value.</p>
     *
     * <p>Otherwise, returns <code>new XRect()</code>.</p>
     *
     * @return a copy of the 2-dimensional bounds of the original paintable
     */
    public final XRect getOriginalBounds2D() {
        // get the default bounds
        XRect bounds = getDefaultOriginalBounds2D();
        
        if (bounds != null)
            return bounds;
        
        // get the actual bounds
        bounds = getActualBounds2D();
        
        if (bounds != null)
            return bounds;
        
        return new XRect();
    }
    
    
    /**
     * <p>Returns the actual bounds of the original paintable or
     * <code>null</code> if the paintable is effectively empty.</p>
     *
     * <p>A derived class must supply this method.</p>
     */
    public abstract XRect getActualBounds2D();
    
    
    /**
     * <p>Returns a copy of the original center of the paint region.</p>
     *
     * <p>In this class, the method is implemented as follows.</p>
     *
     * <p>If the value of <code>getDefaultOriginalCenter</code>
     * is non-<code>null</code>, then returns this value.</p>
     *
     * <p>Otherwise, returns the center of the rectangle returned
     * by the method <code>getOriginalBounds2D</code>.</p>
     *
     * <p>A derived class may override this method if the object
     * geometry suggests that a different center is more appropriate.
     * However, if the user has set a default original center, this
     * center must be returned by an override method.</p>
     *
     * <p>This method must not return <code>null</code>.</p>
     *
     * @return a copy of the center of the paint region
     */
    public XPoint2D getOriginalCenter() {
        // get default original center
        XPoint2D center = getDefaultOriginalCenter();
        
        if (center != null)
            return center;
        
        // get the center of the original bounds
        XRect bounds = getOriginalBounds2D();
        
        double x = bounds.getCenterX();
        double y = bounds.getCenterY();
        
        return new XPoint2D(x, y);
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the
     * original paintable without mutation.</p>
     *
     * <p>A derived class must supply this method.</p>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     * @return whether or not a specified point is inside the
     *         original paintable
     */
    public abstract boolean originalContains(double x, double y);
    
    
    /**
     * <p>Tests if a specified point is inside the
     * original paintable without mutation.</p>
     *
     * @param  p a specified <code>Point2D</code>
     * @return whether or not a specified point is inside the
     *         original paintable
     */
    public final boolean originalContains(Point2D p) {
        if (p == null)
            return false;
        
        return originalContains(p.getX(), p.getY());
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is possibly inside the
     * original paintable.</p>
     *
     * <p>This method returns <code>true</code> if and only if:</p>
     *
     * <UL>
     *   <LI>The method <code>isVisible</code> returns <code>true</code>.</LI>
     *   <LI>The point is in the rectangle <code>getOriginalBounds2D</code>.</LI>
     * </UL>
     *
     * <p>This is a possible helper method for implementing the method
     * <code>originalContains</code>.  Its use is up to the designer
     * of a derived class.</p>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     * @return whether or not a specified point is possibly inside the paintable
     */
    protected final boolean possiblyContains(double x, double y) {
        return visible && getOriginalBounds2D().contains(x, y);
    }
    
    
    /**
     * <p>Applies the current opacity to calculate and set a net opacity.</p>
     *
     * <p>The following conditions must hold:</p>
     *
     * <UL>
     *   <LI>The current opacity of this paintable is less than 1.</LI>
     *   <LI>The given graphics context either has no <code>Composite</code> set
     *       or has a <code>Composite</code> of type <code>AlphaComposite</code>
     *       whose rule is <code>SRC_OVER</code>.
     * </UL>
     *
     * <p>In the case that the graphics context has a <code>Composite</code> set
     * of type <code>AlphaComposite</code> with rule <code>SRC_OVER</code>, then
     * the net opacity is the product of the current opacity of this object and
     * the alpha value of the <code>AlphaComposite</code>.</p>
     *
     * <p>Otherwise the net opacity equals the current opacity of this object.</p>
     *
     * @param h the graphics context whose opacity will be changed
     */
    public final void applyOpacity(Graphics2D h) {
        // return if there is no need to change opacity
        if (opacity >= 1.0f)
            return;
        
        // check the current composite for h and compute net opacity
        Composite composite = h.getComposite();
        float netOpacity = opacity;
        
        if (composite instanceof AlphaComposite) {
            AlphaComposite alphaComposite = (AlphaComposite) composite;
            
            // return if caller has set a compositing rule != SRC_OVER
            if (alphaComposite.getRule() != AlphaComposite.SRC_OVER)
                return;
            
            // set the net opacity via the product rule
            netOpacity *= alphaComposite.getAlpha();
        }
        else {
            if (composite != null)
                // return if caller has set a special Composite class
                return;
        }
        
        // set a new composite rule
        h.setComposite
            (AlphaComposite.getInstance(AlphaComposite.SRC_OVER, netOpacity));
    }
    
    
    /**
     * <p>Sets the default Bounds2D rectangle, that is,
     * the default for computation of <code>getBounds2D</code>.</p>
     *
     * <p>It is valid to set the default Bounds2D rectangle to
     * <code>null</code> to force a computation of the bounds.
     * Setting the default Bounds2D rectangle to a rectangle
     * with zero width or height is equivalent to setting it
     * to <code>null</code>.</p>
     *
     * <p>Fires property change: SET_DEFAULT_BOUNDS2D.</p>
     * 
     * @param rectangle the default Bounds2D rectangle
     */
    public final void setDefaultBounds2D(Rectangle2D rectangle) {
        if (rectangle == null) {
            if (defaultBounds2D == null)
                return;
            
            defaultBounds2D = null;
        }
        else {
            double x = rectangle.getX();
            double y = rectangle.getY();
            double w = rectangle.getWidth();
            double h = rectangle.getHeight();
            
            defaultBounds2D = new XRect(x, y, w, h);
        }
        
        firePropertyChange(SET_DEFAULT_BOUNDS2D, null, null);
    }
    
    
    /**
     * <p>Returns a copy of the default Bounds2D rectangle, that is,
     * the default for computation of <code>getBounds2D</code>.</p>
     *
     * <p>Returns <code>null</code> to signal no setting.</p>
     *
     * @return a copy of the default Bounds2D rectangle
     */
    public final XRect getDefaultBounds2D() {
        if (defaultBounds2D == null)
            return null;
        else
            return new XRect(defaultBounds2D);
    }
    
    
    /**
     * <p>Sets the default center, that is,
     * the default for computation of <code>getCenter</code>.</p>
     *
     * <p>It is valid to set the default center to
     * <code>null</code> to force a computation of the center.</p>
     *
     * <p>Fires property change: SET_DEFAULT_CENTER.</p>
     * 
     * @param center the default center
     */
    public final void setDefaultCenter(Point2D center) {
        if (center == null) {
            if (defaultCenter == null)
                return;
            
            defaultCenter = null;
        }
        else {
            double x = center.getX();
            double y = center.getY();
            
            defaultCenter = new XPoint2D(x, y);
        }
        
        firePropertyChange(SET_DEFAULT_CENTER, null, null);
    }
    
    
    /**
     * <p>Returns a copy of the default center, that is,
     * the default for computation of <code>getCenter</code>.</p>
     *
     * <p>Returns <code>null</code> to signal no setting.</p>
     *
     * @return a copy of the default center
     */
    public final XPoint2D getDefaultCenter() {
        if (defaultCenter == null)
            return null;
        else
            return new XPoint2D(defaultCenter);
    }
    
    
    /**
     * <p>Sets the default original Bounds2D rectangle, that is,
     * the default for computation of <code>getOriginalBounds2D</code>.</p>
     *
     * <p>It is valid to set the default original Bounds2D rectangle
     * to <code>null</code> to force a computation of the bounds.
     * Setting the default original Bounds2D rectangle to a rectangle
     * with zero width or height is equivalent to setting it
     * to <code>null</code>.</p>
     *
     * <p>Fires property change: SET_DEFAULT_ORIGINAL_BOUNDS2D.</p>
     * 
     * @param rectangle the default original Bounds2D rectangle
     */
    public final void setDefaultOriginalBounds2D(Rectangle2D rectangle) {
        if (rectangle == null) {
            if (defaultOriginalBounds2D == null)
                return;
            
            defaultOriginalBounds2D = null;
        }
        else {
            double x = rectangle.getX();
            double y = rectangle.getY();
            double w = rectangle.getWidth();
            double h = rectangle.getHeight();
            
            defaultOriginalBounds2D = new XRect(x, y, w, h);
        }
        
        firePropertyChange(SET_DEFAULT_ORIGINAL_BOUNDS2D, null, null);
    }
    
    
    /**
     * <p>Returns a copy of the default original Bounds2D rectangle, that is,
     * the default for computation of <code>getOriginalBounds2D</code>.</p>
     *
     * <p>Returns <code>null</code> to signal no setting.</p>
     *
     * @return a copy of the default original Bounds2D rectangle
     */
    public final XRect getDefaultOriginalBounds2D() {
        if (defaultOriginalBounds2D == null)
            return null;
        else
            return new XRect(defaultOriginalBounds2D);
    }
    
    
    /**
     * <p>Sets the default original center, that is,
     * the default for computation of <code>getOriginalCenter</code>.</p>
     *
     * <p>It is valid to set the default original center to
     * <code>null</code> to force a computation of the center.</p>
     *
     * <p>Fires property change: SET_DEFAULT_ORIGINAL_CENTER.</p>
     * 
     * @param center the default original center
     */
    public final void setDefaultOriginalCenter(Point2D center) {
        if (center == null) {
            if (defaultOriginalCenter == null)
                return;
            
            defaultOriginalCenter = null;
        }
        else {
            double x = center.getX();
            double y = center.getY();
            
            defaultOriginalCenter = new XPoint2D(x, y);
        }
        
        firePropertyChange(SET_DEFAULT_ORIGINAL_CENTER, null, null);
    }
    
    
    /**
     * <p>Returns a copy of the default original center, that is,
     * the default for computation of <code>getOriginalCenter</code>.</p>
     *
     * <p>Returns <code>null</code> to signal no setting.</p>
     *
     * @return a copy of the default original center
     */
    public final XPoint2D getDefaultOriginalCenter() {
        if (defaultOriginalCenter == null)
            return null;
        else
            return new XPoint2D(defaultOriginalCenter);
    }
    
    
    /** Property Change. */
    
    /**
     * <p>Add a <code>PropertyChangeListener</code> to the listener list.
     * The listener is registered for all properties.</p>
     *
     * @param listener the PropertyChangeListener to be added
     */
    public final void addPropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(listener);
    }

    /**
     * <p>Add a <code>PropertyChangeListener</code> to the listener list for a
     * specific property.  The listener will be invoked only when a call on
     * <code>firePropertyChange</code> names that specific property.</p>
     *
     * @param propertyName the name of the property to listen on 
     * @param listener the PropertyChangeListener to be added
     */
    public final void addPropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(propertyName, listener);
    }

    /**
     * <p>Add all items in the given <code>PropertyChangeListener</code> array
     * to the listener list.  These items are registered for all properties.</p>
     *
     * @param listeners the PropertyChangeListener array to be added
     */
    public final void addPropertyChangeListeners(PropertyChangeListener[] listeners)
    {
        if (listeners == null)
            return;
        
        int length = listeners.length;
        
        for (int i = 0; i < length; i++)
            addPropertyChangeListener(listeners[i]);
    }
    
    
    /**
     * <p>Add all items in the given <code>PropertyChangeListener</code> array
     * to the listener list for a specific property.  These items will be invoked
     * only when a call on <code>firePropertyChange</code> names that specific
     * property.</p>
     *
     * @param listeners the PropertyChangeListener array to be added
     */
    public final void addPropertyChangeListeners(
        String propertyName,
        PropertyChangeListener[] listeners)
    {
        if (listeners == null)
            return;
        
        int length = listeners.length;
        
        for (int i = 0; i < length; i++)
            addPropertyChangeListener(propertyName, listeners[i]);
    }
    
    
    /**
     * <p>Remove a <code>PropertyChangeListener</code> from the listener list.
     * This removes a <code>PropertyChangeListener</code> that was registered
     * for all properties.</p>
     *
     * @param listener the PropertyChangeListener to be removed
     */
    public final void removePropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(listener);
    }

    /**
     * <p>Remove a <code>PropertyChangeListener</code> for a specific property.</p>
     *
     * @param propertyName the name of the property that was listened on 
     * @param listener the PropertyChangeListener to be removed
     */
    public final void removePropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(propertyName, listener);
    }
    
    
    /**
     * <p>Returns an array of all listeners that were added to this object.</p>
     *
     * @return an array with all listeners
     */
    public final PropertyChangeListener[] getPropertyChangeListeners() {
        return changeAdapter.getPropertyChangeListeners();
    }
    
    
    /**
     * <p>Returns an array of all listeners that were added to this object
     * and associated with the named property.</p>
     *
     * @param propertyName the name of the property to seek 
     */
    public final PropertyChangeListener[]
        getPropertyChangeListeners(String propertyName)
    {
        return changeAdapter.getPropertyChangeListeners(propertyName);
    }
    
    
    /**
     * <p>Check if there are any listeners for a specific property.</p>
     *
     * @param propertyName the name of the property to check 
     */
    public final boolean hasListeners(String propertyName) {
        return changeAdapter.hasListeners(propertyName);
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal and non-null.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        Object oldValue,
        Object newValue)
    {
        changeAdapter.firePropertyChange(propertyName, oldValue, newValue);
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        boolean oldValue,
        boolean newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange(propertyName, oldValue, newValue);
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        char oldValue,
        char newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Character(oldValue), new Character(newValue));
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        byte oldValue,
        byte newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Byte(oldValue), new Byte(newValue));
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        short oldValue,
        short newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Short(oldValue), new Short(newValue));
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        int oldValue,
        int newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange(propertyName, oldValue, newValue);
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        long oldValue,
        long newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Long(oldValue), new Long(newValue));
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        float oldValue,
        float newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Float(oldValue), new Float(newValue));
    }
    
    
    /**
     * <p>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</p>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        double oldValue,
        double newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Double(oldValue), new Double(newValue));
    }
    
    
    /**
     * <p>Fire an existing <code>PropertyChangeEvent</code> to any registered
     * listeners.  No event is fired if the given event's old and new values
     * are equal and non-null.</p>
     *
     * @param evt the PropertyChangeEvent object
     */
    public final void firePropertyChange(PropertyChangeEvent evt) {
        changeAdapter.firePropertyChange(evt);
    }
    
    
    /**
     * <p>Returns the <code>PropertyChangeForwardingListener</code> that
     * will forward the property change events it receives to this object.</p>
     *
     * @return the forwarding listener
     */
    public final PropertyChangeForwardingListener getForwardingListener() {
        return forwardingListener;
    }
    

    /**
     * Add the forwarding listener as a property change listener
     * for the given object if the object supports property change.
     *
     * @param object the object that should add the forwarding listener
     */
    public final void addForwardingListener(Object object) {
        if (object instanceof SupportsPropertyChange) {
            SupportsPropertyChange spc = (SupportsPropertyChange) object;
            spc.addPropertyChangeListener(getForwardingListener());
        }
    }
    
    
    /**
     * Remove the forwarding listener as a property change listener
     * for the given object if the object supports property change.
     *
     * @param object the object that should remove the forwarding listener
     */
    public final void removeForwardingListener(Object object) {
        if (object instanceof SupportsPropertyChange) {
            SupportsPropertyChange spc = (SupportsPropertyChange) object;
            spc.removePropertyChangeListener(getForwardingListener());
        }
    }
    
    
    /**
     * Remove the forwarding listener as a property change listener
     * for the old object if the old object supports property change
     * and add the forwarding listener as a property change listener
     * for the new object if the new object supports property change.
     *
     * @param oldobject the old object that should remove the forwarding listener
     * @param newobject the new object that should add the forwarding listener
     */
    public final void removeAndAddForwardingListener(Object oldobject, Object newobject)
    {
        removeForwardingListener(oldobject);
        addForwardingListener(newobject);
    }    
    
}
