org.kineticsystem.commons.layout.TetrisLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.kineticsystem.commons.layout.TetrisLayout.java

Source

/*
 * TetrisLayout.java
 *
 * Created on February 8, 2004, 2:53 PM
 *
 * Copyright (C) 2004 Remigi Giovanni
 * g.remigi@kineticsystem.org
 * www.kineticsystem.org
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.kineticsystem.commons.layout;

// Java classes.

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

// Apache classes.

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Tetris layout.
 * @author Giovanni Remigi
 * $Revision: 148 $
 */
public class TetrisLayout implements LayoutManager2 {

    /* /////////////////////////////////////////////////////////////////////////
     * Log framework.
     */

    /** Apache log framework. */
    private static Log logger = LogFactory.getLog(TetrisLayout.class);

    /* /////////////////////////////////////////////////////////////////////////
     * Private constants.
     */

    /** Exit constant returned by a fatal error. */
    static final int FATAL_ERROR = -1;

    /** Default space between components. */
    private static final int DEFAULT_GAP = 2;

    /* /////////////////////////////////////////////////////////////////////////
     * Testing layout colors.
     */

    /** Color used to draw the spanning layout in a debugging session. */
    private Color spanningLayoutColor;

    /** Color used to draw the internal layout in a debugging session. */
    private Color internalLayoutColor;

    /** Color used to draw the external layout in a debugging session. */
    private Color externalLayoutColor;

    /* /////////////////////////////////////////////////////////////////////////
     * Private variables.
     */

    /** Vertical spaces between rows. */
    private Gap[] rGaps;

    /** Horizontal spaces between columns. */
    private Gap[] cGaps;

    /** Layout columns. */
    private Bar[] cBars;

    /** Layout rows. */
    private Bar[] rBars;

    /** Column size functions used in laying out components. */
    private SplittedLine[] cSizes;

    /** Row size functions used in laying out components. */
    private SplittedLine[] rSizes;

    /**
     * Component constraints: this map keeps track of the relation between a
     * component and its layout contraints.
     */
    private HashMap<Component, Cell> constraintsMap;

    /**
     * A copy of all component dimensions: preferred, minimum and maximum
     * ones.
     */
    private HashMap<Component, Size> componentSizes;

    /** The component incapsulating the layout algorithm. */
    private LayoutStrategy strategy;

    /** The size of the container. */
    private Dimension containerSize;

    /** The width of the container without gaps and border. */
    private double preferredGapFreeWidth;

    /** The height of the container without gaps and border. */
    private double preferredGapFreeHeight;

    /** The width of the container without gaps and border. */
    private double minGapFreeWidth;

    /** The height of the container without gaps and border. */
    private double minGapFreeHeight;

    /** The width of the container without gaps and border. */
    private double maxGapFreeWidth;

    /** The height of the container without gaps and border. */
    private double maxGapFreeHeight;

    /** The container beeing laid out. */
    private Container container;

    /* /////////////////////////////////////////////////////////////////////////
     * Constructors.
     */

    /**
     * Create a new layout with given rows and columns.
     * @param rows The number of rows.
     * @param cols The number of columns.
     */
    public TetrisLayout(int rows, int cols) {

        if ((rows <= 0) || (cols <= 0)) {
            logger.fatal("Cannot create layout: " + "need positive row and column number!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }

        // Setup testing colors.

        spanningLayoutColor = Color.ORANGE;
        internalLayoutColor = Color.BLUE;
        externalLayoutColor = Color.RED;

        // Setup global variables and structures.

        cBars = Bar.createArray(cols);
        rBars = Bar.createArray(rows);

        // Set horizontal gaps and positions.

        cSizes = SplittedLine.createArray(cols + 1);
        cGaps = Gap.createArray(cols + 1);
        for (int i = 0; i < cols + 1; i++) {
            cGaps[i].setSize(DEFAULT_GAP);
        }
        cGaps[0].setSize(0);
        cGaps[cols].setSize(0);

        // Set vertical gaps and positions.

        rSizes = SplittedLine.createArray(rows + 1);
        rGaps = Gap.createArray(rows + 1);
        for (int i = 0; i < rows + 1; i++) {
            rGaps[i].setSize(DEFAULT_GAP);
        }
        rGaps[0].setSize(0);
        rGaps[rows].setSize(0);

        constraintsMap = new HashMap<Component, Cell>();
        componentSizes = new HashMap<Component, Size>();

        preferredGapFreeWidth = 0;
        preferredGapFreeHeight = 0;

        // Setup default layout algorithm.

        strategy = new DefaultLayoutStrategy();
        strategy.initialize(rBars.length, cBars.length);
    }

    /* /////////////////////////////////////////////////////////////////////////
     * Setter methods.
     */

    /**
     * Implement <code>LayoutStrategy</code> interface to create your own
     * layout algorithm and plug it in before adding components.
     * @param strategy The <code>LayoutStrategy</code> implementing you custom
     *        layout algorithm.
     */
    public void setLayoutStrategy(LayoutStrategy strategy) {

        if (strategy == null) {
            this.strategy = new DefaultLayoutStrategy();
        } else {
            this.strategy = strategy;
        }
        strategy.initialize(rBars.length, cBars.length);
    }

    /**
     * Set space between components.
     * @param gap Absolute space between components.
     */
    public void setGap(int gap) {

        if (gap < 0) {
            logger.fatal("Cannot set parameter: " + "space between component must be a positive number!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        for (int i = 1; i < cBars.length; i++) {
            cGaps[i].setSize(gap);
        }
        for (int i = 1; i < rBars.length; i++) {
            rGaps[i].setSize(gap);
        }
    }

    /**
     * Set vertical space between components.
     * @param gap Absolute vertical space between components.
     */
    public void setVerticalGap(int gap) {

        if (gap < 0) {
            logger.fatal("Cannot set parameter: " + "space between component must be a positive number!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        for (int i = 1; i < rBars.length; i++) {
            rGaps[i].setSize(gap);
        }
    }

    /**
     * Set vertical space between components.
     * @param gapIndex the index of the gap to set.
     * @param gap Absolute vertical space between components.
     */
    public void setVerticalGap(int gapIndex, int gap) {

        if ((gapIndex < 0) || (gapIndex > rBars.length)) {
            logger.fatal("Cannot set parameter: " + "gap index " + gapIndex + " out of range!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        if (gap < 0) {
            logger.fatal("Cannot set parameter: " + "space between component must be a positive number!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        rGaps[gapIndex].setSize(gap);
    }

    /**
     * Set horizontal space between components.
     * @param gap Absolute horizontal space between components.
     */
    public void setHorizontalGap(int gap) {

        if (gap < 0) {
            logger.fatal("Cannot set parameter: " + "space between component must be a positive number!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        for (int i = 1; i < cBars.length; i++) {
            cGaps[i].setSize(gap);
        }
    }

    /**
     * Set horizontal space between components.
     * @param gapIndex the index of the gap to set.
     * @param gap Absolute horizontal space between components.
     */
    public void setHorizontalGap(int gapIndex, int gap) {

        if ((gapIndex < 0) || (gapIndex > cBars.length)) {
            logger.fatal("Cannot set parameter: " + "gap index " + gapIndex + " out of range!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        if (gap < 0) {
            logger.fatal("Cannot set parameter: " + "space between component must be a positive number!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        cGaps[gapIndex].setSize(gap);
    }

    /**
     * Set the weight of a column. This parameter effects the way a given column
     * is resized during the overall resize of the layout.
     * @param col The given column index.
     * @param weight A positive value determining the way the given column is
     *        resized during the resize of the layout. If this value is 0, the
     *        column is not resized.
     */
    public void setColWeight(int col, double weight) {

        if ((col < 0) || (col > cBars.length)) {
            logger.fatal("Cannot set parameter: column index out of range!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        if (weight < 0) {
            logger.fatal("Cannot set parameter: " + "column" + col + " weight must be a positive value!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        cBars[col].setWeight(weight);
    }

    /**
     * Set the weight of a row. This parameter effects the way a given row is
     * resized during the overall resize of the layout.
     * @param row The given row index.
     * @param weight A positive value determining the way the given row is
     *        resized during the resize of the layout. If this value is 0, the
     *        row is not resized.
     */
    public void setRowWeight(int row, double weight) {

        if ((row < 0) || (row > rBars.length)) {
            logger.fatal("Cannot set parameter: row index out of range!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        if (weight < 0) {
            logger.fatal("Cannot set parameter: " + "row " + row + " weight must be a positive value!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
        rBars[row].setWeight(weight);
    }

    /**
     * <p>Add a <code>Connector</code> to the specified column.</p>
     * <p>In a first step, the preferred and minimum size of a layout column is
     * evaluated using preferred and minimum sizes of laid out components.</p>
     * <p>After this first evaluation you can use a connector to force a column
     * size based on precalculated sizes and gaps of the other columns.</p>
     * <p>Remember that a column size can never go under the minimum 
     * precalculated column size.</p>
     * @param col The given column index.
     * @param connector The connector evaluating the column size.
     */
    public void setColConnector(int col, Connector connector) {
        cBars[col].setConnector(connector);
    }

    /**
     * <p>Add a <code>Connector</code> to the specified row.</p>
     * <p>In a first step, the preferred and minimum size of a layout row is
     * evaluated using preferred and minimum sizes of laid out components.</p>
     * <p>After this first evaluation you can use a connector to force a row
     * size based on precalculated sizes and gaps of the other rows.</p>
     * <p>Remember that a row size can never go under the minimum precalculated
     * row size.</p>
     * @param row The given row index.
     * @param connector The connector evaluating the row size.
     */
    public void setRowConnector(int row, Connector connector) {
        rBars[row].setConnector(connector);
    }

    /** 
     * Set the specified component constraint object.
     * @param comp The component whom contraints are applied to.
     * @param constraints The component constaints.
     */
    public void setConstraints(Component comp, Cell constraints) {

        Cell cell = (Cell) constraints.clone();

        // Lay out the component using the Strategy Pattern.

        strategy.addComponent(comp, cell);

        if (comp == null) {
            logger.fatal("Cannot add to layout: cannot add null components!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }

        // Store the component.

        constraintsMap.put(comp, cell);
    }

    /* /////////////////////////////////////////////////////////////////////////
     * Private methods.
     */

    /**
     * Get component at given layout position looking inside the component list.
     * @param row A row in the layout.
     * @param col A column in the layout.
     */
    private Component componentAt(int row, int col) {

        boolean found = false;
        Component comp = null;
        Iterator<Map.Entry<Component, Cell>> iter = constraintsMap.entrySet().iterator();
        while (!found && iter.hasNext()) {
            Map.Entry<Component, Cell> entry = iter.next();
            comp = (Component) entry.getKey();
            Cell cell = (Cell) entry.getValue();
            if ((row >= cell.getRow()) && (row < (cell.getRow() + cell.getRows())) && (col >= cell.getCol())
                    && (col < (cell.getCol() + cell.getCols()))) {
                found = true;
            }
        }
        return comp;
    }

    /**
     * Calculate the preferred size dimensions for the specified panel given
     * the components in the specified parent container. Calculates incremental
     * column width rates and row heights, dimensions used during component
     * laying out. 
     * @param parent The component to be laid out.
     */
    private void setup(Container parent) {

        container = parent;
        Insets insets = parent.getInsets();

        // FIRST PHASE: DEFAULT EVALUATION OF COLUMN AND ROW SIZES.

        // Evaluate column widths.

        for (int c = 0; c < cBars.length; c++) {
            if (cBars[c].getSize() == Bar.COMPONENT_PREFERRED_SIZE) {
                for (int r = 0; r < rBars.length; r++) {
                    Component comp = componentAt(r, c);
                    Cell cell = (Cell) constraintsMap.get(comp);
                    if (comp.isVisible() && ((cell.getCols() + cell.getCol() - 1) == c)) {
                        Size cd = (Size) componentSizes.get(comp);
                        cBars[c].setPreferredSize(Math.max(cd.getPreferredWidth(), cBars[c].getPreferredSize()));
                        cBars[c].setMinimumSize(Math.max(cd.getMinimumWidth(), cBars[c].getMinimumSize()));
                        cBars[c].setMaximumSize(Double.MAX_VALUE);
                    }
                }
            } else {
                cBars[c].setPreferredSize(cBars[c].getSize());
            }

            // Check all components containing column c and store their sizes.

            for (Map.Entry<Component, Cell> entry : constraintsMap.entrySet()) {

                Component comp = (Component) entry.getKey();
                Cell cell = (Cell) entry.getValue();
                if (((cell.getCol() + cell.getCols()) >= c) && (cell.getCol() <= c)) {
                    Size d = (Size) componentSizes.get(comp);
                    d.setPreferredWidth(d.getPreferredWidth() - cBars[c].getPreferredSize());
                    d.setMinimumWidth(d.getMinimumWidth() - cBars[c].getMinimumSize());
                }
            }
        }

        // Evaluate row heights.

        for (int r = 0; r < rBars.length; r++) {
            if (rBars[r].getSize() == Bar.COMPONENT_PREFERRED_SIZE) {
                for (int c = 0; c < cBars.length; c++) {
                    Component comp = componentAt(r, c);
                    Cell cell = (Cell) constraintsMap.get(comp);
                    if (comp.isVisible() && ((cell.getRows() + cell.getRow() - 1) == r)) {
                        Size cd = (Size) componentSizes.get(comp);
                        rBars[r].setPreferredSize(Math.max(cd.getPreferredHeight(), rBars[r].getPreferredSize()));
                        rBars[r].setMinimumSize(Math.max(cd.getMinimumHeight(), rBars[r].getMinimumSize()));
                        rBars[r].setMaximumSize(Double.MAX_VALUE);
                    }
                }
            } else {
                rBars[r].setWeight(rBars[r].getSize());
            }

            // Check all components containing row r and store their sizes.

            for (Map.Entry<Component, Cell> entry : constraintsMap.entrySet()) {

                Component comp = (Component) entry.getKey();
                Cell cell = (Cell) entry.getValue();
                if (((cell.getRow() + cell.getRows()) >= r) && (cell.getRow() <= r)) {
                    Size d = (Size) componentSizes.get(comp);
                    d.setPreferredHeight(d.getPreferredHeight() - rBars[r].getPreferredSize());
                    d.setMinimumHeight(d.getMinimumHeight() - rBars[r].getMinimumSize());
                }
            }
        }

        // SECOND PHASE: OVERRIDE DEFAULT EVALUATION OF COLUMN AND ROW SIZES.

        /*
         * Override previous evaluation of column and row sizes using
         * connectors.
         */

        initBarConnectors(cBars, cGaps);
        initBarConnectors(rBars, rGaps);

        // Evaluates container widths and heights.

        for (int i = 0; i < cBars.length; i++) {
            minGapFreeWidth += cBars[i].getMinimumSize();
            maxGapFreeWidth += cBars[i].getMaximumSize();
            preferredGapFreeWidth += cBars[i].getPreferredSize();
        }
        for (int i = 0; i < rBars.length; i++) {
            minGapFreeHeight += rBars[i].getMinimumSize();
            maxGapFreeHeight += rBars[i].getMaximumSize();
            preferredGapFreeHeight += rBars[i].getPreferredSize();
        }

        // Adds insets and gaps.

        initGaps(cGaps);
        initGaps(rGaps);

        double preferredWidth = preferredGapFreeWidth + insets.left + insets.right
                + cGaps[cBars.length].getIncSize();
        double preferredHeight = preferredGapFreeHeight + insets.top + insets.bottom
                + rGaps[rBars.length].getIncSize();

        // Sets preferred container size.

        containerSize = new Dimension((int) Math.round(preferredWidth), (int) Math.round(preferredHeight));

        // THIRD PHASE: EVALUATE ROW AND COLUMN SIZE FUNCTIONS.

        initSizes(cSizes, cBars, preferredGapFreeWidth);
        initSizes(rSizes, rBars, preferredGapFreeHeight);
    }

    /**
     * Evaluate the incremental sum of a sequence of gaps starting from the left
     * and moving to the right.
     * @param gaps The array of gaps.
     */
    private void initGaps(Gap[] gaps) {
        gaps[0].setIncSize(gaps[0].getSize());
        for (int i = 1; i < gaps.length; i++) {
            gaps[i].setIncSize(gaps[i].getSize() + gaps[i - 1].getIncSize());
        }
    }

    /**
     * Override the default preferred size of columns and rows using
     * <code>Connector</code> objects given in the initialization phase.
     * A preferred size can't go under the previously evaluated minimum size.
     * @param bars The rows or columns we want to initialize.
     * @param gaps Gaps between rows or columns.
     */
    private void initBarConnectors(Bar[] bars, Gap[] gaps) {

        double[] tmpPreferredSizes;
        double[] tmpSizes;
        int[] tmpGaps;

        tmpPreferredSizes = new double[bars.length];
        tmpSizes = new double[bars.length];
        tmpGaps = new int[bars.length + 1];
        for (int i = 0; i < bars.length; i++) {
            tmpSizes[i] = bars[i].getPreferredSize();
        }
        for (int i = 0; i < bars.length + 1; i++) {
            tmpGaps[i] = gaps[i].getSize();
        }

        // Don't go under previously evaluated minimum sizes.

        for (int i = 0; i < bars.length; i++) {
            if (bars[i].getConnector() != null) {
                tmpPreferredSizes[i] = Math.max(bars[i].getConnector().getSize(tmpSizes, tmpGaps),
                        bars[i].getMinimumSize());
            }
        }
        for (int i = 0; i < bars.length; i++) {
            if (bars[i].getConnector() != null) {
                bars[i].setPreferredSize(tmpPreferredSizes[i]);
            }
        }
    }

    /**
     * Evaluate functions decribing row and column sizes.
     * @param splitted The array of object describing row and column sizes.
     * @param bars The array of rows or columns.
     * @param preferredGapFreeSize The current size of the main panel exluding
     *        gaps and insets.
     */
    private void initSizes(SplittedLine[] splitted, Bar[] bars, double preferredGapFreeSize) {

        double maxStart = preferredGapFreeSize;
        double tmpEnd = preferredGapFreeSize;

        double minEnd = preferredGapFreeSize;
        double tmpStart = preferredGapFreeSize;

        Line[] lines = Line.createArray(bars.length + 1);
        for (int i = 1; i < bars.length + 1; i++) {
            Point p = new Point(preferredGapFreeSize, bars[i - 1].getPreferredSize());
            lines[i].setSlope(bars[i - 1].getWeight());
            lines[i].setPoint(p);
            lines[i].setMax(bars[i - 1].getMaximumSize());
            lines[i].setMin(bars[i - 1].getMinimumSize());
        }
        Line[] clone;

        clone = Line.cloneArray(lines);
        while (maxStart != 0) {
            Line.normalize(clone);
            maxStart = Line.getMaxStart(clone);

            if (maxStart != tmpEnd) {
                Line s = new Line(0, new Point(tmpEnd, 0));
                s.setStart(maxStart);
                s.setEnd(tmpEnd);
                tmpEnd = maxStart;
                for (int i = 0; i < clone.length; i++) {
                    s.add(clone[i]);
                    splitted[i].add((Line) s.clone());
                    clone[i].setPoint(new Point(maxStart, clone[i].evaluate(maxStart)));
                }
            }
            for (int i = 0; i < clone.length; i++) {
                if (clone[i].isChecked()) {
                    clone[i].setSlope(0);
                }
            }
        }

        clone = Line.cloneArray(lines);
        while (minEnd != Double.MAX_VALUE) {
            Line.normalize(clone);
            minEnd = Line.getMinEnd(clone);

            if (tmpStart != minEnd) {
                Line s = new Line(0, new Point(tmpStart, 0));
                s.setStart(tmpStart);
                s.setEnd(minEnd);
                tmpStart = minEnd;
                for (int i = 0; i < clone.length; i++) {
                    s.add(clone[i]);
                    splitted[i].add((Line) s.clone());
                    clone[i].setPoint(new Point(minEnd, clone[i].evaluate(minEnd)));
                }
            }
            for (int i = 0; i < clone.length; i++) {
                if (clone[i].isChecked()) {
                    clone[i].setSlope(0);
                }
            }
        }
    }

    /* /////////////////////////////////////////////////////////////////////////
     * LayoutManager2 interface implementation.
     */

    /**
     * Add the specified component to the layout, using the specified
     * constraint object.
     * @param comp The component to be added
     * @param constraints Where/how the component is added to the layout.
     */
    public void addLayoutComponent(Component comp, Object constraints) {

        if (constraints == null) {
            constraints = new Cell();
        }
        if (constraints instanceof Cell) {

            Cell cell = (Cell) constraints;
            if (cell.getRows() < 0 || cell.getCols() < 0) {
                logger.fatal("Cannot add to layout: " + "cell must have positive row and column number!");
                System.exit(TetrisLayout.FATAL_ERROR);
            }
            setConstraints(comp, cell);

            // Store a copy of all component dimensions.

            Size cd = new Size();
            cd.setMaximumWidth(comp.getMaximumSize().getWidth());
            cd.setMaximumHeight(comp.getMaximumSize().getHeight());
            cd.setMinimumWidth(comp.getMinimumSize().getWidth());
            cd.setMinimumHeight(comp.getMinimumSize().getHeight());
            cd.setPreferredWidth(comp.getPreferredSize().getWidth());
            cd.setPreferredHeight(comp.getPreferredSize().getHeight());

            // Check dimensions.

            if (cd.getMinimumWidth() > cd.getPreferredWidth()) {
                logger.warn("Component minimum width is greater than preferred"
                        + " width: set value to preferred size!\n" + cd);
                cd.setMinimumWidth(cd.getPreferredWidth());
            }
            if (cd.getMaximumWidth() < cd.getPreferredWidth()) {
                logger.warn("Component maximum width is less than preferred"
                        + " width: set value to preferred size!\n" + cd);
                cd.setMaximumWidth(cd.getPreferredWidth());
            }
            if (cd.getMinimumHeight() > cd.getPreferredHeight()) {
                logger.warn("Component minimum height is greater than preferred"
                        + " height: set value to preferred size!\n" + cd);
                cd.setMinimumHeight(cd.getPreferredHeight());
            }
            if (cd.getMaximumHeight() < cd.getPreferredHeight()) {
                logger.warn("Component maximum height is less than preferred"
                        + " height: set value to preferred size!\n" + cd);
                cd.setMaximumHeight(cd.getPreferredHeight());
            }

            componentSizes.put(comp, cd);

        } else if (constraints != null) {
            logger.fatal("Cannot add to layout: constraint must be a Cell!");
            System.exit(TetrisLayout.FATAL_ERROR);
        }
    }

    /**
     * Return 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.
     * @param target The component container.
     * @return A number between 0 and 1 representing the alignment.
     */
    public float getLayoutAlignmentX(Container target) {
        return 0.5f;
    }

    /**
     * Return 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.
     * @param target The component container.
     * @return A number between 0 and 1 representing the alignment.
     */
    public float getLayoutAlignmentY(Container target) {
        return 0.5f;
    }

    /**
     * Invalidate the layout, indicating that if the layout manager has cached
     * information it should be discarded (not implemented yet).
     * @param target The component container.
     */
    public void invalidateLayout(Container target) {
        // Not implemented.
    }

    /** 
     * Return the maximum size of this container.
     * @param parent The container to be laid out.
     * @return The maximum container size dimension.
     */
    public Dimension maximumLayoutSize(Container parent) {
        if (containerSize == null) {
            setup(parent);
        }

        Insets insets = parent.getInsets();
        double maxWidth = maxGapFreeWidth + insets.left + insets.right + cGaps[cBars.length].getIncSize();
        double maxHeight = maxGapFreeHeight + insets.top + insets.bottom + rGaps[rBars.length].getIncSize();

        // Return minimal container size.

        Dimension d = new Dimension((int) Math.round(maxWidth), (int) Math.round(maxHeight));
        return d;
    }

    /* /////////////////////////////////////////////////////////////////////////
     * LayoutManager interface implementation.
     */

    /**
     * Add the specified component with the specified name to the layout. This
     * does nothing in TetrisLayout, since constraints are required.
     * @param name The component name.
     * @param comp The component to be inserted.
     */
    public void addLayoutComponent(String name, Component comp) {
        // Do nothing.
    }

    /**
     * Lay out components in the specified container.
     * @param parent The container which needs to be laid out.
     */
    public void layoutContainer(Container parent) {

        /* 
         * This method synchronizes on the tree lock of the component. This tree
         * lock is an object that can be used to provide thread-safe access to
         * the layout manager in case different threads are simultaneously
         * adding or removing components. The tree lock object is used as a
         * synchronization point for all of the methods associated with laying
         * out containers and invalidating components, and it is good
         * programming practice to use it to ensure a thread-safe
         * implementation.
         */

        synchronized (parent.getTreeLock()) {

            // Layout components.

            if (containerSize == null) {
                setup(parent);
            }

            Insets insets = parent.getInsets();

            int componentNumber = parent.getComponentCount();
            if (componentNumber == 0) {
                return;
            }

            Dimension size = parent.getSize();

            cGaps[0].setIncSize(cGaps[0].getSize());
            for (int i = 1; i < cBars.length + 1; i++) {
                cGaps[i].setIncSize(cGaps[i].getSize() + cGaps[i - 1].getIncSize());
            }
            rGaps[0].setIncSize(rGaps[0].getSize());
            for (int i = 1; i < rBars.length + 1; i++) {
                rGaps[i].setIncSize(rGaps[i].getSize() + rGaps[i - 1].getIncSize());
            }

            int actualGapFreeWidth = Math
                    .max(size.width - insets.left - insets.right - cGaps[cBars.length].getIncSize(), 0);
            int actualGapFreeHeight = Math
                    .max(size.height - insets.top - insets.bottom - rGaps[rBars.length].getIncSize(), 0);

            for (Map.Entry<Component, Cell> entry : constraintsMap.entrySet()) {

                Component comp = (Component) entry.getKey();
                Cell cell = (Cell) entry.getValue();
                if (cell != null) {

                    // Actual cell position.

                    int x0 = (int) Math.round(cSizes[cell.getCol()].evaluate(actualGapFreeWidth));
                    int y0 = (int) Math.round(rSizes[cell.getRow()].evaluate(actualGapFreeHeight));
                    int x1 = (int) Math.round(cSizes[cell.getCol() + cell.getCols()].evaluate(actualGapFreeWidth));
                    int y1 = (int) Math.round(rSizes[cell.getRow() + cell.getRows()].evaluate(actualGapFreeHeight));

                    // Actual cell dimension.

                    int w = x1 - x0;
                    int h = y1 - y0;

                    // Component position correction.

                    int xCorrection = insets.left + cGaps[cell.getCol()].getIncSize();
                    int yCorrection = insets.top + rGaps[cell.getRow()].getIncSize();

                    // Component dimension correction.

                    int wCorrection = cGaps[cell.getCol() + cell.getCols() - 1].getIncSize()
                            - cGaps[cell.getCol()].getIncSize();
                    int hCorrection = rGaps[cell.getRow() + cell.getRows() - 1].getIncSize()
                            - rGaps[cell.getRow()].getIncSize();

                    // Preferred component dimension.

                    int wComp = comp.getPreferredSize().width;
                    int hComp = comp.getPreferredSize().height;

                    int fill = cell.getFill();
                    int anchor = cell.getAnchor();

                    switch (fill) {
                    case Cell.NONE: {
                        if (wComp > w) {
                            wComp = w;
                        }
                        if (hComp > h) {
                            hComp = h;
                        }
                        switch (anchor) {
                        case Cell.FIRST_LINE_START:
                            x0 += xCorrection;
                            y0 += yCorrection;
                            break;
                        case Cell.PAGE_START:
                            x0 += xCorrection + (w - wComp) / 2;
                            y0 += yCorrection;
                            break;
                        case Cell.FIRST_LINE_END:
                            x0 += xCorrection + w + wCorrection - wComp;
                            y0 += yCorrection;
                            break;
                        case Cell.LINE_START:
                            x0 += xCorrection;
                            y0 += yCorrection + (h - hComp) / 2;
                            break;
                        case Cell.CENTER:
                            x0 += xCorrection + (w - wComp) / 2;
                            y0 += yCorrection + (h - hComp) / 2;
                            break;
                        case Cell.LINE_END:
                            x0 += xCorrection + w + wCorrection - wComp;
                            y0 += yCorrection + (h - hComp) / 2;
                            break;
                        case Cell.LAST_LINE_START:
                            x0 += xCorrection;
                            y0 += yCorrection + h + hCorrection - hComp;
                            break;
                        case Cell.PAGE_END:
                            x0 += xCorrection + (w - wComp) / 2;
                            y0 += yCorrection + h + hCorrection - hComp;
                            break;
                        case Cell.LAST_LINE_END:
                            x0 += xCorrection + w + wCorrection - wComp;
                            y0 += yCorrection + h - hCorrection - hComp;
                            break;
                        }
                        w = Math.min(w, wComp) + wCorrection;
                        h = Math.min(h, hComp) + hCorrection;
                        break;
                    }
                    case Cell.PROPORTIONAL: {
                        double ratio = Math.min((double) ((double) w / (double) wComp),
                                (double) ((double) h / (double) hComp));
                        wComp = (int) Math.round(wComp * ratio);
                        hComp = (int) Math.round(hComp * ratio);
                        switch (anchor) {
                        case Cell.FIRST_LINE_START:
                            x0 += xCorrection;
                            y0 += yCorrection;
                            break;
                        case Cell.PAGE_START:
                            x0 += xCorrection + (w - wComp) / 2;
                            y0 += yCorrection;
                            break;
                        case Cell.FIRST_LINE_END:
                            x0 += xCorrection + wCorrection + w - wComp;
                            y0 += yCorrection;
                            break;
                        case Cell.LINE_START:
                            x0 += xCorrection;
                            y0 += yCorrection + (h - hComp) / 2;
                            break;
                        case Cell.CENTER:
                            x0 += xCorrection + (w - wComp) / 2;
                            y0 += yCorrection + (h - hComp) / 2;
                            break;
                        case Cell.LINE_END:
                            x0 += xCorrection + wCorrection + w - wComp;
                            y0 += yCorrection + (h - hComp) / 2;
                            break;
                        case Cell.LAST_LINE_START:
                            x0 += xCorrection;
                            y0 += yCorrection + hCorrection + h - hComp;
                            break;
                        case Cell.PAGE_END:
                            x0 += xCorrection + (w - wComp) / 2;
                            y0 += yCorrection + hCorrection + h - hComp;
                            break;
                        case Cell.LAST_LINE_END:
                            x0 += xCorrection + wCorrection + w - wComp;
                            y0 += yCorrection + hCorrection + h - hComp;
                            break;
                        }
                        w = Math.min(w, wComp) + wCorrection;
                        h = Math.min(h, hComp) + hCorrection;
                        break;
                    }
                    case Cell.BOTH: {
                        x0 += xCorrection;
                        y0 += yCorrection;
                        w += wCorrection;
                        h += hCorrection;
                        break;
                    }
                    case Cell.HORIZONTAL: {
                        if (hComp > h) {
                            hComp = h;
                        }
                        switch (anchor) {
                        case Cell.FIRST_LINE_START:
                        case Cell.PAGE_START:
                        case Cell.FIRST_LINE_END:
                            y0 += yCorrection;
                            break;
                        case Cell.LINE_START:
                        case Cell.CENTER:
                        case Cell.LINE_END:
                            y0 += yCorrection + (h - hComp) / 2;
                            break;
                        case Cell.LAST_LINE_START:
                        case Cell.PAGE_END:
                        case Cell.LAST_LINE_END:
                            y0 += yCorrection + h + hCorrection - hComp;
                            break;
                        }
                        x0 += xCorrection;
                        w += wCorrection;
                        h = Math.min(h, hComp) + hCorrection;
                        break;
                    }
                    case Cell.VERTICAL: {
                        if (wComp > w) {
                            wComp = w;
                        }
                        switch (anchor) {
                        case Cell.FIRST_LINE_START:
                        case Cell.LINE_START:
                        case Cell.LAST_LINE_START:
                            x0 += xCorrection;
                            break;
                        case Cell.PAGE_START:
                        case Cell.CENTER:
                        case Cell.PAGE_END:
                            x0 += xCorrection + (w - wComp) / 2;
                            break;
                        case Cell.FIRST_LINE_END:
                        case Cell.LINE_END:
                        case Cell.LAST_LINE_END:
                            x0 += xCorrection + w + wCorrection - wComp;
                            break;
                        }
                        y0 += yCorrection;
                        w = Math.min(w, wComp) + wCorrection;
                        h += hCorrection;
                        break;
                    }
                    }
                    comp.setBounds(x0, y0, w, h);
                }
            }
        }
    }

    /** 
     * Return the minimum size of this container.
     * @param parent The container to be laid out.
     * @return The minimum container size dimension.
     */
    public Dimension minimumLayoutSize(Container parent) {

        if (containerSize == null) {
            setup(parent);
        }

        Insets insets = parent.getInsets();
        double minWidth = minGapFreeWidth + insets.left + insets.right + cGaps[cBars.length].getIncSize();
        double minHeight = minGapFreeHeight + insets.top + insets.bottom + rGaps[rBars.length].getIncSize();

        // Return minimal container size.

        Dimension d = new Dimension((int) Math.round(minWidth), (int) Math.round(minHeight));
        return d;
    }

    /**
     * Calculate the preferred size dimensions for the specified panel given
     * the components in the specified parent container. Used by
     * <code>pack</code> method.
     * @param parent The component to be laid out.
     * @return The preferred size dimensions.
     */
    public Dimension preferredLayoutSize(Container parent) {
        if (containerSize == null) {
            setup(parent);
        }
        return containerSize;
    }

    /**
     * Removes the specified component from the layout (not implemented yet).
     * @param comp The component to be removed
     */
    public void removeLayoutComponent(Component comp) {
        // Not implemented.
    }

    /* /////////////////////////////////////////////////////////////////////////
     * Debugging methods.
     */

    /**
     * <p>This method draws the layout on a graphics context. Used to test the
     * layout during the design process. Override the 
     * <code>paint(Graphics g)</code> of the container to display the layout.
     * Here is an example code where <code>layout</code> is a 
     * <code>TestrisLayout</code>.</p>
     * <pre>
     * JPanel panel = new JPanel(layout) {
     *     public void paint(Graphics g) {
     *         super.paint(g);
     *         ((TetrisLayout) getLayout()).drawLayout(g);
     *     }
     * };
     * </pre>
     * @param g The graphics context.
     */
    public void drawLayout(Graphics g) {

        // Draw the spanning layout.

        g.setColor(spanningLayoutColor);

        Insets insets = container.getInsets();
        Dimension size = container.getSize();

        int actualGapFreeWidth = Math
                .max(size.width - insets.left - insets.right - cGaps[cBars.length].getIncSize(), 0);
        int actualGapFreeHeight = Math
                .max(size.height - insets.top - insets.bottom - rGaps[rBars.length].getIncSize(), 0);

        for (Cell cell : constraintsMap.values()) {

            if (cell != null) {

                /*
                 * Calculate the center of the first and the last cell spanned
                 * by the current component.
                 */

                int cx0 = (int) Math.round((cSizes[cell.getCol()].evaluate(actualGapFreeWidth)
                        + cSizes[cell.getCol() + 1].evaluate(actualGapFreeWidth)) / 2);
                int cy0 = (int) Math.round((rSizes[cell.getRow()].evaluate(actualGapFreeHeight)
                        + rSizes[cell.getRow() + 1].evaluate(actualGapFreeHeight)) / 2);
                int cx1 = (int) Math.round((cSizes[cell.getCol() + cell.getCols()].evaluate(actualGapFreeWidth)
                        + cSizes[cell.getCol() + cell.getCols() - 1].evaluate(actualGapFreeWidth)) / 2) + 1;
                int cy1 = (int) Math.round((rSizes[cell.getRow() + cell.getRows()].evaluate(actualGapFreeHeight)
                        + rSizes[cell.getRow() + cell.getRows() - 1].evaluate(actualGapFreeHeight)) / 2) + 1;

                // Actual cell dimension.

                int w = cx1 - cx0;
                int h = cy1 - cy0;

                // Component position correction.

                int xCorrection = insets.left + cGaps[cell.getCol()].getIncSize();
                int yCorrection = insets.top + rGaps[cell.getRow()].getIncSize();

                // Component dimension correction.

                int wCorrection = cGaps[cell.getCol() + cell.getCols() - 1].getIncSize()
                        - cGaps[cell.getCol()].getIncSize();
                int hCorrection = rGaps[cell.getRow() + cell.getRows() - 1].getIncSize()
                        - rGaps[cell.getRow()].getIncSize();

                cx0 += xCorrection;
                cy0 += yCorrection;
                w += wCorrection;
                h += hCorrection;

                int radius = 6;
                g.drawRect(cx0 + 1, cy0 + 1, w - 1, h - 1);
                g.drawOval(cx0 + 1 - radius / 2, cy0 + 1 - radius / 2, radius, radius);
                g.drawOval(cx0 + w - radius / 2, cy0 + h - radius / 2, radius, radius);
            }
        }

        // Draw the internal layout.

        g.setColor(internalLayoutColor);

        int x2 = (int) Math.round(cSizes[cSizes.length - 1].evaluate(actualGapFreeWidth)
                + cGaps[cSizes.length - 1].getIncSize() + insets.left) - 1;
        int y2 = (int) Math.round(rSizes[rSizes.length - 1].evaluate(actualGapFreeHeight)
                + rGaps[rSizes.length - 1].getIncSize() + insets.top) - 1;

        for (int i = 1; i < cSizes.length - 1; i++) {
            int x = (int) Math
                    .round(cSizes[i].evaluate(actualGapFreeWidth) + insets.left + cGaps[i - 1].getIncSize());
            g.drawLine(x, insets.top, x, y2);
            g.drawLine(x + cGaps[i].getSize(), insets.top, x + cGaps[i].getSize(), y2);
        }
        for (int i = 1; i < rSizes.length - 1; i++) {
            int y = (int) Math
                    .round(rSizes[i].evaluate(actualGapFreeHeight) + insets.top + rGaps[i - 1].getIncSize());
            g.drawLine(insets.left, y, x2, y);
            g.drawLine(insets.left, y + rGaps[i].getSize(), x2, y + rGaps[i].getSize());
        }

        // Draw the layout internal frame.

        g.setColor(externalLayoutColor);
        g.drawRect(insets.left, insets.top, x2 - insets.left, y2 - insets.top);

        // Draw layout external frame.

        g.drawRect(0, 0, x2 + insets.right, y2 + insets.bottom);
    }
}