1 /*
2  * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 
27 package javax.swing;
28 
29 import java.awt.*;
30 import java.beans.ConstructorProperties;
31 import java.io.Serializable;
32 import java.io.PrintStream;
33 
34 /**
35  * A layout manager that allows multiple components to be laid out either
36  * vertically or horizontally. The components will not wrap so, for
37  * example, a vertical arrangement of components will stay vertically
38  * arranged when the frame is resized.
39  * <table class="borderless" style="float:right">
40  * <caption>Example:</caption>
41  *    <TR>
42  *      <TD style="text-align:center">
43  *         <P STYLE="TEXT-ALIGN:CENTER"><IMG SRC="doc-files/BoxLayout-1.gif"
44  *          alt="The following text describes this graphic."
45  *          WIDTH="191" HEIGHT="201" STYLE="FLOAT:BOTTOM; BORDER:0">
46  *      </TD>
47  *    </TR>
48  * </TABLE>
49  * <p>
50  * Nesting multiple panels with different combinations of horizontal and
51  * vertical gives an effect similar to GridBagLayout, without the
52  * complexity. The diagram shows two panels arranged horizontally, each
53  * of which contains 3 components arranged vertically.
54  *
55  * <p> The BoxLayout manager is constructed with an axis parameter that
56  * specifies the type of layout that will be done. There are four choices:
57  *
58  * <blockquote><b>{@code X_AXIS}</b> - Components are laid out horizontally
59  * from left to right.</blockquote>
60  *
61  * <blockquote><b>{@code Y_AXIS}</b> - Components are laid out vertically
62  * from top to bottom.</blockquote>
63  *
64  * <blockquote><b>{@code LINE_AXIS}</b> - Components are laid out the way
65  * words are laid out in a line, based on the container's
66  * {@code ComponentOrientation} property. If the container's
67  * {@code ComponentOrientation} is horizontal then components are laid out
68  * horizontally, otherwise they are laid out vertically.  For horizontal
69  * orientations, if the container's {@code ComponentOrientation} is left to
70  * right then components are laid out left to right, otherwise they are laid
71  * out right to left. For vertical orientations components are always laid out
72  * from top to bottom.</blockquote>
73  *
74  * <blockquote><b>{@code PAGE_AXIS}</b> - Components are laid out the way
75  * text lines are laid out on a page, based on the container's
76  * {@code ComponentOrientation} property. If the container's
77  * {@code ComponentOrientation} is horizontal then components are laid out
78  * vertically, otherwise they are laid out horizontally.  For horizontal
79  * orientations, if the container's {@code ComponentOrientation} is left to
80  * right then components are laid out left to right, otherwise they are laid
81  * out right to left.&nbsp; For vertical orientations components are always
82  * laid out from top to bottom.</blockquote>
83  * <p>
84  * For all directions, components are arranged in the same order as they were
85  * added to the container.
86  * <p>
87  * BoxLayout attempts to arrange components
88  * at their preferred widths (for horizontal layout)
89  * or heights (for vertical layout).
90  * For a horizontal layout,
91  * if not all the components are the same height,
92  * BoxLayout attempts to make all the components
93  * as high as the highest component.
94  * If that's not possible for a particular component,
95  * then BoxLayout aligns that component vertically,
96  * according to the component's Y alignment.
97  * By default, a component has a Y alignment of 0.5,
98  * which means that the vertical center of the component
99  * should have the same Y coordinate as
100  * the vertical centers of other components with 0.5 Y alignment.
101  * <p>
102  * Similarly, for a vertical layout,
103  * BoxLayout attempts to make all components in the column
104  * as wide as the widest component.
105  * If that fails, it aligns them horizontally
106  * according to their X alignments.  For {@code PAGE_AXIS} layout,
107  * horizontal alignment is done based on the leading edge of the component.
108  * In other words, an X alignment value of 0.0 means the left edge of a
109  * component if the container's {@code ComponentOrientation} is left to
110  * right and it means the right edge of the component otherwise.
111  * <p>
112  * Instead of using BoxLayout directly, many programs use the Box class.
113  * The Box class is a lightweight container that uses a BoxLayout.
114  * It also provides handy methods to help you use BoxLayout well.
115  * Adding components to multiple nested boxes is a powerful way to get
116  * the arrangement you want.
117  * <p>
118  * For further information and examples see
119  * <a
120  href="http://docs.oracle.com/javase/tutorial/uiswing/layout/box.html">How to Use BoxLayout</a>,
121  * a section in <em>The Java Tutorial.</em>
122  * <p>
123  * <strong>Warning:</strong>
124  * Serialized objects of this class will not be compatible with
125  * future Swing releases. The current serialization support is
126  * appropriate for short term storage or RMI between applications running
127  * the same version of Swing.  As of 1.4, support for long term storage
128  * of all JavaBeans&trade;
129  * has been added to the {@code java.beans} package.
130  * Please see {@link java.beans.XMLEncoder}.
131  *
132  * @see Box
133  * @see java.awt.ComponentOrientation
134  * @see JComponent#getAlignmentX
135  * @see JComponent#getAlignmentY
136  *
137  * @author   Timothy Prinzing
138  * @since 1.2
139  */
140 @SuppressWarnings("serial")
141 public class BoxLayout implements LayoutManager2, Serializable {
142 
143     /**
144      * Specifies that components should be laid out left to right.
145      */
146     public static final int X_AXIS = 0;
147 
148     /**
149      * Specifies that components should be laid out top to bottom.
150      */
151     public static final int Y_AXIS = 1;
152 
153     /**
154      * Specifies that components should be laid out in the direction of
155      * a line of text as determined by the target container's
156      * {@code ComponentOrientation} property.
157      */
158     public static final int LINE_AXIS = 2;
159 
160     /**
161      * Specifies that components should be laid out in the direction that
162      * lines flow across a page as determined by the target container's
163      * {@code ComponentOrientation} property.
164      */
165     public static final int PAGE_AXIS = 3;
166 
167     /**
168      * Creates a layout manager that will lay out components along the
169      * given axis.
170      *
171      * @param target  the container that needs to be laid out
172      * @param axis  the axis to lay out components along. Can be one of:
173      *              {@code BoxLayout.X_AXIS, BoxLayout.Y_AXIS,
174      *              BoxLayout.LINE_AXIS} or {@code BoxLayout.PAGE_AXIS}
175      *
176      * @exception AWTError  if the value of {@code axis} is invalid
177      */
178     @ConstructorProperties({"target", "axis"})
BoxLayout(Container target, int axis)179     public BoxLayout(Container target, int axis) {
180         if (axis != X_AXIS && axis != Y_AXIS &&
181             axis != LINE_AXIS && axis != PAGE_AXIS) {
182             throw new AWTError("Invalid axis");
183         }
184         this.axis = axis;
185         this.target = target;
186     }
187 
188     /**
189      * Constructs a BoxLayout that
190      * produces debugging messages.
191      *
192      * @param target  the container that needs to be laid out
193      * @param axis  the axis to lay out components along. Can be one of:
194      *              {@code BoxLayout.X_AXIS, BoxLayout.Y_AXIS,
195      *              BoxLayout.LINE_AXIS} or {@code BoxLayout.PAGE_AXIS}
196      *
197      * @param dbg  the stream to which debugging messages should be sent,
198      *   null if none
199      */
BoxLayout(Container target, int axis, PrintStream dbg)200     BoxLayout(Container target, int axis, PrintStream dbg) {
201         this(target, axis);
202         this.dbg = dbg;
203     }
204 
205     /**
206      * Returns the container that uses this layout manager.
207      *
208      * @return the container that uses this layout manager
209      *
210      * @since 1.6
211      */
getTarget()212     public final Container getTarget() {
213         return this.target;
214     }
215 
216     /**
217      * Returns the axis that was used to lay out components.
218      * Returns one of:
219      * {@code BoxLayout.X_AXIS, BoxLayout.Y_AXIS,
220      * BoxLayout.LINE_AXIS} or {@code BoxLayout.PAGE_AXIS}
221      *
222      * @return the axis that was used to lay out components
223      *
224      * @since 1.6
225      */
getAxis()226     public final int getAxis() {
227         return this.axis;
228     }
229 
230     /**
231      * Indicates that a child has changed its layout related information,
232      * and thus any cached calculations should be flushed.
233      * <p>
234      * This method is called by AWT when the invalidate method is called
235      * on the Container.  Since the invalidate method may be called
236      * asynchronously to the event thread, this method may be called
237      * asynchronously.
238      *
239      * @param target  the affected container
240      *
241      * @exception AWTError  if the target isn't the container specified to the
242      *                      BoxLayout constructor
243      */
invalidateLayout(Container target)244     public synchronized void invalidateLayout(Container target) {
245         checkContainer(target);
246         xChildren = null;
247         yChildren = null;
248         xTotal = null;
249         yTotal = null;
250     }
251 
252     /**
253      * Not used by this class.
254      *
255      * @param name the name of the component
256      * @param comp the component
257      */
addLayoutComponent(String name, Component comp)258     public void addLayoutComponent(String name, Component comp) {
259         invalidateLayout(comp.getParent());
260     }
261 
262     /**
263      * Not used by this class.
264      *
265      * @param comp the component
266      */
removeLayoutComponent(Component comp)267     public void removeLayoutComponent(Component comp) {
268         invalidateLayout(comp.getParent());
269     }
270 
271     /**
272      * Not used by this class.
273      *
274      * @param comp the component
275      * @param constraints constraints
276      */
addLayoutComponent(Component comp, Object constraints)277     public void addLayoutComponent(Component comp, Object constraints) {
278         invalidateLayout(comp.getParent());
279     }
280 
281     /**
282      * Returns the preferred dimensions for this layout, given the components
283      * in the specified target container.
284      *
285      * @param target  the container that needs to be laid out
286      * @return the dimensions &gt;= 0 &amp;&amp; &lt;= Integer.MAX_VALUE
287      * @exception AWTError  if the target isn't the container specified to the
288      *                      BoxLayout constructor
289      * @see Container
290      * @see #minimumLayoutSize
291      * @see #maximumLayoutSize
292      */
preferredLayoutSize(Container target)293     public Dimension preferredLayoutSize(Container target) {
294         Dimension size;
295         synchronized(this) {
296             checkContainer(target);
297             checkRequests();
298             size = new Dimension(xTotal.preferred, yTotal.preferred);
299         }
300 
301         Insets insets = target.getInsets();
302         size.width = (int) Math.min((long) size.width + (long) insets.left + (long) insets.right, Integer.MAX_VALUE);
303         size.height = (int) Math.min((long) size.height + (long) insets.top + (long) insets.bottom, Integer.MAX_VALUE);
304         return size;
305     }
306 
307     /**
308      * Returns the minimum dimensions needed to lay out the components
309      * contained in the specified target container.
310      *
311      * @param target  the container that needs to be laid out
312      * @return the dimensions &gt;= 0 &amp;&amp; &lt;= Integer.MAX_VALUE
313      * @exception AWTError  if the target isn't the container specified to the
314      *                      BoxLayout constructor
315      * @see #preferredLayoutSize
316      * @see #maximumLayoutSize
317      */
minimumLayoutSize(Container target)318     public Dimension minimumLayoutSize(Container target) {
319         Dimension size;
320         synchronized(this) {
321             checkContainer(target);
322             checkRequests();
323             size = new Dimension(xTotal.minimum, yTotal.minimum);
324         }
325 
326         Insets insets = target.getInsets();
327         size.width = (int) Math.min((long) size.width + (long) insets.left + (long) insets.right, Integer.MAX_VALUE);
328         size.height = (int) Math.min((long) size.height + (long) insets.top + (long) insets.bottom, Integer.MAX_VALUE);
329         return size;
330     }
331 
332     /**
333      * Returns the maximum dimensions the target container can use
334      * to lay out the components it contains.
335      *
336      * @param target  the container that needs to be laid out
337      * @return the dimensions &gt;= 0 &amp;&amp; &lt;= Integer.MAX_VALUE
338      * @exception AWTError  if the target isn't the container specified to the
339      *                      BoxLayout constructor
340      * @see #preferredLayoutSize
341      * @see #minimumLayoutSize
342      */
maximumLayoutSize(Container target)343     public Dimension maximumLayoutSize(Container target) {
344         Dimension size;
345         synchronized(this) {
346             checkContainer(target);
347             checkRequests();
348             size = new Dimension(xTotal.maximum, yTotal.maximum);
349         }
350 
351         Insets insets = target.getInsets();
352         size.width = (int) Math.min((long) size.width + (long) insets.left + (long) insets.right, Integer.MAX_VALUE);
353         size.height = (int) Math.min((long) size.height + (long) insets.top + (long) insets.bottom, Integer.MAX_VALUE);
354         return size;
355     }
356 
357     /**
358      * Returns the alignment along the X axis for the container.
359      * If the box is horizontal, the default
360      * alignment will be returned. Otherwise, the alignment needed
361      * to place the children along the X axis will be returned.
362      *
363      * @param target  the container
364      * @return the alignment &gt;= 0.0f &amp;&amp; &lt;= 1.0f
365      * @exception AWTError  if the target isn't the container specified to the
366      *                      BoxLayout constructor
367      */
getLayoutAlignmentX(Container target)368     public synchronized float getLayoutAlignmentX(Container target) {
369         checkContainer(target);
370         checkRequests();
371         return xTotal.alignment;
372     }
373 
374     /**
375      * Returns the alignment along the Y axis for the container.
376      * If the box is vertical, the default
377      * alignment will be returned. Otherwise, the alignment needed
378      * to place the children along the Y axis will be returned.
379      *
380      * @param target  the container
381      * @return the alignment &gt;= 0.0f &amp;&amp; &lt;= 1.0f
382      * @exception AWTError  if the target isn't the container specified to the
383      *                      BoxLayout constructor
384      */
getLayoutAlignmentY(Container target)385     public synchronized float getLayoutAlignmentY(Container target) {
386         checkContainer(target);
387         checkRequests();
388         return yTotal.alignment;
389     }
390 
391     /**
392      * Called by the AWT <!-- XXX CHECK! --> when the specified container
393      * needs to be laid out.
394      *
395      * @param target  the container to lay out
396      *
397      * @exception AWTError  if the target isn't the container specified to the
398      *                      BoxLayout constructor
399      */
layoutContainer(Container target)400     public void layoutContainer(Container target) {
401         checkContainer(target);
402         int nChildren = target.getComponentCount();
403         int[] xOffsets = new int[nChildren];
404         int[] xSpans = new int[nChildren];
405         int[] yOffsets = new int[nChildren];
406         int[] ySpans = new int[nChildren];
407 
408         Dimension alloc = target.getSize();
409         Insets in = target.getInsets();
410         alloc.width -= in.left + in.right;
411         alloc.height -= in.top + in.bottom;
412 
413         // Resolve axis to an absolute value (either X_AXIS or Y_AXIS)
414         ComponentOrientation o = target.getComponentOrientation();
415         int absoluteAxis = resolveAxis( axis, o );
416         boolean ltr = (absoluteAxis != axis) ? o.isLeftToRight() : true;
417 
418 
419         // determine the child placements
420         synchronized(this) {
421             checkRequests();
422 
423             if (absoluteAxis == X_AXIS) {
424                 SizeRequirements.calculateTiledPositions(alloc.width, xTotal,
425                                                          xChildren, xOffsets,
426                                                          xSpans, ltr);
427                 SizeRequirements.calculateAlignedPositions(alloc.height, yTotal,
428                                                            yChildren, yOffsets,
429                                                            ySpans);
430             } else {
431                 SizeRequirements.calculateAlignedPositions(alloc.width, xTotal,
432                                                            xChildren, xOffsets,
433                                                            xSpans, ltr);
434                 SizeRequirements.calculateTiledPositions(alloc.height, yTotal,
435                                                          yChildren, yOffsets,
436                                                          ySpans);
437             }
438         }
439 
440         // flush changes to the container
441         for (int i = 0; i < nChildren; i++) {
442             Component c = target.getComponent(i);
443             c.setBounds((int) Math.min((long) in.left + (long) xOffsets[i], Integer.MAX_VALUE),
444                         (int) Math.min((long) in.top + (long) yOffsets[i], Integer.MAX_VALUE),
445                         xSpans[i], ySpans[i]);
446 
447         }
448         if (dbg != null) {
449             for (int i = 0; i < nChildren; i++) {
450                 Component c = target.getComponent(i);
451                 dbg.println(c.toString());
452                 dbg.println("X: " + xChildren[i]);
453                 dbg.println("Y: " + yChildren[i]);
454             }
455         }
456 
457     }
458 
checkContainer(Container target)459     void checkContainer(Container target) {
460         if (this.target != target) {
461             throw new AWTError("BoxLayout can't be shared");
462         }
463     }
464 
checkRequests()465     void checkRequests() {
466         if (xChildren == null || yChildren == null) {
467             // The requests have been invalidated... recalculate
468             // the request information.
469             int n = target.getComponentCount();
470             xChildren = new SizeRequirements[n];
471             yChildren = new SizeRequirements[n];
472             for (int i = 0; i < n; i++) {
473                 Component c = target.getComponent(i);
474                 if (!c.isVisible()) {
475                     xChildren[i] = new SizeRequirements(0,0,0, c.getAlignmentX());
476                     yChildren[i] = new SizeRequirements(0,0,0, c.getAlignmentY());
477                     continue;
478                 }
479                 Dimension min = c.getMinimumSize();
480                 Dimension typ = c.getPreferredSize();
481                 Dimension max = c.getMaximumSize();
482                 xChildren[i] = new SizeRequirements(min.width, typ.width,
483                                                     max.width,
484                                                     c.getAlignmentX());
485                 yChildren[i] = new SizeRequirements(min.height, typ.height,
486                                                     max.height,
487                                                     c.getAlignmentY());
488             }
489 
490             // Resolve axis to an absolute value (either X_AXIS or Y_AXIS)
491             int absoluteAxis = resolveAxis(axis,target.getComponentOrientation());
492 
493             if (absoluteAxis == X_AXIS) {
494                 xTotal = SizeRequirements.getTiledSizeRequirements(xChildren);
495                 yTotal = SizeRequirements.getAlignedSizeRequirements(yChildren);
496             } else {
497                 xTotal = SizeRequirements.getAlignedSizeRequirements(xChildren);
498                 yTotal = SizeRequirements.getTiledSizeRequirements(yChildren);
499             }
500         }
501     }
502 
503     /**
504      * Given one of the 4 axis values, resolve it to an absolute axis.
505      * The relative axis values, PAGE_AXIS and LINE_AXIS are converted
506      * to their absolute couterpart given the target's ComponentOrientation
507      * value.  The absolute axes, X_AXIS and Y_AXIS are returned unmodified.
508      *
509      * @param axis the axis to resolve
510      * @param o the ComponentOrientation to resolve against
511      * @return the resolved axis
512      */
resolveAxis( int axis, ComponentOrientation o )513     private int resolveAxis( int axis, ComponentOrientation o ) {
514         int absoluteAxis;
515         if( axis == LINE_AXIS ) {
516             absoluteAxis = o.isHorizontal() ? X_AXIS : Y_AXIS;
517         } else if( axis == PAGE_AXIS ) {
518             absoluteAxis = o.isHorizontal() ? Y_AXIS : X_AXIS;
519         } else {
520             absoluteAxis = axis;
521         }
522         return absoluteAxis;
523    }
524 
525 
526     private int axis;
527     private Container target;
528 
529     private transient SizeRequirements[] xChildren;
530     private transient SizeRequirements[] yChildren;
531     private transient SizeRequirements xTotal;
532     private transient SizeRequirements yTotal;
533 
534     private transient PrintStream dbg;
535 }
536