--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/GroupLayout.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,3741 @@
+/*
+ * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.LayoutManager2;
+import java.util.*;
+import static java.awt.Component.BaselineResizeBehavior;
+import static javax.swing.LayoutStyle.ComponentPlacement;
+import static javax.swing.SwingConstants.HORIZONTAL;
+import static javax.swing.SwingConstants.VERTICAL;
+
+/**
+ * {@code GroupLayout} is a {@code LayoutManager} that hierarchically
+ * groups components in order to position them in a {@code Container}.
+ * {@code GroupLayout} is intended for use by builders, but may be
+ * hand-coded as well.
+ * Grouping is done by instances of the {@link Group Group} class. {@code
+ * GroupLayout} supports two types of groups. A sequential group
+ * positions its child elements sequentially, one after another. A
+ * parallel group aligns its child elements in one of four ways.
+ * <p>
+ * Each group may contain any number of elements, where an element is
+ * a {@code Group}, {@code Component}, or gap. A gap can be thought
+ * of as an invisible component with a minimum, preferred and maximum
+ * size. In addition {@code GroupLayout} supports a preferred gap,
+ * whose value comes from {@code LayoutStyle}.
+ * <p>
+ * Elements are similar to a spring. Each element has a range as
+ * specified by a minimum, preferred and maximum. Gaps have either a
+ * developer-specified range, or a range determined by {@code
+ * LayoutStyle}. The range for {@code Component}s is determined from
+ * the {@code Component}'s {@code getMinimumSize}, {@code
+ * getPreferredSize} and {@code getMaximumSize} methods. In addition,
+ * when adding {@code Component}s you may specify a particular range
+ * to use instead of that from the component. The range for a {@code
+ * Group} is determined by the type of group. A {@code ParallelGroup}'s
+ * range is the maximum of the ranges of its elements. A {@code
+ * SequentialGroup}'s range is the sum of the ranges of its elements.
+ * <p>
+ * {@code GroupLayout} treats each axis independently. That is, there
+ * is a group representing the horizontal axis, and a group
+ * representing the vertical axis. The horizontal group is
+ * responsible for determining the minimum, preferred and maximum size
+ * along the horizontal axis as well as setting the x and width of the
+ * components contained in it. The vertical group is responsible for
+ * determining the minimum, preferred and maximum size along the
+ * vertical axis as well as setting the y and height of the
+ * components contained in it. Each {@code Component} must exist in both
+ * a horizontal and vertical group, otherwise an {@code IllegalStateException}
+ * is thrown during layout, or when the minimum, preferred or
+ * maximum size is requested.
+ * <p>
+ * The following diagram shows a sequential group along the horizontal
+ * axis. The sequential group contains three components. A parallel group
+ * was used along the vertical axis.
+ * <p align="center">
+ * <img src="doc-files/groupLayout.1.gif">
+ * <p>
+ * To reinforce that each axis is treated independently the diagram shows
+ * the range of each group and element along each axis. The
+ * range of each component has been projected onto the axes,
+ * and the groups are rendered in blue (horizontal) and red (vertical).
+ * For readability there is a gap between each of the elements in the
+ * sequential group.
+ * <p>
+ * The sequential group along the horizontal axis is rendered as a solid
+ * blue line. Notice the sequential group is the sum of the children elements
+ * it contains.
+ * <p>
+ * Along the vertical axis the parallel group is the maximum of the height
+ * of each of the components. As all three components have the same height,
+ * the parallel group has the same height.
+ * <p>
+ * The following diagram shows the same three components, but with the
+ * parallel group along the horizontal axis and the sequential group along
+ * the vertical axis.
+ * <p>
+ * <p align="center">
+ * <img src="doc-files/groupLayout.2.gif">
+ * <p>
+ * As {@code c1} is the largest of the three components, the parallel
+ * group is sized to {@code c1}. As {@code c2} and {@code c3} are smaller
+ * than {@code c1} they are aligned based on the alignment specified
+ * for the component (if specified) or the default alignment of the
+ * parallel group. In the diagram {@code c2} and {@code c3} were created
+ * with an alignment of {@code LEADING}. If the component orientation were
+ * right-to-left then {@code c2} and {@code c3} would be positioned on
+ * the opposite side.
+ * <p>
+ * The following diagram shows a sequential group along both the horizontal
+ * and vertical axis.
+ * <p align="center">
+ * <img src="doc-files/groupLayout.3.gif">
+ * <p>
+ * {@code GroupLayout} provides the ability to insert gaps between
+ * {@code Component}s. The size of the gap is determined by an
+ * instance of {@code LayoutStyle}. This may be turned on using the
+ * {@code setAutoCreateGaps} method. Similarly, you may use
+ * the {@code setAutoCreateContainerGaps} method to insert gaps
+ * between components that touch the edge of the parent container and the
+ * container.
+ * <p>
+ * The following builds a panel consisting of two labels in
+ * one column, followed by two textfields in the next column:
+ * <pre>
+ * JComponent panel = ...;
+ * GroupLayout layout = new GroupLayout(panel);
+ * panel.setLayout(layout);
+ *
+ * // Turn on automatically adding gaps between components
+ * layout.setAutoCreateGaps(true);
+ *
+ * // Turn on automatically creating gaps between components that touch
+ * // the edge of the container and the container.
+ * layout.setAutoCreateContainerGaps(true);
+ *
+ * // Create a sequential group for the horizontal axis.
+ *
+ * GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
+ *
+ * // The sequential group in turn contains two parallel groups.
+ * // One parallel group contains the labels, the other the text fields.
+ * // Putting the labels in a parallel group along the horizontal axis
+ * // positions them at the same x location.
+ * //
+ * // Variable indentation is used to reinforce the level of grouping.
+ * hGroup.addGroup(layout.createParallelGroup().
+ * addComponent(label1).addComponent(label2));
+ * hGroup.addGroup(layout.createParallelGroup().
+ * addComponent(tf1).addComponent(tf2));
+ * layout.setHorizontalGroup(hGroup);
+ *
+ * // Create a sequential group for the vertical axis.
+ * GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
+ *
+ * // The sequential group contains two parallel groups that align
+ * // the contents along the baseline. The first parallel group contains
+ * // the first label and text field, and the second parallel group contains
+ * // the second label and text field. By using a sequential group
+ * // the labels and text fields are positioned vertically after one another.
+ * vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).
+ * addComponent(label1).addComponent(tf1));
+ * vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).
+ * addComponent(label2).addComponent(tf2));
+ * layout.setVerticalGroup(vGroup);
+ * </pre>
+ * <p>
+ * When run the following is produced.
+ * <p align="center">
+ * <img src="doc-files/groupLayout.example.png">
+ * <p>
+ * This layout consists of the following.
+ * <ul><li>The horizontal axis consists of a sequential group containing two
+ * parallel groups. The first parallel group contains the labels,
+ * and the second parallel group contains the text fields.
+ * <li>The vertical axis consists of a sequential group
+ * containing two parallel groups. The parallel groups are configured
+ * to align their components along the baseline. The first parallel
+ * group contains the first label and first text field, and
+ * the second group consists of the second label and second
+ * text field.
+ * </ul>
+ * There are a couple of things to notice in this code:
+ * <ul>
+ * <li>You need not explicitly add the components to the container; this
+ * is indirectly done by using one of the {@code add} methods of
+ * {@code Group}.
+ * <li>The various {@code add} methods return
+ * the caller. This allows for easy chaining of invocations. For
+ * example, {@code group.addComponent(label1).addComponent(label2);} is
+ * equivalent to
+ * {@code group.addComponent(label1); group.addComponent(label2);}.
+ * <li>There are no public constructors for {@code Group}s; instead
+ * use the create methods of {@code GroupLayout}.
+ * </ul>
+ *
+ * @author Tomas Pavek
+ * @author Jan Stola
+ * @author Scott Violet
+ * @since 1.6
+ */
+public class GroupLayout implements LayoutManager2 {
+ // Used in size calculations
+ private static final int MIN_SIZE = 0;
+
+ private static final int PREF_SIZE = 1;
+
+ private static final int MAX_SIZE = 2;
+
+ // Used by prepare, indicates min, pref or max isn't going to be used.
+ private static final int SPECIFIC_SIZE = 3;
+
+ private static final int UNSET = Integer.MIN_VALUE;
+
+ /**
+ * Indicates the size from the component or gap should be used for a
+ * particular range value.
+ *
+ * @see Group
+ */
+ public static final int DEFAULT_SIZE = -1;
+
+ /**
+ * Indicates the preferred size from the component or gap should
+ * be used for a particular range value.
+ *
+ * @see Group
+ */
+ public static final int PREFERRED_SIZE = -2;
+
+ // Whether or not we automatically try and create the preferred
+ // padding between components.
+ private boolean autocreatePadding;
+
+ // Whether or not we automatically try and create the preferred
+ // padding between components the touch the edge of the container and
+ // the container.
+ private boolean autocreateContainerPadding;
+
+ /**
+ * Group responsible for layout along the horizontal axis. This is NOT
+ * the user specified group, use getHorizontalGroup to dig that out.
+ */
+ private Group horizontalGroup;
+
+ /**
+ * Group responsible for layout along the vertical axis. This is NOT
+ * the user specified group, use getVerticalGroup to dig that out.
+ */
+ private Group verticalGroup;
+
+ // Maps from Component to ComponentInfo. This is used for tracking
+ // information specific to a Component.
+ private Map<Component,ComponentInfo> componentInfos;
+
+ // Container we're doing layout for.
+ private Container host;
+
+ // Used by areParallelSiblings, cached to avoid excessive garbage.
+ private Set<Spring> tmpParallelSet;
+
+ // Indicates Springs have changed in some way since last change.
+ private boolean springsChanged;
+
+ // Indicates invalidateLayout has been invoked.
+ private boolean isValid;
+
+ // Whether or not any preferred padding (or container padding) springs
+ // exist
+ private boolean hasPreferredPaddingSprings;
+
+ /**
+ * The LayoutStyle instance to use, if null the sharedInstance is used.
+ */
+ private LayoutStyle layoutStyle;
+
+ /**
+ * If true, components that are not visible are treated as though they
+ * aren't there.
+ */
+ private boolean honorsVisibility;
+
+
+ /**
+ * Enumeration of the possible ways {@code ParallelGroup} can align
+ * its children.
+ *
+ * @see #createParallelGroup(Alignment)
+ * @since 1.6
+ */
+ public enum Alignment {
+ /**
+ * Indicates the elements should be
+ * aligned to the origin. For the horizontal axis with a left to
+ * right orientation this means aligned to the left edge. For the
+ * vertical axis leading means aligned to the top edge.
+ *
+ * @see #createParallelGroup(Alignment)
+ */
+ LEADING,
+
+ /**
+ * Indicates the elements should be aligned to the end of the
+ * region. For the horizontal axis with a left to right
+ * orientation this means aligned to the right edge. For the
+ * vertical axis trailing means aligned to the bottom edge.
+ *
+ * @see #createParallelGroup(Alignment)
+ */
+ TRAILING,
+
+ /**
+ * Indicates the elements should be centered in
+ * the region.
+ *
+ * @see #createParallelGroup(Alignment)
+ */
+ CENTER,
+
+ /**
+ * Indicates the elements should be aligned along
+ * their baseline.
+ *
+ * @see #createParallelGroup(Alignment)
+ * @see #createBaselineGroup(boolean,boolean)
+ */
+ BASELINE
+ }
+
+
+ private static void checkSize(int min, int pref, int max,
+ boolean isComponentSpring) {
+ checkResizeType(min, isComponentSpring);
+ if (!isComponentSpring && pref < 0) {
+ throw new IllegalArgumentException("Pref must be >= 0");
+ } else if (isComponentSpring) {
+ checkResizeType(pref, true);
+ }
+ checkResizeType(max, isComponentSpring);
+ checkLessThan(min, pref);
+ checkLessThan(pref, max);
+ }
+
+ private static void checkResizeType(int type, boolean isComponentSpring) {
+ if (type < 0 && ((isComponentSpring && type != DEFAULT_SIZE &&
+ type != PREFERRED_SIZE) ||
+ (!isComponentSpring && type != PREFERRED_SIZE))) {
+ throw new IllegalArgumentException("Invalid size");
+ }
+ }
+
+ private static void checkLessThan(int min, int max) {
+ if (min >= 0 && max >= 0 && min > max) {
+ throw new IllegalArgumentException(
+ "Following is not met: min<=pref<=max");
+ }
+ }
+
+ /**
+ * Creates a {@code GroupLayout} for the specified {@code Container}.
+ *
+ * @param host the {@code Container} the {@code GroupLayout} is
+ * the {@code LayoutManager} for
+ * @throws IllegalArgumentException if host is {@code null}
+ */
+ public GroupLayout(Container host) {
+ if (host == null) {
+ throw new IllegalArgumentException("Container must be non-null");
+ }
+ honorsVisibility = true;
+ this.host = host;
+ setHorizontalGroup(createParallelGroup(Alignment.LEADING, true));
+ setVerticalGroup(createParallelGroup(Alignment.LEADING, true));
+ componentInfos = new HashMap<Component,ComponentInfo>();
+ tmpParallelSet = new HashSet<Spring>();
+ }
+
+ /**
+ * Sets whether component visiblity is considered when sizing and
+ * positioning components. A value of {@code true} indicates that
+ * non-visible components should not be treated as part of the
+ * layout. A value of {@code false} indicates that components should be
+ * positioned and sized regardless of visibility.
+ * <p>
+ * A value of {@code false} is useful when the visibility of components
+ * is dynamically adjusted and you don't want surrounding components and
+ * the sizing to change.
+ * <p>
+ * The specified value is used for components that do not have an
+ * explicit visibility specified.
+ * <p>
+ * The default is {@code true}.
+ *
+ * @param honorsVisibility whether component visiblity is considered when
+ * sizing and positioning components
+ * @see #setHonorsVisibility(Component,Boolean)
+ */
+ public void setHonorsVisibility(boolean honorsVisibility) {
+ if (this.honorsVisibility != honorsVisibility) {
+ this.honorsVisibility = honorsVisibility;
+ springsChanged = true;
+ isValid = false;
+ invalidateHost();
+ }
+ }
+
+ /**
+ * Returns whether component visiblity is considered when sizing and
+ * positioning components.
+ *
+ * @return whether component visiblity is considered when sizing and
+ * positioning components
+ */
+ public boolean getHonorsVisibility() {
+ return honorsVisibility;
+ }
+
+ /**
+ * Sets whether the component's visiblity is considered for
+ * sizing and positioning. A value of {@code Boolean.TRUE}
+ * indicates that if {@code component} is not visible it should
+ * not be treated as part of the layout. A value of {@code false}
+ * indicates that {@code component} is positioned and sized
+ * regardless of it's visibility. A value of {@code null}
+ * indicates the value specified by the single argument method {@code
+ * setHonorsVisibility} should be used.
+ * <p>
+ * If {@code component} is not a child of the {@code Container} this
+ * {@code GroupLayout} is managine, it will be added to the
+ * {@code Container}.
+ *
+ * @param component the component
+ * @param honorsVisibility whether {@code component}'s visiblity should be
+ * considered for sizing and positioning
+ * @throws IllegalArgumentException if {@code component} is {@code null}
+ * @see #setHonorsVisibility(Component,Boolean)
+ */
+ public void setHonorsVisibility(Component component,
+ Boolean honorsVisibility) {
+ if (component == null) {
+ throw new IllegalArgumentException("Component must be non-null");
+ }
+ getComponentInfo(component).setHonorsVisibility(honorsVisibility);
+ springsChanged = true;
+ isValid = false;
+ invalidateHost();
+ }
+
+ /**
+ * Sets whether a gap between components should automatically be
+ * created. For example, if this is {@code true} and you add two
+ * components to a {@code SequentialGroup} a gap between the
+ * two components is automatically be created. The default is
+ * {@code false}.
+ *
+ * @param autoCreatePadding whether a gap between components is
+ * automatically created
+ */
+ public void setAutoCreateGaps(boolean autoCreatePadding) {
+ if (this.autocreatePadding != autoCreatePadding) {
+ this.autocreatePadding = autoCreatePadding;
+ invalidateHost();
+ }
+ }
+
+ /**
+ * Returns {@code true} if gaps between components are automatically
+ * created.
+ *
+ * @return {@code true} if gaps between components are automatically
+ * created
+ */
+ public boolean getAutoCreateGaps() {
+ return autocreatePadding;
+ }
+
+ /**
+ * Sets whether a gap between the container and components that
+ * touch the border of the container should automatically be
+ * created. The default is {@code false}.
+ *
+ * @param autoCreateContainerPadding whether a gap between the container and
+ * components that touch the border of the container should
+ * automatically be created
+ */
+ public void setAutoCreateContainerGaps(boolean autoCreateContainerPadding){
+ if (this.autocreateContainerPadding != autoCreateContainerPadding) {
+ this.autocreateContainerPadding = autoCreateContainerPadding;
+ horizontalGroup = createTopLevelGroup(getHorizontalGroup());
+ verticalGroup = createTopLevelGroup(getVerticalGroup());
+ invalidateHost();
+ }
+ }
+
+ /**
+ * Returns {@code true} if gaps between the container and components that
+ * border the container are automatically created.
+ *
+ * @return {@code true} if gaps between the container and components that
+ * border the container are automatically created
+ */
+ public boolean getAutoCreateContainerGaps() {
+ return autocreateContainerPadding;
+ }
+
+ /**
+ * Sets the {@code Group} that positions and sizes
+ * components along the horizontal axis.
+ *
+ * @param group the {@code Group} that positions and sizes
+ * components along the horizontal axis
+ * @throws IllegalArgumentException if group is {@code null}
+ */
+ public void setHorizontalGroup(Group group) {
+ if (group == null) {
+ throw new IllegalArgumentException("Group must be non-null");
+ }
+ horizontalGroup = createTopLevelGroup(group);
+ invalidateHost();
+ }
+
+ /**
+ * Returns the {@code Group} that positions and sizes components
+ * along the horizontal axis.
+ *
+ * @return the {@code Group} responsible for positioning and
+ * sizing component along the horizontal axis
+ */
+ private Group getHorizontalGroup() {
+ int index = 0;
+ if (horizontalGroup.springs.size() > 1) {
+ index = 1;
+ }
+ return (Group)horizontalGroup.springs.get(index);
+ }
+
+ /**
+ * Sets the {@code Group} that positions and sizes
+ * components along the vertical axis.
+ *
+ * @param group the {@code Group} that positions and sizes
+ * components along the vertical axis
+ * @throws IllegalArgumentException if group is {@code null}
+ */
+ public void setVerticalGroup(Group group) {
+ if (group == null) {
+ throw new IllegalArgumentException("Group must be non-null");
+ }
+ verticalGroup = createTopLevelGroup(group);
+ invalidateHost();
+ }
+
+ /**
+ * Returns the {@code Group} that positions and sizes components
+ * along the vertical axis.
+ *
+ * @return the {@code Group} responsible for positioning and
+ * sizing component along the vertical axis
+ */
+ private Group getVerticalGroup() {
+ int index = 0;
+ if (verticalGroup.springs.size() > 1) {
+ index = 1;
+ }
+ return (Group)verticalGroup.springs.get(index);
+ }
+
+ /**
+ * Wraps the user specified group in a sequential group. If
+ * container gaps should be generated the necessary springs are
+ * added.
+ */
+ private Group createTopLevelGroup(Group specifiedGroup) {
+ SequentialGroup group = createSequentialGroup();
+ if (getAutoCreateContainerGaps()) {
+ group.addSpring(new ContainerAutoPreferredGapSpring());
+ group.addGroup(specifiedGroup);
+ group.addSpring(new ContainerAutoPreferredGapSpring());
+ } else {
+ group.addGroup(specifiedGroup);
+ }
+ return group;
+ }
+
+ /**
+ * Creates and returns a {@code SequentialGroup}.
+ *
+ * @return a new {@code SequentialGroup}
+ */
+ public SequentialGroup createSequentialGroup() {
+ return new SequentialGroup();
+ }
+
+ /**
+ * Creates and returns a {@code ParallelGroup} with an alignment of
+ * {@code Alignment.LEADING}. This is a cover method for the more
+ * general {@code createParallelGroup(Alignment)} method.
+ *
+ * @return a new {@code ParallelGroup}
+ * @see #createParallelGroup(Alignment)
+ */
+ public ParallelGroup createParallelGroup() {
+ return createParallelGroup(Alignment.LEADING);
+ }
+
+ /**
+ * Creates and returns a {@code ParallelGroup} with the specified
+ * alignment. This is a cover method for the more general {@code
+ * createParallelGroup(Alignment,boolean)} method with {@code true}
+ * supplied for the second argument.
+ *
+ * @param alignment the alignment for the elements of the group
+ * @throws IllegalArgumentException if {@code alignment} is {@code null}
+ * @return a new {@code ParallelGroup}
+ * @see #createBaselineGroup
+ * @see ParallelGroup
+ */
+ public ParallelGroup createParallelGroup(Alignment alignment) {
+ return createParallelGroup(alignment, true);
+ }
+
+ /**
+ * Creates and returns a {@code ParallelGroup} with the specified
+ * alignment and resize behavior. The {@code
+ * alignment} argument specifies how children elements are
+ * positioned that do not fill the group. For example, if a {@code
+ * ParallelGroup} with an alignment of {@code TRAILING} is given
+ * 100 and a child only needs 50, the child is
+ * positioned at the position 50 (with a component orientation of
+ * left-to-right).
+ * <p>
+ * Baseline alignment is only useful when used along the vertical
+ * axis. A {@code ParallelGroup} created with a baseline alignment
+ * along the horizontal axis is treated as {@code LEADING}.
+ * <p>
+ * Refer to {@link GroupLayout.ParallelGroup ParallelGroup} for details on
+ * the behavior of baseline groups.
+ *
+ * @param alignment the alignment for the elements of the group
+ * @param resizable {@code true} if the group is resizable; if the group
+ * is not resizable the preferred size is used for the
+ * minimum and maximum size of the group
+ * @throws IllegalArgumentException if {@code alignment} is {@code null}
+ * @return a new {@code ParallelGroup}
+ * @see #createBaselineGroup
+ * @see GroupLayout.ParallelGroup
+ */
+ public ParallelGroup createParallelGroup(Alignment alignment,
+ boolean resizable){
+ if (alignment == Alignment.BASELINE) {
+ return new BaselineGroup(resizable);
+ }
+ return new ParallelGroup(alignment, resizable);
+ }
+
+ /**
+ * Creates and returns a {@code ParallelGroup} that aligns it's
+ * elements along the baseline.
+ *
+ * @param resizable whether the group is resizable
+ * @param anchorBaselineToTop whether the baseline is anchored to
+ * the top or bottom of the group
+ * @see #createBaselineGroup
+ * @see ParallelGroup
+ */
+ public ParallelGroup createBaselineGroup(boolean resizable,
+ boolean anchorBaselineToTop) {
+ return new BaselineGroup(resizable, anchorBaselineToTop);
+ }
+
+ /**
+ * Forces the specified components to have the same size
+ * regardless of their preferred, minimum or maximum sizes. Components that
+ * are linked are given the maximum of the preferred size of each of
+ * the linked components. For example, if you link two components with
+ * a preferred width of 10 and 20, both components are given a width of 20.
+ * <p>
+ * This can be used multiple times to force any number of
+ * components to share the same size.
+ * <p>
+ * Linked Components are not be resizable.
+ *
+ * @param components the {@code Component}s that are to have the same size
+ * @throws IllegalArgumentException if {@code components} is
+ * {@code null}, or contains {@code null}
+ * @see #linkSize(int,Component[])
+ */
+ public void linkSize(Component... components) {
+ linkSize(SwingConstants.HORIZONTAL, components);
+ linkSize(SwingConstants.VERTICAL, components);
+ }
+
+ /**
+ * Forces the specified components to have the same size along the
+ * specified axis regardless of their preferred, minimum or
+ * maximum sizes. Components that are linked are given the maximum
+ * of the preferred size of each of the linked components. For
+ * example, if you link two components along the horizontal axis
+ * and the preferred width is 10 and 20, both components are given
+ * a width of 20.
+ * <p>
+ * This can be used multiple times to force any number of
+ * components to share the same size.
+ * <p>
+ * Linked {@code Component}s are not be resizable.
+ *
+ * @param components the {@code Component}s that are to have the same size
+ * @param axis the axis to link the size along; one of
+ * {@code SwingConstants.HORIZONTAL} or
+ * {@code SwingConstans.VERTICAL}
+ * @throws IllegalArgumentException if {@code components} is
+ * {@code null}, or contains {@code null}; or {@code axis}
+ * is not {@code SwingConstants.HORIZONTAL} or
+ * {@code SwingConstants.VERTICAL}
+ */
+ public void linkSize(int axis, Component... components) {
+ if (components == null) {
+ throw new IllegalArgumentException("Components must be non-null");
+ }
+ for (int counter = components.length - 1; counter >= 0; counter--) {
+ Component c = components[counter];
+ if (components[counter] == null) {
+ throw new IllegalArgumentException(
+ "Components must be non-null");
+ }
+ // Force the component to be added
+ getComponentInfo(c);
+ }
+ int glAxis;
+ if (axis == SwingConstants.HORIZONTAL) {
+ glAxis = HORIZONTAL;
+ } else if (axis == SwingConstants.VERTICAL) {
+ glAxis = VERTICAL;
+ } else {
+ throw new IllegalArgumentException("Axis must be one of " +
+ "SwingConstants.HORIZONTAL or SwingConstants.VERTICAL");
+ }
+ LinkInfo master = getComponentInfo(
+ components[components.length - 1]).getLinkInfo(glAxis);
+ for (int counter = components.length - 2; counter >= 0; counter--) {
+ master.add(getComponentInfo(components[counter]));
+ }
+ invalidateHost();
+ }
+
+ /**
+ * Replaces an existing component with a new one.
+ *
+ * @param existingComponent the component that should be removed
+ * and replaced with {@code newComponent}
+ * @param newComponent the component to put in
+ * {@code existingComponent}'s place
+ * @throws IllegalArgumentException if either of the components are
+ * {@code null} or {@code existingComponent} is not being managed
+ * by this layout manager
+ */
+ public void replace(Component existingComponent, Component newComponent) {
+ if (existingComponent == null || newComponent == null) {
+ throw new IllegalArgumentException("Components must be non-null");
+ }
+ // Make sure all the components have been registered, otherwise we may
+ // not update the correct Springs.
+ if (springsChanged) {
+ registerComponents(horizontalGroup, HORIZONTAL);
+ registerComponents(verticalGroup, VERTICAL);
+ }
+ ComponentInfo info = componentInfos.remove(existingComponent);
+ if (info == null) {
+ throw new IllegalArgumentException("Component must already exist");
+ }
+ host.remove(existingComponent);
+ if (newComponent.getParent() != host) {
+ host.add(newComponent);
+ }
+ info.setComponent(newComponent);
+ componentInfos.put(newComponent, info);
+ invalidateHost();
+ }
+
+ /**
+ * Sets the {@code LayoutStyle} used to calculate the preferred
+ * gaps between components. A value of {@code null} indicates the
+ * shared instance of {@code LayoutStyle} should be used.
+ *
+ * @param layoutStyle the {@code LayoutStyle} to use
+ * @see LayoutStyle
+ */
+ public void setLayoutStyle(LayoutStyle layoutStyle) {
+ this.layoutStyle = layoutStyle;
+ invalidateHost();
+ }
+
+ /**
+ * Returns the {@code LayoutStyle} used for calculating the preferred
+ * gap between components. This returns the value specified to
+ * {@code setLayoutStyle}, which may be {@code null}.
+ *
+ * @return the {@code LayoutStyle} used for calculating the preferred
+ * gap between components
+ */
+ public LayoutStyle getLayoutStyle() {
+ return layoutStyle;
+ }
+
+ private LayoutStyle getLayoutStyle0() {
+ LayoutStyle layoutStyle = getLayoutStyle();
+ if (layoutStyle == null) {
+ layoutStyle = LayoutStyle.getInstance();
+ }
+ return layoutStyle;
+ }
+
+ private void invalidateHost() {
+ if (host instanceof JComponent) {
+ ((JComponent)host).revalidate();
+ } else {
+ host.invalidate();
+ }
+ host.repaint();
+ }
+
+ //
+ // LayoutManager
+ //
+ /**
+ * Notification that a {@code Component} has been added to
+ * the parent container. You should not invoke this method
+ * directly, instead you should use one of the {@code Group}
+ * methods to add a {@code Component}.
+ *
+ * @param name the string to be associated with the component
+ * @param component the {@code Component} to be added
+ */
+ public void addLayoutComponent(String name, Component component) {
+ }
+
+ /**
+ * Notification that a {@code Component} has been removed from
+ * the parent container. You should not invoke this method
+ * directly, instead invoke {@code remove} on the parent
+ * {@code Container}.
+ *
+ * @param component the component to be removed
+ * @see java.awt.Component#remove
+ */
+ public void removeLayoutComponent(Component component) {
+ ComponentInfo info = componentInfos.remove(component);
+ if (info != null) {
+ info.dispose();
+ springsChanged = true;
+ isValid = false;
+ }
+ }
+
+ /**
+ * Returns the preferred size for the specified container.
+ *
+ * @param parent the container to return the preferred size for
+ * @return the preferred size for {@code parent}
+ * @throws IllegalArgumentException if {@code parent} is not
+ * the same {@code Container} this was created with
+ * @throws IllegalStateException if any of the components added to
+ * this layout are not in both a horizontal and vertical group
+ * @see java.awt.Container#getPreferredSize
+ */
+ public Dimension preferredLayoutSize(Container parent) {
+ checkParent(parent);
+ prepare(PREF_SIZE);
+ return adjustSize(horizontalGroup.getPreferredSize(HORIZONTAL),
+ verticalGroup.getPreferredSize(VERTICAL));
+ }
+
+ /**
+ * Returns the minimum size for the specified container.
+ *
+ * @param parent the container to return the size for
+ * @return the minimum size for {@code parent}
+ * @throws IllegalArgumentException if {@code parent} is not
+ * the same {@code Container} that this was created with
+ * @throws IllegalStateException if any of the components added to
+ * this layout are not in both a horizontal and vertical group
+ * @see java.awt.Container#getMinimumSize
+ */
+ public Dimension minimumLayoutSize(Container parent) {
+ checkParent(parent);
+ prepare(MIN_SIZE);
+ return adjustSize(horizontalGroup.getMinimumSize(HORIZONTAL),
+ verticalGroup.getMinimumSize(VERTICAL));
+ }
+
+ /**
+ * Lays out the specified container.
+ *
+ * @param parent the container to be laid out
+ * @throws IllegalStateException if any of the components added to
+ * this layout are not in both a horizontal and vertical group
+ */
+ public void layoutContainer(Container parent) {
+ // Step 1: Prepare for layout.
+ prepare(SPECIFIC_SIZE);
+ Insets insets = parent.getInsets();
+ int width = parent.getWidth() - insets.left - insets.right;
+ int height = parent.getHeight() - insets.top - insets.bottom;
+ boolean ltr = isLeftToRight();
+ if (getAutoCreateGaps() || getAutoCreateContainerGaps() ||
+ hasPreferredPaddingSprings) {
+ // Step 2: Calculate autopadding springs
+ calculateAutopadding(horizontalGroup, HORIZONTAL, SPECIFIC_SIZE, 0,
+ width);
+ calculateAutopadding(verticalGroup, VERTICAL, SPECIFIC_SIZE, 0,
+ height);
+ }
+ // Step 3: set the size of the groups.
+ horizontalGroup.setSize(HORIZONTAL, 0, width);
+ verticalGroup.setSize(VERTICAL, 0, height);
+ // Step 4: apply the size to the components.
+ for (ComponentInfo info : componentInfos.values()) {
+ info.setBounds(insets, width, ltr);
+ }
+ }
+
+ //
+ // LayoutManager2
+ //
+ /**
+ * Notification that a {@code Component} has been added to
+ * the parent container. You should not invoke this method
+ * directly, instead you should use one of the {@code Group}
+ * methods to add a {@code Component}.
+ *
+ * @param component the component added
+ * @param constraints description of where to place the component
+ */
+ public void addLayoutComponent(Component component, Object constraints) {
+ }
+
+ /**
+ * Returns the maximum size for the specified container.
+ *
+ * @param parent the container to return the size for
+ * @return the maximum size for {@code parent}
+ * @throws IllegalArgumentException if {@code parent} is not
+ * the same {@code Container} that this was created with
+ * @throws IllegalStateException if any of the components added to
+ * this layout are not in both a horizontal and vertical group
+ * @see java.awt.Container#getMaximumSize
+ */
+ public Dimension maximumLayoutSize(Container parent) {
+ checkParent(parent);
+ prepare(MAX_SIZE);
+ return adjustSize(horizontalGroup.getMaximumSize(HORIZONTAL),
+ verticalGroup.getMaximumSize(VERTICAL));
+ }
+
+ /**
+ * Returns the alignment along the x axis. This specifies how
+ * the component would like to be aligned relative to other
+ * components. The value should be a number between 0 and 1
+ * where 0 represents alignment along the origin, 1 is aligned
+ * the furthest away from the origin, 0.5 is centered, etc.
+ *
+ * @param parent the {@code Container} hosting this {@code LayoutManager}
+ * @throws IllegalArgumentException if {@code parent} is not
+ * the same {@code Container} that this was created with
+ * @return the alignment; this implementation returns {@code .5}
+ */
+ public float getLayoutAlignmentX(Container parent) {
+ checkParent(parent);
+ return .5f;
+ }
+
+ /**
+ * Returns the alignment along the y axis. This specifies how
+ * the component would like to be aligned relative to other
+ * components. The value should be a number between 0 and 1
+ * where 0 represents alignment along the origin, 1 is aligned
+ * the furthest away from the origin, 0.5 is centered, etc.
+ *
+ * @param parent the {@code Container} hosting this {@code LayoutManager}
+ * @throws IllegalArgumentException if {@code parent} is not
+ * the same {@code Container} that this was created with
+ * @return alignment; this implementation returns {@code .5}
+ */
+ public float getLayoutAlignmentY(Container parent) {
+ checkParent(parent);
+ return .5f;
+ }
+
+ /**
+ * Invalidates the layout, indicating that if the layout manager
+ * has cached information it should be discarded.
+ *
+ * @param parent the {@code Container} hosting this LayoutManager
+ * @throws IllegalArgumentException if {@code parent} is not
+ * the same {@code Container} that this was created with
+ */
+ public void invalidateLayout(Container parent) {
+ checkParent(parent);
+ // invalidateLayout is called from Container.invalidate, which
+ // does NOT grab the treelock. All other methods do. To make sure
+ // there aren't any possible threading problems we grab the tree lock
+ // here.
+ synchronized(parent.getTreeLock()) {
+ isValid = false;
+ }
+ }
+
+ private void prepare(int sizeType) {
+ boolean visChanged = false;
+ // Step 1: If not-valid, clear springs and update visibility.
+ if (!isValid) {
+ isValid = true;
+ horizontalGroup.setSize(HORIZONTAL, UNSET, UNSET);
+ verticalGroup.setSize(VERTICAL, UNSET, UNSET);
+ for (ComponentInfo ci : componentInfos.values()) {
+ if (ci.updateVisibility()) {
+ visChanged = true;
+ }
+ ci.clearCachedSize();
+ }
+ }
+ // Step 2: Make sure components are bound to ComponentInfos
+ if (springsChanged) {
+ registerComponents(horizontalGroup, HORIZONTAL);
+ registerComponents(verticalGroup, VERTICAL);
+ }
+ // Step 3: Adjust the autopadding. This removes existing
+ // autopadding, then recalculates where it should go.
+ if (springsChanged || visChanged) {
+ checkComponents();
+ horizontalGroup.removeAutopadding();
+ verticalGroup.removeAutopadding();
+ if (getAutoCreateGaps()) {
+ insertAutopadding(true);
+ } else if (hasPreferredPaddingSprings ||
+ getAutoCreateContainerGaps()) {
+ insertAutopadding(false);
+ }
+ springsChanged = false;
+ }
+ // Step 4: (for min/pref/max size calculations only) calculate the
+ // autopadding. This invokes for unsetting the calculated values, then
+ // recalculating them.
+ // If sizeType == SPECIFIC_SIZE, it indicates we're doing layout, this
+ // step will be done later on.
+ if (sizeType != SPECIFIC_SIZE && (getAutoCreateGaps() ||
+ getAutoCreateContainerGaps() || hasPreferredPaddingSprings)) {
+ calculateAutopadding(horizontalGroup, HORIZONTAL, sizeType, 0, 0);
+ calculateAutopadding(verticalGroup, VERTICAL, sizeType, 0, 0);
+ }
+ }
+
+ private void calculateAutopadding(Group group, int axis, int sizeType,
+ int origin, int size) {
+ group.unsetAutopadding();
+ switch(sizeType) {
+ case MIN_SIZE:
+ size = group.getMinimumSize(axis);
+ break;
+ case PREF_SIZE:
+ size = group.getPreferredSize(axis);
+ break;
+ case MAX_SIZE:
+ size = group.getMaximumSize(axis);
+ break;
+ default:
+ break;
+ }
+ group.setSize(axis, origin, size);
+ group.calculateAutopadding(axis);
+ }
+
+ private void checkComponents() {
+ for (ComponentInfo info : componentInfos.values()) {
+ if (info.horizontalSpring == null) {
+ throw new IllegalStateException(info.component +
+ " is not attached to a horizontal group");
+ }
+ if (info.verticalSpring == null) {
+ throw new IllegalStateException(info.component +
+ " is not attached to a vertical group");
+ }
+ }
+ }
+
+ private void registerComponents(Group group, int axis) {
+ List<Spring> springs = group.springs;
+ for (int counter = springs.size() - 1; counter >= 0; counter--) {
+ Spring spring = springs.get(counter);
+ if (spring instanceof ComponentSpring) {
+ ((ComponentSpring)spring).installIfNecessary(axis);
+ } else if (spring instanceof Group) {
+ registerComponents((Group)spring, axis);
+ }
+ }
+ }
+
+ private Dimension adjustSize(int width, int height) {
+ Insets insets = host.getInsets();
+ return new Dimension(width + insets.left + insets.right,
+ height + insets.top + insets.bottom);
+ }
+
+ private void checkParent(Container parent) {
+ if (parent != host) {
+ throw new IllegalArgumentException(
+ "GroupLayout can only be used with one Container at a time");
+ }
+ }
+
+ /**
+ * Returns the {@code ComponentInfo} for the specified Component,
+ * creating one if necessary.
+ */
+ private ComponentInfo getComponentInfo(Component component) {
+ ComponentInfo info = (ComponentInfo)componentInfos.get(component);
+ if (info == null) {
+ info = new ComponentInfo(component);
+ componentInfos.put(component, info);
+ if (component.getParent() != host) {
+ host.add(component);
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Adjusts the autopadding springs for the horizontal and vertical
+ * groups. If {@code insert} is {@code true} this will insert auto padding
+ * springs, otherwise this will only adjust the springs that
+ * comprise auto preferred padding springs.
+ */
+ private void insertAutopadding(boolean insert) {
+ horizontalGroup.insertAutopadding(HORIZONTAL,
+ new ArrayList<AutoPreferredGapSpring>(1),
+ new ArrayList<AutoPreferredGapSpring>(1),
+ new ArrayList<ComponentSpring>(1),
+ new ArrayList<ComponentSpring>(1), insert);
+ verticalGroup.insertAutopadding(VERTICAL,
+ new ArrayList<AutoPreferredGapSpring>(1),
+ new ArrayList<AutoPreferredGapSpring>(1),
+ new ArrayList<ComponentSpring>(1),
+ new ArrayList<ComponentSpring>(1), insert);
+ }
+
+ /**
+ * Returns {@code true} if the two Components have a common ParallelGroup
+ * ancestor along the particular axis.
+ */
+ private boolean areParallelSiblings(Component source, Component target,
+ int axis) {
+ ComponentInfo sourceInfo = getComponentInfo(source);
+ ComponentInfo targetInfo = getComponentInfo(target);
+ Spring sourceSpring;
+ Spring targetSpring;
+ if (axis == HORIZONTAL) {
+ sourceSpring = sourceInfo.horizontalSpring;
+ targetSpring = targetInfo.horizontalSpring;
+ } else {
+ sourceSpring = sourceInfo.verticalSpring;
+ targetSpring = targetInfo.verticalSpring;
+ }
+ Set<Spring> sourcePath = tmpParallelSet;
+ sourcePath.clear();
+ Spring spring = sourceSpring.getParent();
+ while (spring != null) {
+ sourcePath.add(spring);
+ spring = spring.getParent();
+ }
+ spring = targetSpring.getParent();
+ while (spring != null) {
+ if (sourcePath.contains(spring)) {
+ sourcePath.clear();
+ while (spring != null) {
+ if (spring instanceof ParallelGroup) {
+ return true;
+ }
+ spring = spring.getParent();
+ }
+ return false;
+ }
+ spring = spring.getParent();
+ }
+ sourcePath.clear();
+ return false;
+ }
+
+ private boolean isLeftToRight() {
+ return host.getComponentOrientation().isLeftToRight();
+ }
+
+ /**
+ * Returns a string representation of this {@code GroupLayout}.
+ * This method is intended to be used for debugging purposes,
+ * and the content and format of the returned string may vary
+ * between implementations.
+ *
+ * @return a string representation of this {@code GroupLayout}
+ **/
+ public String toString() {
+ if (springsChanged) {
+ registerComponents(horizontalGroup, HORIZONTAL);
+ registerComponents(verticalGroup, VERTICAL);
+ }
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("HORIZONTAL\n");
+ createSpringDescription(buffer, horizontalGroup, " ", HORIZONTAL);
+ buffer.append("\nVERTICAL\n");
+ createSpringDescription(buffer, verticalGroup, " ", VERTICAL);
+ return buffer.toString();
+ }
+
+ private void createSpringDescription(StringBuffer buffer, Spring spring,
+ String indent, int axis) {
+ String origin = "";
+ String padding = "";
+ if (spring instanceof ComponentSpring) {
+ ComponentSpring cSpring = (ComponentSpring)spring;
+ origin = Integer.toString(cSpring.getOrigin()) + " ";
+ String name = cSpring.getComponent().getName();
+ if (name != null) {
+ origin = "name=" + name + ", ";
+ }
+ }
+ if (spring instanceof AutoPreferredGapSpring) {
+ AutoPreferredGapSpring paddingSpring =
+ (AutoPreferredGapSpring)spring;
+ padding = ", userCreated=" + paddingSpring.getUserCreated() +
+ ", matches=" + paddingSpring.getMatchDescription();
+ }
+ buffer.append(indent + spring.getClass().getName() + " " +
+ Integer.toHexString(spring.hashCode()) + " " +
+ origin +
+ ", size=" + spring.getSize() +
+ ", alignment=" + spring.getAlignment() +
+ " prefs=[" + spring.getMinimumSize(axis) +
+ " " + spring.getPreferredSize(axis) +
+ " " + spring.getMaximumSize(axis) +
+ padding + "]\n");
+ if (spring instanceof Group) {
+ List<Spring> springs = ((Group)spring).springs;
+ indent += " ";
+ for (int counter = 0; counter < springs.size(); counter++) {
+ createSpringDescription(buffer, springs.get(counter), indent,
+ axis);
+ }
+ }
+ }
+
+
+ /**
+ * Spring consists of a range: min, pref and max, a value some where in
+ * the middle of that, and a location. Spring caches the
+ * min/max/pref. If the min/pref/max has internally changes, or needs
+ * to be updated you must invoke clear.
+ */
+ private abstract class Spring {
+ private int size;
+ private int min;
+ private int max;
+ private int pref;
+ private Spring parent;
+
+ private Alignment alignment;
+
+ Spring() {
+ min = pref = max = UNSET;
+ }
+
+ /**
+ * Calculates and returns the minimum size.
+ *
+ * @param axis the axis of layout; one of HORIZONTAL or VERTICAL
+ * @return the minimum size
+ */
+ abstract int calculateMinimumSize(int axis);
+
+ /**
+ * Calculates and returns the preferred size.
+ *
+ * @param axis the axis of layout; one of HORIZONTAL or VERTICAL
+ * @return the preferred size
+ */
+ abstract int calculatePreferredSize(int axis);
+
+ /**
+ * Calculates and returns the minimum size.
+ *
+ * @param axis the axis of layout; one of HORIZONTAL or VERTICAL
+ * @return the minimum size
+ */
+ abstract int calculateMaximumSize(int axis);
+
+ /**
+ * Sets the parent of this Spring.
+ */
+ void setParent(Spring parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * Returns the parent of this spring.
+ */
+ Spring getParent() {
+ return parent;
+ }
+
+ // This is here purely as a conveniance for ParallelGroup to avoid
+ // having to track alignment separately.
+ void setAlignment(Alignment alignment) {
+ this.alignment = alignment;
+ }
+
+ /**
+ * Alignment for this Spring, this may be null.
+ */
+ Alignment getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Returns the minimum size.
+ */
+ final int getMinimumSize(int axis) {
+ if (min == UNSET) {
+ min = constrain(calculateMinimumSize(axis));
+ }
+ return min;
+ }
+
+ /**
+ * Returns the preferred size.
+ */
+ final int getPreferredSize(int axis) {
+ if (pref == UNSET) {
+ pref = constrain(calculatePreferredSize(axis));
+ }
+ return pref;
+ }
+
+ /**
+ * Returns the maximum size.
+ */
+ final int getMaximumSize(int axis) {
+ if (max == UNSET) {
+ max = constrain(calculateMaximumSize(axis));
+ }
+ return max;
+ }
+
+ /**
+ * Sets the value and location of the spring. Subclasses
+ * will want to invoke super, then do any additional sizing.
+ *
+ * @param axis HORIZONTAL or VERTICAL
+ * @param origin of this Spring
+ * @param size of the Spring. If size is UNSET, this invokes
+ * clear.
+ */
+ void setSize(int axis, int origin, int size) {
+ this.size = size;
+ if (size == UNSET) {
+ unset();
+ }
+ }
+
+ /**
+ * Resets the cached min/max/pref.
+ */
+ void unset() {
+ size = min = pref = max = UNSET;
+ }
+
+ /**
+ * Returns the current size.
+ */
+ int getSize() {
+ return size;
+ }
+
+ int constrain(int value) {
+ return Math.min(value, Short.MAX_VALUE);
+ }
+
+ int getBaseline() {
+ return -1;
+ }
+
+ BaselineResizeBehavior getBaselineResizeBehavior() {
+ return BaselineResizeBehavior.OTHER;
+ }
+
+ final boolean isResizable(int axis) {
+ int min = getMinimumSize(axis);
+ int pref = getPreferredSize(axis);
+ return (min != pref || pref != getMaximumSize(axis));
+ }
+
+ /**
+ * Returns {@code true} if this spring will ALWAYS have a zero
+ * size. This should NOT check the current size, rather it's
+ * meant to quickly test if this Spring will always have a
+ * zero size.
+ *
+ * @param treatAutopaddingAsZeroSized if {@code true}, auto padding
+ * springs should be treated as having a size of {@code 0}
+ * @return {@code true} if this spring will have a zero size,
+ * {@code false} otherwise
+ */
+ abstract boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized);
+ }
+
+ /**
+ * {@code Group} provides the basis for the two types of
+ * operations supported by {@code GroupLayout}: laying out
+ * components one after another ({@link SequentialGroup SequentialGroup})
+ * or aligned ({@link ParallelGroup ParallelGroup}). {@code Group} and
+ * its subclasses have no public constructor; to create one use
+ * one of {@code createSequentialGroup} or
+ * {@code createParallelGroup}. Additionally, taking a {@code Group}
+ * created from one {@code GroupLayout} and using it with another
+ * will produce undefined results.
+ * <p>
+ * Various methods in {@code Group} and its subclasses allow you
+ * to explicitly specify the range. The arguments to these methods
+ * can take two forms, either a value greater than or equal to 0,
+ * or one of {@code DEFAULT_SIZE} or {@code PREFERRED_SIZE}. A
+ * value greater than or equal to {@code 0} indicates a specific
+ * size. {@code DEFAULT_SIZE} indicates the corresponding size
+ * from the component should be used. For example, if {@code
+ * DEFAULT_SIZE} is passed as the minimum size argument, the
+ * minimum size is obtained from invoking {@code getMinimumSize}
+ * on the component. Likewise, {@code PREFERRED_SIZE} indicates
+ * the value from {@code getPreferredSize} should be used.
+ * The following example adds {@code myComponent} to {@code group}
+ * with specific values for the range. That is, the minimum is
+ * explicitly specified as 100, preferred as 200, and maximum as
+ * 300.
+ * <pre>
+ * group.addComponent(myComponent, 100, 200, 300);
+ * </pre>
+ * The following example adds {@code myComponent} to {@code group} using
+ * a combination of the forms. The minimum size is forced to be the
+ * same as the preferred size, the preferred size is determined by
+ * using {@code myComponent.getPreferredSize} and the maximum is
+ * determined by invoking {@code getMaximumSize} on the component.
+ * <pre>
+ * group.addComponent(myComponent, GroupLayout.PREFERRED_SIZE,
+ * GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE);
+ * </pre>
+ * <p>
+ * Unless otherwise specified all the methods of {@code Group} and
+ * its subclasses that allow you to specify a range throw an
+ * {@code IllegalArgumentException} if passed an invalid range. An
+ * invalid range is one in which any of the values are < 0 and
+ * not one of {@code PREFERRED_SIZE} or {@code DEFAULT_SIZE}, or
+ * the following is not met (for specific values): {@code min}
+ * <= {@code pref} <= {@code max}.
+ * <p>
+ * Similarly any methods that take a {@code Component} throw a
+ * {@code NullPointerException} if passed {@code null} and any methods
+ * that take a {@code Group} throw an {@code IllegalArgumentException} if
+ * passed {@code null}.
+ *
+ * @see #createSequentialGroup
+ * @see #createParallelGroup
+ * @since 1.6
+ */
+ public abstract class Group extends Spring {
+ // private int origin;
+ // private int size;
+ List<Spring> springs;
+
+ Group() {
+ springs = new ArrayList<Spring>();
+ }
+
+ /**
+ * Adds a {@code Group} to this {@code Group}.
+ *
+ * @param group the {@code Group} to add
+ * @return this {@code Group}
+ */
+ public Group addGroup(Group group) {
+ return addSpring(group);
+ }
+
+ /**
+ * Adds a {@code Component} to this {@code Group}.
+ *
+ * @param component the {@code Component} to add
+ * @return this {@code Group}
+ */
+ public Group addComponent(Component component) {
+ return addComponent(component, DEFAULT_SIZE, DEFAULT_SIZE,
+ DEFAULT_SIZE);
+ }
+
+ /**
+ * Adds a {@code Component} to this {@code Group}
+ * with the specified size.
+ *
+ * @param component the {@code Component} to add
+ * @param min the minimum size or one of {@code DEFAULT_SIZE} or
+ * {@code PREFERRED_SIZE}
+ * @param pref the preferred size or one of {@code DEFAULT_SIZE} or
+ * {@code PREFERRED_SIZE}
+ * @param max the maximum size or one of {@code DEFAULT_SIZE} or
+ * {@code PREFERRED_SIZE}
+ * @return this {@code Group}
+ */
+ public Group addComponent(Component component, int min, int pref,
+ int max) {
+ return addSpring(new ComponentSpring(component, min, pref, max));
+ }
+
+ /**
+ * Adds a rigid gap to this {@code Group}.
+ *
+ * @param size the size of the gap
+ * @return this {@code Group}
+ * @throws IllegalArgumentException if {@code size} is less than
+ * {@code 0}
+ */
+ public Group addGap(int size) {
+ return addGap(size, size, size);
+ }
+
+ /**
+ * Adds a gap to this {@code Group} with the specified size.
+ *
+ * @param min the minimum size of the gap
+ * @param pref the preferred size of the gap
+ * @param max the maximum size of the gap
+ * @throws IllegalArgumentException if any of the values are
+ * less than {@code 0}
+ * @return this {@code Group}
+ */
+ public Group addGap(int min, int pref, int max) {
+ return addSpring(new GapSpring(min, pref, max));
+ }
+
+ Spring getSpring(int index) {
+ return springs.get(index);
+ }
+
+ int indexOf(Spring spring) {
+ return springs.indexOf(spring);
+ }
+
+ /**
+ * Adds the Spring to the list of {@code Spring}s and returns
+ * the receiver.
+ */
+ Group addSpring(Spring spring) {
+ springs.add(spring);
+ spring.setParent(this);
+ if (!(spring instanceof AutoPreferredGapSpring) ||
+ !((AutoPreferredGapSpring)spring).getUserCreated()) {
+ springsChanged = true;
+ }
+ return this;
+ }
+
+ //
+ // Spring methods
+ //
+
+ void setSize(int axis, int origin, int size) {
+ super.setSize(axis, origin, size);
+ if (size == UNSET) {
+ for (int counter = springs.size() - 1; counter >= 0;
+ counter--) {
+ getSpring(counter).setSize(axis, origin, size);
+ }
+ } else {
+ setValidSize(axis, origin, size);
+ }
+ }
+
+ /**
+ * This is invoked from {@code setSize} if passed a value
+ * other than UNSET.
+ */
+ abstract void setValidSize(int axis, int origin, int size);
+
+ int calculateMinimumSize(int axis) {
+ return calculateSize(axis, MIN_SIZE);
+ }
+
+ int calculatePreferredSize(int axis) {
+ return calculateSize(axis, PREF_SIZE);
+ }
+
+ int calculateMaximumSize(int axis) {
+ return calculateSize(axis, MAX_SIZE);
+ }
+
+ /**
+ * Calculates the specified size. This is called from
+ * one of the {@code getMinimumSize0},
+ * {@code getPreferredSize0} or
+ * {@code getMaximumSize0} methods. This will invoke
+ * to {@code operator} to combine the values.
+ */
+ int calculateSize(int axis, int type) {
+ int count = springs.size();
+ if (count == 0) {
+ return 0;
+ }
+ if (count == 1) {
+ return getSpringSize(getSpring(0), axis, type);
+ }
+ int size = constrain(operator(getSpringSize(getSpring(0), axis,
+ type), getSpringSize(getSpring(1), axis, type)));
+ for (int counter = 2; counter < count; counter++) {
+ size = constrain(operator(size, getSpringSize(
+ getSpring(counter), axis, type)));
+ }
+ return size;
+ }
+
+ int getSpringSize(Spring spring, int axis, int type) {
+ switch(type) {
+ case MIN_SIZE:
+ return spring.getMinimumSize(axis);
+ case PREF_SIZE:
+ return spring.getPreferredSize(axis);
+ case MAX_SIZE:
+ return spring.getMaximumSize(axis);
+ }
+ assert false;
+ return 0;
+ }
+
+ /**
+ * Used to compute how the two values representing two springs
+ * will be combined. For example, a group that layed things out
+ * one after the next would return {@code a + b}.
+ */
+ abstract int operator(int a, int b);
+
+ //
+ // Padding
+ //
+
+ /**
+ * Adjusts the autopadding springs in this group and its children.
+ * If {@code insert} is true this will insert auto padding
+ * springs, otherwise this will only adjust the springs that
+ * comprise auto preferred padding springs.
+ *
+ * @param axis the axis of the springs; HORIZONTAL or VERTICAL
+ * @param leadingPadding List of AutopaddingSprings that occur before
+ * this Group
+ * @param trailingPadding any trailing autopadding springs are added
+ * to this on exit
+ * @param leading List of ComponentSprings that occur before this Group
+ * @param trailing any trailing ComponentSpring are added to this
+ * List
+ * @param insert Whether or not to insert AutopaddingSprings or just
+ * adjust any existing AutopaddingSprings.
+ */
+ abstract void insertAutopadding(int axis,
+ List<AutoPreferredGapSpring> leadingPadding,
+ List<AutoPreferredGapSpring> trailingPadding,
+ List<ComponentSpring> leading, List<ComponentSpring> trailing,
+ boolean insert);
+
+ /**
+ * Removes any AutopaddingSprings for this Group and its children.
+ */
+ void removeAutopadding() {
+ unset();
+ for (int counter = springs.size() - 1; counter >= 0; counter--) {
+ Spring spring = springs.get(counter);
+ if (spring instanceof AutoPreferredGapSpring) {
+ if (((AutoPreferredGapSpring)spring).getUserCreated()) {
+ ((AutoPreferredGapSpring)spring).reset();
+ } else {
+ springs.remove(counter);
+ }
+ } else if (spring instanceof Group) {
+ ((Group)spring).removeAutopadding();
+ }
+ }
+ }
+
+ void unsetAutopadding() {
+ // Clear cached pref/min/max.
+ unset();
+ for (int counter = springs.size() - 1; counter >= 0; counter--) {
+ Spring spring = springs.get(counter);
+ if (spring instanceof AutoPreferredGapSpring) {
+ ((AutoPreferredGapSpring)spring).unset();
+ } else if (spring instanceof Group) {
+ ((Group)spring).unsetAutopadding();
+ }
+ }
+ }
+
+ void calculateAutopadding(int axis) {
+ for (int counter = springs.size() - 1; counter >= 0; counter--) {
+ Spring spring = springs.get(counter);
+ if (spring instanceof AutoPreferredGapSpring) {
+ // Force size to be reset.
+ spring.unset();
+ ((AutoPreferredGapSpring)spring).calculatePadding(axis);
+ } else if (spring instanceof Group) {
+ ((Group)spring).calculateAutopadding(axis);
+ }
+ }
+ // Clear cached pref/min/max.
+ unset();
+ }
+
+ @Override
+ boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
+ for (int i = springs.size() - 1; i >= 0; i--) {
+ Spring spring = springs.get(i);
+ if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+
+ /**
+ * A {@code Group} that positions and sizes its elements
+ * sequentially, one after another. This class has no public
+ * constructor, use the {@code createSequentialGroup} method
+ * to create one.
+ * <p>
+ * In order to align a {@code SequentialGroup} along the baseline
+ * of a baseline aligned {@code ParallelGroup} you need to specify
+ * which of the elements of the {@code SequentialGroup} is used to
+ * determine the baseline. The element used to calculate the
+ * baseline is specified using one of the {@code add} methods that
+ * take a {@code boolean}. The last element added with a value of
+ * {@code true} for {@code useAsBaseline} is used to calculate the
+ * baseline.
+ *
+ * @see #createSequentialGroup
+ * @since 1.6
+ */
+ public class SequentialGroup extends Group {
+ private Spring baselineSpring;
+
+ SequentialGroup() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SequentialGroup addGroup(Group group) {
+ return (SequentialGroup)super.addGroup(group);
+ }
+
+ /**
+ * Adds a {@code Group} to this {@code Group}.
+ *
+ * @param group the {@code Group} to add
+ * @param useAsBaseline whether the specified {@code Group} should
+ * be used to calculate the baseline for this {@code Group}
+ * @return this {@code Group}
+ */
+ public SequentialGroup addGroup(boolean useAsBaseline, Group group) {
+ super.addGroup(group);
+ if (useAsBaseline) {
+ baselineSpring = group;
+ }
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SequentialGroup addComponent(Component component) {
+ return (SequentialGroup)super.addComponent(component);
+ }
+
+ /**
+ * Adds a {@code Component} to this {@code Group}.
+ *
+ * @param useAsBaseline whether the specified {@code Component} should
+ * be used to calculate the baseline for this {@code Group}
+ * @param component the {@code Component} to add
+ * @return this {@code Group}
+ */
+ public SequentialGroup addComponent(boolean useAsBaseline,
+ Component component) {
+ super.addComponent(component);
+ if (useAsBaseline) {
+ baselineSpring = springs.get(springs.size() - 1);
+ }
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SequentialGroup addComponent(Component component, int min,
+ int pref, int max) {
+ return (SequentialGroup)super.addComponent(
+ component, min, pref, max);
+ }
+
+ /**
+ * Adds a {@code Component} to this {@code Group}
+ * with the specified size.
+ *
+ * @param useAsBaseline whether the specified {@code Component} should
+ * be used to calculate the baseline for this {@code Group}
+ * @param component the {@code Component} to add
+ * @param min the minimum size or one of {@code DEFAULT_SIZE} or
+ * {@code PREFERRED_SIZE}
+ * @param pref the preferred size or one of {@code DEFAULT_SIZE} or
+ * {@code PREFERRED_SIZE}
+ * @param max the maximum size or one of {@code DEFAULT_SIZE} or
+ * {@code PREFERRED_SIZE}
+ * @return this {@code Group}
+ */
+ public SequentialGroup addComponent(boolean useAsBaseline,
+ Component component, int min, int pref, int max) {
+ super.addComponent(component, min, pref, max);
+ if (useAsBaseline) {
+ baselineSpring = springs.get(springs.size() - 1);
+ }
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SequentialGroup addGap(int size) {
+ return (SequentialGroup)super.addGap(size);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SequentialGroup addGap(int min, int pref, int max) {
+ return (SequentialGroup)super.addGap(min, pref, max);
+ }
+
+ /**
+ * Adds an element representing the preferred gap between two
+ * components. The element created to represent the gap is not
+ * resizable.
+ *
+ * @param comp1 the first component
+ * @param comp2 the second component
+ * @param type the type of gap; one of the constants defined by
+ * {@code LayoutStyle}
+ * @return this {@code SequentialGroup}
+ * @throws IllegalArgumentException if {@code type}, {@code comp1} or
+ * {@code comp2} is {@code null}
+ * @see LayoutStyle
+ */
+ public SequentialGroup addPreferredGap(JComponent comp1,
+ JComponent comp2, ComponentPlacement type) {
+ return addPreferredGap(comp1, comp2, type, DEFAULT_SIZE,
+ PREFERRED_SIZE);
+ }
+
+ /**
+ * Adds an element representing the preferred gap between two
+ * components.
+ *
+ * @param comp1 the first component
+ * @param comp2 the second component
+ * @param type the type of gap
+ * @param pref the preferred size of the grap; one of
+ * {@code DEFAULT_SIZE} or a value >= 0
+ * @param max the maximum size of the gap; one of
+ * {@code DEFAULT_SIZE}, {@code PREFERRED_SIZE}
+ * or a value >= 0
+ * @return this {@code SequentialGroup}
+ * @throws IllegalArgumentException if {@code type}, {@code comp1} or
+ * {@code comp2} is {@code null}
+ * @see LayoutStyle
+ */
+ public SequentialGroup addPreferredGap(JComponent comp1,
+ JComponent comp2, ComponentPlacement type, int pref,
+ int max) {
+ if (type == null) {
+ throw new IllegalArgumentException("Type must be non-null");
+ }
+ if (comp1 == null || comp2 == null) {
+ throw new IllegalArgumentException(
+ "Components must be non-null");
+ }
+ checkPreferredGapValues(pref, max);
+ return (SequentialGroup)addSpring(new PreferredGapSpring(
+ comp1, comp2, type, pref, max));
+ }
+
+ /**
+ * Adds an element representing the preferred gap between the
+ * nearest components. During layout, neighboring
+ * components are found, and the size of the added gap is set
+ * based on the preferred gap between the components. If no
+ * neighboring components are found the gap has a size of {@code 0}.
+ * <p>
+ * The element created to represent the gap is not
+ * resizable.
+ *
+ * @param type the type of gap; one of
+ * {@code LayoutStyle.ComponentPlacement.RELATED} or
+ * {@code LayoutStyle.ComponentPlacement.UNRELATED}
+ * @return this {@code SequentialGroup}
+ * @see LayoutStyle
+ * @throws IllegalArgumentException if {@code type} is not one of
+ * {@code LayoutStyle.ComponentPlacement.RELATED} or
+ * {@code LayoutStyle.ComponentPlacement.UNRELATED}
+ */
+ public SequentialGroup addPreferredGap(ComponentPlacement type) {
+ return addPreferredGap(type, DEFAULT_SIZE, DEFAULT_SIZE);
+ }
+
+ /**
+ * Adds an element representing the preferred gap between the
+ * nearest components. During layout, neighboring
+ * components are found, and the minimum of this
+ * gap is set based on the size of the preferred gap between the
+ * neighboring components. If no neighboring components are found the
+ * minimum size is set to 0.
+ *
+ * @param type the type of gap; one of
+ * {@code LayoutStyle.ComponentPlacement.RELATED} or
+ * {@code LayoutStyle.ComponentPlacement.UNRELATED}
+ * @param pref the preferred size of the grap; one of
+ * {@code DEFAULT_SIZE} or a value >= 0
+ * @param max the maximum size of the gap; one of
+ * {@code DEFAULT_SIZE}, {@code PREFERRED_SIZE}
+ * or a value >= 0
+ * @return this {@code SequentialGroup}
+ * @throws IllegalArgumentException if {@code type} is not one of
+ * {@code LayoutStyle.ComponentPlacement.RELATED} or
+ * {@code LayoutStyle.ComponentPlacement.UNRELATED}
+ * @see LayoutStyle
+ */
+ public SequentialGroup addPreferredGap(ComponentPlacement type,
+ int pref, int max) {
+ if (type != ComponentPlacement.RELATED &&
+ type != ComponentPlacement.UNRELATED) {
+ throw new IllegalArgumentException(
+ "Type must be one of " +
+ "LayoutStyle.ComponentPlacement.RELATED or " +
+ "LayoutStyle.ComponentPlacement.UNRELATED");
+ }
+ checkPreferredGapValues(pref, max);
+ hasPreferredPaddingSprings = true;
+ return (SequentialGroup)addSpring(new AutoPreferredGapSpring(
+ type, pref, max));
+ }
+
+ /**
+ * Adds an element representing the preferred gap between an edge
+ * the container and components that touch the border of the
+ * container. This has no effect if the added gap does not
+ * touch an edge of the parent container.
+ * <p>
+ * The element created to represent the gap is not
+ * resizable.
+ *
+ * @return this {@code SequentialGroup}
+ */
+ public SequentialGroup addContainerGap() {
+ return addContainerGap(DEFAULT_SIZE, DEFAULT_SIZE);
+ }
+
+ /**
+ * Adds an element representing the preferred gap between one
+ * edge of the container and the next or previous {@code
+ * Component} with the specified size. This has no
+ * effect if the next or previous element is not a {@code
+ * Component} and does not touch one edge of the parent
+ * container.
+ *
+ * @param pref the preferred size; one of {@code DEFAULT_SIZE} or a
+ * value >= 0
+ * @param max the maximum size; one of {@code DEFAULT_SIZE},
+ * {@code PREFERRED_SIZE} or a value >= 0
+ * @return this {@code SequentialGroup}
+ */
+ public SequentialGroup addContainerGap(int pref, int max) {
+ if ((pref < 0 && pref != DEFAULT_SIZE) ||
+ (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE)||
+ (pref >= 0 && max >= 0 && pref > max)) {
+ throw new IllegalArgumentException(
+ "Pref and max must be either DEFAULT_VALUE " +
+ "or >= 0 and pref <= max");
+ }
+ hasPreferredPaddingSprings = true;
+ return (SequentialGroup)addSpring(
+ new ContainerAutoPreferredGapSpring(pref, max));
+ }
+
+ int operator(int a, int b) {
+ return constrain(a) + constrain(b);
+ }
+
+ void setValidSize(int axis, int origin, int size) {
+ int pref = getPreferredSize(axis);
+ if (size == pref) {
+ // Layout at preferred size
+ for (Spring spring : springs) {
+ int springPref = spring.getPreferredSize(axis);
+ spring.setSize(axis, origin, springPref);
+ origin += springPref;
+ }
+ } else if (springs.size() == 1) {
+ Spring spring = getSpring(0);
+ spring.setSize(axis, origin, Math.min(
+ Math.max(size, spring.getMinimumSize(axis)),
+ spring.getMaximumSize(axis)));
+ } else if (springs.size() > 1) {
+ // Adjust between min/pref
+ setValidSizeNotPreferred(axis, origin, size);
+ }
+ }
+
+ private void setValidSizeNotPreferred(int axis, int origin, int size) {
+ int delta = size - getPreferredSize(axis);
+ assert delta != 0;
+ boolean useMin = (delta < 0);
+ int springCount = springs.size();
+ if (useMin) {
+ delta *= -1;
+ }
+
+ // The following algorithm if used for resizing springs:
+ // 1. Calculate the resizability of each spring (pref - min or
+ // max - pref) into a list.
+ // 2. Sort the list in ascending order
+ // 3. Iterate through each of the resizable Springs, attempting
+ // to give them (pref - size) / resizeCount
+ // 4. For any Springs that can not accomodate that much space
+ // add the remainder back to the amount to distribute and
+ // recalculate how must space the remaining springs will get.
+ // 5. Set the size of the springs.
+
+ // First pass, sort the resizable springs into the List resizable
+ List<SpringDelta> resizable = buildResizableList(axis, useMin);
+ int resizableCount = resizable.size();
+
+ if (resizableCount > 0) {
+ // How much we would like to give each Spring.
+ int sDelta = delta / resizableCount;
+ // Remaining space.
+ int slop = delta - sDelta * resizableCount;
+ int[] sizes = new int[springCount];
+ int sign = useMin ? -1 : 1;
+ // Second pass, accumulate the resulting deltas (relative to
+ // preferred) into sizes.
+ for (int counter = 0; counter < resizableCount; counter++) {
+ SpringDelta springDelta = resizable.get(counter);
+ if ((counter + 1) == resizableCount) {
+ sDelta += slop;
+ }
+ springDelta.delta = Math.min(sDelta, springDelta.delta);
+ delta -= springDelta.delta;
+ if (springDelta.delta != sDelta && counter + 1 <
+ resizableCount) {
+ // Spring didn't take all the space, reset how much
+ // each spring will get.
+ sDelta = delta / (resizableCount - counter - 1);
+ slop = delta - sDelta * (resizableCount - counter - 1);
+ }
+ sizes[springDelta.index] = sign * springDelta.delta;
+ }
+
+ // And finally set the size of each spring
+ for (int counter = 0; counter < springCount; counter++) {
+ Spring spring = getSpring(counter);
+ int sSize = spring.getPreferredSize(axis) + sizes[counter];
+ spring.setSize(axis, origin, sSize);
+ origin += sSize;
+ }
+ } else {
+ // Nothing resizable, use the min or max of each of the
+ // springs.
+ for (int counter = 0; counter < springCount; counter++) {
+ Spring spring = getSpring(counter);
+ int sSize;
+ if (useMin) {
+ sSize = spring.getMinimumSize(axis);
+ } else {
+ sSize = spring.getMaximumSize(axis);
+ }
+ spring.setSize(axis, origin, sSize);
+ origin += sSize;
+ }
+ }
+ }
+
+ /**
+ * Returns the sorted list of SpringDelta's for the current set of
+ * Springs. The list is ordered based on the amount of flexibility of
+ * the springs.
+ */
+ private List<SpringDelta> buildResizableList(int axis,
+ boolean useMin) {
+ // First pass, figure out what is resizable
+ int size = springs.size();
+ List<SpringDelta> sorted = new ArrayList<SpringDelta>(size);
+ for (int counter = 0; counter < size; counter++) {
+ Spring spring = getSpring(counter);
+ int sDelta;
+ if (useMin) {
+ sDelta = spring.getPreferredSize(axis) -
+ spring.getMinimumSize(axis);
+ } else {
+ sDelta = spring.getMaximumSize(axis) -
+ spring.getPreferredSize(axis);
+ }
+ if (sDelta > 0) {
+ sorted.add(new SpringDelta(counter, sDelta));
+ }
+ }
+ Collections.sort(sorted);
+ return sorted;
+ }
+
+ private int indexOfNextNonZeroSpring(
+ int index, boolean treatAutopaddingAsZeroSized) {
+ while (index < springs.size()) {
+ Spring spring = springs.get(index);
+ if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) {
+ return index;
+ }
+ index++;
+ }
+ return index;
+ }
+
+ @Override
+ void insertAutopadding(int axis,
+ List<AutoPreferredGapSpring> leadingPadding,
+ List<AutoPreferredGapSpring> trailingPadding,
+ List<ComponentSpring> leading, List<ComponentSpring> trailing,
+ boolean insert) {
+ List<AutoPreferredGapSpring> newLeadingPadding =
+ new ArrayList<AutoPreferredGapSpring>(leadingPadding);
+ List<AutoPreferredGapSpring> newTrailingPadding =
+ new ArrayList<AutoPreferredGapSpring>(1);
+ List<ComponentSpring> newLeading =
+ new ArrayList<ComponentSpring>(leading);
+ List<ComponentSpring> newTrailing = null;
+ int counter = 0;
+ // Warning, this must use springs.size, as it may change during the
+ // loop.
+ while (counter < springs.size()) {
+ Spring spring = getSpring(counter);
+ if (spring instanceof AutoPreferredGapSpring) {
+ if (newLeadingPadding.size() == 0) {
+ // Autopadding spring. Set the sources of the
+ // autopadding spring based on newLeading.
+ AutoPreferredGapSpring padding =
+ (AutoPreferredGapSpring)spring;
+ padding.setSources(newLeading);
+ newLeading.clear();
+ counter = indexOfNextNonZeroSpring(counter + 1, true);
+ if (counter == springs.size()) {
+ // Last spring in the list, add it to
+ // trailingPadding.
+ if (!(padding instanceof
+ ContainerAutoPreferredGapSpring)) {
+ trailingPadding.add(padding);
+ }
+ } else {
+ newLeadingPadding.clear();
+ newLeadingPadding.add(padding);
+ }
+ } else {
+ counter = indexOfNextNonZeroSpring(counter + 1, true);
+ }
+ } else {
+ // Not a padding spring
+ if (newLeading.size() > 0 && insert) {
+ // There's leading ComponentSprings, create an
+ // autopadding spring.
+ AutoPreferredGapSpring padding =
+ new AutoPreferredGapSpring();
+ // Force the newly created spring to be considered
+ // by NOT incrementing counter
+ springs.add(counter, padding);
+ continue;
+ }
+ if (spring instanceof ComponentSpring) {
+ // Spring is a Component, make it the target of any
+ // leading AutopaddingSpring.
+ ComponentSpring cSpring = (ComponentSpring)spring;
+ if (!cSpring.isVisible()) {
+ counter++;
+ continue;
+ }
+ for (AutoPreferredGapSpring gapSpring : newLeadingPadding) {
+ gapSpring.addTarget(cSpring, axis);
+ }
+ newLeading.clear();
+ newLeadingPadding.clear();
+ counter = indexOfNextNonZeroSpring(counter + 1, false);
+ if (counter == springs.size()) {
+ // Last Spring, add it to trailing
+ trailing.add(cSpring);
+ } else {
+ // Not that last Spring, add it to leading
+ newLeading.add(cSpring);
+ }
+ } else if (spring instanceof Group) {
+ // Forward call to child Group
+ if (newTrailing == null) {
+ newTrailing = new ArrayList<ComponentSpring>(1);
+ } else {
+ newTrailing.clear();
+ }
+ newTrailingPadding.clear();
+ ((Group)spring).insertAutopadding(axis,
+ newLeadingPadding, newTrailingPadding,
+ newLeading, newTrailing, insert);
+ newLeading.clear();
+ newLeadingPadding.clear();
+ counter = indexOfNextNonZeroSpring(
+ counter + 1, (newTrailing.size() == 0));
+ if (counter == springs.size()) {
+ trailing.addAll(newTrailing);
+ trailingPadding.addAll(newTrailingPadding);
+ } else {
+ newLeading.addAll(newTrailing);
+ newLeadingPadding.addAll(newTrailingPadding);
+ }
+ } else {
+ // Gap
+ newLeadingPadding.clear();
+ newLeading.clear();
+ counter++;
+ }
+ }
+ }
+ }
+
+ int getBaseline() {
+ if (baselineSpring != null) {
+ int baseline = baselineSpring.getBaseline();
+ if (baseline >= 0) {
+ int size = 0;
+ for (Spring spring : springs) {
+ if (spring == baselineSpring) {
+ return size + baseline;
+ } else {
+ size += spring.getPreferredSize(VERTICAL);
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ BaselineResizeBehavior getBaselineResizeBehavior() {
+ if (isResizable(VERTICAL)) {
+ if (!baselineSpring.isResizable(VERTICAL)) {
+ // Spring to use for baseline isn't resizable. In this case
+ // baseline resize behavior can be determined based on how
+ // preceeding springs resize.
+ boolean leadingResizable = false;
+ for (Spring spring : springs) {
+ if (spring == baselineSpring) {
+ break;
+ } else if (spring.isResizable(VERTICAL)) {
+ leadingResizable = true;
+ break;
+ }
+ }
+ boolean trailingResizable = false;
+ for (int i = springs.size() - 1; i >= 0; i--) {
+ Spring spring = springs.get(i);
+ if (spring == baselineSpring) {
+ break;
+ }
+ if (spring.isResizable(VERTICAL)) {
+ trailingResizable = true;
+ break;
+ }
+ }
+ if (leadingResizable && !trailingResizable) {
+ return BaselineResizeBehavior.CONSTANT_DESCENT;
+ } else if (!leadingResizable && trailingResizable) {
+ return BaselineResizeBehavior.CONSTANT_ASCENT;
+ }
+ // If we get here, both leading and trailing springs are
+ // resizable. Fall through to OTHER.
+ } else {
+ BaselineResizeBehavior brb = baselineSpring.getBaselineResizeBehavior();
+ if (brb == BaselineResizeBehavior.CONSTANT_ASCENT) {
+ for (Spring spring : springs) {
+ if (spring == baselineSpring) {
+ return BaselineResizeBehavior.CONSTANT_ASCENT;
+ }
+ if (spring.isResizable(VERTICAL)) {
+ return BaselineResizeBehavior.OTHER;
+ }
+ }
+ } else if (brb == BaselineResizeBehavior.CONSTANT_DESCENT) {
+ for (int i = springs.size() - 1; i >= 0; i--) {
+ Spring spring = springs.get(i);
+ if (spring == baselineSpring) {
+ return BaselineResizeBehavior.CONSTANT_DESCENT;
+ }
+ if (spring.isResizable(VERTICAL)) {
+ return BaselineResizeBehavior.OTHER;
+ }
+ }
+ }
+ }
+ return BaselineResizeBehavior.OTHER;
+ }
+ // Not resizable, treat as constant_ascent
+ return BaselineResizeBehavior.CONSTANT_ASCENT;
+ }
+
+ private void checkPreferredGapValues(int pref, int max) {
+ if ((pref < 0 && pref != DEFAULT_SIZE && pref != PREFERRED_SIZE) ||
+ (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE)||
+ (pref >= 0 && max >= 0 && pref > max)) {
+ throw new IllegalArgumentException(
+ "Pref and max must be either DEFAULT_SIZE, " +
+ "PREFERRED_SIZE, or >= 0 and pref <= max");
+ }
+ }
+ }
+
+
+ /**
+ * Used by SequentialGroup in calculating resizability of springs.
+ */
+ private static final class SpringDelta implements Comparable<SpringDelta> {
+ // Original index.
+ public final int index;
+ // Delta, one of pref - min or max - pref.
+ public int delta;
+
+ public SpringDelta(int index, int delta) {
+ this.index = index;
+ this.delta = delta;
+ }
+
+ public int compareTo(SpringDelta o) {
+ return delta - o.delta;
+ }
+
+ public String toString() {
+ return super.toString() + "[index=" + index + ", delta=" +
+ delta + "]";
+ }
+ }
+
+
+ /**
+ * A {@code Group} that aligns and sizes it's children.
+ * {@code ParallelGroup} aligns it's children in
+ * four possible ways: along the baseline, centered, anchored to the
+ * leading edge, or anchored to the trailing edge.
+ * <h3>Baseline</h3>
+ * A {@code ParallelGroup} that aligns it's children along the
+ * baseline must first decide where the baseline is
+ * anchored. The baseline can either be anchored to the top, or
+ * anchored to the bottom of the group. That is, the distance between the
+ * baseline and the beginning of the group can be a constant
+ * distance, or the distance between the end of the group and the
+ * baseline can be a constant distance. The possible choices
+ * correspond to the {@code BaselineResizeBehavior} constants
+ * {@link
+ * java.awt.Component.BaselineResizeBehavior#CONSTANT_ASCENT CONSTANT_ASCENT} and
+ * {@link
+ * java.awt.Component.BaselineResizeBehavior#CONSTANT_DESCENT CONSTANT_DESCENT}.
+ * <p>
+ * The baseline anchor may be explicitly specified by the
+ * {@code createBaselineGroup} method, or determined based on the elements.
+ * If not explicitly specified, the baseline will be anchored to
+ * the bottom if all the elements with a baseline, and that are
+ * aligned to the baseline, have a baseline resize behavior of
+ * {@code CONSTANT_DESCENT}; otherwise the baseline is anchored to the top
+ * of the group.
+ * <p>
+ * Elements aligned to the baseline are resizable if they have have
+ * a baseline resize behavior of {@code CONSTANT_ASCENT} or
+ * {@code CONSTANT_DESCENT}. Elements with a baseline resize
+ * behavior of {@code OTHER} or {@code CENTER_OFFSET} are not resizable.
+ * <p>
+ * The baseline is calculated based on the preferred height of each
+ * of the elements that have a baseline. The baseline is
+ * calculated using the following algorithm:
+ * {@code max(maxNonBaselineHeight, maxAscent + maxDescent)}, where the
+ * {@code maxNonBaselineHeight} is the maximum height of all elements
+ * that do not have a baseline, or are not aligned along the baseline.
+ * {@code maxAscent} is the maximum ascent (baseline) of all elements that
+ * have a baseline and are aligned along the baseline.
+ * {@code maxDescent} is the maximum descent (preferred height - baseline)
+ * of all elements that have a baseline and are aligned along the baseline.
+ * <p>
+ * A {@code ParallelGroup} that aligns it's elements along the baseline
+ * is only useful along the vertical axis. If you create a
+ * baseline group and use it along the horizontal axis an
+ * {@code IllegalStateException} is thrown when you ask
+ * {@code GroupLayout} for the minimum, preferred or maximum size or
+ * attempt to layout the components.
+ * <p>
+ * Elements that are not aligned to the baseline and smaller than the size
+ * of the {@code ParallelGroup} are positioned in one of three
+ * ways: centered, anchored to the leading edge, or anchored to the
+ * trailing edge.
+ *
+ * <h3>Non-baseline {@code ParallelGroup}</h3>
+ * {@code ParallelGroup}s created with an alignment other than
+ * {@code BASELINE} align elements that are smaller than the size
+ * of the group in one of three ways: centered, anchored to the
+ * leading edge, or anchored to the trailing edge.
+ * <p>
+ * The leading edge is based on the axis and {@code
+ * ComponentOrientation}. For the vertical axis the top edge is
+ * always the leading edge, and the bottom edge is always the
+ * trailing edge. When the {@code ComponentOrientation} is {@code
+ * LEFT_TO_RIGHT}, the leading edge is the left edge and the
+ * trailing edge the right edge. A {@code ComponentOrientation} of
+ * {@code RIGHT_TO_LEFT} flips the left and right edges. Child
+ * elements are aligned based on the specified alignment the
+ * element was added with. If you do not specify an alignment, the
+ * alignment specified for the {@code ParallelGroup} is used.
+ * <p>
+ * To align elements along the baseline you {@code createBaselineGroup},
+ * or {@code createParallelGroup} with an alignment of {@code BASELINE}.
+ * If the group was not created with a baseline alignment, and you attempt
+ * to add an element specifying a baseline alignment, an
+ * {@code IllegalArgumentException} is thrown.
+ *
+ * @see #createParallelGroup()
+ * @see #createBaselineGroup(boolean,boolean)
+ * @since 1.6
+ */
+ public class ParallelGroup extends Group {
+ // How children are layed out.
+ private final Alignment childAlignment;
+ // Whether or not we're resizable.
+ private final boolean resizable;
+
+ ParallelGroup(Alignment childAlignment, boolean resizable) {
+ this.childAlignment = childAlignment;
+ this.resizable = resizable;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ParallelGroup addGroup(Group group) {
+ return (ParallelGroup)super.addGroup(group);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ParallelGroup addComponent(Component component) {
+ return (ParallelGroup)super.addComponent(component);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ParallelGroup addComponent(Component component, int min, int pref,
+ int max) {
+ return (ParallelGroup)super.addComponent(component, min, pref, max);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ParallelGroup addGap(int pref) {
+ return (ParallelGroup)super.addGap(pref);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ParallelGroup addGap(int min, int pref, int max) {
+ return (ParallelGroup)super.addGap(min, pref, max);
+ }
+
+ /**
+ * Adds a {@code Group} to this {@code ParallelGroup} with the
+ * specified alignment. If the child is smaller than the
+ * {@code Group} it is aligned based on the specified
+ * alignment.
+ *
+ * @param alignment the alignment
+ * @param group the {@code Group} to add
+ * @return this {@code ParallelGroup}
+ * @throws IllegalArgumentException if {@code alignment} is
+ * {@code null}
+ */
+ public ParallelGroup addGroup(Alignment alignment, Group group) {
+ checkChildAlignment(alignment);
+ group.setAlignment(alignment);
+ return (ParallelGroup)addSpring(group);
+ }
+
+ /**
+ * Adds a {@code Component} to this {@code ParallelGroup} with
+ * the specified alignment.
+ *
+ * @param alignment the alignment
+ * @param component the {@code Component} to add
+ * @return this {@code Group}
+ * @throws IllegalArgumentException if {@code alignment} is
+ * {@code null}
+ */
+ public ParallelGroup addComponent(Component component,
+ Alignment alignment) {
+ return addComponent(component, alignment, DEFAULT_SIZE, DEFAULT_SIZE,
+ DEFAULT_SIZE);
+ }
+
+ /**
+ * Adds a {@code Component} to this {@code ParallelGroup} with the
+ * specified alignment and size.
+ *
+ * @param alignment the alignment
+ * @param component the {@code Component} to add
+ * @param min the minimum size
+ * @param pref the preferred size
+ * @param max the maximum size
+ * @throws IllegalArgumentException if {@code alignment} is
+ * {@code null}
+ * @return this {@code Group}
+ */
+ public ParallelGroup addComponent(Component component,
+ Alignment alignment, int min, int pref, int max) {
+ checkChildAlignment(alignment);
+ ComponentSpring spring = new ComponentSpring(component,
+ min, pref, max);
+ spring.setAlignment(alignment);
+ return (ParallelGroup)addSpring(spring);
+ }
+
+ boolean isResizable() {
+ return resizable;
+ }
+
+ int operator(int a, int b) {
+ return Math.max(a, b);
+ }
+
+ int calculateMinimumSize(int axis) {
+ if (!isResizable()) {
+ return getPreferredSize(axis);
+ }
+ return super.calculateMinimumSize(axis);
+ }
+
+ int calculateMaximumSize(int axis) {
+ if (!isResizable()) {
+ return getPreferredSize(axis);
+ }
+ return super.calculateMaximumSize(axis);
+ }
+
+ void setValidSize(int axis, int origin, int size) {
+ for (Spring spring : springs) {
+ setChildSize(spring, axis, origin, size);
+ }
+ }
+
+ void setChildSize(Spring spring, int axis, int origin, int size) {
+ Alignment alignment = spring.getAlignment();
+ int springSize = Math.min(
+ Math.max(spring.getMinimumSize(axis), size),
+ spring.getMaximumSize(axis));
+ if (alignment == null) {
+ alignment = childAlignment;
+ }
+ switch (alignment) {
+ case TRAILING:
+ spring.setSize(axis, origin + size - springSize,
+ springSize);
+ break;
+ case CENTER:
+ spring.setSize(axis, origin +
+ (size - springSize) / 2,springSize);
+ break;
+ default: // LEADING, or BASELINE
+ spring.setSize(axis, origin, springSize);
+ break;
+ }
+ }
+
+ @Override
+ void insertAutopadding(int axis,
+ List<AutoPreferredGapSpring> leadingPadding,
+ List<AutoPreferredGapSpring> trailingPadding,
+ List<ComponentSpring> leading, List<ComponentSpring> trailing,
+ boolean insert) {
+ for (Spring spring : springs) {
+ if (spring instanceof ComponentSpring) {
+ if (((ComponentSpring)spring).isVisible()) {
+ for (AutoPreferredGapSpring gapSpring :
+ leadingPadding) {
+ gapSpring.addTarget((ComponentSpring)spring, axis);
+ }
+ trailing.add((ComponentSpring)spring);
+ }
+ } else if (spring instanceof Group) {
+ ((Group)spring).insertAutopadding(axis, leadingPadding,
+ trailingPadding, leading, trailing, insert);
+ } else if (spring instanceof AutoPreferredGapSpring) {
+ ((AutoPreferredGapSpring)spring).setSources(leading);
+ trailingPadding.add((AutoPreferredGapSpring)spring);
+ }
+ }
+ }
+
+ private void checkChildAlignment(Alignment alignment) {
+ checkChildAlignment(alignment, (this instanceof BaselineGroup));
+ }
+
+ private void checkChildAlignment(Alignment alignment,
+ boolean allowsBaseline) {
+ if (alignment == null) {
+ throw new IllegalArgumentException("Alignment must be non-null");
+ }
+ if (!allowsBaseline && alignment == Alignment.BASELINE) {
+ throw new IllegalArgumentException("Alignment must be one of:" +
+ "LEADING, TRAILING or CENTER");
+ }
+ }
+ }
+
+
+ /**
+ * An extension of {@code ParallelGroup} that aligns its
+ * constituent {@code Spring}s along the baseline.
+ */
+ private class BaselineGroup extends ParallelGroup {
+ // Whether or not all child springs have a baseline
+ private boolean allSpringsHaveBaseline;
+
+ // max(spring.getBaseline()) of all springs aligned along the baseline
+ // that have a baseline
+ private int prefAscent;
+
+ // max(spring.getPreferredSize().height - spring.getBaseline()) of all
+ // springs aligned along the baseline that have a baseline
+ private int prefDescent;
+
+ // Whether baselineAnchoredToTop was explicitly set
+ private boolean baselineAnchorSet;
+
+ // Whether the baseline is anchored to the top or the bottom.
+ // If anchored to the top the baseline is always at prefAscent,
+ // otherwise the baseline is at (height - prefDescent)
+ private boolean baselineAnchoredToTop;
+
+ // Whether or not the baseline has been calculated.
+ private boolean calcedBaseline;
+
+ BaselineGroup(boolean resizable) {
+ super(Alignment.LEADING, resizable);
+ prefAscent = prefDescent = -1;
+ calcedBaseline = false;
+ }
+
+ BaselineGroup(boolean resizable, boolean baselineAnchoredToTop) {
+ this(resizable);
+ this.baselineAnchoredToTop = baselineAnchoredToTop;
+ baselineAnchorSet = true;
+ }
+
+ void unset() {
+ super.unset();
+ prefAscent = prefDescent = -1;
+ calcedBaseline = false;
+ }
+
+ void setValidSize(int axis, int origin, int size) {
+ checkAxis(axis);
+ if (prefAscent == -1) {
+ super.setValidSize(axis, origin, size);
+ } else {
+ // do baseline layout
+ baselineLayout(origin, size);
+ }
+ }
+
+ int calculateSize(int axis, int type) {
+ checkAxis(axis);
+ if (!calcedBaseline) {
+ calculateBaselineAndResizeBehavior();
+ }
+ if (type == MIN_SIZE) {
+ return calculateMinSize();
+ }
+ if (type == MAX_SIZE) {
+ return calculateMaxSize();
+ }
+ if (allSpringsHaveBaseline) {
+ return prefAscent + prefDescent;
+ }
+ return Math.max(prefAscent + prefDescent,
+ super.calculateSize(axis, type));
+ }
+
+ private void calculateBaselineAndResizeBehavior() {
+ // calculate baseline
+ prefAscent = 0;
+ prefDescent = 0;
+ int baselineSpringCount = 0;
+ BaselineResizeBehavior resizeBehavior = null;
+ for (Spring spring : springs) {
+ if (spring.getAlignment() == null ||
+ spring.getAlignment() == Alignment.BASELINE) {
+ int baseline = spring.getBaseline();
+ if (baseline >= 0) {
+ if (spring.isResizable(VERTICAL)) {
+ BaselineResizeBehavior brb = spring.
+ getBaselineResizeBehavior();
+ if (resizeBehavior == null) {
+ resizeBehavior = brb;
+ } else if (brb != resizeBehavior) {
+ resizeBehavior = BaselineResizeBehavior.
+ CONSTANT_ASCENT;
+ }
+ }
+ prefAscent = Math.max(prefAscent, baseline);
+ prefDescent = Math.max(prefDescent, spring.
+ getPreferredSize(VERTICAL) - baseline);
+ baselineSpringCount++;
+ }
+ }
+ }
+ if (!baselineAnchorSet) {
+ if (resizeBehavior == BaselineResizeBehavior.CONSTANT_DESCENT){
+ this.baselineAnchoredToTop = false;
+ } else {
+ this.baselineAnchoredToTop = true;
+ }
+ }
+ allSpringsHaveBaseline = (baselineSpringCount == springs.size());
+ calcedBaseline = true;
+ }
+
+ private int calculateMaxSize() {
+ int maxAscent = prefAscent;
+ int maxDescent = prefDescent;
+ int nonBaselineMax = 0;
+ for (Spring spring : springs) {
+ int baseline;
+ int springMax = spring.getMaximumSize(VERTICAL);
+ if ((spring.getAlignment() == null ||
+ spring.getAlignment() == Alignment.BASELINE) &&
+ (baseline = spring.getBaseline()) >= 0) {
+ int springPref = spring.getPreferredSize(VERTICAL);
+ if (springPref != springMax) {
+ switch (spring.getBaselineResizeBehavior()) {
+ case CONSTANT_ASCENT:
+ if (baselineAnchoredToTop) {
+ maxDescent = Math.max(maxDescent,
+ springMax - baseline);
+ }
+ break;
+ case CONSTANT_DESCENT:
+ if (!baselineAnchoredToTop) {
+ maxAscent = Math.max(maxAscent,
+ springMax - springPref + baseline);
+ }
+ break;
+ default: // CENTER_OFFSET and OTHER, not resizable
+ break;
+ }
+ }
+ } else {
+ // Not aligned along the baseline, or no baseline.
+ nonBaselineMax = Math.max(nonBaselineMax, springMax);
+ }
+ }
+ return Math.max(nonBaselineMax, maxAscent + maxDescent);
+ }
+
+ private int calculateMinSize() {
+ int minAscent = 0;
+ int minDescent = 0;
+ int nonBaselineMin = 0;
+ if (baselineAnchoredToTop) {
+ minAscent = prefAscent;
+ } else {
+ minDescent = prefDescent;
+ }
+ for (Spring spring : springs) {
+ int springMin = spring.getMinimumSize(VERTICAL);
+ int baseline;
+ if ((spring.getAlignment() == null ||
+ spring.getAlignment() == Alignment.BASELINE) &&
+ (baseline = spring.getBaseline()) >= 0) {
+ int springPref = spring.getPreferredSize(VERTICAL);
+ BaselineResizeBehavior brb = spring.
+ getBaselineResizeBehavior();
+ switch (brb) {
+ case CONSTANT_ASCENT:
+ if (baselineAnchoredToTop) {
+ minDescent = Math.max(springMin - baseline,
+ minDescent);
+ } else {
+ minAscent = Math.max(baseline, minAscent);
+ }
+ break;
+ case CONSTANT_DESCENT:
+ if (!baselineAnchoredToTop) {
+ minAscent = Math.max(
+ baseline - (springPref - springMin),
+ minAscent);
+ } else {
+ minDescent = Math.max(springPref - baseline,
+ minDescent);
+ }
+ break;
+ default:
+ // CENTER_OFFSET and OTHER are !resizable, use
+ // the preferred size.
+ minAscent = Math.max(baseline, minAscent);
+ minDescent = Math.max(springPref - baseline,
+ minDescent);
+ break;
+ }
+ } else {
+ // Not aligned along the baseline, or no baseline.
+ nonBaselineMin = Math.max(nonBaselineMin, springMin);
+ }
+ }
+ return Math.max(nonBaselineMin, minAscent + minDescent);
+ }
+
+ /**
+ * Lays out springs that have a baseline along the baseline. All
+ * others are centered.
+ */
+ private void baselineLayout(int origin, int size) {
+ int ascent;
+ int descent;
+ if (baselineAnchoredToTop) {
+ ascent = prefAscent;
+ descent = size - ascent;
+ } else {
+ ascent = size - prefDescent;
+ descent = prefDescent;
+ }
+ for (Spring spring : springs) {
+ Alignment alignment = spring.getAlignment();
+ if (alignment == null || alignment == Alignment.BASELINE) {
+ int baseline = spring.getBaseline();
+ if (baseline >= 0) {
+ int springMax = spring.getMaximumSize(VERTICAL);
+ int springPref = spring.getPreferredSize(VERTICAL);
+ int height = springPref;
+ int y;
+ switch(spring.getBaselineResizeBehavior()) {
+ case CONSTANT_ASCENT:
+ y = origin + ascent - baseline;
+ height = Math.min(descent, springMax -
+ baseline) + baseline;
+ break;
+ case CONSTANT_DESCENT:
+ height = Math.min(ascent, springMax -
+ springPref + baseline) +
+ (springPref - baseline);
+ y = origin + ascent +
+ (springPref - baseline) - height;
+ break;
+ default: // CENTER_OFFSET & OTHER, not resizable
+ y = origin + ascent - baseline;
+ break;
+ }
+ spring.setSize(VERTICAL, y, height);
+ } else {
+ setChildSize(spring, VERTICAL, origin, size);
+ }
+ } else {
+ setChildSize(spring, VERTICAL, origin, size);
+ }
+ }
+ }
+
+ int getBaseline() {
+ if (springs.size() > 1) {
+ // Force the baseline to be calculated
+ getPreferredSize(VERTICAL);
+ return prefAscent;
+ } else if (springs.size() == 1) {
+ return springs.get(0).getBaseline();
+ }
+ return -1;
+ }
+
+ BaselineResizeBehavior getBaselineResizeBehavior() {
+ if (springs.size() == 1) {
+ return springs.get(0).getBaselineResizeBehavior();
+ }
+ if (baselineAnchoredToTop) {
+ return BaselineResizeBehavior.CONSTANT_ASCENT;
+ }
+ return BaselineResizeBehavior.CONSTANT_DESCENT;
+ }
+
+ // If the axis is VERTICAL, throws an IllegalStateException
+ private void checkAxis(int axis) {
+ if (axis == HORIZONTAL) {
+ throw new IllegalStateException(
+ "Baseline must be used along vertical axis");
+ }
+ }
+ }
+
+
+ private final class ComponentSpring extends Spring {
+ private Component component;
+ private int origin;
+
+ // min/pref/max are either a value >= 0 or one of
+ // DEFAULT_SIZE or PREFERRED_SIZE
+ private final int min;
+ private final int pref;
+ private final int max;
+
+ // Baseline for the component, computed as necessary.
+ private int baseline = -1;
+
+ // Whether or not the size has been requested yet.
+ private boolean installed;
+
+ private ComponentSpring(Component component, int min, int pref,
+ int max) {
+ this.component = component;
+ if (component == null) {
+ throw new IllegalArgumentException(
+ "Component must be non-null");
+ }
+
+ checkSize(min, pref, max, true);
+
+ this.min = min;
+ this.max = max;
+ this.pref = pref;
+
+ // getComponentInfo makes sure component is a child of the
+ // Container GroupLayout is the LayoutManager for.
+ getComponentInfo(component);
+ }
+
+ int calculateMinimumSize(int axis) {
+ if (isLinked(axis)) {
+ return getLinkSize(axis, MIN_SIZE);
+ }
+ return calculateNonlinkedMinimumSize(axis);
+ }
+
+ int calculatePreferredSize(int axis) {
+ if (isLinked(axis)) {
+ return getLinkSize(axis, PREF_SIZE);
+ }
+ int min = getMinimumSize(axis);
+ int pref = calculateNonlinkedPreferredSize(axis);
+ int max = getMaximumSize(axis);
+ return Math.min(max, Math.max(min, pref));
+ }
+
+ int calculateMaximumSize(int axis) {
+ if (isLinked(axis)) {
+ return getLinkSize(axis, MAX_SIZE);
+ }
+ return Math.max(getMinimumSize(axis),
+ calculateNonlinkedMaximumSize(axis));
+ }
+
+ boolean isVisible() {
+ return getComponentInfo(getComponent()).isVisible();
+ }
+
+ int calculateNonlinkedMinimumSize(int axis) {
+ if (!isVisible()) {
+ return 0;
+ }
+ if (min >= 0) {
+ return min;
+ }
+ if (min == PREFERRED_SIZE) {
+ return calculateNonlinkedPreferredSize(axis);
+ }
+ assert (min == DEFAULT_SIZE);
+ return getSizeAlongAxis(axis, component.getMinimumSize());
+ }
+
+ int calculateNonlinkedPreferredSize(int axis) {
+ if (!isVisible()) {
+ return 0;
+ }
+ if (pref >= 0) {
+ return pref;
+ }
+ assert (pref == DEFAULT_SIZE || pref == PREFERRED_SIZE);
+ return getSizeAlongAxis(axis, component.getPreferredSize());
+ }
+
+ int calculateNonlinkedMaximumSize(int axis) {
+ if (!isVisible()) {
+ return 0;
+ }
+ if (max >= 0) {
+ return max;
+ }
+ if (max == PREFERRED_SIZE) {
+ return calculateNonlinkedPreferredSize(axis);
+ }
+ assert (max == DEFAULT_SIZE);
+ return getSizeAlongAxis(axis, component.getMaximumSize());
+ }
+
+ private int getSizeAlongAxis(int axis, Dimension size) {
+ return (axis == HORIZONTAL) ? size.width : size.height;
+ }
+
+ private int getLinkSize(int axis, int type) {
+ if (!isVisible()) {
+ return 0;
+ }
+ ComponentInfo ci = getComponentInfo(component);
+ return ci.getLinkSize(axis, type);
+ }
+
+ void setSize(int axis, int origin, int size) {
+ super.setSize(axis, origin, size);
+ this.origin = origin;
+ if (size == UNSET) {
+ baseline = -1;
+ }
+ }
+
+ int getOrigin() {
+ return origin;
+ }
+
+ void setComponent(Component component) {
+ this.component = component;
+ }
+
+ Component getComponent() {
+ return component;
+ }
+
+ int getBaseline() {
+ if (baseline == -1) {
+ Spring horizontalSpring = getComponentInfo(component).
+ horizontalSpring;
+ int width = horizontalSpring.getPreferredSize(HORIZONTAL);
+ int height = getPreferredSize(VERTICAL);
+ if (width > 0 && height > 0) {
+ baseline = component.getBaseline(width, height);
+ }
+ }
+ return baseline;
+ }
+
+ BaselineResizeBehavior getBaselineResizeBehavior() {
+ return getComponent().getBaselineResizeBehavior();
+ }
+
+ private boolean isLinked(int axis) {
+ return getComponentInfo(component).isLinked(axis);
+ }
+
+ void installIfNecessary(int axis) {
+ if (!installed) {
+ installed = true;
+ if (axis == HORIZONTAL) {
+ getComponentInfo(component).horizontalSpring = this;
+ } else {
+ getComponentInfo(component).verticalSpring = this;
+ }
+ }
+ }
+
+ @Override
+ boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
+ return !isVisible();
+ }
+ }
+
+
+ /**
+ * Spring representing the preferred distance between two components.
+ */
+ private class PreferredGapSpring extends Spring {
+ private final JComponent source;
+ private final JComponent target;
+ private final ComponentPlacement type;
+ private final int pref;
+ private final int max;
+
+ PreferredGapSpring(JComponent source, JComponent target,
+ ComponentPlacement type, int pref, int max) {
+ this.source = source;
+ this.target = target;
+ this.type = type;
+ this.pref = pref;
+ this.max = max;
+ }
+
+ int calculateMinimumSize(int axis) {
+ return getPadding(axis);
+ }
+
+ int calculatePreferredSize(int axis) {
+ if (pref == DEFAULT_SIZE || pref == PREFERRED_SIZE) {
+ return getMinimumSize(axis);
+ }
+ int min = getMinimumSize(axis);
+ int max = getMaximumSize(axis);
+ return Math.min(max, Math.max(min, pref));
+ }
+
+ int calculateMaximumSize(int axis) {
+ if (max == PREFERRED_SIZE || max == DEFAULT_SIZE) {
+ return getPadding(axis);
+ }
+ return Math.max(getMinimumSize(axis), max);
+ }
+
+ private int getPadding(int axis) {
+ int position;
+ if (axis == HORIZONTAL) {
+ position = SwingConstants.EAST;
+ } else {
+ position = SwingConstants.SOUTH;
+ }
+ return getLayoutStyle0().getPreferredGap(source,
+ target, type, position, host);
+ }
+
+ @Override
+ boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Spring represented a certain amount of space.
+ */
+ private class GapSpring extends Spring {
+ private final int min;
+ private final int pref;
+ private final int max;
+
+ GapSpring(int min, int pref, int max) {
+ checkSize(min, pref, max, false);
+ this.min = min;
+ this.pref = pref;
+ this.max = max;
+ }
+
+ int calculateMinimumSize(int axis) {
+ if (min == PREFERRED_SIZE) {
+ return getPreferredSize(axis);
+ }
+ return min;
+ }
+
+ int calculatePreferredSize(int axis) {
+ return pref;
+ }
+
+ int calculateMaximumSize(int axis) {
+ if (max == PREFERRED_SIZE) {
+ return getPreferredSize(axis);
+ }
+ return max;
+ }
+
+ @Override
+ boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Spring reprensenting the distance between any number of sources and
+ * targets. The targets and sources are computed during layout. An
+ * instance of this can either be dynamically created when
+ * autocreatePadding is true, or explicitly created by the developer.
+ */
+ private class AutoPreferredGapSpring extends Spring {
+ List<ComponentSpring> sources;
+ ComponentSpring source;
+ private List<AutoPreferredGapMatch> matches;
+ int size;
+ int lastSize;
+ private final int pref;
+ private final int max;
+ // Type of gap
+ private ComponentPlacement type;
+ private boolean userCreated;
+
+ private AutoPreferredGapSpring() {
+ this.pref = PREFERRED_SIZE;
+ this.max = PREFERRED_SIZE;
+ this.type = ComponentPlacement.RELATED;
+ }
+
+ AutoPreferredGapSpring(int pref, int max) {
+ this.pref = pref;
+ this.max = max;
+ }
+
+ AutoPreferredGapSpring(ComponentPlacement type, int pref, int max) {
+ this.type = type;
+ this.pref = pref;
+ this.max = max;
+ this.userCreated = true;
+ }
+
+ public void setSource(ComponentSpring source) {
+ this.source = source;
+ }
+
+ public void setSources(List<ComponentSpring> sources) {
+ this.sources = new ArrayList<ComponentSpring>(sources);
+ }
+
+ public void setUserCreated(boolean userCreated) {
+ this.userCreated = userCreated;
+ }
+
+ public boolean getUserCreated() {
+ return userCreated;
+ }
+
+ void unset() {
+ lastSize = getSize();
+ super.unset();
+ size = 0;
+ }
+
+ public void reset() {
+ size = 0;
+ sources = null;
+ source = null;
+ matches = null;
+ }
+
+ public void calculatePadding(int axis) {
+ size = UNSET;
+ int maxPadding = UNSET;
+ if (matches != null) {
+ LayoutStyle p = getLayoutStyle0();
+ int position;
+ if (axis == HORIZONTAL) {
+ if (isLeftToRight()) {
+ position = SwingConstants.EAST;
+ } else {
+ position = SwingConstants.WEST;
+ }
+ } else {
+ position = SwingConstants.SOUTH;
+ }
+ for (int i = matches.size() - 1; i >= 0; i--) {
+ AutoPreferredGapMatch match = matches.get(i);
+ maxPadding = Math.max(maxPadding,
+ calculatePadding(p, position, match.source,
+ match.target));
+ }
+ }
+ if (size == UNSET) {
+ size = 0;
+ }
+ if (maxPadding == UNSET) {
+ maxPadding = 0;
+ }
+ if (lastSize != UNSET) {
+ size += Math.min(maxPadding, lastSize);
+ }
+ }
+
+ private int calculatePadding(LayoutStyle p, int position,
+ ComponentSpring source,
+ ComponentSpring target) {
+ int delta = target.getOrigin() - (source.getOrigin() +
+ source.getSize());
+ if (delta >= 0) {
+ int padding;
+ if ((source.getComponent() instanceof JComponent) &&
+ (target.getComponent() instanceof JComponent)) {
+ padding = p.getPreferredGap(
+ (JComponent)source.getComponent(),
+ (JComponent)target.getComponent(), type, position,
+ host);
+ } else {
+ padding = 10;
+ }
+ if (padding > delta) {
+ size = Math.max(size, padding - delta);
+ }
+ return padding;
+ }
+ return 0;
+ }
+
+ public void addTarget(ComponentSpring spring, int axis) {
+ int oAxis = (axis == HORIZONTAL) ? VERTICAL : HORIZONTAL;
+ if (source != null) {
+ if (areParallelSiblings(source.getComponent(),
+ spring.getComponent(), oAxis)) {
+ addValidTarget(source, spring);
+ }
+ } else {
+ Component component = spring.getComponent();
+ for (int counter = sources.size() - 1; counter >= 0;
+ counter--){
+ ComponentSpring source = sources.get(counter);
+ if (areParallelSiblings(source.getComponent(),
+ component, oAxis)) {
+ addValidTarget(source, spring);
+ }
+ }
+ }
+ }
+
+ private void addValidTarget(ComponentSpring source,
+ ComponentSpring target) {
+ if (matches == null) {
+ matches = new ArrayList<AutoPreferredGapMatch>(1);
+ }
+ matches.add(new AutoPreferredGapMatch(source, target));
+ }
+
+ int calculateMinimumSize(int axis) {
+ return size;
+ }
+
+ int calculatePreferredSize(int axis) {
+ if (pref == PREFERRED_SIZE || pref == DEFAULT_SIZE) {
+ return size;
+ }
+ return Math.max(size, pref);
+ }
+
+ int calculateMaximumSize(int axis) {
+ if (max >= 0) {
+ return Math.max(getPreferredSize(axis), max);
+ }
+ return size;
+ }
+
+ String getMatchDescription() {
+ return (matches == null) ? "" : matches.toString();
+ }
+
+ public String toString() {
+ return super.toString() + getMatchDescription();
+ }
+
+ @Override
+ boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
+ return treatAutopaddingAsZeroSized;
+ }
+ }
+
+
+ /**
+ * Represents two springs that should have autopadding inserted between
+ * them.
+ */
+ private final static class AutoPreferredGapMatch {
+ public final ComponentSpring source;
+ public final ComponentSpring target;
+
+ AutoPreferredGapMatch(ComponentSpring source, ComponentSpring target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ private String toString(ComponentSpring spring) {
+ return spring.getComponent().getName();
+ }
+
+ public String toString() {
+ return "[" + toString(source) + "-" + toString(target) + "]";
+ }
+ }
+
+
+ /**
+ * An extension of AutopaddingSpring used for container level padding.
+ */
+ private class ContainerAutoPreferredGapSpring extends
+ AutoPreferredGapSpring {
+ private List<ComponentSpring> targets;
+
+ ContainerAutoPreferredGapSpring() {
+ super();
+ setUserCreated(true);
+ }
+
+ ContainerAutoPreferredGapSpring(int pref, int max) {
+ super(pref, max);
+ setUserCreated(true);
+ }
+
+ public void addTarget(ComponentSpring spring, int axis) {
+ if (targets == null) {
+ targets = new ArrayList<ComponentSpring>(1);
+ }
+ targets.add(spring);
+ }
+
+ public void calculatePadding(int axis) {
+ LayoutStyle p = getLayoutStyle0();
+ int maxPadding = 0;
+ int position;
+ size = 0;
+ if (targets != null) {
+ // Leading
+ if (axis == HORIZONTAL) {
+ if (isLeftToRight()) {
+ position = SwingConstants.WEST;
+ } else {
+ position = SwingConstants.EAST;
+ }
+ } else {
+ position = SwingConstants.SOUTH;
+ }
+ for (int i = targets.size() - 1; i >= 0; i--) {
+ ComponentSpring targetSpring = targets.get(i);
+ int padding = 10;
+ if (targetSpring.getComponent() instanceof JComponent) {
+ padding = p.getContainerGap(
+ (JComponent)targetSpring.getComponent(),
+ position, host);
+ maxPadding = Math.max(padding, maxPadding);
+ padding -= targetSpring.getOrigin();
+ } else {
+ maxPadding = Math.max(padding, maxPadding);
+ }
+ size = Math.max(size, padding);
+ }
+ } else {
+ // Trailing
+ if (axis == HORIZONTAL) {
+ if (isLeftToRight()) {
+ position = SwingConstants.EAST;
+ } else {
+ position = SwingConstants.WEST;
+ }
+ } else {
+ position = SwingConstants.SOUTH;
+ }
+ if (sources != null) {
+ for (int i = sources.size() - 1; i >= 0; i--) {
+ ComponentSpring sourceSpring = sources.get(i);
+ maxPadding = Math.max(maxPadding,
+ updateSize(p, sourceSpring, position));
+ }
+ } else if (source != null) {
+ maxPadding = updateSize(p, source, position);
+ }
+ }
+ if (lastSize != UNSET) {
+ size += Math.min(maxPadding, lastSize);
+ }
+ }
+
+ private int updateSize(LayoutStyle p, ComponentSpring sourceSpring,
+ int position) {
+ int padding = 10;
+ if (sourceSpring.getComponent() instanceof JComponent) {
+ padding = p.getContainerGap(
+ (JComponent)sourceSpring.getComponent(), position,
+ host);
+ }
+ int delta = Math.max(0, getParent().getSize() -
+ sourceSpring.getSize() - sourceSpring.getOrigin());
+ size = Math.max(size, padding - delta);
+ return padding;
+ }
+
+ String getMatchDescription() {
+ if (targets != null) {
+ return "leading: " + targets.toString();
+ }
+ if (sources != null) {
+ return "trailing: " + sources.toString();
+ }
+ return "--";
+ }
+ }
+
+
+ // LinkInfo contains the set of ComponentInfosthat are linked along a
+ // particular axis.
+ private static class LinkInfo {
+ private final int axis;
+ private final List<ComponentInfo> linked;
+ private int size;
+
+ LinkInfo(int axis) {
+ linked = new ArrayList<ComponentInfo>();
+ size = UNSET;
+ this.axis = axis;
+ }
+
+ public void add(ComponentInfo child) {
+ LinkInfo childMaster = child.getLinkInfo(axis, false);
+ if (childMaster == null) {
+ linked.add(child);
+ child.setLinkInfo(axis, this);
+ } else if (childMaster != this) {
+ linked.addAll(childMaster.linked);
+ for (ComponentInfo childInfo : childMaster.linked) {
+ childInfo.setLinkInfo(axis, this);
+ }
+ }
+ clearCachedSize();
+ }
+
+ public void remove(ComponentInfo info) {
+ linked.remove(info);
+ info.setLinkInfo(axis, null);
+ if (linked.size() == 1) {
+ linked.get(0).setLinkInfo(axis, null);
+ }
+ clearCachedSize();
+ }
+
+ public void clearCachedSize() {
+ size = UNSET;
+ }
+
+ public int getSize(int axis) {
+ if (size == UNSET) {
+ size = calculateLinkedSize(axis);
+ }
+ return size;
+ }
+
+ private int calculateLinkedSize(int axis) {
+ int size = 0;
+ for (ComponentInfo info : linked) {
+ ComponentSpring spring;
+ if (axis == HORIZONTAL) {
+ spring = info.horizontalSpring;
+ } else {
+ assert (axis == VERTICAL);
+ spring = info.verticalSpring;
+ }
+ size = Math.max(size,
+ spring.calculateNonlinkedPreferredSize(axis));
+ }
+ return size;
+ }
+ }
+
+ /**
+ * Tracks the horizontal/vertical Springs for a Component.
+ * This class is also used to handle Springs that have their sizes
+ * linked.
+ */
+ private class ComponentInfo {
+ // Component being layed out
+ private Component component;
+
+ ComponentSpring horizontalSpring;
+ ComponentSpring verticalSpring;
+
+ // If the component's size is linked to other components, the
+ // horizontalMaster and/or verticalMaster reference the group of
+ // linked components.
+ private LinkInfo horizontalMaster;
+ private LinkInfo verticalMaster;
+
+ private boolean visible;
+ private Boolean honorsVisibility;
+
+ ComponentInfo(Component component) {
+ this.component = component;
+ updateVisibility();
+ }
+
+ public void dispose() {
+ // Remove horizontal/vertical springs
+ removeSpring(horizontalSpring);
+ horizontalSpring = null;
+ removeSpring(verticalSpring);
+ verticalSpring = null;
+ // Clean up links
+ if (horizontalMaster != null) {
+ horizontalMaster.remove(this);
+ }
+ if (verticalMaster != null) {
+ verticalMaster.remove(this);
+ }
+ }
+
+ void setHonorsVisibility(Boolean honorsVisibility) {
+ this.honorsVisibility = honorsVisibility;
+ }
+
+ private void removeSpring(Spring spring) {
+ if (spring != null) {
+ ((Group)spring.getParent()).springs.remove(spring);
+ }
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ /**
+ * Updates the cached visibility.
+ *
+ * @return true if the visibility changed
+ */
+ boolean updateVisibility() {
+ boolean honorsVisibility;
+ if (this.honorsVisibility == null) {
+ honorsVisibility = GroupLayout.this.getHonorsVisibility();
+ } else {
+ honorsVisibility = this.honorsVisibility;
+ }
+ boolean newVisible = (honorsVisibility) ?
+ component.isVisible() : true;
+ if (visible != newVisible) {
+ visible = newVisible;
+ return true;
+ }
+ return false;
+ }
+
+ public void setBounds(Insets insets, int parentWidth, boolean ltr) {
+ int x = horizontalSpring.getOrigin();
+ int w = horizontalSpring.getSize();
+ int y = verticalSpring.getOrigin();
+ int h = verticalSpring.getSize();
+
+ if (!ltr) {
+ x = parentWidth - x - w;
+ }
+ component.setBounds(x + insets.left, y + insets.top, w, h);
+ }
+
+ public void setComponent(Component component) {
+ this.component = component;
+ if (horizontalSpring != null) {
+ horizontalSpring.setComponent(component);
+ }
+ if (verticalSpring != null) {
+ verticalSpring.setComponent(component);
+ }
+ }
+
+ public Component getComponent() {
+ return component;
+ }
+
+ /**
+ * Returns true if this component has its size linked to
+ * other components.
+ */
+ public boolean isLinked(int axis) {
+ if (axis == HORIZONTAL) {
+ return horizontalMaster != null;
+ }
+ assert (axis == VERTICAL);
+ return (verticalMaster != null);
+ }
+
+ private void setLinkInfo(int axis, LinkInfo linkInfo) {
+ if (axis == HORIZONTAL) {
+ horizontalMaster = linkInfo;
+ } else {
+ assert (axis == VERTICAL);
+ verticalMaster = linkInfo;
+ }
+ }
+
+ public LinkInfo getLinkInfo(int axis) {
+ return getLinkInfo(axis, true);
+ }
+
+ private LinkInfo getLinkInfo(int axis, boolean create) {
+ if (axis == HORIZONTAL) {
+ if (horizontalMaster == null && create) {
+ // horizontalMaster field is directly set by adding
+ // us to the LinkInfo.
+ new LinkInfo(HORIZONTAL).add(this);
+ }
+ return horizontalMaster;
+ } else {
+ assert (axis == VERTICAL);
+ if (verticalMaster == null && create) {
+ // verticalMaster field is directly set by adding
+ // us to the LinkInfo.
+ new LinkInfo(VERTICAL).add(this);
+ }
+ return verticalMaster;
+ }
+ }
+
+ public void clearCachedSize() {
+ if (horizontalMaster != null) {
+ horizontalMaster.clearCachedSize();
+ }
+ if (verticalMaster != null) {
+ verticalMaster.clearCachedSize();
+ }
+ }
+
+ int getLinkSize(int axis, int type) {
+ if (axis == HORIZONTAL) {
+ return horizontalMaster.getSize(axis);
+ } else {
+ assert (axis == VERTICAL);
+ return verticalMaster.getSize(axis);
+ }
+ }
+
+ }
+}