0001 /*
0002 * Copyright 2001-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing;
0026
0027 import java.awt.Component;
0028 import java.awt.Container;
0029 import java.awt.Dimension;
0030 import java.awt.FontMetrics;
0031 import java.awt.Insets;
0032 import java.awt.LayoutManager2;
0033 import java.awt.Rectangle;
0034 import java.util.*;
0035
0036 /**
0037 * A <code>SpringLayout</code> lays out the children of its associated container
0038 * according to a set of constraints.
0039 * See <a href="http://java.sun.com/docs/books/tutorial/uiswing/layout/spring.html">How to Use SpringLayout</a>
0040 * in <em>The Java Tutorial</em> for examples of using
0041 * <code>SpringLayout</code>.
0042 *
0043 * <p>
0044 * Each constraint,
0045 * represented by a <code>Spring</code> object,
0046 * controls the vertical or horizontal distance
0047 * between two component edges.
0048 * The edges can belong to
0049 * any child of the container,
0050 * or to the container itself.
0051 * For example,
0052 * the allowable width of a component
0053 * can be expressed using a constraint
0054 * that controls the distance between the west (left) and east (right)
0055 * edges of the component.
0056 * The allowable <em>y</em> coordinates for a component
0057 * can be expressed by constraining the distance between
0058 * the north (top) edge of the component
0059 * and the north edge of its container.
0060 *
0061 * <P>
0062 * Every child of a <code>SpringLayout</code>-controlled container,
0063 * as well as the container itself,
0064 * has exactly one set of constraints
0065 * associated with it.
0066 * These constraints are represented by
0067 * a <code>SpringLayout.Constraints</code> object.
0068 * By default,
0069 * <code>SpringLayout</code> creates constraints
0070 * that make their associated component
0071 * have the minimum, preferred, and maximum sizes
0072 * returned by the component's
0073 * {@link java.awt.Component#getMinimumSize},
0074 * {@link java.awt.Component#getPreferredSize}, and
0075 * {@link java.awt.Component#getMaximumSize}
0076 * methods. The <em>x</em> and <em>y</em> positions are initially not
0077 * constrained, so that until you constrain them the <code>Component</code>
0078 * will be positioned at 0,0 relative to the <code>Insets</code> of the
0079 * parent <code>Container</code>.
0080 *
0081 * <p>
0082 * You can change
0083 * a component's constraints in several ways.
0084 * You can
0085 * use one of the
0086 * {@link #putConstraint putConstraint}
0087 * methods
0088 * to establish a spring
0089 * linking the edges of two components within the same container.
0090 * Or you can get the appropriate <code>SpringLayout.Constraints</code>
0091 * object using
0092 * {@link #getConstraints getConstraints}
0093 * and then modify one or more of its springs.
0094 * Or you can get the spring for a particular edge of a component
0095 * using {@link #getConstraint getConstraint},
0096 * and modify it.
0097 * You can also associate
0098 * your own <code>SpringLayout.Constraints</code> object
0099 * with a component by specifying the constraints object
0100 * when you add the component to its container
0101 * (using
0102 * {@link Container#add(Component, Object)}).
0103 *
0104 * <p>
0105 * The <code>Spring</code> object representing each constraint
0106 * has a minimum, preferred, maximum, and current value.
0107 * The current value of the spring
0108 * is somewhere between the minimum and maximum values,
0109 * according to the formula given in the
0110 * {@link Spring#sum} method description.
0111 * When the minimum, preferred, and maximum values are the same,
0112 * the current value is always equal to them;
0113 * this inflexible spring is called a <em>strut</em>.
0114 * You can create struts using the factory method
0115 * {@link Spring#constant(int)}.
0116 * The <code>Spring</code> class also provides factory methods
0117 * for creating other kinds of springs,
0118 * including springs that depend on other springs.
0119 *
0120 * <p>
0121 * In a <code>SpringLayout</code>, the position of each edge is dependent on
0122 * the position of just one other edge. If a constraint is subsequently added
0123 * to create a new binding for an edge, the previous binding is discarded
0124 * and the edge remains dependent on a single edge.
0125 * Springs should only be attached
0126 * between edges of the container and its immediate children; the behavior
0127 * of the <code>SpringLayout</code> when presented with constraints linking
0128 * the edges of components from different containers (either internal or
0129 * external) is undefined.
0130 *
0131 * <h3>
0132 * SpringLayout vs. Other Layout Managers
0133 * </h3>
0134 *
0135 * <blockquote>
0136 * <hr>
0137 * <strong>Note:</strong>
0138 * Unlike many layout managers,
0139 * <code>SpringLayout</code> doesn't automatically set the location of
0140 * the components it manages.
0141 * If you hand-code a GUI that uses <code>SpringLayout</code>,
0142 * remember to initialize component locations by constraining the west/east
0143 * and north/south locations.
0144 * <p>
0145 * Depending on the constraints you use,
0146 * you may also need to set the size of the container explicitly.
0147 * <hr>
0148 * </blockquote>
0149 *
0150 * <p>
0151 * Despite the simplicity of <code>SpringLayout</code>,
0152 * it can emulate the behavior of most other layout managers.
0153 * For some features,
0154 * such as the line breaking provided by <code>FlowLayout</code>,
0155 * you'll need to
0156 * create a special-purpose subclass of the <code>Spring</code> class.
0157 *
0158 * <p>
0159 * <code>SpringLayout</code> also provides a way to solve
0160 * many of the difficult layout
0161 * problems that cannot be solved by nesting combinations
0162 * of <code>Box</code>es. That said, <code>SpringLayout</code> honors the
0163 * <code>LayoutManager2</code> contract correctly and so can be nested with
0164 * other layout managers -- a technique that can be preferable to
0165 * creating the constraints implied by the other layout managers.
0166 * <p>
0167 * The asymptotic complexity of the layout operation of a <code>SpringLayout</code>
0168 * is linear in the number of constraints (and/or components).
0169 * <p>
0170 * <strong>Warning:</strong>
0171 * Serialized objects of this class will not be compatible with
0172 * future Swing releases. The current serialization support is
0173 * appropriate for short term storage or RMI between applications running
0174 * the same version of Swing. As of 1.4, support for long term storage
0175 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0176 * has been added to the <code>java.beans</code> package.
0177 * Please see {@link java.beans.XMLEncoder}.
0178 *
0179 * @see Spring
0180 * @see SpringLayout.Constraints
0181 *
0182 * @version 1.32 05/05/07
0183 * @author Philip Milne
0184 * @author Scott Violet
0185 * @author Joe Winchester
0186 * @since 1.4
0187 */
0188 public class SpringLayout implements LayoutManager2 {
0189 private Map componentConstraints = new HashMap();
0190
0191 private Spring cyclicReference = Spring.constant(Spring.UNSET);
0192 private Set cyclicSprings;
0193 private Set acyclicSprings;
0194
0195 /**
0196 * Specifies the top edge of a component's bounding rectangle.
0197 */
0198 public static final String NORTH = "North";
0199
0200 /**
0201 * Specifies the bottom edge of a component's bounding rectangle.
0202 */
0203 public static final String SOUTH = "South";
0204
0205 /**
0206 * Specifies the right edge of a component's bounding rectangle.
0207 */
0208 public static final String EAST = "East";
0209
0210 /**
0211 * Specifies the left edge of a component's bounding rectangle.
0212 */
0213 public static final String WEST = "West";
0214
0215 /**
0216 * Specifies the horizontal center of a component's bounding rectangle.
0217 *
0218 * @since 1.6
0219 */
0220 public static final String HORIZONTAL_CENTER = "HorizontalCenter";
0221
0222 /**
0223 * Specifies the vertical center of a component's bounding rectangle.
0224 *
0225 * @since 1.6
0226 */
0227 public static final String VERTICAL_CENTER = "VerticalCenter";
0228
0229 /**
0230 * Specifies the baseline of a component.
0231 *
0232 * @since 1.6
0233 */
0234 public static final String BASELINE = "Baseline";
0235
0236 /**
0237 * Specifies the width of a component's bounding rectangle.
0238 *
0239 * @since 1.6
0240 */
0241 public static final String WIDTH = "Width";
0242
0243 /**
0244 * Specifies the height of a component's bounding rectangle.
0245 *
0246 * @since 1.6
0247 */
0248 public static final String HEIGHT = "Height";
0249
0250 private static String[] ALL_HORIZONTAL = { WEST, WIDTH, EAST,
0251 HORIZONTAL_CENTER };
0252
0253 private static String[] ALL_VERTICAL = { NORTH, HEIGHT, SOUTH,
0254 VERTICAL_CENTER, BASELINE };
0255
0256 /**
0257 * A <code>Constraints</code> object holds the
0258 * constraints that govern the way a component's size and position
0259 * change in a container controlled by a <code>SpringLayout</code>.
0260 * A <code>Constraints</code> object is
0261 * like a <code>Rectangle</code>, in that it
0262 * has <code>x</code>, <code>y</code>,
0263 * <code>width</code>, and <code>height</code> properties.
0264 * In the <code>Constraints</code> object, however,
0265 * these properties have
0266 * <code>Spring</code> values instead of integers.
0267 * In addition,
0268 * a <code>Constraints</code> object
0269 * can be manipulated as four edges
0270 * -- north, south, east, and west --
0271 * using the <code>constraint</code> property.
0272 *
0273 * <p>
0274 * The following formulas are always true
0275 * for a <code>Constraints</code> object (here WEST and <code>x</code> are synonyms, as are and NORTH and <code>y</code>):
0276 *
0277 * <pre>
0278 * EAST = WEST + WIDTH
0279 * SOUTH = NORTH + HEIGHT
0280 * HORIZONTAL_CENTER = WEST + WIDTH/2
0281 * VERTICAL_CENTER = NORTH + HEIGHT/2
0282 * ABSOLUTE_BASELINE = NORTH + RELATIVE_BASELINE*
0283 * </pre>
0284 * <p>
0285 * For example, if you have specified the WIDTH and WEST (X) location
0286 * the EAST is calculated as WEST + WIDTH. If you instead specified
0287 * the WIDTH and EAST locations the WEST (X) location is then calculated
0288 * as EAST - WIDTH.
0289 * <p>
0290 * [RELATIVE_BASELINE is a private constraint that is set automatically when
0291 * the SpringLayout.Constraints(Component) constuctor is called or when
0292 * a constraints object is registered with a SpringLayout object.]
0293 * <p>
0294 * <b>Note</b>: In this document,
0295 * operators represent methods
0296 * in the <code>Spring</code> class.
0297 * For example, "a + b" is equal to
0298 * <code>Spring.sum(a, b)</code>,
0299 * and "a - b" is equal to
0300 * <code>Spring.sum(a, Spring.minus(b))</code>.
0301 * See the
0302 * {@link Spring <code>Spring</code> API documentation}
0303 * for further details
0304 * of spring arithmetic.
0305 *
0306 * <p>
0307 *
0308 * Because a <code>Constraints</code> object's properties --
0309 * representing its edges, size, and location -- can all be set
0310 * independently and yet are interrelated,
0311 * a <code>Constraints</code> object can become <em>over-constrained</em>.
0312 * For example, if the <code>WEST</code>, <code>WIDTH</code> and
0313 * <code>EAST</code> edges are all set, steps must be taken to ensure that
0314 * the first of the formulas above holds. To do this, the
0315 * <code>Constraints</code>
0316 * object throws away the <em>least recently set</em>
0317 * constraint so as to make the formulas hold.
0318 * @since 1.4
0319 */
0320 public static class Constraints {
0321 private Spring x;
0322 private Spring y;
0323 private Spring width;
0324 private Spring height;
0325 private Spring east;
0326 private Spring south;
0327 private Spring horizontalCenter;
0328 private Spring verticalCenter;
0329 private Spring baseline;
0330
0331 private List<String> horizontalHistory = new ArrayList<String>(
0332 2);
0333 private List<String> verticalHistory = new ArrayList<String>(2);
0334
0335 // Used for baseline calculations
0336 private Component c;
0337
0338 /**
0339 * Creates an empty <code>Constraints</code> object.
0340 */
0341 public Constraints() {
0342 }
0343
0344 /**
0345 * Creates a <code>Constraints</code> object with the
0346 * specified values for its
0347 * <code>x</code> and <code>y</code> properties.
0348 * The <code>height</code> and <code>width</code> springs
0349 * have <code>null</code> values.
0350 *
0351 * @param x the spring controlling the component's <em>x</em> value
0352 * @param y the spring controlling the component's <em>y</em> value
0353 */
0354 public Constraints(Spring x, Spring y) {
0355 setX(x);
0356 setY(y);
0357 }
0358
0359 /**
0360 * Creates a <code>Constraints</code> object with the
0361 * specified values for its
0362 * <code>x</code>, <code>y</code>, <code>width</code>,
0363 * and <code>height</code> properties.
0364 * Note: If the <code>SpringLayout</code> class
0365 * encounters <code>null</code> values in the
0366 * <code>Constraints</code> object of a given component,
0367 * it replaces them with suitable defaults.
0368 *
0369 * @param x the spring value for the <code>x</code> property
0370 * @param y the spring value for the <code>y</code> property
0371 * @param width the spring value for the <code>width</code> property
0372 * @param height the spring value for the <code>height</code> property
0373 */
0374 public Constraints(Spring x, Spring y, Spring width,
0375 Spring height) {
0376 setX(x);
0377 setY(y);
0378 setWidth(width);
0379 setHeight(height);
0380 }
0381
0382 /**
0383 * Creates a <code>Constraints</code> object with
0384 * suitable <code>x</code>, <code>y</code>, <code>width</code> and
0385 * <code>height</code> springs for component, <code>c</code>.
0386 * The <code>x</code> and <code>y</code> springs are constant
0387 * springs initialised with the component's location at
0388 * the time this method is called. The <code>width</code> and
0389 * <code>height</code> springs are special springs, created by
0390 * the <code>Spring.width()</code> and <code>Spring.height()</code>
0391 * methods, which track the size characteristics of the component
0392 * when they change.
0393 *
0394 * @param c the component whose characteristics will be reflected by this Constraints object
0395 * @throws NullPointerException if <code>c</code> is null.
0396 * @since 1.5
0397 */
0398 public Constraints(Component c) {
0399 this .c = c;
0400 setX(Spring.constant(c.getX()));
0401 setY(Spring.constant(c.getY()));
0402 setWidth(Spring.width(c));
0403 setHeight(Spring.height(c));
0404 }
0405
0406 private void pushConstraint(String name, Spring value,
0407 boolean horizontal) {
0408 boolean valid = true;
0409 List<String> history = horizontal ? horizontalHistory
0410 : verticalHistory;
0411 if (history.contains(name)) {
0412 history.remove(name);
0413 valid = false;
0414 } else if (history.size() == 2 && value != null) {
0415 history.remove(0);
0416 valid = false;
0417 }
0418 if (value != null) {
0419 history.add(name);
0420 }
0421 if (!valid) {
0422 String[] all = horizontal ? ALL_HORIZONTAL
0423 : ALL_VERTICAL;
0424 for (int i = 0; i < all.length; i++) {
0425 String s = all[i];
0426 if (!history.contains(s)) {
0427 setConstraint(s, null);
0428 }
0429 }
0430 }
0431 }
0432
0433 private Spring sum(Spring s1, Spring s2) {
0434 return (s1 == null || s2 == null) ? null : Spring.sum(s1,
0435 s2);
0436 }
0437
0438 private Spring difference(Spring s1, Spring s2) {
0439 return (s1 == null || s2 == null) ? null : Spring
0440 .difference(s1, s2);
0441 }
0442
0443 private Spring scale(Spring s, float factor) {
0444 return (s == null) ? null : Spring.scale(s, factor);
0445 }
0446
0447 private int getBaselineFromHeight(int height) {
0448 if (height < 0) {
0449 // Bad Scott, Bad Scott!
0450 return -c.getBaseline(c.getPreferredSize().width,
0451 -height);
0452 }
0453 return c.getBaseline(c.getPreferredSize().width, height);
0454 }
0455
0456 private int getHeightFromBaseLine(int baseline) {
0457 Dimension prefSize = c.getPreferredSize();
0458 int prefHeight = prefSize.height;
0459 int prefBaseline = c
0460 .getBaseline(prefSize.width, prefHeight);
0461 if (prefBaseline == baseline) {
0462 // If prefBaseline < 0, then no baseline, assume preferred
0463 // height.
0464 // If prefBaseline == baseline, then specified baseline
0465 // matches preferred baseline, return preferred height
0466 return prefHeight;
0467 }
0468 // Valid baseline
0469 switch (c.getBaselineResizeBehavior()) {
0470 case CONSTANT_DESCENT:
0471 return prefHeight + (baseline - prefBaseline);
0472 case CENTER_OFFSET:
0473 return prefHeight + 2 * (baseline - prefBaseline);
0474 case CONSTANT_ASCENT:
0475 // Component baseline and specified baseline will NEVER
0476 // match, fall through to default
0477 default: // OTHER
0478 // No way to map from baseline to height.
0479 }
0480 return Integer.MIN_VALUE;
0481 }
0482
0483 private Spring heightToRelativeBaseline(Spring s) {
0484 return new Spring.SpringMap(s) {
0485 protected int map(int i) {
0486 return getBaselineFromHeight(i);
0487 }
0488
0489 protected int inv(int i) {
0490 return getHeightFromBaseLine(i);
0491 }
0492 };
0493 }
0494
0495 private Spring relativeBaselineToHeight(Spring s) {
0496 return new Spring.SpringMap(s) {
0497 protected int map(int i) {
0498 return getHeightFromBaseLine(i);
0499 }
0500
0501 protected int inv(int i) {
0502 return getBaselineFromHeight(i);
0503 }
0504 };
0505 }
0506
0507 private boolean defined(List history, String s1, String s2) {
0508 return history.contains(s1) && history.contains(s2);
0509 }
0510
0511 /**
0512 * Sets the <code>x</code> property,
0513 * which controls the <code>x</code> value
0514 * of a component's location.
0515 *
0516 * @param x the spring controlling the <code>x</code> value
0517 * of a component's location
0518 *
0519 * @see #getX
0520 * @see SpringLayout.Constraints
0521 */
0522 public void setX(Spring x) {
0523 this .x = x;
0524 pushConstraint(WEST, x, true);
0525 }
0526
0527 /**
0528 * Returns the value of the <code>x</code> property.
0529 *
0530 * @return the spring controlling the <code>x</code> value
0531 * of a component's location
0532 *
0533 * @see #setX
0534 * @see SpringLayout.Constraints
0535 */
0536 public Spring getX() {
0537 if (x == null) {
0538 if (defined(horizontalHistory, EAST, WIDTH)) {
0539 x = difference(east, width);
0540 } else if (defined(horizontalHistory,
0541 HORIZONTAL_CENTER, WIDTH)) {
0542 x = difference(horizontalCenter, scale(width, 0.5f));
0543 } else if (defined(horizontalHistory,
0544 HORIZONTAL_CENTER, EAST)) {
0545 x = difference(scale(horizontalCenter, 2f), east);
0546 }
0547 }
0548 return x;
0549 }
0550
0551 /**
0552 * Sets the <code>y</code> property,
0553 * which controls the <code>y</code> value
0554 * of a component's location.
0555 *
0556 * @param y the spring controlling the <code>y</code> value
0557 * of a component's location
0558 *
0559 * @see #getY
0560 * @see SpringLayout.Constraints
0561 */
0562 public void setY(Spring y) {
0563 this .y = y;
0564 pushConstraint(NORTH, y, false);
0565 }
0566
0567 /**
0568 * Returns the value of the <code>y</code> property.
0569 *
0570 * @return the spring controlling the <code>y</code> value
0571 * of a component's location
0572 *
0573 * @see #setY
0574 * @see SpringLayout.Constraints
0575 */
0576 public Spring getY() {
0577 if (y == null) {
0578 if (defined(verticalHistory, SOUTH, HEIGHT)) {
0579 y = difference(south, height);
0580 } else if (defined(verticalHistory, VERTICAL_CENTER,
0581 HEIGHT)) {
0582 y = difference(verticalCenter, scale(height, 0.5f));
0583 } else if (defined(verticalHistory, VERTICAL_CENTER,
0584 SOUTH)) {
0585 y = difference(scale(verticalCenter, 2f), south);
0586 } else if (defined(verticalHistory, BASELINE, HEIGHT)) {
0587 y = difference(baseline,
0588 heightToRelativeBaseline(height));
0589 } else if (defined(verticalHistory, BASELINE, SOUTH)) {
0590 y = scale(difference(baseline,
0591 heightToRelativeBaseline(south)), 2f);
0592 /*
0593 } else if (defined(verticalHistory, BASELINE, VERTICAL_CENTER)) {
0594 y = scale(difference(baseline, heightToRelativeBaseline(scale(verticalCenter, 2))), 1f/(1-2*0.5f));
0595 */
0596 }
0597 }
0598 return y;
0599 }
0600
0601 /**
0602 * Sets the <code>width</code> property,
0603 * which controls the width of a component.
0604 *
0605 * @param width the spring controlling the width of this
0606 * <code>Constraints</code> object
0607 *
0608 * @see #getWidth
0609 * @see SpringLayout.Constraints
0610 */
0611 public void setWidth(Spring width) {
0612 this .width = width;
0613 pushConstraint(WIDTH, width, true);
0614 }
0615
0616 /**
0617 * Returns the value of the <code>width</code> property.
0618 *
0619 * @return the spring controlling the width of a component
0620 *
0621 * @see #setWidth
0622 * @see SpringLayout.Constraints
0623 */
0624 public Spring getWidth() {
0625 if (width == null) {
0626 if (horizontalHistory.contains(EAST)) {
0627 width = difference(east, getX());
0628 } else if (horizontalHistory
0629 .contains(HORIZONTAL_CENTER)) {
0630 width = scale(difference(horizontalCenter, getX()),
0631 2f);
0632 }
0633 }
0634 return width;
0635 }
0636
0637 /**
0638 * Sets the <code>height</code> property,
0639 * which controls the height of a component.
0640 *
0641 * @param height the spring controlling the height of this <code>Constraints</code>
0642 * object
0643 *
0644 * @see #getHeight
0645 * @see SpringLayout.Constraints
0646 */
0647 public void setHeight(Spring height) {
0648 this .height = height;
0649 pushConstraint(HEIGHT, height, false);
0650 }
0651
0652 /**
0653 * Returns the value of the <code>height</code> property.
0654 *
0655 * @return the spring controlling the height of a component
0656 *
0657 * @see #setHeight
0658 * @see SpringLayout.Constraints
0659 */
0660 public Spring getHeight() {
0661 if (height == null) {
0662 if (verticalHistory.contains(SOUTH)) {
0663 height = difference(south, getY());
0664 } else if (verticalHistory.contains(VERTICAL_CENTER)) {
0665 height = scale(difference(verticalCenter, getY()),
0666 2f);
0667 } else if (verticalHistory.contains(BASELINE)) {
0668 height = relativeBaselineToHeight(difference(
0669 baseline, getY()));
0670 }
0671 }
0672 return height;
0673 }
0674
0675 private void setEast(Spring east) {
0676 this .east = east;
0677 pushConstraint(EAST, east, true);
0678 }
0679
0680 private Spring getEast() {
0681 if (east == null) {
0682 east = sum(getX(), getWidth());
0683 }
0684 return east;
0685 }
0686
0687 private void setSouth(Spring south) {
0688 this .south = south;
0689 pushConstraint(SOUTH, south, false);
0690 }
0691
0692 private Spring getSouth() {
0693 if (south == null) {
0694 south = sum(getY(), getHeight());
0695 }
0696 return south;
0697 }
0698
0699 private Spring getHorizontalCenter() {
0700 if (horizontalCenter == null) {
0701 horizontalCenter = sum(getX(), scale(getWidth(), 0.5f));
0702 }
0703 return horizontalCenter;
0704 }
0705
0706 private void setHorizontalCenter(Spring horizontalCenter) {
0707 this .horizontalCenter = horizontalCenter;
0708 pushConstraint(HORIZONTAL_CENTER, horizontalCenter, true);
0709 }
0710
0711 private Spring getVerticalCenter() {
0712 if (verticalCenter == null) {
0713 verticalCenter = sum(getY(), scale(getHeight(), 0.5f));
0714 }
0715 return verticalCenter;
0716 }
0717
0718 private void setVerticalCenter(Spring verticalCenter) {
0719 this .verticalCenter = verticalCenter;
0720 pushConstraint(VERTICAL_CENTER, verticalCenter, false);
0721 }
0722
0723 private Spring getBaseline() {
0724 if (baseline == null) {
0725 baseline = sum(getY(),
0726 heightToRelativeBaseline(getHeight()));
0727 }
0728 return baseline;
0729 }
0730
0731 private void setBaseline(Spring baseline) {
0732 this .baseline = baseline;
0733 pushConstraint(BASELINE, baseline, false);
0734 }
0735
0736 /**
0737 * Sets the spring controlling the specified edge.
0738 * The edge must have one of the following values:
0739 * <code>SpringLayout.NORTH</code>,
0740 * <code>SpringLayout.SOUTH</code>,
0741 * <code>SpringLayout.EAST</code>,
0742 * <code>SpringLayout.WEST</code>,
0743 * <code>SpringLayout.HORIZONTAL_CENTER</code>,
0744 * <code>SpringLayout.VERTICAL_CENTER</code>,
0745 * <code>SpringLayout.BASELINE</code>,
0746 * <code>SpringLayout.WIDTH</code> or
0747 * <code>SpringLayout.HEIGHT</code>.
0748 * For any other <code>String</code> value passed as the edge,
0749 * no action is taken. For a <code>null</code> edge, a
0750 * <code>NullPointerException</code> is thrown.
0751 *
0752 * @param edgeName the edge to be set
0753 * @param s the spring controlling the specified edge
0754 *
0755 * @throws NullPointerException if <code>edgeName</code> is <code>null</code>
0756 *
0757 * @see #getConstraint
0758 * @see #NORTH
0759 * @see #SOUTH
0760 * @see #EAST
0761 * @see #WEST
0762 * @see #HORIZONTAL_CENTER
0763 * @see #VERTICAL_CENTER
0764 * @see #BASELINE
0765 * @see #WIDTH
0766 * @see #HEIGHT
0767 * @see SpringLayout.Constraints
0768 */
0769 public void setConstraint(String edgeName, Spring s) {
0770 edgeName = edgeName.intern();
0771 if (edgeName == WEST) {
0772 setX(s);
0773 } else if (edgeName == NORTH) {
0774 setY(s);
0775 } else if (edgeName == EAST) {
0776 setEast(s);
0777 } else if (edgeName == SOUTH) {
0778 setSouth(s);
0779 } else if (edgeName == HORIZONTAL_CENTER) {
0780 setHorizontalCenter(s);
0781 } else if (edgeName == WIDTH) {
0782 setWidth(s);
0783 } else if (edgeName == HEIGHT) {
0784 setHeight(s);
0785 } else if (edgeName == VERTICAL_CENTER) {
0786 setVerticalCenter(s);
0787 } else if (edgeName == BASELINE) {
0788 setBaseline(s);
0789 }
0790 }
0791
0792 /**
0793 * Returns the value of the specified edge, which may be
0794 * a derived value, or even <code>null</code>.
0795 * The edge must have one of the following values:
0796 * <code>SpringLayout.NORTH</code>,
0797 * <code>SpringLayout.SOUTH</code>,
0798 * <code>SpringLayout.EAST</code>,
0799 * <code>SpringLayout.WEST</code>,
0800 * <code>SpringLayout.HORIZONTAL_CENTER</code>,
0801 * <code>SpringLayout.VERTICAL_CENTER</code>,
0802 * <code>SpringLayout.BASELINE</code>,
0803 * <code>SpringLayout.WIDTH</code> or
0804 * <code>SpringLayout.HEIGHT</code>.
0805 * For any other <code>String</code> value passed as the edge,
0806 * <code>null</code> will be returned. Throws
0807 * <code>NullPointerException</code> for a <code>null</code> edge.
0808 *
0809 * @param edgeName the edge whose value
0810 * is to be returned
0811 *
0812 * @return the spring controlling the specified edge, may be <code>null</code>
0813 *
0814 * @throws NullPointerException if <code>edgeName</code> is <code>null</code>
0815 *
0816 * @see #setConstraint
0817 * @see #NORTH
0818 * @see #SOUTH
0819 * @see #EAST
0820 * @see #WEST
0821 * @see #HORIZONTAL_CENTER
0822 * @see #VERTICAL_CENTER
0823 * @see #BASELINE
0824 * @see #WIDTH
0825 * @see #HEIGHT
0826 * @see SpringLayout.Constraints
0827 */
0828 public Spring getConstraint(String edgeName) {
0829 edgeName = edgeName.intern();
0830 return (edgeName == WEST) ? getX()
0831 : (edgeName == NORTH) ? getY()
0832 : (edgeName == EAST) ? getEast()
0833 : (edgeName == SOUTH) ? getSouth()
0834 : (edgeName == WIDTH) ? getWidth()
0835 : (edgeName == HEIGHT) ? getHeight()
0836 : (edgeName == HORIZONTAL_CENTER) ? getHorizontalCenter()
0837 : (edgeName == VERTICAL_CENTER) ? getVerticalCenter()
0838 : (edgeName == BASELINE) ? getBaseline()
0839 : null;
0840 }
0841
0842 /*pp*/void reset() {
0843 Spring[] allSprings = { x, y, width, height, east, south,
0844 horizontalCenter, verticalCenter, baseline };
0845 for (int i = 0; i < allSprings.length; i++) {
0846 Spring s = allSprings[i];
0847 if (s != null) {
0848 s.setValue(Spring.UNSET);
0849 }
0850 }
0851 }
0852 }
0853
0854 private static class SpringProxy extends Spring {
0855 private String edgeName;
0856 private Component c;
0857 private SpringLayout l;
0858
0859 public SpringProxy(String edgeName, Component c, SpringLayout l) {
0860 this .edgeName = edgeName;
0861 this .c = c;
0862 this .l = l;
0863 }
0864
0865 private Spring getConstraint() {
0866 return l.getConstraints(c).getConstraint(edgeName);
0867 }
0868
0869 public int getMinimumValue() {
0870 return getConstraint().getMinimumValue();
0871 }
0872
0873 public int getPreferredValue() {
0874 return getConstraint().getPreferredValue();
0875 }
0876
0877 public int getMaximumValue() {
0878 return getConstraint().getMaximumValue();
0879 }
0880
0881 public int getValue() {
0882 return getConstraint().getValue();
0883 }
0884
0885 public void setValue(int size) {
0886 getConstraint().setValue(size);
0887 }
0888
0889 /*pp*/boolean isCyclic(SpringLayout l) {
0890 return l.isCyclic(getConstraint());
0891 }
0892
0893 public String toString() {
0894 return "SpringProxy for " + edgeName + " edge of "
0895 + c.getName() + ".";
0896 }
0897 }
0898
0899 /**
0900 * Constructs a new <code>SpringLayout</code>.
0901 */
0902 public SpringLayout() {
0903 }
0904
0905 private void resetCyclicStatuses() {
0906 cyclicSprings = new HashSet();
0907 acyclicSprings = new HashSet();
0908 }
0909
0910 private void setParent(Container p) {
0911 resetCyclicStatuses();
0912 Constraints pc = getConstraints(p);
0913
0914 pc.setX(Spring.constant(0));
0915 pc.setY(Spring.constant(0));
0916 // The applyDefaults() method automatically adds width and
0917 // height springs that delegate their calculations to the
0918 // getMinimumSize(), getPreferredSize() and getMaximumSize()
0919 // methods of the relevant component. In the case of the
0920 // parent this will cause an infinite loop since these
0921 // methods, in turn, delegate their calculations to the
0922 // layout manager. Check for this case and replace the
0923 // the springs that would cause this problem with a
0924 // constant springs that supply default values.
0925 Spring width = pc.getWidth();
0926 if (width instanceof Spring.WidthSpring
0927 && ((Spring.WidthSpring) width).c == p) {
0928 pc.setWidth(Spring.constant(0, 0, Integer.MAX_VALUE));
0929 }
0930 Spring height = pc.getHeight();
0931 if (height instanceof Spring.HeightSpring
0932 && ((Spring.HeightSpring) height).c == p) {
0933 pc.setHeight(Spring.constant(0, 0, Integer.MAX_VALUE));
0934 }
0935 }
0936
0937 /*pp*/boolean isCyclic(Spring s) {
0938 if (s == null) {
0939 return false;
0940 }
0941 if (cyclicSprings.contains(s)) {
0942 return true;
0943 }
0944 if (acyclicSprings.contains(s)) {
0945 return false;
0946 }
0947 cyclicSprings.add(s);
0948 boolean result = s.isCyclic(this );
0949 if (!result) {
0950 acyclicSprings.add(s);
0951 cyclicSprings.remove(s);
0952 } else {
0953 System.err.println(s + " is cyclic. ");
0954 }
0955 return result;
0956 }
0957
0958 private Spring abandonCycles(Spring s) {
0959 return isCyclic(s) ? cyclicReference : s;
0960 }
0961
0962 // LayoutManager methods.
0963
0964 /**
0965 * Has no effect,
0966 * since this layout manager does not
0967 * use a per-component string.
0968 */
0969 public void addLayoutComponent(String name, Component c) {
0970 }
0971
0972 /**
0973 * Removes the constraints associated with the specified component.
0974 *
0975 * @param c the component being removed from the container
0976 */
0977 public void removeLayoutComponent(Component c) {
0978 componentConstraints.remove(c);
0979 }
0980
0981 private static Dimension addInsets(int width, int height,
0982 Container p) {
0983 Insets i = p.getInsets();
0984 return new Dimension(width + i.left + i.right, height + i.top
0985 + i.bottom);
0986 }
0987
0988 public Dimension minimumLayoutSize(Container parent) {
0989 setParent(parent);
0990 Constraints pc = getConstraints(parent);
0991 return addInsets(
0992 abandonCycles(pc.getWidth()).getMinimumValue(),
0993 abandonCycles(pc.getHeight()).getMinimumValue(), parent);
0994 }
0995
0996 public Dimension preferredLayoutSize(Container parent) {
0997 setParent(parent);
0998 Constraints pc = getConstraints(parent);
0999 return addInsets(abandonCycles(pc.getWidth())
1000 .getPreferredValue(), abandonCycles(pc.getHeight())
1001 .getPreferredValue(), parent);
1002 }
1003
1004 // LayoutManager2 methods.
1005
1006 public Dimension maximumLayoutSize(Container parent) {
1007 setParent(parent);
1008 Constraints pc = getConstraints(parent);
1009 return addInsets(
1010 abandonCycles(pc.getWidth()).getMaximumValue(),
1011 abandonCycles(pc.getHeight()).getMaximumValue(), parent);
1012 }
1013
1014 /**
1015 * If <code>constraints</code> is an instance of
1016 * <code>SpringLayout.Constraints</code>,
1017 * associates the constraints with the specified component.
1018 * <p>
1019 * @param component the component being added
1020 * @param constraints the component's constraints
1021 *
1022 * @see SpringLayout.Constraints
1023 */
1024 public void addLayoutComponent(Component component,
1025 Object constraints) {
1026 if (constraints instanceof Constraints) {
1027 putConstraints(component, (Constraints) constraints);
1028 }
1029 }
1030
1031 /**
1032 * Returns 0.5f (centered).
1033 */
1034 public float getLayoutAlignmentX(Container p) {
1035 return 0.5f;
1036 }
1037
1038 /**
1039 * Returns 0.5f (centered).
1040 */
1041 public float getLayoutAlignmentY(Container p) {
1042 return 0.5f;
1043 }
1044
1045 public void invalidateLayout(Container p) {
1046 }
1047
1048 // End of LayoutManger2 methods
1049
1050 /**
1051 * Links edge <code>e1</code> of component <code>c1</code> to
1052 * edge <code>e2</code> of component <code>c2</code>,
1053 * with a fixed distance between the edges. This
1054 * constraint will cause the assignment
1055 * <pre>
1056 * value(e1, c1) = value(e2, c2) + pad</pre>
1057 * to take place during all subsequent layout operations.
1058 * <p>
1059 * @param e1 the edge of the dependent
1060 * @param c1 the component of the dependent
1061 * @param pad the fixed distance between dependent and anchor
1062 * @param e2 the edge of the anchor
1063 * @param c2 the component of the anchor
1064 *
1065 * @see #putConstraint(String, Component, Spring, String, Component)
1066 */
1067 public void putConstraint(String e1, Component c1, int pad,
1068 String e2, Component c2) {
1069 putConstraint(e1, c1, Spring.constant(pad), e2, c2);
1070 }
1071
1072 /**
1073 * Links edge <code>e1</code> of component <code>c1</code> to
1074 * edge <code>e2</code> of component <code>c2</code>. As edge
1075 * <code>(e2, c2)</code> changes value, edge <code>(e1, c1)</code> will
1076 * be calculated by taking the (spring) sum of <code>(e2, c2)</code>
1077 * and <code>s</code>.
1078 * Each edge must have one of the following values:
1079 * <code>SpringLayout.NORTH</code>,
1080 * <code>SpringLayout.SOUTH</code>,
1081 * <code>SpringLayout.EAST</code>,
1082 * <code>SpringLayout.WEST</code>,
1083 * <code>SpringLayout.VERTICAL_CENTER</code>,
1084 * <code>SpringLayout.HORIZONTAL_CENTER</code> or
1085 * <code>SpringLayout.BASELINE</code>.
1086 * <p>
1087 * @param e1 the edge of the dependent
1088 * @param c1 the component of the dependent
1089 * @param s the spring linking dependent and anchor
1090 * @param e2 the edge of the anchor
1091 * @param c2 the component of the anchor
1092 *
1093 * @see #putConstraint(String, Component, int, String, Component)
1094 * @see #NORTH
1095 * @see #SOUTH
1096 * @see #EAST
1097 * @see #WEST
1098 * @see #VERTICAL_CENTER
1099 * @see #HORIZONTAL_CENTER
1100 * @see #BASELINE
1101 */
1102 public void putConstraint(String e1, Component c1, Spring s,
1103 String e2, Component c2) {
1104 putConstraint(e1, c1, Spring.sum(s, getConstraint(e2, c2)));
1105 }
1106
1107 private void putConstraint(String e, Component c, Spring s) {
1108 if (s != null) {
1109 getConstraints(c).setConstraint(e, s);
1110 }
1111 }
1112
1113 private Constraints applyDefaults(Component c,
1114 Constraints constraints) {
1115 if (constraints == null) {
1116 constraints = new Constraints();
1117 }
1118 if (constraints.c == null) {
1119 constraints.c = c;
1120 }
1121 if (constraints.horizontalHistory.size() < 2) {
1122 applyDefaults(constraints, WEST, Spring.constant(0), WIDTH,
1123 Spring.width(c), constraints.horizontalHistory);
1124 }
1125 if (constraints.verticalHistory.size() < 2) {
1126 applyDefaults(constraints, NORTH, Spring.constant(0),
1127 HEIGHT, Spring.height(c),
1128 constraints.verticalHistory);
1129 }
1130 return constraints;
1131 }
1132
1133 private void applyDefaults(Constraints constraints, String name1,
1134 Spring spring1, String name2, Spring spring2,
1135 List<String> history) {
1136 if (history.size() == 0) {
1137 constraints.setConstraint(name1, spring1);
1138 constraints.setConstraint(name2, spring2);
1139 } else {
1140 // At this point there must be exactly one constraint defined already.
1141 // Check width/height first.
1142 if (constraints.getConstraint(name2) == null) {
1143 constraints.setConstraint(name2, spring2);
1144 } else {
1145 // If width/height is already defined, install a default for x/y.
1146 constraints.setConstraint(name1, spring1);
1147 }
1148 // Either way, leave the user's constraint topmost on the stack.
1149 Collections.rotate(history, 1);
1150 }
1151 }
1152
1153 private void putConstraints(Component component,
1154 Constraints constraints) {
1155 componentConstraints.put(component, applyDefaults(component,
1156 constraints));
1157 }
1158
1159 /**
1160 * Returns the constraints for the specified component.
1161 * Note that,
1162 * unlike the <code>GridBagLayout</code>
1163 * <code>getConstraints</code> method,
1164 * this method does not clone constraints.
1165 * If no constraints
1166 * have been associated with this component,
1167 * this method
1168 * returns a default constraints object positioned at
1169 * 0,0 relative to the parent's Insets and its width/height
1170 * constrained to the minimum, maximum, and preferred sizes of the
1171 * component. The size characteristics
1172 * are not frozen at the time this method is called;
1173 * instead this method returns a constraints object
1174 * whose characteristics track the characteristics
1175 * of the component as they change.
1176 *
1177 * @param c the component whose constraints will be returned
1178 *
1179 * @return the constraints for the specified component
1180 */
1181 public Constraints getConstraints(Component c) {
1182 Constraints result = (Constraints) componentConstraints.get(c);
1183 if (result == null) {
1184 if (c instanceof javax.swing.JComponent) {
1185 Object cp = ((javax.swing.JComponent) c)
1186 .getClientProperty(SpringLayout.class);
1187 if (cp instanceof Constraints) {
1188 return applyDefaults(c, (Constraints) cp);
1189 }
1190 }
1191 result = new Constraints();
1192 putConstraints(c, result);
1193 }
1194 return result;
1195 }
1196
1197 /**
1198 * Returns the spring controlling the distance between
1199 * the specified edge of
1200 * the component and the top or left edge of its parent. This
1201 * method, instead of returning the current binding for the
1202 * edge, returns a proxy that tracks the characteristics
1203 * of the edge even if the edge is subsequently rebound.
1204 * Proxies are intended to be used in builder envonments
1205 * where it is useful to allow the user to define the
1206 * constraints for a layout in any order. Proxies do, however,
1207 * provide the means to create cyclic dependencies amongst
1208 * the constraints of a layout. Such cycles are detected
1209 * internally by <code>SpringLayout</code> so that
1210 * the layout operation always terminates.
1211 *
1212 * @param edgeName must be one of
1213 * <code>SpringLayout.NORTH</code>,
1214 * <code>SpringLayout.SOUTH</code>,
1215 * <code>SpringLayout.EAST</code>,
1216 * <code>SpringLayout.WEST</code>,
1217 * <code>SpringLayout.VERTICAL_CENTER</code>,
1218 * <code>SpringLayout.HORIZONTAL_CENTER</code> or
1219 * <code>SpringLayout.BASELINE</code>
1220 * @param c the component whose edge spring is desired
1221 *
1222 * @return a proxy for the spring controlling the distance between the
1223 * specified edge and the top or left edge of its parent
1224 *
1225 * @see #NORTH
1226 * @see #SOUTH
1227 * @see #EAST
1228 * @see #WEST
1229 * @see #VERTICAL_CENTER
1230 * @see #HORIZONTAL_CENTER
1231 * @see #BASELINE
1232 */
1233 public Spring getConstraint(String edgeName, Component c) {
1234 // The interning here is unnecessary; it was added for efficiency.
1235 edgeName = edgeName.intern();
1236 return new SpringProxy(edgeName, c, this );
1237 }
1238
1239 public void layoutContainer(Container parent) {
1240 setParent(parent);
1241
1242 int n = parent.getComponentCount();
1243 getConstraints(parent).reset();
1244 for (int i = 0; i < n; i++) {
1245 getConstraints(parent.getComponent(i)).reset();
1246 }
1247
1248 Insets insets = parent.getInsets();
1249 Constraints pc = getConstraints(parent);
1250 abandonCycles(pc.getX()).setValue(0);
1251 abandonCycles(pc.getY()).setValue(0);
1252 abandonCycles(pc.getWidth()).setValue(
1253 parent.getWidth() - insets.left - insets.right);
1254 abandonCycles(pc.getHeight()).setValue(
1255 parent.getHeight() - insets.top - insets.bottom);
1256
1257 for (int i = 0; i < n; i++) {
1258 Component c = parent.getComponent(i);
1259 Constraints cc = getConstraints(c);
1260 int x = abandonCycles(cc.getX()).getValue();
1261 int y = abandonCycles(cc.getY()).getValue();
1262 int width = abandonCycles(cc.getWidth()).getValue();
1263 int height = abandonCycles(cc.getHeight()).getValue();
1264 c.setBounds(insets.left + x, insets.top + y, width, height);
1265 }
1266 }
1267 }
|