/* * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Karsten Lentzsch nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.forms.layout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager2; import java.awt.Rectangle; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * FormLayout is a powerful, flexible and precise general purpose * layout manager. It aligns components vertically and horizontally in * a dynamic rectangular grid of cells, with each component occupying one or * more cells. * A <a href="../../../../../whitepaper.pdf" target="secondary">whitepaper</a> * about the FormLayout ships with the product documentation and is available * <a href="http://www.jgoodies.com/articles/forms.pdf">online</a>.<p> * * To use FormLayout you first define the grid by specifying the * columns and rows. In a second step you add components to the grid. You can * specify columns and rows via human-readable String descriptions or via * arrays of {@link ColumnSpec} and {@link RowSpec} instances.<p> * * Each component managed by a FormLayout is associated with an instance of * {@link CellConstraints}. The constraints object specifies where a component * should be located on the form's grid and how the component should be * positioned. In addition to its constraints object the * <code>FormLayout</code> also considers each component's minimum and * preferred sizes in order to determine a component's size.<p> * * FormLayout has been designed to work with non-visual builders that help you * specify the layout and fill the grid. For example, the * {@link com.jgoodies.forms.builder.ButtonBarBuilder} assists you in building button * bars; it creates a standardized FormLayout and provides a minimal API that * specializes in adding buttons. Other builders can create frequently used * panel design, for example a form that consists of rows of label-component * pairs.<p> * * FormLayout has been prepared to work with different types of sizes as * defined by the {@link Size} interface.<p> * * <strong>Example 1</strong> (Plain FormLayout):<br> * The following example creates a panel with 3 data columns and 3 data rows; * the columns and rows are specified before components are added * to the form. * <pre> * FormLayout layout = new FormLayout( * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns * "pref, 3dlu, pref, 3dlu, pref"); // rows * * CellConstraints cc = new CellConstraints(); * JPanel panel = new JPanel(layout); * panel.add(new JLabel("Label1"), cc.xy (1, 1)); * panel.add(new JTextField(), cc.xywh(3, 1, 3, 1)); * panel.add(new JLabel("Label2"), cc.xy (1, 3)); * panel.add(new JTextField(), cc.xy (3, 3)); * panel.add(new JLabel("Label3"), cc.xy (1, 5)); * panel.add(new JTextField(), cc.xy (3, 5)); * panel.add(new JButton("/u2026"), cc.xy (5, 5)); * return panel; * </pre><p> * * <strong>Example 2</strong> (Using PanelBuilder):<br> * This example creates the same panel as above using the * {@link com.jgoodies.forms.builder.PanelBuilder} to add components to the form. * <pre> * FormLayout layout = new FormLayout( * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns * "pref, 3dlu, pref, 3dlu, pref"); // rows * * PanelBuilder builder = new PanelBuilder(layout); * CellConstraints cc = new CellConstraints(); * builder.addLabel("Label1", cc.xy (1, 1)); * builder.add(new JTextField(), cc.xywh(3, 1, 3, 1)); * builder.addLabel("Label2", cc.xy (1, 3)); * builder.add(new JTextField(), cc.xy (3, 3)); * builder.addLabel("Label3", cc.xy (1, 5)); * builder.add(new JTextField(), cc.xy (3, 5)); * builder.add(new JButton("/u2026"), cc.xy (5, 5)); * return builder.getPanel(); * </pre><p> * * <strong>Example 3</strong> (Using DefaultFormBuilder):<br> * This example utilizes the * {@link com.jgoodies.forms.builder.DefaultFormBuilder} that * ships with the source distribution. * <pre> * FormLayout layout = new FormLayout( * "right:pref, 6dlu, 50dlu, 4dlu, default"); // 5 columns; add rows later * * DefaultFormBuilder builder = new DefaultFormBuilder(layout); * builder.append("Label1", new JTextField(), 3); * builder.append("Label2", new JTextField()); * builder.append("Label3", new JTextField()); * builder.append(new JButton("/u2026")); * return builder.getPanel(); * </pre><p> * * TODO: In the Forms 1.0.x invisible components are not taken into account * when the FormLayout lays out the container. Add an optional setting for * this on both the container-level and component-level. So one can specify * that invisible components shall be taken into account, but may exclude * individual components. Or the other way round, exclude invisible components, * and include individual components. The API of both the FormLayout and * CellConstraints classes shall be extended to support this option. * This feature is planned for the Forms version 1.1 and is described in * <a href="https://forms.dev.java.net/issues/show_bug.cgi?id=28">issue #28</a> * of the Forms' issue tracker where you can track the progress. * * @author Karsten Lentzsch * @version $Revision$ * * @see ColumnSpec * @see RowSpec * @see CellConstraints * @see com.jgoodies.forms.builder.AbstractFormBuilder * @see com.jgoodies.forms.builder.ButtonBarBuilder * @see com.jgoodies.forms.builder.DefaultFormBuilder * @see com.jgoodies.forms.factories.FormFactory * @see Size * @see Sizes */ public final class FormLayout implements LayoutManager2, Serializable { /** * Holds the column specifications. * * @see ColumnSpec * @see #getColumnCount() * @see #getColumnSpec(int) * @see #appendColumn(ColumnSpec) * @see #insertColumn(int, ColumnSpec) * @see #removeColumn(int) */ private final List colSpecs; /** * Holds the row specifications. * * @see RowSpec * @see #getRowCount() * @see #getRowSpec(int) * @see #appendRow(RowSpec) * @see #insertRow(int, RowSpec) * @see #removeRow(int) */ private final List rowSpecs; /** * Holds the column groups as an array of arrays of column indices. * * @see #getColumnGroups() * @see #setColumnGroups(int[][]) * @see #addGroupedColumn(int) */ private int[][] colGroupIndices; /** * Holds the row groups as an array of arrays of row indices. * * @see #getRowGroups() * @see #setRowGroups(int[][]) * @see #addGroupedRow(int) */ private int[][] rowGroupIndices; /** * Maps components to their associated <code>CellConstraints</code>. * * @see CellConstraints * @see #getConstraints(Component) * @see #setConstraints(Component, CellConstraints) */ private final Map constraintMap; // Fields used by the Layout Algorithm ********************************** /** * Holds the components that occupy exactly one column. * For each column we keep a list of these components. */ private transient List[] colComponents; /** * Holds the components that occupy exactly one row. * For each row we keep a list of these components. */ private transient List[] rowComponents; /** * Caches component minimum and preferred sizes. * All requests for component sizes shall be directed to the cache. */ private final ComponentSizeCache componentSizeCache; /** * These functional objects are used to measure component sizes. * They abstract from horizontal and vertical orientation and so, * allow to implement the layout algorithm for both orientations with a * single set of methods. */ private final Measure minimumWidthMeasure; private final Measure minimumHeightMeasure; private final Measure preferredWidthMeasure; private final Measure preferredHeightMeasure; // Instance Creation **************************************************** /** * Constructs an empty FormLayout. Columns and rows must be added * before components can be added to the layout container.<p> * * This constructor is intended to be used in environments * that add columns and rows dynamically. */ public FormLayout() { this(new ColumnSpec[0], new RowSpec[0]); } /** * Constructs a FormLayout using the given encoded column specifications. * The constructed layout has no rows; these must be added * before components can be added to the layout container.<p> * * This constructor is primarily intended to be used with builder classes * that add rows dynamically, such as the <code>DefaultFormBuilder</code>.<p> * * <strong>Examples:</strong><pre> * // Label, gap, component * FormLayout layout = new FormLayout( * "pref, 4dlu, pref"); * * // Right-aligned label, gap, component, gap, component * FormLayout layout = new FormLayout( * "right:pref, 4dlu, 50dlu, 4dlu, 50dlu"); * * // Left-aligned labels, gap, components, gap, components * FormLayout layout = new FormLayout( * "left:pref, 4dlu, pref, 4dlu, pref"); * </pre> See the class comment for more examples. * * @param encodedColumnSpecs comma separated encoded column specifications * @exception NullPointerException if encodedColumnSpecs is <code>null</code> */ public FormLayout(String encodedColumnSpecs) { this(ColumnSpec.decodeSpecs(encodedColumnSpecs), new RowSpec[0]); } /** * Constructs a FormLayout using the given * encoded column and row specifications.<p> * * This constructor is recommended for most hand-coded layouts.<p> * * <strong>Examples:</strong><pre> * FormLayout layout = new FormLayout( * "pref, 4dlu, pref", // columns * "p, 3dlu, p"); // rows * * FormLayout layout = new FormLayout( * "right:pref, 4dlu, pref", // columns * "p, 3dlu, p, 3dlu, fill:p:grow"); // rows * * FormLayout layout = new FormLayout( * "left:pref, 4dlu, 50dlu", // columns * "p, 2px, p, 3dlu, p, 9dlu, p"); // rows * * FormLayout layout = new FormLayout( * "max(75dlu;pref), 4dlu, default", // columns * "p, 3dlu, p, 3dlu, p, 3dlu, p"); // rows * </pre> See the class comment for more examples. * * @param encodedColumnSpecs comma separated encoded column specifications * @param encodedRowSpecs comma separated encoded row specifications * @exception NullPointerException if encodedColumnSpecs or encodedRowSpecs * is <code>null</code> */ public FormLayout(String encodedColumnSpecs, String encodedRowSpecs) { this(ColumnSpec.decodeSpecs(encodedColumnSpecs), RowSpec .decodeSpecs(encodedRowSpecs)); } /** * Constructs a FormLayout using the given column and row specifications. * * @param colSpecs an array of column specifications. * @param rowSpecs an array of row specifications. * @exception NullPointerException if colSpecs or rowSpecs is null */ public FormLayout(ColumnSpec[] colSpecs, RowSpec[] rowSpecs) { if (colSpecs == null) { throw new NullPointerException( "The column specifications must not be null."); } if (rowSpecs == null) { throw new NullPointerException( "The row specifications must not be null."); } this.colSpecs = new ArrayList(Arrays.asList(colSpecs)); this.rowSpecs = new ArrayList(Arrays.asList(rowSpecs)); colGroupIndices = new int[][] {}; rowGroupIndices = new int[][] {}; int initialCapacity = colSpecs.length * rowSpecs.length / 4; constraintMap = new HashMap(initialCapacity); componentSizeCache = new ComponentSizeCache(initialCapacity); minimumWidthMeasure = new MinimumWidthMeasure(componentSizeCache); minimumHeightMeasure = new MinimumHeightMeasure(componentSizeCache); preferredWidthMeasure = new PreferredWidthMeasure(componentSizeCache); preferredHeightMeasure = new PreferredHeightMeasure(componentSizeCache); } // Accessing the Column and Row Specifications ************************** /** * Returns the number of columns in this layout. * * @return the number of columns */ public int getColumnCount() { return colSpecs.size(); } /** * Returns the number of rows in this layout. * * @return the number of rows */ public int getRowCount() { return rowSpecs.size(); } /** * Returns the <code>ColumnSpec</code> at the specified column index. * * @param columnIndex the column index of the requested <code>ColumnSpec</code> * @return the <code>ColumnSpec</code> at the specified column * @exception IndexOutOfBoundsException if the column index is out of range */ public ColumnSpec getColumnSpec(int columnIndex) { return (ColumnSpec) colSpecs.get(columnIndex - 1); } /** * Sets the <code>ColumnSpec</code> at the specified column index. * * @param columnIndex the index of the column to be changed * @param columnSpec the <code>ColumnSpec</code> to be set * @exception NullPointerException if the column specification is null * @exception IndexOutOfBoundsException if the column index is out of range */ public void setColumnSpec(int columnIndex, ColumnSpec columnSpec) { if (columnSpec == null) { throw new NullPointerException("The column spec must not be null."); } colSpecs.set(columnIndex - 1, columnSpec); } /** * Returns the <code>RowSpec</code> at the specified row index. * * @param rowIndex the row index of the requested <code>RowSpec</code> * @return the <code>RowSpec</code> at the specified row * @exception IndexOutOfBoundsException if the row index is out of range */ public RowSpec getRowSpec(int rowIndex) { return (RowSpec) rowSpecs.get(rowIndex - 1); } /** * Sets the <code>RowSpec</code> at the specified row index. * * @param rowIndex the index of the row to be changed * @param rowSpec the <code>RowSpec</code> to be set * @exception NullPointerException if the row specification is null * @exception IndexOutOfBoundsException if the row index is out of range */ public void setRowSpec(int rowIndex, RowSpec rowSpec) { if (rowSpec == null) { throw new NullPointerException("The row spec must not be null."); } rowSpecs.set(rowIndex - 1, rowSpec); } /** * Appends the given column specification to the right hand side of all * columns. * * @param columnSpec the column specification to be added * @exception NullPointerException if the column specification is null */ public void appendColumn(ColumnSpec columnSpec) { if (columnSpec == null) { throw new NullPointerException("The column spec must not be null."); } colSpecs.add(columnSpec); } /** * Inserts the specified column at the specified position. Shifts components * that intersect the new column to the right hand side and readjusts * column groups.<p> * * The component shift works as follows: components that were located on * the right hand side of the inserted column are shifted one column to * the right; component column span is increased by one if it intersects * the new column.<p> * * Column group indices that are greater or equal than the given column * index will be increased by one. * * @param columnIndex index of the column to be inserted * @param columnSpec specification of the column to be inserted * @exception IndexOutOfBoundsException if the column index is out of range */ public void insertColumn(int columnIndex, ColumnSpec columnSpec) { if (columnIndex < 1 || columnIndex > getColumnCount()) { throw new IndexOutOfBoundsException("The column index " + columnIndex + "must be in the range [1, " + getColumnCount() + "]."); } colSpecs.add(columnIndex - 1, columnSpec); shiftComponentsHorizontally(columnIndex, false); adjustGroupIndices(colGroupIndices, columnIndex, false); } /** * Removes the column with the given column index from the layout. * Components will be rearranged and column groups will be readjusted. * Therefore, the column must not contain components and must not be part * of a column group.<p> * * The component shift works as follows: components that were located on * the right hand side of the removed column are moved one column to the * left; component column span is decreased by one if it intersects the * removed column.<p> * * Column group indices that are greater than the column index will be * decreased by one.<p> * * <strong>Note:</strong> If one of the constraints mentioned above * is violated, this layout's state becomes illegal and it is unsafe * to work with this layout. * A typical layout implementation can ensure that these constraints are * not violated. However, in some cases you may need to check these * conditions before you invoke this method. The Forms extras contain * source code for class <code>FormLayoutUtils</code> that provides * the required test methods:<br> * <code>#columnContainsComponents(Container, int)</code> and<br> * <code>#isGroupedColumn(FormLayout, int)</code>. * * @param columnIndex index of the column to remove * @exception IndexOutOfBoundsException if the column index is out of range * @exception IllegalStateException if the column contains components * or if the column is already grouped * */ public void removeColumn(int columnIndex) { if (columnIndex < 1 || columnIndex > getColumnCount()) { throw new IndexOutOfBoundsException("The column index " + columnIndex + " must be in the range [1, " + getColumnCount() + "]."); } colSpecs.remove(columnIndex - 1); shiftComponentsHorizontally(columnIndex, true); adjustGroupIndices(colGroupIndices, columnIndex, true); } /** * Appends the given row specification to the bottom of all rows. * * @param rowSpec the row specification to be added to the form layout * @exception NullPointerException if the rowSpec is null */ public void appendRow(RowSpec rowSpec) { if (rowSpec == null) { throw new NullPointerException("The row spec must not be null."); } rowSpecs.add(rowSpec); } /** * Inserts the specified column at the specified position. Shifts * components that intersect the new column to the right and readjusts * column groups.<p> * * The component shift works as follows: components that were located on * the right hand side of the inserted column are shifted one column to * the right; component column span is increased by one if it intersects * the new column.<p> * * Column group indices that are greater or equal than the given column * index will be increased by one. * * @param rowIndex index of the row to be inserted * @param rowSpec specification of the row to be inserted * @exception IndexOutOfBoundsException if the row index is out of range */ public void insertRow(int rowIndex, RowSpec rowSpec) { if (rowIndex < 1 || rowIndex > getRowCount()) { throw new IndexOutOfBoundsException("The row index " + rowIndex + " must be in the range [1, " + getRowCount() + "]."); } rowSpecs.add(rowIndex - 1, rowSpec); shiftComponentsVertically(rowIndex, false); adjustGroupIndices(rowGroupIndices, rowIndex, false); } /** * Removes the row with the given row index from the layout. Components * will be rearranged and row groups will be readjusted. Therefore, the * row must not contain components and must not be part of a row group.<p> * * The component shift works as follows: components that were located * below the removed row are moved up one row; component row span is * decreased by one if it intersects the removed row.<p> * * Row group indices that are greater than the row index will be decreased * by one.<p> * * <strong>Note:</strong> If one of the constraints mentioned above * is violated, this layout's state becomes illegal and it is unsafe * to work with this layout. * A typical layout implementation can ensure that these constraints are * not violated. However, in some cases you may need to check these * conditions before you invoke this method. The Forms extras contain * source code for class <code>FormLayoutUtils</code> that provides * the required test methods:<br> * <code>#rowContainsComponents(Container, int)</code> and<br> * <code>#isGroupedRow(FormLayout, int)</code>. * * @param rowIndex index of the row to remove * @exception IndexOutOfBoundsException if the row index is out of range * @exception IllegalStateException if the row contains components * or if the row is already grouped * */ public void removeRow(int rowIndex) { if (rowIndex < 1 || rowIndex > getRowCount()) { throw new IndexOutOfBoundsException("The row index " + rowIndex + "must be in the range [1, " + getRowCount() + "]."); } rowSpecs.remove(rowIndex - 1); shiftComponentsVertically(rowIndex, true); adjustGroupIndices(rowGroupIndices, rowIndex, true); } /** * Shifts components horizontally, either to the right if a column has been * inserted or to the left if a column has been removed. * * @param columnIndex index of the column to remove * @param remove true for remove, false for insert * @exception IllegalStateException if a removed column contains components */ private void shiftComponentsHorizontally(int columnIndex, boolean remove) { final int offset = remove ? -1 : 1; for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); CellConstraints constraints = (CellConstraints) entry.getValue(); int x1 = constraints.gridX; int w = constraints.gridWidth; int x2 = x1 + w - 1; if (x1 == columnIndex && remove) { throw new IllegalStateException("The removed column " + columnIndex + " must not contain component origins.\n" + "Illegal component=" + entry.getKey()); } else if (x1 >= columnIndex) { constraints.gridX += offset; } else if (x2 >= columnIndex) { constraints.gridWidth += offset; } } } /** * Shifts components vertically, either to the bottom if a row has been * inserted or to the top if a row has been removed. * * @param rowIndex index of the row to remove * @param remove true for remove, false for insert * @exception IllegalStateException if a removed column contains components */ private void shiftComponentsVertically(int rowIndex, boolean remove) { final int offset = remove ? -1 : 1; for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); CellConstraints constraints = (CellConstraints) entry.getValue(); int y1 = constraints.gridY; int h = constraints.gridHeight; int y2 = y1 + h - 1; if (y1 == rowIndex && remove) { throw new IllegalStateException("The removed row " + rowIndex + " must not contain component origins.\n" + "Illegal component=" + entry.getKey()); } else if (y1 >= rowIndex) { constraints.gridY += offset; } else if (y2 >= rowIndex) { constraints.gridHeight += offset; } } } /** * Adjusts group indices. Shifts the given groups to left, right, up, * down according to the specified remove or add flag. * * @param allGroupIndices the groups to be adjusted * @param modifiedIndex the modified column or row index * @param remove true for remove, false for add * @exception IllegalStateException if we remove and the index is grouped */ private void adjustGroupIndices(int[][] allGroupIndices, int modifiedIndex, boolean remove) { final int offset = remove ? -1 : +1; for (int group = 0; group < allGroupIndices.length; group++) { int[] groupIndices = allGroupIndices[group]; for (int i = 0; i < groupIndices.length; i++) { int index = groupIndices[i]; if (index == modifiedIndex && remove) { throw new IllegalStateException("The removed index " + modifiedIndex + " must not be grouped."); } else if (index >= modifiedIndex) { groupIndices[i] += offset; } } } } // Accessing Constraints ************************************************ /** * Looks up and returns the constraints for the specified component. * A copy of the actual <code>CellConstraints</code> object is returned. * * @param component the component to be queried * @return the <code>CellConstraints</code> for the specified component * @exception NullPointerException if component is <code>null</code> or * has not been added to the container */ public CellConstraints getConstraints(Component component) { if (component == null) { throw new NullPointerException("The component must not be null."); } CellConstraints constraints = (CellConstraints) constraintMap .get(component); if (constraints == null) { throw new NullPointerException( "The component has not been added to the container."); } return (CellConstraints) constraints.clone(); } /** * Sets the constraints for the specified component in this layout. * * @param component the component to be modified * @param constraints the constraints to be applied * @exception NullPointerException if the component or constraints object * is <code>null</code> */ public void setConstraints(Component component, CellConstraints constraints) { if (component == null) { throw new NullPointerException("The component must not be null."); } if (constraints == null) { throw new NullPointerException("The constraints must not be null."); } constraints.ensureValidGridBounds(getColumnCount(), getRowCount()); constraintMap.put(component, constraints.clone()); } /** * Removes the constraints for the specified component in this layout. * * @param component the component to be modified */ private void removeConstraints(Component component) { constraintMap.remove(component); componentSizeCache.removeEntry(component); } // Accessing Column and Row Groups ************************************** /** * Returns a deep copy of the column groups. * * @return the column groups as two-dimensional int array */ public int[][] getColumnGroups() { return deepClone(colGroupIndices); } /** * Sets the column groups, where each column in a group gets the same * group wide width. Each group is described by an array of integers that * are interpreted as column indices. The parameter is an array of such * group descriptions.<p> * * <strong>Examples:</strong><pre> * // Group columns 1, 3 and 4. * setColumnGroups(new int[][]{ {1, 3, 4}}); * * // Group columns 1, 3, 4, and group columns 7 and 9 * setColumnGroups(new int[][]{ {1, 3, 4}, {7, 9}}); * </pre> * * @param colGroupIndices a two-dimensional array of column groups indices * @exception IndexOutOfBoundsException if an index is outside the grid * @exception IllegalArgumentException if a column index is used twice */ public void setColumnGroups(int[][] colGroupIndices) { int maxColumn = getColumnCount(); boolean[] usedIndices = new boolean[maxColumn + 1]; for (int group = 0; group < colGroupIndices.length; group++) { for (int j = 0; j < colGroupIndices[group].length; j++) { int colIndex = colGroupIndices[group][j]; if (colIndex < 1 || colIndex > maxColumn) { throw new IndexOutOfBoundsException( "Invalid column group index " + colIndex + " in group " + (group + 1)); } if (usedIndices[colIndex]) { throw new IllegalArgumentException("Column index " + colIndex + " must not be used in multiple column groups."); } usedIndices[colIndex] = true; } } this.colGroupIndices = deepClone(colGroupIndices); } /** * Adds the specified column index to the last column group. * In case there are no groups, a new group will be created. * * @param columnIndex the column index to be set grouped */ public void addGroupedColumn(int columnIndex) { int[][] newColGroups = getColumnGroups(); // Create a group if none exists. if (newColGroups.length == 0) { newColGroups = new int[][] { { columnIndex } }; } else { int lastGroupIndex = newColGroups.length - 1; int[] lastGroup = newColGroups[lastGroupIndex]; int groupSize = lastGroup.length; int[] newLastGroup = new int[groupSize + 1]; System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize); newLastGroup[groupSize] = columnIndex; newColGroups[lastGroupIndex] = newLastGroup; } setColumnGroups(newColGroups); } /** * Returns a deep copy of the row groups. * * @return the row groups as two-dimensional int array */ public int[][] getRowGroups() { return deepClone(rowGroupIndices); } /** * Sets the row groups, where each row in such a group gets the same group * wide height. Each group is described by an array of integers that are * interpreted as row indices. The parameter is an array of such group * descriptions.<p> * * <strong>Examples:</strong><pre> * // Group rows 1 and 2. * setRowGroups(new int[][]{ {1, 2}}); * * // Group rows 1 and 2, and group rows 5, 7, and 9. * setRowGroups(new int[][]{ {1, 2}, {5, 7, 9}}); * </pre> * * @param rowGroupIndices a two-dimensional array of row group indices. * @exception IndexOutOfBoundsException if an index is outside the grid */ public void setRowGroups(int[][] rowGroupIndices) { int rowCount = getRowCount(); boolean[] usedIndices = new boolean[rowCount + 1]; for (int i = 0; i < rowGroupIndices.length; i++) { for (int j = 0; j < rowGroupIndices[i].length; j++) { int rowIndex = rowGroupIndices[i][j]; if (rowIndex < 1 || rowIndex > rowCount) { throw new IndexOutOfBoundsException( "Invalid row group index " + rowIndex + " in group " + (i + 1)); } if (usedIndices[rowIndex]) { throw new IllegalArgumentException("Row index " + rowIndex + " must not be used in multiple row groups."); } usedIndices[rowIndex] = true; } } this.rowGroupIndices = deepClone(rowGroupIndices); } /** * Adds the specified row index to the last row group. * In case there are no groups, a new group will be created. * * @param rowIndex the index of the row that should be grouped */ public void addGroupedRow(int rowIndex) { int[][] newRowGroups = getRowGroups(); // Create a group if none exists. if (newRowGroups.length == 0) { newRowGroups = new int[][] { { rowIndex } }; } else { int lastGroupIndex = newRowGroups.length - 1; int[] lastGroup = newRowGroups[lastGroupIndex]; int groupSize = lastGroup.length; int[] newLastGroup = new int[groupSize + 1]; System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize); newLastGroup[groupSize] = rowIndex; newRowGroups[lastGroupIndex] = newLastGroup; } setRowGroups(newRowGroups); } // Implementing the LayoutManager and LayoutManager2 Interfaces ********* /** * Throws an <code>UnsupportedOperationException</code>. Does not add * the specified component with the specified name to the layout. * * @param name indicates entry's position and anchor * @param component component to add * @exception UnsupportedOperationException always */ public void addLayoutComponent(String name, Component component) { throw new UnsupportedOperationException( "Use #addLayoutComponent(Component, Object) instead."); } /** * Adds the specified component to the layout, using the specified * <code>constraints</code> object. Note that constraints are mutable and * are, therefore, cloned when cached. * * @param comp the component to be added * @param constraints the component's cell constraints * @exception NullPointerException if <code>constraints</code> is <code>null</code> * @exception IllegalArgumentException if <code>constraints</code> is not a * <code>CellConstraints</code> or a String that cannot be used to construct * a <code>CellConstraints</code> */ public void addLayoutComponent(Component comp, Object constraints) { if (constraints instanceof String) { setConstraints(comp, new CellConstraints((String) constraints)); } else if (constraints instanceof CellConstraints) { setConstraints(comp, (CellConstraints) constraints); } else if (constraints == null) { throw new NullPointerException("The constraints must not be null."); } else { throw new IllegalArgumentException("Illegal constraint type " + constraints.getClass()); } } /** * Removes the specified component from this layout.<p> * * Most applications do not call this method directly. * * @param comp the component to be removed. * @see Container#remove(java.awt.Component) * @see Container#removeAll() */ public void removeLayoutComponent(Component comp) { removeConstraints(comp); } // Layout Requests ****************************************************** /** * Determines the minimum size of the <code>parent</code> container * using this form layout.<p> * * Most applications do not call this method directly. * * @param parent the container in which to do the layout * @return the minimum size of the <code>parent</code> container * * @see Container#doLayout() */ public Dimension minimumLayoutSize(Container parent) { return computeLayoutSize(parent, minimumWidthMeasure, minimumHeightMeasure); } /** * Determines the preferred size of the <code>parent</code> * container using this form layout.<p> * * Most applications do not call this method directly. * * @param parent the container in which to do the layout * @return the preferred size of the <code>parent</code> container * * @see Container#getPreferredSize() */ public Dimension preferredLayoutSize(Container parent) { return computeLayoutSize(parent, preferredWidthMeasure, preferredHeightMeasure); } /** * Returns the maximum dimensions for this layout given the components * in the specified target container. * * @param target the container which needs to be laid out * @see Container * @see #minimumLayoutSize(Container) * @see #preferredLayoutSize(Container) * @return the maximum dimensions for this layout */ public Dimension maximumLayoutSize(Container target) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } /** * Returns the alignment along the x axis. This specifies how * the component would like to be aligned relative to other * components. The value should be a number between 0 and 1 * where 0 represents alignment along the origin, 1 is aligned * the furthest away from the origin, 0.5 is centered, etc. * * @param parent the parent container * @return the value <code>0.5f</code> to indicate center alignment */ public float getLayoutAlignmentX(Container parent) { return 0.5f; } /** * Returns the alignment along the y axis. This specifies how * the component would like to be aligned relative to other * components. The value should be a number between 0 and 1 * where 0 represents alignment along the origin, 1 is aligned * the furthest away from the origin, 0.5 is centered, etc. * * @param parent the parent container * @return the value <code>0.5f</code> to indicate center alignment */ public float getLayoutAlignmentY(Container parent) { return 0.5f; } /** * Invalidates the layout, indicating that if the layout manager * has cached information it should be discarded. * * @param target the container that holds the layout to be invalidated */ public void invalidateLayout(Container target) { invalidateCaches(); } /** * Lays out the specified container using this form layout. This method * reshapes components in the specified container in order to satisfy the * contraints of this <code>FormLayout</code> object.<p> * * Most applications do not call this method directly.<p> * * The form layout performs the following steps: * <ol> * <li>find components that occupy exactly one column or row * <li>compute minimum widths and heights * <li>compute preferred widths and heights * <li>give cols and row equal size if they share a group * <li>compress default columns and rows if total is less than pref size * <li>give cols and row equal size if they share a group * <li>distribute free space * <li>set components bounds * </ol> * * @param parent the container in which to do the layout * @see Container * @see Container#doLayout() */ public void layoutContainer(Container parent) { synchronized (parent.getTreeLock()) { initializeColAndRowComponentLists(); Dimension size = parent.getSize(); Insets insets = parent.getInsets(); int totalWidth = size.width - insets.left - insets.right; int totalHeight = size.height - insets.top - insets.bottom; int[] x = computeGridOrigins(parent, totalWidth, insets.left, colSpecs, colComponents, colGroupIndices, minimumWidthMeasure, preferredWidthMeasure); int[] y = computeGridOrigins(parent, totalHeight, insets.top, rowSpecs, rowComponents, rowGroupIndices, minimumHeightMeasure, preferredHeightMeasure); layoutComponents(x, y); } } // Layout Algorithm ***************************************************** /** * Initializes two lists for columns and rows that hold a column's * or row's components that span only this column or row.<p> * * Iterates over all components and their associated constraints; * every component that has a column span or row span of 1 * is put into the column's or row's component list.<p> * * As of the Forms version 1.0.x invisible components are not taken * into account when the container is layed out. See the TODO in the * JavaDoc class commment for details on this issue. */ private void initializeColAndRowComponentLists() { colComponents = new LinkedList[getColumnCount()]; for (int i = 0; i < getColumnCount(); i++) { colComponents[i] = new LinkedList(); } rowComponents = new LinkedList[getRowCount()]; for (int i = 0; i < getRowCount(); i++) { rowComponents[i] = new LinkedList(); } for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); Component component = (Component) entry.getKey(); if (!component.isVisible()) { continue; } CellConstraints constraints = (CellConstraints) entry.getValue(); if (constraints.gridWidth == 1) { colComponents[constraints.gridX - 1].add(component); } if (constraints.gridHeight == 1) { rowComponents[constraints.gridY - 1].add(component); } } } /** * Computes and returns the layout size of the given <code>parent</code> * container using the specified measures. * * @param parent the container in which to do the layout * @param defaultWidthMeasure the measure used to compute the default width * @param defaultHeightMeasure the measure used to compute the default height * @return the layout size of the <code>parent</code> container */ private Dimension computeLayoutSize(Container parent, Measure defaultWidthMeasure, Measure defaultHeightMeasure) { synchronized (parent.getTreeLock()) { initializeColAndRowComponentLists(); int[] colWidths = maximumSizes(parent, colSpecs, colComponents, minimumWidthMeasure, preferredWidthMeasure, defaultWidthMeasure); int[] rowHeights = maximumSizes(parent, rowSpecs, rowComponents, minimumHeightMeasure, preferredHeightMeasure, defaultHeightMeasure); int[] groupedWidths = groupedSizes(colGroupIndices, colWidths); int[] groupedHeights = groupedSizes(rowGroupIndices, rowHeights); // Convert sizes to origins. int[] xOrigins = computeOrigins(groupedWidths, 0); int[] yOrigins = computeOrigins(groupedHeights, 0); int width1 = sum(groupedWidths); int height1 = sum(groupedHeights); int maxWidth = width1; int maxHeight = height1; /* * Take components that span multiple columns or rows into account. * This shall be done if and only if a component spans an interval * that can grow. */ // First computes the maximum number of cols/rows a component // can span without spanning a growing column. int[] maxFixedSizeColsTable = computeMaximumFixedSpanTable(colSpecs); int[] maxFixedSizeRowsTable = computeMaximumFixedSpanTable(rowSpecs); for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); Component component = (Component) entry.getKey(); if (!component.isVisible()) { continue; } CellConstraints constraints = (CellConstraints) entry .getValue(); if ((constraints.gridWidth > 1) && (constraints.gridWidth > maxFixedSizeColsTable[constraints.gridX - 1])) { //int compWidth = minimumWidthMeasure.sizeOf(component); int compWidth = defaultWidthMeasure.sizeOf(component); //int compWidth = preferredWidthMeasure.sizeOf(component); int gridX1 = constraints.gridX - 1; int gridX2 = gridX1 + constraints.gridWidth; int lead = xOrigins[gridX1]; int trail = width1 - xOrigins[gridX2]; int myWidth = lead + compWidth + trail; if (myWidth > maxWidth) { maxWidth = myWidth; } } if ((constraints.gridHeight > 1) && (constraints.gridHeight > maxFixedSizeRowsTable[constraints.gridY - 1])) { //int compHeight = minimumHeightMeasure.sizeOf(component); int compHeight = defaultHeightMeasure.sizeOf(component); //int compHeight = preferredHeightMeasure.sizeOf(component); int gridY1 = constraints.gridY - 1; int gridY2 = gridY1 + constraints.gridHeight; int lead = yOrigins[gridY1]; int trail = height1 - yOrigins[gridY2]; int myHeight = lead + compHeight + trail; if (myHeight > maxHeight) { maxHeight = myHeight; } } } Insets insets = parent.getInsets(); int width = maxWidth + insets.left + insets.right; int height = maxHeight + insets.top + insets.bottom; return new Dimension(width, height); } } /** * Computes and returns the grid's origins. * * @param container the layout container * @param totalSize the total size to assign * @param offset the offset from left or top margin * @param formSpecs the column or row specs, resp. * @param componentLists the components list for each col/row * @param minMeasure the measure used to determin min sizes * @param prefMeasure the measure used to determin pre sizes * @param groupIndices the group specification * @return an int array with the origins */ private int[] computeGridOrigins(Container container, int totalSize, int offset, List formSpecs, List[] componentLists, int[][] groupIndices, Measure minMeasure, Measure prefMeasure) { /* For each spec compute the minimum and preferred size that is * the maximum of all component minimum and preferred sizes resp. */ int[] minSizes = maximumSizes(container, formSpecs, componentLists, minMeasure, prefMeasure, minMeasure); int[] prefSizes = maximumSizes(container, formSpecs, componentLists, minMeasure, prefMeasure, prefMeasure); int[] groupedMinSizes = groupedSizes(groupIndices, minSizes); int[] groupedPrefSizes = groupedSizes(groupIndices, prefSizes); int totalMinSize = sum(groupedMinSizes); int totalPrefSize = sum(groupedPrefSizes); int[] compressedSizes = compressedSizes(formSpecs, totalSize, totalMinSize, totalPrefSize, groupedMinSizes, prefSizes); int[] groupedSizes = groupedSizes(groupIndices, compressedSizes); int totalGroupedSize = sum(groupedSizes); int[] sizes = distributedSizes(formSpecs, totalSize, totalGroupedSize, groupedSizes); return computeOrigins(sizes, offset); } /** * Computes origins from sizes taking the specified offset into account. * * @param sizes the array of sizes * @param offset an offset for the first origin * @return an array of origins */ private int[] computeOrigins(int[] sizes, int offset) { int count = sizes.length; int[] origins = new int[count + 1]; origins[0] = offset; for (int i = 1; i <= count; i++) { origins[i] = origins[i - 1] + sizes[i - 1]; } return origins; } /** * Lays out the components using the given x and y origins, the column * and row specifications, and the component constraints.<p> * * The actual computation is done by each component's form constraint * object. We just compute the cell, the cell bounds and then hand over * the component, cell bounds, and measure to the form constraints. * This will allow potential subclasses of <code>CellConstraints</code> * to do special micro-layout corrections. For example, such a subclass * could map JComponent classes to visual layout bounds that may * lead to a slightly different bounds. * * @param x an int array of the horizontal origins * @param y an int array of the vertical origins */ private void layoutComponents(int[] x, int[] y) { Rectangle cellBounds = new Rectangle(); for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); Component component = (Component) entry.getKey(); CellConstraints constraints = (CellConstraints) entry.getValue(); int gridX = constraints.gridX - 1; int gridY = constraints.gridY - 1; int gridWidth = constraints.gridWidth; int gridHeight = constraints.gridHeight; cellBounds.x = x[gridX]; cellBounds.y = y[gridY]; cellBounds.width = x[gridX + gridWidth] - cellBounds.x; cellBounds.height = y[gridY + gridHeight] - cellBounds.y; constraints.setBounds(component, this, cellBounds, minimumWidthMeasure, minimumHeightMeasure, preferredWidthMeasure, preferredHeightMeasure); } } /** * Invalidates the component size caches. */ private void invalidateCaches() { componentSizeCache.invalidate(); } /** * Computes and returns the sizes for the given form specs, component * lists and measures fot minimum, preferred, and default size. * * @param container the layout container * @param formSpecs the column or row specs, resp. * @param componentLists the components list for each col/row * @param minMeasure the measure used to determin min sizes * @param prefMeasure the measure used to determin pre sizes * @param defaultMeasure the measure used to determin default sizes * @return the column or row sizes */ private int[] maximumSizes(Container container, List formSpecs, List[] componentLists, Measure minMeasure, Measure prefMeasure, Measure defaultMeasure) { FormSpec formSpec; int size = formSpecs.size(); int[] result = new int[size]; for (int i = 0; i < size; i++) { formSpec = (FormSpec) formSpecs.get(i); result[i] = formSpec.maximumSize(container, componentLists[i], minMeasure, prefMeasure, defaultMeasure); } return result; } /** * Computes and returns the compressed sizes. Compresses space for columns * and rows iff the available space is less than the total preferred size * but more than the total minimum size.<p> * * Only columns and row that are specified to be compressable will be * affected. You can specify a column and row as compressable by * giving it the component size <tt>default</tt>. * * @param formSpecs the column or row specs to use * @param totalSize the total available size * @param totalMinSize the sum of all minimum sizes * @param totalPrefSize the sum of all preferred sizes * @param minSizes an int array of column/row minimum sizes * @param prefSizes an int array of column/row preferred sizes * @return an int array of compressed column/row sizes */ private int[] compressedSizes(List formSpecs, int totalSize, int totalMinSize, int totalPrefSize, int[] minSizes, int[] prefSizes) { // If we have less space than the total min size answer the min sizes. if (totalSize < totalMinSize) { return minSizes; } // If we have more space than the total pref size answer the pref sizes. if (totalSize >= totalPrefSize) { return prefSizes; } int count = formSpecs.size(); int[] sizes = new int[count]; double totalCompressionSpace = totalPrefSize - totalSize; double maxCompressionSpace = totalPrefSize - totalMinSize; double compressionFactor = totalCompressionSpace / maxCompressionSpace; // System.out.println("Total compression space=" + totalCompressionSpace); // System.out.println("Max compression space =" + maxCompressionSpace); // System.out.println("Compression factor =" + compressionFactor); for (int i = 0; i < count; i++) { FormSpec formSpec = (FormSpec) formSpecs.get(i); sizes[i] = prefSizes[i]; if (formSpec.getSize() == Sizes.DEFAULT) { sizes[i] -= (int) Math.round((prefSizes[i] - minSizes[i]) * compressionFactor); } } return sizes; } /** * Computes and returns the grouped sizes. * Gives grouped columns and rows the same size. * * @param groups the group specification * @param rawSizes the raw sizes before the grouping * @return the grouped sizes */ private int[] groupedSizes(int[][] groups, int[] rawSizes) { // Return the compressed sizes if there are no groups. if (groups == null || groups.length == 0) { return rawSizes; } // Initialize the result with the given compressed sizes. int[] sizes = new int[rawSizes.length]; for (int i = 0; i < sizes.length; i++) { sizes[i] = rawSizes[i]; } // For each group equalize the sizes. for (int group = 0; group < groups.length; group++) { int[] groupIndices = groups[group]; int groupMaxSize = 0; // Compute the group's maximum size. for (int i = 0; i < groupIndices.length; i++) { int index = groupIndices[i] - 1; groupMaxSize = Math.max(groupMaxSize, sizes[index]); } // Set all sizes of this group to the group's maximum size. for (int i = 0; i < groupIndices.length; i++) { int index = groupIndices[i] - 1; sizes[index] = groupMaxSize; } } return sizes; } /** * Distributes free space over columns and rows and * returns the sizes after this distribution process. * * @param formSpecs the column/row specifications to work with * @param totalSize the total available size * @param totalPrefSize the sum of all preferred sizes * @param inputSizes the input sizes * @return the distributed sizes */ private int[] distributedSizes(List formSpecs, int totalSize, int totalPrefSize, int[] inputSizes) { double totalFreeSpace = totalSize - totalPrefSize; // Do nothing if there's no free space. if (totalFreeSpace < 0) { return inputSizes; } // Compute the total weight. int count = formSpecs.size(); double totalWeight = 0.0; for (int i = 0; i < count; i++) { FormSpec formSpec = (FormSpec) formSpecs.get(i); totalWeight += formSpec.getResizeWeight(); } // Do nothing if there's no resizing column. if (totalWeight == 0.0) { return inputSizes; } int[] sizes = new int[count]; double restSpace = totalFreeSpace; int roundedRestSpace = (int) totalFreeSpace; for (int i = 0; i < count; i++) { FormSpec formSpec = (FormSpec) formSpecs.get(i); double weight = formSpec.getResizeWeight(); if (weight == FormSpec.NO_GROW) { sizes[i] = inputSizes[i]; } else { double roundingCorrection = restSpace - roundedRestSpace; double extraSpace = totalFreeSpace * weight / totalWeight; double correctedExtraSpace = extraSpace - roundingCorrection; int roundedExtraSpace = (int) Math.round(correctedExtraSpace); sizes[i] = inputSizes[i] + roundedExtraSpace; restSpace -= extraSpace; roundedRestSpace -= roundedExtraSpace; } } return sizes; } /** * Computes and returns the sum of integers in the given array of ints. * * @param sizes an array of ints to sum up * @return the sum of ints in the array */ private int sum(int[] sizes) { int sum = 0; for (int i = sizes.length - 1; i >= 0; i--) { sum += sizes[i]; } return sum; } /** * Computes and returns a table that maps a column/row index * to the maximum number of columns/rows that a component can span * without spanning a growing column.<p> * * Iterates over the specs from right to left/bottom to top, * sets the table value to zero if a spec can grow, * otherwise increases the span by one.<p> * * <strong>Examples:</strong><pre> * "pref, 4dlu, pref, 2dlu, p:grow, 2dlu, pref" -> * [4, 3, 2, 1, 0, MAX_VALUE, MAX_VALUE] * * "p:grow, 4dlu, p:grow, 9dlu, pref" -> * [0, 1, 0, MAX_VALUE, MAX_VALUE] * * "p, 4dlu, p, 2dlu, 0:grow" -> * [4, 3, 2, 1, 0] * </pre> * * @param formSpecs the column specs or row specs * @return a table that maps a spec index to the maximum span for * fixed size specs */ private int[] computeMaximumFixedSpanTable(List formSpecs) { int size = formSpecs.size(); int[] table = new int[size]; int maximumFixedSpan = Integer.MAX_VALUE; // Could be 1 for (int i = size - 1; i >= 0; i--) { FormSpec spec = (FormSpec) formSpecs.get(i); // ArrayList access if (spec.canGrow()) { maximumFixedSpan = 0; } table[i] = maximumFixedSpan; if (maximumFixedSpan < Integer.MAX_VALUE) { maximumFixedSpan++; } } return table; } // Measuring Component Sizes ******************************************** /** * An interface that describes how to measure a <code>Component</code>. * Used to abstract from horizontal and vertical dimensions as well as * minimum and preferred sizes. */ static interface Measure { /** * Computes and returns the size of the given <code>Component</code>. * * @param component the component to measure * @return the component's size */ int sizeOf(Component component); } /** * An abstract implementation of the <code>Measure</code> interface * that caches component sizes. */ private abstract static class CachingMeasure implements Measure, Serializable { /** * Holds previously requested component sizes. * Used to minimize size requests to subcomponents. */ protected final ComponentSizeCache cache; private CachingMeasure(ComponentSizeCache cache) { this.cache = cache; } } /** * Measures a component by computing its minimum width. */ private static final class MinimumWidthMeasure extends CachingMeasure { private MinimumWidthMeasure(ComponentSizeCache cache) { super(cache); } public int sizeOf(Component c) { return cache.getMinimumSize(c).width; } } /** * Measures a component by computing its minimum height. */ private static final class MinimumHeightMeasure extends CachingMeasure { private MinimumHeightMeasure(ComponentSizeCache cache) { super(cache); } public int sizeOf(Component c) { return cache.getMinimumSize(c).height; } } /** * Measures a component by computing its preferred width. */ private static final class PreferredWidthMeasure extends CachingMeasure { private PreferredWidthMeasure(ComponentSizeCache cache) { super(cache); } public int sizeOf(Component c) { return cache.getPreferredSize(c).width; } } /** * Measures a component by computing its preferred height. */ private static final class PreferredHeightMeasure extends CachingMeasure { private PreferredHeightMeasure(ComponentSizeCache cache) { super(cache); } public int sizeOf(Component c) { return cache.getPreferredSize(c).height; } } // Caching Component Sizes ********************************************** /** * A cache for component minimum and preferred sizes. * Used to reduce the requests to determine a component's size. */ private static final class ComponentSizeCache implements Serializable { /** Maps components to their minimum sizes. */ private final Map minimumSizes; /** Maps components to their preferred sizes. */ private final Map preferredSizes; /** * Constructs a <code>ComponentSizeCache</code>. * * @param initialCapacity the initial cache capacity */ private ComponentSizeCache(int initialCapacity) { minimumSizes = new HashMap(initialCapacity); preferredSizes = new HashMap(initialCapacity); } /** * Invalidates the cache. Clears all stored size information. */ void invalidate() { minimumSizes.clear(); preferredSizes.clear(); } /** * Returns the minimum size for the given component. Tries to look up * the value from the cache; lazily creates the value if it has not * been requested before. * * @param component the component to compute the minimum size * @return the component's minimum size */ Dimension getMinimumSize(Component component) { Dimension size = (Dimension) minimumSizes.get(component); if (size == null) { size = component.getMinimumSize(); minimumSizes.put(component, size); } return size; } /** * Returns the preferred size for the given component. Tries to look * up the value from the cache; lazily creates the value if it has not * been requested before. * * @param component the component to compute the preferred size * @return the component's preferred size */ Dimension getPreferredSize(Component component) { Dimension size = (Dimension) preferredSizes.get(component); if (size == null) { size = component.getPreferredSize(); preferredSizes.put(component, size); } return size; } void removeEntry(Component component) { minimumSizes.remove(component); preferredSizes.remove(component); } } // Exposing the Layout Information ************************************** /** * Computes and returns the horizontal and vertical grid origins. * Performs the same layout process as <code>#layoutContainer</code> * but does not layout the components.<p> * * This method has been added only to make it easier to debug * the form layout. <strong>You must not call this method directly; * It may be removed in a future release or the visibility * may be reduced.</strong> * * @param parent the <code>Container</code> to inspect * @return an object that comprises the grid x and y origins */ public LayoutInfo getLayoutInfo(Container parent) { synchronized (parent.getTreeLock()) { initializeColAndRowComponentLists(); Dimension size = parent.getSize(); Insets insets = parent.getInsets(); int totalWidth = size.width - insets.left - insets.right; int totalHeight = size.height - insets.top - insets.bottom; int[] x = computeGridOrigins(parent, totalWidth, insets.left, colSpecs, colComponents, colGroupIndices, minimumWidthMeasure, preferredWidthMeasure); int[] y = computeGridOrigins(parent, totalHeight, insets.top, rowSpecs, rowComponents, rowGroupIndices, minimumHeightMeasure, preferredHeightMeasure); return new LayoutInfo(x, y); } } /** * Stores column and row origins. */ public static final class LayoutInfo { /** * Holds the origins of the columns. */ public final int[] columnOrigins; /** * Holds the origins of the rows. */ public final int[] rowOrigins; private LayoutInfo(int[] xOrigins, int[] yOrigins) { this.columnOrigins = xOrigins; this.rowOrigins = yOrigins; } /** * Returns the layout's horizontal origin, the origin of the first column. * * @return the layout's horizontal origin, the origin of the first column. */ public int getX() { return columnOrigins[0]; } /** * Returns the layout's vertical origin, the origin of the first row. * * @return the layout's vertical origin, the origin of the first row. */ public int getY() { return rowOrigins[0]; } /** * Returns the layout's width, the size between the first and the last * column origin. * * @return the layout's width. */ public int getWidth() { return columnOrigins[columnOrigins.length - 1] - columnOrigins[0]; } /** * Returns the layout's height, the size between the first and last row. * * @return the layout's height. */ public int getHeight() { return rowOrigins[rowOrigins.length - 1] - rowOrigins[0]; } } // Helper Code ********************************************************** /** * Creates and returns a deep copy of the given array. * Unlike <code>#clone</code> that performs a shallow copy, * this method copies both array levels. * * @param array the array to clone * @return a deep copy of the given array * * @see Object#clone() */ private int[][] deepClone(int[][] array) { int[][] result = new int[array.length][]; for (int i = 0; i < result.length; i++) { result[i] = array[i].clone(); } return result; } // Serialization ******************************************************** /** * In addition to the default serialization mechanism this class * invalidates the component size cache. The cache will be populated * again after the deserialization. * Also, the fields <code>colComponents</code> and * <code>rowComponents</code> have been marked as transient * to exclude them from the serialization. */ private void writeObject(ObjectOutputStream out) throws IOException { invalidateCaches(); out.defaultWriteObject(); } // Debug Helper Code **************************************************** /* // Prints the given column widths and row heights. private void printSizes(String title, int[] colWidths, int[] rowHeights) { System.out.println(); System.out.println(title); int totalWidth = 0; System.out.print("Column widths: "); for (int i=0; i < getColumnCount(); i++) { int width = colWidths[i]; totalWidth += width; System.out.print(width + ", "); } System.out.println(" Total=" + totalWidth); int totalHeight = 0; System.out.print("Row heights: "); for (int i=0; i < getRowCount(); i++) { int height = rowHeights[i]; totalHeight += height; System.out.print(height + ", "); } System.out.println(" Total=" + totalHeight); System.out.println(); } */ }