001/*
002 * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003 *
004 * Redistribution and use in source and binary forms, with or without
005 * modification, are permitted provided that the following conditions are met:
006 *
007 *  o Redistributions of source code must retain the above copyright notice,
008 *    this list of conditions and the following disclaimer.
009 *
010 *  o Redistributions in binary form must reproduce the above copyright notice,
011 *    this list of conditions and the following disclaimer in the documentation
012 *    and/or other materials provided with the distribution.
013 *
014 *  o Neither the name of JGoodies Karsten Lentzsch nor the names of
015 *    its contributors may be used to endorse or promote products derived
016 *    from this software without specific prior written permission.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030
031package com.jgoodies.forms.layout;
032
033import java.awt.Component;
034import java.awt.Container;
035import java.awt.Dimension;
036import java.awt.Insets;
037import java.awt.LayoutManager2;
038import java.awt.Rectangle;
039import java.io.IOException;
040import java.io.ObjectOutputStream;
041import java.io.Serializable;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.HashMap;
045import java.util.Iterator;
046import java.util.LinkedList;
047import java.util.List;
048import java.util.Map;
049
050/**
051 * FormLayout is a powerful, flexible and precise general purpose
052 * layout manager. It aligns components vertically and horizontally in
053 * a dynamic rectangular grid of cells, with each component occupying one or
054 * more cells.
055 * A whitepaper
056 * about the FormLayout ships with the product documentation and is available
057 * <a href="https://web.archive.org/web/20061209004117/http://www.jgoodies.com:80/articles/forms.pdf">online</a>.<p>
058 *
059 * To use FormLayout you first define the grid by specifying the
060 * columns and rows. In a second step you add components to the grid. You can
061 * specify columns and rows via human-readable String descriptions or via
062 * arrays of {@link ColumnSpec} and {@link RowSpec} instances.<p>
063 *
064 * Each component managed by a FormLayout is associated with an instance of
065 * {@link CellConstraints}. The constraints object specifies where a component
066 * should be located on the form's grid and how the component should be
067 * positioned. In addition to its constraints object the
068 * <code>FormLayout</code> also considers each component's minimum and
069 * preferred sizes in order to determine a component's size.<p>
070 *
071 * FormLayout has been designed to work with non-visual builders that help you
072 * specify the layout and fill the grid. For example, the
073 * {@link com.jgoodies.forms.builder.ButtonBarBuilder} assists you in building button
074 * bars; it creates a standardized FormLayout and provides a minimal API that
075 * specializes in adding buttons. Other builders can create frequently used
076 * panel design, for example a form that consists of rows of label-component
077 * pairs.<p>
078 *
079 * FormLayout has been prepared to work with different types of sizes as
080 * defined by the {@link Size} interface.<p>
081 *
082 * <strong>Example 1</strong> (Plain FormLayout):<br>
083 * The following example creates a panel with 3 data columns and 3 data rows;
084 * the columns and rows are specified before components are added
085 * to the form.
086 * <pre>
087 * FormLayout layout = new FormLayout(
088 *      "right:pref, 6dlu, 50dlu, 4dlu, default",  // columns
089 *      "pref, 3dlu, pref, 3dlu, pref");           // rows
090 *
091 * CellConstraints cc = new CellConstraints();
092 * JPanel panel = new JPanel(layout);
093 * panel.add(new JLabel("Label1"),   cc.xy  (1, 1));
094 * panel.add(new JTextField(),       cc.xywh(3, 1, 3, 1));
095 * panel.add(new JLabel("Label2"),   cc.xy  (1, 3));
096 * panel.add(new JTextField(),       cc.xy  (3, 3));
097 * panel.add(new JLabel("Label3"),   cc.xy  (1, 5));
098 * panel.add(new JTextField(),       cc.xy  (3, 5));
099 * panel.add(new JButton("/u2026"),  cc.xy  (5, 5));
100 * return panel;
101 * </pre><p>
102 *
103 * <strong>Example 2</strong> (Using PanelBuilder):<br>
104 * This example creates the same panel as above using the
105 * {@link com.jgoodies.forms.builder.PanelBuilder} to add components to the form.
106 * <pre>
107 * FormLayout layout = new FormLayout(
108 *      "right:pref, 6dlu, 50dlu, 4dlu, default",  // columns
109 *      "pref, 3dlu, pref, 3dlu, pref");           // rows
110 *
111 * PanelBuilder builder = new PanelBuilder(layout);
112 * CellConstraints cc = new CellConstraints();
113 * builder.addLabel("Label1",         cc.xy  (1, 1));
114 * builder.add(new JTextField(),      cc.xywh(3, 1, 3, 1));
115 * builder.addLabel("Label2",         cc.xy  (1, 3));
116 * builder.add(new JTextField(),      cc.xy  (3, 3));
117 * builder.addLabel("Label3",         cc.xy  (1, 5));
118 * builder.add(new JTextField(),      cc.xy  (3, 5));
119 * builder.add(new JButton("/u2026"), cc.xy  (5, 5));
120 * return builder.getPanel();
121 * </pre><p>
122 *
123 * <strong>Example 3</strong> (Using DefaultFormBuilder):<br>
124 * This example utilizes the
125 * {@link com.jgoodies.forms.builder.DefaultFormBuilder} that
126 * ships with the source distribution.
127 * <pre>
128 * FormLayout layout = new FormLayout(
129 *      "right:pref, 6dlu, 50dlu, 4dlu, default"); // 5 columns; add rows later
130 *
131 * DefaultFormBuilder builder = new DefaultFormBuilder(layout);
132 * builder.append("Label1", new JTextField(), 3);
133 * builder.append("Label2", new JTextField());
134 * builder.append("Label3", new JTextField());
135 * builder.append(new JButton("/u2026"));
136 * return builder.getPanel();
137 * </pre><p>
138 *
139 * TODO: In the Forms 1.0.x invisible components are not taken into account
140 * when the FormLayout lays out the container. Add an optional setting for
141 * this on both the container-level and component-level. So one can specify
142 * that invisible components shall be taken into account, but may exclude
143 * individual components. Or the other way round, exclude invisible components,
144 * and include individual components. The API of both the FormLayout and
145 * CellConstraints classes shall be extended to support this option.
146 * This feature is planned for the Forms version 1.1 and is described in
147 * <a href="https://forms.dev.java.net/issues/show_bug.cgi?id=28">issue #28</a>
148 * of the Forms' issue tracker where you can track the progress.
149 *
150 * @author Karsten Lentzsch
151 * @version $Revision$
152 *
153 * @see        ColumnSpec
154 * @see        RowSpec
155 * @see        CellConstraints
156 * @see        com.jgoodies.forms.builder.AbstractFormBuilder
157 * @see        com.jgoodies.forms.builder.ButtonBarBuilder
158 * @see        com.jgoodies.forms.builder.DefaultFormBuilder
159 * @see        com.jgoodies.forms.factories.FormFactory
160 * @see        Size
161 * @see        Sizes
162 */
163@SuppressWarnings("serial")
164public final class FormLayout implements LayoutManager2, Serializable {
165
166    /**
167     * Holds the column specifications.
168     *
169     * @see ColumnSpec
170     * @see #getColumnCount()
171     * @see #getColumnSpec(int)
172     * @see #appendColumn(ColumnSpec)
173     * @see #insertColumn(int, ColumnSpec)
174     * @see #removeColumn(int)
175     */
176    private final List colSpecs;
177
178    /**
179     * Holds the row specifications.
180     *
181     * @see RowSpec
182     * @see #getRowCount()
183     * @see #getRowSpec(int)
184     * @see #appendRow(RowSpec)
185     * @see #insertRow(int, RowSpec)
186     * @see #removeRow(int)
187     */
188    private final List rowSpecs;
189
190    /**
191     * Holds the column groups as an array of arrays of column indices.
192     *
193     * @see #getColumnGroups()
194     * @see #setColumnGroups(int[][])
195     * @see #addGroupedColumn(int)
196     */
197    private int[][] colGroupIndices;
198
199    /**
200     * Holds the row groups as an array of arrays of row indices.
201     *
202     * @see #getRowGroups()
203     * @see #setRowGroups(int[][])
204     * @see #addGroupedRow(int)
205     */
206    private int[][] rowGroupIndices;
207
208    /**
209     * Maps components to their associated <code>CellConstraints</code>.
210     *
211     * @see CellConstraints
212     * @see #getConstraints(Component)
213     * @see #setConstraints(Component, CellConstraints)
214     */
215    private final Map constraintMap;
216
217    // Fields used by the Layout Algorithm **********************************
218
219    /**
220     * Holds the components that occupy exactly one column.
221     * For each column we keep a list of these components.
222     */
223    private transient List[] colComponents;
224
225    /**
226     * Holds the components that occupy exactly one row.
227     * For each row we keep a list of these components.
228     */
229    private transient List[] rowComponents;
230
231    /**
232     * Caches component minimum and preferred sizes.
233     * All requests for component sizes shall be directed to the cache.
234     */
235    private final ComponentSizeCache componentSizeCache;
236
237    /**
238     * These functional objects are used to measure component sizes.
239     * They abstract from horizontal and vertical orientation and so,
240     * allow to implement the layout algorithm for both orientations with a
241     * single set of methods.
242     */
243    private final Measure minimumWidthMeasure;
244    private final Measure minimumHeightMeasure;
245    private final Measure preferredWidthMeasure;
246    private final Measure preferredHeightMeasure;
247
248    // Instance Creation ****************************************************
249
250    /**
251     * Constructs an empty FormLayout. Columns and rows must be added
252     * before components can be added to the layout container.<p>
253     *
254     * This constructor is intended to be used in environments
255     * that add columns and rows dynamically.
256     */
257    public FormLayout() {
258        this(new ColumnSpec[0], new RowSpec[0]);
259    }
260
261    /**
262     * Constructs a FormLayout using the given encoded column specifications.
263     * The constructed layout has no rows; these must be added
264     * before components can be added to the layout container.<p>
265     *
266     * This constructor is primarily intended to be used with builder classes
267     * that add rows dynamically, such as the <code>DefaultFormBuilder</code>.<p>
268     *
269     * <strong>Examples:</strong><pre>
270     * // Label, gap, component
271     * FormLayout layout = new FormLayout(
272     *      "pref, 4dlu, pref");
273     *
274     * // Right-aligned label, gap, component, gap, component
275     * FormLayout layout = new FormLayout(
276     *      "right:pref, 4dlu, 50dlu, 4dlu, 50dlu");
277     *
278     * // Left-aligned labels, gap, components, gap, components
279     * FormLayout layout = new FormLayout(
280     *      "left:pref, 4dlu, pref, 4dlu, pref");
281     * </pre> See the class comment for more examples.
282     *
283     * @param encodedColumnSpecs  comma separated encoded column specifications
284     * @exception NullPointerException  if encodedColumnSpecs is <code>null</code>
285     */
286    public FormLayout(String encodedColumnSpecs) {
287        this(ColumnSpec.decodeSpecs(encodedColumnSpecs), new RowSpec[0]);
288    }
289
290    /**
291     * Constructs a FormLayout using the given
292     * encoded column and row specifications.<p>
293     *
294     * This constructor is recommended for most hand-coded layouts.<p>
295     *
296     * <strong>Examples:</strong><pre>
297     * FormLayout layout = new FormLayout(
298     *      "pref, 4dlu, pref",               // columns
299     *      "p, 3dlu, p");                    // rows
300     *
301     * FormLayout layout = new FormLayout(
302     *      "right:pref, 4dlu, pref",         // columns
303     *      "p, 3dlu, p, 3dlu, fill:p:grow"); // rows
304     *
305     * FormLayout layout = new FormLayout(
306     *      "left:pref, 4dlu, 50dlu",         // columns
307     *      "p, 2px, p, 3dlu, p, 9dlu, p");   // rows
308     *
309     * FormLayout layout = new FormLayout(
310     *      "max(75dlu;pref), 4dlu, default", // columns
311     *      "p, 3dlu, p, 3dlu, p, 3dlu, p");  // rows
312     * </pre> See the class comment for more examples.
313     *
314     * @param encodedColumnSpecs  comma separated encoded column specifications
315     * @param encodedRowSpecs     comma separated encoded row specifications
316     * @exception NullPointerException  if encodedColumnSpecs or encodedRowSpecs
317     *     is <code>null</code>
318     */
319    public FormLayout(String encodedColumnSpecs, String encodedRowSpecs) {
320        this(ColumnSpec.decodeSpecs(encodedColumnSpecs),
321                RowSpec.decodeSpecs(encodedRowSpecs));
322    }
323
324    /**
325     * Constructs a FormLayout using the given column and row specifications.
326     *
327     * @param colSpecs        an array of column specifications.
328     * @param rowSpecs        an array of row specifications.
329     * @exception NullPointerException if colSpecs or rowSpecs is null
330     */
331    public FormLayout(ColumnSpec[] colSpecs, RowSpec[] rowSpecs) {
332        if (colSpecs == null) {
333            throw new NullPointerException(
334                    "The column specifications must not be null.");
335        }
336        if (rowSpecs == null) {
337            throw new NullPointerException(
338                    "The row specifications must not be null.");
339        }
340
341        this.colSpecs = new ArrayList(Arrays.asList(colSpecs));
342        this.rowSpecs = new ArrayList(Arrays.asList(rowSpecs));
343        colGroupIndices = new int[][] {};
344        rowGroupIndices = new int[][] {};
345        int initialCapacity = colSpecs.length * rowSpecs.length / 4;
346        constraintMap = new HashMap(initialCapacity);
347        componentSizeCache = new ComponentSizeCache(initialCapacity);
348        minimumWidthMeasure = new MinimumWidthMeasure(componentSizeCache);
349        minimumHeightMeasure = new MinimumHeightMeasure(componentSizeCache);
350        preferredWidthMeasure = new PreferredWidthMeasure(componentSizeCache);
351        preferredHeightMeasure = new PreferredHeightMeasure(componentSizeCache);
352    }
353
354    // Accessing the Column and Row Specifications **************************
355
356    /**
357     * Returns the number of columns in this layout.
358     *
359     * @return the number of columns
360     */
361    public int getColumnCount() {
362        return colSpecs.size();
363    }
364
365    /**
366     * Returns the number of rows in this layout.
367     *
368     * @return the number of rows
369     */
370    public int getRowCount() {
371        return rowSpecs.size();
372    }
373
374    /**
375     * Returns the <code>ColumnSpec</code> at the specified column index.
376     *
377     * @param columnIndex   the column index of the requested <code>ColumnSpec</code>
378     * @return the <code>ColumnSpec</code> at the specified column
379     * @exception IndexOutOfBoundsException if the column index is out of range
380     */
381    public ColumnSpec getColumnSpec(int columnIndex) {
382        return (ColumnSpec) colSpecs.get(columnIndex - 1);
383    }
384
385    /**
386     * Sets the <code>ColumnSpec</code> at the specified column index.
387     *
388     * @param columnIndex   the index of the column to be changed
389     * @param columnSpec    the <code>ColumnSpec</code> to be set
390     * @exception NullPointerException if the column specification is null
391     * @exception IndexOutOfBoundsException if the column index is out of range
392     */
393    public void setColumnSpec(int columnIndex, ColumnSpec columnSpec) {
394        if (columnSpec == null) {
395            throw new NullPointerException("The column spec must not be null.");
396        }
397        colSpecs.set(columnIndex - 1, columnSpec);
398    }
399
400    /**
401     * Returns the <code>RowSpec</code> at the specified row index.
402     *
403     * @param rowIndex   the row index of the requested <code>RowSpec</code>
404     * @return the <code>RowSpec</code> at the specified row
405     * @exception IndexOutOfBoundsException if the row index is out of range
406     */
407    public RowSpec getRowSpec(int rowIndex) {
408        return (RowSpec) rowSpecs.get(rowIndex - 1);
409    }
410
411    /**
412     * Sets the <code>RowSpec</code> at the specified row index.
413     *
414     * @param rowIndex   the index of the row to be changed
415     * @param rowSpec    the <code>RowSpec</code> to be set
416     * @exception NullPointerException if the row specification is null
417     * @exception IndexOutOfBoundsException if the row index is out of range
418     */
419    public void setRowSpec(int rowIndex, RowSpec rowSpec) {
420        if (rowSpec == null) {
421            throw new NullPointerException("The row spec must not be null.");
422        }
423        rowSpecs.set(rowIndex - 1, rowSpec);
424    }
425
426    /**
427     * Appends the given column specification to the right hand side of all
428     * columns.
429     *
430     * @param columnSpec the column specification to be added
431     * @exception NullPointerException if the column specification is null
432     */
433    public void appendColumn(ColumnSpec columnSpec) {
434        if (columnSpec == null) {
435            throw new NullPointerException("The column spec must not be null.");
436        }
437        colSpecs.add(columnSpec);
438    }
439
440    /**
441     * Inserts the specified column at the specified position. Shifts components
442     * that intersect the new column to the right hand side and readjusts
443     * column groups.<p>
444     *
445     * The component shift works as follows: components that were located on
446     * the right hand side of the inserted column are shifted one column to
447     * the right; component column span is increased by one if it intersects
448     * the new column.<p>
449     *
450     * Column group indices that are greater or equal than the given column
451     * index will be increased by one.
452     *
453     * @param columnIndex  index of the column to be inserted
454     * @param columnSpec   specification of the column to be inserted
455     * @exception IndexOutOfBoundsException if the column index is out of range
456     */
457    public void insertColumn(int columnIndex, ColumnSpec columnSpec) {
458        if (columnIndex < 1 || columnIndex > getColumnCount()) {
459            throw new IndexOutOfBoundsException("The column index "
460                    + columnIndex + "must be in the range [1, "
461                    + getColumnCount() + "].");
462        }
463        colSpecs.add(columnIndex - 1, columnSpec);
464        shiftComponentsHorizontally(columnIndex, false);
465        adjustGroupIndices(colGroupIndices, columnIndex, false);
466    }
467
468    /**
469     * Removes the column with the given column index from the layout.
470     * Components will be rearranged and column groups will be readjusted.
471     * Therefore, the column must not contain components and must not be part
472     * of a column group.<p>
473     *
474     * The component shift works as follows: components that were located on
475     * the right hand side of the removed column are moved one column to the
476     * left; component column span is decreased by one if it intersects the
477     * removed column.<p>
478     *
479     * Column group indices that are greater than the column index will be
480     * decreased by one.<p>
481     *
482     * <strong>Note:</strong> If one of the constraints mentioned above
483     * is violated, this layout's state becomes illegal and it is unsafe
484     * to work with this layout.
485     * A typical layout implementation can ensure that these constraints are
486     * not violated. However, in some cases you may need to check these
487     * conditions before you invoke this method. The Forms extras contain
488     * source code for class <code>FormLayoutUtils</code> that provides
489     * the required test methods:<br>
490     * <code>#columnContainsComponents(Container, int)</code> and<br>
491     * <code>#isGroupedColumn(FormLayout, int)</code>.
492     *
493     * @param columnIndex  index of the column to remove
494     * @exception IndexOutOfBoundsException if the column index is out of range
495     * @exception IllegalStateException  if the column contains components
496     *     or if the column is already grouped
497     *
498     */
499    public void removeColumn(int columnIndex) {
500        if (columnIndex < 1 || columnIndex > getColumnCount()) {
501            throw new IndexOutOfBoundsException("The column index "
502                    + columnIndex + " must be in the range [1, "
503                    + getColumnCount() + "].");
504        }
505        colSpecs.remove(columnIndex - 1);
506        shiftComponentsHorizontally(columnIndex, true);
507        adjustGroupIndices(colGroupIndices, columnIndex, true);
508    }
509
510    /**
511     * Appends the given row specification to the bottom of all rows.
512     *
513     * @param rowSpec  the row specification to be added to the form layout
514     * @exception NullPointerException if the rowSpec is null
515     */
516    public void appendRow(RowSpec rowSpec) {
517        if (rowSpec == null) {
518            throw new NullPointerException("The row spec must not be null.");
519        }
520        rowSpecs.add(rowSpec);
521    }
522
523    /**
524     * Inserts the specified column at the specified position. Shifts
525     * components that intersect the new column to the right and readjusts
526     * column groups.<p>
527     *
528     * The component shift works as follows: components that were located on
529     * the right hand side of the inserted column are shifted one column to
530     * the right; component column span is increased by one if it intersects
531     * the new column.<p>
532     *
533     * Column group indices that are greater or equal than the given column
534     * index will be increased by one.
535     *
536     * @param rowIndex  index of the row to be inserted
537     * @param rowSpec   specification of the row to be inserted
538     * @exception IndexOutOfBoundsException if the row index is out of range
539     */
540    public void insertRow(int rowIndex, RowSpec rowSpec) {
541        if (rowIndex < 1 || rowIndex > getRowCount()) {
542            throw new IndexOutOfBoundsException("The row index " + rowIndex
543                    + " must be in the range [1, " + getRowCount() + "].");
544        }
545        rowSpecs.add(rowIndex - 1, rowSpec);
546        shiftComponentsVertically(rowIndex, false);
547        adjustGroupIndices(rowGroupIndices, rowIndex, false);
548    }
549
550    /**
551     * Removes the row with the given row index from the layout. Components
552     * will be rearranged and row groups will be readjusted. Therefore, the
553     * row must not contain components and must not be part of a row group.<p>
554     *
555     * The component shift works as follows: components that were located
556     * below the removed row are moved up one row; component row span is
557     * decreased by one if it intersects the removed row.<p>
558     *
559     * Row group indices that are greater than the row index will be decreased
560     * by one.<p>
561     *
562     * <strong>Note:</strong> If one of the constraints mentioned above
563     * is violated, this layout's state becomes illegal and it is unsafe
564     * to work with this layout.
565     * A typical layout implementation can ensure that these constraints are
566     * not violated. However, in some cases you may need to check these
567     * conditions before you invoke this method. The Forms extras contain
568     * source code for class <code>FormLayoutUtils</code> that provides
569     * the required test methods:<br>
570     * <code>#rowContainsComponents(Container, int)</code> and<br>
571     * <code>#isGroupedRow(FormLayout, int)</code>.
572     *
573     * @param rowIndex  index of the row to remove
574     * @exception IndexOutOfBoundsException if the row index is out of range
575     * @exception IllegalStateException if the row contains components
576     *     or if the row is already grouped
577     *
578     */
579    public void removeRow(int rowIndex) {
580        if (rowIndex < 1 || rowIndex > getRowCount()) {
581            throw new IndexOutOfBoundsException("The row index " + rowIndex
582                    + "must be in the range [1, " + getRowCount() + "].");
583        }
584        rowSpecs.remove(rowIndex - 1);
585        shiftComponentsVertically(rowIndex, true);
586        adjustGroupIndices(rowGroupIndices, rowIndex, true);
587    }
588
589    /**
590     * Shifts components horizontally, either to the right if a column has been
591     * inserted or to the left if a column has been removed.
592     *
593     * @param columnIndex  index of the column to remove
594     * @param remove                  true for remove, false for insert
595     * @exception IllegalStateException if a removed column contains components
596     */
597    private void shiftComponentsHorizontally(int columnIndex, boolean remove) {
598        final int offset = remove ? -1 : 1;
599        for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) {
600            Map.Entry entry = (Map.Entry) i.next();
601            CellConstraints constraints = (CellConstraints) entry.getValue();
602            int x1 = constraints.gridX;
603            int w = constraints.gridWidth;
604            int x2 = x1 + w - 1;
605            if (x1 == columnIndex && remove) {
606                throw new IllegalStateException("The removed column "
607                        + columnIndex + " must not contain component origins.\n"
608                        + "Illegal component=" + entry.getKey());
609            } else if (x1 >= columnIndex) {
610                constraints.gridX += offset;
611            } else if (x2 >= columnIndex) {
612                constraints.gridWidth += offset;
613            }
614        }
615    }
616
617    /**
618     * Shifts components vertically, either to the bottom if a row has been
619     * inserted or to the top if a row has been removed.
620     *
621     * @param rowIndex      index of the row to remove
622     * @param remove        true for remove, false for insert
623     * @exception IllegalStateException if a removed column contains components
624     */
625    private void shiftComponentsVertically(int rowIndex, boolean remove) {
626        final int offset = remove ? -1 : 1;
627        for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) {
628            Map.Entry entry = (Map.Entry) i.next();
629            CellConstraints constraints = (CellConstraints) entry.getValue();
630            int y1 = constraints.gridY;
631            int h = constraints.gridHeight;
632            int y2 = y1 + h - 1;
633            if (y1 == rowIndex && remove) {
634                throw new IllegalStateException("The removed row " + rowIndex
635                        + " must not contain component origins.\n"
636                        + "Illegal component=" + entry.getKey());
637            } else if (y1 >= rowIndex) {
638                constraints.gridY += offset;
639            } else if (y2 >= rowIndex) {
640                constraints.gridHeight += offset;
641            }
642        }
643    }
644
645    /**
646     * Adjusts group indices. Shifts the given groups to left, right, up,
647     * down according to the specified remove or add flag.
648     *
649     * @param allGroupIndices   the groups to be adjusted
650     * @param modifiedIndex     the modified column or row index
651     * @param remove                        true for remove, false for add
652     * @exception IllegalStateException if we remove and the index is grouped
653     */
654    private void adjustGroupIndices(int[][] allGroupIndices, int modifiedIndex,
655            boolean remove) {
656        final int offset = remove ? -1 : +1;
657        for (int[] allGroupIndice : allGroupIndices) {
658            int[] groupIndices = allGroupIndice;
659            for (int i = 0; i < groupIndices.length; i++) {
660                int index = groupIndices[i];
661                if (index == modifiedIndex && remove) {
662                    throw new IllegalStateException("The removed index "
663                            + modifiedIndex + " must not be grouped.");
664                } else if (index >= modifiedIndex) {
665                    groupIndices[i] += offset;
666                }
667            }
668        }
669    }
670
671    // Accessing Constraints ************************************************
672
673    /**
674     * Looks up and returns the constraints for the specified component.
675     * A copy of the actual <code>CellConstraints</code> object is returned.
676     *
677     * @param component    the component to be queried
678     * @return the <code>CellConstraints</code> for the specified component
679     * @exception NullPointerException if component is <code>null</code> or
680     *     has not been added to the container
681     */
682    public CellConstraints getConstraints(Component component) {
683        if (component == null) {
684            throw new NullPointerException("The component must not be null.");
685        }
686
687        CellConstraints constraints = (CellConstraints) constraintMap
688                .get(component);
689        if (constraints == null) {
690            throw new NullPointerException(
691                    "The component has not been added to the container.");
692        }
693
694        return (CellConstraints) constraints.clone();
695    }
696
697    /**
698     * Sets the constraints for the specified component in this layout.
699     *
700     * @param component     the component to be modified
701     * @param constraints   the constraints to be applied
702     * @exception NullPointerException   if the component or constraints object
703     *     is <code>null</code>
704     */
705    public void setConstraints(Component component,
706            CellConstraints constraints) {
707        if (component == null) {
708            throw new NullPointerException("The component must not be null.");
709        }
710        if (constraints == null) {
711            throw new NullPointerException("The constraints must not be null.");
712        }
713
714        constraints.ensureValidGridBounds(getColumnCount(), getRowCount());
715        constraintMap.put(component, constraints.clone());
716    }
717
718    /**
719     * Removes the constraints for the specified component in this layout.
720     *
721     * @param component  the component to be modified
722     */
723    private void removeConstraints(Component component) {
724        constraintMap.remove(component);
725        componentSizeCache.removeEntry(component);
726    }
727
728    // Accessing Column and Row Groups **************************************
729
730    /**
731     * Returns a deep copy of the column groups.
732     *
733     * @return the column groups as two-dimensional int array
734     */
735    public int[][] getColumnGroups() {
736        return deepClone(colGroupIndices);
737    }
738
739    /**
740     * Sets the column groups, where each column in a group gets the same
741     * group wide width. Each group is described by an array of integers that
742     * are interpreted as column indices. The parameter is an array of such
743     * group descriptions.<p>
744     *
745     * <strong>Examples:</strong><pre>
746     * // Group columns 1, 3 and 4.
747     * setColumnGroups(new int[][]{ {1, 3, 4}});
748     *
749     * // Group columns 1, 3, 4, and group columns 7 and 9
750     * setColumnGroups(new int[][]{ {1, 3, 4}, {7, 9}});
751     * </pre>
752     *
753     * @param colGroupIndices        a two-dimensional array of column groups indices
754     * @exception        IndexOutOfBoundsException if an index is outside the grid
755     * @exception IllegalArgumentException if a column index is used twice
756     */
757    public void setColumnGroups(int[][] colGroupIndices) {
758        int maxColumn = getColumnCount();
759        boolean[] usedIndices = new boolean[maxColumn + 1];
760        for (int group = 0; group < colGroupIndices.length; group++) {
761            for (int j = 0; j < colGroupIndices[group].length; j++) {
762                int colIndex = colGroupIndices[group][j];
763                if (colIndex < 1 || colIndex > maxColumn) {
764                    throw new IndexOutOfBoundsException(
765                            "Invalid column group index " + colIndex
766                                    + " in group " + (group + 1));
767                }
768                if (usedIndices[colIndex]) {
769                    throw new IllegalArgumentException("Column index "
770                            + colIndex
771                            + " must not be used in multiple column groups.");
772                }
773                usedIndices[colIndex] = true;
774            }
775        }
776        this.colGroupIndices = deepClone(colGroupIndices);
777    }
778
779    /**
780     * Adds the specified column index to the last column group.
781     * In case there are no groups, a new group will be created.
782     *
783     * @param columnIndex        the column index to be set grouped
784     */
785    public void addGroupedColumn(int columnIndex) {
786        int[][] newColGroups = getColumnGroups();
787        // Create a group if none exists.
788        if (newColGroups.length == 0) {
789            newColGroups = new int[][] { { columnIndex } };
790        } else {
791            int lastGroupIndex = newColGroups.length - 1;
792            int[] lastGroup = newColGroups[lastGroupIndex];
793            int groupSize = lastGroup.length;
794            int[] newLastGroup = new int[groupSize + 1];
795            System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize);
796            newLastGroup[groupSize] = columnIndex;
797            newColGroups[lastGroupIndex] = newLastGroup;
798        }
799        setColumnGroups(newColGroups);
800    }
801
802    /**
803     * Returns a deep copy of the row groups.
804     *
805     * @return the row groups as two-dimensional int array
806     */
807    public int[][] getRowGroups() {
808        return deepClone(rowGroupIndices);
809    }
810
811    /**
812     * Sets the row groups, where each row in such a group gets the same group
813     * wide height. Each group is described by an array of integers that are
814     * interpreted as row indices. The parameter is an array of such group
815     * descriptions.<p>
816     *
817     * <strong>Examples:</strong><pre>
818     * // Group rows 1 and 2.
819     * setRowGroups(new int[][]{ {1, 2}});
820     *
821     * // Group rows 1 and 2, and group rows 5, 7, and 9.
822     * setRowGroups(new int[][]{ {1, 2}, {5, 7, 9}});
823     * </pre>
824     *
825     * @param rowGroupIndices a two-dimensional array of row group indices.
826     * @exception IndexOutOfBoundsException if an index is outside the grid
827     */
828    public void setRowGroups(int[][] rowGroupIndices) {
829        int rowCount = getRowCount();
830        boolean[] usedIndices = new boolean[rowCount + 1];
831        for (int i = 0; i < rowGroupIndices.length; i++) {
832            for (int j = 0; j < rowGroupIndices[i].length; j++) {
833                int rowIndex = rowGroupIndices[i][j];
834                if (rowIndex < 1 || rowIndex > rowCount) {
835                    throw new IndexOutOfBoundsException(
836                            "Invalid row group index " + rowIndex + " in group "
837                                    + (i + 1));
838                }
839                if (usedIndices[rowIndex]) {
840                    throw new IllegalArgumentException("Row index " + rowIndex
841                            + " must not be used in multiple row groups.");
842                }
843                usedIndices[rowIndex] = true;
844            }
845        }
846        this.rowGroupIndices = deepClone(rowGroupIndices);
847    }
848
849    /**
850     * Adds the specified row index to the last row group.
851     * In case there are no groups, a new group will be created.
852     *
853     * @param rowIndex   the index of the row that should be grouped
854     */
855    public void addGroupedRow(int rowIndex) {
856        int[][] newRowGroups = getRowGroups();
857        // Create a group if none exists.
858        if (newRowGroups.length == 0) {
859            newRowGroups = new int[][] { { rowIndex } };
860        } else {
861            int lastGroupIndex = newRowGroups.length - 1;
862            int[] lastGroup = newRowGroups[lastGroupIndex];
863            int groupSize = lastGroup.length;
864            int[] newLastGroup = new int[groupSize + 1];
865            System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize);
866            newLastGroup[groupSize] = rowIndex;
867            newRowGroups[lastGroupIndex] = newLastGroup;
868        }
869        setRowGroups(newRowGroups);
870    }
871
872    // Implementing the LayoutManager and LayoutManager2 Interfaces *********
873
874    /**
875     * Throws an <code>UnsupportedOperationException</code>. Does not add
876     * the specified component with the specified name to the layout.
877     *
878     * @param name         indicates entry's position and anchor
879     * @param component    component to add
880     * @exception UnsupportedOperationException always
881     */
882    @Override
883    public void addLayoutComponent(String name, Component component) {
884        throw new UnsupportedOperationException(
885                "Use #addLayoutComponent(Component, Object) instead.");
886    }
887
888    /**
889     * Adds the specified component to the layout, using the specified
890     * <code>constraints</code> object.  Note that constraints are mutable and
891     * are, therefore, cloned when cached.
892     *
893     * @param comp         the component to be added
894     * @param constraints  the component's cell constraints
895     * @exception NullPointerException if <code>constraints</code> is <code>null</code>
896     * @exception IllegalArgumentException if <code>constraints</code> is not a
897     * <code>CellConstraints</code> or a String that cannot be used to construct
898     * a <code>CellConstraints</code>
899     */
900    @Override
901    public void addLayoutComponent(Component comp, Object constraints) {
902        if (constraints instanceof String) {
903            setConstraints(comp, new CellConstraints((String) constraints));
904        } else if (constraints instanceof CellConstraints) {
905            setConstraints(comp, (CellConstraints) constraints);
906        } else if (constraints == null) {
907            throw new NullPointerException("The constraints must not be null.");
908        } else {
909            throw new IllegalArgumentException(
910                    "Illegal constraint type " + constraints.getClass());
911        }
912    }
913
914    /**
915     * Removes the specified component from this layout.<p>
916     *
917     * Most applications do not call this method directly.
918     *
919     * @param comp   the component to be removed.
920     * @see Container#remove(java.awt.Component)
921     * @see Container#removeAll()
922     */
923    @Override
924    public void removeLayoutComponent(Component comp) {
925        removeConstraints(comp);
926    }
927
928    // Layout Requests ******************************************************
929
930    /**
931     * Determines the minimum size of the <code>parent</code> container
932     * using this form layout.<p>
933     *
934     * Most applications do not call this method directly.
935     *
936     * @param parent   the container in which to do the layout
937     * @return the minimum size of the <code>parent</code> container
938     *
939     * @see Container#doLayout()
940     */
941    @Override
942    public Dimension minimumLayoutSize(Container parent) {
943        return computeLayoutSize(parent, minimumWidthMeasure,
944                minimumHeightMeasure);
945    }
946
947    /**
948     * Determines the preferred size of the <code>parent</code>
949     * container using this form layout.<p>
950     *
951     * Most applications do not call this method directly.
952     *
953     * @param parent   the container in which to do the layout
954     * @return the preferred size of the <code>parent</code> container
955     *
956     * @see Container#getPreferredSize()
957     */
958    @Override
959    public Dimension preferredLayoutSize(Container parent) {
960        return computeLayoutSize(parent, preferredWidthMeasure,
961                preferredHeightMeasure);
962    }
963
964    /**
965     * Returns the maximum dimensions for this layout given the components
966     * in the specified target container.
967     *
968     * @param target    the container which needs to be laid out
969     * @see Container
970     * @see #minimumLayoutSize(Container)
971     * @see #preferredLayoutSize(Container)
972     * @return the maximum dimensions for this layout
973     */
974    @Override
975    public Dimension maximumLayoutSize(Container target) {
976        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
977    }
978
979    /**
980     * Returns the alignment along the x axis.  This specifies how
981     * the component would like to be aligned relative to other
982     * components.  The value should be a number between 0 and 1
983     * where 0 represents alignment along the origin, 1 is aligned
984     * the furthest away from the origin, 0.5 is centered, etc.
985     *
986     * @param parent   the parent container
987     * @return the value <code>0.5f</code> to indicate center alignment
988     */
989    @Override
990    public float getLayoutAlignmentX(Container parent) {
991        return 0.5f;
992    }
993
994    /**
995     * Returns the alignment along the y axis.  This specifies how
996     * the component would like to be aligned relative to other
997     * components.  The value should be a number between 0 and 1
998     * where 0 represents alignment along the origin, 1 is aligned
999     * the furthest away from the origin, 0.5 is centered, etc.
1000     *
1001     * @param parent  the parent container
1002     * @return the value <code>0.5f</code> to indicate center alignment
1003     */
1004    @Override
1005    public float getLayoutAlignmentY(Container parent) {
1006        return 0.5f;
1007    }
1008
1009    /**
1010     * Invalidates the layout, indicating that if the layout manager
1011     * has cached information it should be discarded.
1012     *
1013     * @param target   the container that holds the layout to be invalidated
1014     */
1015    @Override
1016    public void invalidateLayout(Container target) {
1017        invalidateCaches();
1018    }
1019
1020    /**
1021     * Lays out the specified container using this form layout.  This method
1022     * reshapes components in the specified container in order to satisfy the
1023     * constraints of this <code>FormLayout</code> object.<p>
1024     *
1025     * Most applications do not call this method directly.<p>
1026     *
1027     * The form layout performs the following steps:
1028     * <ol>
1029     * <li>find components that occupy exactly one column or row
1030     * <li>compute minimum widths and heights
1031     * <li>compute preferred widths and heights
1032     * <li>give cols and row equal size if they share a group
1033     * <li>compress default columns and rows if total is less than pref size
1034     * <li>give cols and row equal size if they share a group
1035     * <li>distribute free space
1036     * <li>set components bounds
1037     * </ol>
1038     *
1039     * @param parent        the container in which to do the layout
1040     * @see Container
1041     * @see Container#doLayout()
1042     */
1043    @Override
1044    public void layoutContainer(Container parent) {
1045        synchronized (parent.getTreeLock()) {
1046            initializeColAndRowComponentLists();
1047            Dimension size = parent.getSize();
1048
1049            Insets insets = parent.getInsets();
1050            int totalWidth = size.width - insets.left - insets.right;
1051            int totalHeight = size.height - insets.top - insets.bottom;
1052
1053            int[] x = computeGridOrigins(parent, totalWidth, insets.left,
1054                    colSpecs, colComponents, colGroupIndices,
1055                    minimumWidthMeasure, preferredWidthMeasure);
1056            int[] y = computeGridOrigins(parent, totalHeight, insets.top,
1057                    rowSpecs, rowComponents, rowGroupIndices,
1058                    minimumHeightMeasure, preferredHeightMeasure);
1059
1060            layoutComponents(x, y);
1061        }
1062    }
1063
1064    // Layout Algorithm *****************************************************
1065
1066    /**
1067     * Initializes two lists for columns and rows that hold a column's
1068     * or row's components that span only this column or row.<p>
1069     *
1070     * Iterates over all components and their associated constraints;
1071     * every component that has a column span or row span of 1
1072     * is put into the column's or row's component list.<p>
1073     *
1074     * As of the Forms version 1.0.x invisible components are not taken
1075     * into account when the container is layed out. See the TODO in the
1076     * JavaDoc class comment for details on this issue.
1077     */
1078    private void initializeColAndRowComponentLists() {
1079        colComponents = new LinkedList[getColumnCount()];
1080        for (int i = 0; i < getColumnCount(); i++) {
1081            colComponents[i] = new LinkedList();
1082        }
1083
1084        rowComponents = new LinkedList[getRowCount()];
1085        for (int i = 0; i < getRowCount(); i++) {
1086            rowComponents[i] = new LinkedList();
1087        }
1088
1089        for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) {
1090            Map.Entry entry = (Map.Entry) i.next();
1091            Component component = (Component) entry.getKey();
1092            if (!component.isVisible()) {
1093                continue;
1094            }
1095
1096            CellConstraints constraints = (CellConstraints) entry.getValue();
1097            if (constraints.gridWidth == 1) {
1098                colComponents[constraints.gridX - 1].add(component);
1099            }
1100
1101            if (constraints.gridHeight == 1) {
1102                rowComponents[constraints.gridY - 1].add(component);
1103            }
1104        }
1105    }
1106
1107    /**
1108     * Computes and returns the layout size of the given <code>parent</code>
1109     * container using the specified measures.
1110     *
1111     * @param parent   the container in which to do the layout
1112     * @param defaultWidthMeasure   the measure used to compute the default width
1113     * @param defaultHeightMeasure  the measure used to compute the default height
1114     * @return the layout size of the <code>parent</code> container
1115     */
1116    private Dimension computeLayoutSize(Container parent,
1117            Measure defaultWidthMeasure, Measure defaultHeightMeasure) {
1118        synchronized (parent.getTreeLock()) {
1119            initializeColAndRowComponentLists();
1120            int[] colWidths = maximumSizes(parent, colSpecs, colComponents,
1121                    minimumWidthMeasure, preferredWidthMeasure,
1122                    defaultWidthMeasure);
1123            int[] rowHeights = maximumSizes(parent, rowSpecs, rowComponents,
1124                    minimumHeightMeasure, preferredHeightMeasure,
1125                    defaultHeightMeasure);
1126            int[] groupedWidths = groupedSizes(colGroupIndices, colWidths);
1127            int[] groupedHeights = groupedSizes(rowGroupIndices, rowHeights);
1128
1129            // Convert sizes to origins.
1130            int[] xOrigins = computeOrigins(groupedWidths, 0);
1131            int[] yOrigins = computeOrigins(groupedHeights, 0);
1132
1133            int width1 = sum(groupedWidths);
1134            int height1 = sum(groupedHeights);
1135            int maxWidth = width1;
1136            int maxHeight = height1;
1137
1138            /*
1139             * Take components that span multiple columns or rows into account.
1140             * This shall be done if and only if a component spans an interval
1141             * that can grow.
1142             */
1143            // First computes the maximum number of cols/rows a component
1144            // can span without spanning a growing column.
1145            int[] maxFixedSizeColsTable = computeMaximumFixedSpanTable(
1146                    colSpecs);
1147            int[] maxFixedSizeRowsTable = computeMaximumFixedSpanTable(
1148                    rowSpecs);
1149
1150            for (Iterator i = constraintMap.entrySet().iterator(); i
1151                    .hasNext();) {
1152                Map.Entry entry = (Map.Entry) i.next();
1153                Component component = (Component) entry.getKey();
1154                if (!component.isVisible()) {
1155                    continue;
1156                }
1157
1158                CellConstraints constraints = (CellConstraints) entry
1159                        .getValue();
1160                if (constraints.gridWidth > 1
1161                        && constraints.gridWidth > maxFixedSizeColsTable[constraints.gridX
1162                                - 1]) {
1163                    //int compWidth = minimumWidthMeasure.sizeOf(component);
1164                    int compWidth = defaultWidthMeasure.sizeOf(component);
1165                    //int compWidth = preferredWidthMeasure.sizeOf(component);
1166                    int gridX1 = constraints.gridX - 1;
1167                    int gridX2 = gridX1 + constraints.gridWidth;
1168                    int lead = xOrigins[gridX1];
1169                    int trail = width1 - xOrigins[gridX2];
1170                    int myWidth = lead + compWidth + trail;
1171                    if (myWidth > maxWidth) {
1172                        maxWidth = myWidth;
1173                    }
1174                }
1175
1176                if (constraints.gridHeight > 1
1177                        && constraints.gridHeight > maxFixedSizeRowsTable[constraints.gridY
1178                                - 1]) {
1179                    //int compHeight = minimumHeightMeasure.sizeOf(component);
1180                    int compHeight = defaultHeightMeasure.sizeOf(component);
1181                    //int compHeight = preferredHeightMeasure.sizeOf(component);
1182                    int gridY1 = constraints.gridY - 1;
1183                    int gridY2 = gridY1 + constraints.gridHeight;
1184                    int lead = yOrigins[gridY1];
1185                    int trail = height1 - yOrigins[gridY2];
1186                    int myHeight = lead + compHeight + trail;
1187                    if (myHeight > maxHeight) {
1188                        maxHeight = myHeight;
1189                    }
1190                }
1191            }
1192            Insets insets = parent.getInsets();
1193            int width = maxWidth + insets.left + insets.right;
1194            int height = maxHeight + insets.top + insets.bottom;
1195            return new Dimension(width, height);
1196        }
1197    }
1198
1199    /**
1200     * Computes and returns the grid's origins.
1201     *
1202     * @param container         the layout container
1203     * @param totalSize         the total size to assign
1204     * @param offset                     the offset from left or top margin
1205     * @param formSpecs             the column or row specs, resp.
1206     * @param componentLists        the components list for each col/row
1207     * @param minMeasure                the measure used to determine min sizes
1208     * @param prefMeasure                the measure used to determine pre sizes
1209     * @param groupIndices                the group specification
1210     * @return an int array with the origins
1211     */
1212    private int[] computeGridOrigins(Container container, int totalSize,
1213            int offset, List formSpecs, List[] componentLists,
1214            int[][] groupIndices, Measure minMeasure, Measure prefMeasure) {
1215        /* For each spec compute the minimum and preferred size that is
1216         * the maximum of all component minimum and preferred sizes resp.
1217         */
1218        int[] minSizes = maximumSizes(container, formSpecs, componentLists,
1219                minMeasure, prefMeasure, minMeasure);
1220        int[] prefSizes = maximumSizes(container, formSpecs, componentLists,
1221                minMeasure, prefMeasure, prefMeasure);
1222
1223        int[] groupedMinSizes = groupedSizes(groupIndices, minSizes);
1224        int[] groupedPrefSizes = groupedSizes(groupIndices, prefSizes);
1225        int totalMinSize = sum(groupedMinSizes);
1226        int totalPrefSize = sum(groupedPrefSizes);
1227        int[] compressedSizes = compressedSizes(formSpecs, totalSize,
1228                totalMinSize, totalPrefSize, groupedMinSizes, prefSizes);
1229        int[] groupedSizes = groupedSizes(groupIndices, compressedSizes);
1230        int totalGroupedSize = sum(groupedSizes);
1231        int[] sizes = distributedSizes(formSpecs, totalSize, totalGroupedSize,
1232                groupedSizes);
1233        return computeOrigins(sizes, offset);
1234    }
1235
1236    /**
1237     * Computes origins from sizes taking the specified offset into account.
1238     *
1239     * @param sizes     the array of sizes
1240     * @param offset    an offset for the first origin
1241     * @return an array of origins
1242     */
1243    private int[] computeOrigins(int[] sizes, int offset) {
1244        int count = sizes.length;
1245        int[] origins = new int[count + 1];
1246        origins[0] = offset;
1247        for (int i = 1; i <= count; i++) {
1248            origins[i] = origins[i - 1] + sizes[i - 1];
1249        }
1250        return origins;
1251    }
1252
1253    /**
1254     * Lays out the components using the given x and y origins, the column
1255     * and row specifications, and the component constraints.<p>
1256     *
1257     * The actual computation is done by each component's form constraint
1258     * object. We just compute the cell, the cell bounds and then hand over
1259     * the component, cell bounds, and measure to the form constraints.
1260     * This will allow potential subclasses of <code>CellConstraints</code>
1261     * to do special micro-layout corrections. For example, such a subclass
1262     * could map JComponent classes to visual layout bounds that may
1263     * lead to a slightly different bounds.
1264     *
1265     * @param x     an int array of the horizontal origins
1266     * @param y     an int array of the vertical origins
1267     */
1268    private void layoutComponents(int[] x, int[] y) {
1269        Rectangle cellBounds = new Rectangle();
1270        for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) {
1271            Map.Entry entry = (Map.Entry) i.next();
1272            Component component = (Component) entry.getKey();
1273            CellConstraints constraints = (CellConstraints) entry.getValue();
1274
1275            int gridX = constraints.gridX - 1;
1276            int gridY = constraints.gridY - 1;
1277            int gridWidth = constraints.gridWidth;
1278            int gridHeight = constraints.gridHeight;
1279            cellBounds.x = x[gridX];
1280            cellBounds.y = y[gridY];
1281            cellBounds.width = x[gridX + gridWidth] - cellBounds.x;
1282            cellBounds.height = y[gridY + gridHeight] - cellBounds.y;
1283
1284            constraints.setBounds(component, this, cellBounds,
1285                    minimumWidthMeasure, minimumHeightMeasure,
1286                    preferredWidthMeasure, preferredHeightMeasure);
1287        }
1288    }
1289
1290    /**
1291     * Invalidates the component size caches.
1292     */
1293    private void invalidateCaches() {
1294        componentSizeCache.invalidate();
1295    }
1296
1297    /**
1298     * Computes and returns the sizes for the given form specs, component
1299     * lists and measures fot minimum, preferred, and default size.
1300     *
1301     * @param container         the layout container
1302     * @param formSpecs         the column or row specs, resp.
1303     * @param componentLists    the components list for each col/row
1304     * @param minMeasure        the measure used to determine min sizes
1305     * @param prefMeasure       the measure used to determine pre sizes
1306     * @param defaultMeasure    the measure used to determine default sizes
1307     * @return the column or row sizes
1308     */
1309    private int[] maximumSizes(Container container, List formSpecs,
1310            List[] componentLists, Measure minMeasure, Measure prefMeasure,
1311            Measure defaultMeasure) {
1312        FormSpec formSpec;
1313        int size = formSpecs.size();
1314        int[] result = new int[size];
1315        for (int i = 0; i < size; i++) {
1316            formSpec = (FormSpec) formSpecs.get(i);
1317            result[i] = formSpec.maximumSize(container, componentLists[i],
1318                    minMeasure, prefMeasure, defaultMeasure);
1319        }
1320        return result;
1321    }
1322
1323    /**
1324     * Computes and returns the compressed sizes. Compresses space for columns
1325     * and rows iff the available space is less than the total preferred size
1326     * but more than the total minimum size.<p>
1327     *
1328     * Only columns and row that are specified to be compressible will be
1329     * affected. You can specify a column and row as compressible by
1330     * giving it the component size <tt>default</tt>.
1331     *
1332     * @param formSpecs      the column or row specs to use
1333     * @param totalSize      the total available size
1334     * @param totalMinSize   the sum of all minimum sizes
1335     * @param totalPrefSize  the sum of all preferred sizes
1336     * @param minSizes       an int array of column/row minimum sizes
1337     * @param prefSizes      an int array of column/row preferred sizes
1338     * @return an int array of compressed column/row sizes
1339     */
1340    private int[] compressedSizes(List formSpecs, int totalSize,
1341            int totalMinSize, int totalPrefSize, int[] minSizes,
1342            int[] prefSizes) {
1343
1344        // If we have less space than the total min size answer the min sizes.
1345        if (totalSize < totalMinSize) {
1346            return minSizes;
1347        }
1348        // If we have more space than the total pref size answer the pref sizes.
1349        if (totalSize >= totalPrefSize) {
1350            return prefSizes;
1351        }
1352
1353        int count = formSpecs.size();
1354        int[] sizes = new int[count];
1355
1356        double totalCompressionSpace = totalPrefSize - totalSize;
1357        double maxCompressionSpace = totalPrefSize - totalMinSize;
1358        double compressionFactor = totalCompressionSpace / maxCompressionSpace;
1359
1360        //      System.out.println("Total compression space=" + totalCompressionSpace);
1361        //      System.out.println("Max compression space  =" + maxCompressionSpace);
1362        //      System.out.println("Compression factor     =" + compressionFactor);
1363
1364        for (int i = 0; i < count; i++) {
1365            FormSpec formSpec = (FormSpec) formSpecs.get(i);
1366            sizes[i] = prefSizes[i];
1367            if (formSpec.getSize() == Sizes.DEFAULT) {
1368                sizes[i] -= (int) Math.round(
1369                        (prefSizes[i] - minSizes[i]) * compressionFactor);
1370            }
1371        }
1372        return sizes;
1373    }
1374
1375    /**
1376     * Computes and returns the grouped sizes.
1377     * Gives grouped columns and rows the same size.
1378     *
1379     * @param groups        the group specification
1380     * @param rawSizes        the raw sizes before the grouping
1381     * @return the grouped sizes
1382     */
1383    private int[] groupedSizes(int[][] groups, int[] rawSizes) {
1384        // Return the compressed sizes if there are no groups.
1385        if (groups == null || groups.length == 0) {
1386            return rawSizes;
1387        }
1388
1389        // Initialize the result with the given compressed sizes.
1390        int[] sizes = new int[rawSizes.length];
1391        for (int i = 0; i < sizes.length; i++) {
1392            sizes[i] = rawSizes[i];
1393        }
1394
1395        // For each group equalize the sizes.
1396        for (int[] groupIndices : groups) {
1397            int groupMaxSize = 0;
1398            // Compute the group's maximum size.
1399            for (int groupIndice : groupIndices) {
1400                int index = groupIndice - 1;
1401                groupMaxSize = Math.max(groupMaxSize, sizes[index]);
1402            }
1403            // Set all sizes of this group to the group's maximum size.
1404            for (int groupIndice : groupIndices) {
1405                int index = groupIndice - 1;
1406                sizes[index] = groupMaxSize;
1407            }
1408        }
1409        return sizes;
1410    }
1411
1412    /**
1413     * Distributes free space over columns and rows and
1414     * returns the sizes after this distribution process.
1415     *
1416     * @param formSpecs      the column/row specifications to work with
1417     * @param totalSize      the total available size
1418     * @param totalPrefSize  the sum of all preferred sizes
1419     * @param inputSizes     the input sizes
1420     * @return the distributed sizes
1421     */
1422    private int[] distributedSizes(List formSpecs, int totalSize,
1423            int totalPrefSize, int[] inputSizes) {
1424        double totalFreeSpace = totalSize - totalPrefSize;
1425        // Do nothing if there's no free space.
1426        if (totalFreeSpace < 0) {
1427            return inputSizes;
1428        }
1429
1430        // Compute the total weight.
1431        int count = formSpecs.size();
1432        double totalWeight = 0.0;
1433        for (int i = 0; i < count; i++) {
1434            FormSpec formSpec = (FormSpec) formSpecs.get(i);
1435            totalWeight += formSpec.getResizeWeight();
1436        }
1437
1438        // Do nothing if there's no resizing column.
1439        if (totalWeight == 0.0) {
1440            return inputSizes;
1441        }
1442
1443        int[] sizes = new int[count];
1444
1445        double restSpace = totalFreeSpace;
1446        int roundedRestSpace = (int) totalFreeSpace;
1447        for (int i = 0; i < count; i++) {
1448            FormSpec formSpec = (FormSpec) formSpecs.get(i);
1449            double weight = formSpec.getResizeWeight();
1450            if (weight == FormSpec.NO_GROW) {
1451                sizes[i] = inputSizes[i];
1452            } else {
1453                double roundingCorrection = restSpace - roundedRestSpace;
1454                double extraSpace = totalFreeSpace * weight / totalWeight;
1455                double correctedExtraSpace = extraSpace - roundingCorrection;
1456                int roundedExtraSpace = (int) Math.round(correctedExtraSpace);
1457                sizes[i] = inputSizes[i] + roundedExtraSpace;
1458                restSpace -= extraSpace;
1459                roundedRestSpace -= roundedExtraSpace;
1460            }
1461        }
1462        return sizes;
1463    }
1464
1465    /**
1466     * Computes and returns the sum of integers in the given array of ints.
1467     *
1468     * @param sizes           an array of ints to sum up
1469     * @return the sum of ints in the array
1470     */
1471    private int sum(int[] sizes) {
1472        int sum = 0;
1473        for (int i = sizes.length - 1; i >= 0; i--) {
1474            sum += sizes[i];
1475        }
1476        return sum;
1477    }
1478
1479    /**
1480     * Computes and returns a table that maps a column/row index
1481     * to the maximum number of columns/rows that a component can span
1482     * without spanning a growing column.<p>
1483     *
1484     * Iterates over the specs from right to left/bottom to top,
1485     * sets the table value to zero if a spec can grow,
1486     * otherwise increases the span by one.<p>
1487     *
1488     * <strong>Examples:</strong><pre>
1489     * "pref, 4dlu, pref, 2dlu, p:grow, 2dlu,      pref" ->
1490     * [4,    3,    2,    1,    0,      MAX_VALUE, MAX_VALUE]
1491     *
1492     * "p:grow, 4dlu, p:grow, 9dlu,      pref" ->
1493     * [0,      1,    0,      MAX_VALUE, MAX_VALUE]
1494     *
1495     * "p, 4dlu, p, 2dlu, 0:grow" ->
1496     * [4, 3,    2, 1,    0]
1497     * </pre>
1498     *
1499     * @param formSpecs  the column specs or row specs
1500     * @return a table that maps a spec index to the maximum span for
1501     *    fixed size specs
1502     */
1503    private int[] computeMaximumFixedSpanTable(List formSpecs) {
1504        int size = formSpecs.size();
1505        int[] table = new int[size];
1506        int maximumFixedSpan = Integer.MAX_VALUE; // Could be 1
1507        for (int i = size - 1; i >= 0; i--) {
1508            FormSpec spec = (FormSpec) formSpecs.get(i); // ArrayList access
1509            if (spec.canGrow()) {
1510                maximumFixedSpan = 0;
1511            }
1512            table[i] = maximumFixedSpan;
1513            if (maximumFixedSpan < Integer.MAX_VALUE) {
1514                maximumFixedSpan++;
1515            }
1516        }
1517        return table;
1518    }
1519
1520    // Measuring Component Sizes ********************************************
1521
1522    /**
1523     * An interface that describes how to measure a <code>Component</code>.
1524     * Used to abstract from horizontal and vertical dimensions as well as
1525     * minimum and preferred sizes.
1526     */
1527    static interface Measure {
1528
1529        /**
1530         * Computes and returns the size of the given <code>Component</code>.
1531         *
1532         * @param component  the component to measure
1533         * @return the component's size
1534         */
1535        int sizeOf(Component component);
1536    }
1537
1538    /**
1539     * An abstract implementation of the <code>Measure</code> interface
1540     * that caches component sizes.
1541     */
1542    private abstract static class CachingMeasure
1543            implements Measure, Serializable {
1544
1545        /**
1546         * Holds previously requested component sizes.
1547         * Used to minimize size requests to subcomponents.
1548         */
1549        protected final ComponentSizeCache cache;
1550
1551        private CachingMeasure(ComponentSizeCache cache) {
1552            this.cache = cache;
1553        }
1554
1555    }
1556
1557    /**
1558     * Measures a component by computing its minimum width.
1559     */
1560    private static final class MinimumWidthMeasure extends CachingMeasure {
1561        private MinimumWidthMeasure(ComponentSizeCache cache) {
1562            super(cache);
1563        }
1564
1565        @Override
1566        public int sizeOf(Component c) {
1567            return cache.getMinimumSize(c).width;
1568        }
1569    }
1570
1571    /**
1572     * Measures a component by computing its minimum height.
1573     */
1574    private static final class MinimumHeightMeasure extends CachingMeasure {
1575        private MinimumHeightMeasure(ComponentSizeCache cache) {
1576            super(cache);
1577        }
1578
1579        @Override
1580        public int sizeOf(Component c) {
1581            return cache.getMinimumSize(c).height;
1582        }
1583    }
1584
1585    /**
1586     * Measures a component by computing its preferred width.
1587     */
1588    private static final class PreferredWidthMeasure extends CachingMeasure {
1589        private PreferredWidthMeasure(ComponentSizeCache cache) {
1590            super(cache);
1591        }
1592
1593        @Override
1594        public int sizeOf(Component c) {
1595            return cache.getPreferredSize(c).width;
1596        }
1597    }
1598
1599    /**
1600     * Measures a component by computing its preferred height.
1601     */
1602    private static final class PreferredHeightMeasure extends CachingMeasure {
1603        private PreferredHeightMeasure(ComponentSizeCache cache) {
1604            super(cache);
1605        }
1606
1607        @Override
1608        public int sizeOf(Component c) {
1609            return cache.getPreferredSize(c).height;
1610        }
1611    }
1612
1613    // Caching Component Sizes **********************************************
1614
1615    /**
1616     * A cache for component minimum and preferred sizes.
1617     * Used to reduce the requests to determine a component's size.
1618     */
1619    private static final class ComponentSizeCache implements Serializable {
1620
1621        /** Maps components to their minimum sizes.  */
1622        private final Map minimumSizes;
1623
1624        /** Maps components to their preferred sizes. */
1625        private final Map preferredSizes;
1626
1627        /**
1628         * Constructs a <code>ComponentSizeCache</code>.
1629         *
1630         * @param initialCapacity        the initial cache capacity
1631         */
1632        private ComponentSizeCache(int initialCapacity) {
1633            minimumSizes = new HashMap(initialCapacity);
1634            preferredSizes = new HashMap(initialCapacity);
1635        }
1636
1637        /**
1638         * Invalidates the cache. Clears all stored size information.
1639         */
1640        void invalidate() {
1641            minimumSizes.clear();
1642            preferredSizes.clear();
1643        }
1644
1645        /**
1646         * Returns the minimum size for the given component. Tries to look up
1647         * the value from the cache; lazily creates the value if it has not
1648         * been requested before.
1649         *
1650         * @param component        the component to compute the minimum size
1651         * @return the component's minimum size
1652         */
1653        Dimension getMinimumSize(Component component) {
1654            Dimension size = (Dimension) minimumSizes.get(component);
1655            if (size == null) {
1656                size = component.getMinimumSize();
1657                minimumSizes.put(component, size);
1658            }
1659            return size;
1660        }
1661
1662        /**
1663         * Returns the preferred size for the given component. Tries to look
1664         * up the value from the cache; lazily creates the value if it has not
1665         * been requested before.
1666         *
1667         * @param component         the component to compute the preferred size
1668         * @return the component's preferred size
1669         */
1670        Dimension getPreferredSize(Component component) {
1671            Dimension size = (Dimension) preferredSizes.get(component);
1672            if (size == null) {
1673                size = component.getPreferredSize();
1674                preferredSizes.put(component, size);
1675            }
1676            return size;
1677        }
1678
1679        void removeEntry(Component component) {
1680            minimumSizes.remove(component);
1681            preferredSizes.remove(component);
1682        }
1683    }
1684
1685    // Exposing the Layout Information **************************************
1686
1687    /**
1688     * Computes and returns the horizontal and vertical grid origins.
1689     * Performs the same layout process as <code>#layoutContainer</code>
1690     * but does not layout the components.<p>
1691     *
1692     * This method has been added only to make it easier to debug
1693     * the form layout. <strong>You must not call this method directly;
1694     * It may be removed in a future release or the visibility
1695     * may be reduced.</strong>
1696     *
1697     * @param parent   the <code>Container</code> to inspect
1698     * @return an object that comprises the grid x and y origins
1699     */
1700    public LayoutInfo getLayoutInfo(Container parent) {
1701        synchronized (parent.getTreeLock()) {
1702            initializeColAndRowComponentLists();
1703            Dimension size = parent.getSize();
1704
1705            Insets insets = parent.getInsets();
1706            int totalWidth = size.width - insets.left - insets.right;
1707            int totalHeight = size.height - insets.top - insets.bottom;
1708
1709            int[] x = computeGridOrigins(parent, totalWidth, insets.left,
1710                    colSpecs, colComponents, colGroupIndices,
1711                    minimumWidthMeasure, preferredWidthMeasure);
1712            int[] y = computeGridOrigins(parent, totalHeight, insets.top,
1713                    rowSpecs, rowComponents, rowGroupIndices,
1714                    minimumHeightMeasure, preferredHeightMeasure);
1715            return new LayoutInfo(x, y);
1716        }
1717    }
1718
1719    /**
1720     * Stores column and row origins.
1721     */
1722    public static final class LayoutInfo {
1723
1724        /**
1725         * Holds the origins of the columns.
1726         */
1727        public final int[] columnOrigins;
1728
1729        /**
1730         * Holds the origins of the rows.
1731         */
1732        public final int[] rowOrigins;
1733
1734        private LayoutInfo(int[] xOrigins, int[] yOrigins) {
1735            this.columnOrigins = xOrigins;
1736            this.rowOrigins = yOrigins;
1737        }
1738
1739        /**
1740         * Returns the layout's horizontal origin, the origin of the first column.
1741         *
1742         * @return the layout's horizontal origin, the origin of the first column.
1743         */
1744        public int getX() {
1745            return columnOrigins[0];
1746        }
1747
1748        /**
1749         * Returns the layout's vertical origin, the origin of the first row.
1750         *
1751         * @return the layout's vertical origin, the origin of the first row.
1752         */
1753        public int getY() {
1754            return rowOrigins[0];
1755        }
1756
1757        /**
1758         * Returns the layout's width, the size between the first and the last
1759         * column origin.
1760         *
1761         * @return the layout's width.
1762         */
1763        public int getWidth() {
1764            return columnOrigins[columnOrigins.length - 1] - columnOrigins[0];
1765        }
1766
1767        /**
1768         * Returns the layout's height, the size between the first and last row.
1769         *
1770         * @return the layout's height.
1771         */
1772        public int getHeight() {
1773            return rowOrigins[rowOrigins.length - 1] - rowOrigins[0];
1774        }
1775
1776    }
1777
1778    // Helper Code **********************************************************
1779
1780    /**
1781     * Creates and returns a deep copy of the given array.
1782     * Unlike <code>#clone</code> that performs a shallow copy,
1783     * this method copies both array levels.
1784     *
1785     * @param array   the array to clone
1786     * @return a deep copy of the given array
1787     *
1788     * @see Object#clone()
1789     */
1790    private int[][] deepClone(int[][] array) {
1791        int[][] result = new int[array.length][];
1792        for (int i = 0; i < result.length; i++) {
1793            result[i] = array[i].clone();
1794        }
1795        return result;
1796    }
1797
1798    // Serialization ********************************************************
1799
1800    /**
1801     * In addition to the default serialization mechanism this class
1802     * invalidates the component size cache. The cache will be populated
1803     * again after the deserialization.
1804     * Also, the fields <code>colComponents</code> and
1805     * <code>rowComponents</code> have been marked as transient
1806     * to exclude them from the serialization.
1807     */
1808    private void writeObject(ObjectOutputStream out) throws IOException {
1809        invalidateCaches();
1810        out.defaultWriteObject();
1811    }
1812
1813    // Debug Helper Code ****************************************************
1814
1815    /*
1816    // Prints the given column widths and row heights.
1817    private void printSizes(String title, int[] colWidths, int[] rowHeights) {
1818        System.out.println();
1819        System.out.println(title);
1820        int totalWidth = 0;
1821        System.out.print("Column widths: ");
1822        for (int i=0; i < getColumnCount(); i++) {
1823            int width = colWidths[i];
1824            totalWidth += width;
1825            System.out.print(width + ", ");
1826        }
1827        System.out.println(" Total=" + totalWidth);
1828
1829        int totalHeight = 0;
1830        System.out.print("Row heights:   ");
1831        for (int i=0; i < getRowCount(); i++) {
1832            int height = rowHeights[i];
1833            totalHeight += height;
1834            System.out.print(height + ", ");
1835        }
1836        System.out.println(" Total=" + totalHeight);
1837        System.out.println();
1838    }
1839
1840     */
1841
1842}