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