/*
 * @(#)DisplayCollection.java    1.0  18 February 2001
 *
 * Copyright 2004
 * 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 javax.swing.*;

/**
 * <P>A dynamic linear collection of 
 * <CODE>{@link Displayable Displayable}</CODE> objects, 
 * displayed using either a horizontal or vertical layout.</P>
 *
 * <P>The functionality of this container assumes use of a
 * <CODE>BoxLayout</CODE> set through the 
 * <CODE>{@link #setOrientation(int) setOrientation}</CODE> method
 * and effects are undefined if the user changes the layout
 * through other means.</P>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.0
 * @see ArrayPanel
 */
public class DisplayCollection extends DisplayPanel {

    // The panel is laid out using a BorderLayout,
    // with the collection contained in an inner DisplayPanel
    // added in the NORTH, and a blank JPanel 
    // added in the CENTER to claim the remaining space.
    //
    // This is due to the fact that a container using a BoxLayout 
    // will exhibit inconsistent behavior when placed 
    // in the center location of a BorderLayout.

    /** The default orientation for a display collection. */
    public static final int DEFAULT_ORIENTATION = VERTICAL;

    /** The orientation of this display collection. */
    protected int direction = DEFAULT_ORIENTATION;
    
    /** Panel containing the actual collection of components. */
    protected DisplayPanel inner = new DisplayPanel();
    
    //////////////////
    // Constructors //
    //////////////////

    /**
     * Constructs an empty collection 
     * with the default orientation.
     *
     * @see #DisplayCollection(int)
     * @see #DisplayCollection(Displayable[])
     * @see #DisplayCollection(Displayable[], int)
     */
    public DisplayCollection() {
        this(null, DEFAULT_ORIENTATION);
    }
    
    /**
     * Constructs an empty collection 
     * with the provided layout orientation.
     *
     * @param orientation the desired layout orientation
     * @see #DisplayCollection()
     * @see #DisplayCollection(Displayable[])
     * @see #DisplayCollection(Displayable[], int)
     */
    public DisplayCollection(int orientation) {
        this(null, orientation);
    }

    /**
     * Constructs a collection 
     * containing the given array of objects 
     * with the default orientation.
     *
     * @param obj an array of <CODE>Displayable</CODE> objects
     * @see #DisplayCollection()
     * @see #DisplayCollection(int)
     * @see #DisplayCollection(Displayable[], int)
     */
    public DisplayCollection(Displayable[] obj) {
        this(obj, DEFAULT_ORIENTATION);
    }

    /**
     * Constructs a collection 
     * containing the given array of objects
     * with the given orientation.
     *
     * @param obj an array of <CODE>Displayable</CODE> objects
     * @param orientation the orientation for this collection
     * @see #DisplayCollection()
     * @see #DisplayCollection(Displayable[])
     * @see #DisplayCollection(int)
     */
    public DisplayCollection(Displayable[] obj, int orientation) {
        
        // the inner panel, which holds the actual collection
        // of objects, is placed in the north location of the
        // layout to ensure that it retains its preferred
        // size and grows as needed
        // 
        // the JPanel is placed in the center location to
        // absorb all extra space as needed
        setLayout(new BorderLayout());
        super.add(inner, BorderLayout.NORTH);
        super.add(new JPanel(), BorderLayout.CENTER);

        // assign the desired orientation for the collection
        setOrientation(orientation);

        // add the objects to the collection
        if (obj != null) {
            for (int i = 0; i < obj.length; i++)
                add((Component)obj[i]);
        }
    }
    
    /////////////////
    // Displayable //
    /////////////////
    
    /**
     * Sets the view states for 
     * <CODE>Displayable</CODE> objects in the collection 
     * to the data encoded in the given <CODE>String</CODE>.
     *
     * Each object in the collection has its view state
     * set from the encoded <CODE>String</CODE> data
     * in the manner described in the API documentation for the
     * <CODE>{@link DisplayPanel DisplayPanel}</CODE> class.
     *
     * @param data the encoded <CODE>String</CODE> data
     * @see #getViewState()
     * @see Displayable
     */
    public void setViewState(String data) {
        inner.setViewState(data);
        
        // notify listeners of property change
        firePropertyChange(
            VIEW_STATE, 
            null, 
            data);
    }
    
    /**
     * Returns the view states 
     * for objects in the collection 
     * as an encoded <CODE>String</CODE>.
     *
     * The view state for the collection is encoded
     * in the manner described in the API documentation for the
     * <CODE>{@link DisplayPanel DisplayPanel}</CODE> class.
     *
     * @see #setViewState(String)
     * @see Displayable
     */
    public String getViewState() {
        return inner.getViewState();
    }
    
    /**
     * Sets the default view states for objects in the collection 
     * to the data encoded in the given <CODE>String</CODE>.
     *
     * Each object in the collection has its default view state
     * set from the encoded <CODE>String</CODE> data
     * in the manner described in the API documentation for the
     * <CODE>{@link DisplayPanel DisplayPanel}</CODE> class.
     *
     * @param data the desired default <CODE>String</CODE> data
     * @see #reset()
     * @see Displayable
     */
    public void setDefaultViewState(String data) {
        inner.setDefaultViewState(data);

        // notify listeners of property change
        firePropertyChange(DEFAULT_VIEW_STATE, null, data);
    }
    
    /**
     * Returns the default view states 
     * for objects in the collection 
     * as an encoded <CODE>String</CODE>.
     *
     * The default view state for the collection is encoded
     * in the manner described in the API documentation for the
     * <CODE>{@link DisplayPanel DisplayPanel}</CODE> class.
     *
     * @see #setDefaultViewState(String)
     * @see #reset()
     */
    public String getDefaultViewState() {
        return inner.getDefaultViewState();
    }

    /**
     * Recursively resets each of the objects 
     * in the collection.
     *
     * @see #setDefaultViewState(String)
     * @see Displayable
     */
    public void reset() {
        inner.reset();
    }
    
    ////////////////
    // Public API //
    ////////////////

    /**
     * Adds the specified component to the end of this container.
     *
     * @param c the component to be added
     * @return the component argument
     * @see #add(Component, int)
     * @see #add(Component, Object)
     * @see #add(Component, Object, int)
     * @see #add(String, Component)
     * @see #remove(Component)
     * @see #remove(int)
     * @see #removeAll()
     */
    public Component add(Component c) {
        inner.add(c);
        revalidate();
        return c;
    }
    
    /**
     * Adds the specified component to this container 
     * at the given position.
     *
     * @param c the component to be added
     * @param index the position at which to insert the component,
     *      or -1 to insert the component at the end.
     * @return the component argument
     * @see #add(Component)
     * @see #add(Component, Object)
     * @see #add(Component, Object, int)
     * @see #add(String, Component)
     * @see #remove(Component)
     * @see #remove(int)
     * @see #removeAll()
     */
    public Component add(Component c, int index) {
        inner.add(c, index);
        revalidate();
        return c;
    }

    /**
     * Adds the specified component to the end of this container. 
     *
     * Also notifies the layout manager to add the component 
     * to this container's layout 
     * using the specified constraints object.
     *
     * @param c the component to be added
     * @param constraints an object expressing layout constraints 
     *      for this component
     * @see #add(Component)
     * @see #add(Component, int)
     * @see #add(Component, Object, int)
     * @see #add(String, Component)
     * @see #remove(Component)
     * @see #remove(int)
     * @see #removeAll()
     */
    public void add(Component c, Object constraints) {
        inner.add(c, constraints);
        revalidate();
    }

    /**
     * Adds the specified component at the given position. 
     *
     * Also notifies the layout manager to add the component 
     * to this container's layout 
     * using the specified constraints object.
     *
     * @param c the component to be added
     * @param constraints an object expressing layout constraints 
     *      for this component
     * @param index the position at which to insert the component,
     *      or -1 to insert the component at the end.
     * @see #add(Component)
     * @see #add(Component, int)
     * @see #add(Component, Object)
     * @see #add(String, Component)
     * @see #remove(Component)
     * @see #remove(int)
     * @see #removeAll()
     */
    public void add(Component c, Object constraints, int index) {
        inner.add(c, constraints, index);
        revalidate();
    }

    /**
     * Adds the specified component to this container. 
     * 
     * It is strongly advised to use the 1.1 method, 
     * <CODE>{@link #add(Component, Object) 
     *               add(Component, Object)}</CODE>,
     * in place of this method.
     *
     * @param name the name of the component to be added
     * @param c the component to be added
     * @return the component argument
     * @see #add(Component)
     * @see #add(Component, int)
     * @see #add(Component, Object)
     * @see #add(Component, Object, int)
     * @see #remove(Component)
     * @see #remove(int)
     * @see #removeAll()
     */
    public Component add(String name, Component c) {
        inner.add(name, c);
        revalidate();
        return c;
    }

    /**
     * Removes the given component from this container.
     *
     * @param c the component to be removed
     * @see #add(Component)
     * @see #add(Component, int)
     * @see #add(Component, Object)
     * @see #add(Component, Object, int)
     * @see #add(String, Component)
     * @see #remove(int)
     * @see #removeAll()
     */
    public void remove(Component c) {
        inner.remove(c);
        revalidate();
    }
    
    /**
     * Removes the component at the given index 
     * from this container.
     *
     * @param index the index of the component to be removed
     * @see #add(Component)
     * @see #add(Component, int)
     * @see #add(Component, Object)
     * @see #add(Component, Object, int)
     * @see #add(String, Component)
     * @see #remove(Component)
     * @see #removeAll()
     */
    public void remove(int index) {
        inner.remove(index);
        revalidate();
    }
    
    /**
     * Removes all the components from this container.
     *
     * @see #add(Component)
     * @see #add(Component, int)
     * @see #add(Component, Object)
     * @see #add(Component, Object, int)
     * @see #add(String, Component)
     * @see #remove(Component)
     * @see #remove(int)
     */
    public void removeAll() {
        inner.removeAll();
        revalidate();
    }

    /**
     * Returns the component at the specified index
     * in the collection.
     *
     * It is strongly advised to never use the
     * <CODE>getComponent</CODE> method, 
     * as its behavior is undefined for this container.
     *
     * @param index the index of the desired component
     */
    public Component getItem(int index) {
        return inner.getComponent(index);
    }

    /**
     * Returns an array containing all of the components
     * held in the collection.
     *
     * It is strongly advised to never use the
     * <CODE>getComponents</CODE> method, 
     * as its behavior is undefined for this container.
     */
    public Component[] getItemArray() {
        return inner.getComponents();
    }

    /**
     * Returns the number of objects in the collection.
     *
     * It is strongly advised to never use the
     * <CODE>getComponentCount</CODE> method, 
     * as its behavior is undefined for this container.
     */
    public int getItemCount() {
        return inner.getComponentCount();
    }
    
    /**
     * Sets the orientation for the collection
     * to the given value.
     *
     * If the given orientation value is not valid,
     * the orientation is not changed.
     *
     * The functionality of this container assumes use of a
     * <CODE>BoxLayout</CODE> set through this method,
     * and effects are undefined if the user changes the layout
     * through other means.
     *
     * @param orientation the desired layout orientation value
     * @see #getOrientation()
     * @see #VERTICAL
     * @see #HORIZONTAL
     * @see #DEFAULT
     */
    public void setOrientation(int orientation) {
        int oldOrientation = getOrientation();
    
        switch (orientation) {
        
            // check for valid orientation value
            case VERTICAL:
            case HORIZONTAL:
                direction = orientation;
                break;
        
            // check for request for default orientation
            case DEFAULT:
                direction = DEFAULT_ORIENTATION;
                break;
            
            // ignore invalid orientation values
            default:
                return;
        }

        // update the layout
        switch (direction) {
            case HORIZONTAL:
                inner.setLayout(
                    new BoxLayout(inner, BoxLayout.X_AXIS));
                break;
            
            default:
                inner.setLayout(
                    new BoxLayout(inner, BoxLayout.Y_AXIS));
        }

        // update the visualization
        revalidate();
            
        // if the orientation has changed
        if (getOrientation() != oldOrientation) {

            // notify listeners of property change
            firePropertyChange(
                ORIENTATION,
                oldOrientation,
                getOrientation());
        }
    }
    
    /**
     * Returns the orientation value for the collection.
     *
     * @see #setOrientation(int)
     * @see #VERTICAL
     * @see #HORIZONTAL
     */
    public int getOrientation() {
        return direction;
    }
    
    /**
     * Returns the panel containing the collection.
     *
     * This method is provided 
     * with the intention of allowing the user
     * to set properties of the panel such as borders, etc.
     * and not with the intention of allowing the user
     * to set layout properties of the panel.
     *
     * The functionality of this container assumes use of a
     * <CODE>BoxLayout</CODE> set through the 
     * <CODE>{@link #setOrientation(int) setOrientation}</CODE> 
     * method, and effects are undefined 
     * if the user changes the layout through other means.
     */
    public DisplayPanel getCollectionPanel() {
        return inner;
    }
    
    /**
     * Sets the background color for the entire panel
     * to the provided color.
     *
     * @param background the desired background color
     */
    public void setBackground(Color background) {
        super.setBackground(background);
        
        // set the background color for the inner panel
        // and the JPanel used as a filler
        for (int i = 0; i < getComponentCount(); i++) {
            Component comp = getComponent(i);
            comp.setBackground(background);
        }
    }
}
