View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Blacksmith, Inc.
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.ui.swing.components;
20  
21  import java.awt.Component;
22  import java.awt.Container;
23  import java.awt.Dimension;
24  import java.awt.FlowLayout;
25  import java.awt.Insets;
26  
27  /***
28   * BetterFlowLayout works just like FlowLayout, except that
29   * you can specify a vertical orientation in addition to the
30   * usual horizontal orientations.  You can also specify that
31   * all the components be sized to the same height and/or width.
32   * By default, the behavior is identical to FlowLayout.
33   *
34   * @author michael@mpowers.net
35   * @author $Author: cgruber $
36   * @version $Revision: 904 $
37   * $Date: 2006-02-18 23:19:05 +0000 (Sat, 18 Feb 2006) $
38   */
39  public class BetterFlowLayout extends FlowLayout {
40  
41      /***
42       * This value indicates vertical orientation and
43       * that each column of components should be top-justified.
44       */
45      public static final int TOP 	= 32;
46  
47      /***
48       * This value indicates vertical orientation and
49       * that each column of components should be centered.
50       */
51      public static final int CENTER_VERTICAL	= 16;
52  
53      /***
54       * This value indicates vertical orientation and
55       * that each column of components should be bottom-justified.
56       */
57      public static final int BOTTOM 	= 8;
58  
59      /***
60       * Tracks orientation.
61       */
62      protected boolean isHorizontal = true;
63  
64      /***
65       * Tracks component sizing of width.
66       */
67      protected boolean isWidthUniform = false;
68      /***
69       * Tracks component sizing of height.
70       */
71      protected boolean isHeightUniform = false;
72  
73      /***
74       * Constructs a new Flow Layout with a centered alignment and a
75       * default 5-unit horizontal and vertical gap.
76       */
77      public BetterFlowLayout() {
78  	this(CENTER, 5, 5);
79      }
80  
81      /***
82       * Constructs a new Flow Layout with the specified alignment and a
83       * default 5-unit horizontal and vertical gap.
84       * The value of the alignment argument must be one of
85       * <code>BetterFlowLayout.LEFT</code>, <code>BetterFlowLayout.RIGHT</code>,
86       * or <code>BetterFlowLayout.CENTER</code>.
87       * @param align the alignment value
88       */
89      public BetterFlowLayout(int align) {
90  	this(align, 5, 5);
91      }
92  
93      /***
94       * Creates a new flow layout manager with the indicated alignment
95       * and the indicated horizontal and vertical gaps.
96       * <p>
97       * The value of the alignment argument must be one of
98       * <code>BetterFlowLayout.LEFT</code>, <code>BetterFlowLayout.RIGHT</code>,
99       * or <code>BetterFlowLayout.CENTER</code>.
100      * @param      align   the alignment value.
101      * @param      hgap    the horizontal gap between components.
102      * @param      vgap    the vertical gap between components.
103      */
104     public BetterFlowLayout(int align, int hgap, int vgap) {
105     	setHgap(hgap);
106 	    setVgap(vgap);
107         setAlignment(align);
108     }
109 
110     /***
111      * Sets whether all components should have the same height.
112      * @param isUniform the new value.
113      * @see #isHeightUniform
114      */
115     public void setHeightUniform(boolean isUniform) {
116         isHeightUniform = isUniform;
117     }
118 
119     /***
120      * Sets whether all components should have the same width.
121      * @param isUniform the new value.
122      * @see #isWidthUniform
123      */
124     public void setWidthUniform(boolean isUniform) {
125         isWidthUniform = isUniform;
126     }
127 
128     /***
129      * Determines whether all components will have the same height.
130      * The uniform height will be the maximum of the preferred heights
131      * of all the components in the container.
132      * This value defaults to false.
133      * @return whether components will have the same height.
134      */
135     public boolean isHeightUniform() {
136         return isHeightUniform;
137     }
138 
139     /***
140      * Determines whether all components will have the same width.
141      * The uniform height will be the maximum of the preferred widths
142      * of all the components in the container.
143      * This value defaults to false.
144      * @return whether components will have the same width.
145      */
146     public boolean isWidthUniform() {
147         return isWidthUniform;
148     }
149 
150     /***
151      * Sets the alignment for this layout.
152      * Possible values for horizontal orientation are <code>LEFT</code>,
153      * <code>RIGHT</code>, and <code>CENTER</code>.
154      * Possible values for vertical orientation are <code>TOP</code>,
155      * <code>BOTTOM</code>, and <code>CENTER_VERTICAL</code>.
156      * @param      align the alignment value.
157      * @see        java.awt.FlowLayout#getAlignment
158      */
159     public void setAlignment(int align) {
160         if ( ( align == TOP ) || ( align == BOTTOM ) || ( align == CENTER_VERTICAL ) )
161         {
162             isHorizontal = false;
163         }
164         else
165         {
166             isHorizontal = true;
167         }
168 
169         super.setAlignment( align );
170     }
171 
172     /***
173      * Returns the preferred dimensions for this layout given the components
174      * in the specified target container.
175      * @param target the component which needs to be laid out
176      * @return    the preferred dimensions to lay out the
177      *                    subcomponents of the specified container.
178      * @see Container
179      * @see #minimumLayoutSize
180      * @see       java.awt.Container#getPreferredSize
181      */
182     public Dimension preferredLayoutSize(Container target) {
183         if ( isHorizontal ) {
184             return preferredLayoutSizeHorizontal( target );
185         } else {
186             return preferredLayoutSizeVertical( target );
187         }
188     }
189 
190     /***
191      * Returns the preferred dimensions for this layout given the components
192      * in the specified target container.
193      * @param target the component which needs to be laid out
194      * @return    the preferred dimensions to lay out the
195      *                    subcomponents of the specified container.
196      * @see Container
197      * @see #minimumLayoutSize
198      * @see       java.awt.Container#getPreferredSize
199      */
200     public Dimension preferredLayoutSizeHorizontal(Container target) {
201       synchronized (target.getTreeLock()) {
202         Dimension dim = new Dimension(0, 0);
203         int nmembers = target.getComponentCount();
204         int maxWidth = 0;
205 
206         for (int i = 0 ; i < nmembers ; i++) {
207             Component m = target.getComponent(i);
208             if (m.isVisible()) {
209             Dimension d = m.getPreferredSize();
210             dim.height = Math.max(dim.height, d.height);
211             maxWidth = Math.max(maxWidth, d.width);
212             if (i > 0) {
213                 dim.width += getHgap();
214             }
215             dim.width += d.width;
216             }
217         }
218         if ( isWidthUniform )
219             dim.width = ( maxWidth + getHgap() ) * nmembers - getHgap();
220         Insets insets = target.getInsets();
221         dim.width += insets.left + insets.right + getHgap()*2;
222         dim.height += insets.top + insets.bottom + getVgap()*2;
223         return dim;
224       }
225     }
226 
227     /***
228      * Returns the preferred dimensions for this layout given the components
229      * in the specified target container.
230      * @param target the component which needs to be laid out
231      * @return    the preferred dimensions to lay out the
232      *                    subcomponents of the specified container.
233      * @see Container
234      * @see #minimumLayoutSize
235      * @see       java.awt.Container#getPreferredSize
236      */
237     public Dimension preferredLayoutSizeVertical(Container target) {
238       synchronized (target.getTreeLock()) {
239         Dimension dim = new Dimension(0, 0);
240         int nmembers = target.getComponentCount();
241         int maxHeight = 0;
242 
243         for (int i = 0 ; i < nmembers ; i++) {
244             Component m = target.getComponent(i);
245             if (m.isVisible()) {
246             Dimension d = m.getPreferredSize();
247             dim.width = Math.max(dim.width, d.width);
248             maxHeight = Math.max(maxHeight, d.height);
249             if (i > 0) {
250                 dim.height += getVgap();
251             }
252             dim.height += d.height;
253             }
254         }
255         if ( isHeightUniform )
256             dim.height = ( maxHeight + getVgap() ) * nmembers - getVgap();
257         Insets insets = target.getInsets();
258         dim.width += insets.left + insets.right + getHgap()*2;
259         dim.height += insets.top + insets.bottom + getVgap()*2;
260         return dim;
261       }
262     }
263 
264     /***
265      * Returns the minimum dimensions needed to layout the components
266      * contained in the specified target container.
267      * @param target the component which needs to be laid out
268      * @return    the minimum dimensions to lay out the
269      *                    subcomponents of the specified container.
270      * @see #preferredLayoutSize
271      * @see       java.awt.Container
272      * @see       java.awt.Container#doLayout
273      */
274     public Dimension minimumLayoutSize(Container target) {
275         // preferred size is also the minimum size
276         if ( isHorizontal ) {
277             return preferredLayoutSizeHorizontal( target );
278         } else {
279             return preferredLayoutSizeVertical( target );
280         }
281     }
282 
283     /***
284      * Lays out the container. This method lets each component take
285      * its preferred size by reshaping the components in the
286      * target container in order to satisfy the constraints of
287      * this <code>BetterFlowLayout</code> object.
288      * @param target the specified component being laid out.
289      * @see Container
290      * @see       java.awt.Container#doLayout
291      */
292     public void layoutContainer(Container target) {
293         if ( isHorizontal ) {
294             layoutContainerHorizontal( target );
295         } else {
296             layoutContainerVertical( target );
297         }
298     }
299 
300     /***
301      * Lays out the container. This method lets each component take
302      * its preferred size by reshaping the components in the
303      * target container in order to satisfy the constraints of
304      * this <code>BetterFlowLayout</code> object.
305      * @param target the specified component being laid out.
306      * @see Container
307      * @see       java.awt.Container#doLayout
308      */
309     protected void layoutContainerHorizontal(Container target) {
310       synchronized (target.getTreeLock()) {
311         Insets insets = target.getInsets();
312         int maxwidth = target.getSize().width - (insets.left + insets.right + getHgap()*2);
313         int nmembers = target.getComponentCount();
314         int x = 0, y = insets.top + getVgap();
315         int rowh = 0, start = 0;
316 
317         boolean ltr = true; // target.getComponentOrientation().isLeftToRight();
318         Dimension uniform = getUniformDimension( target );
319 
320         for (int i = 0 ; i < nmembers ; i++) {
321             Component m = target.getComponent(i);
322             if (m.isVisible()) {
323                 Dimension d = m.getPreferredSize();
324                 if ( isWidthUniform )
325                     d.width = uniform.width;
326                 if ( isHeightUniform )
327                     d.height = uniform.height;
328                 m.setSize(d.width, d.height);
329 
330                 if ((x == 0) || ((x + d.width) <= maxwidth)) {
331                     if (x > 0) {
332                         x += getHgap();
333                     }
334                     x += d.width;
335                     rowh = Math.max(rowh, d.height);
336                 } else {
337                     moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, i, ltr);
338                     x = d.width;
339                     y += getVgap() + rowh;
340                     rowh = d.height;
341                     start = i;
342                 }
343             }
344         }
345         moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, nmembers, ltr);
346       }
347     }
348 
349     /***
350      * Centers the elements in the specified row, if there is any slack.
351      * @param target the component which needs to be moved
352      * @param x the x coordinate
353      * @param y the y coordinate
354      * @param width the width dimensions
355      * @param height the height dimensions
356      * @param rowStart the beginning of the row
357      * @param rowEnd the the ending of the row
358      */
359     private void moveComponentsHorizontal(Container target, int x, int y, int width, int height,
360                                 int rowStart, int rowEnd, boolean ltr) {
361       synchronized (target.getTreeLock()) {
362 	switch (getAlignment()) {
363 	case LEFT:
364 	    x += ltr ? 0 : width;
365 	    break;
366 	case CENTER:
367 	    x += width / 2;
368 	    break;
369 	case RIGHT:
370 	    x += ltr ? width : 0;
371 	    break;
372 //1.2	case LEADING:
373 //1.2	    break;
374 //1.2	case TRAILING:
375 //1.2	    x += width;
376 //1.2	    break;
377 	}
378 	for (int i = rowStart ; i < rowEnd ; i++) {
379 	    Component m = target.getComponent(i);
380 	    if (m.isVisible()) {
381 	        if (ltr) {
382         	    m.setLocation(x, y + (height - m.getBounds().height) / 2);
383 	        } else {
384 	            m.setLocation(target.getBounds().width - x - m.getBounds().width, y + (height - m.getBounds().height) / 2);
385             }
386                 x += m.getBounds().width + getHgap();
387 	    }
388 	}
389       }
390     }
391 
392     /***
393      * Lays out the container. This method lets each component take
394      * its preferred size by reshaping the components in the
395      * target container in order to satisfy the constraints of
396      * this <code>BetterFlowLayout</code> object.
397      * @param target the specified component being laid out.
398      * @see Container
399      * @see       java.awt.Container#doLayout
400      */
401     protected void layoutContainerVertical(Container target) {
402       synchronized (target.getTreeLock()) {
403 
404         Insets insets = target.getInsets();
405         int maxheight = target.getBounds().height - (insets.top + insets.bottom + getVgap()*2);
406         int nmembers = target.getComponentCount();
407         int y = 0, x = insets.left + getHgap();
408         int colw = 0, start = 0;
409 
410         Dimension uniform = getUniformDimension( target );
411         for (int i = 0 ; i < nmembers ; i++) {
412             Component m = target.getComponent(i);
413             if (m.isVisible()) {
414                 Dimension d = m.getPreferredSize();
415                 if ( isWidthUniform )
416                     d.width = uniform.width;
417                 if ( isHeightUniform )
418                     d.height = uniform.height;
419                 m.setSize(d.width, d.height);
420 
421                 if ((y == 0) || ((y + d.height) <= maxheight)) {
422                     if (y > 0) {
423                     y += getVgap();
424                     }
425                     y += d.height;
426                     colw = Math.max(colw, d.width);
427                 } else {
428                     moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, i );
429                     y = d.height;
430                     x += getHgap() + colw;
431                     colw = d.width;
432                     start = i;
433                 }
434             }
435         }
436         moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, nmembers );
437       }
438     }
439 
440     /***
441      * Centers the elements in the specified row, if there is any slack.
442      * @param target the component which needs to be moved
443      * @param x the x coordinate
444      * @param y the y coordinate
445      * @param width the width dimensions
446      * @param height the height dimensions
447      * @param colStart the beginning of the column
448      * @param colEnd the the ending of the column
449      */
450     private void moveComponentsVertical(Container target, int x, int y, int width, int height,
451                                 int colStart, int colEnd) {
452       synchronized (target.getTreeLock()) {
453         switch (getAlignment()) {
454         case TOP:
455             y += 0;
456             break;
457         case CENTER_VERTICAL:
458             y += ( height / 2 ); // - preferredLayoutSize( target ).height ) / 2 );
459             break;
460         case BOTTOM:
461             y += height;
462             break;
463         }
464         for (int i = colStart ; i < colEnd ; i++) {
465             Component m = target.getComponent(i);
466             if (m.isVisible()) {
467                 m.setLocation(x + (width - m.getBounds().width) / 2, y );
468 //                m.setLocation(x, y );
469 //                m.setSize( width, m.getBounds().height ); //!
470                 y += m.getBounds().height + getVgap();
471             }
472         }
473       }
474     }
475 
476     /***
477      * Returns a dimension representing the maximum preferred
478      * height and width of all the components in the container.
479      * @param target the container to scan.
480      * @return a dimension containing the maximum values.
481      */
482     protected Dimension getUniformDimension(Container target) {
483         Component m = null;
484         Dimension preferred = null;
485         int maxWidth = 0, maxHeight = 0;
486         int nmembers = target.getComponentCount();
487         for ( int i = 0; i < nmembers; i++ ) {
488             m = target.getComponent( i );
489             if ( m.isVisible() ) {
490                 preferred = m.getPreferredSize();
491                 maxWidth = Math.max( maxWidth, preferred.width );
492                 maxHeight = Math.max( maxHeight, preferred.height );
493             }
494         }
495         return new Dimension( maxWidth, maxHeight );
496     }
497 
498     /***
499      * Returns a string representation of this <code>BetterFlowLayout</code>
500      * object and its values.
501      * @return     a string representation of this layout.
502      */
503     public String toString() {
504 	String str = "";
505 	switch (getAlignment()) {
506 	  case TOP:        str = ",align=top"; break;
507 	  case CENTER_VERTICAL:      str = ",align=vertical"; break;
508 	  case BOTTOM:       str = ",align=bottom"; break;
509       default: return super.toString();
510 	}
511 	return getClass().getName() + "[hgap=" + getHgap() + ",vgap=" + getVgap() + str + "]";
512     }
513 
514 
515 }