0001: /*
0002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * o Redistributions of source code must retain the above copyright notice,
0008: * this list of conditions and the following disclaimer.
0009: *
0010: * o Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
0015: * its contributors may be used to endorse or promote products derived
0016: * from this software without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
0020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
0025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
0026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
0027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
0028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: */
0030:
0031: package com.jgoodies.forms.layout;
0032:
0033: import java.awt.Component;
0034: import java.awt.Container;
0035: import java.awt.Dimension;
0036: import java.awt.Insets;
0037: import java.awt.LayoutManager2;
0038: import java.awt.Rectangle;
0039: import java.io.IOException;
0040: import java.io.ObjectOutputStream;
0041: import java.io.Serializable;
0042: import java.util.*;
0043:
0044: /**
0045: * FormLayout is a powerful, flexible and precise general purpose
0046: * layout manager. It aligns components vertically and horizontally in
0047: * a dynamic rectangular grid of cells, with each component occupying one or
0048: * more cells.
0049: * A <a href="../../../../../whitepaper.pdf" target="secondary">whitepaper</a>
0050: * about the FormLayout ships with the product documentation and is available
0051: * <a href="http://www.jgoodies.com/articles/forms.pdf">online</a>.<p>
0052: *
0053: * To use FormLayout you first define the grid by specifying the
0054: * columns and rows. In a second step you add components to the grid. You can
0055: * specify columns and rows via human-readable String descriptions or via
0056: * arrays of {@link ColumnSpec} and {@link RowSpec} instances.<p>
0057: *
0058: * Each component managed by a FormLayout is associated with an instance of
0059: * {@link CellConstraints}. The constraints object specifies where a component
0060: * should be located on the form's grid and how the component should be
0061: * positioned. In addition to its constraints object the
0062: * <code>FormLayout</code> also considers each component's minimum and
0063: * preferred sizes in order to determine a component's size.<p>
0064: *
0065: * FormLayout has been designed to work with non-visual builders that help you
0066: * specify the layout and fill the grid. For example, the
0067: * {@link com.jgoodies.forms.builder.ButtonBarBuilder} assists you in building button
0068: * bars; it creates a standardized FormLayout and provides a minimal API that
0069: * specializes in adding buttons. Other builders can create frequently used
0070: * panel design, for example a form that consists of rows of label-component
0071: * pairs.<p>
0072: *
0073: * FormLayout has been prepared to work with different types of sizes as
0074: * defined by the {@link Size} interface.<p>
0075: *
0076: * <strong>Example 1</strong> (Plain FormLayout):<br>
0077: * The following example creates a panel with 3 data columns and 3 data rows;
0078: * the columns and rows are specified before components are added
0079: * to the form.
0080: * <pre>
0081: * FormLayout layout = new FormLayout(
0082: * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns
0083: * "pref, 3dlu, pref, 3dlu, pref"); // rows
0084: *
0085: * CellConstraints cc = new CellConstraints();
0086: * JPanel panel = new JPanel(layout);
0087: * panel.add(new JLabel("Label1"), cc.xy (1, 1));
0088: * panel.add(new JTextField(), cc.xywh(3, 1, 3, 1));
0089: * panel.add(new JLabel("Label2"), cc.xy (1, 3));
0090: * panel.add(new JTextField(), cc.xy (3, 3));
0091: * panel.add(new JLabel("Label3"), cc.xy (1, 5));
0092: * panel.add(new JTextField(), cc.xy (3, 5));
0093: * panel.add(new JButton("/u2026"), cc.xy (5, 5));
0094: * return panel;
0095: * </pre><p>
0096: *
0097: * <strong>Example 2</strong> (Using PanelBuilder):<br>
0098: * This example creates the same panel as above using the
0099: * {@link com.jgoodies.forms.builder.PanelBuilder} to add components to the form.
0100: * <pre>
0101: * FormLayout layout = new FormLayout(
0102: * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns
0103: * "pref, 3dlu, pref, 3dlu, pref"); // rows
0104: *
0105: * PanelBuilder builder = new PanelBuilder(layout);
0106: * CellConstraints cc = new CellConstraints();
0107: * builder.addLabel("Label1", cc.xy (1, 1));
0108: * builder.add(new JTextField(), cc.xywh(3, 1, 3, 1));
0109: * builder.addLabel("Label2", cc.xy (1, 3));
0110: * builder.add(new JTextField(), cc.xy (3, 3));
0111: * builder.addLabel("Label3", cc.xy (1, 5));
0112: * builder.add(new JTextField(), cc.xy (3, 5));
0113: * builder.add(new JButton("/u2026"), cc.xy (5, 5));
0114: * return builder.getPanel();
0115: * </pre><p>
0116: *
0117: * <strong>Example 3</strong> (Using DefaultFormBuilder):<br>
0118: * This example utilizes the
0119: * {@link com.jgoodies.forms.builder.DefaultFormBuilder} that
0120: * ships with the source distribution.
0121: * <pre>
0122: * FormLayout layout = new FormLayout(
0123: * "right:pref, 6dlu, 50dlu, 4dlu, default"); // 5 columns; add rows later
0124: *
0125: * DefaultFormBuilder builder = new DefaultFormBuilder(layout);
0126: * builder.append("Label1", new JTextField(), 3);
0127: * builder.append("Label2", new JTextField());
0128: * builder.append("Label3", new JTextField());
0129: * builder.append(new JButton("/u2026"));
0130: * return builder.getPanel();
0131: * </pre><p>
0132: *
0133: * TODO: In the Forms 1.0.x invisible components are not taken into account
0134: * when the FormLayout lays out the container. Add an optional setting for
0135: * this on both the container-level and component-level. So one can specify
0136: * that invisible components shall be taken into account, but may exclude
0137: * individual components. Or the other way round, exclude invisible components,
0138: * and include individual components. The API of both the FormLayout and
0139: * CellConstraints classes shall be extended to support this option.
0140: * This feature is planned for the Forms version 1.1 and is described in
0141: * <a href="https://forms.dev.java.net/issues/show_bug.cgi?id=28">issue #28</a>
0142: * of the Forms' issue tracker where you can track the progress.
0143: *
0144: * @author Karsten Lentzsch
0145: * @version $Revision: 1.6 $
0146: *
0147: * @see ColumnSpec
0148: * @see RowSpec
0149: * @see CellConstraints
0150: * @see com.jgoodies.forms.builder.AbstractFormBuilder
0151: * @see com.jgoodies.forms.builder.ButtonBarBuilder
0152: * @see com.jgoodies.forms.builder.DefaultFormBuilder
0153: * @see com.jgoodies.forms.factories.FormFactory
0154: * @see Size
0155: * @see Sizes
0156: */
0157:
0158: public final class FormLayout implements LayoutManager2, Serializable {
0159:
0160: /**
0161: * Holds the column specifications.
0162: *
0163: * @see ColumnSpec
0164: * @see #getColumnCount()
0165: * @see #getColumnSpec(int)
0166: * @see #appendColumn(ColumnSpec)
0167: * @see #insertColumn(int, ColumnSpec)
0168: * @see #removeColumn(int)
0169: */
0170: private final List colSpecs;
0171:
0172: /**
0173: * Holds the row specifications.
0174: *
0175: * @see RowSpec
0176: * @see #getRowCount()
0177: * @see #getRowSpec(int)
0178: * @see #appendRow(RowSpec)
0179: * @see #insertRow(int, RowSpec)
0180: * @see #removeRow(int)
0181: */
0182: private final List rowSpecs;
0183:
0184: /**
0185: * Holds the column groups as an array of arrays of column indices.
0186: *
0187: * @see #getColumnGroups()
0188: * @see #setColumnGroups(int[][])
0189: * @see #addGroupedColumn(int)
0190: */
0191: private int[][] colGroupIndices;
0192:
0193: /**
0194: * Holds the row groups as an array of arrays of row indices.
0195: *
0196: * @see #getRowGroups()
0197: * @see #setRowGroups(int[][])
0198: * @see #addGroupedRow(int)
0199: */
0200: private int[][] rowGroupIndices;
0201:
0202: /**
0203: * Maps components to their associated <code>CellConstraints</code>.
0204: *
0205: * @see CellConstraints
0206: * @see #getConstraints(Component)
0207: * @see #setConstraints(Component, CellConstraints)
0208: */
0209: private final Map constraintMap;
0210:
0211: // Fields used by the Layout Algorithm **********************************
0212:
0213: /**
0214: * Holds the components that occupy exactly one column.
0215: * For each column we keep a list of these components.
0216: */
0217: private transient List[] colComponents;
0218:
0219: /**
0220: * Holds the components that occupy exactly one row.
0221: * For each row we keep a list of these components.
0222: */
0223: private transient List[] rowComponents;
0224:
0225: /**
0226: * Caches component minimum and preferred sizes.
0227: * All requests for component sizes shall be directed to the cache.
0228: */
0229: private final ComponentSizeCache componentSizeCache;
0230:
0231: /**
0232: * These functional objects are used to measure component sizes.
0233: * They abstract from horizontal and vertical orientation and so,
0234: * allow to implement the layout algorithm for both orientations with a
0235: * single set of methods.
0236: */
0237: private final Measure minimumWidthMeasure;
0238: private final Measure minimumHeightMeasure;
0239: private final Measure preferredWidthMeasure;
0240: private final Measure preferredHeightMeasure;
0241:
0242: // Instance Creation ****************************************************
0243:
0244: /**
0245: * Constructs an empty FormLayout. Columns and rows must be added
0246: * before components can be added to the layout container.<p>
0247: *
0248: * This constructor is intended to be used in environments
0249: * that add columns and rows dynamically.
0250: */
0251: public FormLayout() {
0252: this (new ColumnSpec[0], new RowSpec[0]);
0253: }
0254:
0255: /**
0256: * Constructs a FormLayout using the given encoded column specifications.
0257: * The constructed layout has no rows; these must be added
0258: * before components can be added to the layout container.<p>
0259: *
0260: * This constructor is primarily intended to be used with builder classes
0261: * that add rows dynamically, such as the <code>DefaultFormBuilder</code>.<p>
0262: *
0263: * <strong>Examples:</strong><pre>
0264: * // Label, gap, component
0265: * FormLayout layout = new FormLayout(
0266: * "pref, 4dlu, pref");
0267: *
0268: * // Right-aligned label, gap, component, gap, component
0269: * FormLayout layout = new FormLayout(
0270: * "right:pref, 4dlu, 50dlu, 4dlu, 50dlu");
0271: *
0272: * // Left-aligned labels, gap, components, gap, components
0273: * FormLayout layout = new FormLayout(
0274: * "left:pref, 4dlu, pref, 4dlu, pref");
0275: * </pre> See the class comment for more examples.
0276: *
0277: * @param encodedColumnSpecs comma separated encoded column specifications
0278: * @throws NullPointerException if encodedColumnSpecs is <code>null</code>
0279: */
0280: public FormLayout(String encodedColumnSpecs) {
0281: this (ColumnSpec.decodeSpecs(encodedColumnSpecs), new RowSpec[0]);
0282: }
0283:
0284: /**
0285: * Constructs a FormLayout using the given
0286: * encoded column and row specifications.<p>
0287: *
0288: * This constructor is recommended for most hand-coded layouts.<p>
0289: *
0290: * <strong>Examples:</strong><pre>
0291: * FormLayout layout = new FormLayout(
0292: * "pref, 4dlu, pref", // columns
0293: * "p, 3dlu, p"); // rows
0294: *
0295: * FormLayout layout = new FormLayout(
0296: * "right:pref, 4dlu, pref", // columns
0297: * "p, 3dlu, p, 3dlu, fill:p:grow"); // rows
0298: *
0299: * FormLayout layout = new FormLayout(
0300: * "left:pref, 4dlu, 50dlu", // columns
0301: * "p, 2px, p, 3dlu, p, 9dlu, p"); // rows
0302: *
0303: * FormLayout layout = new FormLayout(
0304: * "max(75dlu;pref), 4dlu, default", // columns
0305: * "p, 3dlu, p, 3dlu, p, 3dlu, p"); // rows
0306: * </pre> See the class comment for more examples.
0307: *
0308: * @param encodedColumnSpecs comma separated encoded column specifications
0309: * @param encodedRowSpecs comma separated encoded row specifications
0310: * @throws NullPointerException if encodedColumnSpecs or encodedRowSpecs
0311: * is <code>null</code>
0312: */
0313: public FormLayout(String encodedColumnSpecs, String encodedRowSpecs) {
0314: this (ColumnSpec.decodeSpecs(encodedColumnSpecs), RowSpec
0315: .decodeSpecs(encodedRowSpecs));
0316: }
0317:
0318: /**
0319: * Constructs a FormLayout using the given column specifications.
0320: * The constructed layout has no rows; these must be added
0321: * before components can be added to the layout container.
0322: *
0323: * @param colSpecs an array of column specifications.
0324: * @throws NullPointerException if colSpecs is null
0325: *
0326: * @since 1.1
0327: */
0328: public FormLayout(ColumnSpec[] colSpecs) {
0329: this (colSpecs, new RowSpec[] {});
0330: }
0331:
0332: /**
0333: * Constructs a FormLayout using the given column and row specifications.
0334: *
0335: * @param colSpecs an array of column specifications.
0336: * @param rowSpecs an array of row specifications.
0337: * @throws NullPointerException if colSpecs or rowSpecs is null
0338: */
0339: public FormLayout(ColumnSpec[] colSpecs, RowSpec[] rowSpecs) {
0340: if (colSpecs == null)
0341: throw new NullPointerException(
0342: "The column specifications must not be null.");
0343: if (rowSpecs == null)
0344: throw new NullPointerException(
0345: "The row specifications must not be null.");
0346:
0347: this .colSpecs = new ArrayList(Arrays.asList(colSpecs));
0348: this .rowSpecs = new ArrayList(Arrays.asList(rowSpecs));
0349: colGroupIndices = new int[][] {};
0350: rowGroupIndices = new int[][] {};
0351: int initialCapacity = colSpecs.length * rowSpecs.length / 4;
0352: constraintMap = new HashMap(initialCapacity);
0353: componentSizeCache = new ComponentSizeCache(initialCapacity);
0354: minimumWidthMeasure = new MinimumWidthMeasure(
0355: componentSizeCache);
0356: minimumHeightMeasure = new MinimumHeightMeasure(
0357: componentSizeCache);
0358: preferredWidthMeasure = new PreferredWidthMeasure(
0359: componentSizeCache);
0360: preferredHeightMeasure = new PreferredHeightMeasure(
0361: componentSizeCache);
0362: }
0363:
0364: // Accessing the Column and Row Specifications **************************
0365:
0366: /**
0367: * Returns the number of columns in this layout.
0368: *
0369: * @return the number of columns
0370: */
0371: public int getColumnCount() {
0372: return colSpecs.size();
0373: }
0374:
0375: /**
0376: * Returns the number of rows in this layout.
0377: *
0378: * @return the number of rows
0379: */
0380: public int getRowCount() {
0381: return rowSpecs.size();
0382: }
0383:
0384: /**
0385: * Returns the <code>ColumnSpec</code> at the specified column index.
0386: *
0387: * @param columnIndex the column index of the requested <code>ColumnSpec</code>
0388: * @return the <code>ColumnSpec</code> at the specified column
0389: * @throws IndexOutOfBoundsException if the column index is out of range
0390: */
0391: public ColumnSpec getColumnSpec(int columnIndex) {
0392: return (ColumnSpec) colSpecs.get(columnIndex - 1);
0393: }
0394:
0395: /**
0396: * Sets the <code>ColumnSpec</code> at the specified column index.
0397: *
0398: * @param columnIndex the index of the column to be changed
0399: * @param columnSpec the <code>ColumnSpec</code> to be set
0400: * @throws NullPointerException if the column specification is null
0401: * @throws IndexOutOfBoundsException if the column index is out of range
0402: */
0403: public void setColumnSpec(int columnIndex, ColumnSpec columnSpec) {
0404: if (columnSpec == null) {
0405: throw new NullPointerException(
0406: "The column spec must not be null.");
0407: }
0408: colSpecs.set(columnIndex - 1, columnSpec);
0409: }
0410:
0411: /**
0412: * Returns the <code>RowSpec</code> at the specified row index.
0413: *
0414: * @param rowIndex the row index of the requested <code>RowSpec</code>
0415: * @return the <code>RowSpec</code> at the specified row
0416: * @throws IndexOutOfBoundsException if the row index is out of range
0417: */
0418: public RowSpec getRowSpec(int rowIndex) {
0419: return (RowSpec) rowSpecs.get(rowIndex - 1);
0420: }
0421:
0422: /**
0423: * Sets the <code>RowSpec</code> at the specified row index.
0424: *
0425: * @param rowIndex the index of the row to be changed
0426: * @param rowSpec the <code>RowSpec</code> to be set
0427: * @throws NullPointerException if the row specification is null
0428: * @throws IndexOutOfBoundsException if the row index is out of range
0429: */
0430: public void setRowSpec(int rowIndex, RowSpec rowSpec) {
0431: if (rowSpec == null) {
0432: throw new NullPointerException(
0433: "The row spec must not be null.");
0434: }
0435: rowSpecs.set(rowIndex - 1, rowSpec);
0436: }
0437:
0438: /**
0439: * Appends the given column specification to the right hand side of all
0440: * columns.
0441: *
0442: * @param columnSpec the column specification to be added
0443: * @throws NullPointerException if the column specification is null
0444: */
0445: public void appendColumn(ColumnSpec columnSpec) {
0446: if (columnSpec == null) {
0447: throw new NullPointerException(
0448: "The column spec must not be null.");
0449: }
0450: colSpecs.add(columnSpec);
0451: }
0452:
0453: /**
0454: * Inserts the specified column at the specified position. Shifts components
0455: * that intersect the new column to the right hand side and readjusts
0456: * column groups.<p>
0457: *
0458: * The component shift works as follows: components that were located on
0459: * the right hand side of the inserted column are shifted one column to
0460: * the right; component column span is increased by one if it intersects
0461: * the new column.<p>
0462: *
0463: * Column group indices that are greater or equal than the given column
0464: * index will be increased by one.
0465: *
0466: * @param columnIndex index of the column to be inserted
0467: * @param columnSpec specification of the column to be inserted
0468: * @throws IndexOutOfBoundsException if the column index is out of range
0469: */
0470: public void insertColumn(int columnIndex, ColumnSpec columnSpec) {
0471: if (columnIndex < 1 || columnIndex > getColumnCount()) {
0472: throw new IndexOutOfBoundsException("The column index "
0473: + columnIndex + "must be in the range [1, "
0474: + getColumnCount() + "].");
0475: }
0476: colSpecs.add(columnIndex - 1, columnSpec);
0477: shiftComponentsHorizontally(columnIndex, false);
0478: adjustGroupIndices(colGroupIndices, columnIndex, false);
0479: }
0480:
0481: /**
0482: * Removes the column with the given column index from the layout.
0483: * Components will be rearranged and column groups will be readjusted.
0484: * Therefore, the column must not contain components and must not be part
0485: * of a column group.<p>
0486: *
0487: * The component shift works as follows: components that were located on
0488: * the right hand side of the removed column are moved one column to the
0489: * left; component column span is decreased by one if it intersects the
0490: * removed column.<p>
0491: *
0492: * Column group indices that are greater than the column index will be
0493: * decreased by one.<p>
0494: *
0495: * <strong>Note:</strong> If one of the constraints mentioned above
0496: * is violated, this layout's state becomes illegal and it is unsafe
0497: * to work with this layout.
0498: * A typical layout implementation can ensure that these constraints are
0499: * not violated. However, in some cases you may need to check these
0500: * conditions before you invoke this method. The Forms extras contain
0501: * source code for class <code>FormLayoutUtils</code> that provides
0502: * the required test methods:<br>
0503: * <code>#columnContainsComponents(Container, int)</code> and<br>
0504: * <code>#isGroupedColumn(FormLayout, int)</code>.
0505: *
0506: * @param columnIndex index of the column to remove
0507: * @throws IndexOutOfBoundsException if the column index is out of range
0508: * @throws IllegalStateException if the column contains components
0509: * or if the column is already grouped
0510: *
0511: * @see com.jgoodies.forms.extras.FormLayoutUtils#columnContainsComponent(Container, int)
0512: * @see com.jgoodies.forms.extras.FormLayoutUtils#isGroupedColumn(FormLayout, int)
0513: */
0514: public void removeColumn(int columnIndex) {
0515: if (columnIndex < 1 || columnIndex > getColumnCount()) {
0516: throw new IndexOutOfBoundsException("The column index "
0517: + columnIndex + " must be in the range [1, "
0518: + getColumnCount() + "].");
0519: }
0520: colSpecs.remove(columnIndex - 1);
0521: shiftComponentsHorizontally(columnIndex, true);
0522: adjustGroupIndices(colGroupIndices, columnIndex, true);
0523: }
0524:
0525: /**
0526: * Appends the given row specification to the bottom of all rows.
0527: *
0528: * @param rowSpec the row specification to be added to the form layout
0529: * @throws NullPointerException if the rowSpec is null
0530: */
0531: public void appendRow(RowSpec rowSpec) {
0532: if (rowSpec == null) {
0533: throw new NullPointerException(
0534: "The row spec must not be null.");
0535: }
0536: rowSpecs.add(rowSpec);
0537: }
0538:
0539: /**
0540: * Inserts the specified column at the specified position. Shifts
0541: * components that intersect the new column to the right and readjusts
0542: * column groups.<p>
0543: *
0544: * The component shift works as follows: components that were located on
0545: * the right hand side of the inserted column are shifted one column to
0546: * the right; component column span is increased by one if it intersects
0547: * the new column.<p>
0548: *
0549: * Column group indices that are greater or equal than the given column
0550: * index will be increased by one.
0551: *
0552: * @param rowIndex index of the row to be inserted
0553: * @param rowSpec specification of the row to be inserted
0554: * @throws IndexOutOfBoundsException if the row index is out of range
0555: */
0556: public void insertRow(int rowIndex, RowSpec rowSpec) {
0557: if (rowIndex < 1 || rowIndex > getRowCount()) {
0558: throw new IndexOutOfBoundsException("The row index "
0559: + rowIndex + " must be in the range [1, "
0560: + getRowCount() + "].");
0561: }
0562: rowSpecs.add(rowIndex - 1, rowSpec);
0563: shiftComponentsVertically(rowIndex, false);
0564: adjustGroupIndices(rowGroupIndices, rowIndex, false);
0565: }
0566:
0567: /**
0568: * Removes the row with the given row index from the layout. Components
0569: * will be rearranged and row groups will be readjusted. Therefore, the
0570: * row must not contain components and must not be part of a row group.<p>
0571: *
0572: * The component shift works as follows: components that were located
0573: * below the removed row are moved up one row; component row span is
0574: * decreased by one if it intersects the removed row.<p>
0575: *
0576: * Row group indices that are greater than the row index will be decreased
0577: * by one.<p>
0578: *
0579: * <strong>Note:</strong> If one of the constraints mentioned above
0580: * is violated, this layout's state becomes illegal and it is unsafe
0581: * to work with this layout.
0582: * A typical layout implementation can ensure that these constraints are
0583: * not violated. However, in some cases you may need to check these
0584: * conditions before you invoke this method. The Forms extras contain
0585: * source code for class <code>FormLayoutUtils</code> that provides
0586: * the required test methods:<br>
0587: * <code>#rowContainsComponents(Container, int)</code> and<br>
0588: * <code>#isGroupedRow(FormLayout, int)</code>.
0589: *
0590: * @param rowIndex index of the row to remove
0591: * @throws IndexOutOfBoundsException if the row index is out of range
0592: * @throws IllegalStateException if the row contains components
0593: * or if the row is already grouped
0594: *
0595: * @see com.jgoodies.forms.extras.FormLayoutUtils#rowContainsComponent(Container, int)
0596: * @see com.jgoodies.forms.extras.FormLayoutUtils#isGroupedRow(FormLayout, int)
0597: */
0598: public void removeRow(int rowIndex) {
0599: if (rowIndex < 1 || rowIndex > getRowCount()) {
0600: throw new IndexOutOfBoundsException("The row index "
0601: + rowIndex + "must be in the range [1, "
0602: + getRowCount() + "].");
0603: }
0604: rowSpecs.remove(rowIndex - 1);
0605: shiftComponentsVertically(rowIndex, true);
0606: adjustGroupIndices(rowGroupIndices, rowIndex, true);
0607: }
0608:
0609: /**
0610: * Shifts components horizontally, either to the right if a column has been
0611: * inserted or to the left if a column has been removed.
0612: *
0613: * @param columnIndex index of the column to remove
0614: * @param remove true for remove, false for insert
0615: * @throws IllegalStateException if a removed column contains components
0616: */
0617: private void shiftComponentsHorizontally(int columnIndex,
0618: boolean remove) {
0619: final int offset = remove ? -1 : 1;
0620: for (Iterator i = constraintMap.entrySet().iterator(); i
0621: .hasNext();) {
0622: Map.Entry entry = (Map.Entry) i.next();
0623: CellConstraints constraints = (CellConstraints) entry
0624: .getValue();
0625: int x1 = constraints.gridX;
0626: int w = constraints.gridWidth;
0627: int x2 = x1 + w - 1;
0628: if (x1 == columnIndex && remove) {
0629: throw new IllegalStateException("The removed column "
0630: + columnIndex
0631: + " must not contain component origins.\n"
0632: + "Illegal component=" + entry.getKey());
0633: } else if (x1 >= columnIndex) {
0634: constraints.gridX += offset;
0635: } else if (x2 >= columnIndex) {
0636: constraints.gridWidth += offset;
0637: }
0638: }
0639: }
0640:
0641: /**
0642: * Shifts components vertically, either to the bottom if a row has been
0643: * inserted or to the top if a row has been removed.
0644: *
0645: * @param rowIndex index of the row to remove
0646: * @param remove true for remove, false for insert
0647: * @throws IllegalStateException if a removed column contains components
0648: */
0649: private void shiftComponentsVertically(int rowIndex, boolean remove) {
0650: final int offset = remove ? -1 : 1;
0651: for (Iterator i = constraintMap.entrySet().iterator(); i
0652: .hasNext();) {
0653: Map.Entry entry = (Map.Entry) i.next();
0654: CellConstraints constraints = (CellConstraints) entry
0655: .getValue();
0656: int y1 = constraints.gridY;
0657: int h = constraints.gridHeight;
0658: int y2 = y1 + h - 1;
0659: if (y1 == rowIndex && remove) {
0660: throw new IllegalStateException("The removed row "
0661: + rowIndex
0662: + " must not contain component origins.\n"
0663: + "Illegal component=" + entry.getKey());
0664: } else if (y1 >= rowIndex) {
0665: constraints.gridY += offset;
0666: } else if (y2 >= rowIndex) {
0667: constraints.gridHeight += offset;
0668: }
0669: }
0670: }
0671:
0672: /**
0673: * Adjusts group indices. Shifts the given groups to left, right, up,
0674: * down according to the specified remove or add flag.
0675: *
0676: * @param allGroupIndices the groups to be adjusted
0677: * @param modifiedIndex the modified column or row index
0678: * @param remove true for remove, false for add
0679: * @throws IllegalStateException if we remove and the index is grouped
0680: */
0681: private void adjustGroupIndices(int[][] allGroupIndices,
0682: int modifiedIndex, boolean remove) {
0683: final int offset = remove ? -1 : +1;
0684: for (int group = 0; group < allGroupIndices.length; group++) {
0685: int[] groupIndices = allGroupIndices[group];
0686: for (int i = 0; i < groupIndices.length; i++) {
0687: int index = groupIndices[i];
0688: if (index == modifiedIndex && remove) {
0689: throw new IllegalStateException(
0690: "The removed index " + modifiedIndex
0691: + " must not be grouped.");
0692: } else if (index >= modifiedIndex) {
0693: groupIndices[i] += offset;
0694: }
0695: }
0696: }
0697: }
0698:
0699: // Accessing Constraints ************************************************
0700:
0701: /**
0702: * Looks up and returns the constraints for the specified component.
0703: * A copy of the actual <code>CellConstraints</code> object is returned.
0704: *
0705: * @param component the component to be queried
0706: * @return the <code>CellConstraints</code> for the specified component
0707: * @throws NullPointerException if component is <code>null</code> or
0708: * has not been added to the container
0709: */
0710: public CellConstraints getConstraints(Component component) {
0711: if (component == null)
0712: throw new NullPointerException(
0713: "The component must not be null.");
0714:
0715: CellConstraints constraints = (CellConstraints) constraintMap
0716: .get(component);
0717: if (constraints == null)
0718: throw new NullPointerException(
0719: "The component has not been added to the container.");
0720:
0721: return (CellConstraints) constraints.clone();
0722: }
0723:
0724: /**
0725: * Sets the constraints for the specified component in this layout.
0726: *
0727: * @param component the component to be modified
0728: * @param constraints the constraints to be applied
0729: * @throws NullPointerException if the component or constraints object
0730: * is <code>null</code>
0731: */
0732: public void setConstraints(Component component,
0733: CellConstraints constraints) {
0734: if (component == null)
0735: throw new NullPointerException(
0736: "The component must not be null.");
0737: if (constraints == null)
0738: throw new NullPointerException(
0739: "The constraints must not be null.");
0740:
0741: constraints.ensureValidGridBounds(getColumnCount(),
0742: getRowCount());
0743: constraintMap.put(component, constraints.clone());
0744: }
0745:
0746: /**
0747: * Removes the constraints for the specified component in this layout.
0748: *
0749: * @param component the component to be modified
0750: */
0751: private void removeConstraints(Component component) {
0752: constraintMap.remove(component);
0753: componentSizeCache.removeEntry(component);
0754: }
0755:
0756: // Accessing Column and Row Groups **************************************
0757:
0758: /**
0759: * Returns a deep copy of the column groups.
0760: *
0761: * @return the column groups as two-dimensional int array
0762: */
0763: public int[][] getColumnGroups() {
0764: return deepClone(colGroupIndices);
0765: }
0766:
0767: /**
0768: * Sets the column groups, where each column in a group gets the same
0769: * group wide width. Each group is described by an array of integers that
0770: * are interpreted as column indices. The parameter is an array of such
0771: * group descriptions.<p>
0772: *
0773: * <strong>Examples:</strong><pre>
0774: * // Group columns 1, 3 and 4.
0775: * setColumnGroups(new int[][]{ {1, 3, 4}});
0776: *
0777: * // Group columns 1, 3, 4, and group columns 7 and 9
0778: * setColumnGroups(new int[][]{ {1, 3, 4}, {7, 9}});
0779: * </pre>
0780: *
0781: * @param colGroupIndices a two-dimensional array of column groups indices
0782: * @throws IndexOutOfBoundsException if an index is outside the grid
0783: * @throws IllegalArgumentException if a column index is used twice
0784: */
0785: public void setColumnGroups(int[][] colGroupIndices) {
0786: int maxColumn = getColumnCount();
0787: boolean[] usedIndices = new boolean[maxColumn + 1];
0788: for (int group = 0; group < colGroupIndices.length; group++) {
0789: for (int j = 0; j < colGroupIndices[group].length; j++) {
0790: int colIndex = colGroupIndices[group][j];
0791: if (colIndex < 1 || colIndex > maxColumn) {
0792: throw new IndexOutOfBoundsException(
0793: "Invalid column group index " + colIndex
0794: + " in group " + (group + 1));
0795: }
0796: if (usedIndices[colIndex]) {
0797: throw new IllegalArgumentException(
0798: "Column index "
0799: + colIndex
0800: + " must not be used in multiple column groups.");
0801: }
0802: usedIndices[colIndex] = true;
0803: }
0804: }
0805: this .colGroupIndices = deepClone(colGroupIndices);
0806: }
0807:
0808: /**
0809: * Adds the specified column index to the last column group.
0810: * In case there are no groups, a new group will be created.
0811: *
0812: * @param columnIndex the column index to be set grouped
0813: */
0814: public void addGroupedColumn(int columnIndex) {
0815: int[][] newColGroups = getColumnGroups();
0816: // Create a group if none exists.
0817: if (newColGroups.length == 0) {
0818: newColGroups = new int[][] { { columnIndex } };
0819: } else {
0820: int lastGroupIndex = newColGroups.length - 1;
0821: int[] lastGroup = newColGroups[lastGroupIndex];
0822: int groupSize = lastGroup.length;
0823: int[] newLastGroup = new int[groupSize + 1];
0824: System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize);
0825: newLastGroup[groupSize] = columnIndex;
0826: newColGroups[lastGroupIndex] = newLastGroup;
0827: }
0828: setColumnGroups(newColGroups);
0829: }
0830:
0831: /**
0832: * Returns a deep copy of the row groups.
0833: *
0834: * @return the row groups as two-dimensional int array
0835: */
0836: public int[][] getRowGroups() {
0837: return deepClone(rowGroupIndices);
0838: }
0839:
0840: /**
0841: * Sets the row groups, where each row in such a group gets the same group
0842: * wide height. Each group is described by an array of integers that are
0843: * interpreted as row indices. The parameter is an array of such group
0844: * descriptions.<p>
0845: *
0846: * <strong>Examples:</strong><pre>
0847: * // Group rows 1 and 2.
0848: * setRowGroups(new int[][]{ {1, 2}});
0849: *
0850: * // Group rows 1 and 2, and group rows 5, 7, and 9.
0851: * setRowGroups(new int[][]{ {1, 2}, {5, 7, 9}});
0852: * </pre>
0853: *
0854: * @param rowGroupIndices a two-dimensional array of row group indices.
0855: * @throws IndexOutOfBoundsException if an index is outside the grid
0856: */
0857: public void setRowGroups(int[][] rowGroupIndices) {
0858: int rowCount = getRowCount();
0859: boolean[] usedIndices = new boolean[rowCount + 1];
0860: for (int i = 0; i < rowGroupIndices.length; i++) {
0861: for (int j = 0; j < rowGroupIndices[i].length; j++) {
0862: int rowIndex = rowGroupIndices[i][j];
0863: if (rowIndex < 1 || rowIndex > rowCount) {
0864: throw new IndexOutOfBoundsException(
0865: "Invalid row group index " + rowIndex
0866: + " in group " + (i + 1));
0867: }
0868: if (usedIndices[rowIndex]) {
0869: throw new IllegalArgumentException(
0870: "Row index "
0871: + rowIndex
0872: + " must not be used in multiple row groups.");
0873: }
0874: usedIndices[rowIndex] = true;
0875: }
0876: }
0877: this .rowGroupIndices = deepClone(rowGroupIndices);
0878: }
0879:
0880: /**
0881: * Adds the specified row index to the last row group.
0882: * In case there are no groups, a new group will be created.
0883: *
0884: * @param rowIndex the index of the row that should be grouped
0885: */
0886: public void addGroupedRow(int rowIndex) {
0887: int[][] newRowGroups = getRowGroups();
0888: // Create a group if none exists.
0889: if (newRowGroups.length == 0) {
0890: newRowGroups = new int[][] { { rowIndex } };
0891: } else {
0892: int lastGroupIndex = newRowGroups.length - 1;
0893: int[] lastGroup = newRowGroups[lastGroupIndex];
0894: int groupSize = lastGroup.length;
0895: int[] newLastGroup = new int[groupSize + 1];
0896: System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize);
0897: newLastGroup[groupSize] = rowIndex;
0898: newRowGroups[lastGroupIndex] = newLastGroup;
0899: }
0900: setRowGroups(newRowGroups);
0901: }
0902:
0903: // Implementing the LayoutManager and LayoutManager2 Interfaces *********
0904:
0905: /**
0906: * Throws an <code>UnsupportedOperationException</code>. Does not add
0907: * the specified component with the specified name to the layout.
0908: *
0909: * @param name indicates entry's position and anchor
0910: * @param component component to add
0911: * @throws UnsupportedOperationException always
0912: */
0913: public void addLayoutComponent(String name, Component component) {
0914: throw new UnsupportedOperationException(
0915: "Use #addLayoutComponent(Component, Object) instead.");
0916: }
0917:
0918: /**
0919: * Adds the specified component to the layout, using the specified
0920: * <code>constraints</code> object. Note that constraints are mutable and
0921: * are, therefore, cloned when cached.
0922: *
0923: * @param comp the component to be added
0924: * @param constraints the component's cell constraints
0925: * @throws NullPointerException if <code>constraints</code> is <code>null</code>
0926: * @throws IllegalArgumentException if <code>constraints</code> is not a
0927: * <code>CellConstraints</code> or a String that cannot be used to construct
0928: * a <code>CellConstraints</code>
0929: */
0930: public void addLayoutComponent(Component comp, Object constraints) {
0931: if (constraints instanceof String) {
0932: setConstraints(comp, new CellConstraints(
0933: (String) constraints));
0934: } else if (constraints instanceof CellConstraints) {
0935: setConstraints(comp, (CellConstraints) constraints);
0936: } else if (constraints == null) {
0937: throw new NullPointerException(
0938: "The constraints must not be null.");
0939: } else {
0940: throw new IllegalArgumentException(
0941: "Illegal constraint type " + constraints.getClass());
0942: }
0943: }
0944:
0945: /**
0946: * Removes the specified component from this layout.<p>
0947: *
0948: * Most applications do not call this method directly.
0949: *
0950: * @param comp the component to be removed.
0951: * @see Container#remove(java.awt.Component)
0952: * @see Container#removeAll()
0953: */
0954: public void removeLayoutComponent(Component comp) {
0955: removeConstraints(comp);
0956: }
0957:
0958: // Layout Requests ******************************************************
0959:
0960: /**
0961: * Determines the minimum size of the <code>parent</code> container
0962: * using this form layout.<p>
0963: *
0964: * Most applications do not call this method directly.
0965: *
0966: * @param parent the container in which to do the layout
0967: * @return the minimum size of the <code>parent</code> container
0968: *
0969: * @see Container#doLayout()
0970: */
0971: public Dimension minimumLayoutSize(Container parent) {
0972: return computeLayoutSize(parent, minimumWidthMeasure,
0973: minimumHeightMeasure);
0974: }
0975:
0976: /**
0977: * Determines the preferred size of the <code>parent</code>
0978: * container using this form layout.<p>
0979: *
0980: * Most applications do not call this method directly.
0981: *
0982: * @param parent the container in which to do the layout
0983: * @return the preferred size of the <code>parent</code> container
0984: *
0985: * @see Container#getPreferredSize()
0986: */
0987: public Dimension preferredLayoutSize(Container parent) {
0988: return computeLayoutSize(parent, preferredWidthMeasure,
0989: preferredHeightMeasure);
0990: }
0991:
0992: /**
0993: * Returns the maximum dimensions for this layout given the components
0994: * in the specified target container.
0995: *
0996: * @param target the container which needs to be laid out
0997: * @see Container
0998: * @see #minimumLayoutSize(Container)
0999: * @see #preferredLayoutSize(Container)
1000: * @return the maximum dimensions for this layout
1001: */
1002: public Dimension maximumLayoutSize(Container target) {
1003: return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1004: }
1005:
1006: /**
1007: * Returns the alignment along the x axis. This specifies how
1008: * the component would like to be aligned relative to other
1009: * components. The value should be a number between 0 and 1
1010: * where 0 represents alignment along the origin, 1 is aligned
1011: * the furthest away from the origin, 0.5 is centered, etc.
1012: *
1013: * @param parent the parent container
1014: * @return the value <code>0.5f</code> to indicate center alignment
1015: */
1016: public float getLayoutAlignmentX(Container parent) {
1017: return 0.5f;
1018: }
1019:
1020: /**
1021: * Returns the alignment along the y axis. This specifies how
1022: * the component would like to be aligned relative to other
1023: * components. The value should be a number between 0 and 1
1024: * where 0 represents alignment along the origin, 1 is aligned
1025: * the furthest away from the origin, 0.5 is centered, etc.
1026: *
1027: * @param parent the parent container
1028: * @return the value <code>0.5f</code> to indicate center alignment
1029: */
1030: public float getLayoutAlignmentY(Container parent) {
1031: return 0.5f;
1032: }
1033:
1034: /**
1035: * Invalidates the layout, indicating that if the layout manager
1036: * has cached information it should be discarded.
1037: *
1038: * @param target the container that holds the layout to be invalidated
1039: */
1040: public void invalidateLayout(Container target) {
1041: invalidateCaches();
1042: }
1043:
1044: /**
1045: * Lays out the specified container using this form layout. This method
1046: * reshapes components in the specified container in order to satisfy the
1047: * contraints of this <code>FormLayout</code> object.<p>
1048: *
1049: * Most applications do not call this method directly.<p>
1050: *
1051: * The form layout performs the following steps:
1052: * <ol>
1053: * <li>find components that occupy exactly one column or row
1054: * <li>compute minimum widths and heights
1055: * <li>compute preferred widths and heights
1056: * <li>give cols and row equal size if they share a group
1057: * <li>compress default columns and rows if total is less than pref size
1058: * <li>give cols and row equal size if they share a group
1059: * <li>distribute free space
1060: * <li>set components bounds
1061: * </ol>
1062: *
1063: * @param parent the container in which to do the layout
1064: * @see Container
1065: * @see Container#doLayout()
1066: */
1067: public void layoutContainer(Container parent) {
1068: synchronized (parent.getTreeLock()) {
1069: initializeColAndRowComponentLists();
1070: Dimension size = parent.getSize();
1071:
1072: Insets insets = parent.getInsets();
1073: int totalWidth = size.width - insets.left - insets.right;
1074: int totalHeight = size.height - insets.top - insets.bottom;
1075:
1076: int[] x = computeGridOrigins(parent, totalWidth,
1077: insets.left, colSpecs, colComponents,
1078: colGroupIndices, minimumWidthMeasure,
1079: preferredWidthMeasure);
1080: int[] y = computeGridOrigins(parent, totalHeight,
1081: insets.top, rowSpecs, rowComponents,
1082: rowGroupIndices, minimumHeightMeasure,
1083: preferredHeightMeasure);
1084:
1085: layoutComponents(x, y);
1086: }
1087: }
1088:
1089: // Layout Algorithm *****************************************************
1090:
1091: /**
1092: * Initializes two lists for columns and rows that hold a column's
1093: * or row's components that span only this column or row.<p>
1094: *
1095: * Iterates over all components and their associated constraints;
1096: * every component that has a column span or row span of 1
1097: * is put into the column's or row's component list.<p>
1098: *
1099: * As of the Forms version 1.0.x invisible components are not taken
1100: * into account when the container is layed out. See the TODO in the
1101: * JavaDoc class commment for details on this issue.
1102: */
1103: private void initializeColAndRowComponentLists() {
1104: colComponents = new LinkedList[getColumnCount()];
1105: for (int i = 0; i < getColumnCount(); i++) {
1106: colComponents[i] = new LinkedList();
1107: }
1108:
1109: rowComponents = new LinkedList[getRowCount()];
1110: for (int i = 0; i < getRowCount(); i++) {
1111: rowComponents[i] = new LinkedList();
1112: }
1113:
1114: for (Iterator i = constraintMap.entrySet().iterator(); i
1115: .hasNext();) {
1116: Map.Entry entry = (Map.Entry) i.next();
1117: Component component = (Component) entry.getKey();
1118: if (!component.isVisible())
1119: continue;
1120:
1121: CellConstraints constraints = (CellConstraints) entry
1122: .getValue();
1123: if (constraints.gridWidth == 1)
1124: colComponents[constraints.gridX - 1].add(component);
1125:
1126: if (constraints.gridHeight == 1)
1127: rowComponents[constraints.gridY - 1].add(component);
1128: }
1129: }
1130:
1131: /**
1132: * Computes and returns the layout size of the given <code>parent</code>
1133: * container using the specified measures.
1134: *
1135: * @param parent the container in which to do the layout
1136: * @param defaultWidthMeasure the measure used to compute the default width
1137: * @param defaultHeightMeasure the measure used to compute the default height
1138: * @return the layout size of the <code>parent</code> container
1139: */
1140: private Dimension computeLayoutSize(Container parent,
1141: Measure defaultWidthMeasure, Measure defaultHeightMeasure) {
1142: synchronized (parent.getTreeLock()) {
1143: initializeColAndRowComponentLists();
1144: int[] colWidths = maximumSizes(parent, colSpecs,
1145: colComponents, minimumWidthMeasure,
1146: preferredWidthMeasure, defaultWidthMeasure);
1147: int[] rowHeights = maximumSizes(parent, rowSpecs,
1148: rowComponents, minimumHeightMeasure,
1149: preferredHeightMeasure, defaultHeightMeasure);
1150: int[] groupedWidths = groupedSizes(colGroupIndices,
1151: colWidths);
1152: int[] groupedHeights = groupedSizes(rowGroupIndices,
1153: rowHeights);
1154:
1155: // Convert sizes to origins.
1156: int[] xOrigins = computeOrigins(groupedWidths, 0);
1157: int[] yOrigins = computeOrigins(groupedHeights, 0);
1158:
1159: int width1 = sum(groupedWidths);
1160: int height1 = sum(groupedHeights);
1161: int maxWidth = width1;
1162: int maxHeight = height1;
1163:
1164: /*
1165: * Take components that span multiple columns or rows into account.
1166: * This shall be done if and only if a component spans an interval
1167: * that can grow.
1168: */
1169: // First computes the maximum number of cols/rows a component
1170: // can span without spanning a growing column.
1171: int[] maxFixedSizeColsTable = computeMaximumFixedSpanTable(colSpecs);
1172: int[] maxFixedSizeRowsTable = computeMaximumFixedSpanTable(rowSpecs);
1173:
1174: for (Iterator i = constraintMap.entrySet().iterator(); i
1175: .hasNext();) {
1176: Map.Entry entry = (Map.Entry) i.next();
1177: Component component = (Component) entry.getKey();
1178: if (!component.isVisible())
1179: continue;
1180:
1181: CellConstraints constraints = (CellConstraints) entry
1182: .getValue();
1183: if ((constraints.gridWidth > 1)
1184: && (constraints.gridWidth > maxFixedSizeColsTable[constraints.gridX - 1])) {
1185: //int compWidth = minimumWidthMeasure.sizeOf(component);
1186: int compWidth = defaultWidthMeasure
1187: .sizeOf(component);
1188: //int compWidth = preferredWidthMeasure.sizeOf(component);
1189: int gridX1 = constraints.gridX - 1;
1190: int gridX2 = gridX1 + constraints.gridWidth;
1191: int lead = xOrigins[gridX1];
1192: int trail = width1 - xOrigins[gridX2];
1193: int myWidth = lead + compWidth + trail;
1194: if (myWidth > maxWidth) {
1195: maxWidth = myWidth;
1196: }
1197: }
1198:
1199: if ((constraints.gridHeight > 1)
1200: && (constraints.gridHeight > maxFixedSizeRowsTable[constraints.gridY - 1])) {
1201: //int compHeight = minimumHeightMeasure.sizeOf(component);
1202: int compHeight = defaultHeightMeasure
1203: .sizeOf(component);
1204: //int compHeight = preferredHeightMeasure.sizeOf(component);
1205: int gridY1 = constraints.gridY - 1;
1206: int gridY2 = gridY1 + constraints.gridHeight;
1207: int lead = yOrigins[gridY1];
1208: int trail = height1 - yOrigins[gridY2];
1209: int myHeight = lead + compHeight + trail;
1210: if (myHeight > maxHeight) {
1211: maxHeight = myHeight;
1212: }
1213: }
1214: }
1215: Insets insets = parent.getInsets();
1216: int width = maxWidth + insets.left + insets.right;
1217: int height = maxHeight + insets.top + insets.bottom;
1218: return new Dimension(width, height);
1219: }
1220: }
1221:
1222: /**
1223: * Computes and returns the grid's origins.
1224: *
1225: * @param container the layout container
1226: * @param totalSize the total size to assign
1227: * @param offset the offset from left or top margin
1228: * @param formSpecs the column or row specs, resp.
1229: * @param componentLists the components list for each col/row
1230: * @param minMeasure the measure used to determin min sizes
1231: * @param prefMeasure the measure used to determin pre sizes
1232: * @param groupIndices the group specification
1233: * @return an int array with the origins
1234: */
1235: private int[] computeGridOrigins(Container container,
1236: int totalSize, int offset, List formSpecs,
1237: List[] componentLists, int[][] groupIndices,
1238: Measure minMeasure, Measure prefMeasure) {
1239: /* For each spec compute the minimum and preferred size that is
1240: * the maximum of all component minimum and preferred sizes resp.
1241: */
1242: int[] minSizes = maximumSizes(container, formSpecs,
1243: componentLists, minMeasure, prefMeasure, minMeasure);
1244: int[] prefSizes = maximumSizes(container, formSpecs,
1245: componentLists, minMeasure, prefMeasure, prefMeasure);
1246:
1247: int[] groupedMinSizes = groupedSizes(groupIndices, minSizes);
1248: int[] groupedPrefSizes = groupedSizes(groupIndices, prefSizes);
1249: int totalMinSize = sum(groupedMinSizes);
1250: int totalPrefSize = sum(groupedPrefSizes);
1251: int[] compressedSizes = compressedSizes(formSpecs, totalSize,
1252: totalMinSize, totalPrefSize, groupedMinSizes, prefSizes);
1253: int[] groupedSizes = groupedSizes(groupIndices, compressedSizes);
1254: int totalGroupedSize = sum(groupedSizes);
1255: int[] sizes = distributedSizes(formSpecs, totalSize,
1256: totalGroupedSize, groupedSizes);
1257: return computeOrigins(sizes, offset);
1258: }
1259:
1260: /**
1261: * Computes origins from sizes taking the specified offset into account.
1262: *
1263: * @param sizes the array of sizes
1264: * @param offset an offset for the first origin
1265: * @return an array of origins
1266: */
1267: private int[] computeOrigins(int[] sizes, int offset) {
1268: int count = sizes.length;
1269: int[] origins = new int[count + 1];
1270: origins[0] = offset;
1271: for (int i = 1; i <= count; i++) {
1272: origins[i] = origins[i - 1] + sizes[i - 1];
1273: }
1274: return origins;
1275: }
1276:
1277: /**
1278: * Lays out the components using the given x and y origins, the column
1279: * and row specifications, and the component constraints.<p>
1280: *
1281: * The actual computation is done by each component's form constraint
1282: * object. We just compute the cell, the cell bounds and then hand over
1283: * the component, cell bounds, and measure to the form constraints.
1284: * This will allow potential subclasses of <code>CellConstraints</code>
1285: * to do special micro-layout corrections. For example, such a subclass
1286: * could map JComponent classes to visual layout bounds that may
1287: * lead to a slightly different bounds.
1288: *
1289: * @param x an int array of the horizontal origins
1290: * @param y an int array of the vertical origins
1291: */
1292: private void layoutComponents(int[] x, int[] y) {
1293: Rectangle cellBounds = new Rectangle();
1294: for (Iterator i = constraintMap.entrySet().iterator(); i
1295: .hasNext();) {
1296: Map.Entry entry = (Map.Entry) i.next();
1297: Component component = (Component) entry.getKey();
1298: CellConstraints constraints = (CellConstraints) entry
1299: .getValue();
1300:
1301: int gridX = constraints.gridX - 1;
1302: int gridY = constraints.gridY - 1;
1303: int gridWidth = constraints.gridWidth;
1304: int gridHeight = constraints.gridHeight;
1305: cellBounds.x = x[gridX];
1306: cellBounds.y = y[gridY];
1307: cellBounds.width = x[gridX + gridWidth] - cellBounds.x;
1308: cellBounds.height = y[gridY + gridHeight] - cellBounds.y;
1309:
1310: constraints.setBounds(component, this , cellBounds,
1311: minimumWidthMeasure, minimumHeightMeasure,
1312: preferredWidthMeasure, preferredHeightMeasure);
1313: }
1314: }
1315:
1316: /**
1317: * Invalidates the component size caches.
1318: */
1319: private void invalidateCaches() {
1320: componentSizeCache.invalidate();
1321: }
1322:
1323: /**
1324: * Computes and returns the sizes for the given form specs, component
1325: * lists and measures fot minimum, preferred, and default size.
1326: *
1327: * @param container the layout container
1328: * @param formSpecs the column or row specs, resp.
1329: * @param componentLists the components list for each col/row
1330: * @param minMeasure the measure used to determin min sizes
1331: * @param prefMeasure the measure used to determin pre sizes
1332: * @param defaultMeasure the measure used to determin default sizes
1333: * @return the column or row sizes
1334: */
1335: private int[] maximumSizes(Container container, List formSpecs,
1336: List[] componentLists, Measure minMeasure,
1337: Measure prefMeasure, Measure defaultMeasure) {
1338: FormSpec formSpec;
1339: int size = formSpecs.size();
1340: int[] result = new int[size];
1341: for (int i = 0; i < size; i++) {
1342: formSpec = (FormSpec) formSpecs.get(i);
1343: result[i] = formSpec.maximumSize(container,
1344: componentLists[i], minMeasure, prefMeasure,
1345: defaultMeasure);
1346: }
1347: return result;
1348: }
1349:
1350: /**
1351: * Computes and returns the compressed sizes. Compresses space for columns
1352: * and rows iff the available space is less than the total preferred size
1353: * but more than the total minimum size.<p>
1354: *
1355: * Only columns and rows that are specified to be compressable will be
1356: * affected. You can specify a column and row as compressable by
1357: * giving it the component size <tt>default</tt>.
1358: *
1359: * @param formSpecs the column or row specs to use
1360: * @param totalSize the total available size
1361: * @param totalMinSize the sum of all minimum sizes
1362: * @param totalPrefSize the sum of all preferred sizes
1363: * @param minSizes an int array of column/row minimum sizes
1364: * @param prefSizes an int array of column/row preferred sizes
1365: * @return an int array of compressed column/row sizes
1366: */
1367: private int[] compressedSizes(List formSpecs, int totalSize,
1368: int totalMinSize, int totalPrefSize, int[] minSizes,
1369: int[] prefSizes) {
1370:
1371: // If we have less space than the total min size, answer the min sizes.
1372: if (totalSize < totalMinSize)
1373: return minSizes;
1374: // If we have more space than the total pref size, answer the pref sizes.
1375: if (totalSize >= totalPrefSize)
1376: return prefSizes;
1377:
1378: int count = formSpecs.size();
1379: int[] sizes = new int[count];
1380:
1381: double totalCompressionSpace = totalPrefSize - totalSize;
1382: double maxCompressionSpace = totalPrefSize - totalMinSize;
1383: double compressionFactor = totalCompressionSpace
1384: / maxCompressionSpace;
1385:
1386: // System.out.println("Total compression space=" + totalCompressionSpace);
1387: // System.out.println("Max compression space =" + maxCompressionSpace);
1388: // System.out.println("Compression factor =" + compressionFactor);
1389:
1390: for (int i = 0; i < count; i++) {
1391: FormSpec formSpec = (FormSpec) formSpecs.get(i);
1392: sizes[i] = prefSizes[i];
1393: if (formSpec.getSize().compressible()) {
1394: sizes[i] -= (int) Math
1395: .round((prefSizes[i] - minSizes[i])
1396: * compressionFactor);
1397: }
1398: }
1399: return sizes;
1400: }
1401:
1402: /**
1403: * Computes and returns the grouped sizes.
1404: * Gives grouped columns and rows the same size.
1405: *
1406: * @param groups the group specification
1407: * @param rawSizes the raw sizes before the grouping
1408: * @return the grouped sizes
1409: */
1410: private int[] groupedSizes(int[][] groups, int[] rawSizes) {
1411: // Return the compressed sizes if there are no groups.
1412: if (groups == null || groups.length == 0) {
1413: return rawSizes;
1414: }
1415:
1416: // Initialize the result with the given compressed sizes.
1417: int[] sizes = new int[rawSizes.length];
1418: for (int i = 0; i < sizes.length; i++) {
1419: sizes[i] = rawSizes[i];
1420: }
1421:
1422: // For each group equalize the sizes.
1423: for (int group = 0; group < groups.length; group++) {
1424: int[] groupIndices = groups[group];
1425: int groupMaxSize = 0;
1426: // Compute the group's maximum size.
1427: for (int i = 0; i < groupIndices.length; i++) {
1428: int index = groupIndices[i] - 1;
1429: groupMaxSize = Math.max(groupMaxSize, sizes[index]);
1430: }
1431: // Set all sizes of this group to the group's maximum size.
1432: for (int i = 0; i < groupIndices.length; i++) {
1433: int index = groupIndices[i] - 1;
1434: sizes[index] = groupMaxSize;
1435: }
1436: }
1437: return sizes;
1438: }
1439:
1440: /**
1441: * Distributes free space over columns and rows and
1442: * returns the sizes after this distribution process.
1443: *
1444: * @param formSpecs the column/row specifications to work with
1445: * @param totalSize the total available size
1446: * @param totalPrefSize the sum of all preferred sizes
1447: * @param inputSizes the input sizes
1448: * @return the distributed sizes
1449: */
1450: private int[] distributedSizes(List formSpecs, int totalSize,
1451: int totalPrefSize, int[] inputSizes) {
1452: double totalFreeSpace = totalSize - totalPrefSize;
1453: // Do nothing if there's no free space.
1454: if (totalFreeSpace < 0)
1455: return inputSizes;
1456:
1457: // Compute the total weight.
1458: int count = formSpecs.size();
1459: double totalWeight = 0.0;
1460: for (int i = 0; i < count; i++) {
1461: FormSpec formSpec = (FormSpec) formSpecs.get(i);
1462: totalWeight += formSpec.getResizeWeight();
1463: }
1464:
1465: // Do nothing if there's no resizing column.
1466: if (totalWeight == 0.0)
1467: return inputSizes;
1468:
1469: int[] sizes = new int[count];
1470:
1471: double restSpace = totalFreeSpace;
1472: int roundedRestSpace = (int) totalFreeSpace;
1473: for (int i = 0; i < count; i++) {
1474: FormSpec formSpec = (FormSpec) formSpecs.get(i);
1475: double weight = formSpec.getResizeWeight();
1476: if (weight == FormSpec.NO_GROW) {
1477: sizes[i] = inputSizes[i];
1478: } else {
1479: double roundingCorrection = restSpace
1480: - roundedRestSpace;
1481: double extraSpace = totalFreeSpace * weight
1482: / totalWeight;
1483: double correctedExtraSpace = extraSpace
1484: - roundingCorrection;
1485: int roundedExtraSpace = (int) Math
1486: .round(correctedExtraSpace);
1487: sizes[i] = inputSizes[i] + roundedExtraSpace;
1488: restSpace -= extraSpace;
1489: roundedRestSpace -= roundedExtraSpace;
1490: }
1491: }
1492: return sizes;
1493: }
1494:
1495: /**
1496: * Computes and returns the sum of integers in the given array of ints.
1497: *
1498: * @param sizes an array of ints to sum up
1499: * @return the sum of ints in the array
1500: */
1501: private int sum(int[] sizes) {
1502: int sum = 0;
1503: for (int i = sizes.length - 1; i >= 0; i--) {
1504: sum += sizes[i];
1505: }
1506: return sum;
1507: }
1508:
1509: /**
1510: * Computes and returns a table that maps a column/row index
1511: * to the maximum number of columns/rows that a component can span
1512: * without spanning a growing column.<p>
1513: *
1514: * Iterates over the specs from right to left/bottom to top,
1515: * sets the table value to zero if a spec can grow,
1516: * otherwise increases the span by one.<p>
1517: *
1518: * <strong>Examples:</strong><pre>
1519: * "pref, 4dlu, pref, 2dlu, p:grow, 2dlu, pref" ->
1520: * [4, 3, 2, 1, 0, MAX_VALUE, MAX_VALUE]
1521: *
1522: * "p:grow, 4dlu, p:grow, 9dlu, pref" ->
1523: * [0, 1, 0, MAX_VALUE, MAX_VALUE]
1524: *
1525: * "p, 4dlu, p, 2dlu, 0:grow" ->
1526: * [4, 3, 2, 1, 0]
1527: * </pre>
1528: *
1529: * @param formSpecs the column specs or row specs
1530: * @return a table that maps a spec index to the maximum span for
1531: * fixed size specs
1532: */
1533: private int[] computeMaximumFixedSpanTable(List formSpecs) {
1534: int size = formSpecs.size();
1535: int[] table = new int[size];
1536: int maximumFixedSpan = Integer.MAX_VALUE; // Could be 1
1537: for (int i = size - 1; i >= 0; i--) {
1538: FormSpec spec = (FormSpec) formSpecs.get(i); // ArrayList access
1539: if (spec.canGrow()) {
1540: maximumFixedSpan = 0;
1541: }
1542: table[i] = maximumFixedSpan;
1543: if (maximumFixedSpan < Integer.MAX_VALUE)
1544: maximumFixedSpan++;
1545: }
1546: return table;
1547: }
1548:
1549: // Measuring Component Sizes ********************************************
1550:
1551: /**
1552: * An interface that describes how to measure a <code>Component</code>.
1553: * Used to abstract from horizontal and vertical dimensions as well as
1554: * minimum and preferred sizes.
1555: *
1556: * @since 1.1
1557: */
1558: public static interface Measure {
1559:
1560: /**
1561: * Computes and returns the size of the given <code>Component</code>.
1562: *
1563: * @param component the component to measure
1564: * @return the component's size
1565: */
1566: int sizeOf(Component component);
1567: }
1568:
1569: /**
1570: * An abstract implementation of the <code>Measure</code> interface
1571: * that caches component sizes.
1572: */
1573: private abstract static class CachingMeasure implements Measure,
1574: Serializable {
1575:
1576: /**
1577: * Holds previously requested component sizes.
1578: * Used to minimize size requests to subcomponents.
1579: */
1580: protected final ComponentSizeCache cache;
1581:
1582: private CachingMeasure(ComponentSizeCache cache) {
1583: this .cache = cache;
1584: }
1585:
1586: }
1587:
1588: /**
1589: * Measures a component by computing its minimum width.
1590: */
1591: private static final class MinimumWidthMeasure extends
1592: CachingMeasure {
1593: private MinimumWidthMeasure(ComponentSizeCache cache) {
1594: super (cache);
1595: }
1596:
1597: public int sizeOf(Component c) {
1598: return cache.getMinimumSize(c).width;
1599: }
1600: }
1601:
1602: /**
1603: * Measures a component by computing its minimum height.
1604: */
1605: private static final class MinimumHeightMeasure extends
1606: CachingMeasure {
1607: private MinimumHeightMeasure(ComponentSizeCache cache) {
1608: super (cache);
1609: }
1610:
1611: public int sizeOf(Component c) {
1612: return cache.getMinimumSize(c).height;
1613: }
1614: }
1615:
1616: /**
1617: * Measures a component by computing its preferred width.
1618: */
1619: private static final class PreferredWidthMeasure extends
1620: CachingMeasure {
1621: private PreferredWidthMeasure(ComponentSizeCache cache) {
1622: super (cache);
1623: }
1624:
1625: public int sizeOf(Component c) {
1626: return cache.getPreferredSize(c).width;
1627: }
1628: }
1629:
1630: /**
1631: * Measures a component by computing its preferred height.
1632: */
1633: private static final class PreferredHeightMeasure extends
1634: CachingMeasure {
1635: private PreferredHeightMeasure(ComponentSizeCache cache) {
1636: super (cache);
1637: }
1638:
1639: public int sizeOf(Component c) {
1640: return cache.getPreferredSize(c).height;
1641: }
1642: }
1643:
1644: // Caching Component Sizes **********************************************
1645:
1646: /**
1647: * A cache for component minimum and preferred sizes.
1648: * Used to reduce the requests to determine a component's size.
1649: */
1650: private static final class ComponentSizeCache implements
1651: Serializable {
1652:
1653: /** Maps components to their minimum sizes. */
1654: private final Map minimumSizes;
1655:
1656: /** Maps components to their preferred sizes. */
1657: private final Map preferredSizes;
1658:
1659: /**
1660: * Constructs a <code>ComponentSizeCache</code>.
1661: *
1662: * @param initialCapacity the initial cache capacity
1663: */
1664: private ComponentSizeCache(int initialCapacity) {
1665: minimumSizes = new HashMap(initialCapacity);
1666: preferredSizes = new HashMap(initialCapacity);
1667: }
1668:
1669: /**
1670: * Invalidates the cache. Clears all stored size information.
1671: */
1672: void invalidate() {
1673: minimumSizes.clear();
1674: preferredSizes.clear();
1675: }
1676:
1677: /**
1678: * Returns the minimum size for the given component. Tries to look up
1679: * the value from the cache; lazily creates the value if it has not
1680: * been requested before.
1681: *
1682: * @param component the component to compute the minimum size
1683: * @return the component's minimum size
1684: */
1685: Dimension getMinimumSize(Component component) {
1686: Dimension size = (Dimension) minimumSizes.get(component);
1687: if (size == null) {
1688: size = component.getMinimumSize();
1689: minimumSizes.put(component, size);
1690: }
1691: return size;
1692: }
1693:
1694: /**
1695: * Returns the preferred size for the given component. Tries to look
1696: * up the value from the cache; lazily creates the value if it has not
1697: * been requested before.
1698: *
1699: * @param component the component to compute the preferred size
1700: * @return the component's preferred size
1701: */
1702: Dimension getPreferredSize(Component component) {
1703: Dimension size = (Dimension) preferredSizes.get(component);
1704: if (size == null) {
1705: size = component.getPreferredSize();
1706: preferredSizes.put(component, size);
1707: }
1708: return size;
1709: }
1710:
1711: void removeEntry(Component component) {
1712: minimumSizes.remove(component);
1713: preferredSizes.remove(component);
1714: }
1715: }
1716:
1717: // Exposing the Layout Information **************************************
1718:
1719: /**
1720: * Computes and returns the horizontal and vertical grid origins.
1721: * Performs the same layout process as <code>#layoutContainer</code>
1722: * but does not layout the components.<p>
1723: *
1724: * This method has been added only to make it easier to debug
1725: * the form layout. <strong>You must not call this method directly;
1726: * It may be removed in a future release or the visibility
1727: * may be reduced.</strong>
1728: *
1729: * @param parent the <code>Container</code> to inspect
1730: * @return an object that comprises the grid x and y origins
1731: */
1732: public LayoutInfo getLayoutInfo(Container parent) {
1733: synchronized (parent.getTreeLock()) {
1734: initializeColAndRowComponentLists();
1735: Dimension size = parent.getSize();
1736:
1737: Insets insets = parent.getInsets();
1738: int totalWidth = size.width - insets.left - insets.right;
1739: int totalHeight = size.height - insets.top - insets.bottom;
1740:
1741: int[] x = computeGridOrigins(parent, totalWidth,
1742: insets.left, colSpecs, colComponents,
1743: colGroupIndices, minimumWidthMeasure,
1744: preferredWidthMeasure);
1745: int[] y = computeGridOrigins(parent, totalHeight,
1746: insets.top, rowSpecs, rowComponents,
1747: rowGroupIndices, minimumHeightMeasure,
1748: preferredHeightMeasure);
1749: return new LayoutInfo(x, y);
1750: }
1751: }
1752:
1753: /**
1754: * Stores column and row origins.
1755: */
1756: public static final class LayoutInfo {
1757:
1758: /**
1759: * Holds the origins of the columns.
1760: */
1761: public final int[] columnOrigins;
1762:
1763: /**
1764: * Holds the origins of the rows.
1765: */
1766: public final int[] rowOrigins;
1767:
1768: private LayoutInfo(int[] xOrigins, int[] yOrigins) {
1769: this .columnOrigins = xOrigins;
1770: this .rowOrigins = yOrigins;
1771: }
1772:
1773: /**
1774: * Returns the layout's horizontal origin, the origin of the first column.
1775: *
1776: * @return the layout's horizontal origin, the origin of the first column.
1777: */
1778: public int getX() {
1779: return columnOrigins[0];
1780: }
1781:
1782: /**
1783: * Returns the layout's vertical origin, the origin of the first row.
1784: *
1785: * @return the layout's vertical origin, the origin of the first row.
1786: */
1787: public int getY() {
1788: return rowOrigins[0];
1789: }
1790:
1791: /**
1792: * Returns the layout's width, the size between the first and the last
1793: * column origin.
1794: *
1795: * @return the layout's width.
1796: */
1797: public int getWidth() {
1798: return columnOrigins[columnOrigins.length - 1]
1799: - columnOrigins[0];
1800: }
1801:
1802: /**
1803: * Returns the layout's height, the size between the first and last row.
1804: *
1805: * @return the layout's height.
1806: */
1807: public int getHeight() {
1808: return rowOrigins[rowOrigins.length - 1] - rowOrigins[0];
1809: }
1810:
1811: }
1812:
1813: // Helper Code **********************************************************
1814:
1815: /**
1816: * Creates and returns a deep copy of the given array.
1817: * Unlike <code>#clone</code> that performs a shallow copy,
1818: * this method copies both array levels.
1819: *
1820: * @param array the array to clone
1821: * @return a deep copy of the given array
1822: *
1823: * @see Object#clone()
1824: */
1825: private int[][] deepClone(int[][] array) {
1826: int[][] result = new int[array.length][];
1827: for (int i = 0; i < result.length; i++) {
1828: result[i] = (int[]) array[i].clone();
1829: }
1830: return result;
1831: }
1832:
1833: // Serialization ********************************************************
1834:
1835: /**
1836: * In addition to the default serialization mechanism this class
1837: * invalidates the component size cache. The cache will be populated
1838: * again after the deserialization.
1839: * Also, the fields <code>colComponents</code> and
1840: * <code>rowComponents</code> have been marked as transient
1841: * to exclude them from the serialization.
1842: */
1843: private void writeObject(ObjectOutputStream out) throws IOException {
1844: invalidateCaches();
1845: out.defaultWriteObject();
1846: }
1847:
1848: // Debug Helper Code ****************************************************
1849:
1850: /*
1851: // Prints the given column widths and row heights.
1852: private void printSizes(String title, int[] colWidths, int[] rowHeights) {
1853: System.out.println();
1854: System.out.println(title);
1855: int totalWidth = 0;
1856: System.out.print("Column widths: ");
1857: for (int i=0; i < getColumnCount(); i++) {
1858: int width = colWidths[i];
1859: totalWidth += width;
1860: System.out.print(width + ", ");
1861: }
1862: System.out.println(" Total=" + totalWidth);
1863:
1864: int totalHeight = 0;
1865: System.out.print("Row heights: ");
1866: for (int i=0; i < getRowCount(); i++) {
1867: int height = rowHeights[i];
1868: totalHeight += height;
1869: System.out.print(height + ", ");
1870: }
1871: System.out.println(" Total=" + totalHeight);
1872: System.out.println();
1873: }
1874:
1875: */
1876:
1877: }
|