/*
* Copyright (c) 2001, 2006, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.util.*;
A SpringLayout
lays out the children of its associated container
according to a set of constraints.
See How to Use SpringLayout
in The Java Tutorial for examples of using
SpringLayout
.
Each constraint,
represented by a Spring
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 y 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.
Every child of a SpringLayout
-controlled container,
as well as the container itself,
has exactly one set of constraints
associated with it.
These constraints are represented by
a SpringLayout.Constraints
object.
By default,
SpringLayout
creates constraints that make their associated component have the minimum, preferred, and maximum sizes returned by the component's Component.getMinimumSize
, Component.getPreferredSize
, and Component.getMaximumSize
methods. The x and y positions are initially not
constrained, so that until you constrain them the Component
will be positioned at 0,0 relative to the Insets
of the
parent Container
.
You can change a component's constraints in several ways. You can use one of the putConstraint
methods to establish a spring linking the edges of two components within the same container. Or you can get the appropriate SpringLayout.Constraints
object using getConstraints
and then modify one or more of its springs. Or you can get the spring for a particular edge of a component using getConstraint
, and modify it. You can also associate your own SpringLayout.Constraints
object with a component by specifying the constraints object when you add the component to its container (using Container.add(Component, Object)
).
The Spring
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 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 strut. You can create struts using the factory method Spring.constant(int)
. The Spring
class also provides factory methods
for creating other kinds of springs,
including springs that depend on other springs.
In a SpringLayout
, 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 SpringLayout
when presented with constraints linking
the edges of components from different containers (either internal or
external) is undefined.
SpringLayout vs. Other Layout Managers
Note:
Unlike many layout managers,
SpringLayout
doesn't automatically set the location of
the components it manages.
If you hand-code a GUI that uses SpringLayout
,
remember to initialize component locations by constraining the west/east
and north/south locations.
Depending on the constraints you use,
you may also need to set the size of the container explicitly.
Despite the simplicity of SpringLayout
,
it can emulate the behavior of most other layout managers.
For some features,
such as the line breaking provided by FlowLayout
,
you'll need to
create a special-purpose subclass of the Spring
class.
SpringLayout
also provides a way to solve
many of the difficult layout
problems that cannot be solved by nesting combinations
of Box
es. That said, SpringLayout
honors the
LayoutManager2
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.
The asymptotic complexity of the layout operation of a SpringLayout
is linear in the number of constraints (and/or components).
Warning:
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 JavaBeansTM
has been added to the java.beans
package. Please see XMLEncoder
.
Author: Philip Milne, Scott Violet, Joe Winchester See Also: Since: 1.4
/**
* A <code>SpringLayout</code> lays out the children of its associated container
* according to a set of constraints.
* See <a href="http://java.sun.com/docs/books/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<sup><font size="-2">TM</font></sup>
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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
/**
* 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
/**
* 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
/**
* 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
/**
* 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
/**
* 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 Constraints
object holds the
constraints that govern the way a component's size and position
change in a container controlled by a SpringLayout
.
A Constraints
object is
like a Rectangle
, in that it
has x
, y
,
width
, and height
properties.
In the Constraints
object, however,
these properties have
Spring
values instead of integers.
In addition,
a Constraints
object
can be manipulated as four edges
-- north, south, east, and west --
using the constraint
property.
The following formulas are always true
for a Constraints
object (here WEST and x
are synonyms, as are and NORTH and y
):
EAST = WEST + WIDTH
SOUTH = NORTH + HEIGHT
HORIZONTAL_CENTER = WEST + WIDTH/2
VERTICAL_CENTER = NORTH + HEIGHT/2
ABSOLUTE_BASELINE = NORTH + RELATIVE_BASELINE*
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.
[RELATIVE_BASELINE is a private constraint that is set automatically when
the SpringLayout.Constraints(Component) constuctor is called or when
a constraints object is registered with a SpringLayout object.]
Note: In this document,
operators represent methods
in the Spring
class.
For example, "a + b" is equal to
Spring.sum(a, b)
,
and "a - b" is equal to
Spring.sum(a, Spring.minus(b))
. See the Spring
API documentation
for further details of spring arithmetic.
Because a Constraints
object's properties --
representing its edges, size, and location -- can all be set
independently and yet are interrelated,
a Constraints
object can become over-constrained.
For example, if the WEST
, WIDTH
and
EAST
edges are all set, steps must be taken to ensure that
the first of the formulas above holds. To do this, the
Constraints
object throws away the least recently set
constraint so as to make the formulas hold.
Since: 1.4
/**
* 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) constuctor 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 <code>Spring</code> 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 Constraints
object.
/**
* Creates an empty <code>Constraints</code> object.
*/
public Constraints() {
}
Creates a Constraints
object with the
specified values for its
x
and y
properties.
The height
and width
springs
have null
values.
Params: - x – the spring controlling the component's x value
- y – the spring controlling the component's y value
/**
* 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 Constraints
object with the
specified values for its
x
, y
, width
,
and height
properties.
Note: If the SpringLayout
class
encounters null
values in the
Constraints
object of a given component,
it replaces them with suitable defaults.
Params: - x – the spring value for the
x
property - y – the spring value for the
y
property - width – the spring value for the
width
property - height – the spring value for the
height
property
/**
* 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 Constraints
object with
suitable x
, y
, width
and
height
springs for component, c
.
The x
and y
springs are constant
springs initialised with the component's location at
the time this method is called. The width
and
height
springs are special springs, created by
the Spring.width()
and Spring.height()
methods, which track the size characteristics of the component
when they change.
Params: - c – the component whose characteristics will be reflected by this Constraints object
Throws: - NullPointerException – if
c
is null.
Since: 1.5
/**
* 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 x
property,
which controls the x
value
of a component's location.
Params: - x – the spring controlling the
x
value
of a component's location
See Also:
/**
* 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 x
property.
See Also: Returns: the spring controlling the x
value
of a component's location
/**
* 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 y
property,
which controls the y
value
of a component's location.
Params: - y – the spring controlling the
y
value
of a component's location
See Also:
/**
* 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 y
property.
See Also: Returns: the spring controlling the y
value
of a component's location
/**
* 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 width
property,
which controls the width of a component.
Params: - width – the spring controlling the width of this
Constraints
object
See Also:
/**
* 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 width
property.
See Also: Returns: the spring controlling the width of a component
/**
* 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 height
property,
which controls the height of a component.
Params: - height – the spring controlling the height of this
Constraints
object
See Also:
/**
* 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 height
property.
See Also: Returns: the spring controlling the height of a component
/**
* 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:
SpringLayout.NORTH
,
SpringLayout.SOUTH
,
SpringLayout.EAST
,
SpringLayout.WEST
,
SpringLayout.HORIZONTAL_CENTER
,
SpringLayout.VERTICAL_CENTER
,
SpringLayout.BASELINE
,
SpringLayout.WIDTH
or
SpringLayout.HEIGHT
.
For any other String
value passed as the edge,
no action is taken. For a null
edge, a
NullPointerException
is thrown.
Params: - edgeName – the edge to be set
- s – the spring controlling the specified edge
Throws: - NullPointerException – if
edgeName
is null
See Also:
/**
* 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.
*
* @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 null
.
The edge must have one of the following values:
SpringLayout.NORTH
,
SpringLayout.SOUTH
,
SpringLayout.EAST
,
SpringLayout.WEST
,
SpringLayout.HORIZONTAL_CENTER
,
SpringLayout.VERTICAL_CENTER
,
SpringLayout.BASELINE
,
SpringLayout.WIDTH
or
SpringLayout.HEIGHT
.
For any other String
value passed as the edge,
null
will be returned. Throws
NullPointerException
for a null
edge.
Params: - edgeName – the edge whose value
is to be returned
Throws: - NullPointerException – if
edgeName
is null
See Also: Returns: the spring controlling the specified edge, may be null
/**
* 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 SpringLayout
.
/**
* 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.
/**
* 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.
Params: - c – the component being removed from the container
/**
* 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 constraints
is an instance of
SpringLayout.Constraints
,
associates the constraints with the specified component.
Params: - component – the component being added
- constraints – the component's constraints
See Also:
/**
* 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).
/**
* Returns 0.5f (centered).
*/
public float getLayoutAlignmentX(Container p) {
return 0.5f;
}
Returns 0.5f (centered).
/**
* Returns 0.5f (centered).
*/
public float getLayoutAlignmentY(Container p) {
return 0.5f;
}
public void invalidateLayout(Container p) {}
// End of LayoutManger2 methods
Links edge e1
of component c1
to
edge e2
of component c2
,
with a fixed distance between the edges. This
constraint will cause the assignment
value(e1, c1) = value(e2, c2) + pad
to take place during all subsequent layout operations.
Params: - e1 – the edge of the dependent
- c1 – the component of the dependent
- pad – the fixed distance between dependent and anchor
- e2 – the edge of the anchor
- c2 – the component of the anchor
See Also:
/**
* 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 e1
of component c1
to
edge e2
of component c2
. As edge
(e2, c2)
changes value, edge (e1, c1)
will
be calculated by taking the (spring) sum of (e2, c2)
and s
.
Each edge must have one of the following values:
SpringLayout.NORTH
,
SpringLayout.SOUTH
,
SpringLayout.EAST
,
SpringLayout.WEST
,
SpringLayout.VERTICAL_CENTER
,
SpringLayout.HORIZONTAL_CENTER
or
SpringLayout.BASELINE
.
Params: - e1 – the edge of the dependent
- c1 – the component of the dependent
- s – the spring linking dependent and anchor
- e2 – the edge of the anchor
- c2 – the component of the anchor
See Also:
/**
* 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 GridBagLayout
getConstraints
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.
Params: - c – the component whose constraints will be returned
Returns: the constraints for the specified component
/**
* 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 envonments
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 SpringLayout
so that
the layout operation always terminates.
Params: - edgeName – must be one of
SpringLayout.NORTH
,
SpringLayout.SOUTH
,
SpringLayout.EAST
,
SpringLayout.WEST
,
SpringLayout.VERTICAL_CENTER
,
SpringLayout.HORIZONTAL_CENTER
or
SpringLayout.BASELINE
- c – the component whose edge spring is desired
See Also: Returns: a proxy for the spring controlling the distance between the
specified edge and the top or left edge of its parent
/**
* 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 envonments
* 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);
}
}
}