/*
 * @(#)PaintableSequence.java   2.3.4  6 February 2005
 *
 * Copyright 2005
 * 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 java.awt.*;
import java.awt.geom.*;
import java.util.*;

/**
 * <p>A <code>PaintableSequence</code> encapsulates a sequence of
 * <code>Paintable</code> objects.</p>
 *
 * <p>If a <code>Paintable</code> implements <code>MutatablePaintable</code>,
 * then it is installed in the sequence as is.  Otherwise, it is wrapped using
 * a <code>MutatableWrapper</code> prior to being installed.  As a consequence,
 * all items in the internal sequence implement <code>MutatablePaintable</code>.</p>
 *
 * <p>Whenever there is a change in the overall mutator for the sequence, this
 * change is immediately applied to the individual items in the sequence.  As a
 * consequence, the individual sequence items maintain their own knowledge of
 * the net mutation that must be applied in paint operations.  In particular,
 * as far as geometry is concerned, an individual sequence item may be viewed
 * either as part of the sequence or as a stand-alone <code>MutatablePaintable
 * </code>.  This design decision enables interactive manipulation of sequence
 * items to be handled in a natural fashion.</p>
 *
 * @author  Richard Rasala
 * @version 2.3.4
 * @since   2.3
 */
public class PaintableSequence
    extends AbstractMutatablePaintable
{
    /** Bound property name to set, add, or append one or more paintables. */
    public static String SET_PAINTABLE     = "set.paintable";
    
    /** Bound property name to remove one or more paintables. */
    public static String REMOVE_PAINTABLE  = "remove.paintable";
    
    
    /** The vector to hold the mutatable paintable sequence. */
    private Vector paintablesequence = new Vector(16);
    
    /** The mutator strategy usage for the paintable sequence. */
    private Mutator.StrategyUsage usage = Mutator.MUTATE_AS_ITEMS;
    
    
    /**
     * <p>The default constructor with an empty sequence and the mutator strategy
     * usage set to <code>Mutator.MUTATE_AS_ITEMS</code>.</p>
     *
     * @see #PaintableSequence(Paintable[])
     * @see #PaintableSequence(Paintable[], Mutator.StrategyUsage)
     */
    public PaintableSequence() {
        this(null, null);
    }
    
    
    /**
     * <p>The constructor that sets up the paintable sequence using an initial
     * array of <code>Paintable</code> objects.</p>
     *
     * <p>If the array is <code>null</code> then the sequence is set to empty.</p>
     *
     * <p>The array may contain <code>null</code> objects that will, in effect,
     * reserve positions that may later be filled using <code>setPaintable</code>.</p>
     *
     * @param paintables the array of paintables to set
     * @see #PaintableSequence()
     * @see #PaintableSequence(Paintable[], Mutator.StrategyUsage)
     * @see #appendSequence(Paintable[])
     */
    public PaintableSequence(Paintable[] paintables) {
        this(paintables, null);
    }
    
    
    /**
     * <p>The constructor that sets up the paintable sequence using an initial
     * array of <code>Paintable</code> objects
     * and the given choice of mutator strategy usage.</p>
     *
     * <p>If the array is <code>null</code> then the sequence is set to empty.</p>
     *
     * <p>The array may contain <code>null</code> objects that will, in effect,
     * reserve positions that may later be filled using <code>setPaintable</code>.</p>
     *
     * @param paintables the array of paintables to set
     * @param usage the mutator strategy usage
     * @see #PaintableSequence()
     * @see #PaintableSequence(Paintable[])
     * @see #appendSequence(Paintable[])
     * @see #setMutatorStrategyUsage(Mutator.StrategyUsage)
     */
    public PaintableSequence
        (Paintable[] paintables, Mutator.StrategyUsage usage)
    {
        appendSequence(paintables);
        setMutatorStrategyUsage(usage);
    }
    
    
    /**
     * <p>Removes all objects from this paintable sequence.</p>
     *
     * <p>If the existing sequence is non-empty,
     * fires property change: REMOVE_PAINTABLE.</p>
     */
    public final void clear() {
        int N = paintablesequence.size();
        
        if (N == 0)
            return;
        
        for (int i = 0; i < N; i++)
            removeForwardingListener(paintablesequence.get(i));
        
        paintablesequence.clear();
        
        firePropertyChange(REMOVE_PAINTABLE, null, null);
    }
    
    
    /**
     * <p>Installs the given array of <code>Paintable</code> objects into the
     * paintable sequence and removes the existing objects in the sequence.</p>
     *
     * <p>Each <code>Paintable</code> is installed using the protected wrapper
     * method <code>wrapPaintable</code>.</p>
     *
     * <p>If the existing sequence is non-empty,
     * fires property change: REMOVE_PAINTABLE.</p>
     *
     * <p>If the given paintables array is non-<code>null</code> and of
     * length greater than zero,
     * fires property change: SET_PAINTABLE.</p>
     * 
     * <p>If the sequence of paintables being set are exactly the same as the
     * sequence of paintables currently installed, then this method will do
     * nothing.</p>
     *
     * @param paintables the array of paintables to set
     * @see #clear()
     * @see #appendSequence(Paintable [])
     * @see #wrapPaintable(Paintable)
     */
    public final void setSequence(Paintable[] paintables) {
        if (paintables == null) {
            clear();
            return;
        }
        
        // check for equality of current data and given paintables
        int M = paintablesequence.size();
        int N = paintables.length;
        
        if (M == N) {
            boolean equal = true;
            
            for (int i = 0; (i < M) && equal; i++)
                equal = (getMutatablePaintable(i) == paintables[i]);
            
            if (equal)
                return;
        }
        
        clear();
        appendSequence(paintables);
    }
    
    
    /**
     * <p>Appends the given array of <code>Paintable</code> objects to the end
     * of the paintable sequence.</p>
     *
     * <p>Each <code>Paintable</code> is installed using the protected wrapper
     * method <code>wrapPaintable</code>.</p>
     *
     * <p>If the given paintables array is non-<code>null</code> and of
     * length greater than zero,
     * fires property change: SET_PAINTABLE.</p>
     * 
     * @param paintables the array of paintables to append
     * @see #setSequence(Paintable [])
     * @see #wrapPaintable(Paintable)
     */
    public final void appendSequence(Paintable[] paintables) {
        if (paintables == null)
            return;
        
        int N = paintables.length;
        
        if (N == 0)
            return;
        
        for (int i = 0; i < N; i++) {
            MutatablePaintable mp = wrapPaintable(paintables[i]);
            addForwardingListener(mp);
            paintablesequence.add(mp);
        }
        
        firePropertyChange(SET_PAINTABLE, null, null);
    }
    
    
    /**
     * <p>Installs a <code>Paintable</code> as a <code>MutatablePaintable</code>
     * at the given index in the paintable sequence.  The object currently at
     * the position in the sequence is replaced.</p>
     *
     * <p>The <code>Paintable</code> is installed using the protected wrapper
     * method <code>wrapPaintable</code>.</p>
     *
     * <p>This method does nothing if the index is not in [0, length).</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param index the position in the sequence
     * @param paintable the paintable to set at the position
     * @see #addPaintable(int, Paintable)
     * @see #appendPaintable(Paintable)
     * @see #removePaintable(int)
     * @see #getMutatablePaintable(int)
     * @see #wrapPaintable(Paintable)
     */
    public final void setPaintable(int index, Paintable paintable) {
        if ((index < 0) || (index >= paintablesequence.size()))
            return;
        
        MutatablePaintable oldmp = getMutatablePaintable(index);
        MutatablePaintable newmp = wrapPaintable(paintable);
        
        if (newmp == oldmp)
            return;
        
        removeAndAddForwardingListener(oldmp, newmp);
        
        paintablesequence.set(index, newmp);
        
        firePropertyChange(SET_PAINTABLE, null, null);
    }
    
    
    /**
     * <p>Installs a <code>Paintable</code> as a <code>MutatablePaintable</code>
     * at the given index in the paintable sequence.  All current elements in
     * the sequence from that index onward are displaced by one index position.</p>
     *
     * <p>The <code>Paintable</code> is installed using the protected wrapper
     * method <code>wrapPaintable</code>.</p>
     *
     * <p>This method does nothing if the index is not in [0, length].  If the index
     * equals 0, this method is equivalent to <code>addPaintableAtTop</code>.  If the
     * index equals length, this method is equivalent to <code>appendPaintable</code>.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param index the position in the sequence
     * @param paintable the paintable to set at the position
     * @see #setPaintable(int, Paintable)
     * @see #appendPaintable(Paintable)
     * @see #removePaintable(int)
     * @see #getMutatablePaintable(int)
     * @see #wrapPaintable(Paintable)
     */
    public final void addPaintable(int index, Paintable paintable) {
        if ((index < 0) || (index > paintablesequence.size()))
            return;
        
        MutatablePaintable mp = wrapPaintable(paintable);
        
        addForwardingListener(mp);
        
        paintablesequence.add(index, mp);
        
        firePropertyChange(SET_PAINTABLE, null, null);
    }
    
    
    /**
     * <p>Installs a <code>Paintable</code> as a <code>MutatablePaintable</code>
     * at the topmost position of the paintable sequence (index 0).  All current
     * elements in the sequence are displaced by one index position.</p>
     *
     * <p>The <code>Paintable</code> is installed using the protected wrapper
     * method <code>wrapPaintable</code>.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param paintable the paintable to add at the top
     * @see #setPaintable(int, Paintable)
     * @see #appendPaintable(Paintable)
     * @see #removePaintable(int)
     * @see #getMutatablePaintable(int)
     * @see #wrapPaintable(Paintable)
     */
    public final void addPaintableAtTop(Paintable paintable) {
        addPaintable(0, paintable);
    }
    
    
    /**
     * <p>Appends a <code>Paintable</code> as a <code>MutatablePaintable</code>
     * at the end of the paintable sequence.</p>
     *
     * <p>The <code>Paintable</code> is installed using the protected wrapper
     * method <code>wrapPaintable</code>.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param paintable the paintable to append
     * @see #setPaintable(int, Paintable)
     * @see #addPaintable(int, Paintable)
     * @see #removePaintable(int)
     * @see #getMutatablePaintable(int)
     * @see #wrapPaintable(Paintable)
     */
    public final void appendPaintable(Paintable paintable) {
        addPaintable(paintablesequence.size(), paintable);
    }
    
    
    /**
     * <p>Removes the <code>MutatablePaintable</code> at the given index
     * in the paintable sequence.  All objects in the sequence at higher
     * index positions are displaced to one index position lower.</p>
     *
     * <p>This method does nothing if the index is not in [0, length).</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     * 
     * @param index the position in the sequence
     * @see #setPaintable(int, Paintable)
     * @see #addPaintable(int, Paintable)
     * @see #appendPaintable(Paintable)
     * @see #getMutatablePaintable(int)
     */
    public final void removePaintable(int index) {
        if ((index < 0) || (index >= paintablesequence.size()))
            return;
        
        removeForwardingListener(paintablesequence.get(index));
        
        paintablesequence.remove(index);
        
        firePropertyChange(REMOVE_PAINTABLE, null, null);
    }
    
    
    /**
     * <p>Removes the given <code>MutatablePaintable</code> from the
     * paintable sequence.  All objects in the sequence at higher
     * index positions are displaced to one index position lower.</p>
     *
     * <p>This method does nothing if the <code>MutatablePaintable</code>
     * is not in the sequence.</p>
     *
     * <p>Fires property change: REMOVE_PAINTABLE.</p>
     * 
     * @param index the position in the sequence
     * @see #setPaintable(int, Paintable)
     * @see #addPaintable(int, Paintable)
     * @see #appendPaintable(Paintable)
     * @see #getMutatablePaintable(int)
     */
    public final void removePaintable(MutatablePaintable mp) {
        removePaintable(getIndex(mp));
    }
    
    
    /**
     * <p>Returns the <code>MutatablePaintable</code> at the given index in the
     * sequence.</p>
     *
     * <p>This method returns <code>null</code> if index is not in [0, length).</p>
     *
     * @param index the position in the sequence
     * @return the mutatable paintable to get at the position
     * @see #setPaintable(int, Paintable)
     * @see #addPaintable(int, Paintable)
     * @see #appendPaintable(Paintable)
     * @see #removePaintable(int)
     */
    public final MutatablePaintable getMutatablePaintable(int index) {
        if ((index < 0) || (index >= paintablesequence.size()))
            return null;
        
        return (MutatablePaintable) paintablesequence.get(index);
    }
    
    
    /**
     * <p>Returns the index in the paintable sequence of the given
     * <code>MutatablePaintable</code>.</p>
     *
     * <p>The index is also known as the z-index since it represents
     * the order in which items will appear when painted with index
     * 0 in the topmost position.</p>
     *
     * <p>Returns -1 if the given <code>MutatablePaintable</code> is
     * <code>null</code> or is not in the paintable sequence.</p>
     *
     * @param the mutatable paintable to find
     * @return the index of the mutatable paintable
     */
    public final int getIndex(MutatablePaintable mp) {
        int index = -1;
        
        if (mp == null)
            return index;
        
        int length = length();
        
        for (int i = 0; i < length; i++) {
            if (mp == getMutatablePaintable(i)) {
                index = i;
                break;
            }
        }
        
        return index;
    }
    
    
    /**
     * Returns the length of the paintable sequence.
     *
     * @return the length of the paintable sequence
     */
    public final int length() {
        return paintablesequence.size();
    }
    
    
    /**
     * <p>Shift the paintable at index i to index j and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the indices i and j are not in the
     * range [0, length) and also does nothing if i == j.</p>
     *
     * @param i the current position of a paintable
     * @param j the new position of the shifted paintable
     */
    public final void shiftPaintable(int i, int j) {
        int length = length();
        
        if ((i < 0) || (i >= length))
            return;
        
        if ((j < 0) || (j >= length))
            return;
        
        if (i == j)
            return;
        
        // remove item at i and then add item at j
        // if j > i must first adjust j
        
        if (j > i) j--;
        
        MutatablePaintable mp = getMutatablePaintable(i);
        
        removePaintable(i);
        addPaintable(j, mp);
    }
    
    
    /**
     * <p>Shift the paintable at index i to the top and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == 0.</p>
     *
     * @param i the current position of a paintable
     */
    public final void shiftPaintableToTop(int i) {
        shiftPaintable(i, 0);
    }
    
    
    /**
     * <p>Shift the paintable at index i to the bottom and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == (length-1).</p>
     *
     * @param i the current position of a paintable
     */
    public final void shiftPaintableToBottom(int i) {
        shiftPaintable(i, length() - 1);
    }
    
    
    /**
     * <p>Shift the paintable at index i one index closer to the
     * top.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == 0.</p>
     *
     * @param i the current position of a paintable
     */
    public final void shiftPaintableUp(int i) {
        shiftPaintable(i, i-1);
    }
    
    
    /**
     * <p>Shift the paintable at index i one index closer to the
     * bottom.</p>
     *
     * <p>Does nothing if the index i is not in the range
     * [0, length) and also does nothing if i == (length-1).</p>
     *
     * @param i the current position of a paintable
     */
    public final void shiftPaintableDown(int i) {
        shiftPaintable(i, i+1);
    }
    
    
    /**
     * <p>Shift the given mutatable paintable to index i and shift
     * all paintables in between as needed.</p>
     *
     * <p>Does nothing if the given mutatable paintable is not in
     * the sequence.</p>
     *
     * <p>Does nothing if index i is not in the range [0, length)
     * and also does nothing if the given mutatable paintable is
     * already at index i.</p>
     *
     * @param mp the mutatable paintable to shift
     * @param i the new position of the shifted paintable
     */
    public final void shiftPaintable(MutatablePaintable mp, int i) {
        shiftPaintable(getIndex(mp), i);
    }
    
    
    /**
     * <p>Shift the given mutatable paintable to the top and shift
     * other paintables as needed.</p>
     *
     * <p>Does nothing if the given mutatable paintable is not in
     * the sequence or is already at the top.</p>
     *
     * @param mp the mutatable paintable to shift
     */
    public final void shiftPaintableToTop(MutatablePaintable mp) {
        shiftPaintable(getIndex(mp), 0);
    }
    
    
    /**
     * <p>Shift the given mutatable paintable to the bottom and
     * shift other paintables as needed.</p>
     *
     * <p>Does nothing if the given mutatable paintable is not in
     * the sequence or is already at the bottom.</p>
     *
     * @param mp the mutatable paintable to shift
     */
    public final void shiftPaintableToBottom(MutatablePaintable mp) {
        shiftPaintable(getIndex(mp), length() - 1);
    }
    
    
    /**
     * <p>Shift the given mutatable paintable one index closer to the
     * top and shift other paintables as needed.</p>
     *
     * <p>Does nothing if the given mutatable paintable is not in
     * the sequence or is already at the top.</p>
     *
     * @param mp the mutatable paintable to shift
     */
    public final void shiftPaintableUp(MutatablePaintable mp) {
        int index = getIndex(mp);
        shiftPaintable(index, index - 1);
    }
    
    
    /**
     * <p>Shift the given mutatable paintable one index closer to the
     * bottom and shift other paintables as needed.</p>
     *
     * <p>Does nothing if the given mutatable paintable is not in
     * the sequence or is already at the bottom.</p>
     *
     * @param mp the mutatable paintable to shift
     */
    public final void shiftPaintableDown(MutatablePaintable mp) {
        int index = getIndex(mp);
        shiftPaintable(index, index + 1);
    }
    
    
    /**
     * Returns an array with the <code>MutatablePaintable</code> items in
     * this paintable sequence.
     *
     * @return the sequence items in a <code>MutatablePaintable</code> array
     */
    public final MutatablePaintable[] toArray() {
        return (MutatablePaintable[])
            paintablesequence.toArray(new MutatablePaintable[0]);
    }
    
    
    /**
     * <p>Paints onto a <code>Graphics</code> context g using information
     * from the paintable sequence and from its individual items.</p>
     *
     * <p>This method applies the bounds and opacity for the overall sequence
     * and then paints each individual item.  The order of painting is from
     * index (length - 1) to index 0, that is, the item at index 0 is the
     * topmost entity that is painted.</p>
     *
     * <p>If the paintable sequence is empty, this method will not paint.</p>
     *
     * @param g the graphics context on which to paint
     */
    public final void paint(Graphics g) {
        if ((g == null) || !isVisible())
            return;
        
        int N = paintablesequence.size();
        
        if (N == 0)
            return;
        
        Graphics2D h = getPreparedGraphics2D(g);
        
        for (int i = (N - 1); i >= 0; i--) {
            MutatablePaintable mp = getMutatablePaintable(i);
            
            if (mp != null)
                mp.paint(h);
        }
    }
    
    
    /**
     * <p>Returns the bounds of the paintable based on the default settings or on
     * more detailed computations.</p>
     *
     * <p>If the value of <code>getDefaultBounds2D</code> is non-<code>null</code>,
     * then this value is returned.  In particular, by setting the default Bounds2D
     * rectangle, this paintable can reserve space even if it is currently empty.</p>
     *
     * <p>If the value of <code>getDefaultBounds2D</code> is <code>null</code>,
     * then this method returns the value of <code>getOriginalBounds2D</code>.
     * This rule makes sense since mutator transforms are automatically applied
     * to the individual objects in a paintable sequence and therefore there is
     * no difference between mutated bounds and original bounds in this case.</p>
     *
     * @see AbstractMutatablePaintable#getBounds2D()
     * @see #getOriginalBounds2D()
     * @see AbstractPaintable#setDefaultBounds2D(Rectangle2D)
     * @see AbstractPaintable#getDefaultBounds2D()
     */
    public final Rectangle2D getBounds2D() {
        Rectangle2D rectangle = getDefaultBounds2D();
        
        if (rectangle != null)
            return rectangle;
        
        return getOriginalBounds2D();
    }
    
    
    /**
     * <p>If the value of <code>getDefaultCenter</code> is non-<code>null</code>,
     * then this value is returned.</p>
     *
     * <p>If the value of <code>getDefaultCenter</code> is <code>null</code>,
     * then this method returns the value of <code>getOriginalCenter</code>.</p>
     *
     * @see AbstractMutatablePaintable#getCenter()
     * @see #getOriginalCenter()
     * @see AbstractPaintable#setDefaultCenter(Point2D)
     * @see AbstractPaintable#getDefaultCenter()
     */
    public final Point2D getCenter() {
        Point2D center = getDefaultCenter();
        
        if (center != null)
            return center;
        
        return getOriginalCenter();
    }
    
    
    /**
     * <p>Returns a copy of the actual bounds of the paintable sequence.</p>
     *
     * <p>The bounds computation ignores any object in the sequence that has
     * zero width or zero height.</p>
     *
     * <p>If the paintable sequence is effectively empty, this method returns
     * <code>new Rectangle2D.Double()</code>.</p>
     *
     * <p>This method ignores the default Bound2D rectangle to provide a way
     * to determine the actual bounds of the sequence.</p>
     *
     * @return a copy of the actual bounds of the paintable sequence
     */
    public final Rectangle2D getOriginalBounds2D() {
        Rectangle2D rectangle = null;
        
        int N = paintablesequence.size();
        
        for (int i = 0; i < N; i++) {
            MutatablePaintable mp = getMutatablePaintable(i);
            
            if (mp == null)
                continue;
            
            // get the next rectangle
            Rectangle2D next = mp.getBounds2D();
            
            // ignore next if its width or its height is zero
            if ((next.getWidth() <= 0) || (next.getHeight() <= 0))
                continue;
            
            // add in the bounds of the next rectangle
            if (rectangle == null)
                rectangle = next;
            else
                rectangle.add(next);
        }
        
        if (rectangle == null)
            return new Rectangle2D.Double();
        else
            return rectangle;
    }
    
    
    /**
     * <p>Returns a copy of the actual center of the paintable sequence.</p>
     *
     * <p>By default, this method returns the center of the rectangle
     * returned by <code>getOriginalBounds2D()<code>.</p>
     *
     * <p>This method ignores the default center to provide a way to
     * determine the actual center of the sequence.</p>
     *
     * @return a copy of the actual center of the paintable sequence
     * @see #getOriginalBounds2D()
     */
    public final Point2D getOriginalCenter() {
        Rectangle2D bounds = getOriginalBounds2D();
        
        double x = bounds.getCenterX();
        double y = bounds.getCenterY();
        
        return new Point2D.Double(x, y);
    }
    
    
    /**
     * <p>Sets the mutator transform to 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>Whenever there is a change in the overall mutator for the sequence, this
     * change is immediately applied to the individual items in the sequence.  As a
     * consequence, the individual sequence items maintain their own knowledge of
     * the net mutation that must be applied in paint operations.  In particular,
     * as far as geometry is concerned, an individual sequence item may be viewed
     * either as part of the sequence or as a standalone <code>MutatablePaintable
     * </code>.  This design decision enables interactive manipulation of sequence
     * items to be handled in a natural fashion.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param M the invertible affine transform to set as the mutator
     * @see #addPreMutation(Mutator.Strategy)
     * @see #addPostMutation(Mutator.Strategy)
     */
    public final void setMutator(AffineTransform M) {
        AffineTransform A = getMutator();
        AffineTransform B = getMutatorInverse();
        
        if ((M == null) || (M.equals(A)))
            return;
        
        try {
            // test to make sure that the inverse of M exists
            AffineTransform N = M.createInverse();
            
            // calculate the net mutation to apply to individual sequence items
            AffineTransform T = TransformFactory.compose(M, B);
            
            // apply the net mutation to individual sequence items
            int length = paintablesequence.size();
            
            for (int i = 0; i < length; i++) {
                MutatablePaintable mp = getMutatablePaintable(i);
                
                if (mp == null)
                    continue;
                
                mp.addPostMutation(T);
            }
            
            // call the inherited method to maintain the mutator data structure
            // and to fire the SET_MUTATOR property change
            super.setMutator(M);
        }
        catch (NoninvertibleTransformException exception) {
            // on error do nothing
            return;
        }
    }
    
    
    /**
     * <p>Adds the mutation determined by the given strategy and by the current
     * setting of the mutator strategy usage.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_GROUP</code>
     * then this method applies the <code>Mutator.Strategy</code> object to
     * the mutator of the paintable sequence as a whole by composition on the
     * right using the original center to construct the pre-mutation.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_ITEMS</code>
     * then this method recursively applies the <code>Mutator.Strategy</code>
     * object to the individual items in the paintable sequence.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param strategy the mutator strategy to apply
     * @see #setMutator(AffineTransform)
     * @see #addPostMutation(Mutator.Strategy)
     */
    public final void addPreMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        if (usage == Mutator.MUTATE_AS_ITEMS) {
            int N = paintablesequence.size();
            
            for (int i = 0; i < N; i++) {
                MutatablePaintable mp = getMutatablePaintable(i);
                
                if (mp != null)
                    mp.addPreMutation(strategy);
            }
        }
        else {
            super.addPreMutation(strategy);
        }
    }
    
    
    /**
     * <p>Adds the mutation determined by the given strategy and by the current
     * setting of the mutator strategy usage.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_GROUP</code>
     * then this method applies the <code>Mutator.Strategy</code> object to
     * the mutator of the paintable sequence as a whole by composition on the
     * left using the mutated center to construct the post-mutation.</p>
     *
     * <p>If the current mutator strategy usage is <code>MUTATE_AS_ITEMS</code>
     * then this method recursively applies the <code>Mutator.Strategy</code>
     * object to the individual items in the paintable sequence.</p>
     *
     * <p>Fires property change: SET_MUTATOR.</p>
     * 
     * @param strategy the mutator strategy to apply
     * @see #setMutator(AffineTransform)
     * @see #addPreMutation(Mutator.Strategy)
     */
    public final void addPostMutation(Mutator.Strategy strategy) {
        if (strategy == null)
            return;
        
        if (usage == Mutator.MUTATE_AS_ITEMS) {
            int N = paintablesequence.size();
            
            for (int i = 0; i < N; i++) {
                MutatablePaintable mp = getMutatablePaintable(i);
                
                if (mp != null)
                    mp.addPostMutation(strategy);
            }
        }
        else {
            super.addPostMutation(strategy);
        }
    }
    
    
    /**
     * <p>Set the mutator strategy usage to an allowable setting.</p>
     *
     * <p>The allowable settings are:</p>
     *
     * <ul>
     *   <li>MUTATE_AS_GROUP: mutate the sequence as a group</li>
     *   <li>MUTATE_AS_ITEMS: mutate the individual sequence items.</li>
     * </ul>
     *
     * @param usage the mutator strategy usage
     * @see #getMutatorStrategyUsage()
     */
    public final void setMutatorStrategyUsage(Mutator.StrategyUsage usage) {
        if (usage != null)
            this.usage = usage;
    }
    
    
    /**
     * <p>Returns the current mutator strategy usage.</p>
     *
     * @see #setMutatorStrategyUsage(Mutator.StrategyUsage usage)
     */
    public final Mutator.StrategyUsage getMutatorStrategyUsage() {
        return usage;
    }
    
    
    /**
     * <p>Sets the mutator strategy usage to MUTATE_AS_GROUP.</p>
     *
     * @see #setMutatorStrategyUsage(Mutator.StrategyUsage usage)
     */
    public final void setMutateAsGroup() {
        usage = Mutator.MUTATE_AS_GROUP;
    }
    
    
    /**
     * <p>Sets the mutator strategy usage to MUTATE_AS_ITEMS.</p>
     *
     * @see #setMutatorStrategyUsage(Mutator.StrategyUsage usage)
     */
    public final void setMutateAsItems() {
        usage = Mutator.MUTATE_AS_ITEMS;
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside this sequence.</p>
     *
     * <p>This method returns <code>false</code> if one or more of the following
     * conditions occurs:</p>
     *
     * <ul>
     *   <li>The point is not in the rectangle <code>getBounds2D</code>.</li>
     *   <li>The method <code>isVisible</code> returns <code>false</code>.</li>
     * </ul>
     *
     * @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 final boolean contains(double x, double y) {
        return hitsItem(x, y) != null;
    }
    
    
    /**
     * <p>If the point specified by coordinates is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns the
     * appropriate <code>MutatablePaintable</code> object depending on the
     * current <code>Mutator.StrategyUsage</code> setting.</p>
     *
     * <p>If the strategy usage setting is <code>MUTATE_AS_ITEMS</code>,
     * then this method returns the same <code>MutatablePaintable</code>
     * as the method <code>hitsItem</code>.</p>
     * 
     * <p>If the strategy usage setting is <code>MUTATE_AS_GROUP</code>,
     * then this method returns a reference to this paintable sequence.
     *
     * <p>If the point is not inside any item of the paintable sequence,
     * then this method returns <code>null</code>.</p>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     * @return the appropriate mutatable paintable or <code>null</code>
     */
    public final MutatablePaintable hits(double x, double y) {
        MutatablePaintable mp = hitsItem(x, y);
        
        if (mp == null)
            return null;
        
        if (usage == Mutator.MUTATE_AS_ITEMS)
            return mp;
        else
            return this;
    }
    
    
    /**
     * <p>If the point is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns the
     * appropriate <code>MutatablePaintable</code> object depending on the
     * current <code>Mutator.StrategyUsage</code> setting.</p>
     *
     * <p>If the strategy usage setting is <code>MUTATE_AS_ITEMS</code>,
     * then this method returns the same <code>MutatablePaintable</code>
     * as the method <code>hitsItem</code>.</p>
     * 
     * <p>If the strategy usage setting is <code>MUTATE_AS_GROUP</code>,
     * then this method returns a reference to this paintable sequence.
     *
     * <p>If the point is not inside any item of the paintable sequence,
     * then this method returns <code>null</code>.</p>
     *
     * @param  p a specified <code>Point2D</code>
     * @return the appropriate mutatable paintable or <code>null</code>
     */
    public final MutatablePaintable hits(Point2D p) {
        if (p == null)
            return null;
        
        return hits(p.getX(), p.getY());
    }
    
    
    /**
     * <p>If the point specified by coordinates is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns
     * the topmost <code>MutatablePaintable</code> item that contains
     * the point; otherwise this method returns <code>null</code>.</p>
     *
     * <p>This method returns <code>null</code> if one or more of the following
     * conditions occurs:</p>
     *
     * <ul>
     *   <li>The point is not in the rectangle <code>getBounds2D</code>.</li>
     *   <li>The method <code>isVisible</code> returns <code>false</code>.</li>
     * </ul>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     * @return the topmost item containing the point or <code>null</code>
     */
    public final MutatablePaintable hitsItem(double x, double y) {
        if (!possiblyContains(x, y))
            return null;
        
        int N = paintablesequence.size();
        
        for (int i = 0; i < N; i++) {
            MutatablePaintable mp = getMutatablePaintable(i);
            
            if (mp != null)
                if (mp.contains(x, y))
                    return mp;
        }
        
        return null;
    }
    
    
    /**
     * <p>If the point is inside one of the items
     * in the <code>PaintableSequence</code> then this method returns
     * the topmost <code>MutatablePaintable</code> item that contains
     * the point; otherwise this method returns <code>null</code>.</p>
     *
     * <p>This method returns <code>null</code> if one or more of the following
     * conditions occurs:</p>
     *
     * <ul>
     *   <li>The point is <code>null</code>.</li>
     *   <li>The point is not in the rectangle <code>getBounds2D</code>.</li>
     *   <li>The method <code>isVisible</code> returns <code>false</code>.</li>
     * </ul>
     *
     * @param  p a specified <code>Point2D</code>
     * @return the topmost item containing the point or <code>null</code>
     */
    public final MutatablePaintable hitsItem(Point2D p) {
        if (p == null)
            return null;
        
        return hits(p.getX(), p.getY());
    }
    
    
    /**
     * <p>By default, this method returns the result of applying the
     * method <code>PaintableTools.wrapPaintable</code>.</p>
     * 
     * <p>This method may be overridden in a derived class if a more
     * sophisticated wrapping process is desired.</p>
     *
     * @param  paintable the paintable to wrap as a mutatable paintable
     * @return the associated mutatable paintable
     */
    protected MutatablePaintable wrapPaintable(Paintable paintable) {
        return PaintableTools.wrapPaintable(paintable);
    }
    
}
