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

package edu.neu.ccs.gui;

import edu.neu.ccs.*;

import java.awt.*;
import java.awt.event.*;

/**
 * <p>Class <code>MultiColorView</code> provides a view in
 * which there is a table with labels in column 0 and
 * corresponding <code>ColorView</code> objects in column 1.
 * This class enables a user to set multiples colors using
 * a single panel.</p>
 * 
 * <p>Unlike <code>ColorView</code>, <code>MultiColorView</code>
 * does not implement the interface <code>TypedView</code>.
 * Rather, an object of this class can return
 * either an individual color based on an index
 * or an array of all colors represented in the view.</p>
 * 
 * <p>As of 2.6.0a, one may add one or more action listeners
 * to respond if any color view within the multi-color view
 * is changed.  To keep things simple, we decided against an
 * implementation that would single out which internal color
 * view has changed.</p>
 * 
 * <p>To add an action listener that applies to one color view
 * within this object, use the method <code>getColorView</code>
 * to access the view and then add the action listener
 * directly to the particular view.</p>
 * 
 * @author  Richard Rasala
 * @version 2.6.0a
 * @since   2.6.0
 * @see     ColorView
 * @see     VisualColorList
 * @see     VisualColorSampler
 */
public class MultiColorView
    extends BasePane
{
    /** The gap between the cells in the internal TablePanel. */
    private int GAP = 6;
    
    
    // Data and View
    
    /** The array of all ColorView objects. */
    private ColorView[] colorviews = null;
    
    /** The array of all Annotation objects used for the labels. */
    private Annotation[] annotations = null;
    
    /** The number of ColorView objects in this panel. */
    private int viewcount = 0;
    
    /** The internal TablePanel that holds the color views and the labels. */
    private TablePanel colorpanel = null;
    
    /** The action sequence for handling set color action listeners. */
    private ActionSequence setColorActions = new ActionSequence();
    
    
    
    // Constructor
    
    /**
     * <p>The <code>MultiColorView</code> constructor creates
     * a panel with a vertical collection of <code>ColorView</code>
     * objects initialized with the given colors array and a
     * corresponding set of labels initialized with the given
     * labels.  The labels are placed in column 0 and the
     * <code>ColorView</code> objects in column 1.</p>
     * 
     * <p>The constructor does significant error checking that
     * will be described below.</p>
     * 
     * <p>If colors is <code>null</code>, or of length 0, or has
     * no non-<code>null</code> <code>Color</code> entries, then
     * then initialization will take place as if exactly one
     * color was supplied, namely, red.  In effect, this raises
     * a &ldquo;red flag&rdquo; during development.</p>
     * 
     * <p>Otherwise, the count of <code>ColorView</code> objects
     * in the panel will equal the number of non-<code>null</code>
     * <code>Color</code> entries in the colors array.</p>
     * 
     * <p>Next, the labels array will be scanned and its
     * non-<code>null</code> strings will be used in succession
     * to provide labels for the corresponding <code>ColorView</code>
     * objects.  If there are no labels or not enough labels then
     * labels of the form &ldquo;Color n&rdquo; will be created.</p>
     * 
     * <p>Obviously, for best results, the colors and labels arrays
     * should be non-<code>null</code>, of the same size, and have
     * all non-<code>null</code> entries.</p>
     * 
     * @param colors the colors to initialize the <code>ColorView</code> objects
     * @param labels the labels for the <code>ColorView</code> objects
     */
    public MultiColorView
        (Color[] colors, String[] labels)
    {
        initializeColorViews(colors);
        initializeAnnotations(labels);
        createColorPanel();
    }
    
    
    // Methods
    
    /**
     * <p>Returns the number of <code>ColorView</code> objects
     * in this panel.</p>
     */
    public final int getViewCount() {
        return viewcount;
    }
    
    
    /**
     * <p>Returns the color corresponding to the given index.</p>
     * 
     * <p>To facilitate a situation in which the caller wants
     * more colors than are available in this view, the index
     * will be reduced modulo the viewcount before the color
     * is returned.  This design means the colors will, in
     * effect, be recycled as index exceeds viewcount.</p>
     * 
     * @param index the index of the desired color
     */
    public final Color getColor(int index) {
        index = index % viewcount;
        
        if (index < 0)
            index += viewcount;
        
        return colorviews[index].getColor();
    }
    
    
    /**
     * <p>Set the color of the <code>ColorView</code> at the
     * given index to the given color.</p>
     * 
     * <p>Requires:</p>
     * 
     * <ul>
     *   <li>0 &lt;= index &lt; viewcount</li>
     *   <li>color is non-<code>null</code></li>
     * </ul>
     * 
     * <p>Does nothing if these requirements are not met.</p>
     * 
     * <p>Perform the actions attached to this view if a
     * new color is set.</p>
     * 
     * @param index the index of the desired color
     * @param color the color to set
     */
    public final void setColor(int index, Color color) {
        if (color == null)
            return;
        
        if ((index < 0) || (index >= viewcount))
            return;
        
        colorviews[index].setColor(color);
    }
    
    
    /**
     * <p>Set the color of the <code>ColorView</code> at the
     * given index to the given color.</p>
     * 
     * <p>Requires:</p>
     * 
     * <ul>
     *   <li>0 &lt;= index &lt; viewcount</li>
     *   <li>color is non-<code>null</code></li>
     * </ul>
     * 
     * <p>Does nothing if these requirements are not met.</p>
     * 
     * <p>Does not perform the actions attached to this view.</p>
     * 
     * @param index the index of the desired color
     * @param color the color to set
     */
    public final void setColorHelper(int index, Color color) {
        if (color == null)
            return;
        
        if ((index < 0) || (index >= viewcount))
            return;
        
        colorviews[index].setColorHelper(color);
    }
    
    
    /**
     * <p>Returns an array with the current colors represented
     * in the <code>ColorView</code> objects.</p>
     */
    public final Color[] getColors() {
        Color[] colors = new Color[viewcount];
        
        for (int index = 0; index < viewcount; index++) {
            colors[index] = colorviews[index].getColor();
        }
        
        return colors;
    }
    
    
    /**
     * <p>Append an action listener to be performed when the color
     * of any internal <code>ColorView</code> is set.</p>
     */
    public final void addAction(ActionListener action) {
        setColorActions.add(action);
    }
    
    
    /**
     * <p>Remove an action listener that was to be performed when
     * the color of any internal <code>ColorView</code> is set.</p>
     *
     * <p>Returns <code>true</code> if removal was successful and
     * <code>false</code> otherwise.</p>
     */
    public final boolean removeAction(ActionListener action) {
        return setColorActions.remove(action);
    }
    
    
    /**
     * <p>Returns the i-th <code>ColorView</code> in this object
     * or <code>null</code> if i is out of range.</p>
     * 
     * @param i the index of the desired color view
     */
    public final ColorView getColorView(int i) {
        if ((i < 0) || (i >= viewcount))
            return null;
        
        return colorviews[i];
    }
    
    
    /**
     * <p>Initialize the <code>ColorView</code> objects in
     * the panel using the given colors array.</p>
     * 
     * <p>For notes on error checking, see the comments for
     * the constructor.</p>
     * 
     * @param colors the colors to initialize the <code>ColorView</code> objects
     */
    private void initializeColorViews(Color[] colors) {
        int length = (colors == null) ? 0 : colors.length;
        
        for (int i = 0; i < length; i++) {
            if (colors[i] != null)
                viewcount++;
        }
        
        if (viewcount == 0) {
            colors = new Color[] { Colors.red };
            viewcount = 1;
        }
        
        colorviews = new ColorView[viewcount];
        
        int index = 0;
        
        for (int i = 0; i < viewcount; i++) {
            while (colors[index] == null)
                index++;
            
            colorviews[i] =
                new ColorView(colors[index], true);
            
            colorviews[i].addAction(setColorActions);
            
            index++;
        }
    }
    
    
    /**
     * <p>Initialize the labels for the <code>ColorView</code>
     * objects in the panel using the given labels array.</p>
     * 
     * <p>For notes on error checking, see the comments for
     * the constructor.</p>
     * 
     * @param labels the labels for the <code>ColorView</code> objects
     */
    private void initializeAnnotations(String[] labels) {
        annotations = new Annotation[viewcount];
        
        int length = (labels == null) ? 0 : labels.length;
        int index = 0;
        
        for (int i = 0; (i < length) && (index < viewcount); i++) {
            if (labels[i] != null) {
                makeAnnotation(index, labels[i]);
                index++;
            }
        }
        
        while (index < viewcount) {
            makeAnnotation(index, "Color " + index);
            index++;
        }
    }
    
    
    /**
     * <p>Initialize the annotation at the given index
     * using the given label.</p>
     * 
     * <p>Assumes 0 &lt;= index &lt; viewcount.</p>
     * 
     * <p>Assumes label is non-<code>null</code>.</p>
     * 
     * @param index the index of the label
     * @param label the label
     */
    private void makeAnnotation
        (int index, String label)
    {
        annotations[index] =
            new Annotation(label, labelFont);
    }
    
    
    /**
     * <p>Creates the color table panel with labels and
     * <code>ColorView</code> objects assuming that the
     * individual objects have already been constructed.</p>
     */
    private void createColorPanel() {
        colorpanel = new TablePanel(viewcount, 2, GAP, GAP, WEST);
        
        for (int row = 0; row < viewcount; row++) {
            colorpanel.addObject(annotations[row], row, 0);
            colorpanel.addObject(colorviews[row],  row, 1);
        }
        
        addObject(colorpanel);
    }
    
    
    /**
     * <p>A static array of colors for test purposes in the main program.</p>
     * 
     * <p>The initial colors in this array are:</p>
     * 
     * <ul>
     *   <li>red</li>
     *   <li>blue</li>
     *   <li>lime</li>
     *   <li>orange</li>
     *   <li>skyblue</li>
     *   <li>darkviolet</li>
     *   <li>tan</li>
     *   <li>black</li>
     * </ul>
     * 
     * <p>This array is intentionally not final so a caller may change it
     * and run additional tests.</p>
     */
    public static Color[] testcolors =
        { Colors.red,
          Colors.blue,
          Colors.lime,
          Colors.orange,
          Colors.skyblue,
          Colors.darkviolet,
          Colors.tan,
          Colors.black
        };
    
    
    /**
     * <p>A test program that displays a <code>MultiColorView</code>
     * using the internal static array <code>testcolors</code> for
     * the initialization.</p>
     * 
     * <p>The caller is permitted to modify <code>testcolors</code>
     * to run additional tests.</p>
     * 
     * <p>No labels are supplied for this test so the default labels
     * are used.</p>
     * 
     * @param args ignored
     */
    public static void main(String[] args) {
        new MultiColorView(testcolors, null).frame("MultiColorView Test");
    }
    
}

