/*
 * @(#)SimpleArrayPanel.java    2.4.0   24 August 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 edu.neu.ccs.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.filter.*;

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
import java.util.*;
import java.lang.reflect.*;

/**
 * <p>Class <code>SimpleArrayPanel</code> provides a panel that can
 * hold one or more views of a given type with the number of such
 * views under interactive user control.</p>
 *
 * <p>The views are arranged in a 4-column vertical list with the
 * following structure in each row:</p>
 *
 * <ul>
 *   <li>Column 0 contains an automatically generated row label.</li>
 *   <li>Column 1 contains a view whose type is supplied in the
 *       constructor as a <code>TypedView</code> Class object.</li>
 *   <li>Column 2 contains a button that will in effect insert
 *       a new row at that position.</li>
 *   <li>Column 3 contains a button that will in effect delete
 *       that particular row.</li>
 * </ul>
 *
 * <p>This 4-column vertical list is placed in a scroll pane.  The
 * designer can determine how many rows will be visible at once.</p>
 * 
 * <p>The interactive controls that permit the user to change the
 * total number of views are placed in a panel underneath the scroll
 * pane.</p>
 *
 * <p>To construct a <code>SimpleArrayPanel</code>, the user must
 * supply its <code>viewType</code> which is an object of type
 * <code>Class</code> that defines the view that will be repeated
 * in the panel.</p>
 *
 * <p>The following restrictions on the <code>viewType</code> must
 * hold:</p>
 *
 * <ul>
 *   <li>The <code>viewType</code> must not be <code>null</code>.</li>
 *   <li>The <code>viewType</code> must extend <code>JComponent</code>
 *       so a view may be directly inserted into its panel.</li>
 *   <li>The <code>viewType</code> must implement <code>TypedView</code>
 *       so the model data can be extracted from a view by the standard
 *       methods of the <code>TypedView</code> interface.</li>
 *   <li>Normally, the <code>viewType</code> class must have a default
 *       constructor.</li>
 * </ul> 
 *
 * <p>If the method <code>createViewForRow</code> is overridden, that
 * method may use a different constructor for the <code>viewType</code>
 * class and it is then not necessary to have a default constructor.</p>
 *
 * <p>The most common way to obtain a <code>viewType</code> is to
 * take the name of a class, say <code>FooView</code>, that extends
 * <code>JComponent</code>, implements <code>TypedView</code>, and
 * has a default constructor, and then to use
 * <code>FooView.class</code> for the <code>viewType</code>.  Later
 * in this introduction, we will use this notation for examples.</p>
 *
 * <p>The <code>SimpleArrayPanel</code> class has several constructors
 * that take optional parameters.  We gather here a description of
 * these optional parameters and their default values.</p>
 *
 * <ul>
 *   <li><code>initialIndex</code>: Internally, rows are numbered from
 *       zero; for the user interface, rows will be labeled with
 *       <code>index&nbsp;+&nbsp;initialIndex</code>;
 *       the default value for <code>initialIndex</code> is 0.</li>
 *   <li><code>visibleViewCount</code>: The visible view count is the
 *       number of views that should be visible in the scroll pane at
 *       one time; the minimum and the default value is 2.</li>
 *   <li><code>gap</code>: The gap is the horizontal and vertical pixel
 *       gap to be used in the <code>TablePanel</code>s made internally
 *       in the array panel; the minimum and the default value is 4.</li>
 *   <li><code>autoHalo</code>: If true, then each view will be wrapped
 *       in a new <code>Halo</code> so that error highlighting will be
 *       localized around the view; the default is false since it may
 *       be the case that a view has its own error highlight strategy.</li>
 * </ul>
 *
 * <p>By default, the interactive controls that permit the user to change
 * the number of views show the length of the array, that is,
 * the total number of views.  For some applications, this value may not
 * be the most meaningful.  For example, for a classic polynomial, the
 * meaningful number is the degree which is the highest coefficient that
 * is available.  In this case, the degree equals length&nbsp;-&nbsp;1.
 * To accomodate this, the <code>SimpleArrayPanel</code> class maintains
 * an internal value <code>lengthOffset</code> that is added to the
 * length before it is displayed in the user interface.  The following 2
 * calls would tweak the interface to make it suitable for polynomial
 * coefficients:</p>
 *
 * <pre>    setLengthOffset(-1);</pre>
 * <pre>    setLengthButtonName("Set Degree");</pre>
 *
 * <p>The <code>SimpleArrayPanel</code> class smoothly handles the
 * transition between the internal length and the number displayed
 * in the interactive controls.</p>
 *
 * <p>To obtain an object that collects all views into an array for
 * internal manipulation, use the method <code>getViews</code>.  This
 * method returns an <code>Object</code> that is actually an array and
 * that may be cast to an array of <code>viewType</code>.</p>
 *
 * <p>For example, if <code>viewType</code> is <code>FooView.class</code>,
 * then the return value of <code>getViews</code> may be cast to an array
 * type <code>FooView[]</code>.</p>
 *
 * <p>Similarly, to obtain an object that uses the view states of the
 * various views to create an array of corresponding data model objects,
 * use either <code>demandObjects</code> or <code>requestObjects</code>.
 * These methods return an <code>Object</code> that is actually an array
 * that may be cast to an array of <code>viewDataType</code>.</p>
 * 
 * <p>For example, if <code>viewType</code> is <code>FooView.class</code>
 * and its corresponding model data type is <code>Foo</code>, then the
 * return value for either of the methods above may be cast to the array
 * type <code>Foo[]</code>.</p>
 *
 * <p>Both <code>demandObjects</code> and <code>requestObjects</code> use
 * any automatic error checking and correction that has been built in to
 * the corresponding methods in the <code>viewType</code> class.</p>
 *
 * <p>The class <code>SimpleArrayPanel</code> is similar to the existing
 * class <code>ArrayPanel</code> but is simpler to use since the most
 * common defaults are taken as given.  More importantly, this class can
 * be used as is since it is not abstract and sufficient tools are made
 * available to manage the model-view interaction directly.  Finally,
 * the new interface controls that permit a user to insert or delete an
 * individual row gives the user much more power than is available with
 * the older <code>ArrayPanel</code> class.</p>
 *
 * <p>The class <code>SimpleArrayPanel</code> does not itself implement
 * the interface <code>TypedView</code>.  The critical reason is that
 * to implement <code>TypedView</code>, it would be necessary to define
 * the following methods whose definitions cannot be done in a uniform
 * manner:</p>
 *
 * <pre>    public Class getDataType()</pre>
 * <pre>    public Stringable demandObject()</pre>
 * <pre>    public Stringable requestObject() throws CancelledException</pre>
 *
 * <p>If the user of this class wishes to build a <code>TypedView</code>,
 * then it is necessary to create or obtain a view class appropriate for
 * an overall view and to define a derived class of this class that does
 * implement the above three methods.
 * 
 * <p>As a convenience, dummy implementations of the following methods of
 * the interface <code>TypedView</code> have already been provided:</p>
 *
 * <pre>    public void setInputProperties(InputProperties properties)</pre>
 * <pre>    public InputProperties getInputProperties()</pre>
 *
 * <p>For backward compatibility, the older class <code>ArrayPanel</code>
 * is still part of JPT and has been left basically as is.  We recommend
 * <code>SimpleArrayPanel</code> for all new code.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.4.0
 */
public class SimpleArrayPanel extends TablePanel {
    
    /**
     * The minimum number of views that must be in the array panel;
     * the value is 1.
     */
    public static final int MINIMUM_LENGTH  = 1; // views
    
    /**
     * The minimum number of views that should be visible in the
     * scroll pane without needing scrolling; the value is 2.
     */
    public static final int MINIMUM_VISIBLE = 2; // views
    
    /**
     * The minimum horizontal and vertical cell gap to be used for
     * all <code>TablePanel</code> objects constructed directly in
     * this array panel; the value is 4.
     */
    public static final int MINIMUM_GAP     = 4; // pixels
    
    
    /** The <code>Paintable</code> to make the "insert one row icon". */
    public static final Paintable insertRowPaintable
        = makeInsertRowPaintable();
    
    /** The <code>Paintable</code> to make the "delete one row icon". */
    public static final Paintable deleteRowPaintable
        = makeDeleteRowPaintable();
    
    
    /**
     * The view type is the type of each input view in the
     * array panel; this type must
     * extend the class <code>JComponent</code>,
     * implement the interface <code>TypedView</code>, and
     * have a public default constructor.
     */
    protected Class viewType = null;
    
    /**
     * The view data type is the data type associated with
     * the view type via the method
     * <code>getDataType()</code> of <code>TypedView</code>.
     */
    protected Class viewDataType = null;
    
    
    /**
     * The actual length of the array, that is, the current
     * number of input views in the array panel.
     */
    protected int arrayLength = 0;
    
    /**
     * The lengthTFV should display the user meaningful length
     * of the array which equals
     * <code>arrayLength&nbsp;+&nbsp;lengthOffset</code>.
     */
    protected int lengthOffset = 0;
    
    /**
     * The initial index in the automatic labels of the input
     * views in the array panel.
     */
    protected int initialIndex = 0;
    
    
    /**
     * The number of views that should be visible in the scroll
     * pane without needing scrolling; this number is used to
     * set the minimum size of the scroll pane view port.
     */
    protected int visibleViewCount = MINIMUM_VISIBLE;
    
    /**
     * The horizontal and vertical cell gap to be used for all
     * <code>TablePanel</code> objects constructed directly in
     * this array panel.
     */
    protected int gap = MINIMUM_GAP;
    
    /**
     * Determines whether or not to wrap each view of the array
     * panel in a <code>Halo</code> to localize error highlights.
     */
    protected boolean autoHalo = false;
    
    
    /** The preferred width of a default instance of the view type. */
    protected int viewWidth = 0;
    
    /** The preferred height of a default instance of the view type. */
    protected int viewHeight = 0;
    
    /** The preferred width of the labels and text field view. */
    protected int labelWidth = TextFieldView.getSampleWidth("0000");
    
    
    /**
     * The panel that contains the 2-column vertical list with a
     * label in the left column of each row and a view in the
     * right column of each row.
     */
    protected TablePanel innerPanel = null;
    
    /** The scroll pane that contains the inner panel. */
    protected JPTScrollPane scrollPane = null;
    
    /** The interactive controls panel. */
    protected TablePanel controls = null;
    
    
    /**
     * The lengthTFV is part of the interactive user controls;
     * this field displays the user meaningful length of the
     * array which equals
     * <code>arrayLength&nbsp;+&nbsp;lengthOffset</code>.
     */
    protected TextFieldView lengthTFV = new TextFieldView(labelWidth);
    
    
    /** The action to increment the number of views. */
    protected SimpleAction incrementLength =
        new SimpleAction("+") {
            public void perform() { incrementLength(); };
        };
    
    
    /** The action to decrement the number of views. */
    protected SimpleAction decrementLength =
        new SimpleAction("-") {
            public void perform() { decrementLength(); };
        };
    
    
    /**
     * The action to set the array length from the user meaningful
     * length in the lengthTFV; this action takes care of required
     * adjustments.
     */
    protected SimpleAction setLengthFromGUI =
        new SimpleAction("Set Length") {
            public void perform() { setLengthFromGUI(); };
        };
    
    
    /**
     * The vector to hold references to the views in this panel
     * for ease of access by the methods of this class.
     */
    protected Vector viewList = new Vector();
    
    
    /**
     * <p>The 1-parameter constructor.</p>
     *
     * <p>For a complete discussion of contructor parameters and
     * defaults, see the introduction to this class.</p>
     *
     * @param viewType         the type of view to use in this panel
     */
    public SimpleArrayPanel
        (Class viewType)
    {
        this(viewType, 0, MINIMUM_VISIBLE, MINIMUM_GAP, false);
    }
    
    
    /**
     * <p>The 2-parameter constructor.</p>
     *
     * <p>For a complete discussion of contructor parameters and
     * defaults, see the introduction to this class.</p>
     *
     * @param viewType         the type of view to use in this panel
     * @param initialIndex     the initial index in the labels
     */
    public SimpleArrayPanel
        (Class viewType, int initialIndex)
    {
        this(viewType, initialIndex, MINIMUM_VISIBLE, MINIMUM_GAP, false);
    }
    
    
    /**
     * <p>The 3-parameter constructor.</p>
     *
     * <p>For a complete discussion of contructor parameters and
     * defaults, see the introduction to this class.</p>
     *
     * @param viewType         the type of view to use in this panel
     * @param initialIndex     the initial index in the labels
     * @param visibleViewCount the number of views to show at once
     */
    public SimpleArrayPanel
        (Class viewType, int initialIndex, int visibleViewCount)
    {
        this(viewType, initialIndex, visibleViewCount, MINIMUM_GAP, false);
    }
    
    
    /**
     * <p>The 4-parameter constructor.</p>
     *
     * <p>For a complete discussion of contructor parameters and
     * defaults, see the introduction to this class.</p>
     *
     * @param viewType         the type of view to use in this panel
     * @param initialIndex     the initial index in the labels
     * @param visibleViewCount the number of views to show at once
     * @param gap              the pixel gap between cells
     */
    public SimpleArrayPanel
        (Class viewType, int initialIndex, int visibleViewCount, int gap)
    {
        this(viewType, initialIndex, visibleViewCount, gap, false);
    }
    
    
    /**
     * <p>The 5-parameter constructor.</p>
     *
     * <p>For a complete discussion of contructor parameters and
     * defaults, see the introduction to this class.</p>
     *
     * @param viewType         the type of view to use in this panel
     * @param initialIndex     the initial index in the labels
     * @param visibleViewCount the number of views to show at once
     * @param gap              the pixel gap between cells
     * @param autoHalo         whether or not to auto-halo the views
     */
    public SimpleArrayPanel
        (Class   viewType,
         int     initialIndex,
         int     visibleViewCount,
         int     gap,
         boolean autoHalo)
    {
        setViewType(viewType);
        setInitialIndex(initialIndex);
        setVisibleViewCount(visibleViewCount);
        setTableGap(gap);
        setAutoHalo(autoHalo);
        
        buildArrayPanel();
    }
    
    
    /** Returns the type of the views repeated in this panel. */
    public final Class getViewType() {
        return viewType;
    }
    
    
    /**
     * Returns the model data type associated with the views
     * repeated in this panel.
     */
    public final Class getViewDataType() {
        return viewDataType;
    }
    
    
    /**
     * <p>Returns the i-th view installed in this panel.</p>
     *
     * <p>Returns <code>null</code> if i is out of range.</p>
     *
     * @param i the view index
     */
    public final TypedView getTypedView(int i) {
        if ((i < 0) || (i >= arrayLength))
            return null;
        
        return (TypedView) viewList.get(i);
    }
    
    
    /**
     * <p>Sets the view state of the i-th view to the given data.</p>
     *
     * <p>Does nothing if i is out of range or the given data is
     * <code>null</code>.</p>
     *
     * @param i the view index
     * @param data the desired view state
     */
    public final void setViewState(int i, String data) {
        TypedView typedview = getTypedView(i);
        
        if ((typedview != null) && (data != null))
            typedview.setViewState(data);
    }
    
    
    /**
     * <p>Gets the view state of the i-th view.</p>
     *
     * <p>Returns <code>null</code> if i is out of range.</p>
     *
     * @param i the view index
     */
    public final String getViewState(int i) {
        TypedView typedview = getTypedView(i);
        
        if (typedview == null)
            return null;
        
        return typedview.getViewState();
    }
    
    
    /**
     * <p>Sets the default view state of the i-th view to the
     * given data.</p>
     *
     * <p>Does nothing if i is out of range or the given data is
     * <code>null</code>.</p>
     *
     * @param i the view index
     * @param data the desired default view state
     */
    public final void setDefaultViewState(int i, String data) {
        TypedView typedview = getTypedView(i);
        
        if ((typedview != null) && (data != null))
            typedview.setDefaultViewState(data);
    }
    
    
    /**
     * <p>Gets the default view state of the i-th view.</p>
     *
     * <p>Returns <code>null</code> if i is out of range.</p>
     *
     * @param i the view index
     */
    public final String getDefaultViewState(int i) {
        TypedView typedview = getTypedView(i);
        
        if (typedview == null)
            return null;
        
        return typedview.getDefaultViewState();
    }
    
    
    /**
     * <p>Resets the i-th view state to its default state.</p>
     *
     * <p>Does nothing if i is out of range.</p>
     *
     * @param i the view index
     */
    public final void reset(int i) {
        TypedView typedview = getTypedView(i);
        
        if (typedview == null)
            return;
        
        typedview.reset();
    }
    
    
    /**
     * <p>Applies <code>demandObject</code> to the i-th view
     * and returns the resulting <code>Stringable</code>.</p>
     *
     * <p>Returns <code>null</code> if i is out of range.</p>
     *
     * @param i the view index
     */
    public final Stringable demandObject(int i) {
        TypedView typedview = getTypedView(i);
        
        if (typedview == null)
            return null;
        
        return typedview.demandObject();
    }
    
    
    /**
     * <p>Applies <code>requestObject</code> to the i-th view
     * and returns the resulting <code>Stringable</code>.</p>
     *
     * <p>Returns <code>null</code> if i is out of range.</p>
     *
     * <p>Throws <code>CancelledException</code> if the user
     * cancels when an error has been detected.</p>
     *
     * @param i the view index
     * @throws CancelledException
     */
    public final Stringable requestObject(int i)
        throws CancelledException
    {
        TypedView typedview = getTypedView(i);
        
        if (typedview == null)
            return null;
        
        return typedview.requestObject();
    }
    
    
    /**
     * <p>Applies <code>setInputProperties</code> to the i-th
     * view.</p>
     *
     * <p>Does nothing if i is out of range.</p>
     *
     * @param i the view index
     */
    public final void setInputProperties(int i, InputProperties properties)
    {
        TypedView typedview = getTypedView(i);
        
        if (typedview == null)
            return;
        
        typedview.setInputProperties(properties);
    }
    
    
    /**
     * <p>Gets the input properties of the i-th view.</p>
     *
     * <p>Returns <code>null</code> if i is out of range.</p>
     *
     * @param i the view index
     */
    public final InputProperties getInputProperties(int i) {
        TypedView typedview = getTypedView(i);
        
        if (typedview == null)
            return null;
        
        return typedview.getInputProperties();
    }
    
    
    /**
     * <p>Returns an object that is actually an array with the
     * list of views installed in the panel.</p>
     *
     * <p>Assume, for example,
     * that <code>viewType</code> is <code>FooView.class</code>
     * and that <code>number</code> is the number of views in
     * the panel.  Then the return value may be cast to an array
     * <code>FooView[]</code> and the length of this array will
     * be <code>number</code>.</p>
     *
     * <p>The array is constructed by the following Java call
     * that, in effect, uses reflection:</p>
     *
     * <pre>    Array.newInstance(viewType, number)</pre>
     *
     * <p>This array is then populated by calls to the method
     * <code>getTypedView(i)</code>.</p>
     */
    public final Object getViews() {
        Object array = Array.newInstance(viewType, arrayLength);
        
        for (int i = 0; i < arrayLength; i++)
            Array.set(array, i, getTypedView(i));
        
        return array;
    }
    
    
    /**
     * <p>Returns an object that is actually an array that is
     * populated by calling <code>demandObject</code> for each
     * view.</p>
     *
     * <p>Assume, for example,
     * that <code>viewType</code> is <code>FooView.class</code>,
     * that <code>viewDataType</code> is <code>Foo.class</code>,
     * and that <code>number</code> is the number of views in
     * the panel.  Then the return value may be cast to an array
     * <code>Foo[]</code> and the length of this array will be
     * <code>number</code>.</p>
     *
     * <p>The array is constructed by the following Java call
     * that, in effect, uses reflection:</p>
     *
     * <pre>    Array.newInstance(viewDataType, number)</pre>
     *
     * <p>This array is then populated by calls to the method
     * <code>demandObject(i)</code>.</p>
     */
    public final Object demandObjects() {
        Object array = Array.newInstance(viewDataType, arrayLength);
        
        for (int i = 0; i < arrayLength; i++)
            Array.set(array, i, demandObject(i));
        
        return array;
    }
    
    
    /**
     * <p>Returns an object that is actually an array that is
     * populated by calling <code>requestObject</code> for each
     * view.</p>
     *
     * <p>Assume, for example,
     * that <code>viewType</code> is <code>FooView.class</code>,
     * that <code>viewDataType</code> is <code>Foo.class</code>,
     * and that <code>number</code> is the number of views in
     * the panel.  Then the return value may be cast to an array
     * <code>Foo[]</code> and the length of this array will be
     * <code>number</code>.</p>
     *
     * <p>The array is constructed by the following Java call
     * that, in effect, uses reflection:</p>
     *
     * <pre>    Array.newInstance(viewDataType, number)</pre>
     *
     * <p>This array is then populated by calls to the method
     * <code>requestObject(i)</code>.</p>
     *
     * <p>Throws <code>CancelledException</code> if the user
     * cancels when an error has been detected.</p>
     *
     * @throws CancelledException
     */
    public final Object requestObjects()
        throws CancelledException
    {
        Object array = Array.newInstance(viewDataType, arrayLength);
        
        for (int i = 0; i < arrayLength; i++)
            Array.set(array, i, requestObject(i));
        
        return array;
    }
    
    
    /**
     * <p>Returns an array of type <code>TypedView[]</code> with
     * the views in the panel.</p>
     *
     * <p>This method is less useful than <code>getViews()</code>
     * since the array returned cannot be directly cast to an
     * array of the actual <code>viewType</code> due to basic
     * restrictions on how Java constructs arrays.  Therefore,
     * this method is only useful in so far as you wish to treat
     * the views simply as <code>TypedView</code> objects.</p>
     */
    public final TypedView[] getTypedViewArray() {
        TypedView[] typedviews = new TypedView[arrayLength];
        
        for (int i = 0; i < arrayLength; i++)
            typedviews[i] = getTypedView(i);
        
        return typedviews;
    }
    
    
    /**
     * <p>Returns an array of type <code>Stringable[]</code> that
     * is populated by calling <code>demandObject</code> for each
     * view.</p>
     *
     * <p>This method is less useful than
     * <code>demandObjects()</code>
     * since the array returned cannot be directly cast to an
     * array of the actual <code>viewDataType</code> due to basic
     * restrictions on how Java constructs arrays.  Therefore,
     * this method is only useful in so far as you wish to treat
     * the objects simply as <code>Stringable</code> objects.</p>
     */
    public final Stringable[] demandStringableArray() {
        Stringable[] stringables = new Stringable[arrayLength];
        
        for (int i = 0; i < arrayLength; i++)
            stringables[i] = demandObject(i);
        
        return stringables;
    }
    
    
    /**
     * <p>Returns an array of type <code>Stringable[]</code> that
     * is populated by calling <code>requestObject</code> for each
     * view.</p>
     *
     * <p>This method is less useful than
     * <code>requestObjects()</code>
     * since the array returned cannot be directly cast to an
     * array of the actual <code>viewDataType</code> due to basic
     * restrictions on how Java constructs arrays.  Therefore,
     * this method is only useful in so far as you wish to treat
     * the objects simply as <code>Stringable</code> objects.</p>
     *
     * <p>Throws <code>CancelledException</code> if the user
     * cancels when an error has been detected.</p>
     *
     * @throws CancelledException
     */
    public final Stringable[] requestStringableArray()
        throws CancelledException
    {
        Stringable[] stringables = new Stringable[arrayLength];
        
        for (int i = 0; i < arrayLength; i++)
            stringables[i] = requestObject(i);
        
        return stringables;
    }
    
    
    /**
     * <p>Sets the view states of the views in this panel
     * to the corresponding strings in the data array.</p>
     *
     * @param data the desired view state array
     */
    public final void setViewStates(String[] data) {
        if (data == null)
            return;
        
        int min = Math.min(arrayLength, data.length);
        
        for (int i = 0; i < min; i++)
            if (data[i] != null)
                setViewState(i, data[i]);
    }
    
    
    /**
     * <p>Gets the view states of the views in this panel
     * as items in a string data array.</p>
     */
    public final String[] getViewStates() {
        String[] data = new String[arrayLength];
        
        for (int i = 0; i < arrayLength; i++)
            data[i] = getViewState(i);
        
        return data;
    }
    
    
    /**
     * <p>Sets the default view states of the views in this panel
     * to the corresponding strings in the data array.</p>
     *
     * @param data the desired default view state array
     */
    public final void setDefaultViewStates(String[] data) {
        if (data == null)
            return;
        
        int min = Math.min(arrayLength, data.length);
        
        for (int i = 0; i < min; i++)
            if (data[i] != null)
                setDefaultViewState(i, data[i]);
    }
    
    
    /**
     * <p>Gets the default view states of the views in this panel
     * as items in a string data array.</p>
     */
    public final String[] getDefaultViewStates() {
        String[] data = new String[arrayLength];
        
        for (int i = 0; i < arrayLength; i++)
            data[i] = getDefaultViewState(i);
        
        return data;
    }
    
    
    /**
     * <p>Sets the input properties of each view to the same input
     * properties object.</p>
     *
     * @param properties the common input properties for all views
     */
    public final void setCommonInputProperties
        (InputProperties properties)
    {
        for (int i = 0; i < arrayLength; i++)
            setInputProperties(i, properties);
    }
    
    
    /**
     * <p>Sets the input properties of each view to the corresponding
     * input properties object in the given array of properties.</p>
     *
     * @param properties the array of input properties for all views
     */
    public final void setInputPropertiesByView
        (InputProperties[] properties)
    {
        if (properties == null)
            return;
        
        int min = Math.min(arrayLength, properties.length);
        
        for (int i = 0; i < min; i++)
            setInputProperties(i, properties[i]);
    }
    
    
    /**
     * <p>Gets the input properties object for each view and then
     * returns this information in an array.
     */
    public final InputProperties[] getInputPropertiesByView() {
        InputProperties[] properties = new InputProperties[arrayLength];
        
        for (int i = 0; i < arrayLength; i++)
            properties[i] = getInputProperties(i);
        
        return properties;
    }
    
    
    /**
     * <p>Sets the view state of the panel using the data
     * encoded in the given string.</p>
     *
     * <p>Equivalent to:</p>
     *
     * <pre>    setViewStates(Strings.decode(data))</pre>
     *
     * @param data the encoded view state
     */
    public final void setViewState(String data) {
        setViewStates(Strings.decode(data));
    }
    
    
    /**
     * <p>Gets the view state of the panel as an
     * encoded string.
     *
     * <p>Equivalent to:</p>
     *
     * <pre>    return CodecUtilities.encode(getViewStates())</pre>
     */
    public final String getViewState() {
        return CodecUtilities.encode(getViewStates());
    }
    
    
    /**
     * <p>Sets the default view state of the panel using the data
     * encoded in the given string.</p>
     *
     * <p>Equivalent to:</p>
     *
     * <pre>    setDefaultViewStates(Strings.decode(data))</pre>
     *
     * @param data the encoded default view state
     */
    public final void setDefaultViewState(String data) {
        setDefaultViewStates(Strings.decode(data));
    }
    
    
    /**
     * <p>Gets the default view state of the panel as an
     * encoded string.
     *
     * <p>Equivalent to:</p>
     *
     * <pre>    return CodecUtilities.encode(getDefaultViewStates())</pre>
     */
    public final String getDefaultViewState() {
        return CodecUtilities.encode(getDefaultViewStates());
    }
    
    
    /** Resets each view to its default view state. */
    public final void reset() {
        for (int i = 0; i < arrayLength; i++)
            reset(i);
    }
    
    
    /**
     * <p>This method does nothing since the panel as a whole does
     * not utilize input properties.</p>
     *
     * <p>Implemented trivially in case the user wants to create a
     * derived class that implements <code>TypedView</code>.</p>
     *
     * @param properties ignored
     */
    public final void setInputProperties(InputProperties properties) {}
    
    
    /**
     * <p>This method returns <code>null</code> since the panel as
     * a whole does not utilize input properties.</p>
     *
     * <p>Implemented trivially in case the user wants to create a
     * derived class that implements <code>TypedView</code>.</p>
     */
    public final InputProperties getInputProperties() { return null; }
    
    
    /**
     * <p>Returns the number of views in this panel or, equivalently,
     * the length of the array of views.</p>
     */ 
    public final int getLength() {
        return arrayLength;
    }
    
    
    /**
     * <p>Returns the measure of the length of the array that is
     * meaningful to the end user.</p>
     *
     * <p>Equivalent to:</p>
     *
     * <pre>    getLength() + getLengthOffest()</pre>
     */
    public final int getUserLength() {
        return arrayLength + lengthOffset;
    }
    
    
    /**
     * <p>Returns the constant that should be added to the length
     * to obtain the length measure meaningful to the end user.</p>
     */
    public final int getLengthOffset() {
        return lengthOffset;
    }
    
    
    /**
     * <p>Returns the text on the GUI button that sets the length
     * of the array using the user meaningful length that is held
     * in the corresponding GUI text field view.</p>
     */
    public final String getLengthButtonName() {
        return (String) setLengthFromGUI.getValue(Action.NAME);
    }
    
    
    /**
     * <p>Sets the number of views in this panel or, equivalently,
     * the length of the array of views.</p>
     *
     * <p>The actual length set will be at least 1.</p>
     *
     * <p>This method is responsible for adding labels, views,
     * and the "insert one row" and "delete one row" buttons if
     * the length will be increased.  To do this, this method
     * calls:</p>
     *
     * <pre>    String createLabelForRow(int i)</pre>
     *
     * <pre>    TypedView createViewForRow(int i)</pre>
     *
     * <p>These methods may be overridden in a derived class if
     * the user of this class wants non-default behavior but the
     * constraints discussed in this class must be observed.</p>
     *
     * <p>This method is responsible for removing labels, views,
     * and buttons if the length will be decreased.</p>
     *
     * @param length the desired number of views in this panel 
     */
    public final void setLength(int length) {
        if (length < MINIMUM_LENGTH)
            length = MINIMUM_LENGTH;
        
        if (length == arrayLength)
            return;
        
        if (length > arrayLength) {
            // add labels, views, and buttons
            for (int i = arrayLength; i < length; i++) {
                // add label to panel
                String label = createLabelForRow(i);
                innerPanel.addObject(label, i, 0);
                
                // add typed view to panel and view list
                // auto halo if necessary
                TypedView view = createViewForRow(i);
                viewList.add(view);
                
                Object object = view;
                
                if (autoHalo)
                    object = new Halo(object);
                    
                innerPanel.addObject(object, i, 1);
                
                // add insert and delete buttons
                JButton insertButton = getInsertRowButton(i);
                JButton deleteButton = getDeleteRowButton(i);
                
                innerPanel.addObject(insertButton, i, 2);
                innerPanel.addObject(deleteButton, i, 3);
            }
        }
        else {
            // remove objects
            for (int i = arrayLength - 1; i >= length; i--) {
                // remove view from view list
                viewList.remove(i);
                
                int k = 4 * i;
                
                // remove insert and delete buttons
                innerPanel.remove(k + 3);
                innerPanel.remove(k + 2);
                
                // remove view from panel
                innerPanel.remove(k + 1);
                
                // remove label from panel
                innerPanel.remove(k);
            }
        }
        
        arrayLength = length;
        
        lengthTFV.setViewState("" + getUserLength());
        
        repaint();
    }
    
    
    /**
     * <p>Sets the number of views using the measure of length
     * that is meaningful to the end user.</p>
     *
     * @param userLength the user meaningful length
     */
    public final void setUserLength(int userLength) {
        setLength(userLength -  lengthOffset);
    }
    
    
    /**
     * <p>Sets the constant that should be added to the length
     * to obtain the length measure meaningful to the end user.</p>
     *
     * @param offset the offset used to adjust the length
     */
    public final void setLengthOffset(int offset) {
        lengthOffset = offset;
        
        lengthTFV.setViewState("" + getUserLength());
    }
    
    
    /**
     * <p>Sets the text on the GUI button that sets the length of
     * the array using the user meaningful length that is held in
     * the corresponding GUI text field view.</p>
     *
     * @param name the text for the button that sets the length
     */
    public final void setLengthButtonName(String name) {
        if ((name == null) || (name.length() == 0))
            return;
        
        setLengthFromGUI.putValue(Action.NAME, name);
    }
    
    
    /** A helper method to increment the length by 1. */
    public final void incrementLength() {
        setLength(arrayLength + 1);
    }
    
    
    /** A helper method to decrement the length by 1. */
    public final void decrementLength() {
        setLength(arrayLength - 1);
    }
    
    
    /**
     * <p>Returns the label String for the i-th row.</p>
     *
     * <p>By default, this method returns:</p>
     *
     * <pre>    "" + (i + initialIndex)</code></pre>
     *
     * <p>May be overridden in a derived class if the designer
     * wants to create a different label String.</p>
     *
     * @param i the row index
     */
    public String createLabelForRow(int i) {
        return "" + (i + initialIndex);
    }
    
    
    /**
     * <p>Returns a new <code>TypedView</code> for the i-th row
     * whose class is given by the view type.</p>
     *
     * <p>By default, this method returns:</p>
     *
     * <pre>    (TypedView) viewType.newInstance()</pre>
     *
     * <p>In particular, this implementation does not use the
     * index i.</p>
     *
     * <p>May be overridden in a derived class if the designer
     * wants to use a non-default constructor for the view.</p>
     *
     * <p>The view returned must be assignable to the view type
     * used to construct the array panel.</p>
     *
     * <p>Furthermore, the view returned by this method must
     * have the same preferred size independent of the index i.</p>
     *
     * @param i the row index
     */
    protected TypedView createViewForRow(int i) {
        TypedView typedview = null;
        
        try {
            typedview = (TypedView) viewType.newInstance();
        }
        catch (Exception ex) {
            throw new RuntimeException
                ("Failed to construct view in SimpleArrayPanel");
        }
        
        return typedview;
    }
    
    
    /**
     * <p>Method used in the constructor to set the view type of
     * the views in this panel.</p>
     *
     * <p>Performs error checks to verify the constraints that
     * are discussed in the introduction to this class.</p>
     *
     * <p>Extracts the model data type corresponding to the view
     * type and determines the dimensions of a typical view.</p>
     *
     * @param viewType the class of the views in this panel
     */
    protected final void setViewType(Class viewType) {
        if (viewType == null)
            throw new NullPointerException
                ("View type in SimpleArrayPanel must be non-null");
        
        if (! JComponent.class.isAssignableFrom(viewType))
            throw new IllegalArgumentException
                ("View type in SimpleArrayPanel must extend JComponent");
        
        if (! TypedView.class.isAssignableFrom(viewType))
            throw new IllegalArgumentException
                ("View type in SimpleArrayPanel must implement TypedView");
        
        // must set the viewType before call to createViewForRow
        
        this.viewType = viewType;
        
        // call createViewForRow to create a typical view
        // in order to extract useful information
        
        TypedView  typedview = createViewForRow(0);
        JComponent component = (JComponent) typedview;
        
        this.viewDataType = typedview.getDataType();
        
        Dimension dimension = component.getPreferredSize();
        
        viewWidth  = dimension.width;
        viewHeight = dimension.height;
    }
    
    
    /**
     * <p>Sets the initial index for the GUI view labels.</p>
     *
     * @param initialIndex the initial index for the labels
     */
    protected final void setInitialIndex(int initialIndex) {
        this.initialIndex = initialIndex;
    }
    
    
    /**
     * <p>Sets the number of views that should be visible in
     * the scroll pane without needing scrolling.</p>
     *
     * <p>This number is used to set the minimum size of the
     * scroll pane view port.</p>
     *
     * @param visibleViewCount the number of views visible at once
     */
    protected final void setVisibleViewCount(int visibleViewCount) {
        if (visibleViewCount < MINIMUM_VISIBLE)
            visibleViewCount = MINIMUM_VISIBLE;
        
        this.visibleViewCount = visibleViewCount;
    }
    
    
    /**
     * <p>Sets the horizontal and vertical gap for
     * <code>TablePanel</code> objects constructed in this panel.
     *
     * @param gap the standard pixel gap in tables
     */
    protected final void setTableGap(int gap) {
        if (gap < MINIMUM_GAP)
            gap = MINIMUM_GAP;
        
        this.gap = gap;
        
        setHorizontalGap(gap);
        setVerticalGap(gap);
    }
    
    
    /**
     * <p>Sets whether or not each view should be wrapped in a
     * <code>Halo</code> object before being inserted into the
     * panel.</p>
     *
     * @param autoHalo whether or not to automatically halo
     */
    protected final void setAutoHalo(boolean autoHalo) {
        this.autoHalo = autoHalo;
    }
    
    
    /** Main method to build the array panel. */
    protected final void buildArrayPanel() {
        buildInnerPanel();
        buildScrollPane();
        buildControls();
        
        setTableAlignment(CENTER);
        
        addObject(scrollPane, 0, 0);
        addObject(controls,   1, 0);
        
        lengthTFV.addActionListener(setLengthFromGUI);
    }
    
    
    /** Helper method to build the inner panel with labels and views. */
    protected final void buildInnerPanel() {
        innerPanel = new TablePanel(16, 4, gap, gap, CENTER);
        innerPanel.setMinimumColumnWidth(0, labelWidth);
        
        setLength(MINIMUM_LENGTH);
    }
    
    
    /** Helper method to build the scroll pane. */
    protected final void buildScrollPane() {
        // The subtle work in this method is to count pixels
        // to make the correct number of views to be visible
        // in the vertical direction and to avoid scrolling
        // in the horizontal (if possible)
        
        int vsb = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS;
        int hsb = JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS;
        
        Border border =
            BorderFactory.createEmptyBorder(gap, gap, gap, gap);
        
        innerPanel.setBorder(border);
        
        scrollPane = new JPTScrollPane(innerPanel, vsb, hsb);
        
        int extra  = (autoHalo) ? 2 * Halo.DEFAULT_INSET : 0;
        
        JButton sample = getInsertRowButton(0);
        Dimension dimension = sample.getPreferredSize();
        
        int w = (int) dimension.getWidth();
        int h = (int) dimension.getHeight();
        
        h = Math.max(viewHeight + extra, h);
        
        int width  = labelWidth + viewWidth + extra + 2 * w + 5 * gap;
        int height = visibleViewCount * (h + gap) + gap;
        
        scrollPane.setViewportPreferredSize(width, height);
    }
    
    
    /** Helper method to build the end user controls. */
    protected final void buildControls() {
        Object[] vArray =
            { setLengthFromGUI, lengthTFV };
            
        TablePanel nestedControls =
            new TablePanel(vArray, VERTICAL, gap, gap, CENTER);
        
        JButton increment =
            PaintableTools.makeButton(insertRowPaintable, incrementLength);
        
        JButton decrement =
            PaintableTools.makeButton(deleteRowPaintable, decrementLength);
        
        Object[] hArray =
            { increment, nestedControls, decrement };
        
        controls =
            new TablePanel(hArray, HORIZONTAL, gap, gap, CENTER);
    }
    
    
    /**
     * <p>Insert a row at the given index by inserting a row at the end
     * of the array panel and then moving the contents of each cell upward
     * by one cell starting at the index position.</p>
     *
     * <p>Changes the contents of the cell at index to its default view
     * state.</p>
     *
     * <p>Does nothing unless 0&nbsp;&lt;=&nbsp;index&nbsp;&lt;getLength().</p>
     *
     * @param index the index at which to insert a row
     */
    public final void insertRowAtIndex(int index) {
        int L = getLength();
        
        if ((index < 0) || (index >= L))
            return;
        
        // insert cell at end
        incrementLength();
        
        // move contents one cell upward
        for (int i = L; i > index; i--) {
            TypedView above = getTypedView(i);
            TypedView below = getTypedView(i - 1);
            
            above.setViewState(below.getViewState());
        }
        
        // reset view state of cell at index
        TypedView start = getTypedView(index);
        start.setViewState(start.getDefaultViewState());
    }
    
    
    /**
     * <p>Delete a row at the given index by moving the contents of
     * each cell at a higher position downward by one cell and then
     * by deleting the the cell at the end of the array.</p>
     *
     * <p>Does nothing if the array length is 1.</p>
     *
     * <p>Does nothing unless 0&nbsp;&lt;=&nbsp;index&nbsp;&lt;getLength().</p>
     *
     * @param index the index at which to delete a row
     */
    public final void deleteRowAtIndex(int index) {
        int L = getLength();
        
        if (L <= MINIMUM_LENGTH)
            return;
        
        if ((index < 0) || (index >= L))
            return;
        
        // move contents one cell downward
        for (int i = (index + 1); i < L; i++) {
            TypedView above = getTypedView(i);
            TypedView below = getTypedView(i - 1);
            
            below.setViewState(above.getViewState());
        }
        
        // delete cell at end
        decrementLength();
    }
    
    
    /**
     * <p>Returns an action that inserts a row at the given index.</p>
     *
     * @param index the index at which to insert a row
     */
    public Action getInsertRowAction(final int index) {
        return new SimpleAction() {
            public void perform() {
                insertRowAtIndex(index);
            }
        };
    }
    
    
    /**
     * <p>Returns an action that deletes a row at the given index.</p>
     *
     * @param index the index at which to delete a row
     */
    public Action getDeleteRowAction(final int index) {
        return new SimpleAction() {
            public void perform() {
                deleteRowAtIndex(index);
            }
        };
    }
    
    
    /**
     * <p>Returns a button that inserts a row at the given index.</p>
     *
     * @param index the index at which to insert a row
     */
    public JButton getInsertRowButton(int index) {
        return PaintableTools.makeButton
            (insertRowPaintable, getInsertRowAction(index));
    }
    
    
    /**
     * <p>Returns a button that deletes a row at the given index.</p>
     *
     * @param index the index at which to delete a row
     */
    public JButton getDeleteRowButton(int index) {
        return PaintableTools.makeButton
            (deleteRowPaintable, getDeleteRowAction(index));
    }
    
    
    /**
     * Helper method to set the length using the end user length
     * extracted from the GUI.
     */
    protected final void setLengthFromGUI() {
        int MINIMUM = MINIMUM_LENGTH + lengthOffset;
         
        StringableFilter filter =
            new MinimumBoundFilter.Long(MINIMUM);
        
        try {
            setUserLength(lengthTFV.requestInt(filter));
        }
        catch (CancelledException ex) { }
    }
    
    
    /** Method to make the <code>Paintable</code> for the "insert row icon". */
    public static Paintable makeInsertRowPaintable() {
        XRect r = new XRect(0, 0, 16, 16);
        
        XLine2D v = new XLine2D( 8,  4,  8, 12);
        XLine2D h = new XLine2D( 4,  8, 12,  8);
        
        BasicStroke stroke = new BasicStroke(2);
        
        ShapePaintable p1 =
            new ShapePaintable
                (v, PaintMode.DRAW, null, Colors.black, stroke);
        
        ShapePaintable p2 =
            new ShapePaintable
                (h, PaintMode.DRAW, null, Colors.black, stroke);
        
        ShapePaintable p3 =
            new ShapePaintable
                (r, PaintMode.FILL, Colors.lightskyblue);
        
        PaintableSequence icon =
            new PaintableSequence(new Object[] { p1, p2, p3 });
        
        return icon;
    }
    
    
    /** Method to make the <code>Paintable</code> for the "delete row icon". */
    public static Paintable makeDeleteRowPaintable() {
        XRect r = new XRect(0, 0, 16, 16);
        
        XLine2D a = new XLine2D( 4,  4, 12, 12);
        XLine2D b = new XLine2D(12,  4,  4, 12);
        
        BasicStroke stroke = new BasicStroke(2);
        
        ShapePaintable p1 =
            new ShapePaintable
                (a, PaintMode.DRAW, null, Colors.black, stroke);
        
        ShapePaintable p2 =
            new ShapePaintable
                (b, PaintMode.DRAW, null, Colors.black, stroke);
        
        ShapePaintable p3 =
            new ShapePaintable
                (r, PaintMode.FILL, Colors.red);
        
        PaintableSequence icon =
            new PaintableSequence(new Object[] { p1, p2, p3 });
        
        return icon;
    }
    
    
}

