Package net.helipilot50.stocktrade.displayproject

Source Code of net.helipilot50.stocktrade.displayproject.GridFieldLayout

/*
Copyright (c) 2003-2009 ITerative Consulting Pty Ltd. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:

o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
 
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
   
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder

 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


*/
package net.helipilot50.stocktrade.displayproject;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;

import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;

import net.helipilot50.stocktrade.framework.ErrorMgr;
import net.helipilot50.stocktrade.framework.TextData;
import net.helipilot50.stocktrade.framework.UsageException;

import org.apache.log4j.Logger;


/**
* This layout manager simulates the Forte GridField layout rules. It is similar in nature to a
* GridBagLayout but with extra features.<p>
* <p>
* For example: (this is not a comprehensive list)<p>
* <li>Grid fields will expand their size if they are too large to accomodate the minimum sizes of their children
* <li>Grid fields can be titles and borders
* <li>Lines between cells in grid fields can be displayed by setting attributes
* <li>Grid fields have an "insertPolicy" property which dictates how to behave if multiple widgets
* are added into the same cell. For example, it can ignore the new widget, replace the old widget
* or expand the grid to cater for this new widget<p>
* <p>
* When a child is added to a grid field, it must be passed with contstraints. These constraints can either
* be a <i>GridBagConstraints</i> instance, or a <i>GridCell</i> instance. If passed a GridBagConstraints, the
* meaning is similar to that used for GridBagLayout. There are some differences though: <p>
* <p>
* <li>If the sum of the weights in a GridBagLayout row or colum is zero, all additional space for that
* column is given to the containing GridBag. In a GridField, this extra space is given to the child
* widgets in equal portions, similar to setting all the weights to 1.
* <li>GridBagLayouts allow different weights for the same column, resulting in undefined behaviour. In
* a GridField, weights are stored at the container (GridField) level, so each row or column has a weight
* assigned to it. If passed a GridBagLayout class with the <i>weightx</i> or <i>weighty</i> property set,
* this new weight is assumed to be the weight for the whole column and will replace any existing weight.
* <li>GridField children cannot span cells in a GridField (ie they must occupy exactly one cell) so the
* <i>gridwidth</i> and <i>gridheight</i> properties are ignored.
* <li>The row and column attributes must be set to an explicit value, RELATIVE cannot be used.<p> 
* <p>
* NB: This class extends GridBagLayout ONLY to aid the visual editors in rendering the class
* No inherited functionality is used.
* @author Tim
*
*/
@SuppressWarnings("serial")
public class GridFieldLayout extends GridBagLayout implements LayoutManager2, ComponentListener, PropertyChangeListener {
    /**
     * The number of rows in the grid layout. Adjusted as new items are added or removed from the layout
     */
    private int rows = 0;

    /**
     * The number of columns in the grid layout. Adjusted as new items are added or removed from the layout
     */
    private int columns = 0;

    /**
     * The weights of the rows in the grid field. These must all be >= 0, and rowWeights.getSize() == rows
     */
    protected ArrayList<Integer> rowWeights = new ArrayList<Integer>();

    /**
     * The weights of the columns in the grid field. These must all be >= 0, and columnWeights.getSize() == rows
     */
    protected ArrayList<Integer> columnWeights = new ArrayList<Integer>();

    /**
     * Row alignment controls how extra space is added to the grid field when it is enlarged vertically.
     */
    protected int rowAlignment = Constants.RA_JUSTIFY;
    /**
     * Column alignment controls how extra space is added to the grid field when it is enlarged horizontally.
     */
    protected int colAlignment = Constants.CA_JUSTIFY;

    /**
     * This mirrors the Forte attribute IsCollapsed. If collapsed is true, we will collapse
     * the grid fields if any rows are removed.
     */
    private boolean collapsed = false;
    /**
     * Determine whether invisible children should have their size considered in the layouts or not.
     */
    protected boolean ignoreInvisibleChildren = false;

    /**
     * The logger for debugging
     */
    private static Logger _log = Logger.getLogger(GridFieldLayout.class);

    /**
     * Cached information to speed up the layout process
     */
    private int[] rowStartLocs = null;
    private int[] rowHeights = null;
    private int[] colStartLocs = null;
    private int[] colWidths = null;
    /**
     * The raw minimum column widths of the children of this layout. This number does NOT include any
     * minimum sizes attributed to partners of this grid field
     */
    private int[] myMinWidths = new int[0];
    /**
     * The raw minimum row heights of the children of this layout. This number does NOT include any
     * minimum sizes attributed to partners of this grid field
     */
    private int[] myMinHeights = new int[0];

    /**
     * These constants determine how extra space is added to the grid. They are set from the
     * CA_xxx and RA_xxx constants
     */
    private static final int cBEFORE = 1;
    private static final int cCENTRE = 2;
    private static final int cAFTER = 3;
    private static final int cJUSTIFY = 4;

    /**
     * If this grid field is in a height partnerships with other grids,
     * they form a circular linked list in theory. 
     */
    protected GridField heightMatrixPartner = null;
    protected GridField widthMatrixPartner = null;

    /**
     * A helper to aid with the layout
     */
    private LayoutManagerHelper helper = new LayoutManagerHelper();

    /**
     * Since we listen for when our children change size, we need to ignore requests when
     * we have caused our children to change size
     */
    private boolean isAdjusting = false;

    public GridFieldLayout() {
        super();
    }

    /**
     * Retrieve the number of rows currently in the grid field
     * @return the number of rows
     */
    public int getRows(){
        return this.rows;
    }

    /**
     * Retrieve the number of columns in the grid field
     * @return the number of columns
     */
    public int getColumns(){
        return this.columns;
    }

    /**
     * Add a component to the layout
     */
    public void addLayoutComponent(String name, Component comp) {
//      debug("addLayoutComponent(\"" + name + "\", "+ comp + ")");
        GridCell constraints = GridField.getConstraints((JComponent)comp);
        if (constraints == null) {
            UnsupportedOperationException errorVar = new UnsupportedOperationException("Cannot add a layout component to a GridFieldLayout with the addLayoutComponent(String name, Component comp) overload");
            ErrorMgr.addError(errorVar);
            throw errorVar;
        }
        else {
            addLayoutComponent(comp, constraints);
        }
    }

    /**
     * Add a component into the gridfield
     */
    @SuppressWarnings("deprecation")
    public void addLayoutComponent(Component comp, Object constraints) {
        // We need to still support the GridBagConstraints structure for backwards compatibility
        // Some of the methods on a grid cell are deprecated, so we need the about warning supression
        // to ensure no spurious warnings are generated.
        if (constraints == null) {
            constraints = GridField.getConstraints((JComponent)comp);
            if (constraints == null) {
                constraints = new GridCell();
            }
        }
        else if (constraints instanceof GridBagConstraints) {
            constraints = new GridCell((GridBagConstraints)constraints);
        }
        if (constraints instanceof GridCell) {
            GridCell gc = (GridCell)constraints;

            if (gc.row >= rows) {
                rows = gc.row + 1;
                rowWeights.ensureCapacity(rows);
                for (int i = rowWeights.size(); i < rows; i++) {
                    rowWeights.add(new Integer(0));
                }
            }
            if (gc.column >= columns) {
                columns = gc.column + 1;
                columnWeights.ensureCapacity(gc.column+1);
                for (int i = columnWeights.size(); i < columns; i++) {
                    columnWeights.add(new Integer(0));
                }
            }
            if (gc.colWeight > 0) {
                // The older style of creating a component, using grid bags, can set this. We
                // need to obey it for all columns in the grid, even though this isn't necessarily a great idea
                columnWeights.set(gc.column, new Integer(gc.colWeight));
            }
            if (gc.rowWeight > 0) {
                rowWeights.set(gc.row, new Integer(gc.rowWeight));
            }

            // Attach this onto this child as a placeholder
            GridField.setConstraints((JComponent)comp, gc);

            // register for any events that will affect the layout
            comp.addComponentListener(this);
            comp.addPropertyChangeListener(this);

            // We need to invalidate our parent, but also reset the minimum and preferred sizes as these are now invalid
            if (comp.getParent() != null) {
                comp.getParent().setMinimumSize(null);
                comp.getParent().setPreferredSize(null);
            }
            invalidateLayout(comp.getParent());
        }
        else {
            UsageException errorVar = new UsageException("constraints must be an instance of grid bag constraints or grid cell");
            ErrorMgr.addError(errorVar);
            throw errorVar;
        }
    }

    /**
     * Remove a component from the grid field
     */
    public void removeLayoutComponent(Component comp) {

        Container parent = comp.getParent();
        // TF:15/8/07:Only remove rows and columns if we're collapsed
        if (parent != null && collapsed){
            // We're trying to see if there are any components with the same row and column
            // as the passed component. If there aren't any, we'll remove the row/column
            GridCell parentCell = findConstraints(comp);
            boolean hasRow = false;
            boolean hasCol = false;
            int len = parent.getComponentCount();
            for (int k = 0; k < len; k++){
                Component thisComp = parent.getComponent(k);
                if (thisComp != comp) {
                    GridCell gc = findConstraints(thisComp);
                    hasRow |= (gc.row == parentCell.row);
                    hasCol |= (gc.column == parentCell.column);
                }
            }
            if (!hasRow) {
                rowWeights.remove(parentCell.row);
                rows--;
            }
            if (!hasCol) {
                columnWeights.remove(parentCell.column);
                columns--;
            }
            // Now we must fix up any constraints otherwise they'll be outside the column
            for (int k = 0; k < len; k++){
                Component thisComp = parent.getComponent(k);
                if (thisComp != comp) {
                    GridCell gc = findConstraints(thisComp);
                    if (!hasRow && gc.row >= parentCell.row) {
                        gc.row--;
                    }
                    if (!hasCol && gc.column >= parentCell.column) {
                        gc.column--;
                    }
                }
            }
        }
        invalidateLayout(parent);
        helper.removeLayoutComponent(comp);
        comp.removeComponentListener(this);
        comp.removePropertyChangeListener(this);

        // Also reset the minimum layout size to force re-calculation
        if (parent != null) {
            parent.setMinimumSize(null);
            parent.setPreferredSize(null);
        }
    }


    /**
     * Return whether the grid field is collapsed or not
     * @return true if the grid field is collapsed
     */
    public boolean isCollapsed() {
        return collapsed;
    }

    /**
     * Set the height-wise matrix partner for this grid field. Stored on the layout
     * manager because this property affects only the layout
     * @param partner
     */
    public void setHeightMatrixPartner(GridField partner) {
        this.heightMatrixPartner = partner;
    }

    /**
     * Return the height-wise matrix partner for the associated grid field.
     * @return
     */
    public GridField getHeightMatrixPartner() {
        return heightMatrixPartner;
    }

    /**
     * Set the width-wise matrix partner for this grid field. Stored on the layout
     * manager because this property affects only the layout
     * @param partner
     */
    public void setWidthMatrixPartner(GridField partner) {
        this.widthMatrixPartner = partner;
    }

    /**
     * Return the width-wise matrix partner for the associated grid field.
     * @return
     */
    public GridField getWidthMatrixPartner() {
        return widthMatrixPartner;
    }

    /**
     * Remove a column from the grid field. The position is specified by a zero-based index
     * which specifies the column to remove from the passed grid. If pForce is true, the
     * column will be removed irrespective of whether it's empty or not. If pForce is false,
     * the column will be removed only if it's empty.
     * @param pField the grid from which to remove the column
     * @param pPosition the position of the column, a
     * zero based index which must be in the range [0,columns) or an UsageException
     * will be thrown.
     * @param pForce whether non-empty columns should be deleted or not.
     */
    public void deleteColumn(GridField pField, int pPosition, boolean pForce) {
        if (pPosition < 0 || pPosition >= columns) {
            UsageException errorVar = new UsageException("pPosition must be in the range (0->" + (columns-1));
            ErrorMgr.addError(errorVar);
            throw errorVar;
        }
        int count = pField.getComponentCount();
        // Disable the collapse-on-delete feature temporarily, otherwise the logic becomes more
        // tricky as we have to determine whether to delete the column or not.
        boolean oldCollapse = collapsed;
        collapsed = false;
        for (int i = 0; i < count; i++) {
            Component c = pField.getComponent(i);
            GridCell gc = findConstraints(c);
            if (gc.column == pPosition) {
                if (pForce) {
                    pField.remove(c);
                }
                else {
                    return;
                }
            }
        }
        collapsed = oldCollapse;
        // Now adjust the indicies of the grid field components
        for (int i = 0; i < count; i++) {
            Component c = pField.getComponent(i);
            GridCell gc = findConstraints(c);
            if (gc.column >= pPosition) {
                gc.column--;
            }
        }
        columnWeights.remove(pPosition-1);
        colStartLocs = null;
        colWidths = null;
        columns--;
    }

    /**
     * Add a column into the model at the passed position. The position
     * indicies are 1-based. If the collapsed attribute is true,
     * this method does nothing. This method must be called on the EDT
     */
    public void insertColumn(GridField parent, int position) {
        for (Component c : parent.getComponents()) {
            GridCell cell = findConstraints(c);
            // Adjust the grid location if we need to. Note that the col is
            // 0-based, but the position is 1-based, so we convert to 0-based.
            // This is not the most efficient code, but it is more understandable
            if (cell.column >= position-1) {
                cell.column++;
            }
        }
        columns++;
        columnWeights.add(position-1, new Integer(0));
        colStartLocs = null;
        colWidths = null;
    }

    /**
     * Remove a row from the grid field. The position is specified by a zero-based index
     * which specifies the row to remove from the passed grid. If pForce is true, the
     * column will be removed irrespective of whether it's empty or not. If pForce is false,
     * the column will be removed only if it's empty.
     * @param pField the grid from which to remove the row
     * @param pPosition the position of the row, a
     * zero based index which must be in the range [0,columns) or an UsageException
     * will be thrown.
     * @param pForce whether non-empty rows should be deleted or not.
     */
    public void deleteRow(GridField pField, int pPosition, boolean pForce) {
        if (pPosition < 0 || pPosition >= rows) {
            UsageException errorVar = new UsageException("pPosition must be in the range (0->" + (rows-1));
            ErrorMgr.addError(errorVar);
            throw errorVar;
        }
        int count = pField.getComponentCount();
        // Disable the collapse-on-delete feature temporarily, otherwise the logic becomes more
        // tricky as we have to determine whether to delete the column or not.
        boolean oldCollapse = collapsed;
        collapsed = false;
        for (int i = 0; i < count; i++) {
            Component c = pField.getComponent(i);
            GridCell gc = findConstraints(c);
            if (gc.row == pPosition) {
                if (pForce) {
                    pField.remove(c);
                }
                else {
                    return;
                }
            }
        }
        collapsed = oldCollapse;
        // Now adjust the indicies of the grid field components
        for (int i = 0; i < count; i++) {
            Component c = pField.getComponent(i);
            GridCell gc = findConstraints(c);
            if (gc.row >= pPosition) {
                gc.row--;
            }
        }
        rowWeights.remove(pPosition - 1);
        rowStartLocs = null;
        rowHeights = null;
        rows--;
    }

    /**
     * Add a column into the model at the passed position. The position
     * indicies are 1-based. If the collapsed attribute is true,
     * this method does nothing. This method must be called on the EDT
     *
     * What happens if this position if > columns (eg columns + 10)?
     */
    public void insertRow(GridField parent, int position) {
        for (Component c : parent.getComponents()) {
            GridCell cell = findConstraints(c);
            // Adjust the grid location if we need to. Note that the row is
            // 0-based, but the position is 1-based, so we convert to 0-based.
            if (cell.row >= position-1) {
                cell.row++;
            }
        }
        rowStartLocs = null;
        rowHeights = null;
        rows++;
        rowWeights.add(position-1, new Integer(0));
    }

    /**
     * Sets the number of rows in the grid field to the passed number
     * of rows. If the collapsed attribute is true,
     * this method does nothing. This method must be called on the EDT
     */
    public void setRows(GridField parent, int pRows) {
        if (this.collapsed) {
            return;
        }
        int usedRows = 0;
        for (Component c : parent.getComponents()) {
            GridCell cell = findConstraints(c);
            if (cell.row > usedRows) {
                usedRows = cell.row;
            }
        }
        rowStartLocs = null;
        rowHeights = null;
        rows = Math.max(usedRows, pRows);
        parent.invalidate();
    }

    /**
     * Sets the number of rows in the grid field to the passed number
     * of rows. If the collapsed attribute is true,
     * this method does nothing. This method must be called on the EDT
     */
    public void setColumns(GridField parent, int pColumns) {
        if (this.collapsed) {
            return;
        }
        int usedCols = 0;
        for (Component c : parent.getComponents()) {
            GridCell cell = findConstraints(c);
            if (cell.column > usedCols) {
                usedCols = cell.column;
            }
        }
        colStartLocs = null;
        columnWidths = null;
        columns = Math.max(usedCols, pColumns);
        parent.invalidate();
    }

    /**
     * Set the "collapseOnDelete" property of the grid field. This property dictates whether
     * empty columns and rows of the grid field are to be removed or not.
     * @param field
     * @param pCollapsed
     */
    public void setCollapsed(GridField field, boolean pCollapsed) {
        if (collapsed == pCollapsed) return;
        collapsed = pCollapsed;
        if (collapsed) {
            // See if there are any rows or columns that are now obsolete and remove them
            int[] rowCounts = new int[rows];
            int[] colCounts = new int[columns];
            boolean[] deleteRow = new boolean[rows];
            boolean[] deleteColumn = new boolean[columns];

            int count = field.getComponentCount();
            for (int i = 0; i < count; i++) {
                Component c = field.getComponent(i);
                GridCell gc = findConstraints(c);
                rowCounts[gc.row]++;
                colCounts[gc.column]++;
            }
            // Now alter the row and column counts so that the number in the i-th
            // cell is the number of blank row/columns up to and including this one
            int sum = 0;
            for (int i = 0; i < rows; i++) {
                if (rowCounts[i] == 0) {
                    // There's nothing in this row, increment the sum
                    deleteRow[i] = true;
                    sum++;
                }
                // Ok, now set the current count to the number of rows deleted up to this point, ie sum
                rowCounts[i] = sum;
            }
            // Now repeat for columns
            sum = 0;
            for (int i = 0; i < columns; i++) {
                if (colCounts[i] == 0) {
                    deleteColumn[i] = true;
                    sum++;
                }
                colCounts[i] = sum;
            }

            // For each component, subtract off the rowCount[gridy] and colCount[gridx]
            for (int i = 0; i < count; i++) {
                GridCell gc = findConstraints(field.getComponent(i));
                if (gc != null) {
                    gc.column -= colCounts[gc.column];
                    gc.row-= rowCounts[gc.row];
                }
            }
            // Iterate over the row and columns deleteing as appropriate. We must do this backward
            for (int i = rows-1; i >= 0; i++) {
                if (deleteRow[i]) {
                    rowWeights.remove(i);
                    rows--;
                }
            }
            for (int i = columns-1; i >= 0; i++) {
                if (deleteColumn[i]) {
                    columnWeights.remove(i);
                    columns--;
                }
            }
            colStartLocs = null;
            colWidths = null;
            rowStartLocs = null;
            rowHeights = null;
            field.invalidate();
        }
    }

    public int getColumnAlignment() {
        return this.colAlignment;
    }

    public void setColumnAlignment(int pAlignment) {
        switch (pAlignment) {
        case Constants.CA_CENTER:
        case Constants.CA_JUSTIFY:
        case Constants.CA_LEFT:
        case Constants.CA_RIGHT:
            this.colAlignment = pAlignment;
            break;
        default:
            UsageException errorVar = new UsageException("Column alignment passed invalid value of " + pAlignment);
            ErrorMgr.addError(errorVar);
            throw errorVar;
        }
    }

    public int getRowAlignment() {
        return this.rowAlignment;
    }

    public void setRowAlignment(int pAlignment) {
        switch (pAlignment) {
        case Constants.RA_CENTER:
        case Constants.RA_JUSTIFY:
        case Constants.RA_TOP:
        case Constants.RA_BOTTOM:
            this.rowAlignment = pAlignment;
            break;
        default:
            UsageException errorVar = new UsageException("Row alignment passed invalid value of " + pAlignment);
            ErrorMgr.addError(errorVar);
            throw errorVar;
        }
    }

    /**
     * Set the weight of the passed column to the passed value. Values less than 0, or outside the range
     * of available columns are ignored. We also need to map from a 1-based column index to 0-based.
     * @param pColumn
     * @param pWeight
     */
    public void setColumnJustifyWeight(int pColumn, int pWeight) {
        pColumn--;
        if (pColumn >= 0 && pColumn < columnWeights.size() && pWeight >= 0) {
            this.columnWeights.set(pColumn, new Integer(pWeight));
            colStartLocs = null;
            colWidths = null;
            rowStartLocs = null;
            rowHeights = null;
        }
    }

    /**
     * Set the column justify weights, all in one shot. This method will fail and return false if
     * the weights.length != columns or any weight < 0
     * @param weights
     * @return
     */
    public boolean setColumnJustifyWeights(int[] weights) {
        if (weights.length != columns) {
            return false;
        }
        for (int weight : weights) {
            if (weight < 0) {
                return false;
            }
        }
        for (int i = 0; i < weights.length; i++) {
            this.columnWeights.set(i, weights[i]);
        }
        // Invalid the layout
        colStartLocs = null;
        colWidths = null;
        rowStartLocs = null;
        rowHeights = null;
        return true;
    }

    /**
     * Set the weight of the passed column to the passed value. Values less than 0, or outside the range
     * of available columns are ignored. The index passed should be 1-based and will be converted to a
     * 0-based column weight
     * @param pColumn
     * @param pWeight
     */
    public void setRowJustifyWeight(int pRow, int pWeight) {
        pRow--;
        if (pRow >= 0 && pRow < rowWeights.size() && pWeight >= 0) {
            this.rowWeights.set(pRow, new Integer(pWeight));
            colStartLocs = null;
            colWidths = null;
            rowStartLocs = null;
            rowHeights = null;
        }
    }

    /**
     * Set the row justify weights, all in one shot. This method will fail and return false if
     * the weights.length != rows or any weight < 0
     * @param weights
     * @return
     */
    public boolean setRowJustifyWeights(int[] weights) {
        if (weights.length != rows) {
            return false;
        }
        for (int weight : weights) {
            if (weight < 0) {
                return false;
            }
        }
        for (int i = 0; i < weights.length; i++) {
            this.rowWeights.set(i, weights[i]);
        }     
        colStartLocs = null;
        colWidths = null;
        rowStartLocs = null;
        rowHeights = null;
        return true;
    }

    public int[] getRowJustifyWeights() {
        int[] result = new int[rows];
        for (int i = 0; i < rows; i++) {
            result[i] = this.rowWeights.get(i);
        }
        return result;
    }

    public int[] getColumnJustifyWeights() {
        int[] result = new int[columns];
        for (int i = 0; i < columns; i++) {
            result[i] = this.columnWeights.get(i);
        }
        return result;
    }

    protected GridCell findConstraints(Component comp) {
        return GridField.getConstraints((JComponent)comp);
    }

    private int sum(int[] pArray) {
        int sum = 0;
        for (int item : pArray) {
            sum += item;
        }
        return sum;
    }

    /**
     * An optimisation field that caches the width of the title. If this value is
     * < 0, it has not been set yet.
     */
    private int cachedTitleWidth = -1;
    /**
     * Calculate the width of the title of the passed grid field. If the
     * grid field doesn't have a caption, this method returns 0
     * @param parentGrid
     * @return The width of the title, or 0 if there is no title.
     */
    public int getTitleWidth(GridField parentGrid) {
        if (cachedTitleWidth < 0) {
            int captionWidth = 0;
            TextData tdCaption = parentGrid.getCaption();
            String caption = (tdCaption == null) ? "": tdCaption.toString();
            if ("".equals(caption)) {
                captionWidth = 0;
            }
            else {
                Font font = null;
                if (parentGrid.getCaptionFont() != null)
                    font = parentGrid.getCaptionFont();
                else
                    font = parentGrid.getFont();
                FontMetrics fm = parentGrid.getFontMetrics(font);
                // TF:21/8/07:Set the caption to be a little bigger to allow some dashes on the side.
                captionWidth = SwingUtilities.computeStringWidth(fm, caption) + 10;
            }
            _log.debug("\tCaption width for \"" + caption + "\" = " + captionWidth);
            cachedTitleWidth = captionWidth;
        }
        return cachedTitleWidth;
    }

    private int getRowDistributionStrategy() {
        switch (rowAlignment) {
            case Constants.RA_BOTTOM:  return cBEFORE;
            case Constants.RA_CENTER:  return cCENTRE;
            case Constants.RA_TOP:    return cAFTER;
            default:          return cJUSTIFY;
        }
    }

    private int getColumnDistributionStrategy() {
        switch (colAlignment) {
            case Constants.CA_RIGHT:  return cBEFORE;
            case Constants.CA_CENTER:  return cCENTRE;
            case Constants.CA_LEFT:    return cAFTER;
            default:          return cJUSTIFY;
        }
    }

    public void layoutContainer(Container parent) {
        LayoutManagerHelper.setDebugBackgroundColor(parent);
        LayoutManagerHelper.makeDebugTooltip((JComponent)parent);
        performLayout(parent, true);
    }

    /**
     * This is the default size of the grid. This is the amount of memory that will initially
     * get allocated to the following arrays. The arrays are statically populated rather than
     * allocated each time through the layout to increase throughput at the cost of memory,
     * because the layout is called frequently and needs to be efficient
     */
    private static final int DEFAULT_GRID_SIZE = 10;
    private boolean[] rowPopulated = new boolean[DEFAULT_GRID_SIZE];
    private boolean[] columnPopulated = new boolean[DEFAULT_GRID_SIZE];
    private int[] minHeights = new int[DEFAULT_GRID_SIZE];
    private int[] minWidths = new int[DEFAULT_GRID_SIZE];

    /**
     * Initialise the variables to perform the layout, clearing them or
     * populating them as needed
     */
    private void initialiseLayout() {
        if (rowPopulated.length < rows) {
            rowPopulated = new boolean[rows];
            minHeights = new int[rows];
        }
        if (columnPopulated.length < columns) {
            columnPopulated = new boolean[columns];
            minWidths = new int[columns];
        }

        Arrays.fill(rowPopulated, 0, rows, false);
        Arrays.fill(columnPopulated, 0, columns, false);

        // Get the minimum sizes of each cell.
        // Clear out the whole array.  TF+CraigM: 19/03/2008
        Arrays.fill(minHeights, 0, minHeights.length, 0);
        Arrays.fill(minWidths, 0, minWidths.length, 0);

        if (rows != myMinHeights.length) {
            myMinHeights = new int[rows];
        }

        if (columns != myMinWidths.length) {
            myMinWidths = new int [columns];
        }
    }
   
    /**
     * Layout this container. If this method is passed pDoLayout == false,
     * then the layout dimensions will be calculated, but no layout (ie
     * adjusting of sizes or locations of children) will be performed.
     * @param parent
     * @param pDoLayout
     * @return The minimum size of this grid field
     */
    public Dimension performLayout(Container parent, boolean pDoLayout) {
// Temp debugging
//System.out.println("parent: " + parent);
//Exception e = new Exception();
//e.fillInStackTrace();
//if (e.getStackTrace().length > 800) {
//  System.out.println("Possible infinite recusion!");
//}
        // TF:8/11/07: We need to know which rows and columns have components in them. Initially
        // we used if the minimum size was 0 to determine this, but if we have a child that is
        // size to parent with no minimum size, this algorithm fails.
        // TF:23/11/07:Optimisation: We no longer use local arrays, they are now class members to
        // prevent lots of extra memory allocations. The arrays' sizes are all kept in sync so
        // we only need to do the test once. We allocate more than we need just in case the array
        // grows some more...
      initialiseLayout();

        Insets borderInsets = parent.getInsets();
        GridField grid = (GridField)parent;

        Component[] comps = parent.getComponents();
        for (Component aComp : comps) {
          computeSizesBasedOnChild(grid, aComp);
        }

        // The width is the sum of the cell widths + the border margins
        boolean hasRowSeparators = grid.horzDividerWeight != Constants.W_NONE && grid.horzDividerWeight != Constants.W_DEFAULT;
        boolean hasColumnSeparators = grid.vertDividerWeight != Constants.W_NONE && grid.vertDividerWeight != Constants.W_DEFAULT;

        int minWidth = 0, minHeight = 0;

        // Calculate the minimum width of the grid field
        // TF:10/05/2008:We must adjust the min column width prior to calculating the matrix partners
        if (!hasColumnSeparators) {
          adjustColumnMinWidthsForNoSeparators(grid);
        }
        setLocalMinWidths();
        compensateMinWidthsForMatrixPartners(grid);


        minWidth = sum(minWidths);
        if (hasColumnSeparators) {
            // TF:19/9/07:Handle empty columns properly
            int nonZeroWidthColums = 0;
            for (int thisWidth : minWidths) {
                if (thisWidth > 0) {
                    nonZeroWidthColums++;
                }
            }
            minWidth += UIutils.getLineWeightInPixels(grid.vertDividerWeight) * Math.max(0, nonZeroWidthColums-1);
        }

       
        // Now repeat the same calculations for heights
        // TF:10/05/2008:We must adjust the min row heights prior to calculating the matrix partners
        if (!hasRowSeparators) {
          adjustRowMinHeightsForNoSeparators(grid);
        }

        setLocalMinHeights();
        compensateMinHeightsForMatrixPartners(grid);

        minHeight = sum(minHeights);
        if (hasRowSeparators) {
            // TF:19/9/07:Handle empty rows properly
            int nonZeroHeightRows = 0;
            for (int thisHeight : minHeights) {
                if (thisHeight > 0) {
                    nonZeroHeightRows++;
                }
            }
            minHeight += UIutils.getLineWeightInPixels(grid.horzDividerWeight) * Math.max(0, nonZeroHeightRows-1);
        }

        minHeight += borderInsets.top + borderInsets.bottom;
        minWidth += borderInsets.left + borderInsets.right;

        // We need to add some other factors into the mix here, like title widths, explicit and minimum sizes and so
        // on. However, we need to keep out aggregated child minimums around so we know how to re-distribute the extra space if any.
        int aggregatedChildWidth = minWidth;
        int aggregatedChildHeight = minHeight;

        int titleWidth = getTitleWidth((GridField)parent);
        if (titleWidth > minWidth) {
            minWidth = titleWidth;
        }

        // Our absolute minimum is the grid minWidth and minHeight properties
        if (minWidth < grid.minWidth) {
            minWidth = grid.minWidth;
        }
        if (minHeight < grid.minHeight) {
            minHeight = grid.minHeight;
        }

        // If we have no children, our absolute minimum size is 60x60 pixels
        // TF:13/9/07:This only applies if we have 0 kids, not if there are no visible ones
        if (comps.length == 0) {
            minWidth = Math.max(minWidth, 60);
            minHeight = Math.max(minHeight, 60);
        }
        // If we're using explicit layout, the min size is the greater of the min size and the current size
        Dimension currentSize = parent.getSize();
        GridCell parentCell = GridField.getConstraints(grid);
        if (parentCell.widthPolicy == Constants.SP_EXPLICIT) {
            minWidth = Math.max(minWidth, currentSize.width);
        }
        if (parentCell.heightPolicy == Constants.SP_EXPLICIT) {
            minHeight = Math.max(minHeight, currentSize.height);
        }

        // minSize is the smallest size that this grid can reduce down to. For Natural fields, this is as calculated. For
        // Explicit fields, it's the larger of this and the current minSize. For Parent, it's also this number
        Dimension minSize = new Dimension(minWidth, minHeight);
        if (pDoLayout) {
            // We need to layout all the components, compensating for additional room
            int widthPolicy = parentCell.getWidthPolicy();
            int heightPolicy = parentCell.getHeightPolicy();

            int newWidth = minWidth, newHeight = minHeight;

            if (widthPolicy == Constants.SP_TO_PARTNER || heightPolicy == Constants.SP_TO_PARTNER) {
              // TF:02/05/2008:We need to ensure that our size remains the size of the partnership
              Dimension currentMinSize = LayoutManagerHelper.getMinimumSize(parent);
              if (widthPolicy == Constants.SP_TO_PARTNER) {
                newWidth = currentMinSize.width;
              }
              if (heightPolicy == Constants.SP_TO_PARTNER) {
                newHeight = currentMinSize.height;
              }
            }
           
            if (widthPolicy == Constants.SP_TO_PARENT) {
                newWidth = Math.max(currentSize.width, minWidth);
            }

            if (heightPolicy == Constants.SP_TO_PARENT) {
                newHeight = Math.max(currentSize.height, minHeight);
            }

            // Now determine all the column widths and start locations based on the minimum size and the column weights
            colStartLocs = new int[columns];
            colWidths = new int[columns];
            int separatorSize = 0;
            if (hasColumnSeparators) {
                separatorSize = UIutils.getLineWeightInPixels(grid.vertDividerWeight);
            }
            distributeSpace(columns, colStartLocs, colWidths, columnWeights, minWidths, columnPopulated, borderInsets.left, newWidth - aggregatedChildWidth, separatorSize, getColumnDistributionStrategy());

            // Repeat for rows
            rowStartLocs = new int[rows];
            rowHeights = new int[rows];
            separatorSize = 0;
            if (hasRowSeparators) {
                separatorSize = UIutils.getLineWeightInPixels(grid.horzDividerWeight);
            }
            distributeSpace(rows, rowStartLocs, rowHeights, rowWeights, minHeights, rowPopulated, borderInsets.top, newHeight - aggregatedChildHeight, separatorSize, getRowDistributionStrategy());

            sizeAndLocateChildren(comps, grid, colStartLocs, rowStartLocs, colWidths, rowHeights);

            // Set our minimum size and actual size and preferred size (latter is option, but can't hurt)
            boolean hasChanged = false;
            // TF:18/9/07: We are adjusting our sizes ourself, don't fire the change event logic.
            this.isAdjusting = true;
            if (!parent.isMinimumSizeSet() || !minSize.equals(parent.getMinimumSize())) {
                parent.setMinimumSize(minSize);
                hasChanged = true;
            }

            if (currentSize.width != newWidth || currentSize.height != newHeight) {
                // TF:21/9/07: Viewports have special rules for laying out. If our preferrred size < view size, the
                // viewport will resize the component to be the viewport size. This is fine if we're using parent,
                // but otherwise we need to trick the viewport and just accept the size it's given us
                if (parent.getParent() != null && parent.getParent() instanceof JViewport) {
                    if (newWidth > currentSize.width || newHeight > currentSize.height) {
                        hasChanged = true;
                    }
                }
                else {
                    hasChanged = true;
                }
                if (hasChanged) {
                    parent.setSize(newWidth, newHeight);
                    parent.setPreferredSize(new Dimension(newWidth, newHeight));
                }
            }
            this.isAdjusting = false;
            // If this component has changed size, force a re-layout
            if (hasChanged) {
                // If our parent's parent is a grid field, it will be listening for a change to both the
                // minimum size and the preferred size, and re-layout itself then, so we don't need to
                // explicitly tell it that we've changed.
                if (!(parent.getParent() instanceof GridField)) {
                    parent.invalidate();
                    if (parent.getParent() != null) {
                      //PM:18/3/08 chance to invalidate to stop infinite layout
                      // TF:10/05/2008:Changed this back to validate to allow window forms to re-layout
                      // properly when an underlying child changes size to force them to expand.
                        parent.getParent().validate();
                        parent.getParent().invalidate();
                    }
                }
                this.relayoutPartnersifNeeded(parent, minWidths, minHeights);
            }
        }
        if (parent.isMinimumSizeSet() && parent.getMinimumSize().equals(minSize)) {
            // Already set to what it should be, do nothing
        }
        else {
            this.isAdjusting = true;
            parent.setMinimumSize(minSize);
            this.isAdjusting = false;
        }
        return minSize;
    }

  /**
     * If the height policy of this grid field is SP_TO_MATRIX_PARTNER, we need to set the
     * minHeights to the widest of the partner local minimum heights
     * @param grid
     */
    private void compensateMinHeightsForMatrixPartners(GridField grid) {
        if (LayoutManagerHelper.getHeightPolicy(grid) == Constants.SP_TO_MATRIX_PARTNER) {
            GridField partner = this.heightMatrixPartner;
            while (partner != null && partner != grid) {
                int length = Math.min(rows, partner.layout.myMinHeights.length);
                for (int i = 0; i < length; i++) {
                    if (partner.layout.myMinHeights[i] > minHeights[i]) {
                        minHeights[i] = partner.layout.myMinHeights[i];
                    }
                }
                partner = partner.getHeightMatrixPartner();
            }
        }

  }

  /**
     * If the width policy of this grid field is SP_TO_MATRIX_PARTNER, we need to set the
     * minWidths to the widest of the partner local minimum widths
     * @param grid
     */
    private void compensateMinWidthsForMatrixPartners(GridField grid) {
        // We may need to take care of width and height partnerships too, to adjust the min sizes
        if (LayoutManagerHelper.getWidthPolicy(grid) == Constants.SP_TO_MATRIX_PARTNER) {
            GridField partner = this.widthMatrixPartner;
            while (partner != null && partner != grid) {
                int length = Math.min(columns, partner.layout.myMinWidths.length);
                for (int i = 0; i < length; i++) {
                    if (partner.layout.myMinWidths[i] > minWidths[i]) {
                        minWidths[i] = partner.layout.myMinWidths[i];
                    }
                }
                partner = partner.getWidthMatrixPartner();
            }
        }
  }

  /**
     * For a child in the grid field, calculate what the total effect of that child is
     * on the minimum widths / heights of the grid field, and also help determine whether
     * a particular row or column in a grid field is populated or not.
     * @param grid
     * @param aComp
     */
    private void computeSizesBasedOnChild(GridField grid, Component aComp) {
        if (aComp.isVisible() || !ignoreInvisibleChildren) {
            GridCell cell = findConstraints(aComp);
            // TF:18/9/07: If we're getting the minimum size of our child, we're about to re-lay it out
            // anyway. So ignore the minimum size change events that will be fired if it hasn't computed
            // it's minimum size before, or if it's changed it's min size
            this.isAdjusting = true;
            Dimension minSize = LayoutManagerHelper.getMinimumSize(aComp);
            this.isAdjusting = false;
            int width = minSize.width;
            int height = minSize.height;

            // The width of this cell is it's size + max(grid.defaultcell margins, cell.cellmargin)
            width += Math.max(grid.leftMargin, cell.leftMargin) + Math.max(grid.rightMargin, cell.rightMargin);
            height += Math.max(grid.topMargin, cell.topMargin) + Math.max(grid.bottomMargin, cell.bottomMargin);

            if (width > minWidths[cell.column]) {
                minWidths[cell.column] = width;
            }
            if (height > minHeights[cell.row]) {
                minHeights[cell.row] = height;
            }
            rowPopulated[cell.row] = true;
            columnPopulated[cell.column] = true;
        }
  }

  /**
     * Copy the minimum heights (minHeights) into the myMinHeights array
     */
    private void setLocalMinHeights() {
        for (int i = 0; i < rows; i++) {
            myMinHeights[i] = minHeights[i];
        }
  }

    /**
     * Copy the minimum widths (minWidths) into the myMinWidths array
     */
    private void setLocalMinWidths() {
        for (int i = 0; i < columns; i++) {
            myMinWidths[i] = minWidths[i];
        }
  }

    /**
   * Strangely, in Forte, the presence of the separator lines also affects the layout. Consider 2 buttons in a grid with line separator:
   *  <pre>
   *      +------+       |       +------+
   *      |  b1  |<--d-->|<--d-->|  b2  |
   *      +------+       |       +------+
   *  </pre>   
   * However, without separators, the distance inter-cell is reduced:
   * <pre>
   *      +------+       +------+
   *      |  b1  |<--d-->|  b2  |
   *      +------+       +------+
   * </pre>    
   * This makes the grid look pretty, but introduces a little bit of fudging here and there... <p>
   * <p>
     * If there are no separators, our inter-cell spacing is the maximum of the top and bottom margins, so reduce each cell
   * by min(bottomMargin of i-1, topMargin of i) + min(bottomMargin of i, topMargin of i+1)
   * However, the margins are constant on the grid (individual cell margins should be reduced by the same amount
   * as above so we can ignore them) so reduce width by 2*min(bottomMargin, topMargin) for all cells not the
   * first or last, and by min(bottomMargin, topMargin) for these 2 outer cells (first, last) as they still require
   * the full distance to the outer edge of the grid. We will however then be double counting these spaces. So if
   * we have another cell on our top, reduce size by min(bottomMargin, topMargin)/2 and the same amount if we have
   * a bottom margin. (Note we calculate these different ways to avoid rounding errors.
     * @param grid
     */
  private void adjustRowMinHeightsForNoSeparators(GridField grid) {
        // Find the first and last visible rows
        int firstVisible = -1;
        int lastVisible = -1;
        for (int i = 0; i < rows; i++) {
            if (minHeights[i] > 0) {
                if (firstVisible < 0) {
                    firstVisible = i;
                }
                lastVisible = i;
            }
        }

        int distance = Math.min(grid.topMargin, grid.bottomMargin);
        if (distance > 0) {
            int topDistance = distance / 2;
            int bottomDistance = distance - topDistance;
            for (int i = 0; i < rows; i++) {
                if (i > firstVisible ) {
                    minHeights[i] -= topDistance;
                }
                if (i < lastVisible) {
                    minHeights[i] -= bottomDistance;
                }
                // TF:18/9/07: Added check in to ensure that empty columns are handled properly
                if (minHeights[i] < 0) {
                    minHeights[i] = 0;
                }
            }
        }
  }
 
  /**
   * Strangely, in Forte, the presence of the separator lines also affects the layout. Consider 2 buttons in a grid with line separator:
   *  <pre>
   *      +------+       |       +------+
   *      |  b1  |<--d-->|<--d-->|  b2  |
   *      +------+       |       +------+
   *  </pre>   
   * However, without separators, the distance inter-cell is reduced:
   * <pre>
   *      +------+       +------+
   *      |  b1  |<--d-->|  b2  |
   *      +------+       +------+
   * </pre>    
   * This makes the grid look pretty, but introduces a little bit of fudging here and there... <p>
   * <p>
   * If there are no separators, our inter-cell spacing is the maximum of the left and right margins, so reduce each cell
   * by min(rightMargin of i-1, leftMargin of i) + min(rightMargin of i, leftMargin of i+1)
   * However, the margins are constant on the grid (individual cell margins should be reduced by the same amount
   * as above so we can ignore them) so reduce width by 2*min(rightMargin, leftMargin) for all cells not the
   * first or last, and by min(rightMargin, leftMargin) for these 2 outer cells (first, last) as they still require
   * the full distance to the outer edge of the grid. We will however then be double counting these spaces. So if
   * we have another cell on our left, reduce size by min(rightMargin, leftMargin)/2 and the same amount if we have
   * a right margin. (Note we calculate these different ways to avoid rounding errors.

   * @param grid
   */
  private void adjustColumnMinWidthsForNoSeparators(GridField grid) {
        // Find the first and last visible columns
        int firstVisible = -1;
        int lastVisible = -1;
        for (int i = 0; i < columns; i++) {
            if (minWidths[i] > 0) {
                if (firstVisible < 0) {
                    firstVisible = i;
                }
                lastVisible = i;
            }
        }

        int distance = Math.min(grid.rightMargin, grid.leftMargin);
        if (distance > 0) {
            int leftDistance = distance / 2;
            int rightDistance = distance - leftDistance;
            for (int i = 0; i < columns; i++) {
                // TF:19/9/07:Need the first and last visible columns instead of the first and last physical columns
                if (i > firstVisible ) {
                    minWidths[i] -= leftDistance;
                }
                if (i < lastVisible) {
                    minWidths[i] -= rightDistance;
                }
                // TF:18/9/07: Added check in to ensure that empty columns are handled properly
                if (minWidths[i] < 0) {
                    minWidths[i] = 0;
                }
            }
        }
  }

  /**
     * Determine if any of our partners need to re-lay themselves out as a result of us laying
     * ourself out, and if they do, allow them to do so. We must do this after we've finished
     * correcting our own sizes and only if our sizes have changed.
     * @param parent
     * @param minWidths
     * @param minHeights
     */
    private void relayoutPartnersifNeeded(Container parent, int[] minWidths, int[] minHeights) {
        ArrayList<GridField> partnersToReLayout = null;
        // If our size has changed, we may need to re-layout our partners
        if (LayoutManagerHelper.getWidthPolicy(parent) == Constants.SP_TO_MATRIX_PARTNER) {
            GridField partner = this.widthMatrixPartner;
            while (partner != null && partner != parent) {
                int length = Math.min(columns, partner.layout.myMinWidths.length);
                for (int i = 0; i < length; i++) {
                    if (partner.layout.myMinWidths[i] < minWidths[i]) {
                        if (partnersToReLayout == null) {
                            partnersToReLayout = new ArrayList<GridField>();
                        }
                        if (partnersToReLayout.indexOf(partner) < 0) {
                            partnersToReLayout.add(partner);
                        }
                    }
                }
                partner = partner.getWidthMatrixPartner();
            }
        }

        if (LayoutManagerHelper.getHeightPolicy(parent) == Constants.SP_TO_MATRIX_PARTNER) {
            GridField partner = this.heightMatrixPartner;
            while (partner != null && partner != parent) {
                int length = Math.min(rows, partner.layout.myMinHeights.length);
                for (int i = 0; i < length; i++) {
                    if (partner.layout.myMinHeights[i] < minHeights[i]) {
                        if (partnersToReLayout == null) {
                            partnersToReLayout = new ArrayList<GridField>();
                        }
                        if (partnersToReLayout.indexOf(partner) < 0) {
                            partnersToReLayout.add(partner);
                        }
                    }
                }
                partner = partner.getHeightMatrixPartner();
            }
        }

        // Now lay out the other grid fields that require it.
        if (partnersToReLayout != null) {
            for (GridField partner : partnersToReLayout) {
                partner.doLayout();
            }
        }
    }

    private void sizeAndLocateChildren(Component[]children, GridField grid, int[] colStartLocs, int[] rowStartLocs, int[] colWidths, int[]rowHeights) {
        // TF:23/11/07:Passed in the children to prevent an unneeded array clone and synchronise block
//        Component[] children = grid.getComponents();
        for (Component child : children) {

            GridCell cell = findConstraints(child);
            int calcX = colStartLocs[(cell== null) ? 0 : cell.column];
            int calcY = rowStartLocs[(cell== null) ? 0 : cell.row];
            int calcWidth = colWidths[(cell== null) ? 0 : cell.column];
            int calcHeight = rowHeights[(cell== null) ? 0 : cell.row];

            Dimension minSize = LayoutManagerHelper.getMinimumSize(child);

            int widthPolicy = (cell== null) ? Constants.SP_DEFAULT : cell.getWidthPolicy();
            int heightPolicy = (cell== null) ? Constants.SP_DEFAULT : cell.getHeightPolicy();
            int gravity = (cell== null) ? Constants.CG_DEFAULT  : cell.cellGravity;
            if (gravity == Constants.CG_INHERIT) {
                gravity = grid.cellGravity;
            }

            boolean hasRowSeparators = grid.horzDividerWeight != Constants.W_NONE && grid.horzDividerWeight != Constants.W_DEFAULT;
            boolean hasColumnSeparators = grid.vertDividerWeight != Constants.W_NONE && grid.vertDividerWeight != Constants.W_DEFAULT;
            int leftMargin, rightMargin;
            if (hasColumnSeparators) {
                leftMargin = Math.max(grid.leftMargin, cell.leftMargin);
                rightMargin = Math.max(grid.rightMargin, cell.rightMargin);
            }
            else {
                int distance = Math.min(grid.leftMargin, grid.rightMargin);
                int leftDistance = distance / 2;
                int rightDistance = distance - leftDistance;
                // TF:9/9/07: Removed an accidental and unfortunate double negative that had crept in here
                leftMargin = Math.max(grid.leftMargin, cell.leftMargin) - ((cell.column > 0) ? leftDistance : 0);
                rightMargin = Math.max(grid.rightMargin, cell.rightMargin) - ((cell.column < columns - 1) ? rightDistance : 0);
            }
            int topMargin, bottomMargin;
            if (hasRowSeparators) {
                topMargin = Math.max(grid.topMargin, cell.topMargin);
                bottomMargin = Math.max(grid.bottomMargin, cell.bottomMargin);
            }
            else {
                int distance = Math.min(grid.topMargin, grid.bottomMargin);
                int topDistance = distance / 2;
                int bottomDistance = distance - topDistance;
                topMargin = Math.max(grid.topMargin, cell.topMargin) - ((cell.row > 0) ? topDistance : 0);
                bottomMargin = Math.max(grid.bottomMargin, cell.bottomMargin) - ((cell.row < rows - 1) ? bottomDistance : 0);
            }

            int newX, newY, newWidth, newHeight;
            switch (widthPolicy) {
                case Constants.SP_TO_PARENT:
                    newWidth = calcWidth - leftMargin - rightMargin;
                    newX = calcX + leftMargin;
                    break;

                case Constants.SP_EXPLICIT:
                case Constants.SP_NATURAL:
                default:
                    newWidth = minSize.width;
                    switch (gravity) {
                    case Constants.CG_BOTTOMLEFT:
                    case Constants.CG_MIDDLELEFT:
                    case Constants.CG_TOPLEFT:
                        newX = calcX + leftMargin;
                        break;
                    case Constants.CG_BOTTOMCENTER:
                    case Constants.CG_CENTER:
                    case Constants.CG_TOPCENTER:
                        newX = (calcX + leftMargin) + (calcWidth - leftMargin - rightMargin - minSize.width)/2;
                        break;
                    default:
                        newX = calcX + calcWidth - minSize.width - rightMargin;
                        break;
                    }
                    break;
            }

            switch (heightPolicy) {
            case Constants.SP_TO_PARENT:
                newHeight = calcHeight - topMargin - bottomMargin;
                newY = calcY + topMargin;
                break;

            case Constants.SP_EXPLICIT:
            case Constants.SP_NATURAL:
            default:
                newHeight = minSize.height;
                switch (gravity) {
                case Constants.CG_TOPCENTER:
                case Constants.CG_TOPLEFT:
                case Constants.CG_TOPRIGHT:
                    newY = calcY + topMargin;
                    break;
                case Constants.CG_MIDDLELEFT:
                case Constants.CG_CENTER:
                case Constants.CG_MIDDLERIGHT:
                    newY = (calcY + topMargin) + (calcHeight - topMargin - bottomMargin - minSize.height)/2;
                    break;
                default:
                    newY = calcY + calcHeight - minSize.height - bottomMargin;
                    break;
                }
                break;
            }

            this.isAdjusting = true;
          // TF:08/12/2008:If we have a child grid with a minimum size which is invisible and sized to parent inside another
          // grid which ignores invisible children, we enter an infinite loop where we try to size the child to 0 which forces
            // it to relayout which sets the size to the minimum size which forces the parent to re-layot, etc....
          if (!child.isVisible() && grid.ignoreInvisibleChildren) {
            if (newWidth < minSize.width) {
              newWidth = minSize.width;
            }
            if (newHeight < minSize.height) {
              newHeight = minSize.height;
            }
          }
          // TF:23/11/07:Replaced this with setBounds which is more efficient
            if (child.getX() != newX || child.getY() != newY || child.getWidth() != newWidth || child.getHeight() != newHeight) {
              child.setBounds(newX, newY, newWidth, newHeight);
              LayoutManagerHelper.setPartnerSizes(child);
            }
            this.isAdjusting = false;
        }
    }

    /**
     * Distribute the extra space if any over the components according to the passed weights. The locations and size arrays must be
     * pre-allocated prior to calling this method but will be populated by this method. This method does not account for the cell
     * margins, because these are specific to the particular cell.
     * @param locations
     * @param sizes
     * @param weights
     * @param minSizes
     * @param startLoc
     * @param extraSpaceToAllocate
     * @param separatorSize
     * @param strategy
     */
    private void distributeSpace(int count, int[] locations, int[] sizes, ArrayList<Integer> weights, int[] minSizes, boolean[] populated, int startLoc, int extraSpaceToAllocate, int separatorSize, int strategy) {
        // TF:19/9/07: If the minSize of a column/row is 0, we don't do anything with it because there's nothing in this column
        // TF:8/11/07: Changed this to use an explicit boolean
        int weightSum = 0;
        int nonZeroWeightCount = 0;
        for (int i = 0; i < count; i++) {
            if (populated[i]) {
                weightSum += ((Integer)weights.get(i)).intValue();
                nonZeroWeightCount++;
            }
        }
        int loc = startLoc;

        if (nonZeroWeightCount > 0) {
            switch(strategy) {
                case cJUSTIFY:
                    double spaceAllocated = 0.0;
                    for (int i = 0; i < count; i++) {
                        // TF:19/9/07: If the minSize of a column/row is 0, we don't do anything with it
                        if (populated[i]) {
                            double extraSpace;
                            locations[i] = loc;
                            if (weightSum == 0) {
                                extraSpace = ((double)extraSpaceToAllocate) / nonZeroWeightCount;
                            }
                            else {
                                extraSpace= ((double)extraSpaceToAllocate) * (((double)weights.get(i)) / weightSum);
                            }
                            // Use a slightly more complicated formula to prevent rounding errors
                            sizes[i] = minSizes[i] + ((int)(spaceAllocated + extraSpace)) - (int)spaceAllocated;
                            spaceAllocated += extraSpace;
                            loc += sizes[i] + separatorSize;
                        }
                    }
                    break;

                case cBEFORE:
                    loc += extraSpaceToAllocate;
                    for (int i = 0; i < count; i++) {
                        locations[i] = loc;
                        sizes[i] = minSizes[i];
                        // TF:19/9/07: If the minSize of a column/row is 0, we don't do anything with it
                        if (populated[i]) {
                            loc += sizes[i] + separatorSize;
                        }
                    }
                    break;

                case cCENTRE:
                    loc += extraSpaceToAllocate / 2;
                    for (int i = 0; i < count; i++) {
                        locations[i] = loc;
                        sizes[i] = minSizes[i];
                        // TF:19/9/07: If the minSize of a column/row is 0, we don't do anything with it
                        if (populated[i]) {
                            loc += sizes[i] + separatorSize;
                        }
                    }
                    break;

                case cAFTER:
                    for (int i = 0; i < count; i++) {
                        locations[i] = loc;
                        sizes[i] = minSizes[i];
                        // TF:19/9/07: If the minSize of a column/row is 0, we don't do anything with it
                        if (populated[i]) {
                            loc += sizes[i] + separatorSize;
                        }
                    }
                    break;
            }
        }
    }

    /**
     * Return the location of the cells (row coordinates only) within this grid field. The returned
     * values are all in the range 0 to Width. However, because of inter-cell drawing, these may be
     * spaced wider than the sum of the row widths.
     * @return
     */
    protected int[] getRowStartLocs(GridField parent) {
        if (rowStartLocs == null) {
            layoutContainer(parent);
        }
        return rowStartLocs;
    }

    protected int[] getRowHeights(GridField parent) {
        if (rowHeights == null) {
            layoutContainer(parent);
        }
        return rowHeights;
    }

    protected int[] getColStartLocs(GridField parent) {
        if (colStartLocs == null) {
            layoutContainer(parent);
        }
        return colStartLocs;
    }

    protected int[] getColWidths(GridField parent) {
        if (colWidths == null) {
            layoutContainer(parent);
        }
        return colWidths;
    }

    /**
     * See where to insert this component based on it's grid bag constraints and the current
     * insert policy. The insert policy may cause other components in the grid to alter their
     * position, for example if the insert policy dictates that we expand the grid then any
     * elements that are in expanded areas are moved. This method will update all the locations
     * of the elements in the grid that need to be adjusted.<p>
     * <p>
     * This method returns true if the component can be inserted into the grid, and false if
     * the component should be ignored.<p>
     * <p>
     * If the ignoreCollapsed
     * @param newOne
     * @return
     */
    protected boolean applyInsertPolicy(GridField parent, JComponent newOne, boolean ignoreCollapse) {
        GridCell gbc = findConstraints(newOne);
        // TF:01/05/2008:During the initial insert into a grid, we want to overwrite anything that
        // already exists in the cell. This is because the superclass to the one defining the grid
        // may have already specified widgets below this one, and if collapse is on then removing
        // the widget prior to inserting this one might have shrunk things up, occupying this widget.
        // Using the replace policy ensures things are done correctly.
        int policyToUse = ignoreCollapse ? Constants.IP_REPLACE : parent.insertPolicy;
        switch (policyToUse) {
            case Constants.IP_REPLACE:
              boolean fallThroughToExpand = false;
                // If either of the row or column passed to this method is -1, we need to
                // replace the element at 0,0
                if (gbc.row == -1 || gbc.column == -1) {
                    gbc.row = 0;
                    gbc.column = 0;
                }
                else if (gbc.row < 0 || gbc.column < 0) {
                    UsageException errorVar = new UsageException("gridx and gridy should be >= 0, not row = " + gbc.row + ", column = " + gbc.column);
                    ErrorMgr.addError(errorVar);
                    throw errorVar;
                }
                else if (gbc.row >= rows || gbc.column >= columns) {
                  // TF:28/04/2008:if ignoreCollapse is true, we're doing the initial insert. In this case we don't
                  // have the rows and columns necessarily set, so we need to add the components in by expanding the grid
                  if (ignoreCollapse) {
                    // TF:09/05/2008:JIRA JCT-545:Since this is the initial insert, we must obey the
                    // constraints as posted. This means that we'll get the correct row and column
                    // even if a row or column appears empty when this element is inserted (because
                    // another element might be inserted later to make the empty row/column populated)
//                    if (isCollapsed()) {
//                      if (gbc.row >= rows) {
//                        gbc.row = rows;
//                      }
//                      if (gbc.column >= columns) {
//                        gbc.column = columns;
//                      }
//                    }
                    for (int i = rows; i <= gbc.row; i++) {
                      rowWeights.add(i, new Integer(0));
                    }
                    for (int i = columns; i <= gbc.column; i++) {
                      columnWeights.add(i, new Integer(0));
                    }
                    rows = Math.max(rows, gbc.row + 1);
                    columns = Math.max(columns, gbc.column + 1);
                  }
                  else {
                      // Using the replace policy, we never insert a column outside the grid
                    // TF:10/08/2008:JCT-582:If the grid field is empty, we allow the replace
                    if (parent.getComponentCount() == 0) {
                      fallThroughToExpand = true;
                    }
                    else {
                      return false;
                    }
                  }
                }
                if (!fallThroughToExpand) {
                  JComponent comp = parent.getChildInCell(gbc.row + 1, gbc.column + 1);
                  if (comp != null) {
                      parent.remove(comp);
                  }
                  break;
                }
               
        case Constants.IP_EXPAND:
            // If the row or column is -1, we shuffle everything up and across
            if (gbc.row == -1 || gbc.column == -1) {
                gbc.row = 0;
                gbc.column = 0;
                for (Component thisChild : parent.getComponents()) {
                    if (thisChild != newOne) {
                        GridCell effective = findConstraints(thisChild);
                        effective.row++;
                        effective.column++;
                    }
                }
                rows++;
                columns++;
                rowWeights.add(0, new Integer(0));
                columnWeights.add(0, new Integer(0));
            }
            else if (gbc.column < 0 || gbc.row < 0) {
                UsageException errorVar = new UsageException("gridx and gridy should be >= 0, not gridx = " + gbc.column + ", gridy = " + gbc.row);
                ErrorMgr.addError(errorVar);
                throw errorVar;
            }
            else if (gbc.column >= columns || gbc.row >= rows) {
                // If we need to collapse the columns, update the value passed so that it's the
                // real value not the passed one
                if (isCollapsed() && !ignoreCollapse) {
                    if (gbc.column >= columns) {
                        gbc.column = columns;
                    }
                    if (gbc.row >= rows) {
                        gbc.row = rows;
                    }
                }
            }
            else {
                // We're inserting the value in the middle of the existing grid. If the cell is blank, just put in
                // in there, otherwise, expand the grid by adding rows
                boolean found = false;
                if (parent.getChildInCell(gbc.row+1, gbc.column+1) != null) {
                    found = true;
                }
                if (found) {
                    // There is a child in the cell, so we can't just insert here, we need to shift everything down
                    for (Component thisChild : parent.getComponents()) {
                        GridCell effective = findConstraints(thisChild);
                        if (effective.row >= gbc.row) {
                            effective.row++;
                        }
                    }
                    rows++;
                    rowWeights.add(gbc.row, new Integer(0));
                }
            }
            break;
        case Constants.IP_EXPAND_COLUMNS:
            // This doesn't seem to be supported in Forte, so we've disallowed it earlier
            break;
        case Constants.IP_EXPAND_ROWS:
            // This doesn't seem to be supported in Forte, so we've disallowed it earlier
            break;
        case Constants.IP_REJECT:
            // We reject the insertion of the element, just return false
            return false;

        }
        // Force the layout to be invalidated
        invalidateLayout(parent);
        return true;
    }



    /**
     * Returns the maximum dimensions for this layout given the components
     * in the specified target container.
     * @param target the container which needs to be laid out
     * @see Container
     * @see #minimumLayoutSize(Container)
     * @see #preferredLayoutSize(Container)
     * @return the maximum dimensions for this layout
     */
    public Dimension maximumLayoutSize(Container target) {
        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
    }

    /**
     * Returns the alignment along the x axis.  This specifies how
     * the component would like to be aligned relative to other
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     * <p>
     * @return the value <code>0.5f</code> to indicate centered
     */
    public float getLayoutAlignmentX(Container parent) {
        return Component.CENTER_ALIGNMENT;
    }

    /**
     * Returns the alignment along the y axis.  This specifies how
     * the component would like to be aligned relative to other
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     * <p>
     * @return the value <code>0.5f</code> to indicate centered
     */
    public float getLayoutAlignmentY(Container parent) {
        return Component.CENTER_ALIGNMENT;
    }

    /**
     * Invalidate the layout, discarding any cached information we possess.
     */
    public void invalidateLayout(Container target) {
        if (rowHeights != null) {
            rowHeights = null;
            rowStartLocs = null;
            colWidths = null;
            colStartLocs = null;
        }
    }

    public Dimension minimumLayoutSize(Container parent) {
        return performLayout(parent, false);
    }

    public Dimension preferredLayoutSize(Container parent) {
        return minimumLayoutSize(parent);
    }

    /**
     * If any of our children move, resize, get hidden, etc, we need to
     * invalidate our layout and then re-apply it to the children.
     * @param e - the component event
     */
    private void handleComponentChange(ComponentEvent e) {
        Component c = e.getComponent().getParent();
        if (c != null && !this.isAdjusting) {
          // TF:03/11/2008:Added in an invalidate, because our parent wasn't necessarily getting
          // laid out properly because it might still be valid.
          c.invalidate();
            c.validate();
            e.getComponent().validate();
        }
    }

    public void componentHidden(ComponentEvent e) {
        handleComponentChange(e);
    }

    public void componentMoved(ComponentEvent e) {
        handleComponentChange(e);
    }

    public void componentResized(ComponentEvent e) {
        handleComponentChange(e);
    }

    public void componentShown(ComponentEvent e) {
        handleComponentChange(e);
    }

    public void propertyChange(PropertyChangeEvent evt) {
        // TF: 18/9/07: Added test to see if we're adjusting and abort the layout if we are...
        if (isAdjusting) {
            return;
        }

        // TF:23/11/07:Introduced caching of the title size, need to reset this cache if it changes
        if (evt.getPropertyName().equals("caption") ||
                evt.getPropertyName().equals("captionFont") ||
                evt.getPropertyName().equals("font")) {

            this.cachedTitleWidth = -1;
        }

        if (evt.getPropertyName().equals("minimumSize") ||
            //evt.getPropertyName().equals("preferredSize") ||    // TF:20/11/07:Removed this as it's not needed (we don't care about the preferred size)
            evt.getPropertyName().equals("minHeight") ||
            evt.getPropertyName().equals("minWidth") ||
            evt.getPropertyName().equals("horzDividerStyle") ||
            evt.getPropertyName().equals("horzDividerWeight") ||
            evt.getPropertyName().equals("vertDividerStyle") ||
            evt.getPropertyName().equals("vertDividerWeight") ||
            evt.getPropertyName().equals("rowWeight") ||
            evt.getPropertyName().equals("columnWeight") ||
            evt.getPropertyName().equals("collapsed") ||
            evt.getPropertyName().equals("rowAlignment") ||
            evt.getPropertyName().equals("colAlignment") ||
            evt.getPropertyName().equals("heightMatrixPartner") ||

            // These properties are from our parent GridField
            evt.getPropertyName().equals("cellGravity") ||
            evt.getPropertyName().equals("caption") ||
            evt.getPropertyName().equals("captionFont") ||
            evt.getPropertyName().equals("ignoreInvisibleChildren") ||
            evt.getPropertyName().equals("widthPolicy") ||
            evt.getPropertyName().equals("heightPolicy") ||
            evt.getPropertyName().equals("cellLeftMargin") ||
            evt.getPropertyName().equals("cellRightMargin") ||
            evt.getPropertyName().equals("cellBottomMargin") ||
            evt.getPropertyName().equals("cellTopMargin")) {

            if ((evt.getPropertyName().equals("minimumSize") || evt.getPropertyName().equals("preferredSize")) && evt.getNewValue() == null) {
                // This is just clearing the value, it's like to be set again later to the real value, so do nothing here
                return;
            }
            Component c = ((JComponent)evt.getSource()).getParent();
            if (c != null) {
                c.invalidate();
                c.validate();
            }
        }
    }
}
TOP

Related Classes of net.helipilot50.stocktrade.displayproject.GridFieldLayout

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.