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 = size.width - insets.left - insets.right;
901             int totalHeight = size.height- insets.top - insets.bottom;
902
903             int[] x = computeGridOrigins(parent,
904                                          totalWidth, insets.left,
905                                          colSpecs,
906                                          colComponents,
907                                          colGroupIndices,
908                                          minimumWidthMeasure,
909                                          preferredWidthMeasure
910                                          );
911             int[] y = computeGridOrigins(parent,
912                                          totalHeight, insets.top,
913                                          rowSpecs,
914                                          rowComponents,
915                                          rowGroupIndices,
916                                          minimumHeightMeasure,
917                                          preferredHeightMeasure
918                                          );
919                                          
920             layoutComponents(x, y);
921         }
922     }
923     
924     // Layout Algorithm *****************************************************
925

926     /**
927      * Initializes two lists for columns and rows that hold a column's or
928      * row's components that span only this column or row.
929      * <p>
930      * Iterates over all components and their associated constraints; every
931      * component that has a column span or row span of 1 is put into the
932      * column's or row's component list.
933      */

934     private void initializeColAndRowComponentLists() {
935         colComponents = new LinkedList[getColumnCount()];
936         for (int i=0; i < getColumnCount(); i++) {
937             colComponents[i] = new LinkedList();
938         }
939
940         rowComponents = new LinkedList[getRowCount()];
941         for (int i=0; i < getRowCount(); i++) {
942             rowComponents[i] = new LinkedList();
943         }
944         
945         for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext(); ) {
946             Map.Entry entry = (Map.Entry) i.next();
947             Component JavaDoc component = (Component JavaDoc) entry.getKey();
948             if (!component.isVisible())
949                 continue;
950                 
951             CellConstraints constraints = (CellConstraints) entry.getValue();
952             if (constraints.gridWidth == 1)
953                 colComponents[constraints.gridX-1].add(component);
954             
955             if (constraints.gridHeight == 1)
956                 rowComponents[constraints.gridY-1].add(component);
957         }
958     }
959     
960
961     /**
962      * Computes and answers the layout size of the given <code>parent</code>
963      * container using the specified measures.
964      *
965      * @param parent the container in which to do the layout
966      * @return the layout size of the <code>parent</code> container
967      */

968     private Dimension JavaDoc computeLayoutSize(Container JavaDoc parent,
969                                          Measure defaultWidthMeasure,
970                                          Measure defaultHeightMeasure) {
971         synchronized (parent.getTreeLock()) {
972             initializeColAndRowComponentLists();
973             int[] colWidths = maximumSizes(parent, colSpecs, colComponents,
974                                             minimumWidthMeasure,
975                                             preferredWidthMeasure,
976                                             defaultWidthMeasure);
977             int[] rowHeights = maximumSizes(parent, rowSpecs, rowComponents,
978                                             minimumHeightMeasure,
979                                             preferredHeightMeasure,
980                                             defaultHeightMeasure);
981             int[] groupedWidths = groupedSizes(colGroupIndices, colWidths);
982             int[] groupedHeights = groupedSizes(rowGroupIndices, rowHeights);
983
984             // Convert sizes to origins.
985
int[] xOrigins = computeOrigins(groupedWidths, 0);
986             int[] yOrigins = computeOrigins(groupedHeights, 0);
987
988             int width1 = sum(groupedWidths);
989             int height1 = sum(groupedHeights);
990             int maxWidth = width1;
991             int maxHeight = height1;
992
993             /*
994              * Take components that span multiple columns or rows into account.
995              * This shall be done if and only if a component spans an interval
996              * that can grow.
997              * <strong>Note: </strong> The current algorithm doesn't reflect the
998              * above specification; it checks only if any column or row can grow.
999              */

1000            // First determine whether a column or row can grow:
1001
boolean canGrowHorizontally = containsSpecThatCanGrow(colSpecs);
1002            boolean canGrowVertically = containsSpecThatCanGrow(rowSpecs);
1003            
1004            for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext(); ) {
1005                Map.Entry entry = (Map.Entry) i.next();
1006                Component JavaDoc component = (Component JavaDoc) entry.getKey();
1007                if (!component.isVisible())
1008                    continue;
1009                
1010                CellConstraints constraints = (CellConstraints) entry.getValue();
1011                if (canGrowHorizontally && (constraints.gridWidth > 1)) {
1012                    //int compWidth = minimumWidthMeasure.sizeOf(component);
1013
int compWidth = defaultWidthMeasure.sizeOf(component);
1014                    //int compWidth = preferredWidthMeasure.sizeOf(component);
1015
int gridX1 = constraints.gridX-1;
1016                    int gridX2 = gridX1 + constraints.gridWidth;
1017                    int lead = xOrigins[gridX1];
1018                    int trail = width1 - xOrigins[gridX2];
1019                    int myWidth = lead + compWidth + trail;
1020                    if (myWidth > maxWidth) {
1021                        maxWidth = myWidth;
1022                    }
1023                }
1024            
1025                if (canGrowVertically && (constraints.gridHeight > 1)) {
1026                    //int compHeight = minimumHeightMeasure.sizeOf(component);
1027
int compHeight = defaultHeightMeasure.sizeOf(component);
1028                    //int compHeight = preferredHeightMeasure.sizeOf(component);
1029
int gridY1 = constraints.gridY-1;
1030                    int gridY2 = gridY1 + constraints.gridHeight;
1031                    int lead = yOrigins[gridY1];
1032                    int trail = height1 - yOrigins[gridY2];
1033                    int myHeight = lead + compHeight + trail;
1034                    if (myHeight > maxHeight) {
1035                        maxHeight = myHeight;
1036                    }
1037                }
1038            }
1039            Insets JavaDoc insets = parent.getInsets();
1040            int width = maxWidth + insets.left + insets.right;
1041            int height = maxHeight + insets.top + insets.bottom;
1042            return new Dimension JavaDoc(width, height);
1043        }
1044    }
1045
1046    /**
1047     * Computes and answers the grid's origins.
1048     *
1049     * @param container the layout container
1050     * @param totalSize the total size to assign
1051     * @param offset the offset from left or top margin
1052     * @param formSpecs the column or row specs, resp.
1053     * @param componentLists the components list for each col/row
1054     * @param minMeasure the measure used to determin min sizes
1055     * @param prefMeasure the measure used to determin pre sizes
1056     * @param groupIndices the group specification
1057     * @return an int array with the origins
1058     */

1059    private int[] computeGridOrigins(Container JavaDoc container,
1060                                      int totalSize, int offset,
1061                                      List formSpecs,
1062                                      List[] componentLists,
1063                                      int[][] groupIndices,
1064                                      Measure minMeasure,
1065                                      Measure prefMeasure) {
1066        /* For each spec compute the minimum and preferred size that is
1067         * the maximum of all component minimum and preferred sizes resp.
1068         */

1069        int[] minSizes = maximumSizes(container, formSpecs, componentLists,
1070                                        minMeasure, prefMeasure, minMeasure);
1071        int[] prefSizes = maximumSizes(container, formSpecs, componentLists,
1072                                        minMeasure, prefMeasure, prefMeasure);
1073
1074        int[] groupedMinSizes = groupedSizes(groupIndices, minSizes);
1075        int[] groupedPrefSizes = groupedSizes(groupIndices, prefSizes);
1076        int totalMinSize = sum(groupedMinSizes);
1077        int totalPrefSize = sum(groupedPrefSizes);
1078        int[] compressedSizes = compressedSizes(formSpecs,
1079                                               totalSize,
1080                                               totalMinSize,
1081                                               totalPrefSize,
1082                                               groupedMinSizes,
1083                                               prefSizes);
1084        int[] groupedSizes = groupedSizes(groupIndices, compressedSizes);
1085        int totalGroupedSize = sum(groupedSizes);
1086        int[] sizes = distributedSizes(formSpecs,
1087                                                 totalSize,
1088                                                 totalGroupedSize,
1089                                                 groupedSizes);
1090        return computeOrigins(sizes, offset);
1091    }
1092    
1093
1094    /**
1095     * Computes origins from sizes taking the specified offset into account.
1096     *
1097     * @param sizes the array of sizes
1098     * @param offset an offset for the first origin
1099     * @return an array of origins
1100     */

1101    private int[] computeOrigins(int[] sizes, int offset) {
1102        int count = sizes.length;
1103        int origins[] = new int[count + 1];
1104        origins[0] = offset;
1105        for (int i=1; i <= count; i++) {
1106            origins[i] = origins[i-1] + sizes[i-1];
1107        }
1108        return origins;
1109    }
1110    
1111    /**
1112     * Lays out the components using the given x and y origins, the column and
1113     * row specifications, and the component constraints.
1114     * <p>
1115     * The actual computation is done by each component's form constraint
1116     * object. We just compute the cell, the cell bounds and then hand over
1117     * the component, cell bounds, and measure to the form constraints.
1118     * This will allow potential subclasses of <code>CellConstraints</code>
1119     * to do special micro-layout corrections. For example, such a subclass
1120     * could map JComponent classes to visual layout bounds that may
1121     * lead to a slightly different bounds.
1122     *
1123     * @param x an int array of the horizontal origins
1124     * @param y an int array of the vertical origins
1125     */

1126    private void layoutComponents(int[] x, int[] y) {
1127        Rectangle JavaDoc cellBounds = new Rectangle JavaDoc();
1128        for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext(); ) {
1129            Map.Entry entry = (Map.Entry) i.next();
1130            Component JavaDoc component = (Component JavaDoc) entry.getKey();
1131            CellConstraints constraints = (CellConstraints) entry.getValue();
1132            
1133            int gridX = constraints.gridX-1;
1134            int gridY = constraints.gridY-1;
1135            int gridWidth = constraints.gridWidth;
1136            int gridHeight = constraints.gridHeight;
1137            cellBounds.x = x[gridX];
1138            cellBounds.y = y[gridY];
1139            cellBounds.width = x[gridX + gridWidth ] - cellBounds.x;
1140            cellBounds.height = y[gridY + gridHeight] - cellBounds.y;
1141
1142            constraints.setBounds(component, this, cellBounds,
1143                            minimumWidthMeasure, minimumHeightMeasure,
1144                            preferredWidthMeasure, preferredHeightMeasure);
1145        }
1146    }
1147    
1148
1149    /**
1150     * Invalidates the component size caches.
1151     */

1152    private void invalidateCaches() {
1153        componentSizeCache.invalidate();
1154    }
1155    
1156    
1157    /**
1158     * Computes and answers the sizes for the given form specs, component
1159     * lists and measures fot minimum, preferred, and default size.
1160     *
1161     * @param container the layout container
1162     * @param formSpecs the column or row specs, resp.
1163     * @param componentLists the components list for each col/row
1164     * @param minMeasure the measure used to determin min sizes
1165     * @param prefMeasure the measure used to determin pre sizes
1166     * @param defaultMeasure the measure used to determin default sizes
1167     * @return the column or row sizes
1168     */

1169    private int[] maximumSizes(Container JavaDoc container,
1170                                List formSpecs,
1171                                List[] componentLists,
1172                                Measure minMeasure,
1173                                Measure prefMeasure,
1174                                Measure defaultMeasure) {
1175        FormSpec formSpec;
1176        int size = formSpecs.size();
1177        int result[] = new int[size];
1178        for (int i = 0; i < size; i++) {
1179            formSpec = (FormSpec) formSpecs.get(i);
1180            result[i] = formSpec.maximumSize(container,
1181                                             componentLists[i],
1182                                             minMeasure,
1183                                             prefMeasure,
1184                                             defaultMeasure);
1185        }
1186        return result;
1187    }
1188 
1189
1190    /**
1191     * Computes and answers the compressed sizes. Compresses space for columns
1192     * and rows iff the available space is less than the total preferred size
1193     * but more than the total minimum size.
1194     * <p>
1195     * Only columns and row that are specified to be compressable will be
1196     * affected. You can specify a column and row as compressable by
1197     * giving it the component size <tt>default</tt>.
1198     *
1199     * @param formSpecs the column or row specs to use
1200     * @param totalSize the total available size
1201     * @param totalMinSize the sum of all minimum sizes
1202     * @param totalPrefSize the sum of all preferred sizes
1203     * @param minSizes an int array of column/row minimum sizes
1204     * @param prefSizes an int array of column/row preferred sizes
1205     * @return an int array of compressed column/row sizes
1206     */

1207    private int[] compressedSizes(List formSpecs,
1208                                 int totalSize, int totalMinSize, int totalPrefSize,
1209                                 int[] minSizes, int[] prefSizes) {
1210        
1211        // If we have less space than the total min size answers min sizes.
1212
if (totalSize < totalMinSize)
1213            return minSizes;
1214        // If we have more space than the total pref size answers pref sizes.
1215
if (totalSize >= totalPrefSize)
1216            return prefSizes;
1217            
1218        int count = formSpecs.size();
1219        int[] sizes = new int[count];
1220
1221        double totalCompressionSpace = totalPrefSize - totalSize;
1222        double maxCompressionSpace = totalPrefSize - totalMinSize;
1223        double compressionFactor = totalCompressionSpace / maxCompressionSpace;
1224
1225// System.out.println("Total compression space=" + totalCompressionSpace);
1226
// System.out.println("Max compression space =" + maxCompressionSpace);
1227
// System.out.println("Compression factor =" + compressionFactor);
1228

1229        for (int i=0; i < count; i++) {
1230            FormSpec formSpec = (FormSpec) formSpecs.get(i);
1231            sizes[i] = prefSizes[i];
1232            if (formSpec.getSize() == Sizes.DEFAULT) {
1233                sizes[i] -= (int) Math.round((prefSizes[i] - minSizes[i])
1234                                                 * compressionFactor);
1235            }
1236        }
1237        return sizes;
1238    }
1239 
1240    
1241    /**
1242     * Computes and answers grouped sizes. Gives grouped columns or rows the
1243     * same size.
1244     *
1245     * @param groups the group specification
1246     * @param rawSizes the raw sizes before the grouping
1247     * @return the grouped sizes
1248     */

1249    private int[] groupedSizes(int[][] groups, int[] rawSizes) {
1250        // Return the compressed sizes if there are no groups.
1251
if (groups == null || groups.length == 0) {
1252            return rawSizes;
1253        }
1254        
1255        // Initialize the result with the given compressed sizes.
1256
int[] sizes = new int[rawSizes.length];
1257        for (int i = 0; i < sizes.length; i++) {
1258            sizes[i] = rawSizes[i];
1259        }
1260        
1261        // For each group equalize the sizes.
1262
for (int group = 0; group < groups.length; group++) {
1263            int[] groupIndices = groups[group];
1264            int groupMaxSize = 0;
1265            // Compute the group's maximum size.
1266
for (int i = 0; i < groupIndices.length; i++) {
1267                int index = groupIndices[i] - 1;
1268                groupMaxSize = Math.max(groupMaxSize, sizes[index]);
1269            }
1270            // Set all sizes of this group to the group's maximum size.
1271
for (int i = 0; i < groupIndices.length; i++) {
1272                int index = groupIndices[i] - 1;
1273                sizes[index] = groupMaxSize;
1274            }
1275        }
1276        return sizes;
1277    }
1278 
1279 
1280    /**
1281     * Distributes free space over columns and rows and answers the sizes
1282     * after this distribution process.
1283     *
1284     * @param formSpecs the column/row specifications to work with
1285     * @param totalSize the total available size
1286     * @param totalPrefSize the sum of all preferred sizes
1287     * @param inputSizes the input sizes
1288     * @return the distributed sizes
1289     */

1290    private int[] distributedSizes(List formSpecs,
1291                                    int totalSize, int totalPrefSize,
1292                                    int[] inputSizes) {
1293        double totalFreeSpace = totalSize - totalPrefSize;
1294        // Do nothing if there's no free space.
1295
if (totalFreeSpace < 0)
1296            return inputSizes;
1297            
1298        // Compute the total weight.
1299
int count = formSpecs.size();
1300        double totalWeight = 0.0;
1301        for (int i=0; i < count; i++) {
1302            FormSpec formSpec = (FormSpec) formSpecs.get(i);
1303            totalWeight += formSpec.getResizeWeight();
1304        }
1305        
1306        // Do nothing if there's no resizing column.
1307
if (totalWeight == 0.0)
1308            return inputSizes;
1309            
1310        int[] sizes = new int[count];
1311        
1312        double restSpace = totalFreeSpace;
1313        int roundedRestSpace = (int) totalFreeSpace;
1314        for (int i=0; i < count; i++) {
1315            FormSpec formSpec = (FormSpec) formSpecs.get(i);
1316            double weight = formSpec.getResizeWeight();
1317            if (weight == FormSpec.NO_GROW) {
1318                sizes[i] = inputSizes[i];
1319            } else {
1320                double roundingCorrection = restSpace - roundedRestSpace;
1321                double extraSpace = totalFreeSpace * weight / totalWeight;
1322                double correctedExtraSpace = extraSpace - roundingCorrection;
1323                int roundedExtraSpace = (int) Math.round(correctedExtraSpace);
1324                sizes[i] = inputSizes[i] + roundedExtraSpace;
1325                restSpace -= extraSpace;
1326                roundedRestSpace -= roundedExtraSpace;
1327            }
1328        }
1329        return sizes;
1330    }
1331 
1332    
1333    /**
1334     * Computes and answers the sum of integers in the given array of ints.
1335     *
1336     * @param sizes an array of ints to sum up
1337     * @return the sum of ints in the array
1338     */

1339    private int sum(int[] sizes) {
1340        int sum = 0;
1341        for (int i = sizes.length - 1; i >=0; i--) {
1342            sum += sizes[i];
1343        }
1344        return sum;
1345    }
1346    
1347    private boolean containsSpecThatCanGrow(List formSpecs) {
1348        for (Iterator it = formSpecs.iterator(); it.hasNext();) {
1349            FormSpec formSpec = (FormSpec) it.next();
1350            if (formSpec.canGrow())
1351                return true;
1352        }
1353        return false;
1354    }
1355    
1356      
1357    // Measuring Component Sizes ********************************************
1358

1359    /**
1360     * An interface that describes how to measure a <code>Component</code>.
1361     * Used to abstract from horizontal and vertical dimensions as well as
1362     * minimum and preferred sizes.
1363     */

1364    static interface Measure {
1365        
1366        /**
1367         * Computes and answers the size of the given <code>Component</code>.
1368         *
1369         * @param component the component to measure
1370         * @return the component's size
1371         */

1372        int sizeOf(Component JavaDoc component);
1373    }
1374    
1375    
1376    /**
1377     * An abstract implementation of the <code>Measure</code> interface
1378     * that caches component sizes.
1379     */

1380    private static abstract class CachingMeasure implements Measure {
1381        
1382        protected final ComponentSizeCache cache;
1383        
1384        private CachingMeasure(ComponentSizeCache cache) {
1385            this.cache = cache;
1386        }
1387        
1388    }
1389    
1390    // Measures a component by computing its minimum width.
1391
private static class MinimumWidthMeasure extends CachingMeasure {
1392        private MinimumWidthMeasure(ComponentSizeCache cache) {
1393            super(cache);
1394        }
1395        public int sizeOf(Component JavaDoc c) {
1396            return cache.getMinimumSize(c).width;
1397        }
1398    }
1399
1400    // Measures a component by computing its minimum height.
1401
private static class MinimumHeightMeasure extends CachingMeasure {
1402        private MinimumHeightMeasure(ComponentSizeCache cache) {
1403            super(cache);
1404        }
1405        public int sizeOf(Component JavaDoc c) {
1406            return cache.getMinimumSize(c).height;
1407        }
1408    }
1409
1410    // Measures a component by computing its preferred width.
1411
private static class PreferredWidthMeasure extends CachingMeasure {
1412        private PreferredWidthMeasure(ComponentSizeCache cache) {
1413            super(cache);
1414        }
1415        public int sizeOf(Component JavaDoc c) {
1416            return cache.getPreferredSize(c).width;
1417        }
1418    }
1419
1420    // Measures a component by computing its preferred height.
1421
private static class PreferredHeightMeasure extends CachingMeasure {
1422        private PreferredHeightMeasure(ComponentSizeCache cache) {
1423            super(cache);
1424        }
1425        public int sizeOf(Component JavaDoc c) {
1426            return cache.getPreferredSize(c).height;
1427        }
1428    }
1429
1430
1431    // Caching Component Sizes **********************************************
1432

1433    /*
1434     * A cache for component minimum and preferred sizes.
1435     * Used to reduce the requests to determine a component's size.
1436     */

1437    private static class ComponentSizeCache {
1438        
1439        /** Maps components to their minimum sizes. */
1440        private final Map minimumSizes;
1441
1442        /** Maps components to their preferred sizes. */
1443        private final Map preferredSizes;
1444        
1445        /**
1446         * Constructs a <code>ComponentSizeCache</code>
1447         * @param initialCapacity the initial cache capacity
1448         */

1449        private ComponentSizeCache(int initialCapacity) {
1450            minimumSizes = new HashMap(initialCapacity);
1451            preferredSizes = new HashMap(initialCapacity);
1452        }
1453        
1454        /**
1455         * Invalidates the cache. Clears all stored size information.
1456         */

1457        void invalidate() {
1458            minimumSizes.clear();
1459            preferredSizes.clear();
1460        }
1461        
1462        /**
1463         * Answers the minimum size for the given component. Tries to look up
1464         * the value from the cache; lazily creates the value if it has not
1465         * been requested before.
1466         *
1467         * @param component the component to compute the minimum size
1468         * @return the component's minimum size
1469         */

1470        Dimension JavaDoc getMinimumSize(Component JavaDoc component) {
1471            Dimension JavaDoc size = (Dimension JavaDoc) minimumSizes.get(component);
1472            if (size == null) {
1473                size = component.getMinimumSize();
1474                minimumSizes.put(component, size);
1475            }
1476            return size;
1477        }
1478
1479        /**
1480         * Answers the preferred size for the given component. Tries to look
1481         * up the value from the cache; lazily creates the value if it has not
1482         * been requested before.
1483         *
1484         * @param component the component to compute the preferred size
1485         * @return the component's preferred size
1486         */

1487        Dimension JavaDoc getPreferredSize(Component JavaDoc component) {
1488            Dimension JavaDoc size = (Dimension JavaDoc) preferredSizes.get(component);
1489            if (size == null) {
1490                size = component.getPreferredSize();
1491                preferredSizes.put(component, size);
1492            }
1493            return size;
1494        }
1495        
1496        void removeEntry(Component JavaDoc component) {
1497            minimumSizes.remove(component);
1498            preferredSizes.remove(component);
1499        }
1500    }
1501    
1502
1503    // Exposing the Layout Information **************************************
1504

1505    /**
1506     * Computes and answers the horizontal and vertical grid origins.
1507     * Performs the same layout process as #layoutContainer but
1508     * does not layout the components.
1509     * <p>
1510     * This method has been added only to make it easier to debug
1511     * the form layout. <b>You must not call this method directly;
1512     * It may be removed in a future release or the visibility
1513     * may be reduced.</b>
1514     *
1515     * @param parent the <code>Container</code> to inspect
1516     * @return an object that comprises the grid x and y origins
1517     */

1518    public LayoutInfo getLayoutInfo(Container JavaDoc parent) {
1519        synchronized (parent.getTreeLock()) {
1520            initializeColAndRowComponentLists();
1521            Dimension JavaDoc size = parent.getSize();
1522            
1523            Insets JavaDoc insets = parent.getInsets();
1524            int totalWidth = size.width - insets.left - insets.right;
1525            int totalHeight = size.height- insets.top - insets.bottom;
1526
1527            int[] x = computeGridOrigins(parent,
1528                                         totalWidth, insets.left,
1529                                         colSpecs,
1530                                         colComponents,
1531                                         colGroupIndices,
1532                                         minimumWidthMeasure,
1533                                         preferredWidthMeasure
1534                                         );
1535            int[] y = computeGridOrigins(parent,
1536                                         totalHeight, insets.top,
1537                                         rowSpecs,
1538                                         rowComponents,
1539                                         rowGroupIndices,
1540                                         minimumHeightMeasure,
1541                                         preferredHeightMeasure
1542                                         );
1543            return new LayoutInfo(x, y);
1544        }
1545    }
1546    
1547    
1548    /*
1549     * Stores column and row origins.
1550     */

1551    public static final class LayoutInfo {
1552        
1553        public final int[] columnOrigins;
1554        public final int[] rowOrigins;
1555        
1556        private LayoutInfo(int[] xOrigins, int[] yOrigins) {
1557            this.columnOrigins = xOrigins;
1558            this.rowOrigins = yOrigins;
1559        }
1560        
1561        public int getX() {
1562            return columnOrigins[0];
1563        }
1564        
1565        public int getY() {
1566            return rowOrigins[0];
1567        }
1568        
1569        public int getWidth() {
1570            return columnOrigins[columnOrigins.length-1] - columnOrigins[0];
1571        }
1572        
1573        public int getHeight() {
1574            return rowOrigins[rowOrigins.length-1] - rowOrigins[0];
1575        }
1576        
1577    }
1578    
1579    // Parsing and Decoding of Column and Row Descriptions ******************
1580

1581    /**
1582     * Parses and splits encoded column specifications and returns an array of
1583     * <code>ColumnSpec</code> objects.
1584     *
1585     * @param encodedColSpec the encoded column specification
1586     * @return an array of decoded column specifications
1587     * @throws NullPointerException if the string description is null
1588     */

1589    private static ColumnSpec[] decodeColSpecs(String JavaDoc encodedColSpec) {
1590        if (encodedColSpec == null)
1591            throw new NullPointerException JavaDoc("The column description must not be null.");
1592        
1593        StringTokenizer tokenizer = new StringTokenizer(encodedColSpec, ", ");
1594        int columnCount = tokenizer.countTokens();
1595        ColumnSpec[] colSpecs = new ColumnSpec[columnCount];
1596        for (int i = 0; i < columnCount; i++) {
1597            colSpecs[i] = new ColumnSpec(tokenizer.nextToken());
1598        }
1599        return colSpecs;
1600    }
1601
1602
1603    /**
1604     * Parses and splits encoded row specifications and returns an array of
1605     * <code>RowSpec</code> objects.
1606     *
1607     * @param encodedRowSpec the encoded row specification
1608     * @return an array of decoded row specifications
1609     * @throws NullPointerException if the string description is null
1610     */

1611    private static RowSpec[] decodeRowSpecs(String JavaDoc encodedRowSpec) {
1612        if (encodedRowSpec == null)
1613            throw new NullPointerException JavaDoc("The row description must not be null.");
1614        
1615        StringTokenizer tokenizer = new StringTokenizer(encodedRowSpec, ", ");
1616        int rowCount = tokenizer.countTokens();
1617        RowSpec[] rowSpecs = new RowSpec[rowCount];
1618        for (int i = 0; i < rowCount; i++) {
1619            rowSpecs[i] = new RowSpec(tokenizer.nextToken());
1620        }
1621        return rowSpecs;
1622    }
1623
1624
1625    // Helper Code **********************************************************
1626

1627    /**
1628     * Creates and answers a deep copy of the given array. Unlike #clone
1629     * that performs a shallow copy, this method copies both array levels.
1630     *
1631     * @param array the array to clone
1632     * @return a deep copy of the given array
1633     */

1634    private int[][] deepClone(int[][] array) {
1635        int[][] result = new int[array.length][];
1636        for (int i = 0; i < result.length; i++) {
1637            result[i] = (int[]) array[i].clone();
1638        }
1639        return result;
1640    }
1641    
1642
1643    // Debug Helper Code ****************************************************
1644

1645    /*
1646    // Prints the given column widths and row heights.
1647    private void printSizes(String title, int[] colWidths, int[] rowHeights) {
1648        System.out.println();
1649        System.out.println(title);
1650        int totalWidth = 0;
1651        System.out.print("Column widths: ");
1652        for (int i=0; i < getColumnCount(); i++) {
1653            int width = colWidths[i];
1654            totalWidth += width;
1655            System.out.print(width + ", ");
1656        }
1657        System.out.println(" Total=" + totalWidth);
1658        
1659        int totalHeight = 0;
1660        System.out.print("Row heights: ");
1661        for (int i=0; i < getRowCount(); i++) {
1662            int height = rowHeights[i];
1663            totalHeight += height;
1664            System.out.print(height + ", ");
1665        }
1666        System.out.println(" Total=" + totalHeight);
1667        System.out.println();
1668    }
1669
1670    */

1671
1672    
1673}
1674
Popular Tags