/*
 * @(#)TableLayout.java    2.5.0   4 May 2006
 *
 * Copyright 2006
 * 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.util.JPTConstants;
import java.awt.*;

/**
 * <P>Layout manager effecting a table with an arbitrary number
 * of rows and columns, each with their own width and height.
 *
 * Optional gaps with a distance of an arbitrary number of pixels
 * can be set in the horizontal and vertical directions.</P>
 *
 * <P>The alignment of components within their respective cells
 * can be set to any of the nine compass directions.
 *
 * Alignments can be set for individual cells, table rows,
 * table columns, or the table itself.</P>
 *
 * <P>Empty rows and columns are not included 
 * in the rendering process and do not result 
 * in the application of a gap.</P>
 * 
 * <p>As of 2.5.0, adds methods to return the component at a given
 * row,col position or <code>CellPosition</code>.  These methods
 * permit one to inspect the components without the need to call
 * the heavyweight method <code>getComponentTable()</code>.</p>  
 *
 * <p>Corresponding methods to remove a component from a given
 * cell position are also provided.</p>
 * 
 * @author  Richard Rasala
 * @author  Jeff Raab
 * @version 2.5.0
 * @since   1.1
 */
public class TableLayout 
    implements LayoutManager2, JPTConstants 
{
    /** Value of the default alignment. */
    public static final int DEFAULT_ALIGNMENT = SOUTH_WEST;
    
    /** 
     * Value of the default orientation. 
     * @since 2.0
     */
    public static final int DEFAULT_ORIENTATION = HORIZONTAL;
    
    /** Value designating an alignment explicitly not set. */
    public static final int NO_ALIGNMENT_SET =
        NORTH + WEST + EAST + SOUTH + NORTH_WEST + NORTH_EAST +
        SOUTH_WEST + SOUTH_EAST + CENTER;
    
    /** Value designating an invalid alignment. */
    public static final int INVALID_ALIGNMENT = NO_ALIGNMENT_SET + 1;
    
    /** Value designating a non-existent minimum size. */
    public static final int NO_MINIMUM_SET = -1;

    /** Number of rows in this table layout. */
    protected int rows = 0;
    
    /** Number of columns in this table layout. */
    protected int cols = 0;
    
    /** Horizontal gap between columns in this table layout. */
    protected int hgap = 0;
    
    /** Vertical gap between rows in this table layout. */
    protected int vgap = 0;
    
    /** Table of components in this layout. */
    protected Component[][] table = new Component[0][0];
    
    /** 
     * Orientation for this table layout.
     * @since 2.0
     */
    protected int orientation = DEFAULT_ORIENTATION;

    /** Default alignment for cells in this table layout. */
    protected int tableAlignment = DEFAULT_ALIGNMENT;

    /** Default alignments for rows in this table layout. */
    protected int[] rowAlignment = new int[0];
    
    /** Default alignments for columns in this table layout. */
    protected int[] colAlignment = new int[0];

    /** Alignment for cells in this table layout. */
    protected int[][] cellAlignment = new int[0][0];
    
    /** Minimum heights for rows in this table layout. */
    protected int[] minRowHeight = new int[0];
    
    /** Minimum widths for columns in this table layout. */
    protected int[] minColWidth  = new int[0];
    
    
    //////////////////
    // Constructors //
    //////////////////

    /**
     * Constructs a table layout
     * with the given number of rows and columns.
     *
     * @param rows the number of rows in this table layout
     * @param cols the number of columns in this table layout
     * @see #TableLayout(int, int, int)
     * @see #TableLayout(int, int, int, int)
     * @see #TableLayout(int, int, int, int, int)
     * @see #TableLayout(int, int, int, int, int, int)
     */
    public TableLayout(int rows, int cols) {
        this(rows, cols, 0, 0, DEFAULT_ALIGNMENT, DEFAULT_ORIENTATION);
    }
    
    
    /**
     * Constructs a table layout
     * with the given number of rows and columns,
     * and the given default table cell alignment.
     *
     * @param rows the number of rows in this table layout
     * @param cols the number of columns in this table layout
     * @param align the default table cell alignment
     * @see #TableLayout(int, int)
     * @see #TableLayout(int, int, int, int)
     * @see #TableLayout(int, int, int, int, int)
     * @see #TableLayout(int, int, int, int, int, int)
     * @see JPTConstants#DEFAULT
     */
    public TableLayout(int rows, int cols, int align) {
        this(rows, cols, 0, 0, align, DEFAULT_ORIENTATION);
    }
    
    
    /**
     * Constructs a table layout
     * with the given number of rows and columns,
     * and the given horizontal and vertical gaps.
     *
     * @param rows the number of rows in this table layout
     * @param cols the number of columns in this table layout
     * @param hgap the gap between columns, in pixels
     * @param vgap the gap between rows, in pixels
     * @see #TableLayout(int, int)
     * @see #TableLayout(int, int, int)
     * @see #TableLayout(int, int, int, int, int)
     * @see #TableLayout(int, int, int, int, int, int)
     */
    public TableLayout(int rows, int cols, int hgap, int vgap) {
        this(rows, cols, hgap, vgap, DEFAULT_ALIGNMENT);
    }
    
    
    /**
     * Constructs a table layout
     * with the given number of rows and columns,
     * the given horizontal and vertical gaps,
     * and the given default table cell alignment,
     *
     * @param rows the number of rows in this table layout
     * @param cols the number of columns in this table layout
     * @param hgap the gap between columns, in pixels
     * @param vgap the gap between rows, in pixels
     * @param align the default table cell alignment
     * @see #TableLayout(int, int)
     * @see #TableLayout(int, int, int)
     * @see #TableLayout(int, int, int, int)
     * @see #TableLayout(int, int, int, int, int, int)
     * @see JPTConstants#DEFAULT
     */
    public TableLayout(int rows, int cols, int hgap, int vgap, int align) 
    {
        this(rows, cols, hgap, vgap, align, DEFAULT_ORIENTATION);
    }
    
    
    /**
     * Constructs a table layout
     * with the given number of rows and columns,
     * the given horizontal and vertical gaps,
     * the given default table cell alignment,
     * and the given layout orientation.
     *
     * @param rows the number of rows in this table layout
     * @param cols the number of columns in this table layout
     * @param hgap the gap between columns, in pixels
     * @param vgap the gap between rows, in pixels
     * @param align the default table cell alignment
     * @param orientation the table layout orientation
     * @see #TableLayout(int, int)
     * @see #TableLayout(int, int, int)
     * @see #TableLayout(int, int, int, int)
     * @see #TableLayout(int, int, int, int, int)
     * @see JPTConstants#DEFAULT
     * @since 2.0
     */
    public TableLayout(
        int rows, 
        int cols, 
        int hgap, 
        int vgap, 
        int align,
        int orientation) 
    {
        setRows(rows);
        setColumns(cols);
        
        setHorizontalGap(hgap);
        setVerticalGap(vgap);
        
        setTableAlignment(align);
        
        setOrientation(orientation);
    }
    
    
    ////////////////////
    // LayoutManager2 //
    ////////////////////

    /** 
     * Adds the given component to this table layout
     * at the given table position.
     *
     * If the given position is not a <CODE>CellPosition</CODE>,
     * the given component is added at the next available position.
     *
     * If the given position is beyond 
     * the number of rows or columns in this table,
     * the table is grown to accept the component
     * at the given position.
     *
     * @param c a component to be added
     * @param position the cell position for the component
     * @see #addLayoutComponent(String, Component)
     * @see #getNextAvailablePosition()
     */
    public void addLayoutComponent(Component c, Object position) {
        CellPosition p = null;

        // use position if one is given
        if (position instanceof CellPosition)
            p = (CellPosition)position;

        // if p is still null use the next available position
        if (p == null)
            p = getNextAvailablePosition();
        
        // if p is still null,
        if (p == null) {
            
            // grow a new row if VERTICAL orientation
            if (getOrientation() == VERTICAL)
                p = new CellPosition(rows, 0);
            
            // grow a new column if HORIZONTAL orientation
            // or orientation is neither 
            else 
                p = new CellPosition(0, cols);
        }
        
        // eliminate possible negative row and col coordinates
        if (p.row < 0)
            p.row = 0;
        
        if (p.col < 0)
            p.col = 0;
        
        // grow table if necessary
        if (p.row >= rows)
            setRows(p.row + 1);

        if (p.col >= cols)
            setColumns(p.col + 1);
            
        // add component to table
        table[p.row][p.col] = c;
    }
    
    
    /** 
     * Adds the given component to this layout 
     * at the next available position.
     *
     * @param name the name for the given component (ignored)
     * @param c a component to be added
     * @see #addLayoutComponent(Component, Object)
     */
    public void addLayoutComponent(String name, Component c) {
        addLayoutComponent(c, getNextAvailablePosition());
    }
    
    
    /**
     * <p>Removes the given component
     * from the data structure of this layout.</p>
     * 
     * <p>If the given component is not in this
     * layout, this layout is not changed.</p>
     *
     * @param c a component to be removed
     */
    public void removeLayoutComponent(Component c) {

        // for each row in the table
        for (int row = 0; row < rows; row++) {

            // for each cell in the row
            for (int col = 0; col < cols; col++) {

                // if the component is at the cell,
                // remove it from the table
                if (c == table[row][col]) {
                    table[row][col] = null;
                }
            }
        }
    }
    
    
    /**
     * <p>Removes the component at the given row,col position
     * from the data structure of this layout.</p>
     * 
     * <p>Does nothing if row,col is invalid.</p>
     * 
     * @param row the row position of the cell to remove
     * @param col the col position of the cell to remove
     */
    public void removeLayoutComponent(int row, int col) {
        if (isValidRow(row))
            if (isValidColumn(col))
                table[row][col] = null;
    }
    
    
    /**
     * <p>Removes the component at the given
     * <code>CellPosition</code>
     * from the data structure of this layout.</p>
     * 
     * <p>Does nothing if position
     * is <code>null</code> or invalid.</p>
     * 
     * @param position the <code>CellPosition</code>
     *                 of the cell to remove
     */
    public void removeLayoutComponent(CellPosition position) {
        if (position == null)
            return;
        
        removeLayoutComponent(position.row, position.col);
    }
    
    
    /**
     * This method does nothing, as this layout 
     * does not cache information about its container.
     *
     * @param parent the parent container for this layout
     */
    public void invalidateLayout(Container parent) {}
    
    
    /** 
     * Lays out the components in the given parent container.
     *
     * @param parent the parent container for this layout
     */ 
    public void layoutContainer(Container parent) {
        Insets insets = null;
        
        if (parent != null)
            insets = parent.getInsets();
        else
            insets = new Insets(0, 0, 0, 0);
        
        layoutContainer
            (preferredColumnWidths(), preferredRowHeights(), insets);
    }
    
    
    /** 
     * Returns the minimum size for this table layout.
     *
     * @param parent the parent container for this layout
     * @see #preferredLayoutSize(Container)
     * @see #maximumLayoutSize(Container)
     */
    public Dimension minimumLayoutSize(Container parent) {
        int height = 0;
        
        for (int row = 0; row < rows; row++) {
            int h = minimumRowHeight(row);
            
            if (h > 0) {
                if (height > 0)
                    height += vgap;
                
                height += h;
            }
        }
        
        int width  = 0;
        
        for (int col = 0; col < cols; col++) {
            int w = minimumColumnWidth(col);
            
            if (w > 0) {
                if (width > 0)
                    width += hgap;
                
                width += w;
            }
        }
        
        Insets insets = null;
        
        if (parent != null)
            insets = parent.getInsets();
        else
            insets = new Insets(0, 0, 0, 0);
        
        width  += insets.left + insets.right;
        height += insets.top + insets.bottom;
        
        return new Dimension(width, height);
    }
    
    
    /** 
     * Returns the preferred size for this table layout.
     *
     * @param parent the parent container for this layout
     * @see #minimumLayoutSize(Container)
     * @see #maximumLayoutSize(Container)
     */
    public Dimension preferredLayoutSize(Container parent) {
        int height = 0;
        
        for (int row = 0; row < rows; row++) {
            int h = preferredRowHeight(row);
            
            if (h > 0) {
                if (height > 0)
                    height += vgap;
                
                height += h;
            }
        }
        
        int width  = 0;
        
        for (int col = 0; col < cols; col++) {
            int w = preferredColumnWidth(col);
            
            if (w > 0) {
                if (width > 0)
                    width += hgap;
                
                width += w;
            }
        }
        
        Insets insets = null;
        
        if (parent != null)
            insets = parent.getInsets();
        else
            insets = new Insets(0, 0, 0, 0);
        
        width  += insets.left + insets.right;
        height += insets.top + insets.bottom;
        
        return new Dimension(width, height);
    }
    
    
    /** 
     * Returns the maximum size for this table layout.
     *
     * @param parent the parent container for this layout
     * @see #minimumLayoutSize(Container)
     * @see #preferredLayoutSize(Container)
     */
    public Dimension maximumLayoutSize(Container parent) {
        int height = 0;
        
        for (int row = 0; row < rows; row++) {
            int h = maximumRowHeight(row);
            
            if (h > 0) {
                if (height > 0)
                    height += vgap;
                
                height += h;
            }
        }
        
        int width  = 0;
        
        for (int col = 0; col < cols; col++) {
            int w = maximumColumnWidth(col);
            
            if (w > 0) {
                if (width > 0)
                    width += hgap;
                
                width += w;
            }
        }
        
        Insets insets = null;
        
        if (parent != null)
            insets = parent.getInsets();
        else
            insets = new Insets(0, 0, 0, 0);
        
        width  += insets.left + insets.right;
        height += insets.top + insets.bottom;
        
        return new Dimension(width, height);
    }
    
    
    /** 
     * Returns the alignment for this table layout
     * along the <I>x</I> axis. 
     *
     * @param target the parent container for this layout
     * @see #getLayoutAlignmentY(Container)
     */
    public float getLayoutAlignmentX(Container target) {
        return 0.5f;
    }
    
    
    /** 
     * Returns the alignment along the <I>y</I> axis. 
     *
     * @param target the parent container for this layout
     * @see #getLayoutAlignmentX(Container)
     */
    public float getLayoutAlignmentY(Container target) {
        return 0.5f;
    }
    
    
    ////////////////
    // Public API //
    ////////////////
    
    /** Returns a copy of the table of components in this layout. */
    public Component[][] getComponentTable() {
        Component[][] copy = new Component[rows][cols];
        
        for (int row = 0; row < rows; row++)
            for (int col = 0; col < cols; col++)
                copy[row][col] = table[row][col];
        
        return copy;
    }
    
    
    /**
     * <p>Returns the component at the given row,col position
     * in the table layout data structure.</p>
     * 
     * <p>Returns <code>null</code> if the position is invalid
     * or if there is no component in that cell.</p>
     * 
     * @param row the row position of the cell
     * @param col the col position of the cell
     */
    public Component getTableEntry(int row, int col) {
        if (isValidRow(row))
            if (isValidColumn(col))
                return table[row][col];
        
        return null;
    }
    
    
    /**
     * <p>Returns the component at the given
     * <code>CellPosition</code> position
     * in the table layout data structure.</p>
     * 
     * <p>Returns <code>null</code> if position
     * is <code>null</code> or invalid.</p>
     * 
     * @param position the <code>CellPosition</code> of the cell
     */
    public Component getTableEntry(CellPosition position) {
        if (position == null)
            return null;
        
        return getTableEntry(position.row, position.col);
    }
    
    
    /** 
     * Returns the number of rows in this table layout. 
     *
     * @see #getColumnCount()
     */
    public int getRowCount() {
        return rows;
    }
    
    
    /** 
     * Returns the number of columns in this table layout. 
     *
     * @see #getRowCount()
     */
    public int getColumnCount() {
        return cols;
    }
    
    
    /**
     * Returns whether or not the given row index is valid.
     *
     * @param row the row in question
     * @see #isValidColumn(int)
     */
    public boolean isValidRow(int row) {
        return (row >= 0) && (row < rows);
    }
    
    
    /**
     * Returns whether or not the given column index is valid.
     *
     * @param col the column in question
     * @see #isValidRow(int)
     */
    public boolean isValidColumn(int col) {
        return (col >= 0) && (col < cols);
    }
    
    
    /**
     * Returns whether or not the given row is empty.
     *
     * @param row the row in question
     * @see #isEmptyColumn(int)
     */
    public boolean isEmptyRow(int row) {
        if (!isValidRow(row))
            return true;
    
        for (int col = 0; col < cols; col++) {
            if (table[row][col] != null)
                return false;
        }
        
        return true;
    }
    
    
    /**
     * Returns whether or not the given column is empty.
     *
     * @param col the column in question
     * @see #isEmptyRow(int)
     */
    public boolean isEmptyColumn(int col) {
        if (!isValidColumn(col))
            return true;
    
        for (int row = 0; row < rows; row++) {
            if (table[row][col] != null)
                return false;
        }
        
        return true;
    }
    
    
    /** 
     * Sets the gap between columns in this table layout
     * to the given value, in pixels.
     *
     * @param hgap the desired gap, in pixels
     * @see #getHorizontalGap()
     */
    public void setHorizontalGap(int hgap) {
        if (hgap >= 0)
            this.hgap = hgap;
    }
    
    
    /** 
     * Returns the gap, in pixels, 
     * between columns in this table layout. 
     *
     * @see #setHorizontalGap(int)
     */
    public int getHorizontalGap() {
        return hgap;
    }
    
    
    /** 
     * Sets the gap between rows in this table layout
     * to the given value, in pixels.
     *
     * @param vgap the desired gap, in pixels
     * @see #getVerticalGap()
     */
    public void setVerticalGap(int vgap) {
        if (vgap >= 0)
            this.vgap = vgap;
    }
    
    
    /** 
     * Returns the gap, in pixels, 
     * between rows in this table layout. 
     *
     * @see #setVerticalGap(int)
     */
    public int getVerticalGap() {
        return vgap;
    }
    
    
    /**
     * <p>Sets the orientation for this table layout
     * to the given orientation value.</p>
     *
     * <p>If the given orientation value is invalid,
     * the orientation is not changed.</p>
     *
     * <p>The default orientation is <CODE>HORIZONTAL</CODE>.</p>
     *
     * <p>If the orientation is <CODE>HORIZONTAL</CODE>,
     * then the strategy for adding components whose position
     * is not explicitly specified is to add such components
     * column-by-column and to add new columns if needed.</p>
     *
     * <p>If the orientation is <CODE>VERTICAL</CODE>,
     * then the strategy for adding components whose position
     * is not explicitly specified is to add such components
     * row-by-row and to add new rows if needed.</p>
     *
     * <p>These policies are designed to be consistent with
     * the situation when the table layout is initially empty
     * and must be expanded at each step with either a
     * new column in the case of <CODE>HORIZONTAL</CODE> or a
     * new row in the case of <CODE>VERTICAL</CODE>.</p>
     *
     * @param orientation the orientation for this layout
     * @see #getOrientation()
     * @see JPTConstants#HORIZONTAL
     * @see JPTConstants#VERTICAL
     * @see JPTConstants#DEFAULT
     * @since 2.0
     */
    public void setOrientation(int orientation) {
        switch (orientation) {
            case DEFAULT:
                orientation = DEFAULT_ORIENTATION;
            case HORIZONTAL:
            case VERTICAL:
                break;
            default:
                return;
        }
        
        this.orientation = orientation;
    }
    
    
    /**
     * Returns the orientation for this table layout.
     *
     * @see #setOrientation(int)
     * @see JPTConstants#HORIZONTAL
     * @see JPTConstants#VERTICAL
     * @since 2.0
     */
    public int getOrientation() {
        return orientation;
    }
    
    
    /**
     * Sets the alignment for cells in this table layout
     * to the given alignment value.
     *
     * @param align the alignment value for cells
     * @see #getTableAlignment()
     * @see JPTConstants#DEFAULT
     */
    public void setTableAlignment(int align) {

        // eliminate invalid constants
        align = adjustAlignment(align);
        
        if ((align == INVALID_ALIGNMENT) || (align == NO_ALIGNMENT_SET))
            return;
        
        // set table alignment
        tableAlignment = align;
        
        // update lower-level alignments
        for (int row = 0; row < rows; row++) {
            rowAlignment[row] = align;
        
            for (int col = 0; col < cols; col++) {
                cellAlignment[row][col] = align;
            }
        }
        
        for (int col = 0; col < cols; col++)
            colAlignment[col] = align;
    }
    
    
    /**
     * Returns the default alignment 
     * for cells in this table layout.
     *
     * @see #setTableAlignment(int)
     */
    public int getTableAlignment() {
        return tableAlignment;
    }
    
    
    /**
     * Sets the alignment for cells in the given row
     * to the given alignment value.
     *
     * @param row the row whose alignment is to be set
     * @param align the alignment value for the row
     * @see #getRowAlignment(int)
     * @see JPTConstants#DEFAULT
     */
    public void setRowAlignment(int row, int align) {

        // quit if out of bounds
        if (!isValidRow(row))
            return;
            
        // eliminate invalid constants
        align = adjustAlignment(align);
        
        if (align == INVALID_ALIGNMENT)
            return;
        
        // set row alignment
        rowAlignment[row] = align;

        // update lower-level alignments
        for (int col = 0; col < cols; col++)
            cellAlignment[row][col] = align;
    }
    
    
    /**
     * Returns the default alignment value for the given row.
     *
     * @param row the row whose alignment value is desired
     * @see #setRowAlignment(int, int)
     */
    public int getRowAlignment(int row) {
        if (!isValidRow(row))
            return NO_ALIGNMENT_SET;
        
        return rowAlignment[row];
    }
    
    
    /**
     * Sets the alignment for cells in the given column
     * to the given alignment value.
     *
     * @param col the column whose alignment is to be set
     * @param align the alignment value for the column
     * @see #getColumnAlignment(int)
     * @see JPTConstants#DEFAULT
     */
    public void setColumnAlignment(int col, int align) {

        // quit if out of bounds
        if (!isValidColumn(col))
            return;
            
        // eliminate invalid constants
        align = adjustAlignment(align);
        
        if (align == INVALID_ALIGNMENT)
            return;
        
        // set column alignment
        colAlignment[col] = align;

        // update lower-level alignments
        for (int row = 0; row < rows; row++)
            cellAlignment[row][col] = align;
    }
    
    
    /**
     * Returns the default alignment value for the given column.
     *
     * @param col the column whose alignment value is desired
     * @see #setColumnAlignment(int, int)
     */
    public int getColumnAlignment(int col) {
        if (!isValidColumn(col))
            return NO_ALIGNMENT_SET;
        
        return colAlignment[col];
    }
    
    
    /**
     * Sets the alignment for the cell at the given position
     * to the given alignment value.
     *
     * @param p the position of the cell whose alignment is to be changed
     * @param align the alignment value for the cell
     * @see #setCellAlignment(int, int, int)
     * @see #getCellAlignment(CellPosition)
     * @see #getCellAlignment(int, int)
     * @see #getEffectiveCellAlignment(CellPosition)
     * @see #getEffectiveCellAlignment(int, int)
     * @see JPTConstants#DEFAULT
     */
    public void setCellAlignment(CellPosition p, int align) {
        if (p != null)
            setCellAlignmentImpl(p.row, p.col, align);
    }
    
    
    /**
     * Sets the alignment for the cell at the given position
     * to the given alignment value.
     *
     * @param row the row for the cell whose alignment is to be changed
     * @param col the column for the cell whose alignment is to be changed
     * @see #setCellAlignment(CellPosition, int)
     * @see #getCellAlignment(CellPosition)
     * @see #getCellAlignment(int, int)
     * @see #getEffectiveCellAlignment(CellPosition)
     * @see #getEffectiveCellAlignment(int, int)
     * @see JPTConstants#DEFAULT
     */
    public void setCellAlignment(int row, int col, int align) {
        setCellAlignmentImpl(row, col, align);
    }
    
    
    /**
     * Returns the alignment value for the cell at the given position
     * or the table alignment if an error occurs.
     *
     * @param p the position of the cell whose alignment value is desired
     * @see #setCellAlignment(CellPosition, int)
     * @see #setCellAlignment(int, int, int)
     * @see #getCellAlignment(int, int)
     * @see #getEffectiveCellAlignment(CellPosition)
     * @see #getEffectiveCellAlignment(int, int)
     * @see JPTConstants#DEFAULT
     */
    public int getCellAlignment(CellPosition p) {
        if (p != null)
            return getCellAlignment(p.row, p.col);
        
        return tableAlignment;
    }
    
    
    /**
     * Returns the alignment value for the cell at the given position
     * or the table alignment if an error occurs.
     *
     * @param row the row for the cell whose alignment value is desired
     * @param col the column for the cell whose alignment value is desired
     * @see #setCellAlignment(CellPosition, int)
     * @see #setCellAlignment(int, int, int)
     * @see #getCellAlignment(CellPosition)
     * @see #getEffectiveCellAlignment(CellPosition)
     * @see #getEffectiveCellAlignment(int, int)
     * @see JPTConstants#DEFAULT
     */
    public int getCellAlignment(int row, int col) {
        if (isValidRow(row) && isValidColumn(col))
            return cellAlignment[row][col];
        else
            return tableAlignment;
    }
    
    
    /**
     * Returns the effective alignment value for the cell
     * at the given position, that is,
     * either the cell's own alignment if it has been set
     * or the alignment of the table as a whole.
     *
     * @param p the position of the cell whose alignment value is desired
     * @see #setCellAlignment(CellPosition, int)
     * @see #setCellAlignment(int, int, int)
     * @see #getCellAlignment(CellPosition)
     * @see #getCellAlignment(int, int)
     * @see #getEffectiveCellAlignment(int, int)
     * @see JPTConstants#DEFAULT
     */
    public int getEffectiveCellAlignment(CellPosition p) {
        if (p != null)
            return getEffectiveCellAlignment(p.row, p.col);
        
        return tableAlignment;
    }
    
    
    /**
     * Returns the effective alignment value for the cell
     * at the given position, that is,
     * either the cell's own alignment if it has been set
     * or the alignment of the table as a whole.
     *
     * @param row the row for the cell 
     *      whose alignment value is desired
     * @param col the column for the cell 
     *      whose alignment value is desired
     * @see #setCellAlignment(CellPosition, int)
     * @see #setCellAlignment(int, int, int)
     * @see #getCellAlignment(CellPosition)
     * @see #getCellAlignment(int, int)
     * @see #getEffectiveCellAlignment(CellPosition)
     * @see JPTConstants#DEFAULT
     */
    public int getEffectiveCellAlignment(int row, int col) {
        if (isValidRow(row) && isValidColumn(col))
            if (cellAlignment[row][col] != NO_ALIGNMENT_SET)
                return cellAlignment[row][col];
        
        return tableAlignment;
    }
    
    
    /**
     * Sets the minimum height for the given row
     * to the given height in pixels.
     *
     * @param row the row whose minimum height is to be set
     * @param height the height for the row in pixels
     * @throws ArrayOutOfBoundsException
     *      if the given row is invalid
     * @see #getMinimumRowHeight(int)
     */
    public void setMinimumRowHeight(int row, int height) {
        if (!isValidRow(row))
            return;
        
        if (height < NO_MINIMUM_SET)
            return;
        
        minRowHeight[row] = height;
    }
    
    
    /**
     * Returns the minimum height set for the given row.
     *
     * @param row the row whose minimum height is desired
     * @throws ArrayOutOfBoundsException
     *      if the given row is invalid
     * @see #setMinimumRowHeight(int, int)
     */
    public int getMinimumRowHeight(int row) {
        if (isValidRow(row))
            return minRowHeight[row];
        else
            return NO_MINIMUM_SET;
    }
    
    
    /**
     * Sets all minimum row heights to the same value.
     *
     * @see #setMinimumRowHeight(int, int)
     * @see #setAllMinimumColumnWidths(int)
     */
    public void setAllMinimumRowHeights(int height) {
        if (height < NO_MINIMUM_SET)
            return;
        
        for (int row = 0; row < rows; row++)
            minRowHeight[row] = height;
    }
    
    
    /**
     * Sets the minimum width for the given column
     * to the given width in pixels.
     *
     * @param col the column whose minimum width is to be set
     * @param width the width for the column in pixels
     * @throws ArrayOutOfBoundsException
     *      if the given column is invalid
     * @see #getMinimumColumnWidth(int)
     */
    public void setMinimumColumnWidth(int col, int width) {
        if (!isValidColumn(col))
            return;
        
        if (width < NO_MINIMUM_SET)
            return;
        
        minColWidth[col] = width;
    }
    
    
    /**
     * Returns the minimum width set for the given column.
     *
     * @param col the column whose minimum width is desired
     * @throws ArrayOutOfBoundsException
     *      if the given column is invalid
     * @see #setMinimumColumnWidth(int, int)
     */
    public int getMinimumColumnWidth(int col) {
        if (isValidColumn(col))
            return minColWidth[col];
        else
            return NO_MINIMUM_SET;
    }
    
    
    /**
     * Sets all minimum column widths to the same value.
     *
     * @see #setMinimumColumnWidth(int, int)
     * @see #setAllMinimumRowHeights(int)
     */
    public void setAllMinimumColumnWidths(int width) {
        if (width < NO_MINIMUM_SET)
            return;
        
        for (int col = 0; col < cols; col++)
            minColWidth[col] = width;
    }
    
    
    /**
     * Returns the actual minimum height for the given row.
     *
     * @param row the row whose minimum height is desired
     * @see #preferredRowHeight(int)
     * @see #maximumRowHeight(int)
     */
    public int minimumRowHeight(int row) {
        if (!isValidRow(row))
            return 0;
            
        // establish starting height
        int height = 0;
        
        // for each component in the row
        for (int col = 0; col < cols; col++) {
            if (table[row][col] != null) {
                height = (int)Math.max(
                    height, table[row][col].getMinimumSize().height);
            }        
        }
        
        // if some component has height
        // make sure height reaches minimum for the row
        if (height > 0)
            height = (int) Math.max(height, minRowHeight[row]);
        
        return height;
    }
    
    
    /**
     * Returns the actual minimum width for the given column.
     *
     * @param col the column whose minimum width is desired
     * @see #preferredColumnWidth(int)
     * @see #maximumColumnWidth(int)
     */
    public int minimumColumnWidth(int col) {
        if (!isValidColumn(col))
            return 0;
            
        // establish starting width
        int width = 0;
        
        // for each component in the column
        for (int row = 0; row < rows; row++) {
            if (table[row][col] != null) {
                width = (int)Math.max(
                    width, table[row][col].getMinimumSize().width);
            }        
        }

        // if some component has width
        // make sure width reaches minimum for the col
        if (width > 0)
            width = (int) Math.max(width, minColWidth[col]);
        
        return width;
    }
    
    
    /**
     * Returns the actual preferred height for the given row.
     *
     * @param row the row whose preferred height is desired
     * @see #minimumRowHeight(int)
     * @see #maximumRowHeight(int)
     */
    public int preferredRowHeight(int row) {
        if (!isValidRow(row))
            return 0;
            
        // establish starting height
        int height = 0;
        
        // for each component in the row
        for (int col = 0; col < cols; col++) {
            if (table[row][col] != null) {
                height = (int)Math.max(
                    height, table[row][col].getPreferredSize().height);
            }        
        }
        
        // if some component has height
        // make sure height reaches minimum for the row
        if (height > 0)
            height = (int) Math.max(height, minRowHeight[row]);
        
        return height;
    }
    
    
    /**
     * Returns the actual preferred width for the given column.
     *
     * @param col the column whose preferred width is desired
     * @see #minimumColumnWidth(int)
     * @see #maximumColumnWidth(int)
     */
    public int preferredColumnWidth(int col) {
        if (!isValidColumn(col))
            return 0;
            
        // establish starting width
        int width = 0;
        
        // for each component in the column
        for (int row = 0; row < rows; row++) {
            if (table[row][col] != null) {
                width = (int)Math.max(
                    width, table[row][col].getPreferredSize().width);
            }        
        }

        // if some component has width
        // make sure width reaches minimum for the col
        if (width > 0)
            width = (int) Math.max(width, minColWidth[col]);
        
        return width;
    }
    
    
    /**
     * Returns the actual maximum height for the given row.
     *
     * @param row the row whose maximum height is desired
     * @see #minimumRowHeight(int)
     * @see #preferredRowHeight(int)
     */
    public int maximumRowHeight(int row) {
        if (!isValidRow(row))
            return 0;
            
        // establish starting height
        int height = 0;
        
        // for each component in the row
        for (int col = 0; col < cols; col++) {
            if (table[row][col] != null) {
                height = (int)Math.max(
                    height, table[row][col].getMaximumSize().height);
            }        
        }
        
        // if some component has height
        // make sure height reaches minimum for the row
        if (height > 0)
            height = (int) Math.max(height, minRowHeight[row]);
        
        return height;
    }
    
    
    /**
     * Returns the actual maximum width for the given column.
     *
     * @param col the column whose maximum width is desired
     * @see #minimumColumnWidth(int)
     * @see #preferredColumnWidth(int)
     */
    public int maximumColumnWidth(int col) {
        if (!isValidColumn(col))
            return 0;
            
        // establish starting width
        int width = 0;
        
        // for each component in the column
        for (int row = 0; row < rows; row++) {
            if (table[row][col] != null) {
                width = (int)Math.max(
                    width, table[row][col].getMaximumSize().width);
            }        
        }

        // if some component has width
        // make sure width reaches minimum for the col
        if (width > 0)
            width = (int) Math.max(width, minColWidth[col]);
        
        return width;
    }
    
    
    /** Returns the minimum heights for the rows in this table layout. */
    public int[] minimumRowHeights() {
        int[] heights = new int[rows];
        
        for (int i = 0; i < rows; i++)
            heights[i] = minimumRowHeight(i);
        
        return heights;
    }
    
    
    /** Returns the minimum widths for the columns in this table layout. */
    public int[] minimumColumnWidths() {
        int[] widths = new int[cols];
        
        for (int i = 0; i < cols; i++)
            widths[i] = minimumColumnWidth(i);
        
        return widths;
    }
    
    
    /** Returns the preferred heights for the rows in this table layout. */
    public int[] preferredRowHeights() {
        int[] heights = new int[rows];
        
        for (int i = 0; i < rows; i++)
            heights[i] = preferredRowHeight(i);
        
        return heights;
    }
    
    
    /** Returns the preferred widths for the columns in this table layout. */
    public int[] preferredColumnWidths() {
        int[] widths = new int[cols];
        
        for (int i = 0; i < cols; i++)
            widths[i] = preferredColumnWidth(i);
        
        return widths;
    }
    
    
    /** Returns the maximum heights for the rows in this table layout. */
    public int[] maximumRowHeights() {
        int[] heights = new int[rows];
        
        for (int i = 0; i < rows; i++)
            heights[i] = maximumRowHeight(i);
        
        return heights;
    }
    
    
    /** Returns the maximum widths for the columns in this table layout. */
    public int[] maximumColumnWidths() {
        int[] widths = new int[cols];
        
        for (int i = 0; i < cols; i++)
            widths[i] = maximumColumnWidth(i);
        
        return widths;
    }
    
    
    ///////////////////////
    // Protected methods //
    ///////////////////////

    /**
     * Lays out the components in the parent container using the
     * given arrays of column widths and row heights.
     *
     * @param widths the column widths for this layout
     * @param heights the row heights for this layout
     * @param insets the insets of the parent
     */ 
    protected void layoutContainer
        (int[] widths, int[] heights, Insets insets)
    {
        if ((widths == null) || (heights == null))
            return;
        
        int colMax = (int)Math.min(cols, widths.length );
        int rowMax = (int)Math.min(rows, heights.length);
        
        Dimension size     = null;
        Point     location = null;
        
        int x = insets.left;
        int y = insets.top;
        int componentWidth  = 0;
        int componentHeight = 0;
        Component c = null;
        
        // for each row in the table
        for (int row = 0; row < rowMax; row++) {
            if (heights[row] <= 0)
                continue;
        
            x = insets.left;
            
            // for each cell in the row
            for (int col = 0; col < colMax; col++) {
                if (widths[col] <= 0)
                    continue;
            
                c = table[row][col];
                
                // if there is a component, lay it out
                if (c != null) {

                    // find preferred component size
                    size = new Dimension(c.getPreferredSize());
                    
                    componentWidth  = size.width;
                    componentHeight = size.height;
                    
                    location = applyAlignment(
                        x, y, widths[col], heights[row],
                        componentWidth, componentHeight,
                        getEffectiveCellAlignment(row, col));
                        
                    c.setBounds(
                        location.x, location.y,
                        componentWidth, componentHeight);
                }
                
                // move x position for next component
                x += hgap + widths[col];
            }
            
            // move y position for next component
            y += vgap + heights[row];
        }
    }
    
    
    /**
     * <p>Returns the next empty position in the table layout,
     * that is, a position that does not contain a component;
     * if the table layout is full, returns a new position
     * that will require either adding a column or a row to
     * the internal component table.</p>
     *
     * <p>If the table orientation is <CODE>HORIZONTAL</CODE>,
     * then the strategy for adding components is to fill the
     * table column-by-column and to add new columns if needed.
     * Thus, for example, the cell position (1, 0) will be
     * returned before the cell position (0, 1) if both
     * cell positions are empty.
     * If the table layout is full, this method returns the
     * cell position (0, cols) that will require horizontal
     * expansion of the internal component table by the
     * layout manager.</p>
     *
     * <p>If the table orientation is <CODE>VERTICAL</CODE>,
     * then the strategy for adding components is to fill the
     * table row-by-row and to add new rows if needed.
     * Thus, for example, the cell position (0, 1) will be
     * returned before the cell position (1, 0) if both
     * cell positions are empty.
     * If the table layout is full, this method returns the
     * cell position (rows, 0) that will require vertical
     * expansion of the internal component table by the
     * layout manager.</p>
     *
     * <p>These policies are designed to be consistent with
     * the situation when the table layout is initially empty
     * and must be expanded at each step with either a
     * new column in the case of <CODE>HORIZONTAL</CODE> or a
     * new row in the case of <CODE>VERTICAL</CODE>.</p>
     *
     * <p>Returns <code>null</code> if the orientation is set
     * improperly.</p> 
     */
    protected CellPosition getNextAvailablePosition() {
        
        // For HORIZONTAL orientation:
        // search down each column
        // then move across to next column
        if (orientation == HORIZONTAL) {
            for (int col = 0; col < cols; col++) {
                for (int row = 0; row < rows; row++) {
                
                    // if the cell is free, return its location
                    if (table[row][col] == null)
                        return new CellPosition(row, col);
                }
            }
            
            return new CellPosition(0, cols);
        }
        
        // For VERTICAL orientation:
        // search across each row
        // then move down to next row
        if (orientation == VERTICAL) {
            for (int row = 0; row < rows; row++) {
                for (int col = 0; col < cols; col++) {
                
                    // if the cell is free, return its location
                    if (table[row][col] == null)
                        return new CellPosition(row, col);
                }
            }
            
            return new CellPosition(rows, 0);
        }
        
        // error state
        return null;
    }
    
    
    /**
     * Returns the point, given in terms 
     * of the parent container's coordinate space,
     * where a component of the given size should be located 
     * given a cell with the provided bounds
     * and the given alignment value.
     *
     * @param x the <I>x</I>-coordinate of the cell
     * @param y the <I>y</I>-coordinate of the cell
     * @param cellWidth  the width  of the cell
     * @param cellHeight the height of the cell
     * @param componentWidth  the width  of the component
     * @param componentHeight the height of the component
     * @param align the alignment value for the component
     */
    protected Point applyAlignment(
        int x,
        int y,
        int cellWidth,
        int cellHeight,
        int componentWidth,
        int componentHeight,
        int align)
    {
        Point p = new Point();

        // calculate extra space available in the cell
        int dx = cellWidth  - componentWidth;
        int dy = cellHeight - componentHeight;
        
        if (dx < 0) dx = 0;
        if (dy < 0) dy = 0;
        
        // calculate possible adjustments to the location
        int flushWest  = x;
        int centerWE   = x + dx/2;
        int flushEast  = x + dx;
        
        int flushNorth = y;
        int centerNS   = y + dy/2;
        int flushSouth = y + dy;
        
        // calculate adjusted location based on alignment
        switch (align) {
            case NORTH_EAST:
                p.setLocation(flushEast, flushNorth);
                break;

            case EAST:
                p.setLocation(flushEast, centerNS  );
                break;

            case SOUTH_EAST:  
                p.setLocation(flushEast, flushSouth);
                break;

            case NORTH:
                p.setLocation(centerWE,  flushNorth);
                break;

            case CENTER:
                p.setLocation(centerWE,  centerNS  );
                break;

            case SOUTH:  
                p.setLocation(centerWE,  flushSouth);
                break;

            case NORTH_WEST:
                p.setLocation(flushWest, flushNorth);
                break;

            case WEST:
                p.setLocation(flushWest, centerNS  );
                break;

            case SOUTH_WEST: 
            default:
                p.setLocation(flushWest, flushSouth);
                break;
        }

        return p;
    }
    
    
    /////////////////////
    // Package methods //
    /////////////////////

    /**
     * Sets the number of rows in this table
     * to the given number of rows.
     *
     * @param rowCount the number of rows for this table layout
     */
    void setRows(int rowCount) {
        if (rowCount < 0)
            rowCount = 0;
        
        int oldRows = rows;
        int oldCols = cols;
        
        rows = rowCount;
        
        // build new structures
        Component[][] newTable   = new Component[rows][oldCols];

        int[][] newCellAlignment = new int[rows][oldCols];
            
        int[] newRowAlignment    = new int[rows];
        
        int[] newMinRowHeight    = new int[rows];
        
        // find maximum row to copy from old table
        int maxRow = (int)Math.min(rows, oldRows);

        // copy component information
        for (int row = 0; row < maxRow; row++) {
            for (int col = 0; col < oldCols; col++) {
                newTable[row][col]         = table[row][col];
                newCellAlignment[row][col] = cellAlignment[row][col];
            }
            
            newRowAlignment[row] = rowAlignment[row];
            
            newMinRowHeight[row] = minRowHeight[row];
        }
        
        // set new alignments if necessary
        if (rows > oldRows) {
            for (int row = maxRow; row < rows; row++) {
                for (int col = 0; col < oldCols; col++)
                    newCellAlignment[row][col] = colAlignment[col];
                
                newRowAlignment[row] = NO_ALIGNMENT_SET;
                
                newMinRowHeight[row] = NO_MINIMUM_SET;
            }
        }
        
        // use new structures
        table         = newTable;
        cellAlignment = newCellAlignment;
        rowAlignment  = newRowAlignment;
        minRowHeight  = newMinRowHeight;
    }
    
    
    /**
     * Sets the number of columns in this table layout
     * to the given number of columns.
     *
     * @param colCount the number of columns for this table layout
     */
    void setColumns(int colCount) {
        if (colCount < 0)
            colCount = 0;
        
        int oldRows = rows;
        int oldCols = cols;
        
        cols = colCount;
        
        // build new structures
        Component[][] newTable   = new Component[oldRows][cols];

        int[][] newCellAlignment = new int[oldRows][cols];
            
        int[] newColAlignment    = new int[cols];
        
        int[] newMinColWidth     = new int[cols];
        
        // find maximum column to copy from old table
        int maxCol = (int)Math.min(cols, oldCols);

        // copy component information
        for (int col = 0; col < maxCol; col++) {
            for (int row = 0; row < oldRows; row++) {
                newTable[row][col]         = table[row][col];
                newCellAlignment[row][col] = cellAlignment[row][col];
            }
            
            newColAlignment[col] = colAlignment[col];
            
            newMinColWidth[col]  = minColWidth[col];
        }
        
        // set new alignments if necessary
        if (cols > oldCols) {
            for (int col = oldCols; col < cols; col++) {
                for (int row = 0; row < oldRows; row++)
                    newCellAlignment[row][col] = rowAlignment[row];
                
                newColAlignment[col] = NO_ALIGNMENT_SET;
                
                newMinColWidth[col]  = NO_MINIMUM_SET;
            }
        }
        
        // use new structures
        table         = newTable;
        cellAlignment = newCellAlignment;
        colAlignment  = newColAlignment;
        minColWidth   = newMinColWidth;
    }
    
    
    /**
     * Sets the alignment for the cell at the given position
     * to the given alignment value.
     *
     * @param row the row for the cell
     * @param col the column for the cell
     * @param align the alignment value for the given cell
     */
    void setCellAlignmentImpl(int row, int col, int align) {

        // quit if out of bounds
        if (!(isValidRow(row) && isValidColumn(col)))
            return;

        // eliminate invalid constants
        align = adjustAlignment(align);
        
        if (align == INVALID_ALIGNMENT)
            return;
        
        // set cell alignment
        cellAlignment[row][col] = align;
    }
    
    
    /**
     * Returns the alignment value corresponding to the given value,
     * or <CODE>{@link #INVALID_ALIGNMENT INVALID_ALIGNMENT}</CODE>
     * if the given value is invalid.
     *
     * @param align the alignment value to adjust
     */
    int adjustAlignment(int align) {
        switch (align) {
            case DEFAULT:
                align = DEFAULT_ALIGNMENT;
                break;
            
            case NORTH_WEST:    case NORTH:     case NORTH_EAST:
            case WEST:          case CENTER:    case EAST:
            case SOUTH_WEST:    case SOUTH:     case SOUTH_EAST:  
            case NO_ALIGNMENT_SET:
                break;
            
            default:
                align = INVALID_ALIGNMENT;
        }
        
        return align;
    }
    
}
