/* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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. *
* 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}. *
* 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. *
* {@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. *
* 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. *
* *
* 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. *
* 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. *
* 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. *
* 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. *
*
* *
* 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. *
* The following diagram shows a sequential group along both the horizontal * and vertical axis. *
* *
* {@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. *
* The following builds a panel consisting of two labels in * one column, followed by two textfields in the next column: *
* 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); **
* When run the following is produced. *
* *
* This layout consists of the following. *
* 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. *
* The specified value is used for components that do not have an * explicit visibility specified. *
* 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. *
* 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). *
* 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}. *
* 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 == null) { throw new IllegalArgumentException("alignment must be non null"); } 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. *
* This can be used multiple times to force any number of * components to share the same size. *
* 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. *
* This can be used multiple times to force any number of * components to share the same size. *
* 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
* 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.
*
* 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}.
*
* Similarly any methods that take a {@code Component} throw a
* {@code IllegalArgumentException} if passed {@code null} and any methods
* that take a {@code Group} throw an {@code NullPointerException} if
* passed {@code null}.
*
* @see #createSequentialGroup
* @see #createParallelGroup
* @since 1.6
*/
public abstract class Group extends Spring {
// private int origin;
// private int size;
List
* 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}.
*
* 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.
*
* 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
* 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.
*
* 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.
*
* 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.
*
* 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.
*
* 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.
*
*
* 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.
*
* 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
* group.addComponent(myComponent, 100, 200, 300);
*
* 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.
*
* group.addComponent(myComponent, GroupLayout.PREFERRED_SIZE,
* GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE);
*
* Baseline
* 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}.
* Non-baseline {@code ParallelGroup}
* {@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.
*