/*
* Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing;
import java.awt.
Component;
import java.awt.
Container;
import java.awt.
Dimension;
import java.awt.
FontMetrics;
import java.awt.
Insets;
import java.awt.
LayoutManager2;
import java.awt.
Rectangle;
import java.util.*;
/**
* A <code>SpringLayout</code> lays out the children of its associated container
* according to a set of constraints.
* See <a href="https://docs.oracle.com/javase/tutorial/uiswing/layout/spring.html">How to Use SpringLayout</a>
* in <em>The Java Tutorial</em> for examples of using
* <code>SpringLayout</code>.
*
* <p>
* Each constraint,
* represented by a <code>Spring</code> object,
* controls the vertical or horizontal distance
* between two component edges.
* The edges can belong to
* any child of the container,
* or to the container itself.
* For example,
* the allowable width of a component
* can be expressed using a constraint
* that controls the distance between the west (left) and east (right)
* edges of the component.
* The allowable <em>y</em> coordinates for a component
* can be expressed by constraining the distance between
* the north (top) edge of the component
* and the north edge of its container.
*
* <P>
* Every child of a <code>SpringLayout</code>-controlled container,
* as well as the container itself,
* has exactly one set of constraints
* associated with it.
* These constraints are represented by
* a <code>SpringLayout.Constraints</code> object.
* By default,
* <code>SpringLayout</code> creates constraints
* that make their associated component
* have the minimum, preferred, and maximum sizes
* returned by the component's
* {@link java.awt.Component#getMinimumSize},
* {@link java.awt.Component#getPreferredSize}, and
* {@link java.awt.Component#getMaximumSize}
* methods. The <em>x</em> and <em>y</em> positions are initially not
* constrained, so that until you constrain them the <code>Component</code>
* will be positioned at 0,0 relative to the <code>Insets</code> of the
* parent <code>Container</code>.
*
* <p>
* You can change
* a component's constraints in several ways.
* You can
* use one of the
* {@link #putConstraint putConstraint}
* methods
* to establish a spring
* linking the edges of two components within the same container.
* Or you can get the appropriate <code>SpringLayout.Constraints</code>
* object using
* {@link #getConstraints getConstraints}
* and then modify one or more of its springs.
* Or you can get the spring for a particular edge of a component
* using {@link #getConstraint getConstraint},
* and modify it.
* You can also associate
* your own <code>SpringLayout.Constraints</code> object
* with a component by specifying the constraints object
* when you add the component to its container
* (using
* {@link Container#add(Component, Object)}).
*
* <p>
* The <code>Spring</code> object representing each constraint
* has a minimum, preferred, maximum, and current value.
* The current value of the spring
* is somewhere between the minimum and maximum values,
* according to the formula given in the
* {@link Spring#sum} method description.
* When the minimum, preferred, and maximum values are the same,
* the current value is always equal to them;
* this inflexible spring is called a <em>strut</em>.
* You can create struts using the factory method
* {@link Spring#constant(int)}.
* The <code>Spring</code> class also provides factory methods
* for creating other kinds of springs,
* including springs that depend on other springs.
*
* <p>
* In a <code>SpringLayout</code>, the position of each edge is dependent on
* the position of just one other edge. If a constraint is subsequently added
* to create a new binding for an edge, the previous binding is discarded
* and the edge remains dependent on a single edge.
* Springs should only be attached
* between edges of the container and its immediate children; the behavior
* of the <code>SpringLayout</code> when presented with constraints linking
* the edges of components from different containers (either internal or
* external) is undefined.
*
* <h3>
* SpringLayout vs. Other Layout Managers
* </h3>
*
* <blockquote>
* <hr>
* <strong>Note:</strong>
* Unlike many layout managers,
* <code>SpringLayout</code> doesn't automatically set the location of
* the components it manages.
* If you hand-code a GUI that uses <code>SpringLayout</code>,
* remember to initialize component locations by constraining the west/east
* and north/south locations.
* <p>
* Depending on the constraints you use,
* you may also need to set the size of the container explicitly.
* <hr>
* </blockquote>
*
* <p>
* Despite the simplicity of <code>SpringLayout</code>,
* it can emulate the behavior of most other layout managers.
* For some features,
* such as the line breaking provided by <code>FlowLayout</code>,
* you'll need to
* create a special-purpose subclass of the <code>Spring</code> class.
*
* <p>
* <code>SpringLayout</code> also provides a way to solve
* many of the difficult layout
* problems that cannot be solved by nesting combinations
* of <code>Box</code>es. That said, <code>SpringLayout</code> honors the
* <code>LayoutManager2</code> contract correctly and so can be nested with
* other layout managers -- a technique that can be preferable to
* creating the constraints implied by the other layout managers.
* <p>
* The asymptotic complexity of the layout operation of a <code>SpringLayout</code>
* is linear in the number of constraints (and/or components).
* <p>
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @see Spring
* @see SpringLayout.Constraints
*
* @author Philip Milne
* @author Scott Violet
* @author Joe Winchester
* @since 1.4
*/
public class
SpringLayout implements
LayoutManager2 {
private
Map<
Component,
Constraints>
componentConstraints = new
HashMap<
Component,
Constraints>();
private
Spring cyclicReference =
Spring.
constant(
Spring.
UNSET);
private
Set<
Spring>
cyclicSprings;
private
Set<
Spring>
acyclicSprings;
/**
* Specifies the top edge of a component's bounding rectangle.
*/
public static final
String NORTH = "North";
/**
* Specifies the bottom edge of a component's bounding rectangle.
*/
public static final
String SOUTH = "South";
/**
* Specifies the right edge of a component's bounding rectangle.
*/
public static final
String EAST = "East";
/**
* Specifies the left edge of a component's bounding rectangle.
*/
public static final
String WEST = "West";
/**
* Specifies the horizontal center of a component's bounding rectangle.
*
* @since 1.6
*/
public static final
String HORIZONTAL_CENTER = "HorizontalCenter";
/**
* Specifies the vertical center of a component's bounding rectangle.
*
* @since 1.6
*/
public static final
String VERTICAL_CENTER = "VerticalCenter";
/**
* Specifies the baseline of a component.
*
* @since 1.6
*/
public static final
String BASELINE = "Baseline";
/**
* Specifies the width of a component's bounding rectangle.
*
* @since 1.6
*/
public static final
String WIDTH = "Width";
/**
* Specifies the height of a component's bounding rectangle.
*
* @since 1.6
*/
public static final
String HEIGHT = "Height";
private static
String[]
ALL_HORIZONTAL = {
WEST,
WIDTH,
EAST,
HORIZONTAL_CENTER};
private static
String[]
ALL_VERTICAL = {
NORTH,
HEIGHT,
SOUTH,
VERTICAL_CENTER,
BASELINE};
/**
* A <code>Constraints</code> object holds the
* constraints that govern the way a component's size and position
* change in a container controlled by a <code>SpringLayout</code>.
* A <code>Constraints</code> object is
* like a <code>Rectangle</code>, in that it
* has <code>x</code>, <code>y</code>,
* <code>width</code>, and <code>height</code> properties.
* In the <code>Constraints</code> object, however,
* these properties have
* <code>Spring</code> values instead of integers.
* In addition,
* a <code>Constraints</code> object
* can be manipulated as four edges
* -- north, south, east, and west --
* using the <code>constraint</code> property.
*
* <p>
* The following formulas are always true
* for a <code>Constraints</code> object (here WEST and <code>x</code> are synonyms, as are and NORTH and <code>y</code>):
*
* <pre>
* EAST = WEST + WIDTH
* SOUTH = NORTH + HEIGHT
* HORIZONTAL_CENTER = WEST + WIDTH/2
* VERTICAL_CENTER = NORTH + HEIGHT/2
* ABSOLUTE_BASELINE = NORTH + RELATIVE_BASELINE*
* </pre>
* <p>
* For example, if you have specified the WIDTH and WEST (X) location
* the EAST is calculated as WEST + WIDTH. If you instead specified
* the WIDTH and EAST locations the WEST (X) location is then calculated
* as EAST - WIDTH.
* <p>
* [RELATIVE_BASELINE is a private constraint that is set automatically when
* the SpringLayout.Constraints(Component) constructor is called or when
* a constraints object is registered with a SpringLayout object.]
* <p>
* <b>Note</b>: In this document,
* operators represent methods
* in the <code>Spring</code> class.
* For example, "a + b" is equal to
* <code>Spring.sum(a, b)</code>,
* and "a - b" is equal to
* <code>Spring.sum(a, Spring.minus(b))</code>.
* See the
* {@link Spring Spring API documentation}
* for further details
* of spring arithmetic.
*
* <p>
*
* Because a <code>Constraints</code> object's properties --
* representing its edges, size, and location -- can all be set
* independently and yet are interrelated,
* a <code>Constraints</code> object can become <em>over-constrained</em>.
* For example, if the <code>WEST</code>, <code>WIDTH</code> and
* <code>EAST</code> edges are all set, steps must be taken to ensure that
* the first of the formulas above holds. To do this, the
* <code>Constraints</code>
* object throws away the <em>least recently set</em>
* constraint so as to make the formulas hold.
* @since 1.4
*/
public static class
Constraints {
private
Spring x;
private
Spring y;
private
Spring width;
private
Spring height;
private
Spring east;
private
Spring south;
private
Spring horizontalCenter;
private
Spring verticalCenter;
private
Spring baseline;
private
List<
String>
horizontalHistory = new
ArrayList<
String>(2);
private
List<
String>
verticalHistory = new
ArrayList<
String>(2);
// Used for baseline calculations
private
Component c;
/**
* Creates an empty <code>Constraints</code> object.
*/
public
Constraints() {
}
/**
* Creates a <code>Constraints</code> object with the
* specified values for its
* <code>x</code> and <code>y</code> properties.
* The <code>height</code> and <code>width</code> springs
* have <code>null</code> values.
*
* @param x the spring controlling the component's <em>x</em> value
* @param y the spring controlling the component's <em>y</em> value
*/
public
Constraints(
Spring x,
Spring y) {
setX(
x);
setY(
y);
}
/**
* Creates a <code>Constraints</code> object with the
* specified values for its
* <code>x</code>, <code>y</code>, <code>width</code>,
* and <code>height</code> properties.
* Note: If the <code>SpringLayout</code> class
* encounters <code>null</code> values in the
* <code>Constraints</code> object of a given component,
* it replaces them with suitable defaults.
*
* @param x the spring value for the <code>x</code> property
* @param y the spring value for the <code>y</code> property
* @param width the spring value for the <code>width</code> property
* @param height the spring value for the <code>height</code> property
*/
public
Constraints(
Spring x,
Spring y,
Spring width,
Spring height) {
setX(
x);
setY(
y);
setWidth(
width);
setHeight(
height);
}
/**
* Creates a <code>Constraints</code> object with
* suitable <code>x</code>, <code>y</code>, <code>width</code> and
* <code>height</code> springs for component, <code>c</code>.
* The <code>x</code> and <code>y</code> springs are constant
* springs initialised with the component's location at
* the time this method is called. The <code>width</code> and
* <code>height</code> springs are special springs, created by
* the <code>Spring.width()</code> and <code>Spring.height()</code>
* methods, which track the size characteristics of the component
* when they change.
*
* @param c the component whose characteristics will be reflected by this Constraints object
* @throws NullPointerException if <code>c</code> is null.
* @since 1.5
*/
public
Constraints(
Component c) {
this.
c =
c;
setX(
Spring.
constant(
c.
getX()));
setY(
Spring.
constant(
c.
getY()));
setWidth(
Spring.
width(
c));
setHeight(
Spring.
height(
c));
}
private void
pushConstraint(
String name,
Spring value, boolean
horizontal) {
boolean
valid = true;
List<
String>
history =
horizontal ?
horizontalHistory :
verticalHistory;
if (
history.
contains(
name)) {
history.
remove(
name);
valid = false;
} else if (
history.
size() == 2 &&
value != null) {
history.
remove(0);
valid = false;
}
if (
value != null) {
history.
add(
name);
}
if (!
valid) {
String[]
all =
horizontal ?
ALL_HORIZONTAL :
ALL_VERTICAL;
for (
String s :
all) {
if (!
history.
contains(
s)) {
setConstraint(
s, null);
}
}
}
}
private
Spring sum(
Spring s1,
Spring s2) {
return (
s1 == null ||
s2 == null) ? null :
Spring.
sum(
s1,
s2);
}
private
Spring difference(
Spring s1,
Spring s2) {
return (
s1 == null ||
s2 == null) ? null :
Spring.
difference(
s1,
s2);
}
private
Spring scale(
Spring s, float
factor) {
return (
s == null) ? null :
Spring.
scale(
s,
factor);
}
private int
getBaselineFromHeight(int
height) {
if (
height < 0) {
// Bad Scott, Bad Scott!
return -
c.
getBaseline(
c.
getPreferredSize().
width,
-
height);
}
return
c.
getBaseline(
c.
getPreferredSize().
width,
height);
}
private int
getHeightFromBaseLine(int
baseline) {
Dimension prefSize =
c.
getPreferredSize();
int
prefHeight =
prefSize.
height;
int
prefBaseline =
c.
getBaseline(
prefSize.
width,
prefHeight);
if (
prefBaseline ==
baseline) {
// If prefBaseline < 0, then no baseline, assume preferred
// height.
// If prefBaseline == baseline, then specified baseline
// matches preferred baseline, return preferred height
return
prefHeight;
}
// Valid baseline
switch(
c.
getBaselineResizeBehavior()) {
case
CONSTANT_DESCENT:
return
prefHeight + (
baseline -
prefBaseline);
case
CENTER_OFFSET:
return
prefHeight + 2 * (
baseline -
prefBaseline);
case
CONSTANT_ASCENT:
// Component baseline and specified baseline will NEVER
// match, fall through to default
default: // OTHER
// No way to map from baseline to height.
}
return
Integer.
MIN_VALUE;
}
private
Spring heightToRelativeBaseline(
Spring s) {
return new
Spring.
SpringMap(
s) {
protected int
map(int
i) {
return
getBaselineFromHeight(
i);
}
protected int
inv(int
i) {
return
getHeightFromBaseLine(
i);
}
};
}
private
Spring relativeBaselineToHeight(
Spring s) {
return new
Spring.
SpringMap(
s) {
protected int
map(int
i) {
return
getHeightFromBaseLine(
i);
}
protected int
inv(int
i) {
return
getBaselineFromHeight(
i);
}
};
}
private boolean
defined(
List history,
String s1,
String s2) {
return
history.
contains(
s1) &&
history.
contains(
s2);
}
/**
* Sets the <code>x</code> property,
* which controls the <code>x</code> value
* of a component's location.
*
* @param x the spring controlling the <code>x</code> value
* of a component's location
*
* @see #getX
* @see SpringLayout.Constraints
*/
public void
setX(
Spring x) {
this.
x =
x;
pushConstraint(
WEST,
x, true);
}
/**
* Returns the value of the <code>x</code> property.
*
* @return the spring controlling the <code>x</code> value
* of a component's location
*
* @see #setX
* @see SpringLayout.Constraints
*/
public
Spring getX() {
if (
x == null) {
if (
defined(
horizontalHistory,
EAST,
WIDTH)) {
x =
difference(
east,
width);
} else if (
defined(
horizontalHistory,
HORIZONTAL_CENTER,
WIDTH)) {
x =
difference(
horizontalCenter,
scale(
width, 0.5f));
} else if (
defined(
horizontalHistory,
HORIZONTAL_CENTER,
EAST)) {
x =
difference(
scale(
horizontalCenter, 2f),
east);
}
}
return
x;
}
/**
* Sets the <code>y</code> property,
* which controls the <code>y</code> value
* of a component's location.
*
* @param y the spring controlling the <code>y</code> value
* of a component's location
*
* @see #getY
* @see SpringLayout.Constraints
*/
public void
setY(
Spring y) {
this.
y =
y;
pushConstraint(
NORTH,
y, false);
}
/**
* Returns the value of the <code>y</code> property.
*
* @return the spring controlling the <code>y</code> value
* of a component's location
*
* @see #setY
* @see SpringLayout.Constraints
*/
public
Spring getY() {
if (
y == null) {
if (
defined(
verticalHistory,
SOUTH,
HEIGHT)) {
y =
difference(
south,
height);
} else if (
defined(
verticalHistory,
VERTICAL_CENTER,
HEIGHT)) {
y =
difference(
verticalCenter,
scale(
height, 0.5f));
} else if (
defined(
verticalHistory,
VERTICAL_CENTER,
SOUTH)) {
y =
difference(
scale(
verticalCenter, 2f),
south);
} else if (
defined(
verticalHistory,
BASELINE,
HEIGHT)) {
y =
difference(
baseline,
heightToRelativeBaseline(
height));
} else if (
defined(
verticalHistory,
BASELINE,
SOUTH)) {
y =
scale(
difference(
baseline,
heightToRelativeBaseline(
south)), 2f);
/*
} else if (defined(verticalHistory, BASELINE, VERTICAL_CENTER)) {
y = scale(difference(baseline, heightToRelativeBaseline(scale(verticalCenter, 2))), 1f/(1-2*0.5f));
*/
}
}
return
y;
}
/**
* Sets the <code>width</code> property,
* which controls the width of a component.
*
* @param width the spring controlling the width of this
* <code>Constraints</code> object
*
* @see #getWidth
* @see SpringLayout.Constraints
*/
public void
setWidth(
Spring width) {
this.
width =
width;
pushConstraint(
WIDTH,
width, true);
}
/**
* Returns the value of the <code>width</code> property.
*
* @return the spring controlling the width of a component
*
* @see #setWidth
* @see SpringLayout.Constraints
*/
public
Spring getWidth() {
if (
width == null) {
if (
horizontalHistory.
contains(
EAST)) {
width =
difference(
east,
getX());
} else if (
horizontalHistory.
contains(
HORIZONTAL_CENTER)) {
width =
scale(
difference(
horizontalCenter,
getX()), 2f);
}
}
return
width;
}
/**
* Sets the <code>height</code> property,
* which controls the height of a component.
*
* @param height the spring controlling the height of this <code>Constraints</code>
* object
*
* @see #getHeight
* @see SpringLayout.Constraints
*/
public void
setHeight(
Spring height) {
this.
height =
height;
pushConstraint(
HEIGHT,
height, false);
}
/**
* Returns the value of the <code>height</code> property.
*
* @return the spring controlling the height of a component
*
* @see #setHeight
* @see SpringLayout.Constraints
*/
public
Spring getHeight() {
if (
height == null) {
if (
verticalHistory.
contains(
SOUTH)) {
height =
difference(
south,
getY());
} else if (
verticalHistory.
contains(
VERTICAL_CENTER)) {
height =
scale(
difference(
verticalCenter,
getY()), 2f);
} else if (
verticalHistory.
contains(
BASELINE)) {
height =
relativeBaselineToHeight(
difference(
baseline,
getY()));
}
}
return
height;
}
private void
setEast(
Spring east) {
this.
east =
east;
pushConstraint(
EAST,
east, true);
}
private
Spring getEast() {
if (
east == null) {
east =
sum(
getX(),
getWidth());
}
return
east;
}
private void
setSouth(
Spring south) {
this.
south =
south;
pushConstraint(
SOUTH,
south, false);
}
private
Spring getSouth() {
if (
south == null) {
south =
sum(
getY(),
getHeight());
}
return
south;
}
private
Spring getHorizontalCenter() {
if (
horizontalCenter == null) {
horizontalCenter =
sum(
getX(),
scale(
getWidth(), 0.5f));
}
return
horizontalCenter;
}
private void
setHorizontalCenter(
Spring horizontalCenter) {
this.
horizontalCenter =
horizontalCenter;
pushConstraint(
HORIZONTAL_CENTER,
horizontalCenter, true);
}
private
Spring getVerticalCenter() {
if (
verticalCenter == null) {
verticalCenter =
sum(
getY(),
scale(
getHeight(), 0.5f));
}
return
verticalCenter;
}
private void
setVerticalCenter(
Spring verticalCenter) {
this.
verticalCenter =
verticalCenter;
pushConstraint(
VERTICAL_CENTER,
verticalCenter, false);
}
private
Spring getBaseline() {
if (
baseline == null) {
baseline =
sum(
getY(),
heightToRelativeBaseline(
getHeight()));
}
return
baseline;
}
private void
setBaseline(
Spring baseline) {
this.
baseline =
baseline;
pushConstraint(
BASELINE,
baseline, false);
}
/**
* Sets the spring controlling the specified edge.
* The edge must have one of the following values:
* <code>SpringLayout.NORTH</code>,
* <code>SpringLayout.SOUTH</code>,
* <code>SpringLayout.EAST</code>,
* <code>SpringLayout.WEST</code>,
* <code>SpringLayout.HORIZONTAL_CENTER</code>,
* <code>SpringLayout.VERTICAL_CENTER</code>,
* <code>SpringLayout.BASELINE</code>,
* <code>SpringLayout.WIDTH</code> or
* <code>SpringLayout.HEIGHT</code>.
* For any other <code>String</code> value passed as the edge,
* no action is taken. For a <code>null</code> edge, a
* <code>NullPointerException</code> is thrown.
* <p>
* <b>Note:</b> This method can affect {@code x} and {@code y} values
* previously set for this {@code Constraints}.
*
* @param edgeName the edge to be set
* @param s the spring controlling the specified edge
*
* @throws NullPointerException if <code>edgeName</code> is <code>null</code>
*
* @see #getConstraint
* @see #NORTH
* @see #SOUTH
* @see #EAST
* @see #WEST
* @see #HORIZONTAL_CENTER
* @see #VERTICAL_CENTER
* @see #BASELINE
* @see #WIDTH
* @see #HEIGHT
* @see SpringLayout.Constraints
*/
public void
setConstraint(
String edgeName,
Spring s) {
edgeName =
edgeName.
intern();
if (
edgeName ==
WEST) {
setX(
s);
} else if (
edgeName ==
NORTH) {
setY(
s);
} else if (
edgeName ==
EAST) {
setEast(
s);
} else if (
edgeName ==
SOUTH) {
setSouth(
s);
} else if (
edgeName ==
HORIZONTAL_CENTER) {
setHorizontalCenter(
s);
} else if (
edgeName ==
WIDTH) {
setWidth(
s);
} else if (
edgeName ==
HEIGHT) {
setHeight(
s);
} else if (
edgeName ==
VERTICAL_CENTER) {
setVerticalCenter(
s);
} else if (
edgeName ==
BASELINE) {
setBaseline(
s);
}
}
/**
* Returns the value of the specified edge, which may be
* a derived value, or even <code>null</code>.
* The edge must have one of the following values:
* <code>SpringLayout.NORTH</code>,
* <code>SpringLayout.SOUTH</code>,
* <code>SpringLayout.EAST</code>,
* <code>SpringLayout.WEST</code>,
* <code>SpringLayout.HORIZONTAL_CENTER</code>,
* <code>SpringLayout.VERTICAL_CENTER</code>,
* <code>SpringLayout.BASELINE</code>,
* <code>SpringLayout.WIDTH</code> or
* <code>SpringLayout.HEIGHT</code>.
* For any other <code>String</code> value passed as the edge,
* <code>null</code> will be returned. Throws
* <code>NullPointerException</code> for a <code>null</code> edge.
*
* @param edgeName the edge whose value
* is to be returned
*
* @return the spring controlling the specified edge, may be <code>null</code>
*
* @throws NullPointerException if <code>edgeName</code> is <code>null</code>
*
* @see #setConstraint
* @see #NORTH
* @see #SOUTH
* @see #EAST
* @see #WEST
* @see #HORIZONTAL_CENTER
* @see #VERTICAL_CENTER
* @see #BASELINE
* @see #WIDTH
* @see #HEIGHT
* @see SpringLayout.Constraints
*/
public
Spring getConstraint(
String edgeName) {
edgeName =
edgeName.
intern();
return (
edgeName ==
WEST) ?
getX() :
(
edgeName ==
NORTH) ?
getY() :
(
edgeName ==
EAST) ?
getEast() :
(
edgeName ==
SOUTH) ?
getSouth() :
(
edgeName ==
WIDTH) ?
getWidth() :
(
edgeName ==
HEIGHT) ?
getHeight() :
(
edgeName ==
HORIZONTAL_CENTER) ?
getHorizontalCenter() :
(
edgeName ==
VERTICAL_CENTER) ?
getVerticalCenter() :
(
edgeName ==
BASELINE) ?
getBaseline() :
null;
}
/*pp*/ void
reset() {
Spring[]
allSprings = {
x,
y,
width,
height,
east,
south,
horizontalCenter,
verticalCenter,
baseline};
for (
Spring s :
allSprings) {
if (
s != null) {
s.
setValue(
Spring.
UNSET);
}
}
}
}
private static class
SpringProxy extends
Spring {
private
String edgeName;
private
Component c;
private
SpringLayout l;
public
SpringProxy(
String edgeName,
Component c,
SpringLayout l) {
this.
edgeName =
edgeName;
this.
c =
c;
this.
l =
l;
}
private
Spring getConstraint() {
return
l.
getConstraints(
c).
getConstraint(
edgeName);
}
public int
getMinimumValue() {
return
getConstraint().
getMinimumValue();
}
public int
getPreferredValue() {
return
getConstraint().
getPreferredValue();
}
public int
getMaximumValue() {
return
getConstraint().
getMaximumValue();
}
public int
getValue() {
return
getConstraint().
getValue();
}
public void
setValue(int
size) {
getConstraint().
setValue(
size);
}
/*pp*/ boolean
isCyclic(
SpringLayout l) {
return
l.
isCyclic(
getConstraint());
}
public
String toString() {
return "SpringProxy for " +
edgeName + " edge of " +
c.
getName() + ".";
}
}
/**
* Constructs a new <code>SpringLayout</code>.
*/
public
SpringLayout() {}
private void
resetCyclicStatuses() {
cyclicSprings = new
HashSet<
Spring>();
acyclicSprings = new
HashSet<
Spring>();
}
private void
setParent(
Container p) {
resetCyclicStatuses();
Constraints pc =
getConstraints(
p);
pc.
setX(
Spring.
constant(0));
pc.
setY(
Spring.
constant(0));
// The applyDefaults() method automatically adds width and
// height springs that delegate their calculations to the
// getMinimumSize(), getPreferredSize() and getMaximumSize()
// methods of the relevant component. In the case of the
// parent this will cause an infinite loop since these
// methods, in turn, delegate their calculations to the
// layout manager. Check for this case and replace the
// the springs that would cause this problem with a
// constant springs that supply default values.
Spring width =
pc.
getWidth();
if (
width instanceof
Spring.
WidthSpring && ((
Spring.
WidthSpring)
width).
c ==
p) {
pc.
setWidth(
Spring.
constant(0, 0,
Integer.
MAX_VALUE));
}
Spring height =
pc.
getHeight();
if (
height instanceof
Spring.
HeightSpring && ((
Spring.
HeightSpring)
height).
c ==
p) {
pc.
setHeight(
Spring.
constant(0, 0,
Integer.
MAX_VALUE));
}
}
/*pp*/ boolean
isCyclic(
Spring s) {
if (
s == null) {
return false;
}
if (
cyclicSprings.
contains(
s)) {
return true;
}
if (
acyclicSprings.
contains(
s)) {
return false;
}
cyclicSprings.
add(
s);
boolean
result =
s.
isCyclic(this);
if (!
result) {
acyclicSprings.
add(
s);
cyclicSprings.
remove(
s);
}
else {
System.
err.
println(
s + " is cyclic. ");
}
return
result;
}
private
Spring abandonCycles(
Spring s) {
return
isCyclic(
s) ?
cyclicReference :
s;
}
// LayoutManager methods.
/**
* Has no effect,
* since this layout manager does not
* use a per-component string.
*/
public void
addLayoutComponent(
String name,
Component c) {}
/**
* Removes the constraints associated with the specified component.
*
* @param c the component being removed from the container
*/
public void
removeLayoutComponent(
Component c) {
componentConstraints.
remove(
c);
}
private static
Dimension addInsets(int
width, int
height,
Container p) {
Insets i =
p.
getInsets();
return new
Dimension(
width +
i.
left +
i.
right,
height +
i.
top +
i.
bottom);
}
public
Dimension minimumLayoutSize(
Container parent) {
setParent(
parent);
Constraints pc =
getConstraints(
parent);
return
addInsets(
abandonCycles(
pc.
getWidth()).
getMinimumValue(),
abandonCycles(
pc.
getHeight()).
getMinimumValue(),
parent);
}
public
Dimension preferredLayoutSize(
Container parent) {
setParent(
parent);
Constraints pc =
getConstraints(
parent);
return
addInsets(
abandonCycles(
pc.
getWidth()).
getPreferredValue(),
abandonCycles(
pc.
getHeight()).
getPreferredValue(),
parent);
}
// LayoutManager2 methods.
public
Dimension maximumLayoutSize(
Container parent) {
setParent(
parent);
Constraints pc =
getConstraints(
parent);
return
addInsets(
abandonCycles(
pc.
getWidth()).
getMaximumValue(),
abandonCycles(
pc.
getHeight()).
getMaximumValue(),
parent);
}
/**
* If <code>constraints</code> is an instance of
* <code>SpringLayout.Constraints</code>,
* associates the constraints with the specified component.
* <p>
* @param component the component being added
* @param constraints the component's constraints
*
* @see SpringLayout.Constraints
*/
public void
addLayoutComponent(
Component component,
Object constraints) {
if (
constraints instanceof
Constraints) {
putConstraints(
component, (
Constraints)
constraints);
}
}
/**
* Returns 0.5f (centered).
*/
public float
getLayoutAlignmentX(
Container p) {
return 0.5f;
}
/**
* Returns 0.5f (centered).
*/
public float
getLayoutAlignmentY(
Container p) {
return 0.5f;
}
public void
invalidateLayout(
Container p) {}
// End of LayoutManger2 methods
/**
* Links edge <code>e1</code> of component <code>c1</code> to
* edge <code>e2</code> of component <code>c2</code>,
* with a fixed distance between the edges. This
* constraint will cause the assignment
* <pre>
* value(e1, c1) = value(e2, c2) + pad</pre>
* to take place during all subsequent layout operations.
* <p>
* @param e1 the edge of the dependent
* @param c1 the component of the dependent
* @param pad the fixed distance between dependent and anchor
* @param e2 the edge of the anchor
* @param c2 the component of the anchor
*
* @see #putConstraint(String, Component, Spring, String, Component)
*/
public void
putConstraint(
String e1,
Component c1, int
pad,
String e2,
Component c2) {
putConstraint(
e1,
c1,
Spring.
constant(
pad),
e2,
c2);
}
/**
* Links edge <code>e1</code> of component <code>c1</code> to
* edge <code>e2</code> of component <code>c2</code>. As edge
* <code>(e2, c2)</code> changes value, edge <code>(e1, c1)</code> will
* be calculated by taking the (spring) sum of <code>(e2, c2)</code>
* and <code>s</code>.
* Each edge must have one of the following values:
* <code>SpringLayout.NORTH</code>,
* <code>SpringLayout.SOUTH</code>,
* <code>SpringLayout.EAST</code>,
* <code>SpringLayout.WEST</code>,
* <code>SpringLayout.VERTICAL_CENTER</code>,
* <code>SpringLayout.HORIZONTAL_CENTER</code> or
* <code>SpringLayout.BASELINE</code>.
* <p>
* @param e1 the edge of the dependent
* @param c1 the component of the dependent
* @param s the spring linking dependent and anchor
* @param e2 the edge of the anchor
* @param c2 the component of the anchor
*
* @see #putConstraint(String, Component, int, String, Component)
* @see #NORTH
* @see #SOUTH
* @see #EAST
* @see #WEST
* @see #VERTICAL_CENTER
* @see #HORIZONTAL_CENTER
* @see #BASELINE
*/
public void
putConstraint(
String e1,
Component c1,
Spring s,
String e2,
Component c2) {
putConstraint(
e1,
c1,
Spring.
sum(
s,
getConstraint(
e2,
c2)));
}
private void
putConstraint(
String e,
Component c,
Spring s) {
if (
s != null) {
getConstraints(
c).
setConstraint(
e,
s);
}
}
private
Constraints applyDefaults(
Component c,
Constraints constraints) {
if (
constraints == null) {
constraints = new
Constraints();
}
if (
constraints.
c == null) {
constraints.
c =
c;
}
if (
constraints.
horizontalHistory.
size() < 2) {
applyDefaults(
constraints,
WEST,
Spring.
constant(0),
WIDTH,
Spring.
width(
c),
constraints.
horizontalHistory);
}
if (
constraints.
verticalHistory.
size() < 2) {
applyDefaults(
constraints,
NORTH,
Spring.
constant(0),
HEIGHT,
Spring.
height(
c),
constraints.
verticalHistory);
}
return
constraints;
}
private void
applyDefaults(
Constraints constraints,
String name1,
Spring spring1,
String name2,
Spring spring2,
List<
String>
history) {
if (
history.
size() == 0) {
constraints.
setConstraint(
name1,
spring1);
constraints.
setConstraint(
name2,
spring2);
} else {
// At this point there must be exactly one constraint defined already.
// Check width/height first.
if (
constraints.
getConstraint(
name2) == null) {
constraints.
setConstraint(
name2,
spring2);
} else {
// If width/height is already defined, install a default for x/y.
constraints.
setConstraint(
name1,
spring1);
}
// Either way, leave the user's constraint topmost on the stack.
Collections.
rotate(
history, 1);
}
}
private void
putConstraints(
Component component,
Constraints constraints) {
componentConstraints.
put(
component,
applyDefaults(
component,
constraints));
}
/**
* Returns the constraints for the specified component.
* Note that,
* unlike the <code>GridBagLayout</code>
* <code>getConstraints</code> method,
* this method does not clone constraints.
* If no constraints
* have been associated with this component,
* this method
* returns a default constraints object positioned at
* 0,0 relative to the parent's Insets and its width/height
* constrained to the minimum, maximum, and preferred sizes of the
* component. The size characteristics
* are not frozen at the time this method is called;
* instead this method returns a constraints object
* whose characteristics track the characteristics
* of the component as they change.
*
* @param c the component whose constraints will be returned
*
* @return the constraints for the specified component
*/
public
Constraints getConstraints(
Component c) {
Constraints result =
componentConstraints.
get(
c);
if (
result == null) {
if (
c instanceof javax.swing.
JComponent) {
Object cp = ((javax.swing.
JComponent)
c).
getClientProperty(
SpringLayout.class);
if (
cp instanceof
Constraints) {
return
applyDefaults(
c, (
Constraints)
cp);
}
}
result = new
Constraints();
putConstraints(
c,
result);
}
return
result;
}
/**
* Returns the spring controlling the distance between
* the specified edge of
* the component and the top or left edge of its parent. This
* method, instead of returning the current binding for the
* edge, returns a proxy that tracks the characteristics
* of the edge even if the edge is subsequently rebound.
* Proxies are intended to be used in builder environments
* where it is useful to allow the user to define the
* constraints for a layout in any order. Proxies do, however,
* provide the means to create cyclic dependencies amongst
* the constraints of a layout. Such cycles are detected
* internally by <code>SpringLayout</code> so that
* the layout operation always terminates.
*
* @param edgeName must be one of
* <code>SpringLayout.NORTH</code>,
* <code>SpringLayout.SOUTH</code>,
* <code>SpringLayout.EAST</code>,
* <code>SpringLayout.WEST</code>,
* <code>SpringLayout.VERTICAL_CENTER</code>,
* <code>SpringLayout.HORIZONTAL_CENTER</code> or
* <code>SpringLayout.BASELINE</code>
* @param c the component whose edge spring is desired
*
* @return a proxy for the spring controlling the distance between the
* specified edge and the top or left edge of its parent
*
* @see #NORTH
* @see #SOUTH
* @see #EAST
* @see #WEST
* @see #VERTICAL_CENTER
* @see #HORIZONTAL_CENTER
* @see #BASELINE
*/
public
Spring getConstraint(
String edgeName,
Component c) {
// The interning here is unnecessary; it was added for efficiency.
edgeName =
edgeName.
intern();
return new
SpringProxy(
edgeName,
c, this);
}
public void
layoutContainer(
Container parent) {
setParent(
parent);
int
n =
parent.
getComponentCount();
getConstraints(
parent).
reset();
for (int
i = 0 ;
i <
n ;
i++) {
getConstraints(
parent.
getComponent(
i)).
reset();
}
Insets insets =
parent.
getInsets();
Constraints pc =
getConstraints(
parent);
abandonCycles(
pc.
getX()).
setValue(0);
abandonCycles(
pc.
getY()).
setValue(0);
abandonCycles(
pc.
getWidth()).
setValue(
parent.
getWidth() -
insets.
left -
insets.
right);
abandonCycles(
pc.
getHeight()).
setValue(
parent.
getHeight() -
insets.
top -
insets.
bottom);
for (int
i = 0 ;
i <
n ;
i++) {
Component c =
parent.
getComponent(
i);
Constraints cc =
getConstraints(
c);
int
x =
abandonCycles(
cc.
getX()).
getValue();
int
y =
abandonCycles(
cc.
getY()).
getValue();
int
width =
abandonCycles(
cc.
getWidth()).
getValue();
int
height =
abandonCycles(
cc.
getHeight()).
getValue();
c.
setBounds(
insets.
left +
x,
insets.
top +
y,
width,
height);
}
}
}