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