All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgoodies.forms.layout.FormLayout Maven / Gradle / Ivy

Go to download

The JGoodies Forms framework helps you lay out and implement elegant Swing panels quickly and consistently. It makes simple things easy and the hard stuff possible, the good design easy and the bad difficult.

There is a newer version: 1.9.0
Show newest version
/*
 * Copyright (c) 2002-2014 JGoodies Software GmbH. 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 Software GmbH 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 static com.jgoodies.common.base.Preconditions.checkArgument;
import static com.jgoodies.common.base.Preconditions.checkNotNull;
import static com.jgoodies.common.base.Preconditions.checkState;

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.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JComponent;

import com.jgoodies.common.base.Objects;
import com.jgoodies.common.internal.Messages;


/**
 * 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 whitepaper
 * about the FormLayout ships with the product documentation and is available
 * online.

* * 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.

* * 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} also considers each component's minimum and * preferred sizes in order to determine a component's size.

* * 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 and Actions. Other builders can create * frequently used panel design, for example a form that consists of rows of * label-component pairs.

* * FormLayout has been prepared to work with different types of sizes as * defined by the {@link Size} interface.

* * Example 1 (Plain FormLayout):
* 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. *

 * FormLayout layout = new FormLayout(
 *      "right:pref, 6dlu, 50dlu, 4dlu, default",  // columns
 *      "pref, 3dlu, pref, 3dlu, pref");           // rows
 *
 * 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;
 * 

* * Example 2 (Using PanelBuilder):
* This example creates the same panel as above using the * {@link com.jgoodies.forms.builder.PanelBuilder} to add components to the form. *

 * FormLayout layout = new FormLayout(
 *      "right:pref, 6dlu, 50dlu, 4dlu, default",  // columns
 *      "pref, 3dlu, pref, 3dlu, pref");           // rows
 *
 * PanelBuilder builder = new PanelBuilder(layout);
 * 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();
 * 

* * Example 3 (Using DefaultFormBuilder):
* This example utilizes the * {@link com.jgoodies.forms.builder.DefaultFormBuilder} that * ships with the source distribution. *

 * 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();
 * 
* * @author Karsten Lentzsch * @version $Revision: 1.30 $ * * @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.layout.FormSpecs * @see Size * @see Sizes */ public final class FormLayout implements LayoutManager2, Serializable { // Instance Fields ******************************************************** /** * 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}. * * @see CellConstraints * @see #getConstraints(Component) * @see #setConstraints(Component, CellConstraints) */ private final Map constraintMap; private boolean honorsVisibility = true; // 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.

* * 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. * The string decoding uses the default LayoutMap.

* * This constructor is intended to be used with builder classes that * add rows dynamically, such as the {@code DefaultFormBuilder}.

* * Examples:

     * // 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");
     * 
See the class comment for more examples. * * @param encodedColumnSpecs comma separated encoded column specifications * * @throws NullPointerException if encodedColumnSpecs is {@code null} * * @see LayoutMap#getRoot() */ public FormLayout(String encodedColumnSpecs) { this(encodedColumnSpecs, LayoutMap.getRoot()); } /** * Constructs a FormLayout using the given encoded column specifications * and LayoutMap. The constructed layout has no rows; these must be added * before components can be added to the layout container.

* * This constructor is intended to be used with builder classes that * add rows dynamically, such as the {@code DefaultFormBuilder}.

* * Examples:

     * // Label, gap, component
     * FormLayout layout = new FormLayout(
     *      "pref, 4dlu, pref",
     *      myLayoutMap);
     *
     * // Right-aligned label, gap, component, gap, component
     * FormLayout layout = new FormLayout(
     *      "right:pref, @lcgap, 50dlu, 4dlu, 50dlu",
     *      myLayoutMap);
     *
     * // Left-aligned labels, gap, components, gap, components
     * FormLayout layout = new FormLayout(
     *      "left:pref, @lcgap, pref, @myGap, pref",
     *      myLayoutMap);
     * 
See the class comment for more examples. * * @param encodedColumnSpecs comma separated encoded column specifications * @param layoutMap expands layout column and row variables * * @throws NullPointerException if {@code encodedColumnSpecs} or * {@code layoutMap} is {@code null} * * @see LayoutMap#getRoot() * * @since 1.2 */ public FormLayout(String encodedColumnSpecs, LayoutMap layoutMap) { this(ColumnSpec.decodeSpecs(encodedColumnSpecs, layoutMap), new RowSpec[0]); } /** * Constructs a FormLayout using the given * encoded column and row specifications and the default LayoutMap.

* * This constructor is recommended for most hand-coded layouts.

* * Examples:

     * 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
     * 
See the class comment for more examples. * * @param encodedColumnSpecs comma separated encoded column specifications * @param encodedRowSpecs comma separated encoded row specifications * * @throws NullPointerException if encodedColumnSpecs or encodedRowSpecs * is {@code null} * * @see LayoutMap#getRoot() */ public FormLayout(String encodedColumnSpecs, String encodedRowSpecs) { this(encodedColumnSpecs, encodedRowSpecs, LayoutMap.getRoot()); } /** * Constructs a FormLayout using the given * encoded column and row specifications and the given LayoutMap.

* * Examples:

     * FormLayout layout = new FormLayout(
     *      "pref, 4dlu, pref",               // columns
     *      "p, 3dlu, p",                     // rows
     *      myLayoutMap);                     // custom LayoutMap
     *
     * FormLayout layout = new FormLayout(
     *      "right:pref, 4dlu, pref",         // columns
     *      "p, @lgap, p, @lgap, fill:p:grow",// rows
     *      myLayoutMap);                     // custom LayoutMap
     *
     * FormLayout layout = new FormLayout(
     *      "left:pref, 4dlu, 50dlu",         // columns
     *      "p, 2px, p, 3dlu, p, 9dlu, p",    // rows
     *      myLayoutMap);                     // custom LayoutMap
     *
     * FormLayout layout = new FormLayout(
     *      "max(75dlu;pref), 4dlu, default", // columns
     *      "p, 3dlu, p, 3dlu, p, 3dlu, p",   // rows
     *      myLayoutMap);                     // custom LayoutMap
     * 
See the class comment for more examples. * * @param encodedColumnSpecs comma separated encoded column specifications * @param encodedRowSpecs comma separated encoded row specifications * @param layoutMap expands layout column and row variables * * @throws NullPointerException if {@code encodedColumnSpecs}, * {@code encodedRowSpecs}, or {@code layoutMap} is {@code null} * * @since 1.2 */ public FormLayout( String encodedColumnSpecs, String encodedRowSpecs, LayoutMap layoutMap) { this(ColumnSpec.decodeSpecs(encodedColumnSpecs, layoutMap), RowSpec. decodeSpecs(encodedRowSpecs, layoutMap)); } /** * Constructs a FormLayout using the given column specifications. * The constructed layout has no rows; these must be added * before components can be added to the layout container. * * @param colSpecs an array of column specifications. * @throws NullPointerException if {@code colSpecs} is {@code null} * * @since 1.1 */ public FormLayout(ColumnSpec[] colSpecs) { this(colSpecs, new RowSpec[]{}); } /** * Constructs a FormLayout using the given column and row specifications. * * @param colSpecs an array of column specifications. * @param rowSpecs an array of row specifications. * @throws NullPointerException if {@code colSpecs} or {@code rowSpecs} * is {@code null} */ public FormLayout(ColumnSpec[] colSpecs, RowSpec[] rowSpecs) { checkNotNull(colSpecs, "The column specifications must not be null."); checkNotNull(rowSpecs, "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 {@code ColumnSpec} at the specified column index. * * @param columnIndex the column index of the requested {@code ColumnSpec} * @return the {@code ColumnSpec} at the specified column * @throws IndexOutOfBoundsException if the column index is out of range */ public ColumnSpec getColumnSpec(int columnIndex) { return colSpecs.get(columnIndex - 1); } /** * Sets the ColumnSpec at the specified column index. * * @param columnIndex the index of the column to be changed * @param columnSpec the ColumnSpec to be set * @throws NullPointerException if {@code columnSpec} is {@code null} * @throws IndexOutOfBoundsException if the column index is out of range */ public void setColumnSpec(int columnIndex, ColumnSpec columnSpec) { checkNotNull(columnSpec, "The column spec must not be null."); colSpecs.set(columnIndex - 1, columnSpec); } /** * Appends the given column specification to the right hand side of all * columns. * * @param columnSpec the column specification to be added * @throws NullPointerException if {@code columnSpec} is {@code null} */ public void appendColumn(ColumnSpec columnSpec) { checkNotNull(columnSpec, "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.

* * 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.

* * 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 * @throws 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.

* * 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.

* * Column group indices that are greater than the column index will be * decreased by one.

* * Note: 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} that provides * the required test methods:
* {@code #columnContainsComponents(Container, int)} and
* {@code #isGroupedColumn(FormLayout, int)}. * * @param columnIndex index of the column to remove * @throws IndexOutOfBoundsException if the column index is out of range * @throws IllegalStateException if the column contains components * or if the column is already grouped * * @see com.jgoodies.forms.extras.FormLayoutUtils#columnContainsComponent(Container, int) * @see com.jgoodies.forms.extras.FormLayoutUtils#isGroupedColumn(FormLayout, int) */ 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); } /** * Returns the number of rows in this layout. * * @return the number of rows */ public int getRowCount() { return rowSpecs.size(); } /** * Returns the {@code RowSpec} at the specified row index. * * @param rowIndex the row index of the requested {@code RowSpec} * @return the {@code RowSpec} at the specified row * @throws IndexOutOfBoundsException if the row index is out of range */ public RowSpec getRowSpec(int rowIndex) { return rowSpecs.get(rowIndex - 1); } /** * Sets the RowSpec at the specified row index. * * @param rowIndex the index of the row to be changed * @param rowSpec the RowSpec to be set * @throws NullPointerException if {@code rowSpec} is {@code null} * @throws IndexOutOfBoundsException if the row index is out of range */ public void setRowSpec(int rowIndex, RowSpec rowSpec) { checkNotNull(rowSpec, "The row spec must not be null."); rowSpecs.set(rowIndex - 1, rowSpec); } /** * Appends the given row specification to the bottom of all rows. * * @param rowSpec the row specification to be added to the form layout * @throws NullPointerException if {@code rowSpec} is {@code null} */ public void appendRow(RowSpec rowSpec) { checkNotNull(rowSpec, "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.

* * 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.

* * 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 * @throws 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.

* * 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.

* * Row group indices that are greater than the row index will be decreased * by one.

* * Note: 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} that provides * the required test methods:
* {@code #rowContainsComponents(Container, int)} and
* {@code #isGroupedRow(FormLayout, int)}. * * @param rowIndex index of the row to remove * @throws IndexOutOfBoundsException if the row index is out of range * @throws IllegalStateException if the row contains components * or if the row is already grouped * * @see com.jgoodies.forms.extras.FormLayoutUtils#rowContainsComponent(Container, int) * @see com.jgoodies.forms.extras.FormLayoutUtils#isGroupedRow(FormLayout, int) */ 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 * @throws IllegalStateException if a removed column contains components */ private void shiftComponentsHorizontally(int columnIndex, boolean remove) { final int offset = remove ? -1 : 1; for (Object element : constraintMap.entrySet()) { Map.Entry entry = (Map.Entry) element; 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 * @throws IllegalStateException if a removed column contains components */ private void shiftComponentsVertically(int rowIndex, boolean remove) { final int offset = remove ? -1 : 1; for (Object element : constraintMap.entrySet()) { Map.Entry entry = (Map.Entry) element; 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 * @throws IllegalStateException if we remove and the index is grouped */ private static void adjustGroupIndices(int[][] allGroupIndices, int modifiedIndex, boolean remove) { final int offset = remove ? -1 : +1; for (int[] allGroupIndice : allGroupIndices) { int[] groupIndices = allGroupIndice; 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 actualCellConstraints object is returned. * * @param component the component to be queried * @return the CellConstraints for the specified component * @throws NullPointerException if {@code component} is {@code null} * @throws IllegalStateException if {@code component} has not been * added to the container */ public CellConstraints getConstraints(Component component) { return (CellConstraints) getConstraints0(component).clone(); } private CellConstraints getConstraints0(Component component) { checkNotNull(component, "The component must not be null."); CellConstraints constraints = constraintMap.get(component); checkState(constraints != null, "The component has not been added to the container."); return constraints; } /** * Sets the constraints for the specified component in this layout. * * @param component the component to be modified * @param constraints the constraints to be applied * @throws NullPointerException if {@code component} or {@code constraints} * is {@code null} */ public void setConstraints(Component component, CellConstraints constraints) { checkNotNull(component, "The component must not be null."); checkNotNull(constraints, "The constraints must not be null."); constraints.ensureValidGridBounds(getColumnCount(), getRowCount()); constraintMap.put(component, (CellConstraints) 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.

* * Examples:

     * // 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}});
     * 
* * @param groupOfIndices a two-dimensional array of column groups indices * * @throws IndexOutOfBoundsException if an index is outside the grid * @throws IllegalArgumentException if a column index is used twice, * or of a group of indices contains only a single element */ public void setColumnGroups(int[][] groupOfIndices) { setColumnGroupsImpl(groupOfIndices, true); } private void setColumnGroupsImpl(int[][] groupOfIndices, boolean checkIndices) { int maxColumn = getColumnCount(); boolean[] usedIndices = new boolean[maxColumn + 1]; for (int group = 0; group < groupOfIndices.length; group++) { int[] indices = groupOfIndices[group]; if (checkIndices) { checkArgument(indices.length >= 2, "Each indice group must contain at least two indices."); } for (int indice : indices) { int colIndex = indice; 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(groupOfIndices); } /** * Sets a single column group, where each column gets the same width.

* * Example:

     * // Group columns 1, 3 and 4.
     * setColumnGroup(1, 3, 4);
     * 
* * @param indices the indices for a single column group * @throws IndexOutOfBoundsException if an index is outside the grid * @throws IllegalArgumentException if a column index is used twice * or if there is only a single index * @throws NullPointerException if {@code indices} is {@code null} * * @see #setColumnGroups(int[][]) * * @since 1.8 */ public void setColumnGroup(int... indices) { checkNotNull(indices, Messages.MUST_NOT_BE_NULL, "column group indices"); checkArgument(indices.length >= 2, "You must specify at least two indices."); setColumnGroups(new int[][]{indices}); } /** * 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; } setColumnGroupsImpl(newColGroups, false); } /** * 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.

* * Examples:

     * // 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}});
     * 
* * @param groupOfIndices a two-dimensional array of row group indices * * @throws IndexOutOfBoundsException if an index is outside the grid * @throws IllegalArgumentException if a column index is used twice, * or of a group of indices contains only a single element */ public void setRowGroups(int[][] groupOfIndices) { setRowGroupsImpl(groupOfIndices, true); } private void setRowGroupsImpl(int[][] groupOfIndices, boolean checkIndices) { int rowCount = getRowCount(); boolean[] usedIndices = new boolean[rowCount + 1]; for (int group = 0; group < groupOfIndices.length; group++) { int[] indices = groupOfIndices[group]; if (checkIndices) { checkArgument(indices.length >= 2, "Each indice group must contain at least two indices."); } for (int indice : indices) { int rowIndex = indice; if (rowIndex < 1 || rowIndex > rowCount) { throw new IndexOutOfBoundsException( "Invalid row group index " + rowIndex + " in group " + (group + 1)); } if (usedIndices[rowIndex]) { throw new IllegalArgumentException( "Row index " + rowIndex + " must not be used in multiple row groups."); } usedIndices[rowIndex] = true; } } this.rowGroupIndices = deepClone(groupOfIndices); } /** * Sets a single row group, where each row gets the same height.

* * Example:

     * // Group rows 1 and 2.
     * setRowGroup(1, 2);
     * 
* * @param indices the indices for a single row group * @throws IndexOutOfBoundsException if an index is outside the grid * @throws IllegalArgumentException if a row index is used twice * or if there is only a single index * @throws NullPointerException if {@code indices} is {@code null} * * @see #setRowGroups(int[][]) * * @since 1.8 */ public void setRowGroup(int... indices) { checkNotNull(indices, Messages.MUST_NOT_BE_NULL, "row group indices"); checkArgument(indices.length >= 2, "You must specify at least two indices."); setRowGroups(new int[][]{indices}); } /** * 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; } setRowGroupsImpl(newRowGroups, false); } // Other Accessors ******************************************************** /** * Returns whether invisible components shall be taken into account * by this layout. This container-wide setting can be overridden * per component. See {@link #setHonorsVisibility(boolean)} for details. * * @return {@code true} if the component visibility is honored * by this FormLayout, {@code false} if it is ignored. * This setting can be overridden for individual CellConstraints * using {@link #setHonorsVisibility(Component, Boolean)}. * * @since 1.2 */ public boolean getHonorsVisibility() { return honorsVisibility; } /** * Specifies whether invisible components shall be taken into account by * this layout for computing the layout size and setting component bounds. * If set to {@code true} invisible components will be ignored by * the layout. If set to {@code false} components will be taken into * account regardless of their visibility. Visible components are always * used for sizing and positioning.

* * The default value for this setting is {@code true}. * It is useful to set the value to {@code false} (in other words * to ignore the visibility) if you switch the component visibility * dynamically and want the container to retain the size and * component positions.

* * This container-wide default setting can be overridden per component * using {@link #setHonorsVisibility(Component, Boolean)}.

* * Components are taken into account, if

    *
  1. they are visible, or *
  2. they have no individual setting and the container-wide settings * ignores the visibility (honorsVisibility set to {@code false}), or *
  3. the individual component ignores the visibility. *
* * @param b {@code true} to honor the visibility, i.e. to exclude * invisible components from the sizing and positioning, * {@code false} to ignore the visibility, in other words to * layout visible and invisible components * * @since 1.2 */ public void setHonorsVisibility(boolean b) { boolean oldHonorsVisibility = getHonorsVisibility(); if (oldHonorsVisibility == b) { return; } honorsVisibility = b; Set componentSet = constraintMap.keySet(); if (componentSet.isEmpty()) { return; } Component firstComponent = (Component) componentSet.iterator().next(); Container container = firstComponent.getParent(); invalidateAndRepaint(container); } /** * Specifies whether the given component shall be taken into account * for sizing and positioning. This setting overrides the container-wide * default. See {@link #setHonorsVisibility(boolean)} for details. * * @param component the component that shall get an individual setting * @param b {@code Boolean.TRUE} to override the container * default and honor the visibility for the given component, * {@code Boolean.FALSE} to override the container default and * ignore the visibility for the given component, * {@code null} to use the container default value as specified * by {@link #getHonorsVisibility()}. * * @since 1.2 */ public void setHonorsVisibility(Component component, Boolean b) { CellConstraints constraints = getConstraints0(component); if (Objects.equals(b, constraints.honorsVisibility)) { return; } constraints.honorsVisibility = b; invalidateAndRepaint(component.getParent()); } // Implementing the LayoutManager and LayoutManager2 Interfaces ********* /** * Throws an {@code UnsupportedOperationException}. 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 * @throws UnsupportedOperationException always */ @Override 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} 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 * @throws NullPointerException if {@code constraints} is {@code null} * @throws IllegalArgumentException if {@code constraints} is neither * a String, nor a CellConstraints object, * or a String that is rejected by the CellConstraints construction */ @Override public void addLayoutComponent(Component comp, Object constraints) { checkNotNull(constraints, "The constraints must not be null."); if (constraints instanceof String) { setConstraints(comp, new CellConstraints((String) constraints)); } else if (constraints instanceof CellConstraints) { setConstraints(comp, (CellConstraints) constraints); } else { throw new IllegalArgumentException("Illegal constraint type " + constraints.getClass()); } } /** * Removes the specified component from this layout.

* * Most applications do not call this method directly. * * @param comp the component to be removed. * @see Container#remove(java.awt.Component) * @see Container#removeAll() */ @Override public void removeLayoutComponent(Component comp) { removeConstraints(comp); } // Layout Requests ****************************************************** /** * Determines the minimum size of the {@code parent} container * using this form layout.

* * 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} container * * @see Container#doLayout() */ @Override public Dimension minimumLayoutSize(Container parent) { return computeLayoutSize(parent, minimumWidthMeasure, minimumHeightMeasure); } /** * Determines the preferred size of the {@code parent} * container using this form layout.

* * 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} container * * @see Container#getPreferredSize() */ @Override 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 */ @Override 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 farthest away from the origin, 0.5 is centered, etc. * * @param parent the parent container * @return the value {@code 0.5f} to indicate center alignment */ @Override 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 farthest away from the origin, 0.5 is centered, etc. * * @param parent the parent container * @return the value {@code 0.5f} to indicate center alignment */ @Override 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 */ @Override 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 constraints of this {@code FormLayout} object.

* * Most applications do not call this method directly.

* * The form layout performs the following steps: *

    *
  1. find components that occupy exactly one column or row *
  2. compute minimum widths and heights *
  3. compute preferred widths and heights *
  4. give cols and row equal size if they share a group *
  5. compress default columns and rows if total is less than pref size *
  6. give cols and row equal size if they share a group *
  7. distribute free space *
  8. set components bounds *
* * @param parent the container in which to do the layout * @see Container * @see Container#doLayout() */ @Override 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.

* * 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. */ private void initializeColAndRowComponentLists() { colComponents = new List[getColumnCount()]; for (int i = 0; i < getColumnCount(); i++) { colComponents[i] = new ArrayList(); } rowComponents = new List[getRowCount()]; for (int i = 0; i < getRowCount(); i++) { rowComponents[i] = new ArrayList(); } for (Object element : constraintMap.entrySet()) { Map.Entry entry = (Map.Entry) element; Component component = (Component) entry.getKey(); CellConstraints constraints = (CellConstraints) entry.getValue(); if (takeIntoAccount(component, constraints)) { 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} * 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} 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 (Object element : constraintMap.entrySet()) { Map.Entry entry = (Map.Entry) element; Component component = (Component) entry.getKey(); CellConstraints constraints = (CellConstraints) entry.getValue(); if (!takeIntoAccount(component, constraints)) { continue; } 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 determine min sizes * @param prefMeasure the measure used to determine pre sizes * @param groupIndices the group specification * @return an int array with the origins */ private static 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 static 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.

* * 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} * 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 (Object element : constraintMap.entrySet()) { Map.Entry entry = (Map.Entry) element; 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 for 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 determine min sizes * @param prefMeasure the measure used to determine pre sizes * @param defaultMeasure the measure used to determine default sizes * @return the column or row sizes */ private static 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.

* * Only columns and rows that are specified to be compressible will be * affected. You can specify a column and row as compressible by * giving it the component size default. * * @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 static 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().compressible()) { 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 static 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[] groupIndices : groups) { int groupMaxSize = 0; // Compute the group's maximum size. for (int groupIndice : groupIndices) { int index = groupIndice - 1; groupMaxSize = Math.max(groupMaxSize, sizes[index]); } // Set all sizes of this group to the group's maximum size. for (int groupIndice : groupIndices) { int index = groupIndice - 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 static 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 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.

* * 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.

* * Examples:

     * "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]
     * 
* * @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 static 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; } // Helper Code ************************************************************ /** * 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 static int sum(final int[] sizes) { int sum = 0; for (int i = sizes.length - 1; i >= 0; i--) { sum += sizes[i]; } return sum; } private static void invalidateAndRepaint(Container container) { if (container == null) { return; } if (container instanceof JComponent) { ((JComponent) container).revalidate(); } else { container.invalidate(); } container.repaint(); } /** * Checks and answers whether the given component with the specified * CellConstraints shall be taken into account for the layout. * * @param component the component to test * @param cc the component's associated CellConstraints * @return {@code true} if * a) {@code component} is visible, or * b) {@code component} has no individual setting and the container-wide settings * ignores the visibility, or * c) {@code cc} indicates that this individual component * ignores the visibility. */ private boolean takeIntoAccount(Component component, CellConstraints cc) { return component.isVisible() || cc.honorsVisibility == null && !getHonorsVisibility() || Boolean.FALSE.equals(cc.honorsVisibility); } // Measuring Component Sizes ******************************************** /** * An interface that describes how to measure a {@code Component}. * Used to abstract from horizontal and vertical dimensions as well as * minimum and preferred sizes. * * @since 1.1 */ public static interface Measure { /** * Computes and returns the size of the given {@code Component}. * * @param component the component to measure * @return the component's size */ int sizeOf(Component component); } /** * An abstract implementation of the {@code Measure} 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); } @Override 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); } @Override 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); } @Override 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); } @Override 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}. * * @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 = 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 = 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} * but does not layout the components.

* * This method has been added only to make it easier to debug * the form layout. You must not call this method directly; * It may be removed in a future release or the visibility * may be reduced. * * @param parent the {@code Container} 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} 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 static 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} and * {@code rowComponents} 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(); } */ }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy