KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jgoodies > forms > layout > FormLayout


1 /*
2  * Copyright (c) 2003 JGoodies Karsten Lentzsch. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * o Redistributions of source code must retain the above copyright notice,
8  * this list of conditions and the following disclaimer.
9  *
10  * o Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * o Neither the name of JGoodies Karsten Lentzsch nor the names of
15  * its contributors may be used to endorse or promote products derived
16  * from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */

30
31 package com.jgoodies.forms.layout;
32
33 import java.awt.Component JavaDoc;
34 import java.awt.Container JavaDoc;
35 import java.awt.Dimension JavaDoc;
36 import java.awt.Insets JavaDoc;
37 import java.awt.LayoutManager2 JavaDoc;
38 import java.awt.Rectangle JavaDoc;
39 import java.util.*;
40
41
42 /**
43  * FormLayout is a powerful, flexible and precise general purpose
44  * layout manager. It aligns components vertically and horizontally in
45  * a dynamic rectangular grid of cells, with each component occupying one or
46  * more cells.
47  * A <a HREF="../../../../../whitepaper.pdf" target="secondary">whitepaper</a>
48  * about the FormLayout ships with the product documentation and is available
49  * <a HREF="http://www.jgoodies.com/articles/forms.pdf">online</a>.
50  * <p>
51  * To use <code>FormLayout</code> you first define the grid by specifying the
52  * columns and rows. In a second step you add components to the grid. You can
53  * specify columns and rows via human-readable String descriptions or via
54  * arrays of {@link ColumnSpec} and {@link RowSpec} instances.
55  * <p>
56  * Each component managed by a FormLayout is associated with an instance of
57  * {@link CellConstraints}. The constraints object specifies where a component
58  * should be located on the form's grid and how the component should be
59  * positioned. In addition to its constraints object the
60  * <code>FormLayout</code> also considers each component's minimum and
61  * preferred sizes in order to determine a component's size.
62  * <p>
63  * FormLayout has been designed to work with non-visual builders that help you
64  * specify the layout and fill the grid. For example, the
65  * {@link com.jgoodies.forms.builder.ButtonBarBuilder} assists you in building button
66  * bars; it creates a standardized FormLayout and provides a minimal API that
67  * specializes in adding buttons. Other builders can create frequently used
68  * panel design, for example a form that consists of rows of label-component
69  * pairs.
70  * <p>
71  * FormLayout has been prepared to work with different types of sizes as
72  * defined by the {@link Size} interface.
73  * <p>
74  * <strong>Example 1</strong> (Plain FormLayout):<br>
75  * The following example creates a panel with 3 data columns and 3 data rows;
76  * the columns and rows are specified before components are added
77  * to the form.
78  * <pre>
79  * FormLayout layout = new FormLayout(
80  * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns
81  * "pref, 3dlu, pref, 3dlu, pref"); // rows
82  *
83  * CellConstraints cc = new CellConstraints();
84  * JPanel panel = new JPanel(layout);
85  * panel.add(new JLabel("Label1"), cc.xy (1, 1));
86  * panel.add(new JTextField(), cc.xywh(3, 1, 3, 1));
87  * panel.add(new JLabel("Label2"), cc.xy (1, 3));
88  * panel.add(new JTextField(), cc.xy (3, 3));
89  * panel.add(new JLabel("Label3"), cc.xy (1, 5));
90  * panel.add(new JTextField(), cc.xy (3, 5));
91  * panel.add(new JButton("..."), cc.xy (5, 5));
92  * return panel;
93  * </pre>
94  * <p>
95  * <strong>Example 2</strong> (Using PanelBuilder):<br>
96  * This example creates the same panel as above using the
97  * {@link com.jgoodies.forms.builder.PanelBuilder} to add components to the form.
98  * <pre>
99  * FormLayout layout = new FormLayout(
100  * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns
101  * "pref, 3dlu, pref, 3dlu, pref"); // rows
102  *
103  * PanelBuilder builder = new PanelBuilder(layout);
104  * CellConstraints cc = new CellConstraints();
105  * builder.addLabel("Label1", cc.xy (1, 1));
106  * builder.add(new JTextField(), cc.xywh(3, 1, 3, 1));
107  * builder.addLabel("Label2", cc.xy (1, 3));
108  * builder.add(new JTextField(), cc.xy (3, 3));
109  * builder.addLabel("Label3", cc.xy (1, 5));
110  * builder.add(new JTextField(), cc.xy (3, 5));
111  * builder.add(new JButton("..."), cc.xy (5, 5));
112  * return builder.getPanel();
113  * </pre>
114  * <p>
115  * <strong>Example 3</strong> (Using DefaultFormBuilder):<br>
116  * This example utilizes the
117  * {@link com.jgoodies.forms.extras.DefaultFormBuilder} that
118  * ships with the source distribution.
119  * <pre>
120  * FormLayout layout = new FormLayout(
121  * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns
122  * ""); // add rows dynamically
123  *
124  * DefaultFormBuilder builder = new DefaultFormBuilder(layout);
125  * builder.append("Label1", new JTextField(), 3);
126  * builder.append("Label2", new JTextField());
127  * builder.append("Label3", new JTextField());
128  * builder.append(new JButton("..."));
129  * return builder.getPanel();
130  * </pre>
131  *
132  * @author Karsten Lentzsch
133  * @version $Revision: 1.5 $
134  * @see ColumnSpec
135  * @see RowSpec
136  * @see CellConstraints
137  * @see com.jgoodies.forms.builder.AbstractFormBuilder
138  * @see com.jgoodies.forms.builder.ButtonBarBuilder
139  * @see com.jgoodies.forms.factories.FormFactory
140  * @see Size
141  * @see Sizes
142  */

143
144 public final class FormLayout implements LayoutManager2 JavaDoc {
145
146     /**
147      * Holds the column specifications.
148      * @see ColumnSpec
149      */

150     private final List colSpecs;
151
152     /**
153      * Holds the row specifications.
154      * @see RowSpec
155      */

156     private final List rowSpecs;
157
158     /**
159      * Holds the column groups as an array of arrays of column indices.
160      * @see #setColGroupIndices
161      */

162     private int[][] colGroupIndices;
163
164     /**
165      * Holds the row groups as an array of arrays of row indices.
166      * @see #setRowGroupIndices
167      */

168     private int[][] rowGroupIndices;
169
170     /**
171      * Maps components to their associated <code>CellConstraints</code>.
172      * @see CellConstraints
173      */

174     private final Map constraintMap;
175
176
177     // Fields used by the Layout Algorithm **********************************
178

179     /**
180      * Holds the components that occupy exactly one column.
181      * For each column we keep a list of these components.
182      */

183     private List[] colComponents;
184
185     /**
186      * Holds the components that occupy exactly one row.
187      * For each row we keep a list of these components.
188      */

189     private List[] rowComponents;
190     
191     /**
192      * Caches component minimum and preferred sizes.
193      * All requests for component sizes shall be directed to the cache.
194      */

195     private final ComponentSizeCache componentSizeCache;
196     
197     /**
198      * These functional objects are used to measure component sizes.
199      * They abstract from horizontal and vertical orientation and so,
200      * allow to implement the layout algorithm for both orientations with a
201      * single set of methods.
202      */

203     private final Measure minimumWidthMeasure;
204     private final Measure minimumHeightMeasure;
205     private final Measure preferredWidthMeasure;
206     private final Measure preferredHeightMeasure;
207
208  
209     // Instance Creation ****************************************************
210

211     /**
212      * Constructs an instance of <code>FormLayout</code> using the
213      * given column and row specifications.
214      *
215      * @param colSpecs an array of column specifications.
216      * @param rowSpecs an array of row specifications.
217      * @throws NullPointerException if colSpecs or rowSpecs is null
218      */

219     public FormLayout(ColumnSpec[] colSpecs, RowSpec[] rowSpecs) {
220         if (colSpecs == null)
221             throw new NullPointerException JavaDoc("Column specifications must not be null.");
222         if (rowSpecs == null)
223             throw new NullPointerException JavaDoc("Row specifications must not be null.");
224         
225         this.colSpecs = new ArrayList(Arrays.asList(colSpecs));
226         this.rowSpecs = new ArrayList(Arrays.asList(rowSpecs));
227         colGroupIndices = new int[][]{};
228         rowGroupIndices = new int[][]{};
229         int initialCapacity = colSpecs.length * rowSpecs.length / 4;
230         constraintMap = new HashMap(initialCapacity);
231         componentSizeCache = new ComponentSizeCache(initialCapacity);
232         minimumWidthMeasure = new MinimumWidthMeasure(componentSizeCache);
233         minimumHeightMeasure = new MinimumHeightMeasure(componentSizeCache);
234         preferredWidthMeasure = new PreferredWidthMeasure(componentSizeCache);
235         preferredHeightMeasure = new PreferredHeightMeasure(componentSizeCache);
236     }
237     
238     
239     /**
240      * Constructs an instance of <code>FormLayout</code> using the given
241      * encoded string representations for column and row specifications.
242      * <p>
243      * See the class comment for examples.
244      *
245      * @param encodedColumnSpecs comma separated encoded column specifications
246      * @param encodedRowSpecs comma separated encoded row specifications
247      * @throws NullPointerException if encodedColumnSpecs or encodedRowSpecs is null
248      */

249     public FormLayout(String JavaDoc encodedColumnSpecs, String JavaDoc encodedRowSpecs) {
250         this(decodeColSpecs(encodedColumnSpecs),
251              decodeRowSpecs(encodedRowSpecs));
252     }
253        
254     
255     /**
256      * Constructs an instance of <code>FormLayout</code> using the given
257      * encoded string representation for column specifications.
258      * The constructed layout has no rows; these must be added before
259      * any component can be added to the layout container.
260      * <p>
261      * This constructor is primarily intended to be used with builder classes
262      * that add rows dynamically, such as the <code>DefaultFormBuilder</code>.
263      * <p>
264      * See the class comment for examples.
265      *
266      * @param encodedColumnSpecs comma separated encoded column specifications
267      * @throws NullPointerException if encodedColumnSpecs is null
268      */

269     public FormLayout(String JavaDoc encodedColumnSpecs) {
270         this(encodedColumnSpecs, "");
271     }
272     
273     
274     // Accessing the Column and Row Specifications **************************
275

276     /**
277      * Returns the number of columns in this form layout.
278      *
279      * @return the number of columns
280      */

281     public int getColumnCount() {
282         return colSpecs.size();
283     }
284     
285     /**
286      * Returns the number of rows in this form layout.
287      *
288      * @return the number of rows
289      */

290     public int getRowCount() {
291         return rowSpecs.size();
292     }
293     
294     /**
295      * Returns the <code>ColumnSpec</code> at the specified column.
296      *
297      * @param columnIndex the column index of the requested <code>ColumnSpec</code>
298      * @return the <code>ColumnSpec</code> at the specified column
299      * @throws IndexOutOfBoundsException if the column index is out of range
300      */

301     public ColumnSpec getColumnSpec(int columnIndex) {
302         return (ColumnSpec) colSpecs.get(columnIndex - 1);
303     }
304
305     /**
306      * Returns the <code>RowSpec</code> at the specified row.
307      *
308      * @param rowIndex the row index of the requested <code>RowSpec</code>
309      * @return the <code>RowSpec</code> at the specified row
310      * @throws IndexOutOfBoundsException if the row index is out of range
311      */

312     public RowSpec getRowSpec(int rowIndex) {
313         return (RowSpec) rowSpecs.get(rowIndex - 1);
314     }
315
316     /**
317      * Appends the given column specification to the right hand side of all
318      * columns.
319      * @param columnSpec the column specification to be added
320      * @throws NullPointerException if the column specification is null
321      */

322     public void appendColumn(ColumnSpec columnSpec) {
323         if (columnSpec == null) {
324             throw new NullPointerException JavaDoc("The column spec must not be null.");
325         }
326         colSpecs.add(columnSpec);
327     }
328     
329     /**
330      * Inserts the specified column at the specified position. Shifts components
331      * that intersect the new column to the right hand side and readjusts
332      * column groups.
333      * <p>
334      * The component shift works as follows: components that were located on
335      * the right hand side of the inserted column are shifted one column to
336      * the right; component column span is increased by one if it intersects
337      * the new column.
338      * <p>
339      * Column group indices that are greater or equal than the given column
340      * index will be increased by one.
341      *
342      * @param columnIndex index of the column to be inserted
343      * @param columnSpec specification of the column to be inserted
344      * @throws IndexOutOfBoundsException if the column index is out of range
345      */

346     public void insertColumn(int columnIndex, ColumnSpec columnSpec) {
347         if (columnIndex < 1 || columnIndex > getColumnCount()) {
348             throw new IndexOutOfBoundsException JavaDoc(
349                     "The column index " + columnIndex +
350                     "must be in the range [1, " + getColumnCount() + "].");
351         }
352         colSpecs.add(columnIndex - 1, columnSpec);
353         shiftComponentsHorizontally(columnIndex, false);
354         adjustGroupIndices(colGroupIndices, columnIndex, false);
355     }
356     
357     
358     /**
359      * Removes the column with the given column index from the layout.
360      * Components will be rearranged and column groups will be readjusted.
361      * Therefore, the column must not contain components and must not be part
362      * of a column group.
363      * <p>
364      * The component shift works as follows: components that were located on
365      * the right hand side of the removed column are moved one column to the
366      * left; component column span is decreased by one if it intersects the
367      * removed column.
368      * <p>
369      * Column group indices that are greater than the column index will be
370      * decreased by one.
371      *
372      * @param columnIndex index of the column to remove
373      * @throws IndexOutOfBoundsException if the column index is out of range
374      * @throws IllegalStateException if the column contains components
375      * @throws IllegalStateException if the column is grouped
376      */

377     public void removeColumn(int columnIndex) {
378         if (columnIndex < 1 || columnIndex > getColumnCount()) {
379             throw new IndexOutOfBoundsException JavaDoc(
380                     "The column index " + columnIndex +
381                     " must be in the range [1, " + getColumnCount() + "].");
382         }
383         colSpecs.remove(columnIndex - 1);
384         shiftComponentsHorizontally(columnIndex, true);
385         adjustGroupIndices(colGroupIndices, columnIndex, true);
386     }
387     
388     /**
389      * Appends the given row specification to the bottom of all rows.
390      *
391      * @param rowSpec the row specification to be added to the form layout
392      * @throws NullPointerException if the rowSpec is null
393      */

394     public void appendRow(RowSpec rowSpec) {
395         if (rowSpec == null) {
396             throw new NullPointerException JavaDoc("The row spec must not be null.");
397         }
398         rowSpecs.add(rowSpec);
399     }
400
401     /**
402      * Inserts the specified column at the specified position. Shifts
403      * components that intersect the new column to the right and readjusts
404      * column groups.
405      * <p>
406      * The component shift works as follows: components that were located on
407      * the right hand side of the inserted column are shifted one column to
408      * the right; component column span is increased by one if it intersects
409      * the new column.
410      * <p>
411      * Column group indices that are greater or equal than the given column
412      * index will be increased by one.
413      *
414      * @param rowIndex index of the row to be inserted
415      * @param rowSpec specification of the row to be inserted
416      * @throws IndexOutOfBoundsException if the row index is out of range
417      */

418     public void insertRow(int rowIndex, RowSpec rowSpec) {
419         if (rowIndex < 1 || rowIndex > getRowCount()) {
420             throw new IndexOutOfBoundsException JavaDoc(
421                     "The row index " + rowIndex +
422                     " must be in the range [1, " + getRowCount() + "].");
423         }
424         rowSpecs.add(rowIndex - 1, rowSpec);
425         shiftComponentsVertically(rowIndex, false);
426         adjustGroupIndices(rowGroupIndices, rowIndex, false);
427     }
428         
429     /**
430      * Removes the row with the given row index from the layout. Components
431      * will be rearranged and row groups will be readjusted. Therefore, the
432      * row must not contain components and must not be part of a row group.
433      * <p>
434      * The component shift works as follows: components that were located
435      * below the removed row are moved up one row; component row span is
436      * decreased by one if it intersects the removed row.
437      * <p>
438      * Row group indices that are greater than the row index will be decreased
439      * by one.
440      *
441      * @param rowIndex index of the row to remove
442      * @throws IndexOutOfBoundsException if the row index is out of range
443      * @throws IllegalStateException if the row contains components
444      * @throws IllegalStateException if the row is grouped
445      */

446     public void removeRow(int rowIndex) {
447         if (rowIndex < 1 || rowIndex > getRowCount()) {
448             throw new IndexOutOfBoundsException JavaDoc(
449                     "The row index " + rowIndex +
450                     "must be in the range [1, " + getRowCount() + "].");
451         }
452         rowSpecs.remove(rowIndex - 1);
453         shiftComponentsVertically(rowIndex, true);
454         adjustGroupIndices(rowGroupIndices, rowIndex, true);
455     }
456     
457     
458     /**
459      * Shifts components horizontally, either to the left if a column has been
460      * inserted or the the right if a column has been removed.
461      *
462      * @param columnIndex index of the column to remove
463      * @param remove true for remove, false for insert
464      * @throws IllegalStateException if a removed column contains components
465      */

466     private void shiftComponentsHorizontally(int columnIndex, boolean remove) {
467         final int offset = remove ? -1 : 1;
468         for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext(); ) {
469             Map.Entry entry = (Map.Entry) i.next();
470             CellConstraints constraints = (CellConstraints) entry.getValue();
471             int x1 = constraints.gridX;
472             int w = constraints.gridWidth;
473             int x2 = x1 + w - 1;
474             if (x1 == columnIndex && remove) {
475                 throw new IllegalStateException JavaDoc(
476                     "The removed column " + columnIndex +
477                     " must not contain component origins.\n" +
478                     "Illegal component=" + entry.getKey());
479             } else if (x1 >= columnIndex) {
480                 constraints.gridX += offset;
481             } else if (x2 >= columnIndex) {
482                 constraints.gridWidth += offset;
483             }
484         }
485     }
486     
487     /**
488      * Shifts components horizontally, either to the left if a column has been
489      * inserted or the the right if a column has been removed.
490      *
491      * @param columnIndex index of the column to remove
492      * @param remove true for remove, false for insert
493      * @throws IllegalStateException if a removed column contains components
494      */

495     private void shiftComponentsVertically(int rowIndex, boolean remove) {
496         final int offset = remove ? -1 : 1;
497         for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext(); ) {
498             Map.Entry entry = (Map.Entry) i.next();
499             CellConstraints constraints = (CellConstraints) entry.getValue();
500             int y1 = constraints.gridY;
501             int h = constraints.gridHeight;
502             int y2 = y1 + h - 1;
503             if (y1 == rowIndex && remove) {
504                 throw new IllegalStateException JavaDoc(
505                     "The removed row " + rowIndex +
506                     " must not contain component origins.\n" +
507                     "Illegal component=" + entry.getKey());
508             } else if (y1 >= rowIndex) {
509                 constraints.gridY += offset;
510             } else if (y2 >= rowIndex) {
511                 constraints.gridHeight += offset;
512             }
513         }
514     }
515     
516     /**
517      * Adjusts group indices. Shifts the given groups to left, right, up,
518      * down according to the specified remove or add flag.
519      *
520      * @param allGroupIndices the groups to be adjusted
521      * @param modifiedIndex the modified column or row index
522      * @param remove true for remove, false for add
523      * @throws IllegalStateException if we remove and the index is grouped
524      */

525     private void adjustGroupIndices(int[][] allGroupIndices,
526                                      int modifiedIndex, boolean remove) {
527         final int offset = remove ? -1 : +1;
528         for (int group = 0; group < allGroupIndices.length; group++) {
529             int[] groupIndices = allGroupIndices[group];
530             for (int i = 0; i < groupIndices.length; i++) {
531                 int index = groupIndices[i];
532                 if (index == modifiedIndex && remove) {
533                     throw new IllegalStateException JavaDoc(
534                         "The removed index " + modifiedIndex + " must not be grouped.");
535                 } else if (index >= modifiedIndex) {
536                     groupIndices[i] += offset;
537                 }
538             }
539         }
540     }
541
542         
543     // Accessing Constraints ************************************************
544

545     /**
546      * Sets the constraints for the specified component in this layout.
547      *
548      * @param component the component to be modified
549      * @param constraints the constraints to be applied
550      * @throws NullPointerException if the component is null
551      * @throws NullPointerException if the constraints are null
552      */

553     public void setConstraints(Component JavaDoc component, CellConstraints constraints) {
554         if (component == null)
555             throw new NullPointerException JavaDoc("Component must not be null.");
556         if (constraints == null)
557             throw new NullPointerException JavaDoc("Constraint must not be null.");
558             
559         constraints.ensureValidGridBounds(getColumnCount(), getRowCount());
560         constraintMap.put(component, constraints.clone());
561     }
562     
563     /**
564      * Gets the constraints for the specified component. A copy of
565      * the actual <code>CellConstraints</code> object is returned.
566      *
567      * @param component the component to be queried
568      * @return the <code>CellConstraints</code> for the specified component
569      * @throws NullPointerException if component is null or has not been added
570      * to the Container
571      */

572     public CellConstraints getConstraints(Component JavaDoc component) {
573         if (component == null)
574             throw new NullPointerException JavaDoc("Component must not be null.");
575             
576         CellConstraints constraints = (CellConstraints) constraintMap.get(component);
577         if (constraints == null)
578             throw new NullPointerException JavaDoc("Component has not been added to the container.");
579             
580         return (CellConstraints) constraints.clone();
581     }
582     
583     /**
584      * Removes the constraints for the specified component in this layout.
585      *
586      * @param component the component to be modified
587      */

588     private void removeConstraints(Component JavaDoc component) {
589         constraintMap.remove(component);
590         componentSizeCache.removeEntry(component);
591     }
592     
593
594     // Accessing Column and Row Groups **************************************
595

596     /**
597      * Answers a deep copy of the column groups.
598      *
599      * @return the column groups as two-dimensional int array
600      */

601     public int[][] getColumnGroups() {
602         return deepClone(colGroupIndices);
603     }
604     
605     /**
606      * Sets the column groups, where each column in a group gets the same
607      * group wide width. Each group is described by an array of integers that
608      * are interpreted as column indices. The parameter is an array of such
609      * group descriptions.
610      * <p>
611      * Example: we build two groups, the first of columns 1, 3, and 4;
612      * and a second group for columns 7 and 9.
613      * <pre>
614      * setColumnGroups(new int[][]{ {1, 3, 4}, {7, 9}});
615      * </pre>
616      *
617      * @param colGroupIndices a two-dimensional array of column groups indices
618      * @throws IndexOutOfBoundsException if an index is outside the grid
619      * @throws IllegalArgumentException if a column index is used twice
620      */

621     public void setColumnGroups(int[][] colGroupIndices) {
622         int maxColumn = getColumnCount();
623         boolean[] usedIndices = new boolean[maxColumn + 1];
624         for (int group = 0; group < colGroupIndices.length; group++) {
625             for (int j = 0; j < colGroupIndices[group].length; j++) {
626                 int colIndex = colGroupIndices[group][j];
627                 if (colIndex < 1 || colIndex > maxColumn) {
628                     throw new IndexOutOfBoundsException JavaDoc(
629                         "Invalid column group index " + colIndex +
630                         " in group " + (group+1));
631                 }
632                 if (usedIndices[colIndex]) {
633                     throw new IllegalArgumentException JavaDoc(
634                         "Column index " + colIndex + " must not be used in multiple column groups.");
635                 } else {
636                     usedIndices[colIndex] = true;
637                 }
638             }
639         }
640         this.colGroupIndices = deepClone(colGroupIndices);
641     }
642     
643     /**
644      * Adds the specified column index to the last column group. In case there
645      * are no groups, a new group will be created.
646      *
647      * @param columnIndex the column index to be set grouped
648      */

649     public void addGroupedColumn(int columnIndex) {
650         int[][] newColGroups = getColumnGroups();
651         // Create a group if none exists.
652
if (newColGroups.length == 0) {
653             newColGroups = new int[][]{{columnIndex}};
654         } else {
655             int lastGroupIndex = newColGroups.length-1;
656             int[] lastGroup = newColGroups[lastGroupIndex];
657             int groupSize = lastGroup.length;
658             int[] newLastGroup = new int[groupSize+1];
659             System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize);
660             newLastGroup[groupSize] = columnIndex;
661             newColGroups[lastGroupIndex] = newLastGroup;
662         }
663         setColumnGroups(newColGroups);
664     }
665     
666     /**
667      * Answers a deep copy of the row groups.
668      *
669      * @return the row groups as two-dimensional int array
670      */

671     public int[][] getRowGroups() {
672         return deepClone(rowGroupIndices);
673     }
674     
675     /**
676      * Sets the row groups, where each row in such a group gets the same group
677      * wide height. Each group is described by an array of integers that are
678      * interpreted as row indices. The parameter is an array of such group
679      * descriptions.
680      * <p>
681      * Example: we build two groups, the first of rows 1, and 2,; and a second
682      * group for rows 5, 7, and 9.
683      * <pre>
684      * setRowGroups(new int[][]{ {1, 2}, {5, 7, 9}});
685      * </pre>
686      *
687      * @param rowGroupIndices a two-dimensional array of row group indices.
688      * @throws IndexOutOfBoundsException if an index is outside the grid
689      */

690     public void setRowGroups(int[][] rowGroupIndices) {
691         int rowCount = getRowCount();
692         boolean[] usedIndices = new boolean[rowCount + 1];
693         for (int i = 0; i < rowGroupIndices.length; i++) {
694             for (int j = 0; j < rowGroupIndices[i].length; j++) {
695                 int rowIndex = rowGroupIndices[i][j];
696                 if (rowIndex < 1 || rowIndex > rowCount) {
697                     throw new IndexOutOfBoundsException JavaDoc(
698                         "Invalid row group index " + rowIndex +
699                         " in group " + (i+1));
700                 }
701                 if (usedIndices[rowIndex]) {
702                     throw new IllegalArgumentException JavaDoc(
703                         "Row index " + rowIndex + " must not be used in multiple row groups.");
704                 } else {
705                     usedIndices[rowIndex] = true;
706                 }
707             }
708         }
709         this.rowGroupIndices = deepClone(rowGroupIndices);
710     }
711
712     /**
713      * Adds the specified row index to the last row group. In case there are
714      * no groups, a new group will be created.
715      *
716      * @param rowIndex the index of the row that should be grouped
717      */

718     public void addGroupedRow(int rowIndex) {
719         int[][] newRowGroups = getRowGroups();
720         // Create a group if none exists.
721
if (newRowGroups.length == 0) {
722             newRowGroups = new int[][]{{rowIndex}};
723         } else {
724             int lastGroupIndex = newRowGroups.length-1;
725             int[] lastGroup = newRowGroups[lastGroupIndex];
726             int groupSize = lastGroup.length;
727             int[] newLastGroup = new int[groupSize+1];
728             System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize);
729             newLastGroup[groupSize] = rowIndex;
730             newRowGroups[lastGroupIndex] = newLastGroup;
731         }
732         setRowGroups(newRowGroups);
733     }
734     
735
736     // Implementing the LayoutManager and LayoutManager2 Interfaces *********
737

738     /**
739      * Throws an <code>UnsupportedOperationException</code>. Does not add
740      * the specified component with the specified name to the layout.
741      *
742      * @param name indicates entry's position and anchor
743      * @param component component to add
744      * @throws UnsupportedOperationException always
745      */

746     public void addLayoutComponent(String JavaDoc name, Component JavaDoc component) {
747         throw new UnsupportedOperationException JavaDoc(
748                 "Use #addLayoutComponent(Component, Object) instead.");
749     }
750     
751     /**
752      * Adds the specified component to the layout, using the specified
753      * <code>constraints</code> object. Note that constraints are mutable and
754      * are, therefore, cloned when cached.
755      *
756      * @param comp the component to be added
757      * @param constraints the component's form layout constraints
758      * @throws NullPointerException if <code>constraints</code> is null
759      * @throws IllegalArgumentException if <code>constraints</code> is not a
760      * <code>CellConstraints</code> or a String that can be used to construct
761      * a <code>CellConstraints</code>
762      */

763     public void addLayoutComponent(Component JavaDoc comp, Object JavaDoc constraints) {
764         if (constraints instanceof String JavaDoc) {
765             setConstraints(comp, new CellConstraints((String JavaDoc) constraints));
766         } else if (constraints instanceof CellConstraints) {
767             setConstraints(comp, (CellConstraints) constraints);
768         } else if (constraints == null) {
769             throw new NullPointerException JavaDoc("Constraints must not be null.");
770         } else {
771             throw new IllegalArgumentException JavaDoc("Illegal constraint type " + constraints.getClass());
772         }
773     }
774
775     /**
776      * Removes the specified component from this layout.
777      * <p>
778      * Most applications do not call this method directly.
779      *
780      * @param comp the component to be removed.
781      * @see java.awt.Container#remove(java.awt.Component)
782      * @see java.awt.Container#removeAll()
783      */

784     public void removeLayoutComponent(Component JavaDoc comp) {
785         removeConstraints(comp);
786     }
787     
788
789     // Layout Requests ******************************************************
790

791     /**
792      * Determines the minimum size of the <code>parent</code> container
793      * using this form layout.
794      * <p>
795      * Most applications do not call this method directly.
796      * @param parent the container in which to do the layout
797      * @see java.awt.Container#doLayout
798      * @return the minimum size of the <code>parent</code> container
799      */

800     public Dimension JavaDoc minimumLayoutSize(Container JavaDoc parent) {
801         return computeLayoutSize(parent,
802                                  minimumWidthMeasure,
803                                  minimumHeightMeasure);
804     }
805
806     /**
807      * Determines the preferred size of the <code>parent</code>
808      * container using this form layout.
809      * <p>
810      * Most applications do not call this method directly.
811      *
812      * @param parent the container in which to do the layout
813      * @see java.awt.Container#getPreferredSize
814      * @return the preferred size of the <code>parent</code> container
815      */

816     public Dimension JavaDoc preferredLayoutSize(Container JavaDoc parent) {
817         return computeLayoutSize(parent,
818                                  preferredWidthMeasure,
819                                  preferredHeightMeasure);
820     }
821
822     /**
823      * Returns the maximum dimensions for this layout given the components
824      * in the specified target container.
825      *
826      * @param target the container which needs to be laid out
827      * @see Container
828      * @see #minimumLayoutSize(Container)
829      * @see #preferredLayoutSize(Container)
830      * @return the maximum dimensions for this layout
831      */

832     public Dimension JavaDoc maximumLayoutSize(Container JavaDoc target) {
833         return new Dimension JavaDoc(Integer.MAX_VALUE, Integer.MAX_VALUE);
834     }
835
836     /**
837      * Returns the alignment along the x axis. This specifies how
838      * the component would like to be aligned relative to other
839      * components. The value should be a number between 0 and 1
840      * where 0 represents alignment along the origin, 1 is aligned
841      * the furthest away from the origin, 0.5 is centered, etc.
842      *
843      * @return the value <code>0.5f</code> to indicate center alignment
844      */

845     public float getLayoutAlignmentX(Container JavaDoc parent) {
846         return 0.5f;
847     }
848
849     /**
850      * Returns the alignment along the y axis. This specifies how
851      * the component would like to be aligned relative to other
852      * components. The value should be a number between 0 and 1
853      * where 0 represents alignment along the origin, 1 is aligned
854      * the furthest away from the origin, 0.5 is centered, etc.
855      *
856      * @return the value <code>0.5f</code> to indicate center alignment
857      */

858     public float getLayoutAlignmentY(Container JavaDoc parent) {
859         return 0.5f;
860     }
861
862
863     /**
864      * Invalidates the layout, indicating that if the layout manager
865      * has cached information it should be discarded.
866      */

867     public void invalidateLayout(Container JavaDoc target) {
868         invalidateCaches();
869     }
870
871
872     /**
873      * Lays out the specified container using this form layout. This method
874      * reshapes components in the specified container in order to satisfy the
875      * contraints of this <code>FormLayout</code> object.
876      * <p>
877      * Most applications do not call this method directly.
878      * <p>
879      * The form layout performs the following steps:
880      * <ol>
881      * <li>find components that occupy exactly one column or row
882      * <li>compute minimum widths and heights
883      * <li>compute preferred widths and heights
884      * <li>give cols and row equal size if they share a group
885      * <li>compress default columns and rows if total is less than pref size
886      * <li>give cols and row equal size if they share a group
887      * <li>distribute free space
888      * <li>set components bounds
889      * </ol>
890      * @param parent the container in which to do the layout
891      * @see java.awt.Container
892      * @see java.awt.Container#doLayout
893      */

894     public void layoutContainer(Container JavaDoc parent) {
895         synchronized (parent.getTreeLock()) {
896             initializeColAndRowComponentLists();
897             Dimension JavaDoc size = parent.getSize();
898             
899             Insets JavaDoc insets = parent.getInsets();
900             int totalWidth