com.codename1.ui.layouts.GroupLayout Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 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 com.codename1.ui.layouts;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Form;
import com.codename1.ui.geom.Dimension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
/**
* GroupLayout is a LayoutManager that hierarchically groups components to
* achieve common, and not so common, layouts. Grouping is done by instances
* of the Group class. GroupLayout supports two types of groups:
*
* Sequential: A sequential group positions its child
* elements sequentially, one after another.
* Parallel: A parallel group positions its child
* elements in the same space on top of each other. Parallel groups
* can also align the child elements along their baseline.
*
* Each Group can contain any number of child groups, Components or gaps.
* GroupLayout treats each axis independently. That is, there is a group
* representing the horizontal axis, and a separate group representing the
* vertical axis. The horizontal group is responsible for setting the x
* and width of its contents, where as the vertical group is responsible for
* setting the y and height of its contents.
*
* The following code builds a simple layout consisting of two labels in
* one column, followed by two textfields in the next column:
*
* Container panel = ...;
* GroupLayout layout = new GroupLayout(panel);
* panel.setLayout(layout);
* layout.setAutocreateGaps(true);
* layout.setAutocreateContainerGaps(true);
* GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
* hGroup.add(layout.createParallelGroup().add(label1).add(label2)).
* add(layout.createParallelGroup().add(tf1).add(tf2));
* layout.setHorizontalGroup(hGroup);
* GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
* vGroup.add(layout.createParallelGroup(GroupLayout.BASELINE).add(label1).add(tf1)).
* add(layout.createParallelGroup(GroupLayout.BASELINE).add(label2).add(tf2));
* layout.setVerticalGroup(vGroup);
*
*
* This layout consists of the following:
*
- The horizontal axis consists of a sequential group containing two
* parallel groups. The first parallel group consists of the labels,
* with the second parallel group consisting of the text fields.
*
- The vertical axis similarly consists of a sequential group
* containing two parallel groups. The parallel groups align their
* contents along the baseline. The first parallel group consists
* of the first label and text field, and the second group consists
* of the second label and text field.
*
* There are a couple of things to notice in this code:
*
* - You need not explicitly add the components to the container, this
* is indirectly done by using one of the
add
methods.
* - The various
add
methods of Groups
return
* themselves. This allows for easy chaining of invocations. For
* example, group.add(label1).add(label2);
is equivalent to
* group.add(label1);group.add(label2);
.
* - There are no public constructors for the Groups, instead
* use the create methods of
GroupLayout
.
*
* GroupLayout offer the ability to automatically insert the appropriate gap
* between components. This can be turned on using the
* setAutocreateGaps()
method. Similarly you can use
* the setAutocreateContainerGaps()
method to insert gaps
* between the components and the container.
*
* @version $Revision: 1.25 $
* @author Tomas Pavek
* @author Jan Stola
* @author Scott Violet
* @author Shai Almog
*/
public class GroupLayout extends Layout {
/**
* Compass-direction North (up).
*/
public static final int NORTH = 1;
/**
* Compass-direction east (right).
*/
public static final int EAST = 3;
/**
* Compass-direction south (down).
*/
public static final int SOUTH = 5;
/**
* Compass-direction west (left).
*/
public static final int WEST = 7;
// 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;
/**
* Possible argument when linking sizes of components. Specifies the
* the two component should share the same size along the horizontal
* axis.
*
* @see #linkSize(Component[], int)
*/
public static final int HORIZONTAL = 1;
/**
* Possible argument when linking sizes of components. Specifies the
* the two component should share the same size along the vertical
* axis.
*
* @see #linkSize(Component[],int)
*/
public static final int VERTICAL = 2;
private static final int NO_ALIGNMENT = 0;
/**
* Possible alignment type. 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.
*
* @see #createParallelGroup(int)
*/
public static final int LEADING = 1;
/**
* Possible alignment type. Indicates the elements should be
* aligned to the end. For the horizontal axis with a left to
* right orientation this means aligned to the right.
*
* @see #createParallelGroup(int)
*/
public static final int TRAILING = 2;
/**
* Possible alignment type. Indicates the elements should centered in
* the spaced provided.
*
* @see #createParallelGroup(int)
*/
public static final int CENTER = 4;
/**
* Possible alignment type. Indicates the elements should aligned along
* their baseline.
*
* @see #createParallelGroup(int)
*/
public static final int BASELINE = 3;
/**
* Possible value for the add methods that takes a Component.
* Indicates the size from the component should be used.
*/
public static final int DEFAULT_SIZE = -1;
/**
* Possible value for the add methods that takes a Component.
* Indicates the preferred size should be used.
*/
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 containers
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 HashMap componentInfos;
// Container we're doing layout for.
private Container host;
// Used by areParallelSiblings, cached to avoid excessive garbage.
private ArrayList 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;
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 GroupLayout for the specified Container.
*
* @param host the Container to layout
* @throws IllegalArgumentException if host is null
*/
public GroupLayout(Container host) {
if (host == null) {
throw new IllegalArgumentException("Container must be non-null");
}
if(host instanceof Form) {
host = ((Form)host).getContentPane();
}
honorsVisibility = true;
this.host = host;
setHorizontalGroup(createParallelGroup(LEADING, true));
setVerticalGroup(createParallelGroup(LEADING, true));
componentInfos = new HashMap();
tmpParallelSet = new ArrayList();
}
/**
* Sets whether component visibility is considered when sizing and
* positioning components. A value of true
indicates that
* non-visible components should not be treated as part of the
* layout. A value of false
indicates that components should be
* positioned and sized regardless of visibility.
*
* A value of 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 true
.
*
* @param honorsVisibility whether component visibility 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 visibility is considered when sizing and
* positioning components.
*
* @return whether component visibility is considered when sizing and
* positioning components
*/
public boolean getHonorsVisibility() {
return honorsVisibility;
}
/**
* Sets whether the component's visibility is considered for
* sizing and positioning. A value of Boolean.TRUE
* indicates that if component
is not visible it should
* not be treated as part of the layout. A value of false
* indicates that component
is positioned and sized
* regardless of it's visibility. A value of null
* indicates the value specified by the single argument method
* setHonorsVisibility
should be used.
*
* If component
is not a child of the Container
this
* GroupLayout
is managing, it will be added to the
* Container
.
*
* @param component the component
* @param honorsVisibility whether component
's visibility should be
* considered for sizing and positioning
* @throws IllegalArgumentException if component
is null
* @see #setHonorsVisibility(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();
}
/**
* Returns a textual description of this GroupLayout. The return value
* is intended for debugging purposes only.
*
* @return textual description of this GroupLayout
**/
public String toString() {
if (springsChanged) {
registerComponents(horizontalGroup, HORIZONTAL);
registerComponents(verticalGroup, VERTICAL);
}
StringBuffer buffer = new StringBuffer();
buffer.append("HORIZONTAL\n");
dump(buffer, horizontalGroup, " ", HORIZONTAL);
buffer.append("\nVERTICAL\n");
dump(buffer, verticalGroup, " ", VERTICAL);
return buffer.toString();
}
private void dump(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().toString();
if (name != null) {
origin = "name=" + name + ", ";
}
}
if (spring instanceof AutopaddingSpring) {
AutopaddingSpring paddingSpring = (AutopaddingSpring)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) {
ArrayList springs = ((Group)spring).springs;
indent += " ";
for (int counter = 0; counter < springs.size(); counter++) {
dump(buffer, (Spring)springs.get(counter), indent, axis);
}
}
}
/**
* Sets whether or not a gap between components
* should automatically be created. For example, if this is true
* and you add two components to a SequentialGroup
a
* gap between the two will automatically be created. The default
* is false.
*
* @param autocreatePadding whether or not to automatically created a gap
* between components and the container
*/
public void setAutocreateGaps(boolean autocreatePadding) {
if (this.autocreatePadding != autocreatePadding) {
this.autocreatePadding = autocreatePadding;
invalidateHost();
}
}
/**
* Returns true if gaps between components are automatically be created.
*
* @return true if gaps between components should automatically be created
*/
public boolean getAutocreateGaps() {
return autocreatePadding;
}
/**
* Sets whether or not gaps between the container and the first/last
* components should automatically be created. The default
* is false.
*
* @param autocreatePadding whether or not to automatically create
* gaps between the container and first/last components.
*/
public void setAutocreateContainerGaps(boolean autocreatePadding) {
if (autocreatePadding != autocreateContainerPadding) {
autocreateContainerPadding = autocreatePadding;
horizontalGroup = createTopLevelGroup(getHorizontalGroup());
verticalGroup = createTopLevelGroup(getVerticalGroup());
invalidateHost();
}
}
/**
* Returns whether or not gaps between the container and the
* first/last components should automatically be created. The default
* is false.
*
* @return whether or not the gaps between the container and the
* first/last components should automatically be created
*/
public boolean getAutocreateContainerGaps() {
return autocreateContainerPadding;
}
/**
* Sets the Group
that is responsible for
* layout along the horizontal axis.
*
* @param group Group
responsible for layout along
* the horizontal axis
* @throws IllegalArgumentException if group is null
*/
public void setHorizontalGroup(Group group) {
if (group == null) {
throw new IllegalArgumentException("Group must be non-null");
}
horizontalGroup = createTopLevelGroup(group);
invalidateHost();
}
/**
* Returns the Group
that is responsible for
* layout along the horizontal axis.
*
* @return ParallelGroup
responsible for layout along
* the horizontal axis.
*/
public Group getHorizontalGroup() {
int index = 0;
if (horizontalGroup.springs.size() > 1) {
index = 1;
}
return (Group)horizontalGroup.springs.get(index);
}
/**
* Sets the Group
that is responsible for
* layout along the vertical axis.
*
* @param group Group
responsible for layout along
* the vertical axis.
* @throws IllegalArgumentException if group is null.
*/
public void setVerticalGroup(Group group) {
if (group == null) {
throw new IllegalArgumentException("Group must be non-null");
}
verticalGroup = createTopLevelGroup(group);
invalidateHost();
}
/**
* Returns the ParallelGroup
that is responsible for
* layout along the vertical axis.
*
* @return ParallelGroup
responsible for layout along
* the vertical axis.
*/
public 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 generate the necessary springs are
* added.
*/
private Group createTopLevelGroup(Group specifiedGroup) {
SequentialGroup group = createSequentialGroup();
if (getAutocreateContainerGaps()) {
group.addSpring(new ContainerAutopaddingSpring());
group.add(specifiedGroup);
group.addSpring(new ContainerAutopaddingSpring());
} else {
group.add(specifiedGroup);
}
return group;
}
/**
* Creates and returns a SequentialGroup
.
*
* @return a new SequentialGroup
*/
public SequentialGroup createSequentialGroup() {
return new SequentialGroup();
}
/**
* Creates and returns a ParallelGroup
with a
* LEADING
alignment. This is a cover method for the more
* general createParallelGroup(int)
method.
*
* @return a new ParallelGroup
* @see #createParallelGroup(int)
*/
public ParallelGroup createParallelGroup() {
return createParallelGroup(LEADING);
}
/**
* Creates and returns an ParallelGroup
. The alignment
* specifies how children elements should be positioned when the
* the parallel group is given more space than necessary. For example,
* if a ParallelGroup with an alignment of TRAILING is given 100 pixels
* and a child only needs 50 pixels, the child will be positioned at the
* position 50.
*
* @param alignment alignment for the elements of the Group, one
* of LEADING
, TRAILING
,
* CENTER
or BASELINE
.
* @throws IllegalArgumentException if alignment is not one of
* LEADING
, TRAILING
,
* CENTER
or BASELINE
* @return a new ParallelGroup
*/
public ParallelGroup createParallelGroup(int alignment) {
return createParallelGroup(alignment, true);
}
/**
* Creates and returns an ParallelGroup
. The alignment
* specifies how children elements should be positioned when the
* the parallel group is given more space than necessary. For example,
* if a ParallelGroup with an alignment of TRAILING is given 100 pixels
* and a child only needs 50 pixels, the child will be positioned at the
* position 50.
*
* @param alignment alignment for the elements of the Group, one
* of LEADING
, TRAILING
,
* CENTER
or BASELINE
.
* @param resizable whether or not the group is resizable. If the group
* is not resizable the min/max size will be the same as the
* preferred.
* @throws IllegalArgumentException if alignment is not one of
* LEADING
, TRAILING
,
* CENTER
or BASELINE
* @return a new ParallelGroup
*/
public ParallelGroup createParallelGroup(int alignment, boolean resizable) {
if (alignment == BASELINE) {
return new BaselineGroup(resizable);
}
return new ParallelGroup(alignment, resizable);
}
/**
* Creates and returns a 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
* @return parallel group
* @see #createBaselineGroup
* @see ParallelGroup
*/
public ParallelGroup createBaselineGroup(boolean resizable,
boolean anchorBaselineToTop) {
return new BaselineGroup(resizable, anchorBaselineToTop);
}
/**
* Forces the set of components to have the same size.
* 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 Components to force to have same size.
* @throws IllegalArgumentException if components
is
* null, or contains null.
*/
public void linkSize(Component[] components) {
linkSize(components, HORIZONTAL | VERTICAL);
}
/**
* Forces the set of components to have the same size.
* 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 Components to force to have same size.
* @param axis Axis to bind size, one of HORIZONTAL, VERTICAL or
* HORIZONTAL | VERTICAL
* @throws IllegalArgumentException if components
is
* null, or contains null.
* @throws IllegalArgumentException if axis
does not
* contain HORIZONTAL
or VERTICAL
*/
public void linkSize(Component[] components, int axis) {
if (components == null) {
throw new IllegalArgumentException("Components must be non-null");
}
boolean horizontal = ((axis & HORIZONTAL) == HORIZONTAL);
boolean vertical = ((axis & VERTICAL) == VERTICAL);
if (!vertical && !horizontal) {
throw new IllegalArgumentException(
"Axis must contain HORIZONTAL or VERTICAL");
}
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);
}
if (horizontal) {
linkSize0(components, HORIZONTAL);
}
if (vertical) {
linkSize0(components, VERTICAL);
}
invalidateHost();
}
private void linkSize0(Component[] components, int axis) {
LinkInfo master = getComponentInfo(
components[components.length - 1]).getLinkInfo(axis);
for (int counter = components.length - 2; counter >= 0; counter--) {
master.add(getComponentInfo(components[counter]));
}
}
/**
* Removes an existing component replacing it with the specified component.
*
* @param existingComponent the Component that should be removed and
* replaced with newComponent
* @param newComponent the Component to put in existingComponents place
* @throws IllegalArgumentException is either of the Components are null or
* if 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 = (ComponentInfo)componentInfos.
remove(existingComponent);
if (info == null) {
throw new IllegalArgumentException("Component must already exist");
}
host.removeComponent(existingComponent);
if (newComponent.getParent() != host) {
host.addComponent(newComponent);
}
info.setComponent(newComponent);
componentInfos.put(newComponent, info);
invalidateHost();
}
/**
* Sets the LayoutStyle this GroupLayout is to use. A value of null can
* be used to indicate the shared instance of LayoutStyle should be used.
*
* @param layoutStyle the LayoutStyle to use
*/
public void setLayoutStyle(LayoutStyle layoutStyle) {
this.layoutStyle = layoutStyle;
invalidateHost();
}
/**
* Returns the LayoutStyle instance to use
*
* @return the LayoutStyle instance to use
*/
public LayoutStyle getLayoutStyle() {
return layoutStyle;
}
private LayoutStyle getLayoutStyle0() {
LayoutStyle layoutStyle = getLayoutStyle();
if (layoutStyle == null) {
layoutStyle = LayoutStyle.getSharedInstance();
}
return layoutStyle;
}
private void invalidateHost() {
host.invalidate();
host.repaint();
}
/**
* Notification that a Component
has been removed from
* the parent container. You should not invoke this method
* directly, instead invoke removeComponent
on the parent
* Container
.
*
* @param component the component to be removed
* @see Container#removeComponent
*/
public void removeLayoutComponent(Component component) {
ComponentInfo info = (ComponentInfo)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 size for
* @throws IllegalArgumentException if parent
is not
* the same 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 Container#getPreferredSize
*/
public Dimension getPreferredSize(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 size for
* @throws IllegalArgumentException if parent
is not
* the same 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);
int insetLeft = parent.getStyle().getMarginLeftNoRTL();
int insetTop = parent.getStyle().getMarginTop();
int insetRight = parent.getStyle().getMarginRightNoRTL();
int insetBottom = parent.getStyle().getMarginBottom();
int width = parent.getWidth() - insetLeft - insetRight;
int height = parent.getHeight() - insetTop - insetBottom;
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.
Iterator componentInfo = componentInfos.values().iterator();
while (componentInfo.hasNext()) {
ComponentInfo info = (ComponentInfo)componentInfo.next();
Component c = info.getComponent();
info.setBounds(insetLeft, insetTop, width, ltr);
}
}
/**
* Returns the maximum size for the specified container.
*
* @param parent the container to return size for
* @throws IllegalArgumentException if parent
is not
* the same 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 Container hosting this LayoutManager
* @throws IllegalArgumentException if parent
is not
* the same Container
that this was created with
* @return alignment
*/
/*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 Container hosting this LayoutManager
* @throws IllegalArgumentException if parent
is not
* the same Container
that this was created with
* @return alignment
*/
/*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 Container hosting this LayoutManager
* @throws IllegalArgumentException if parent
is not
* the same 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 (Iterator cis = componentInfos.values().iterator();
cis.hasNext();) {
ComponentInfo ci = (ComponentInfo)cis.next();
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;
}
group.setSize(axis, origin, size);
group.calculateAutopadding(axis);
}
private void checkComponents() {
Iterator infos = componentInfos.values().iterator();
while (infos.hasNext()) {
ComponentInfo info = (ComponentInfo)infos.next();
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) {
ArrayList springs = group.springs;
for (int counter = springs.size() - 1; counter >= 0; counter--) {
Spring 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) {
int insetLeft = host.getStyle().getMarginLeftNoRTL();
int insetTop = host.getStyle().getMarginTop();
int insetRight = host.getStyle().getMarginRightNoRTL();
int insetBottom = host.getStyle().getMarginBottom();
return new Dimension(width + insetLeft + insetRight,
height + insetTop + insetBottom);
}
private void checkParent(Container parent) {
if (parent != host) {
throw new IllegalArgumentException(
"GroupLayout can only be used with one Container at a time");
}
}
/**
* Returns the ComponentInfo
for the specified Component.
*/
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.addComponent(component);
}
}
return info;
}
/**
* Adjusts the autopadding springs for the horizontal and vertical
* groups. If insert
is 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(1),
new ArrayList(1), new ArrayList(1), new ArrayList(1), insert);
verticalGroup.insertAutopadding(VERTICAL, new ArrayList(1),
new ArrayList(1), new ArrayList(1), new ArrayList(1), insert);
}
/**
* Returns 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;
}
ArrayList 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() {
// Need bidi support...
return true;
//return host.getComponentOrientation().isLeftToRight();
}
/**
* Spring consists of a range: min, pref and max a value some where in
* the middle of that and a location. Subclasses must override
* methods to get the min/max/pref and will likely want to override
* the setSize
method. Spring automatically caches the
* min/max/pref. If the min/pref/max has internally changes, or needs
* to be updated you must invoked clear.
*/
abstract class Spring {
private int size;
private int min;
private int max;
private int pref;
private Spring parent;
private int 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(int alignment) {
this.alignment = alignment;
}
int 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;
}
/**
* Resets the cached min/max/pref.
*/
void unset() {
size = min = pref = max = UNSET;
}
/**
* 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();
}
}
/**
* Returns the current size.
*/
int getSize() {
return size;
}
int constrain(int value) {
return Math.min(value, Short.MAX_VALUE);
}
int getBaseline() {
return -1;
}
int getBaselineResizeBehavior() {
return Component.BRB_OTHER;
}
final boolean isResizable(int axis) {
int min = getMinimumSize(axis);
int pref = getPreferredSize(axis);
return (min != pref || pref != getMaximumSize(axis));
}
/**
* Returns 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.
*/
abstract boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized);
}
/**
* Simple copy constructor for ArrayList
*/
private static ArrayList create(ArrayList v) {
int size = v.size();
ArrayList vec = new ArrayList(size);
for(int iter = 0 ; iter < size ; iter++) {
vec.add(v.get(iter));
}
return vec;
}
/**
* Adds all ArrayList elements from source to dest
*/
private static void addAll(ArrayList dest, ArrayList source) {
int size = source.size();
for(int iter = 0 ; iter < size ; iter++) {
dest.add(source.get(iter));
}
}
/**
* Group provides for commonality between the two types of operations
* supported by GroupLayout
: laying out components one
* after another (SequentialGroup
) or layout on top
* of each other (ParallelGroup
). Use one of
* createSequentialGroup
or
* createParallelGroup
to create one.
*/
public abstract class Group extends Spring {
// private int origin;
// private int size;
ArrayList springs;
Group() {
springs = new ArrayList();
}
int indexOf(Spring spring) {
return springs.indexOf(spring);
}
/**
* Adds the Spring to the list of Spring
s and returns
* the receiver.
*/
Group addSpring(Spring spring) {
springs.add(spring);
spring.setParent(this);
if (!(spring instanceof AutopaddingSpring) ||
!((AutopaddingSpring)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 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);
}
/**
* 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 a + b
.
*/
abstract int operator(int a, int b);
/**
* Calculates the specified size. This is called from
* one of the getMinimumSize0
,
* getPreferredSize0
or
* getMaximumSize0
methods. This will invoke
* to 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;
}
Spring getSpring(int index) {
return (Spring)springs.get(index);
}
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;
}
// Padding
/**
* Adjusts the autopadding springs in this group and its children.
* If 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, ArrayList leadingPadding,
ArrayList trailingPadding, ArrayList leading, ArrayList trailing,
boolean insert);
/**
* Removes any AutopaddingSprings.
*/
void removeAutopadding() {
unset();
for (int counter = springs.size() - 1; counter >= 0; counter--) {
Spring spring = (Spring)springs.get(counter);
if (spring instanceof AutopaddingSpring) {
if (((AutopaddingSpring)spring).getUserCreated()) {
((AutopaddingSpring)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 = (Spring)springs.get(counter);
if (spring instanceof AutopaddingSpring) {
((AutopaddingSpring)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 = (Spring)springs.get(counter);
if (spring instanceof AutopaddingSpring) {
// Force size to be reset.
spring.unset();
((AutopaddingSpring)spring).calculatePadding(axis);
} else if (spring instanceof Group) {
((Group)spring).calculateAutopadding(axis);
}
}
// Clear cached pref/min/max.
unset();
}
boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
for (int i = springs.size() -1; i >= 0; i--) {
Spring spring = (Spring)springs.get(i);
if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) {
return false;
}
}
return true;
}
}
/**
* A Group
that lays out its elements sequentially, one
* after another. This class has no public constructor, use the
* createSequentialGroup
method to create one.
*
* @see #createSequentialGroup()
*/
public class SequentialGroup extends Group {
private Spring baselineSpring;
SequentialGroup() {
}
/**
* Adds the specified Group
to this
* SequentialGroup
*
* @param group the Group to add
* @return this Group
*/
public SequentialGroup add(Group group) {
return (SequentialGroup)addSpring(group);
}
/**
* Adds a Group
to this Group
.
*
* @param group the Group
to add
* @param useAsBaseline whether the specified Group
should
* be used to calculate the baseline for this Group
* @return this Group
*/
public SequentialGroup add(boolean useAsBaseline, Group group) {
add(group);
if (useAsBaseline) {
baselineSpring = group;
}
return this;
}
/**
* Adds the specified Component. If the Component's min/max
* are different from its pref than the component will be resizable.
*
* @param component the Component to add
* @return this SequentialGroup
*/
public SequentialGroup add(Component component) {
return add(component, DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE);
}
/**
* Adds a Component
to this Group
.
*
* @param useAsBaseline whether the specified Component
should
* be used to calculate the baseline for this Group
* @param component the Component
to add
* @return this Group
*/
public SequentialGroup add(boolean useAsBaseline, Component component) {
add(component);
if (useAsBaseline) {
baselineSpring = getSpring(springs.size() - 1);
}
return this;
}
/**
* Adds the specified Component
. Min, pref and max
* can be absolute values, or they can be one of
* DEFAULT_SIZE
or PREFERRED_SIZE
. For
* example, the following:
*
* add(component, PREFERRED_SIZE, PREFERRED_SIZE, 1000);
*
* Forces a max of 1000, with the min and preferred equalling that
* of the preferred size of component
.
*
* @param component the Component to add
* @param min the minimum size
* @param pref the preferred size
* @param max the maximum size
* @throws IllegalArgumentException if min, pref or max are
* not positive and not one of PREFERRED_SIZE or DEFAULT_SIZE
* @return this SequentialGroup
*/
public SequentialGroup add(Component component, int min, int pref,
int max) {
return (SequentialGroup)addSpring(new ComponentSpring(
component, min, pref, max));
}
/**
* Adds a Component
to this Group
* with the specified size.
*
* @param useAsBaseline whether the specified Component
should
* be used to calculate the baseline for this Group
* @param component the Component
to add
* @param min the minimum size or one of DEFAULT_SIZE
or
* PREFERRED_SIZE
* @param pref the preferred size or one of DEFAULT_SIZE
or
* PREFERRED_SIZE
* @param max the maximum size or one of DEFAULT_SIZE
or
* PREFERRED_SIZE
* @return this Group
*/
public SequentialGroup add(boolean useAsBaseline,
Component component, int min, int pref, int max) {
add(component, min, pref, max);
if (useAsBaseline) {
baselineSpring = getSpring(springs.size() - 1);
}
return this;
}
/**
* Adds a rigid gap.
*
* @param pref the size of the gap
* @throws IllegalArgumentException if min < 0 or pref < 0 or max < 0
* or the following is not meant min <= pref <= max
* @return this SequentialGroup
*/
public SequentialGroup add(int pref) {
return add(pref, pref, pref);
}
/**
* Adds a gap with the specified size.
*
* @param min the minimum size of the gap, or PREFERRED_SIZE
* @param pref the preferred size of the gap
* @param max the maximum size of the gap, or PREFERRED_SIZE
* @throws IllegalArgumentException if min < 0 or pref < 0 or max < 0
* or the following is not meant min <= pref <= max
* @return this SequentialGroup
*/
public SequentialGroup add(int min, int pref, int max) {
return (SequentialGroup)addSpring(new GapSpring(min, pref, max));
}
/**
* Adds an element representing the preferred gap between the two
* components.
*
* @param comp1 the first component
* @param comp2 the second component
* @param type the type of gap; one of the constants defined by
* LayoutStyle
* @return this SequentialGroup
* @throws IllegalArgumentException if type
is not a
* valid LayoutStyle constant
* @see LayoutStyle
*/
public SequentialGroup addPreferredGap(Component comp1,
Component comp2,
int type) {
return addPreferredGap(comp1, comp2, type, false);
}
/**
* Adds an element representing the preferred gap between the two
* components.
*
* @param comp1 the first component
* @param comp2 the second component
* @param type the type of gap; one of the constants defined by
* LayoutStyle
* @param canGrow true if the gap can grow if more
* space is available
* @return this SequentialGroup
* @throws IllegalArgumentException if type
is not a
* valid LayoutStyle constant
* @see LayoutStyle
*/
public SequentialGroup addPreferredGap(Component comp1,
Component comp2,
int type, boolean canGrow) {
if (type != LayoutStyle.RELATED &&
type != LayoutStyle.UNRELATED &&
type != LayoutStyle.INDENT) {
throw new IllegalArgumentException("Invalid type argument");
}
if (comp1 == null || comp2 == null) {
throw new IllegalArgumentException(
"Components must be non-null");
}
return (SequentialGroup)addSpring(new PaddingSpring(
comp1, comp2, type, canGrow));
}
/**
* Adds an element representing the preferred gap between the
* nearest components. That is, during layout the neighboring
* components are found, and the min, pref and max of this
* element is set based on the preferred gap between the
* components. If no neighboring components are found the
* min, pref and max are set to 0.
*
* @param type the type of gap; one of the LayoutStyle constants
* @return this SequentialGroup
* @throws IllegalArgumentException if type is not one of
* LayoutStyle.RELATED
or
* LayoutStyle.UNRELATED
* @see LayoutStyle
*/
public SequentialGroup addPreferredGap(int type) {
return addPreferredGap(type, DEFAULT_SIZE, DEFAULT_SIZE);
}
/**
* Adds an element for the preferred gap between the
* nearest components. That is, during layout the neighboring
* components are found, and the min of this
* element is set based on the preferred gap between the
* components. If no neighboring components are found the
* min is set to 0. This method allows you to specify the
* preferred and maximum size by way of the pref
* and max
arguments. These can either be a
* value >= 0, in which case the preferred or max is the max
* of the argument and the preferred gap, of DEFAULT_VALUE in
* which case the value is the same as the preferred gap.
*
* @param type the type of gap; one of LayoutStyle.RELATED or
* LayoutStyle.UNRELATED
* @param pref the preferred size; one of DEFAULT_SIZE or a value > 0
* @param max the maximum size; one of DEFAULT_SIZE, PREFERRED_SIZE
* or a value > 0
* @return this SequentialGroup
* @throws IllegalArgumentException if type is not one of
* LayoutStyle.RELATED
or
* LayoutStyle.UNRELATED
or pref/max is
* != DEFAULT_SIZE and < 0, or pref > max
* @see LayoutStyle
*/
public SequentialGroup addPreferredGap(int type, int pref,
int max) {
if (type != LayoutStyle.RELATED && type != LayoutStyle.UNRELATED) {
throw new IllegalArgumentException(
"Padding type must be one of Padding.RELATED or Padding.UNRELATED");
}
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");
}
hasPreferredPaddingSprings = true;
return (SequentialGroup)addSpring(new AutopaddingSpring(
type, pref, max));
}
/**
* Adds an element representing the preferred gap between one edge
* of the container and the next/previous Component. This will have
* no effect if the next/previous element is not a Component and does
* not touch one edge of the parent container.
*
* @return this 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/previous Component. This will have
* no effect if the next/previous element is not a Component and does
* not touch one edge of the parent container.
*
* @param pref the preferred size; one of DEFAULT_SIZE or a value > 0
* @param max the maximum size; one of DEFAULT_SIZE, PREFERRED_SIZE
* or a value > 0.
* @throws IllegalArgumentException if pref/max is
* != DEFAULT_SIZE and < 0, or pref > max
* @return this 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 ContainerAutopaddingSpring(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) {
for (int counter = 0, max = springs.size(); counter < max;
counter++) {
Spring spring = getSpring(counter);
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 accommodate 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 resizable
ArrayList 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 = (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.
*/
private ArrayList buildResizableList(int axis, boolean useMin) {
// First pass, figure out what is resizable
int size = springs.size();
ArrayList sorted = new ArrayList(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));
}
}
//size = sorted.size();
// insertion sort for a relatively small ArrayList
for (int i = 0 ; i < 0 ; i++) {
for (int j = i; j > 0 && ((SpringDelta)sorted.get(j-1)).compareTo(sorted.get(j)) > 0 ; j--) {
Object a = sorted.get(j-1);
Object b = sorted.get(j);
sorted.set(j - 1, b);
sorted.set(j, a);
}
}
return sorted;
}
private int indexOfNextNonZeroSpring(int index, boolean treatAutopaddingAsZeroSized) {
while (index < springs.size()) {
Spring spring = (Spring)springs.get(index);
if (!((Spring)spring).willHaveZeroSize(treatAutopaddingAsZeroSized)) {
return index;
}
index++;
}
return index;
}
void insertAutopadding(int axis, ArrayList leadingPadding,
ArrayList trailingPadding, ArrayList leading, ArrayList trailing,
boolean insert) {
ArrayList newLeadingPadding = create(leadingPadding);
ArrayList newTrailingPadding = new ArrayList(1);
ArrayList newLeading = create(leading);
ArrayList 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 AutopaddingSpring) {
if (newLeadingPadding.size() == 0) {
AutopaddingSpring padding = (AutopaddingSpring)spring;
padding.setSources(newLeading);
newLeading.clear();
int nextCounter = indexOfNextNonZeroSpring(counter + 1, true);
if (nextCounter == springs.size()) {
// Last spring in the list, add it to trailingPadding.
if (!(padding instanceof ContainerAutopaddingSpring)) {
trailingPadding.add(padding);
}
} else {
newLeadingPadding.clear();
newLeadingPadding.add(padding);
}
counter = nextCounter;
} else {
counter = indexOfNextNonZeroSpring(counter + 1, true);
}
} else {
// Not a padding spring
if (newLeading.size() > 0 && insert) {
// There's leading ComponentSprings, create an
// autopadding spring.
AutopaddingSpring padding = new AutopaddingSpring();
// 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 (int i = 0; i < newLeadingPadding.size(); i++) {
((AutopaddingSpring)newLeadingPadding.get(i)).
addTarget(cSpring, axis);
}
newLeading.clear();
newLeadingPadding.clear();
int nextCounter = indexOfNextNonZeroSpring(counter + 1, false);
if (nextCounter == springs.size()) {
// Last Spring, add it to trailing
trailing.add(cSpring);
} else {
// Not that last Spring, add it to leading
newLeading.add(cSpring);
}
counter = nextCounter;
} else if (spring instanceof Group) {
// Forward call to child Group
if (newTrailing == null) {
newTrailing = new ArrayList(1);
} else {
newTrailing.clear();
}
newTrailingPadding.clear();
((Group)spring).insertAutopadding(axis, newLeadingPadding,
newTrailingPadding, newLeading, newTrailing,
insert);
newLeading.clear();
newLeadingPadding.clear();
int nextCounter = indexOfNextNonZeroSpring(counter + 1,
newTrailing.size() == 0);
if (nextCounter == springs.size()) {
addAll(trailing, newTrailing);
addAll(trailingPadding, newTrailingPadding);
} else {
addAll(newLeading, newTrailing);
addAll(newLeadingPadding, newTrailingPadding);
}
counter = nextCounter;
} else {
// Gap
newLeadingPadding.clear();
newLeading.clear();
counter++;
}
}
}
}
int getBaseline() {
if (baselineSpring != null) {
int baseline = baselineSpring.getBaseline();
if (baseline >= 0) {
int size = 0;
for (int i = 0, max = springs.size(); i < max; i++) {
Spring spring = getSpring(i);
if (spring == baselineSpring) {
return size + baseline;
} else {
size += spring.getPreferredSize(VERTICAL);
}
}
}
}
return -1;
}
int 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
// preceding springs resize.
boolean leadingResizable = false;
for (int i = 0, max = springs.size(); i < max; i++) {
Spring spring = getSpring(i);
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 = getSpring(i);
if (spring == baselineSpring) {
break;
}
if (spring.isResizable(VERTICAL)) {
trailingResizable = true;
break;
}
}
if (leadingResizable && !trailingResizable) {
return Component.BRB_CONSTANT_DESCENT;
} else if (!leadingResizable && trailingResizable) {
return Component.BRB_CONSTANT_ASCENT;
}
// If we get here, both leading and trailing springs are
// resizable. Fall through to OTHER.
} else {
int brb = baselineSpring.getBaselineResizeBehavior();
if (brb == Component.BRB_CONSTANT_ASCENT) {
for (int i = 0, max = springs.size(); i < max; i++) {
Spring spring = getSpring(i);
if (spring == baselineSpring) {
return Component.BRB_CONSTANT_ASCENT;
}
if (spring.isResizable(VERTICAL)) {
return Component.BRB_OTHER;
}
}
} else if (brb == Component.BRB_CONSTANT_DESCENT) {
for (int i = springs.size() - 1; i >= 0; i--) {
Spring spring = getSpring(i);
if (spring == baselineSpring) {
return Component.BRB_CONSTANT_DESCENT;
}
if (spring.isResizable(VERTICAL)) {
return Component.BRB_OTHER;
}
}
}
}
return Component.BRB_OTHER;
}
// Not resizable, treat as constant_ascent
return Component.BRB_CONSTANT_ASCENT;
}
}
/**
* Used in figuring out how much space to give resizable springs.
*/
private static final class 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(Object o) {
return delta - ((SpringDelta)o).delta;
}
public String toString() {
return super.toString() + "[index=" + index + ", delta=" +
delta + "]";
}
}
/**
* A Group
that lays out its elements on top of each
* other. If a child element is smaller than the provided space it
* is aligned based on the alignment of the child (if specified) or
* on the alignment of the ParallelGroup.
*
* @see #createParallelGroup()
*/
public class ParallelGroup extends Group {
// How children are layed out.
private final int childAlignment;
// Whether or not we're resizable.
private final boolean resizable;
ParallelGroup(int childAlignment, boolean resizable) {
this.childAlignment = childAlignment;
this.resizable = resizable;
}
/**
* Adds the specified Group
.
*
* @param group the Group to add
* @return this Group
*/
public ParallelGroup add(Group group) {
return (ParallelGroup)addSpring(group);
}
/**
* Adds the specified Component. If the Component's min/max
* are different from its pref than the component will be resizable.
*
* @param component the Component to add
* @return this ParallelGroup
*/
public ParallelGroup add(Component component) {
return add(component, DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE);
}
/**
* Adds the specified Component
. Min, pref and max
* can be absolute values, or they can be one of
* DEFAULT_SIZE
or PREFERRED_SIZE
. For
* example, the following:
*
* add(component, PREFERRED_SIZE, PREFERRED_SIZE, 1000);
*
* Forces a max of 1000, with the min and preferred equalling that
* of the preferred size of component
.
*
* @param component the Component to add
* @param min the minimum size
* @param pref the preferred size
* @param max the maximum size
* @throws IllegalArgumentException if min, pref or max are
* not positive and not one of PREFERRED_SIZE or DEFAULT_SIZE.
* @return this SequentialGroup
*/
public ParallelGroup add(Component component, int min, int pref,
int max) {
return (ParallelGroup)addSpring(new ComponentSpring(
component, min, pref, max));
}
/**
* Adds a rigid gap.
*
* @param pref the size of the gap
* @throws IllegalArgumentException if min < 0 or pref < 0 or max < 0
* or the following is not meant min <= pref <= max.
* @return this ParallelGroup
*/
public ParallelGroup add(int pref) {
return add(pref, pref, pref);
}
/**
* Adds a gap 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 min < 0 or pref < 0 or max < 0
* or the following is not meant min <= pref <= max.
* @return this ParallelGroup
*/
public ParallelGroup add(int min, int pref, int max) {
return (ParallelGroup)addSpring(new GapSpring(min, pref, max));
}
/**
* Adds the specified Group
as a child of this group.
*
* @param alignment the alignment of the Group.
* @param group the Group to add
* @return this ParallelGroup
* @throws IllegalArgumentException if alignment is not one of
* LEADING
, TRAILING
or
* CENTER
*/
public ParallelGroup add(int alignment, Group group) {
checkChildAlignment(alignment);
group.setAlignment(alignment);
return (ParallelGroup)addSpring(group);
}
/**
* Adds the specified Component. If the Component's min/max
* are different from its pref than the component will be resizable.
*
* @param alignment the alignment for the component
* @param component the Component to add
* @return this Group
* @throws IllegalArgumentException if alignment is not one of
* LEADING
, TRAILING
or
* CENTER
*/
public ParallelGroup add(int alignment, Component component) {
return add(alignment, component, DEFAULT_SIZE, DEFAULT_SIZE,
DEFAULT_SIZE);
}
/**
* Adds the specified Component
. Min, pref and max
* can be absolute values, or they can be one of
* DEFAULT_SIZE
or PREFERRED_SIZE
. For
* example, the following:
*
* add(component, PREFERRED_SIZE, PREFERRED_SIZE, 1000);
*
* Forces a max of 1000, with the min and preferred equalling that
* of the preferred size of component
.
*
* @param alignment the alignment for the component.
* @param component the Component to add
* @param min the minimum size
* @param pref the preferred size
* @param max the maximum size
* @throws IllegalArgumentException if min, pref or max are
* not positive and not one of PREFERRED_SIZE or DEFAULT_SIZE.
* @return this Group
*/
public ParallelGroup add(int alignment, Component component, 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 (int i = 0, max = springs.size(); i < max; i++) {
setChildSize(getSpring(i), axis, origin, size);
}
}
void setChildSize(Spring spring, int axis, int origin, int size) {
int alignment = spring.getAlignment();
int springSize = Math.min(
Math.max(spring.getMinimumSize(axis), size),
spring.getMaximumSize(axis));
if (alignment == NO_ALIGNMENT) {
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;
}
}
void insertAutopadding(int axis, ArrayList leadingPadding,
ArrayList trailingPadding, ArrayList leading, ArrayList trailing,
boolean insert) {
for (int counter = 0, max = springs.size(); counter < max; counter++) {
Spring spring = getSpring(counter);
if (spring instanceof ComponentSpring) {
if (((ComponentSpring)spring).isVisible()) {
for (int i = 0; i < leadingPadding.size(); i++) {
((AutopaddingSpring)leadingPadding.get(i)).addTarget(
(ComponentSpring)spring, axis);
}
trailing.add(spring);
}
} else if (spring instanceof Group) {
((Group)spring).insertAutopadding(axis, leadingPadding,
trailingPadding, leading, trailing, insert);
} else if (spring instanceof AutopaddingSpring) {
((AutopaddingSpring)spring).setSources(leading);
trailingPadding.add(spring);
}
}
}
private void checkChildAlignment(int alignment) {
boolean allowsBaseline = (this instanceof BaselineGroup);
if (!allowsBaseline && alignment == BASELINE) {
throw new IllegalArgumentException("Alignment must be one of:" +
"LEADING, TRAILING or CENTER");
}
if (alignment != CENTER && alignment != BASELINE &&
alignment != LEADING && alignment != TRAILING) {
throw new IllegalArgumentException("Alignment must be one of:" +
"LEADING, TRAILING or CENTER");
}
}
}
/**
* An extension of ParallelGroup
that aligns its
* constituent 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(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;
int resizeBehavior = 0;
for (int counter = springs.size() - 1; counter >= 0; counter--) {
Spring spring = getSpring(counter);
if (spring.getAlignment() == NO_ALIGNMENT ||
spring.getAlignment() == BASELINE) {
int baseline = spring.getBaseline();
if (baseline >= 0) {
if (spring.isResizable(VERTICAL)) {
int brb = spring.
getBaselineResizeBehavior();
if (resizeBehavior == 0) {
resizeBehavior = brb;
} else if (brb != resizeBehavior) {
resizeBehavior = Component.BRB_CONSTANT_ASCENT;
}
}
prefAscent = Math.max(prefAscent, baseline);
prefDescent = Math.max(prefDescent, spring.
getPreferredSize(VERTICAL) - baseline);
baselineSpringCount++;
}
}
}
if (!baselineAnchorSet) {
if (resizeBehavior == Component.BRB_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 (int counter = springs.size() - 1; counter >= 0; counter--) {
Spring spring = getSpring(counter);
int baseline;
int springMax = spring.getMaximumSize(VERTICAL);
if ((spring.getAlignment() == NO_ALIGNMENT ||
spring.getAlignment() == BASELINE) &&
(baseline = spring.getBaseline()) >= 0) {
int springPref = spring.getPreferredSize(VERTICAL);
if (springPref != springMax) {
switch (spring.getBaselineResizeBehavior()) {
case Component.BRB_CONSTANT_ASCENT:
if (baselineAnchoredToTop) {
maxDescent = Math.max(maxDescent,
springMax - baseline);
}
break;
case Component.BRB_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 (int counter = springs.size() - 1; counter >= 0; counter--) {
Spring spring = getSpring(counter);
int springMin = spring.getMinimumSize(VERTICAL);
int baseline;
if ((spring.getAlignment() == NO_ALIGNMENT ||
spring.getAlignment() == BASELINE) &&
(baseline = spring.getBaseline()) >= 0) {
int springPref = spring.getPreferredSize(VERTICAL);
switch (spring.getBaselineResizeBehavior()) {
case Component.BRB_CONSTANT_ASCENT:
if (baselineAnchoredToTop) {
minDescent = Math.max(springMin - baseline,
minDescent);
} else {
minAscent = Math.max(baseline, minAscent);
}
break;
case Component.BRB_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 (int counter = springs.size() - 1; counter >= 0; counter--) {
Spring spring = getSpring(counter);
int alignment = spring.getAlignment();
if (alignment == NO_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 Component.BRB_CONSTANT_ASCENT:
y = origin + ascent - baseline;
height = Math.min(descent, springMax -
baseline) + baseline;
break;
case Component.BRB_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 getSpring(0).getBaseline();
}
return -1;
}
int getBaselineResizeBehavior() {
if (springs.size() == 1) {
return getSpring(0).getBaselineResizeBehavior();
}
if (baselineAnchoredToTop) {
return Component.BRB_CONSTANT_ASCENT;
}
return Component.BRB_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");
}
}
}
/**
* A Spring representing one axis of a Component.
* There are three ways to configure this:
*
* - Use the pref/min/max from the component
*
- Use the pref from the component and fix the min to 0 or max
* to a big number.
*
- Force the min/max/pref to be a certain value.
* If the Component's size is to be linked to another components than
* the min/max/pref all come from the ComponentInfo.
*/
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.
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());
// Do we need this if we don't support minimum size?
return getSizeAlongAxis(axis, component.getPreferredSize());
}
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());
return getSizeAlongAxis(axis, component.getPreferredSize());
}
private int getSizeAlongAxis(int axis, Dimension size) {
return (axis == HORIZONTAL) ? size.getWidth() : size.getHeight();
}
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;
}
int 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;
}
}
}
boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
return !isVisible();
}
}
/**
* Spring representing the preferred distance between two components.
*/
private final class PaddingSpring extends Spring {
private final Component source;
private final Component target;
private final int type;
private final boolean canGrow;
PaddingSpring(Component source, Component target, int type,
boolean canGrow) {
this.source = source;
this.target = target;
this.type = type;
this.canGrow = canGrow;
}
int calculateMinimumSize(int axis) {
return getPadding(axis);
}
int calculatePreferredSize(int axis) {
return getPadding(axis);
}
int calculateMaximumSize(int axis) {
if (canGrow) {
return Short.MAX_VALUE;
}
return getPadding(axis);
}
private int getPadding(int axis) {
int position;
if (axis == HORIZONTAL) {
position = EAST;
} else {
position = SOUTH;
}
return getLayoutStyle0().getPreferredGap(source,
target, type, position, host);
}
boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
return false;
}
}
/**
* Spring represented a certain amount of space.
*/
private final 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;
}
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 AutopaddingSpring extends Spring {
ArrayList sources;
ComponentSpring source;
private ArrayList matches;
int size;
int lastSize;
private final int pref;
private final int max;
private int type;
private boolean userCreated;
private AutopaddingSpring() {
this.pref = PREFERRED_SIZE;
this.max = PREFERRED_SIZE;
this.type = LayoutStyle.RELATED;
}
AutopaddingSpring(int pref, int max) {
this.pref = pref;
this.max = max;
}
AutopaddingSpring(int 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(ArrayList sources) {
this.sources = create(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 = EAST;
} else {
position = WEST;
}
} else {
position = SOUTH;
}
for (int i = matches.size() - 1; i >= 0; i--) {
AutopaddingMatch match = (AutopaddingMatch)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 = p.getPreferredGap(source.getComponent(),
target.getComponent(), type, position, host);
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 = (ComponentSpring)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(1);
}
matches.add(new AutopaddingMatch(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();
}
boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) {
return treatAutopaddingAsZeroSized;
}
}
/**
* Represents two springs that should have autopadding inserted between
* them.
*/
private final static class AutopaddingMatch {
public final ComponentSpring source;
public final ComponentSpring target;
AutopaddingMatch(ComponentSpring source, ComponentSpring target) {
this.source = source;
this.target = target;
}
private String toString(ComponentSpring spring) {
return spring.getComponent().toString();
}
public String toString() {
return "[" + toString(source) + "-" + toString(target) + "]";
}
}
/**
* An extension of AutopaddingSpring used for container level padding.
*/
private class ContainerAutopaddingSpring extends AutopaddingSpring {
private ArrayList targets;
ContainerAutopaddingSpring() {
super();
setUserCreated(true);
}
ContainerAutopaddingSpring(int pref, int max) {
super(pref, max);
setUserCreated(true);
}
public void addTarget(ComponentSpring spring, int axis) {
if (targets == null) {
targets = new ArrayList(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 = WEST;
} else {
position = EAST;
}
} else {
position = SOUTH;
}
for (int i = targets.size() - 1; i >= 0; i--) {
ComponentSpring targetSpring = (ComponentSpring)targets.get(i);
int padding = p.getContainerGap(
targetSpring.getComponent(),
position, host);
maxPadding = Math.max(padding, maxPadding);
padding -= targetSpring.getOrigin();
size = Math.max(size, padding);
}
}
else {
// Trailing
if (axis == HORIZONTAL) {
if (isLeftToRight()) {
position = EAST;
} else {
position = WEST;
}
} else {
position = SOUTH;
}
if (sources != null) {
for (int i = sources.size() - 1; i >= 0; i--) {
ComponentSpring sourceSpring = (ComponentSpring)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 = p.getContainerGap(
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 final class LinkInfo {
private final int axis;
private final ArrayList linked;
private int size;
LinkInfo(int axis) {
linked = new ArrayList();
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) {
addAll(linked, childMaster.linked);
for (int i = 0; i < childMaster.linked.size(); i++) {
ComponentInfo childInfo = (ComponentInfo)childMaster.linked.get(i);
childInfo.setLinkInfo(axis, this);
}
}
clearCachedSize();
}
public void remove(ComponentInfo info) {
linked.remove(info);
info.setLinkInfo(axis, null);
if (linked.size() == 1) {
((ComponentInfo)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 (int i = 0; i < linked.size(); i++) {
ComponentInfo info = (ComponentInfo)linked.get(i);
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 final 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.booleanValue();
}
boolean newVisible = (honorsVisibility) ?
component.isVisible() : true;
if (visible != newVisible) {
visible = newVisible;
return true;
}
return false;
}
public void setBounds(int insetX, int insetY, 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.setX(x + insetX);
component.setY(y + insetY);
component.setWidth(w);
component.setHeight(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);
}
}
}
}