View Javadoc

1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. 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    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   */
38  package com.gargoylesoftware.base.gui;
39  
40  import com.gargoylesoftware.base.trace.Trace;
41  import com.gargoylesoftware.base.trace.TraceChannel;
42  import com.gargoylesoftware.base.util.DetailedIllegalArgumentException;
43  import com.gargoylesoftware.base.util.DetailedNullPointerException;
44  import java.awt.Component;
45  import java.awt.Container;
46  import java.awt.Dimension;
47  import java.awt.Graphics;
48  import java.awt.Insets;
49  import java.awt.LayoutManager2;
50  import java.io.Serializable;
51  import java.util.ArrayList;
52  import java.util.HashSet;
53  import java.util.Iterator;
54  import java.util.List;
55  import java.util.Set;
56  import javax.swing.SwingConstants;
57  
58  /***
59   *  The TableLayout lays out items based on a table of rows and columns.<p>
60   *
61   * If you are doing simple layout, you can specify constraints as strings of the format
62   * "row,column".  If you want the component to stretch over multiple rows/columns
63   * then you can specify the constraint as "row+rowspan,column+columnspan" as shown in
64   * the sample below.
65   * <pre>
66   * final TableLayout layout = new TableLayout();
67   * final JPanel panel = new JPanel(layout);
68   *
69   * panel.add( new JLabel("squirrel"), "1,1" );
70   * panel.add( new JLabel("raccoon"), "1,2" );
71   * panel.add( new JLabel("bluejay"), "2,1" );
72   * panel.add( new JLabel("goldfish"), "2,2" );
73   * panel.add( new JLabel("marshhawk"), "3,1+3" );
74   * </pre>
75   *
76   * If you want more flexibility over the layout then this, use a {@link TableLayoutConstraints}
77   * object instead of a string.  Here is a more complicated sample that uses
78   * {@link TableLayoutConstraints} to customize the layout a bit more.  Note the use of
79   * {@link TableLayoutDebuggingPanel} - this will draw lines on layout boundaries to help
80   * debug layout problems.
81   * <pre>
82   * final TableLayout layout = new TableLayout();
83   * final JPanel panel = new TableLayoutDebuggingPanel(layout);
84   *
85   * TableLayoutConstraints constraints;
86   *
87   * layout.setRowExpandable(1, true);
88   *
89   * constraints = new TableLayoutConstraints(1,1);
90   * constraints.setVerticalStretch(true);
91   * panel.add( new JButton("squirrel"), constraints );
92   *
93   * constraints = new TableLayoutConstraints(1,2);
94   * constraints.setVerticalAlignment(TableLayout.TOP);
95   * panel.add( new JButton("raccoon"), constraints );
96   *
97   * panel.add( new JButton("bluejay"), "2,1" );
98   * panel.add( new JButton("goldfish"), "2,2" );
99   * panel.add( new JButton("marshhawk"), "3,1+3" );
100  * </pre>
101  *
102  *  <b>Debugging tip: </b>Most layout problems become obvious if you use a
103  * {@link TableLayoutDebuggingPanel} to see where the layout boundaries are.  In those
104  * rare cases where this doesn't give you enough information, try calling
105  * {@link #setTraceChannel(TraceChannel)} with a non-null TraceChannel such as Trace.out
106  * or Trace.err.  This will dump quite a bit of diagnostic information.
107  * <pre>
108  * layout.setTraceChannel(Trace.out)
109  * </pre>
110  *
111  * @version    $Revision: 1.6 $
112  * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
113  */
114 public class TableLayout
115          implements
116         LayoutManager2,
117         SwingConstants,
118         Serializable {
119 
120     // Required for serialization - do not remove or modify.
121     private static final long serialVersionUID = 396191633848670929L;
122 
123     private final Set rowHeaderPermanentInfos_ = new HashSet();
124     private final Set columnHeaderPermanentInfos_ = new HashSet();
125 
126     private Container parent_;
127     private final List constraints_ = new ArrayList();
128 
129     // If non-null then dump diagnostic information
130     private TraceChannel traceChannel_ = null;
131 
132     // Used to hold temporary sizing information during calculations.
133     private Header tempColumnHeaders_[] = null;
134     private Header tempRowHeaders_[] = null;
135 
136     // The number of columns/rows.  Used when allocating temporary
137     // storage.
138     private int columnCount_ = 0;
139     private int rowCount_ = 0;
140 
141     // True if all the calculations have been performed
142     private boolean tempSizesAreValid_ = false;
143 
144     // If the component size is bigger than the minimum size then these
145     // vars will be used to determine size/position of the resulting
146     // layout
147     private int verticalAlignment_ = CENTER;
148     private int horizontalAlignment_ = CENTER;
149 
150     private Dimension minimumSize_ = null;
151     private Dimension maximumSize_ = null;
152     private Dimension preferredSize_ = null;
153     private Dimension actualSize_ = null;
154 
155     private boolean ignoreInvisibleComponents_ = true;
156 
157 
158     /***
159      *  Create a new TableLayout.
160      */
161     public TableLayout() {
162     }
163 
164 
165     /***
166      *  Convenience method to create a string from a Dimension object.
167      *
168      * @param  dimension  Description of Parameter
169      * @return            Description of the Returned Value
170      */
171     private static String toString( final Dimension dimension ) {
172         return "(" + dimension.width + "," + dimension.height + ")";
173     }
174 
175 
176     /***
177      *  Set the vertical alignment. Legal values are:
178      *  <ul>
179      *    <li> <tt>TableLayout.TOP</tt>
180      *    <li> <tt>TableLayout.CENTER</tt>
181      *    <li> <tt>TableLayout.BOTTOM</tt>
182      *  </ul>
183      *
184      *
185      * @param  alignment  The new vertical alignment.
186      */
187     public void setVerticalAlignment( final int alignment ) {
188         Trace.println( traceChannel_,
189                 "setVerticalAlignment(" + alignment + ")" );
190 
191         switch ( alignment ) {
192             case TableLayout.TOP:
193             case TableLayout.BOTTOM:
194             case TableLayout.CENTER:
195                 verticalAlignment_ = alignment;
196                 break;
197             default:
198                 throw new DetailedIllegalArgumentException( "alignment", new Integer(alignment) );
199         }
200     }
201 
202 
203     /***
204      *  Set the vertical alignment. Legal values are:
205      *  <ul>
206      *    <li> <tt>TableLayout.LEFT</tt>
207      *    <li> <tt>TableLayout.CENTER</tt>
208      *    <li> <tt>TableLayout.RIGHT</tt>
209      *  </ul>
210      *
211      *
212      * @param  alignment  The new horizontal alignment.
213      */
214     public void setHorizontalAlignment( final int alignment ) {
215         Trace.println( traceChannel_, "setHorizontalAlignment()" );
216         switch ( alignment ) {
217             case TableLayout.LEFT:
218             case TableLayout.RIGHT:
219             case TableLayout.CENTER:
220                 horizontalAlignment_ = alignment;
221                 break;
222             default:
223                 throw new DetailedIllegalArgumentException( "alignment", new Integer(alignment) );
224         }
225     }
226 
227 
228     /***
229      *  Set the minimum row height for a specific row.
230      *
231      * @param  index  The row that we are setting the height for.
232      * @param  size   The new minimum height.
233      */
234     public void setMinimumRowHeight( final int index, final int size ) {
235         final HeaderPermanentInfo info
236                  = getPermanentInfo( rowHeaderPermanentInfos_, index, true );
237         info.setMin( size );
238     }
239 
240 
241     /***
242      *  Set the minimum column width for a specific column.
243      *
244      * @param  index  The column that we are setting the width for.
245      * @param  size   The new width.
246      */
247     public void setMinimumColumnWidth( final int index, final int size ) {
248         final HeaderPermanentInfo info
249                  = getPermanentInfo( columnHeaderPermanentInfos_, index, true );
250         info.setMin( size );
251     }
252 
253 
254     /***
255      *  Set whether this row can be expanded beyond its preferred size.
256      *
257      * @param  index         The row index.
258      * @param  isExpandable  true if the row is to be expandable.
259      */
260     public void setRowExpandable( final int index,
261             final boolean isExpandable ) {
262         final HeaderPermanentInfo info
263                  = getPermanentInfo( rowHeaderPermanentInfos_, index, true );
264         info.setExpandable( isExpandable );
265     }
266 
267 
268     /***
269      *  Set whether this column can be expanded beyond its preferred size.
270      *
271      * @param  index         The column index.
272      * @param  isExpandable  true if the column is to be expandable.
273      */
274     public void setColumnExpandable( final int index,
275             final boolean isExpandable ) {
276         final HeaderPermanentInfo info
277                  = getPermanentInfo( columnHeaderPermanentInfos_, index, true );
278         info.setExpandable( isExpandable );
279     }
280 
281 
282     /***
283      *  Set the trace channel used for printing diagnostic information. If the
284      *  channel is null then no tracing will be done.
285      *
286      * @param  channel  The new trace channel.
287      */
288     public void setTraceChannel( final TraceChannel channel ) {
289         traceChannel_ = channel;
290     }
291 
292 
293     /***
294      *  Set whether or not we should ignore an components that are not visible.
295      *
296      * @param  ignore  True if we should ignore them.
297      */
298     public void setIgnoreInvisibleComponents( final boolean ignore ) {
299         ignoreInvisibleComponents_ = ignore;
300     }
301 
302 
303     /***
304      *  I don't really understand what this method is supposed to return so I
305      *  always return 0F. If you can explain this one to me then send me an
306      *  email at mbowler@GargoyleSoftware.com and I'll fix this method up
307      *  appropriately.
308      *
309      * @param  target  The container that this layout is managing.
310      * @return         Zero.
311      */
312     public float getLayoutAlignmentX( final Container target ) {
313         return 0F;
314     }
315 
316 
317     /***
318      *  I don't really understand what this method is supposed to return so I
319      *  always return 0F. If you can explain this one to me then send me an
320      *  email at mbowler@GargoyleSoftware.com and I'll fix this method up
321      *  appropriately.
322      *
323      * @param  target  The container that this layout is managing.
324      * @return         Zero.
325      */
326     public float getLayoutAlignmentY( final Container target ) {
327         return 0F;
328     }
329 
330 
331     /***
332      *  Return the vertical alignment.
333      *
334      * @return    The vertical alignment.
335      * @see       #setVerticalAlignment(int)
336      */
337     public int getVerticalAlignment() {
338         return verticalAlignment_;
339     }
340 
341 
342     /***
343      *  Return the horizontal alignment.
344      *
345      * @return    The horizontal alignment.
346      */
347     public int getHorizontalAlignment() {
348         return horizontalAlignment_;
349     }
350 
351 
352     /***
353      *  Return true if this row can be expanded beyond its preferred size. The
354      *  default is false.
355      *
356      * @param  index  The row index
357      * @return        true if the specified row is expandable.
358      */
359     public boolean isRowExpandable( final int index ) {
360         final HeaderPermanentInfo info
361                  = getPermanentInfo( rowHeaderPermanentInfos_, index, false );
362         final boolean isExpandable;
363         if( info == null ) {
364             isExpandable = false;
365         }
366         else {
367             isExpandable = info.isExpandable();
368         }
369         return isExpandable;
370     }
371 
372 
373     /***
374      *  Return true if this column can be expanded beyond its preferred size.
375      *  The default is false.
376      *
377      * @param  index  The column.
378      * @return        true if the column is expandable.
379      */
380     public boolean isColumnExpandable( final int index ) {
381         final HeaderPermanentInfo info
382                  = getPermanentInfo( columnHeaderPermanentInfos_, index, false );
383         final boolean isExpandable;
384         if( info == null ) {
385             isExpandable = false;
386         }
387         else {
388         		isExpandable = info.isExpandable();
389         }
390         return isExpandable;
391     }
392 
393 
394     /***
395      *  Return the trace channel.
396      *
397      * @return    The trace channel or null if one wasn't set.
398      */
399     public TraceChannel getTraceChannel() {
400         return traceChannel_;
401     }
402 
403 
404     /***
405      *  Get whether or not we should ignore an components that are not visible.
406      *
407      * @return    True if we should ignore them.
408      */
409     public boolean getIgnoreInvisibleComponents() {
410         return ignoreInvisibleComponents_;
411     }
412 
413 
414     /***
415      *  Add the specified component to the layout with the specified
416      *  constraints. This method should never be called as Container.addImpl()
417      *  should call addLayoutComponent(Component,Object) instead. Not
418      *  implemented.
419      *
420      * @param  name                            The constraints string.
421      * @param  comp                            the component that is being
422      *      added.
423      * @throws  UnsupportedOperationException  If called.
424      */
425     public void addLayoutComponent( final String name, final Component comp )
426         throws UnsupportedOperationException {
427         throw new UnsupportedOperationException( "Use addLayoutComponent(Component,Object)" );
428     }
429 
430 
431     /***
432      *  Add the specified component to the layout with the specified
433      *  constraints. Throw an IllegalArgumentException if the same component is
434      *  specified twice. The constraints object can be either an instance of
435      *  TableLayoutConstraints or a String. If it is a string then an instance
436      *  of TableLayoutConstraints will be created with the method
437      *  TableLayoutConstraints.makeConstraints(String).
438      *
439      * @param  comp         The component that is being added.
440      * @param  constraints  The constraints object.
441      * @see                 TableLayoutConstraints#makeConstraints(String)
442      */
443     public void addLayoutComponent( final Component comp, final Object constraints ) {
444         assertNotNull("comp", comp);
445         assertNotNull("constraints", constraints);
446 
447         TableLayoutConstraints tableLayoutConstraints = null;
448         if( constraints instanceof TableLayoutConstraints ) {
449             tableLayoutConstraints = (TableLayoutConstraints)constraints;
450         }
451         else if( constraints instanceof String ) {
452             tableLayoutConstraints
453                      = TableLayoutConstraints.makeConstraints( (String)constraints );
454         }
455         else {
456             throw new DetailedIllegalArgumentException( "constraints", constraints, "Must be an instance "
457                      + "of TableLayoutConstraints or String: "
458                      + constraints.getClass().getName() );
459         }
460 
461         final Iterator iterator = constraints_.iterator();
462         while( iterator.hasNext() ) {
463             if( ( (Entry)iterator.next() ).getComponent() == comp ) {
464                 throw new DetailedIllegalArgumentException( "comp", comp, "Already in layout" );
465             }
466         }
467 
468         tableLayoutConstraints.setImmutable();
469         final Entry entry = new Entry( comp, tableLayoutConstraints );
470         constraints_.add( entry );
471         invalidateLayout();
472     }
473 
474 
475     /***
476      *  Remove the specified component from the layout.
477      *
478      * @param  comp  The component to remove.
479      */
480     public void removeLayoutComponent( final Component comp ) {
481         Trace.println( traceChannel_, "removeLayoutComponent(" + comp + ")" );
482         assertNotNull("comp", comp);
483 
484         Entry entry;
485         final Iterator iterator = constraints_.iterator();
486         while( iterator.hasNext() ) {
487             entry = (Entry)iterator.next();
488             if( entry.getComponent() == comp ) {
489                 iterator.remove();
490                 invalidateLayout();
491                 return;
492             }
493         }
494 
495         throw new DetailedIllegalArgumentException( "comp", comp, "Not found" );
496     }
497 
498 
499     /***
500      *  Get the minimum size of this layout.
501      *
502      * @param  parent  The container that this layout is managing.
503      * @return         The minimum size required for this layout.
504      */
505     public Dimension minimumLayoutSize( final Container parent ) {
506         setParent( parent );
507 
508         if( minimumSize_ == null ) {
509             calculateMinMaxPreferredSizes();
510         }
511         return minimumSize_;
512     }
513 
514 
515     /***
516      *  Return the preferred layout size.
517      *
518      * @param  parent  The container that this layout is managing.
519      * @return         The preferred layout size.
520      */
521     public Dimension preferredLayoutSize( final Container parent ) {
522         setParent( parent );
523 
524         if( preferredSize_ == null ) {
525             calculateMinMaxPreferredSizes();
526         }
527         return preferredSize_;
528     }
529 
530 
531     /***
532      *  Layout all the components in this container.
533      *
534      * @param  parent  The container that this layout is managing.
535      */
536     public void layoutContainer( final Container parent ) {
537         setParent( parent );
538 
539         final Dimension parentSize = parent.getSize();
540         Entry entry;
541         int x = 0;
542         int y = 0;
543         int height = 0;
544         int width = 0;
545         int i;
546         int rowIndex;
547         int columnIndex;
548 
549         // Is there anything to do?
550         if( parent.getComponentCount() == 0 ) {
551             return;
552         }
553 
554         calculateSizes();
555         calculatePositions( parent, parentSize );
556 
557         final Iterator iterator = constraints_.iterator();
558         while( iterator.hasNext() ) {
559             entry = (Entry)iterator.next();
560 
561             rowIndex = entry.getConstraints().getRow();
562             columnIndex = entry.getConstraints().getColumn();
563 
564             y = tempRowHeaders_[rowIndex].getStart();
565             x = tempColumnHeaders_[columnIndex].getStart();
566 
567             height = 0;
568             width = 0;
569             for( i = 0; i < entry.getConstraints().getRowSpan(); i++ ) {
570                 height += tempRowHeaders_[rowIndex + i].getActual();
571             }
572             for( i = 0; i < entry.getConstraints().getColumnSpan(); i++ ) {
573                 width += tempColumnHeaders_[columnIndex + i].getActual();
574             }
575 
576             positionComponent( entry, x, y, width, height );
577         }
578     }
579 
580 
581     /***
582      *  Return the maximum layout size.
583      *
584      * @param  target  The container that this layout is managing.
585      * @return         The maximum layout size.
586      */
587     public Dimension maximumLayoutSize( final Container target ) {
588         setParent( target );
589         if( maximumSize_ == null ) {
590             calculateMinMaxPreferredSizes();
591         }
592         return maximumSize_;
593     }
594 
595 
596     /***
597      *  Invalidate the layout and throw away and temporary calculations.
598      *
599      * @param  target  The container that this layout is managing.
600      */
601     public void invalidateLayout( final Container target ) {
602         setParent( target );
603         invalidateLayout();
604     }
605 
606 
607     /***
608      *  A debugging method that draws lines on the parent component to show
609      *  where the table cell boundaries are. The lines will be drawn in the
610      *  current colour.
611      *
612      * @param  graphics  The graphics object.
613      * @see       TableLayoutDebuggingPanel
614      */
615     public void drawOutlines( final Graphics graphics ) {
616         int i;
617         int j;
618         Header column;
619         Header row;
620 
621         for( i = 0; i < columnCount_; i++ ) {
622             column = tempColumnHeaders_[i];
623             for( j = 0; j < rowCount_; j++ ) {
624                 row = tempRowHeaders_[j];
625                 graphics.drawRect(
626                     column.getStart(),  // x
627                     row.getStart(),     // y
628                     column.getActual(), // width
629                     row.getActual() );  // height
630             }
631         }
632     }
633 
634 
635     /***
636      *  Set the parent container for this layout.
637      *
638      * @param  newParent  The new parent value
639      */
640     private void setParent( final Container newParent ) {
641         assertNotNull("newParent", newParent);
642 
643         if( ( parent_ != null ) && ( newParent != parent_ ) ) {
644             throw new DetailedIllegalArgumentException( "newParent", newParent, "Attempt to reassign parent" );
645         }
646 
647         parent_ = newParent;
648     }
649 
650 
651     /***
652      *  Return an array containing all the headers that are expandable.
653      *
654      * @param  first    Description of Parameter
655      * @param  last     Description of Parameter
656      * @param  headers  Description of Parameter
657      * @return          The expandableHeaders value
658      */
659     private Header[] getExpandableHeaders( final int first,
660             final int last,
661             final Header[] headers ) {
662 
663         final List list = new ArrayList( headers.length );
664         int i;
665 
666         for( i = first; i <= last; i++ ) {
667             if( headers[i].isExpandable() ) {
668                 list.add( headers[i] );
669             }
670         }
671 
672         final Header expandableHeaders[] = new Header[list.size()];
673         list.toArray( expandableHeaders );
674         return expandableHeaders;
675     }
676 
677 
678     /***
679      *  Return the minimum row height. The default is 0.
680      *
681      * @param  index  Description of Parameter
682      * @return        The minimumRowHeight value
683      */
684     private int getMinimumRowHeight( final int index ) {
685         final HeaderPermanentInfo info
686                  = getPermanentInfo( rowHeaderPermanentInfos_, index, false );
687         final int result;
688         if( info == null ) {
689             result = 0;
690         }
691         else {
692             result = info.getMin();
693         }
694         return result;
695     }
696 
697 
698     /***
699      *  Return the minimum column width. The default is 0.
700      *
701      * @param  index  The column index.
702      * @return        The minimum column width for the specified column.
703      */
704     private int getMinimumColumnWidth( final int index ) {
705         final HeaderPermanentInfo info
706                  = getPermanentInfo( columnHeaderPermanentInfos_, index, false );
707         final int result;
708         if( info == null ) {
709             result = 0;
710         }
711         else {
712             result = info.getMin();
713         }
714         return result;
715     }
716 
717 
718     /***
719      *  TODO: Provide comments
720      *
721      * @param  infoList        Description of Parameter
722      * @param  index           Description of Parameter
723      * @param  createIfNeeded  Description of Parameter
724      * @return                 The permanentInfo value
725      */
726     private HeaderPermanentInfo getPermanentInfo( final Set infoList,
727             final int index,
728             final boolean createIfNeeded ) {
729         final Iterator iterator = infoList.iterator();
730         HeaderPermanentInfo info;
731 
732         while( iterator.hasNext() ) {
733             info = (HeaderPermanentInfo)iterator.next();
734             if( info.getIndex() == index ) {
735                 return info;
736             }
737         }
738 
739         // We didn't find one.
740         if( createIfNeeded ) {
741             if( traceChannel_ != null ) {
742                 final StringBuffer buffer = new StringBuffer();
743                 buffer.append( "getPermanentInfo() creating new info for " );
744                 if( infoList == rowHeaderPermanentInfos_ ) {
745                     buffer.append( "row " );
746                 }
747                 else {
748                     buffer.append( "column " );
749                 }
750                 buffer.append( index );
751                 Trace.println( buffer.toString() );
752             }
753             info = new HeaderPermanentInfo( index );
754             infoList.add( info );
755         }
756         else {
757             info = null;
758         }
759 
760         return info;
761     }
762 
763 
764     /***
765      *  Return the TableLayoutConstraints object that corresponds to the
766      *  specified component or null if this component could not be found.
767      *
768      * @param  component  Description of Parameter
769      * @return            The constraints value
770      */
771     private TableLayoutConstraints getConstraints( final Component component ) {
772         Entry entry;
773 
774         final Iterator iterator = constraints_.iterator();
775         while( iterator.hasNext() ) {
776             entry = (Entry)iterator.next();
777 
778             if( entry.getComponent() == component ) {
779                 return entry.getConstraints();
780             }
781         }
782 
783         return null;
784     }
785 
786 
787     /***
788      *  Return the minimum size of the specified component. If the component is
789      *  not visible and we are ignoring invisible components then return a size
790      *  of 0,0
791      *
792      * @param  component  The component that we will be querying
793      * @return            The size
794      */
795     private final Dimension getComponentMinimumSize( final Component component ) {
796     		final Dimension size;
797         if( component.isVisible() == false && ignoreInvisibleComponents_ == true ) {
798             size = new Dimension( 0, 0 );
799         }
800         else {
801             size = component.getMinimumSize();
802         }
803         return size;
804     }
805 
806 
807     /***
808      *  Return the minimum size of the specified component. If the component is
809      *  not visible and we are ignoring invisible components then return a size
810      *  of 0,0
811      *
812      * @param  component  The component that we will be querying
813      * @return            The size
814      */
815     private final Dimension getComponentMaximumSize( final Component component ) {
816     		final Dimension size;
817         if( component.isVisible() == false && ignoreInvisibleComponents_ == true ) {
818             size = new Dimension( 0, 0 );
819         }
820         else {
821             size = component.getMaximumSize();
822         }
823         return size;
824     }
825 
826 
827     /***
828      *  Return the minimum size of the specified component. If the component is
829      *  not visible and we are ignoring invisible components then return a size
830      *  of 0,0
831      *
832      * @param  component  The component that we will be querying
833      * @return            The size
834      */
835     private final Dimension getComponentPreferredSize( final Component component ) {
836     		final Dimension size;
837         if( component.isVisible() == false && ignoreInvisibleComponents_ == true ) {
838             size = new Dimension( 0, 0 );
839         }
840         else {
841             size = component.getPreferredSize();
842         }
843         return size;
844     }
845 
846 
847     /***
848      *  Calculate the various sizes.
849      */
850     private void calculateMinMaxPreferredSizes() {
851         int i;
852         int xMin = 0;
853         int yMin = 0;
854         int xMax = 0;
855         int yMax = 0;
856         int xPreferred = 0;
857         int yPreferred = 0;
858 
859         calculateRowAndColumnCount();
860         calculateSizes();
861 
862         if( false ) {
863             Trace.println(
864             //traceChannel_,
865             "calculateMinMaxPreferredSize() tempRowHeaders_=["
866                      + tempRowHeaders_
867                      + "] rowCount_=["
868                      + rowCount_
869                      + "] columnCount=["
870                      + columnCount_
871                      + "] tempSizesAreValid_=["
872                      + tempSizesAreValid_
873                      + "]" );
874         }
875 
876         for( i = 0; i < rowCount_; i++ ) {
877             yMin += tempRowHeaders_[i].getMin();
878             yMax += tempRowHeaders_[i].getMax();
879             yPreferred += tempRowHeaders_[i].getPreferred();
880         }
881 
882         for( i = 0; i < columnCount_; i++ ) {
883             xMin += tempColumnHeaders_[i].getMin();
884             xMax += tempColumnHeaders_[i].getMax();
885             xPreferred += tempColumnHeaders_[i].getPreferred();
886         }
887 
888         // Adjust for the insets
889         final Insets insets = parent_.getInsets();
890         final int horizontalInset = insets.left + insets.right;
891         final int verticalInset = insets.top + insets.bottom;
892 
893         xMin += horizontalInset;
894         yMin += verticalInset;
895         xPreferred += horizontalInset;
896         yPreferred += verticalInset;
897         xMax += horizontalInset;
898         yMax += verticalInset;
899 
900         xPreferred = Math.max( xPreferred, xMin );
901         xMax = Math.max( xMax, xPreferred );
902         yPreferred = Math.max( yPreferred, yMin );
903         yMax = Math.max( yMax, yPreferred );
904 
905         // If any rows/columns are expandable then the max is infinite
906         if( areAnyExpandable( rowHeaderPermanentInfos_ ) ) {
907             yMax = Integer.MAX_VALUE;
908         }
909         if( areAnyExpandable( columnHeaderPermanentInfos_ ) ) {
910             xMax = Integer.MAX_VALUE;
911         }
912 
913         // TODO: Cache these values if possible.  It's expensive
914         // to keep creating new Dimensions
915         minimumSize_ = new Dimension( xMin, yMin );
916         maximumSize_ = new Dimension( xMax, yMax );
917         preferredSize_ = new Dimension( xPreferred, yPreferred );
918 
919         if( constraints_.size() == 0 ) {
920             // Special case when no children
921             maximumSize_ = new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
922         }
923     }
924 
925 
926     /***
927      *  Return true if any of the infos are expandable.
928      *
929      * @param  permanentInfos  The infos.
930      * @return                 Description of the Returned Value
931      */
932     private boolean areAnyExpandable( final Set permanentInfos ) {
933         HeaderPermanentInfo info;
934 
935         final Iterator iterator = permanentInfos.iterator();
936         while( iterator.hasNext() ) {
937             info = (HeaderPermanentInfo)iterator.next();
938             if( info.isExpandable() ) {
939                 return true;
940             }
941         }
942 
943         return false;
944     }
945 
946 
947     /***
948      *  Invalidate the layout.
949      */
950     private void invalidateLayout() {
951         tempColumnHeaders_ = null;
952         tempRowHeaders_ = null;
953         tempSizesAreValid_ = false;
954         minimumSize_ = null;
955         maximumSize_ = null;
956         preferredSize_ = null;
957         actualSize_ = null;
958         columnCount_ = 0;
959         rowCount_ = 0;
960     }
961 
962 
963     /***
964      *  Calculate all the various sizing information required for this layout.
965      *  Called by layoutContainer(), minimumLayoutSize(), maximumLayoutSize()
966      */
967     private void calculateSizes() {
968         if( false ) {
969             Trace.println( "TableLayout.calculateSizes() "
970                      + "tempSizesAreValid_=["
971                      + tempSizesAreValid_
972                      + "] tempRowHeaders_=["
973                      + tempRowHeaders_
974                      + "] tempColumnHeaders_=["
975                      + tempColumnHeaders_
976                      + "]" );
977         }
978         if( tempSizesAreValid_ && tempRowHeaders_ == null ) {
979             Trace.whereAmI();
980         }
981         Dimension minSize;
982         Dimension maxSize;
983         Dimension preferredSize;
984         Entry entry;
985         TableLayoutConstraints constraints;
986         Iterator iterator;
987 
988         // See if there's anything to do...
989         if( rowCount_ == 0 || columnCount_ == 0 ) {
990             return;
991         }
992 
993         if( tempSizesAreValid_ ) {
994             return;
995         }
996         tempSizesAreValid_ = true;
997 
998         initTempSizes();
999 
1000         //
1001         // On the first pass handle only those constraints that only
1002         // span one cell.
1003         //
1004         iterator = constraints_.iterator();
1005         while( iterator.hasNext() ) {
1006             entry = (Entry)iterator.next();
1007             constraints = entry.getConstraints();
1008 
1009             if( constraints.getObeyMinimumSize() ) {
1010                 minSize = getComponentMinimumSize( entry.getComponent() );
1011             }
1012             else {
1013                 minSize = new Dimension( 0, 0 );
1014             }
1015 
1016             if( constraints.getObeyMaximumSize() ) {
1017                 maxSize = getComponentMaximumSize( entry.getComponent() );
1018             }
1019             else {
1020                 maxSize = new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
1021             }
1022             preferredSize = getComponentPreferredSize( entry.getComponent() );
1023 
1024             // Handle the rows
1025             if( constraints.getRowSpan() == 1 ) {
1026                 Trace.println( traceChannel_, "tempRowHeaders_.length=["
1027                          + tempRowHeaders_.length + "] constraints.getRow()=["
1028                          + constraints.getRow() + "]" );
1029                 tempRowHeaders_[constraints.getRow()].setHasComponents( true );
1030 
1031                 if( minSize.height > tempRowHeaders_[constraints.getRow()].getMin() ) {
1032                     tempRowHeaders_[constraints.getRow()].setMin( minSize.height );
1033                 }
1034                 if( maxSize.height > tempRowHeaders_[constraints.getRow()].getMax() ) {
1035                     tempRowHeaders_[constraints.getRow()].setMax( maxSize.height );
1036                 }
1037                 if( preferredSize.height > tempRowHeaders_[constraints.getRow()].getPreferred() ) {
1038                     tempRowHeaders_[constraints.getRow()].setPreferred( preferredSize.height );
1039                 }
1040 
1041                 // TODO: Do we need to worry about this here?  We will adjust
1042                 //       it later.
1043                 // If the minimum and maximum sizes collide then minimum will
1044                 // always win.
1045                 if( tempRowHeaders_[constraints.getRow()].getMin()
1046                          > tempRowHeaders_[constraints.getRow()].getMax() ) {
1047                     tempRowHeaders_[constraints.getRow()].setMax( tempRowHeaders_[constraints.getRow()].getMin() );
1048                 }
1049                 // If the minimum and preferred sizes collide then minimum will
1050                 // always win.
1051                 if( tempRowHeaders_[constraints.getRow()].getMin()
1052                          > tempRowHeaders_[constraints.getRow()].getPreferred() ) {
1053                     tempRowHeaders_[constraints.getRow()].setPreferred(
1054                         tempRowHeaders_[constraints.getRow()].getMin() );
1055                 }
1056             }
1057 
1058             // Handle the columns
1059             if( constraints.getColumnSpan() == 1 ) {
1060                 if( minSize.width > tempColumnHeaders_[constraints.getColumn()].getMin() ) {
1061                     tempColumnHeaders_[constraints.getColumn()].setMin( minSize.width );
1062                 }
1063                 if( maxSize.width > tempColumnHeaders_[constraints.getColumn()].getMax() ) {
1064                     tempColumnHeaders_[constraints.getColumn()].setMax( maxSize.width );
1065                 }
1066                 if( preferredSize.width > tempColumnHeaders_[constraints.getColumn()].getPreferred() ) {
1067                     tempColumnHeaders_[constraints.getColumn()].setPreferred( preferredSize.width );
1068                 }
1069 
1070                 tempColumnHeaders_[constraints.getColumn()].setHasComponents( true );
1071 
1072                 // If the minimum and maximum sizes collide then minimum will
1073                 // always win.
1074                 if( tempColumnHeaders_[constraints.getColumn()].getMin()
1075                          > tempColumnHeaders_[constraints.getColumn()].getMax() ) {
1076                     tempColumnHeaders_[constraints.getColumn()].setMax(
1077                         tempColumnHeaders_[constraints.getColumn()].getMin() );
1078                 }
1079                 // If the minimum and preferred sizes collide then minimum will
1080                 // always win.
1081                 if( tempColumnHeaders_[constraints.getColumn()].getMin()
1082                          > tempColumnHeaders_[constraints.getColumn()].getPreferred() ) {
1083                     tempColumnHeaders_[constraints.getColumn()].setPreferred(
1084                         tempColumnHeaders_[constraints.getColumn()].getMin() );
1085                 }
1086             }
1087         }
1088 
1089         //
1090         // Do a second pass to handle objects that span multiple cells.
1091         //
1092         iterator = constraints_.iterator();
1093         while( iterator.hasNext() ) {
1094             entry = (Entry)iterator.next();
1095             constraints = entry.getConstraints();
1096 
1097             if( constraints.getObeyMinimumSize() ) {
1098                 minSize = getComponentMinimumSize( entry.getComponent() );
1099             }
1100             else {
1101                 minSize = new Dimension( 0, 0 );
1102             }
1103 
1104             if( constraints.getObeyMaximumSize() == true ) {
1105                 maxSize = getComponentMaximumSize( entry.getComponent() );
1106             }
1107             else {
1108                 maxSize = new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
1109             }
1110             preferredSize = getComponentPreferredSize( entry.getComponent() );
1111 
1112             // Handle the rows
1113             if( constraints.getRowSpan() != 1 ) {
1114                 adjustSizesForSpanning( constraints.getRow(), constraints.getRowSpan(),
1115                         tempRowHeaders_,
1116                         minSize.height, preferredSize.height, maxSize.height );
1117             }
1118 
1119             // Handle the columns
1120             if( constraints.getColumnSpan() != 1 ) {
1121                 adjustSizesForSpanning( constraints.getColumn(), constraints.getColumnSpan(),
1122                         tempColumnHeaders_,
1123                         minSize.width, preferredSize.width, maxSize.width );
1124             }
1125         }
1126 
1127         // Fix up the headers to ensure that:
1128         // minimum < preferred < maximum
1129         adjustHeaderSizes( tempRowHeaders_ );
1130         adjustHeaderSizes( tempColumnHeaders_ );
1131     }
1132 
1133 
1134     /***
1135      *  Adjust the various sizes to account for components than span multiple
1136      *  columns/rows.
1137      *
1138      * @param  start          The starting index of the component.
1139      * @param  span           The number of columns/rows that the component
1140      *      spans.
1141      * @param  sizes          The headers that we are adjusting.
1142      * @param  minSize        The minimum size of the component.
1143      * @param  preferredSize  The preferred size of the component.
1144      * @param  maxSize        The maximum size of the component.
1145      */
1146     private void adjustSizesForSpanning( final int start,
1147             final int span,
1148             final Header sizes[],
1149             final int minSize,
1150             final int preferredSize,
1151             final int maxSize ) {
1152         int combinedSize;
1153         int remainder;
1154         int i;
1155 
1156         final Header expandableHeaders[] = getExpandableHeaders( start, start + span, sizes );
1157 
1158         //TODO: Far too much duplicated code here.  This method needs to
1159         //      be refactored.
1160 
1161         //
1162         // Handle Minimum size
1163         //
1164         combinedSize = 0;
1165         for( i = 0; i < span; i++ ) {
1166             combinedSize += sizes[start + i].getMin();
1167         }
1168         if( minSize > combinedSize ) {
1169             if( expandableHeaders.length == 0 ) {
1170                 final int delta = ( minSize - combinedSize ) / span;
1171                 for( i = 0; i < span; i++ ) {
1172                     sizes[start + i].setMin( sizes[start + i].getMin() + delta );
1173                     combinedSize += delta;
1174                 }
1175                 // Use up the last bit.
1176                 remainder = minSize - combinedSize;
1177                 for( i = 0; i < remainder; i++ ) {
1178                     sizes[start + i].setMin( sizes[start + i].getMin() + 1 );
1179                 }
1180             }
1181             else {
1182                 final int delta = ( minSize - combinedSize ) / expandableHeaders.length;
1183                 for( i = 0; i < expandableHeaders.length; i++ ) {
1184                     expandableHeaders[i].setMin( expandableHeaders[i].getMin() + delta );
1185                     combinedSize += delta;
1186                 }
1187                 // Use up the last bit.
1188                 remainder = minSize - combinedSize;
1189                 for( i = 0; i < remainder; i++ ) {
1190                     expandableHeaders[i].setMin( expandableHeaders[i].getMin() + 1 );
1191                 }
1192             }
1193         }
1194 
1195         //
1196         // Handle preferred size
1197         //
1198         combinedSize = 0;
1199         for( i = 0; i < span; i++ ) {
1200             combinedSize += sizes[start + i].getPreferred();
1201         }
1202         if( preferredSize > combinedSize ) {
1203             if( expandableHeaders.length == 0 ) {
1204                 // None of the headers are marked expandable so expand them all a bit.
1205                 final int delta = ( preferredSize - combinedSize ) / span;
1206                 for( i = 0; i < span; i++ ) {
1207                     sizes[start + i].setPreferred( sizes[start + i].getPreferred() + delta );
1208                     combinedSize += delta;
1209                 }
1210                 // Use up the last bit.
1211                 remainder = preferredSize - combinedSize;
1212                 for( i = 0; i < remainder; i++ ) {
1213                     sizes[start + i].setPreferred( sizes[start + i].getPreferred() + 1 );
1214                 }
1215             }
1216             else {
1217                 // Only expand those that are explicitly marked as expandable.
1218                 final int delta = ( preferredSize - combinedSize ) / expandableHeaders.length;
1219                 for( i = 0; i < expandableHeaders.length; i++ ) {
1220                     expandableHeaders[i].setPreferred( expandableHeaders[i].getPreferred() + delta );
1221                     combinedSize += delta;
1222                 }
1223                 // Use up the last bit.
1224                 remainder = preferredSize - combinedSize;
1225                 for( i = 0; i < remainder; i++ ) {
1226                     expandableHeaders[i].setPreferred( expandableHeaders[i].getPreferred() + 1 );
1227                 }
1228             }
1229         }
1230     }
1231 
1232 
1233     /***
1234      *  Calculate all the positions of the various rows/columns
1235      *
1236      * @param  parent      Description of Parameter
1237      * @param  parentSize  Description of Parameter
1238      */
1239     private void calculatePositions( final Container parent,
1240             final Dimension parentSize ) {
1241         int i;
1242         int rowStart;
1243         int columnStart;
1244 
1245         final Insets insets = parent.getInsets();
1246 
1247         //
1248         // Account for stretching.
1249         //
1250         calculateActualSizes( parentSize );
1251 
1252         //
1253         // Determine the starting coordinates
1254         //
1255         switch ( verticalAlignment_ ) {
1256             case TOP:
1257                 rowStart = insets.top;
1258                 break;
1259             case BOTTOM:
1260                 rowStart = parentSize.height - actualSize_.height - insets.bottom;
1261                 break;
1262             case CENTER:
1263                 rowStart = ( parentSize.height - actualSize_.height
1264                          - insets.top - insets.bottom ) / 2 + insets.top;
1265                 break;
1266             default:
1267                 throw new IllegalStateException( "Unknown verticalAlignment: "
1268                          + verticalAlignment_ );
1269         }
1270 
1271         switch ( horizontalAlignment_ ) {
1272             case LEFT:
1273                 columnStart = insets.left;
1274                 break;
1275             case RIGHT:
1276                 columnStart = parentSize.width - actualSize_.width - insets.right;
1277                 break;
1278             case CENTER:
1279                 columnStart = ( parentSize.width - actualSize_.width
1280                          - insets.left - insets.right ) / 2 + insets.left;
1281                 break;
1282             default:
1283                 throw new IllegalStateException( "Unknown horizontalAlignment: "
1284                          + horizontalAlignment_ );
1285         }
1286 
1287         tempRowHeaders_[0].setStart( rowStart );
1288         for( i = 1; i < rowCount_; i++ ) {
1289             tempRowHeaders_[i].setStart( tempRowHeaders_[i - 1].getStart() + tempRowHeaders_[i - 1].getActual() );
1290         }
1291 
1292         tempColumnHeaders_[0].setStart( columnStart );
1293         for( i = 1; i < columnCount_; i++ ) {
1294             tempColumnHeaders_[i].setStart( tempColumnHeaders_[i - 1].getStart()
1295                      + tempColumnHeaders_[i - 1].getActual() );
1296         }
1297 
1298         // Dump out a bunch of tracing information
1299         if( traceChannel_ != null ) {
1300             Trace.println( traceChannel_,
1301                     "TableLayout.calculatePositions() START parentSize="
1302                      + parentSize );
1303             for( i = 0; i < rowCount_; i++ ) {
1304                 Trace.println( traceChannel_,
1305                         "   tempRowSizes[" + i + "]="
1306                          + tempRowHeaders_[i] );
1307             }
1308             for( i = 0; i < columnCount_; i++ ) {
1309                 Trace.println( traceChannel_,
1310                         "   tempColumnSizes[" + i + "]="
1311                          + tempColumnHeaders_[i] );
1312             }
1313             final Component children[] = parent_.getComponents();
1314             StringBuffer buffer;
1315             TableLayoutConstraints constraints;
1316             for( i = 0; i < children.length; i++ ) {
1317                 constraints = getConstraints( children[i] );
1318                 buffer = new StringBuffer();
1319                 buffer.append( "   children[" + i + "] min=" );
1320                 buffer.append( toString( children[i].getMinimumSize() ) );
1321                 buffer.append( " preferred=" );
1322                 buffer.append( toString( children[i].getPreferredSize() ) );
1323                 buffer.append( " visible=[" );
1324                 buffer.append( children[i].isVisible() );
1325                 buffer.append( " type=[" );
1326                 buffer.append( children[i].getClass().getName() );
1327                 buffer.append( "] name=[" );
1328                 buffer.append( children[i].getName() );
1329                 buffer.append( "] row=[" );
1330                 buffer.append( constraints.getRow() );
1331                 buffer.append( "+" );
1332                 buffer.append( constraints.getRowSpan() );
1333                 buffer.append( "] column=[" );
1334                 buffer.append( constraints.getColumn() );
1335                 buffer.append( "+" );
1336                 buffer.append( constraints.getColumnSpan() );
1337                 buffer.append( "]" );
1338                 Trace.println( traceChannel_, buffer.toString() );
1339             }
1340             Trace.println( traceChannel_,
1341                     "TableLayout.calculatePositions() END" );
1342         }
1343     }
1344 
1345 
1346     /***
1347      *  Position one component given the bounding coordinates
1348      *
1349      * @param  entry   Description of Parameter
1350      * @param  x       Description of Parameter
1351      * @param  y       Description of Parameter
1352      * @param  width   Description of Parameter
1353      * @param  height  Description of Parameter
1354      */
1355     private void positionComponent( final Entry entry,
1356             final int x, final int y,
1357             final int width, final int height ) {
1358 
1359         final TableLayoutConstraints constraints = entry.getConstraints();
1360         final Dimension maxSize;
1361         // TODO: Investigate why we're using preferred size but calling it minimum size
1362         final Dimension minSize = getComponentPreferredSize( entry.getComponent() );
1363         int newWidth;
1364         int newHeight;
1365         final int newX;
1366         final int newY;
1367 
1368         if( constraints.getObeyMaximumSize() == true ) {
1369             maxSize = getComponentMaximumSize( entry.getComponent() );
1370         }
1371         else {
1372             maxSize = new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
1373         }
1374 
1375         if( constraints.getVerticalStretch() ) {
1376             newHeight = Math.min( maxSize.height, height );
1377         }
1378         else {
1379             newHeight = minSize.height;
1380         }
1381 
1382         if( constraints.getHorizontalStretch() ) {
1383             newWidth = Math.min( maxSize.width, width );
1384         }
1385         else {
1386             newWidth = minSize.width;
1387         }
1388 
1389         if( newHeight > height ) {
1390             newHeight = height;
1391         }
1392         if( newWidth > width ) {
1393             newWidth = width;
1394         }
1395 
1396         switch ( constraints.getVerticalAlignment() ) {
1397             case TOP:
1398                 newY = y;
1399                 break;
1400             case BOTTOM:
1401                 newY = y + ( height - newHeight );
1402                 break;
1403             case CENTER:
1404                 newY = y + ( height - newHeight ) / 2;
1405                 break;
1406             default:
1407                 throw new IllegalStateException(
1408                         "Illegal value for verticalAlignment: "
1409                          + constraints.getVerticalAlignment() );
1410         }
1411 
1412         switch ( constraints.getHorizontalAlignment() ) {
1413             case LEFT:
1414                 newX = x;
1415                 break;
1416             case RIGHT:
1417                 newX = x + ( width - newWidth );
1418                 break;
1419             case CENTER:
1420                 newX = x + ( width - newWidth ) / 2;
1421                 break;
1422             default:
1423                 throw new IllegalStateException(
1424                         "Illegal value for horizontalAlignment: "
1425                          + constraints.getVerticalAlignment() );
1426         }
1427 
1428         entry.getComponent().setBounds( newX, newY, newWidth, newHeight );
1429     }
1430 
1431 
1432     /***
1433      *  The list of constraints has been modified. Update the row and column
1434      *  counts according to the new constraints.
1435      */
1436     private void calculateRowAndColumnCount() {
1437         Entry entry;
1438         int lastRow = -1;
1439         int lastColumn = -1;
1440         int row;
1441         int column;
1442 
1443         Iterator iterator;
1444         TableLayoutConstraints constraints;
1445 
1446         iterator = constraints_.iterator();
1447         while( iterator.hasNext() ) {
1448             entry = (Entry)iterator.next();
1449             constraints = entry.getConstraints();
1450 
1451             row = constraints.getRow()
1452                      + constraints.getRowSpan();
1453             if( row > lastRow ) {
1454                 lastRow = row;
1455             }
1456 
1457             column = constraints.getColumn()
1458                      + constraints.getColumnSpan();
1459             if( column > lastColumn ) {
1460                 lastColumn = column;
1461             }
1462         }
1463 
1464         HeaderPermanentInfo info;
1465 
1466         // Walk through the permanent infos to see if we have any for
1467         // rows or columns that aren't accounted for above.
1468 
1469         iterator = rowHeaderPermanentInfos_.iterator();
1470         while( iterator.hasNext() ) {
1471             info = (HeaderPermanentInfo)iterator.next();
1472             if( info.getIndex() > lastRow ) {
1473                 lastRow = info.getIndex();
1474             }
1475         }
1476 
1477         iterator = columnHeaderPermanentInfos_.iterator();
1478         while( iterator.hasNext() ) {
1479             info = (HeaderPermanentInfo)iterator.next();
1480             if( info.getIndex() > lastColumn ) {
1481                 Trace.println( traceChannel_,
1482                         "Increasing column count to " + info.getIndex() );
1483                 lastColumn = info.getIndex();
1484             }
1485         }
1486 
1487         rowCount_ = lastRow + 1;
1488         columnCount_ = lastColumn + 1;
1489 
1490         // KLUDGE: In some circumstances, it is possible to end up with
1491         // one count being zero and the other non-zero.  This is a bug
1492         // but unfortunately, I haven't been able to reproduce it in a
1493         // testcase.  The workaround is to set both counts to zero if
1494         // either one is zero.  Ugh!
1495         if( rowCount_ == 0 || columnCount_ == 0 ) {
1496             rowCount_ = 0;
1497             columnCount_ = 0;
1498         }
1499 
1500         if( traceChannel_ != null ) {
1501             Trace.println( traceChannel_,
1502                     "calculateRowAndColumnCount() rowCount="
1503                      + rowCount_
1504                      + " columnCount_="
1505                      + columnCount_ );
1506         }
1507     }
1508 
1509 
1510     /***
1511      *  Calculate the actual sizes to be used based on the actual dimension of
1512      *  the parent container.
1513      *
1514      * @param  parentSize  Description of Parameter
1515      */
1516     private void calculateActualSizes( final Dimension parentSize ) {
1517         final Dimension preferredSize = preferredLayoutSize( parent_ );
1518         final Insets insets = parent_.getInsets();
1519 
1520         final int x = calculateActualSizes( tempColumnHeaders_,
1521                 preferredSize.width,
1522                 parentSize.width - insets.left - insets.right );
1523         final int y = calculateActualSizes( tempRowHeaders_,
1524                 preferredSize.height,
1525                 parentSize.height - insets.top - insets.bottom );
1526         actualSize_ = new Dimension( x, y );
1527     }
1528 
1529 
1530     /***
1531      *  Fix up all the headers such that minimum <= preferred <= maximum
1532      *
1533      * @param  sizes  Description of Parameter
1534      */
1535     private void adjustHeaderSizes( final Header sizes[] ) {
1536         int i;
1537         Header header;
1538 
1539         for( i = 0; i < sizes.length; i++ ) {
1540             header = sizes[i];
1541             if( header.getMin() > header.getPreferred() ) {
1542                 header.setPreferred( header.getMin() );
1543             }
1544             if( header.getMin() > header.getMax() ) {
1545                 header.setMax( header.getMin() );
1546             }
1547             if( header.getPreferred() > header.getMax() ) {
1548                 header.setPreferred( header.getMax() );
1549             }
1550         }
1551     }
1552 
1553 
1554     /***
1555      *  Calculate the actual sizes for the specified row or column headers.
1556      *  Return the actual length.
1557      *
1558      * @param  sizes            Description of Parameter
1559      * @param  preferredLength  Description of Parameter
1560      * @param  clipLength       Description of Parameter
1561      * @return                  Description of the Returned Value
1562      */
1563     private int calculateActualSizes( final Header sizes[],
1564             final int preferredLength,
1565             final int clipLength ) {
1566 
1567         int i;
1568 
1569         if( clipLength < 1 ) {
1570             // The actual length is not big enough to even display the borders properly
1571             // therefore all components will have to have a zero size
1572             for( i = 0; i < sizes.length; i++ ) {
1573                 sizes[i].setActual( 0 );
1574             }
1575             return 0;
1576         }
1577 
1578         // If the parent is big enough to hold the preferred size then expand
1579         // all components to their preferred sizes.
1580         if( preferredLength <= clipLength ) {
1581             for( i = 0; i < sizes.length; i++ ) {
1582                 sizes[i].setActual( sizes[i].getPreferred() );
1583             }
1584 
1585             if( preferredLength < clipLength ) {
1586                 expandToFit( sizes, clipLength );
1587             }
1588         }
1589         else {
1590             shrinkToFit( sizes, clipLength );
1591         }
1592 
1593         int actualLength = 0;
1594         for( i = 0; i < sizes.length; i++ ) {
1595             actualLength += sizes[i].getActual();
1596         }
1597 
1598         return actualLength;
1599     }
1600 
1601 
1602     /***
1603      *  Expand the specified sizes to fit within the specified clipLength.
1604      *
1605      * @param  sizes       Description of Parameter
1606      * @param  clipLength  Description of Parameter
1607      */
1608     private void expandToFit( final Header sizes[], final int clipLength ) {
1609 
1610         int i;
1611         int numberExpandable = 0;
1612         int currentLength = 0;
1613 
1614         for( i = 0; i < sizes.length; i++ ) {
1615             if( sizes[i].isExpandable() == true ) {
1616                 numberExpandable++;
1617             }
1618             currentLength += sizes[i].getActual();
1619         }
1620 
1621         // If none of the sizes can be expanded then just return.
1622         if( numberExpandable == 0 ) {
1623             return;
1624         }
1625         int addAmount = ( clipLength - currentLength ) / numberExpandable;
1626         for( i = 0; i < sizes.length; i++ ) {
1627             if( sizes[i].isExpandable() == true ) {
1628                 sizes[i].setActual( sizes[i].getActual() + addAmount );
1629             }
1630         }
1631 
1632         // TODO: Clean up this loop
1633         int remaining = ( clipLength - currentLength )
1634                  - ( addAmount * numberExpandable );
1635         if( remaining != 0 ) {
1636             for( i = 0; i < sizes.length; i++ ) {
1637                 if( sizes[i].isExpandable() == true ) {
1638                     sizes[i].setActual( sizes[i].getActual() + 1 );
1639                     remaining--;
1640                     if( remaining == 0 ) {
1641                         break;
1642                     }
1643                 }
1644             }
1645         }
1646 
1647     }
1648 
1649 
1650     /***
1651      *  Shrink the specified sizes to fit within the specified clipLength. Do
1652      *  not shrink beyond the minimum size.
1653      *
1654      * @param  sizes       Description of Parameter
1655      * @param  clipLength  Description of Parameter
1656      */
1657     private void shrinkToFit( final Header sizes[], final int clipLength ) {
1658         if( clipLength < 0 ) {
1659             throw new DetailedIllegalArgumentException(
1660                 "clipLength", new Integer(clipLength), "may not be negative" );
1661         }
1662 
1663         int i;
1664         int remaining = clipLength;
1665 
1666         // Initialize all sizes to their minimum values
1667         for( i = 0; i < sizes.length; i++ ) {
1668             sizes[i].setActual( sizes[i].getMin() );
1669             remaining -= sizes[i].getActual();
1670         }
1671 
1672         if( remaining < 0 ) {
1673             // The clipLength is smaller then the minimum sizes.  Since we
1674             // can't shrink beyond minimum size, return now.
1675             return;
1676         }
1677 
1678         // Now enlarge each one until either we've reached the size we want or
1679         // we can't add any more
1680         int delta;
1681         int addAmount = 1;
1682         int numberChanged = sizes.length;
1683         while( numberChanged != 0 ) {
1684 
1685             // TODO: Clean up this loop
1686             addAmount = remaining / numberChanged;
1687             if( addAmount == 0 ) {
1688                 addAmount = 1;
1689             }
1690 
1691             numberChanged = 0;
1692             for( i = 0; i < sizes.length; i++ ) {
1693                 delta = sizes[i].getPreferred() - sizes[i].getActual();
1694 
1695                 if( delta > 0 ) {
1696                     delta = Math.min( delta, addAmount );
1697                     sizes[i].setActual( sizes[i].getActual() + delta );
1698                     numberChanged++;
1699                     remaining -= delta;
1700                     if( remaining == 0 ) {
1701                         return;
1702                     }
1703                 }
1704             }
1705         }
1706     }
1707 
1708 
1709     /***
1710      *  Initialize the temporary arrays (tempRowHeaders_ and tempColumnHeaders_)
1711      *  for use in a calculation.
1712      */
1713     private void initTempSizes() {
1714         int i;
1715 
1716         //TODO: We have potential lingerers here.  Null out the tempRowHeaders
1717         // when they aren't needed anymore.
1718         //
1719         // Ensure that the temp arrays are of the correct size
1720         //
1721         if( ( tempRowHeaders_ == null ) || ( tempRowHeaders_.length != rowCount_ ) ) {
1722             tempRowHeaders_ = new Header[rowCount_];
1723             for( i = 0; i < rowCount_; i++ ) {
1724                 tempRowHeaders_[i] = new Header();
1725             }
1726         }
1727         if( ( tempColumnHeaders_ == null ) || ( tempColumnHeaders_.length != columnCount_ ) ) {
1728             tempColumnHeaders_ = new Header[columnCount_];
1729             for( i = 0; i < columnCount_; i++ ) {
1730                 tempColumnHeaders_[i] = new Header();
1731             }
1732         }
1733 
1734         //
1735         // Initialize the temp vars to known values.
1736         //
1737         for( i = 0; i < tempRowHeaders_.length; i++ ) {
1738             tempRowHeaders_[i].setMin( getMinimumRowHeight( i ) );
1739             tempRowHeaders_[i].setPreferred( getMinimumRowHeight( i ) );
1740             tempRowHeaders_[i].setMax( Integer.MAX_VALUE );
1741             tempRowHeaders_[i].setHasComponents( false );
1742             tempRowHeaders_[i].setExpandable( isRowExpandable( i ) );
1743         }
1744         for( i = 0; i < tempColumnHeaders_.length; i++ ) {
1745             tempColumnHeaders_[i].setMin( getMinimumColumnWidth( i ) );
1746             tempColumnHeaders_[i].setPreferred( getMinimumColumnWidth( i ) );
1747             tempColumnHeaders_[i].setMax( Integer.MAX_VALUE );
1748             tempColumnHeaders_[i].setHasComponents( false );
1749             tempColumnHeaders_[i].setExpandable( isColumnExpandable( i ) );
1750         }
1751     }
1752 
1753 
1754     /***
1755      *  A convenience class to attach the constraints to a component.
1756      */
1757     private class Entry implements Serializable {
1758         private static final long serialVersionUID = 396191633848670929L;
1759 
1760         /***
1761          *  Description of the Field
1762          */
1763         private final Component component_;
1764         /***
1765          *  Description of the Field
1766          */
1767         private final TableLayoutConstraints constraints_;
1768 
1769 
1770         /***
1771          *  Constructor for the Entry object
1772          *
1773          * @param  comp         Description of Parameter
1774          * @param  constraints  Description of Parameter
1775          */
1776         public Entry( final Component comp,
1777                 final TableLayoutConstraints constraints ) {
1778             component_ = comp;
1779             constraints_ = constraints;
1780         }
1781 
1782 
1783         // Accessors
1784         /***
1785          *  Gets the component attribute of the Entry object
1786          *
1787          * @return    The component value
1788          */
1789         public final Component getComponent() {
1790             return component_;
1791         }
1792 
1793 
1794         /***
1795          *  Gets the constraints attribute of the Entry object
1796          *
1797          * @return    The constraints value
1798          */
1799         public final TableLayoutConstraints getConstraints() {
1800             return constraints_;
1801         }
1802 
1803 
1804         /***
1805          *  Description of the Method
1806          *
1807          * @return    Description of the Returned Value
1808          */
1809         public String toString() {
1810             return this.getClass().getName()
1811                      + " component_=" + getComponent()
1812                      + " constraints_=" + getConstraints();
1813         }
1814     }
1815 
1816 
1817     /***
1818      *  A convenience class to hold information specific to a row or column.
1819      */
1820     private class Header implements Serializable {
1821         private static final long serialVersionUID = 396191633848670929L;
1822 
1823         private int min_;
1824         private int max_;
1825         private int preferred_;
1826         private int actual_;
1827 
1828         private int start_;
1829         private boolean hasComponents_;
1830         private boolean isExpandable_;
1831 
1832 
1833         // Accessors
1834         /***
1835          *  Sets the actual attribute of the Header object
1836          *
1837          * @param  actual  The new actual value
1838          */
1839         public final void setActual( int actual ) {
1840             actual_ = actual;
1841         }
1842 
1843 
1844         /***
1845          *  Sets the preferred attribute of the Header object
1846          *
1847          * @param  preferred  The new preferred value
1848          */
1849         public final void setPreferred( int preferred ) {
1850             preferred_ = preferred;
1851         }
1852 
1853 
1854         /***
1855          *  Sets the min attribute of the Header object
1856          *
1857          * @param  min  The new min value
1858          */
1859         public final void setMin( int min ) {
1860             min_ = min;
1861         }
1862 
1863 
1864         /***
1865          *  Sets the max attribute of the Header object
1866          *
1867          * @param  max  The new max value
1868          */
1869         public final void setMax( int max ) {
1870             max_ = max;
1871         }
1872 
1873 
1874         /***
1875          *  Sets the start attribute of the Header object
1876          *
1877          * @param  start  The new start value
1878          */
1879         public final void setStart( int start ) {
1880             start_ = start;
1881         }
1882 
1883 
1884         /***
1885          *  Sets the hasComponents attribute of the Header object
1886          *
1887          * @param  hasComponents  The new hasComponents value
1888          */
1889         public final void setHasComponents( boolean hasComponents ) {
1890             hasComponents_ = hasComponents;
1891         }
1892 
1893 
1894         /***
1895          *  Sets the expandable attribute of the Header object
1896          *
1897          * @param  expandable  The new expandable value
1898          */
1899         public final void setExpandable( boolean expandable ) {
1900             isExpandable_ = expandable;
1901         }
1902 
1903 
1904         /***
1905          *  Gets the actual attribute of the Header object
1906          *
1907          * @return    The actual value
1908          */
1909         public final int getActual() {
1910             return actual_;
1911         }
1912 
1913 
1914         /***
1915          *  Gets the preferred attribute of the Header object
1916          *
1917          * @return    The preferred value
1918          */
1919         public final int getPreferred() {
1920             return preferred_;
1921         }
1922 
1923 
1924         /***
1925          *  Gets the min attribute of the Header object
1926          *
1927          * @return    The min value
1928          */
1929         public final int getMin() {
1930             return min_;
1931         }
1932 
1933 
1934         /***
1935          *  Gets the max attribute of the Header object
1936          *
1937          * @return    The max value
1938          */
1939         public final int getMax() {
1940             return max_;
1941         }
1942 
1943 
1944         /***
1945          *  Gets the start attribute of the Header object
1946          *
1947          * @return    The start value
1948          */
1949         public final int getStart() {
1950             return start_;
1951         }
1952 
1953 
1954         /***
1955          *  Gets the hasComponents attribute of the Header object
1956          *
1957          * @return    The hasComponents value
1958          */
1959         public final boolean getHasComponents() {
1960             return hasComponents_;
1961         }
1962 
1963 
1964         /***
1965          *  Gets the expandable attribute of the Header object
1966          *
1967          * @return    The expandable value
1968          */
1969         public final boolean isExpandable() {
1970             return isExpandable_;
1971         }
1972 
1973 
1974         /***
1975          *  Description of the Method
1976          *
1977          * @return    Description of the Returned Value
1978          */
1979         public String toString() {
1980             final StringBuffer buffer = new StringBuffer();
1981             buffer.append( "TableLayout.Header[" );
1982             buffer.append( " min=[" );
1983             buffer.append( getMin() );
1984             buffer.append( "] max=[" );
1985             final int max = getMax();
1986             if( max == Integer.MAX_VALUE ) {
1987                 buffer.append( "max_int" );
1988             }
1989             else {
1990                 buffer.append( max );
1991             }
1992             buffer.append( "] preferred=[" );
1993             buffer.append( getPreferred() );
1994             buffer.append( "] start=[" );
1995             buffer.append( getStart() );
1996             buffer.append( "] actual=[" );
1997             buffer.append( getActual() );
1998             buffer.append( "] hasComponents=[" );
1999             buffer.append( getHasComponents() );
2000             buffer.append( "] isExpandable=[" );
2001             buffer.append( isExpandable() );
2002             buffer.append( "]]" );
2003 
2004             return buffer.toString();
2005         }
2006     }
2007 
2008 
2009     /***
2010      *  Contains the information that the user has specified for the specific
2011      *  row or column.
2012      */
2013     //TODO: Document why "permanent".  Perhaps rename these to "user specified"?
2014     private class HeaderPermanentInfo implements Serializable {
2015         private static final long serialVersionUID = 396191633848670929L;
2016         private final int index_;
2017         // row or column number
2018         private int min_;
2019         private boolean isExpandable_;
2020 
2021 
2022         /***
2023          *  Constructor for the HeaderPermanentInfo object
2024          *
2025          * @param  newIndex  Description of Parameter
2026          */
2027         public HeaderPermanentInfo( final int newIndex ) {
2028             if( newIndex < 0 ) {
2029                 throw new DetailedIllegalArgumentException( "newIndex", newIndex, "May not be negative" );
2030             }
2031             index_ = newIndex;
2032             min_ = 0;
2033             isExpandable_ = false;
2034         }
2035 
2036 
2037         /***
2038          *  Sets the min attribute of the HeaderPermanentInfo object
2039          *
2040          * @param  min  The new min value
2041          */
2042         public final void setMin( final int min ) {
2043             min_ = min;
2044         }
2045 
2046 
2047         /***
2048          *  Sets the expandable attribute of the HeaderPermanentInfo object
2049          *
2050          * @param  expandable  The new expandable value
2051          */
2052         public final void setExpandable( final boolean expandable ) {
2053             isExpandable_ = expandable;
2054         }
2055 
2056 
2057         /***
2058          *  Gets the min attribute of the HeaderPermanentInfo object
2059          *
2060          * @return    The min value
2061          */
2062         public final int getMin() {
2063             return min_;
2064         }
2065 
2066 
2067         /***
2068          *  Gets the index attribute of the HeaderPermanentInfo object
2069          *
2070          * @return    The index value
2071          */
2072         public final int getIndex() {
2073             return index_;
2074         }
2075 
2076 
2077         /***
2078          *  Gets the expandable attribute of the HeaderPermanentInfo object
2079          *
2080          * @return    The expandable value
2081          */
2082         public final boolean isExpandable() {
2083             return isExpandable_;
2084         }
2085     }
2086 
2087 
2088     /***
2089      * Verify that the specified value is not null.  If it is then throw an exception
2090      *
2091      * @param fieldName The name of the field to check
2092      * @param fieldValue The value of the field to check
2093      * @exception DetailedNullPointerException If fieldValue is null
2094      */
2095     protected final void assertNotNull( final String fieldName, final Object fieldValue )
2096         throws DetailedNullPointerException {
2097 
2098         if( fieldValue == null ) {
2099             throw new DetailedNullPointerException(fieldName);
2100         }
2101     }
2102 }
2103 
2104