1 /*
   2  * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing;
  26 
  27 import java.awt.Component;
  28 import java.awt.Container;
  29 import java.awt.Dimension;
  30 import java.awt.FontMetrics;
  31 import java.awt.Insets;
  32 import java.awt.LayoutManager2;
  33 import java.awt.Rectangle;
  34 import java.util.*;
  35 
  36 /**
  37  * A <code>SpringLayout</code> lays out the children of its associated container
  38  * according to a set of constraints.
  39  * See <a href="http://java.sun.com/docs/books/tutorial/uiswing/layout/spring.html">How to Use SpringLayout</a>
  40  * in <em>The Java Tutorial</em> for examples of using
  41  * <code>SpringLayout</code>.
  42  *
  43  * <p>
  44  * Each constraint,
  45  * represented by a <code>Spring</code> object,
  46  * controls the vertical or horizontal distance
  47  * between two component edges.
  48  * The edges can belong to
  49  * any child of the container,
  50  * or to the container itself.
  51  * For example,
  52  * the allowable width of a component
  53  * can be expressed using a constraint
  54  * that controls the distance between the west (left) and east (right)
  55  * edges of the component.
  56  * The allowable <em>y</em> coordinates for a component
  57  * can be expressed by constraining the distance between
  58  * the north (top) edge of the component
  59  * and the north edge of its container.
  60  *
  61  * <P>
  62  * Every child of a <code>SpringLayout</code>-controlled container,
  63  * as well as the container itself,
  64  * has exactly one set of constraints
  65  * associated with it.
  66  * These constraints are represented by
  67  * a <code>SpringLayout.Constraints</code> object.
  68  * By default,
  69  * <code>SpringLayout</code> creates constraints
  70  * that make their associated component
  71  * have the minimum, preferred, and maximum sizes
  72  * returned by the component's
  73  * {@link java.awt.Component#getMinimumSize},
  74  * {@link java.awt.Component#getPreferredSize}, and
  75  * {@link java.awt.Component#getMaximumSize}
  76  * methods. The <em>x</em> and <em>y</em> positions are initially not
  77  * constrained, so that until you constrain them the <code>Component</code>
  78  * will be positioned at 0,0 relative to the <code>Insets</code> of the
  79  * parent <code>Container</code>.
  80  *
  81  * <p>
  82  * You can change
  83  * a component's constraints in several ways.
  84  * You can
  85  * use one of the
  86  * {@link #putConstraint putConstraint}
  87  * methods
  88  * to establish a spring
  89  * linking the edges of two components within the same container.
  90  * Or you can get the appropriate <code>SpringLayout.Constraints</code>
  91  * object using
  92  * {@link #getConstraints getConstraints}
  93  * and then modify one or more of its springs.
  94  * Or you can get the spring for a particular edge of a component
  95  * using {@link #getConstraint getConstraint},
  96  * and modify it.
  97  * You can also associate
  98  * your own <code>SpringLayout.Constraints</code> object
  99  * with a component by specifying the constraints object
 100  * when you add the component to its container
 101  * (using
 102  * {@link Container#add(Component, Object)}).
 103  *
 104  * <p>
 105  * The <code>Spring</code> object representing each constraint
 106  * has a minimum, preferred, maximum, and current value.
 107  * The current value of the spring
 108  * is somewhere between the minimum and maximum values,
 109  * according to the formula given in the
 110  * {@link Spring#sum} method description.
 111  * When the minimum, preferred, and maximum values are the same,
 112  * the current value is always equal to them;
 113  * this inflexible spring is called a <em>strut</em>.
 114  * You can create struts using the factory method
 115  * {@link Spring#constant(int)}.
 116  * The <code>Spring</code> class also provides factory methods
 117  * for creating other kinds of springs,
 118  * including springs that depend on other springs.
 119  *
 120  * <p>
 121  * In a <code>SpringLayout</code>, the position of each edge is dependent on
 122  * the position of just one other edge. If a constraint is subsequently added
 123  * to create a new binding for an edge, the previous binding is discarded
 124  * and the edge remains dependent on a single edge.
 125  * Springs should only be attached
 126  * between edges of the container and its immediate children; the behavior
 127  * of the <code>SpringLayout</code> when presented with constraints linking
 128  * the edges of components from different containers (either internal or
 129  * external) is undefined.
 130  *
 131  * <h3>
 132  * SpringLayout vs. Other Layout Managers
 133  * </h3>
 134  *
 135  * <blockquote>
 136  * <hr>
 137  * <strong>Note:</strong>
 138  * Unlike many layout managers,
 139  * <code>SpringLayout</code> doesn't automatically set the location of
 140  * the components it manages.
 141  * If you hand-code a GUI that uses <code>SpringLayout</code>,
 142  * remember to initialize component locations by constraining the west/east
 143  * and north/south locations.
 144  * <p>
 145  * Depending on the constraints you use,
 146  * you may also need to set the size of the container explicitly.
 147  * <hr>
 148  * </blockquote>
 149  *
 150  * <p>
 151  * Despite the simplicity of <code>SpringLayout</code>,
 152  * it can emulate the behavior of most other layout managers.
 153  * For some features,
 154  * such as the line breaking provided by <code>FlowLayout</code>,
 155  * you'll need to
 156  * create a special-purpose subclass of the <code>Spring</code> class.
 157  *
 158  * <p>
 159  * <code>SpringLayout</code> also provides a way to solve
 160  * many of the difficult layout
 161  * problems that cannot be solved by nesting combinations
 162  * of <code>Box</code>es. That said, <code>SpringLayout</code> honors the
 163  * <code>LayoutManager2</code> contract correctly and so can be nested with
 164  * other layout managers -- a technique that can be preferable to
 165  * creating the constraints implied by the other layout managers.
 166  * <p>
 167  * The asymptotic complexity of the layout operation of a <code>SpringLayout</code>
 168  * is linear in the number of constraints (and/or components).
 169  * <p>
 170  * <strong>Warning:</strong>
 171  * Serialized objects of this class will not be compatible with
 172  * future Swing releases. The current serialization support is
 173  * appropriate for short term storage or RMI between applications running
 174  * the same version of Swing.  As of 1.4, support for long term storage
 175  * of all JavaBeans<sup><font size="-2">TM</font></sup>
 176  * has been added to the <code>java.beans</code> package.
 177  * Please see {@link java.beans.XMLEncoder}.
 178  *
 179  * @see Spring
 180  * @see SpringLayout.Constraints
 181  *
 182  * @author      Philip Milne
 183  * @author      Scott Violet
 184  * @author      Joe Winchester
 185  * @since       1.4
 186  */
 187 public class SpringLayout implements LayoutManager2 {
 188     private Map<Component, Constraints> componentConstraints = new HashMap<Component, Constraints>();
 189 
 190     private Spring cyclicReference = Spring.constant(Spring.UNSET);
 191     private Set<Spring> cyclicSprings;
 192     private Set<Spring> acyclicSprings;
 193 
 194 
 195     /**
 196      * Specifies the top edge of a component's bounding rectangle.
 197      */
 198     public static final String NORTH  = "North";
 199 
 200     /**
 201      * Specifies the bottom edge of a component's bounding rectangle.
 202      */
 203     public static final String SOUTH  = "South";
 204 
 205     /**
 206      * Specifies the right edge of a component's bounding rectangle.
 207      */
 208     public static final String EAST   = "East";
 209 
 210     /**
 211      * Specifies the left edge of a component's bounding rectangle.
 212      */
 213     public static final String WEST   = "West";
 214 
 215     /**
 216      * Specifies the horizontal center of a component's bounding rectangle.
 217      *
 218      * @since 1.6
 219      */
 220     public static final String HORIZONTAL_CENTER   = "HorizontalCenter";
 221 
 222     /**
 223      * Specifies the vertical center of a component's bounding rectangle.
 224      *
 225      * @since 1.6
 226      */
 227     public static final String VERTICAL_CENTER   = "VerticalCenter";
 228 
 229     /**
 230      * Specifies the baseline of a component.
 231      *
 232      * @since 1.6
 233      */
 234     public static final String BASELINE   = "Baseline";
 235 
 236     /**
 237      * Specifies the width of a component's bounding rectangle.
 238      *
 239      * @since 1.6
 240      */
 241     public static final String WIDTH = "Width";
 242 
 243     /**
 244      * Specifies the height of a component's bounding rectangle.
 245      *
 246      * @since 1.6
 247      */
 248     public static final String HEIGHT = "Height";
 249 
 250     private static String[] ALL_HORIZONTAL = {WEST, WIDTH, EAST, HORIZONTAL_CENTER};
 251 
 252     private static String[] ALL_VERTICAL = {NORTH, HEIGHT, SOUTH, VERTICAL_CENTER, BASELINE};
 253 
 254     /**
 255      * A <code>Constraints</code> object holds the
 256      * constraints that govern the way a component's size and position
 257      * change in a container controlled by a <code>SpringLayout</code>.
 258      * A <code>Constraints</code> object is
 259      * like a <code>Rectangle</code>, in that it
 260      * has <code>x</code>, <code>y</code>,
 261      * <code>width</code>, and <code>height</code> properties.
 262      * In the <code>Constraints</code> object, however,
 263      * these properties have
 264      * <code>Spring</code> values instead of integers.
 265      * In addition,
 266      * a <code>Constraints</code> object
 267      * can be manipulated as four edges
 268      * -- north, south, east, and west --
 269      * using the <code>constraint</code> property.
 270      *
 271      * <p>
 272      * The following formulas are always true
 273      * for a <code>Constraints</code> object (here WEST and <code>x</code> are synonyms, as are and NORTH and <code>y</code>):
 274      *
 275      * <pre>
 276      *               EAST = WEST + WIDTH
 277      *              SOUTH = NORTH + HEIGHT
 278      *  HORIZONTAL_CENTER = WEST + WIDTH/2
 279      *    VERTICAL_CENTER = NORTH + HEIGHT/2
 280      *  ABSOLUTE_BASELINE = NORTH + RELATIVE_BASELINE*
 281      * </pre>
 282      * <p>
 283      * For example, if you have specified the WIDTH and WEST (X) location
 284      * the EAST is calculated as WEST + WIDTH.  If you instead specified
 285      * the WIDTH and EAST locations the WEST (X) location is then calculated
 286      * as EAST - WIDTH.
 287      * <p>
 288      * [RELATIVE_BASELINE is a private constraint that is set automatically when
 289      * the SpringLayout.Constraints(Component) constuctor is called or when
 290      * a constraints object is registered with a SpringLayout object.]
 291      * <p>
 292      * <b>Note</b>: In this document,
 293      * operators represent methods
 294      * in the <code>Spring</code> class.
 295      * For example, "a + b" is equal to
 296      * <code>Spring.sum(a, b)</code>,
 297      * and "a - b" is equal to
 298      * <code>Spring.sum(a, Spring.minus(b))</code>.
 299      * See the
 300      * {@link Spring <code>Spring</code> API documentation}
 301      * for further details
 302      * of spring arithmetic.
 303      *
 304      * <p>
 305      *
 306      * Because a <code>Constraints</code> object's properties --
 307      * representing its edges, size, and location -- can all be set
 308      * independently and yet are interrelated,
 309      * a <code>Constraints</code> object can become <em>over-constrained</em>.
 310      * For example, if the <code>WEST</code>, <code>WIDTH</code> and
 311      * <code>EAST</code> edges are all set, steps must be taken to ensure that
 312      * the first of the formulas above holds.  To do this, the
 313      * <code>Constraints</code>
 314      * object throws away the <em>least recently set</em>
 315      * constraint so as to make the formulas hold.
 316      * @since 1.4
 317      */
 318     public static class Constraints {
 319        private Spring x;
 320        private Spring y;
 321        private Spring width;
 322        private Spring height;
 323        private Spring east;
 324        private Spring south;
 325         private Spring horizontalCenter;
 326         private Spring verticalCenter;
 327         private Spring baseline;
 328 
 329         private List<String> horizontalHistory = new ArrayList<String>(2);
 330         private List<String> verticalHistory = new ArrayList<String>(2);
 331 
 332         // Used for baseline calculations
 333         private Component c;
 334 
 335        /**
 336         * Creates an empty <code>Constraints</code> object.
 337         */
 338        public Constraints() {
 339        }
 340 
 341        /**
 342         * Creates a <code>Constraints</code> object with the
 343         * specified values for its
 344         * <code>x</code> and <code>y</code> properties.
 345         * The <code>height</code> and <code>width</code> springs
 346         * have <code>null</code> values.
 347         *
 348         * @param x  the spring controlling the component's <em>x</em> value
 349         * @param y  the spring controlling the component's <em>y</em> value
 350         */
 351        public Constraints(Spring x, Spring y) {
 352            setX(x);
 353            setY(y);
 354        }
 355 
 356        /**
 357         * Creates a <code>Constraints</code> object with the
 358         * specified values for its
 359         * <code>x</code>, <code>y</code>, <code>width</code>,
 360         * and <code>height</code> properties.
 361         * Note: If the <code>SpringLayout</code> class
 362         * encounters <code>null</code> values in the
 363         * <code>Constraints</code> object of a given component,
 364         * it replaces them with suitable defaults.
 365         *
 366         * @param x  the spring value for the <code>x</code> property
 367         * @param y  the spring value for the <code>y</code> property
 368         * @param width  the spring value for the <code>width</code> property
 369         * @param height  the spring value for the <code>height</code> property
 370         */
 371        public Constraints(Spring x, Spring y, Spring width, Spring height) {
 372            setX(x);
 373            setY(y);
 374            setWidth(width);
 375            setHeight(height);
 376        }
 377 
 378         /**
 379          * Creates a <code>Constraints</code> object with
 380          * suitable <code>x</code>, <code>y</code>, <code>width</code> and
 381          * <code>height</code> springs for component, <code>c</code>.
 382          * The <code>x</code> and <code>y</code> springs are constant
 383          * springs  initialised with the component's location at
 384          * the time this method is called. The <code>width</code> and
 385          * <code>height</code> springs are special springs, created by
 386          * the <code>Spring.width()</code> and <code>Spring.height()</code>
 387          * methods, which track the size characteristics of the component
 388          * when they change.
 389          *
 390          * @param c  the component whose characteristics will be reflected by this Constraints object
 391          * @throws NullPointerException if <code>c</code> is null.
 392          * @since 1.5
 393          */
 394         public Constraints(Component c) {
 395             this.c = c;
 396             setX(Spring.constant(c.getX()));
 397             setY(Spring.constant(c.getY()));
 398             setWidth(Spring.width(c));
 399             setHeight(Spring.height(c));
 400         }
 401 
 402         private void pushConstraint(String name, Spring value, boolean horizontal) {
 403             boolean valid = true;
 404             List<String> history = horizontal ? horizontalHistory :
 405                                                 verticalHistory;
 406             if (history.contains(name)) {
 407                 history.remove(name);
 408                 valid = false;
 409             } else if (history.size() == 2 && value != null) {
 410                 history.remove(0);
 411                 valid = false;
 412             }
 413             if (value != null) {
 414                 history.add(name);
 415             }
 416             if (!valid) {
 417                 String[] all = horizontal ? ALL_HORIZONTAL : ALL_VERTICAL;
 418                 for (String s : all) {
 419                     if (!history.contains(s)) {
 420                         setConstraint(s, null);
 421                     }
 422                 }
 423             }
 424         }
 425 
 426        private Spring sum(Spring s1, Spring s2) {
 427            return (s1 == null || s2 == null) ? null : Spring.sum(s1, s2);
 428        }
 429 
 430        private Spring difference(Spring s1, Spring s2) {
 431            return (s1 == null || s2 == null) ? null : Spring.difference(s1, s2);
 432        }
 433 
 434         private Spring scale(Spring s, float factor) {
 435             return (s == null) ? null : Spring.scale(s, factor);
 436         }
 437 
 438         private int getBaselineFromHeight(int height) {
 439             if (height < 0) {
 440                 // Bad Scott, Bad Scott!
 441                 return -c.getBaseline(c.getPreferredSize().width,
 442                                       -height);
 443             }
 444             return c.getBaseline(c.getPreferredSize().width, height);
 445         }
 446 
 447         private int getHeightFromBaseLine(int baseline) {
 448             Dimension prefSize = c.getPreferredSize();
 449             int prefHeight = prefSize.height;
 450             int prefBaseline = c.getBaseline(prefSize.width, prefHeight);
 451             if (prefBaseline == baseline) {
 452                 // If prefBaseline < 0, then no baseline, assume preferred
 453                 // height.
 454                 // If prefBaseline == baseline, then specified baseline
 455                 // matches preferred baseline, return preferred height
 456                 return prefHeight;
 457             }
 458             // Valid baseline
 459             switch(c.getBaselineResizeBehavior()) {
 460             case CONSTANT_DESCENT:
 461                 return prefHeight + (baseline - prefBaseline);
 462             case CENTER_OFFSET:
 463                 return prefHeight + 2 * (baseline - prefBaseline);
 464             case CONSTANT_ASCENT:
 465                 // Component baseline and specified baseline will NEVER
 466                 // match, fall through to default
 467             default: // OTHER
 468                 // No way to map from baseline to height.
 469             }
 470             return Integer.MIN_VALUE;
 471         }
 472 
 473          private Spring heightToRelativeBaseline(Spring s) {
 474             return new Spring.SpringMap(s) {
 475                  protected int map(int i) {
 476                     return getBaselineFromHeight(i);
 477                  }
 478 
 479                  protected int inv(int i) {
 480                      return getHeightFromBaseLine(i);
 481                  }
 482             };
 483         }
 484 
 485         private Spring relativeBaselineToHeight(Spring s) {
 486             return new Spring.SpringMap(s) {
 487                 protected int map(int i) {
 488                     return getHeightFromBaseLine(i);
 489                  }
 490 
 491                  protected int inv(int i) {
 492                     return getBaselineFromHeight(i);
 493                  }
 494             };
 495         }
 496 
 497         private boolean defined(List history, String s1, String s2) {
 498             return history.contains(s1) && history.contains(s2);
 499         }
 500 
 501        /**
 502         * Sets the <code>x</code> property,
 503         * which controls the <code>x</code> value
 504         * of a component's location.
 505         *
 506         * @param x the spring controlling the <code>x</code> value
 507         *          of a component's location
 508         *
 509         * @see #getX
 510         * @see SpringLayout.Constraints
 511         */
 512        public void setX(Spring x) {
 513            this.x = x;
 514            pushConstraint(WEST, x, true);
 515        }
 516 
 517        /**
 518         * Returns the value of the <code>x</code> property.
 519         *
 520         * @return the spring controlling the <code>x</code> value
 521         *         of a component's location
 522         *
 523         * @see #setX
 524         * @see SpringLayout.Constraints
 525         */
 526        public Spring getX() {
 527            if (x == null) {
 528                if (defined(horizontalHistory, EAST, WIDTH)) {
 529                    x = difference(east, width);
 530                } else if (defined(horizontalHistory, HORIZONTAL_CENTER, WIDTH)) {
 531                    x = difference(horizontalCenter, scale(width, 0.5f));
 532                } else if (defined(horizontalHistory, HORIZONTAL_CENTER, EAST)) {
 533                    x = difference(scale(horizontalCenter, 2f), east);
 534                }
 535            }
 536            return x;
 537        }
 538 
 539        /**
 540         * Sets the <code>y</code> property,
 541         * which controls the <code>y</code> value
 542         * of a component's location.
 543         *
 544         * @param y the spring controlling the <code>y</code> value
 545         *          of a component's location
 546         *
 547         * @see #getY
 548         * @see SpringLayout.Constraints
 549         */
 550        public void setY(Spring y) {
 551            this.y = y;
 552            pushConstraint(NORTH, y, false);
 553        }
 554 
 555        /**
 556         * Returns the value of the <code>y</code> property.
 557         *
 558         * @return the spring controlling the <code>y</code> value
 559         *         of a component's location
 560         *
 561         * @see #setY
 562         * @see SpringLayout.Constraints
 563         */
 564        public Spring getY() {
 565            if (y == null) {
 566                if (defined(verticalHistory, SOUTH, HEIGHT)) {
 567                    y = difference(south, height);
 568                } else if (defined(verticalHistory, VERTICAL_CENTER, HEIGHT)) {
 569                    y = difference(verticalCenter, scale(height, 0.5f));
 570                } else if (defined(verticalHistory, VERTICAL_CENTER, SOUTH)) {
 571                    y = difference(scale(verticalCenter, 2f), south);
 572                } else if (defined(verticalHistory, BASELINE, HEIGHT)) {
 573                    y = difference(baseline, heightToRelativeBaseline(height));
 574                } else if (defined(verticalHistory, BASELINE, SOUTH)) {
 575                    y = scale(difference(baseline, heightToRelativeBaseline(south)), 2f);
 576 /*
 577                } else if (defined(verticalHistory, BASELINE, VERTICAL_CENTER)) {
 578                    y = scale(difference(baseline, heightToRelativeBaseline(scale(verticalCenter, 2))), 1f/(1-2*0.5f));
 579 */
 580                }
 581            }
 582            return y;
 583        }
 584 
 585        /**
 586         * Sets the <code>width</code> property,
 587         * which controls the width of a component.
 588         *
 589         * @param width the spring controlling the width of this
 590         * <code>Constraints</code> object
 591         *
 592         * @see #getWidth
 593         * @see SpringLayout.Constraints
 594         */
 595        public void setWidth(Spring width) {
 596            this.width = width;
 597            pushConstraint(WIDTH, width, true);
 598        }
 599 
 600        /**
 601         * Returns the value of the <code>width</code> property.
 602         *
 603         * @return the spring controlling the width of a component
 604         *
 605         * @see #setWidth
 606         * @see SpringLayout.Constraints
 607         */
 608        public Spring getWidth() {
 609            if (width == null) {
 610                if (horizontalHistory.contains(EAST)) {
 611                    width = difference(east, getX());
 612                } else if (horizontalHistory.contains(HORIZONTAL_CENTER)) {
 613                    width = scale(difference(horizontalCenter, getX()), 2f);
 614                }
 615            }
 616            return width;
 617        }
 618 
 619        /**
 620         * Sets the <code>height</code> property,
 621         * which controls the height of a component.
 622         *
 623         * @param height the spring controlling the height of this <code>Constraints</code>
 624         * object
 625         *
 626         * @see #getHeight
 627         * @see SpringLayout.Constraints
 628         */
 629        public void setHeight(Spring height) {
 630            this.height = height;
 631            pushConstraint(HEIGHT, height, false);
 632        }
 633 
 634        /**
 635         * Returns the value of the <code>height</code> property.
 636         *
 637         * @return the spring controlling the height of a component
 638         *
 639         * @see #setHeight
 640         * @see SpringLayout.Constraints
 641         */
 642        public Spring getHeight() {
 643            if (height == null) {
 644                if (verticalHistory.contains(SOUTH)) {
 645                    height = difference(south, getY());
 646                } else if (verticalHistory.contains(VERTICAL_CENTER)) {
 647                    height = scale(difference(verticalCenter, getY()), 2f);
 648                } else if (verticalHistory.contains(BASELINE)) {
 649                    height = relativeBaselineToHeight(difference(baseline, getY()));
 650                }
 651            }
 652            return height;
 653        }
 654 
 655        private void setEast(Spring east) {
 656            this.east = east;
 657            pushConstraint(EAST, east, true);
 658        }
 659 
 660        private Spring getEast() {
 661            if (east == null) {
 662                east = sum(getX(), getWidth());
 663            }
 664            return east;
 665        }
 666 
 667        private void setSouth(Spring south) {
 668            this.south = south;
 669            pushConstraint(SOUTH, south, false);
 670        }
 671 
 672        private Spring getSouth() {
 673            if (south == null) {
 674                south = sum(getY(), getHeight());
 675            }
 676            return south;
 677        }
 678 
 679         private Spring getHorizontalCenter() {
 680             if (horizontalCenter == null) {
 681                 horizontalCenter = sum(getX(), scale(getWidth(), 0.5f));
 682             }
 683             return horizontalCenter;
 684         }
 685 
 686         private void setHorizontalCenter(Spring horizontalCenter) {
 687             this.horizontalCenter = horizontalCenter;
 688             pushConstraint(HORIZONTAL_CENTER, horizontalCenter, true);
 689         }
 690 
 691         private Spring getVerticalCenter() {
 692             if (verticalCenter == null) {
 693                 verticalCenter = sum(getY(), scale(getHeight(), 0.5f));
 694             }
 695             return verticalCenter;
 696         }
 697 
 698         private void setVerticalCenter(Spring verticalCenter) {
 699             this.verticalCenter = verticalCenter;
 700             pushConstraint(VERTICAL_CENTER, verticalCenter, false);
 701         }
 702 
 703         private Spring getBaseline() {
 704             if (baseline == null) {
 705                 baseline = sum(getY(), heightToRelativeBaseline(getHeight()));
 706             }
 707             return baseline;
 708         }
 709 
 710         private void setBaseline(Spring baseline) {
 711             this.baseline = baseline;
 712             pushConstraint(BASELINE, baseline, false);
 713         }
 714 
 715        /**
 716         * Sets the spring controlling the specified edge.
 717         * The edge must have one of the following values:
 718         * <code>SpringLayout.NORTH</code>,
 719         * <code>SpringLayout.SOUTH</code>,
 720         * <code>SpringLayout.EAST</code>,
 721         * <code>SpringLayout.WEST</code>,
 722         * <code>SpringLayout.HORIZONTAL_CENTER</code>,
 723         * <code>SpringLayout.VERTICAL_CENTER</code>,
 724         * <code>SpringLayout.BASELINE</code>,
 725         * <code>SpringLayout.WIDTH</code> or
 726         * <code>SpringLayout.HEIGHT</code>.
 727         * For any other <code>String</code> value passed as the edge,
 728         * no action is taken. For a <code>null</code> edge, a
 729         * <code>NullPointerException</code> is thrown.
 730         * <p>
 731         * <b>Note:</b> This method can affect {@code x} and {@code y} values
 732         * previously set for this {@code Constraints}.
 733         *
 734         * @param edgeName the edge to be set
 735         * @param s the spring controlling the specified edge
 736         *
 737         * @throws NullPointerException if <code>edgeName</code> is <code>null</code>
 738         *
 739         * @see #getConstraint
 740         * @see #NORTH
 741         * @see #SOUTH
 742         * @see #EAST
 743         * @see #WEST
 744         * @see #HORIZONTAL_CENTER
 745         * @see #VERTICAL_CENTER
 746         * @see #BASELINE
 747         * @see #WIDTH
 748         * @see #HEIGHT
 749         * @see SpringLayout.Constraints
 750         */
 751        public void setConstraint(String edgeName, Spring s) {
 752            edgeName = edgeName.intern();
 753            if (edgeName == WEST) {
 754                setX(s);
 755            } else if (edgeName == NORTH) {
 756                setY(s);
 757            } else if (edgeName == EAST) {
 758                setEast(s);
 759            } else if (edgeName == SOUTH) {
 760                setSouth(s);
 761            } else if (edgeName == HORIZONTAL_CENTER) {
 762                setHorizontalCenter(s);
 763            } else if (edgeName == WIDTH) {
 764                setWidth(s);
 765            } else if (edgeName == HEIGHT) {
 766                setHeight(s);
 767            } else if (edgeName == VERTICAL_CENTER) {
 768                setVerticalCenter(s);
 769            } else if (edgeName == BASELINE) {
 770                setBaseline(s);
 771            }
 772        }
 773 
 774        /**
 775         * Returns the value of the specified edge, which may be
 776         * a derived value, or even <code>null</code>.
 777         * The edge must have one of the following values:
 778         * <code>SpringLayout.NORTH</code>,
 779         * <code>SpringLayout.SOUTH</code>,
 780         * <code>SpringLayout.EAST</code>,
 781         * <code>SpringLayout.WEST</code>,
 782         * <code>SpringLayout.HORIZONTAL_CENTER</code>,
 783         * <code>SpringLayout.VERTICAL_CENTER</code>,
 784         * <code>SpringLayout.BASELINE</code>,
 785         * <code>SpringLayout.WIDTH</code> or
 786         * <code>SpringLayout.HEIGHT</code>.
 787         * For any other <code>String</code> value passed as the edge,
 788         * <code>null</code> will be returned. Throws
 789         * <code>NullPointerException</code> for a <code>null</code> edge.
 790         *
 791         * @param edgeName the edge whose value
 792         *                 is to be returned
 793         *
 794         * @return the spring controlling the specified edge, may be <code>null</code>
 795         *
 796         * @throws NullPointerException if <code>edgeName</code> is <code>null</code>
 797         *
 798         * @see #setConstraint
 799         * @see #NORTH
 800         * @see #SOUTH
 801         * @see #EAST
 802         * @see #WEST
 803         * @see #HORIZONTAL_CENTER
 804         * @see #VERTICAL_CENTER
 805         * @see #BASELINE
 806         * @see #WIDTH
 807         * @see #HEIGHT
 808         * @see SpringLayout.Constraints
 809         */
 810        public Spring getConstraint(String edgeName) {
 811            edgeName = edgeName.intern();
 812            return (edgeName == WEST)  ? getX() :
 813                    (edgeName == NORTH) ? getY() :
 814                    (edgeName == EAST)  ? getEast() :
 815                    (edgeName == SOUTH) ? getSouth() :
 816                    (edgeName == WIDTH)  ? getWidth() :
 817                    (edgeName == HEIGHT) ? getHeight() :
 818                    (edgeName == HORIZONTAL_CENTER) ? getHorizontalCenter() :
 819                    (edgeName == VERTICAL_CENTER)  ? getVerticalCenter() :
 820                    (edgeName == BASELINE) ? getBaseline() :
 821                   null;
 822        }
 823 
 824        /*pp*/ void reset() {
 825            Spring[] allSprings = {x, y, width, height, east, south,
 826                horizontalCenter, verticalCenter, baseline};
 827            for (Spring s : allSprings) {
 828                if (s != null) {
 829                    s.setValue(Spring.UNSET);
 830                }
 831            }
 832        }
 833    }
 834 
 835    private static class SpringProxy extends Spring {
 836        private String edgeName;
 837        private Component c;
 838        private SpringLayout l;
 839 
 840        public SpringProxy(String edgeName, Component c, SpringLayout l) {
 841            this.edgeName = edgeName;
 842            this.c = c;
 843            this.l = l;
 844        }
 845 
 846        private Spring getConstraint() {
 847            return l.getConstraints(c).getConstraint(edgeName);
 848        }
 849 
 850        public int getMinimumValue() {
 851            return getConstraint().getMinimumValue();
 852        }
 853 
 854        public int getPreferredValue() {
 855            return getConstraint().getPreferredValue();
 856        }
 857 
 858        public int getMaximumValue() {
 859            return getConstraint().getMaximumValue();
 860        }
 861 
 862        public int getValue() {
 863            return getConstraint().getValue();
 864        }
 865 
 866        public void setValue(int size) {
 867            getConstraint().setValue(size);
 868        }
 869 
 870        /*pp*/ boolean isCyclic(SpringLayout l) {
 871            return l.isCyclic(getConstraint());
 872        }
 873 
 874        public String toString() {
 875            return "SpringProxy for " + edgeName + " edge of " + c.getName() + ".";
 876        }
 877     }
 878 
 879     /**
 880      * Constructs a new <code>SpringLayout</code>.
 881      */
 882     public SpringLayout() {}
 883 
 884     private void resetCyclicStatuses() {
 885         cyclicSprings = new HashSet<Spring>();
 886         acyclicSprings = new HashSet<Spring>();
 887     }
 888 
 889     private void setParent(Container p) {
 890         resetCyclicStatuses();
 891         Constraints pc = getConstraints(p);
 892 
 893         pc.setX(Spring.constant(0));
 894         pc.setY(Spring.constant(0));
 895         // The applyDefaults() method automatically adds width and
 896         // height springs that delegate their calculations to the
 897         // getMinimumSize(), getPreferredSize() and getMaximumSize()
 898         // methods of the relevant component. In the case of the
 899         // parent this will cause an infinite loop since these
 900         // methods, in turn, delegate their calculations to the
 901         // layout manager. Check for this case and replace the
 902         // the springs that would cause this problem with a
 903         // constant springs that supply default values.
 904         Spring width = pc.getWidth();
 905         if (width instanceof Spring.WidthSpring && ((Spring.WidthSpring)width).c == p) {
 906             pc.setWidth(Spring.constant(0, 0, Integer.MAX_VALUE));
 907         }
 908         Spring height = pc.getHeight();
 909         if (height instanceof Spring.HeightSpring && ((Spring.HeightSpring)height).c == p) {
 910             pc.setHeight(Spring.constant(0, 0, Integer.MAX_VALUE));
 911         }
 912     }
 913 
 914     /*pp*/ boolean isCyclic(Spring s) {
 915         if (s == null) {
 916             return false;
 917         }
 918         if (cyclicSprings.contains(s)) {
 919             return true;
 920         }
 921         if (acyclicSprings.contains(s)) {
 922             return false;
 923         }
 924         cyclicSprings.add(s);
 925         boolean result = s.isCyclic(this);
 926         if (!result) {
 927             acyclicSprings.add(s);
 928             cyclicSprings.remove(s);
 929         }
 930         else {
 931             System.err.println(s + " is cyclic. ");
 932         }
 933         return result;
 934     }
 935 
 936     private Spring abandonCycles(Spring s) {
 937         return isCyclic(s) ? cyclicReference : s;
 938     }
 939 
 940     // LayoutManager methods.
 941 
 942     /**
 943      * Has no effect,
 944      * since this layout manager does not
 945      * use a per-component string.
 946      */
 947     public void addLayoutComponent(String name, Component c) {}
 948 
 949     /**
 950      * Removes the constraints associated with the specified component.
 951      *
 952      * @param c the component being removed from the container
 953      */
 954     public void removeLayoutComponent(Component c) {
 955         componentConstraints.remove(c);
 956     }
 957 
 958     private static Dimension addInsets(int width, int height, Container p) {
 959         Insets i = p.getInsets();
 960         return new Dimension(width + i.left + i.right, height + i.top + i.bottom);
 961     }
 962 
 963     public Dimension minimumLayoutSize(Container parent) {
 964         setParent(parent);
 965         Constraints pc = getConstraints(parent);
 966         return addInsets(abandonCycles(pc.getWidth()).getMinimumValue(),
 967                          abandonCycles(pc.getHeight()).getMinimumValue(),
 968                          parent);
 969     }
 970 
 971     public Dimension preferredLayoutSize(Container parent) {
 972         setParent(parent);
 973         Constraints pc = getConstraints(parent);
 974         return addInsets(abandonCycles(pc.getWidth()).getPreferredValue(),
 975                          abandonCycles(pc.getHeight()).getPreferredValue(),
 976                          parent);
 977     }
 978 
 979     // LayoutManager2 methods.
 980 
 981     public Dimension maximumLayoutSize(Container parent) {
 982         setParent(parent);
 983         Constraints pc = getConstraints(parent);
 984         return addInsets(abandonCycles(pc.getWidth()).getMaximumValue(),
 985                          abandonCycles(pc.getHeight()).getMaximumValue(),
 986                          parent);
 987     }
 988 
 989     /**
 990      * If <code>constraints</code> is an instance of
 991      * <code>SpringLayout.Constraints</code>,
 992      * associates the constraints with the specified component.
 993      * <p>
 994      * @param   component the component being added
 995      * @param   constraints the component's constraints
 996      *
 997      * @see SpringLayout.Constraints
 998      */
 999     public void addLayoutComponent(Component component, Object constraints) {
1000         if (constraints instanceof Constraints) {
1001             putConstraints(component, (Constraints)constraints);
1002         }
1003     }
1004 
1005     /**
1006      * Returns 0.5f (centered).
1007      */
1008     public float getLayoutAlignmentX(Container p) {
1009         return 0.5f;
1010     }
1011 
1012     /**
1013      * Returns 0.5f (centered).
1014      */
1015     public float getLayoutAlignmentY(Container p) {
1016         return 0.5f;
1017     }
1018 
1019     public void invalidateLayout(Container p) {}
1020 
1021     // End of LayoutManger2 methods
1022 
1023    /**
1024      * Links edge <code>e1</code> of component <code>c1</code> to
1025      * edge <code>e2</code> of component <code>c2</code>,
1026      * with a fixed distance between the edges. This
1027      * constraint will cause the assignment
1028      * <pre>
1029      *     value(e1, c1) = value(e2, c2) + pad</pre>
1030      * to take place during all subsequent layout operations.
1031      * <p>
1032      * @param   e1 the edge of the dependent
1033      * @param   c1 the component of the dependent
1034      * @param   pad the fixed distance between dependent and anchor
1035      * @param   e2 the edge of the anchor
1036      * @param   c2 the component of the anchor
1037      *
1038      * @see #putConstraint(String, Component, Spring, String, Component)
1039      */
1040     public void putConstraint(String e1, Component c1, int pad, String e2, Component c2) {
1041         putConstraint(e1, c1, Spring.constant(pad), e2, c2);
1042     }
1043 
1044     /**
1045      * Links edge <code>e1</code> of component <code>c1</code> to
1046      * edge <code>e2</code> of component <code>c2</code>. As edge
1047      * <code>(e2, c2)</code> changes value, edge <code>(e1, c1)</code> will
1048      * be calculated by taking the (spring) sum of <code>(e2, c2)</code>
1049      * and <code>s</code>.
1050      * Each edge must have one of the following values:
1051      * <code>SpringLayout.NORTH</code>,
1052      * <code>SpringLayout.SOUTH</code>,
1053      * <code>SpringLayout.EAST</code>,
1054      * <code>SpringLayout.WEST</code>,
1055      * <code>SpringLayout.VERTICAL_CENTER</code>,
1056      * <code>SpringLayout.HORIZONTAL_CENTER</code> or
1057      * <code>SpringLayout.BASELINE</code>.
1058      * <p>
1059      * @param   e1 the edge of the dependent
1060      * @param   c1 the component of the dependent
1061      * @param   s the spring linking 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, int, String, Component)
1066      * @see #NORTH
1067      * @see #SOUTH
1068      * @see #EAST
1069      * @see #WEST
1070      * @see #VERTICAL_CENTER
1071      * @see #HORIZONTAL_CENTER
1072      * @see #BASELINE
1073      */
1074     public void putConstraint(String e1, Component c1, Spring s, String e2, Component c2) {
1075         putConstraint(e1, c1, Spring.sum(s, getConstraint(e2, c2)));
1076     }
1077 
1078     private void putConstraint(String e, Component c, Spring s) {
1079         if (s != null) {
1080             getConstraints(c).setConstraint(e, s);
1081         }
1082      }
1083 
1084     private Constraints applyDefaults(Component c, Constraints constraints) {
1085         if (constraints == null) {
1086             constraints = new Constraints();
1087         }
1088         if (constraints.c == null) {
1089             constraints.c = c;
1090         }
1091         if (constraints.horizontalHistory.size() < 2) {
1092             applyDefaults(constraints, WEST, Spring.constant(0), WIDTH,
1093                           Spring.width(c), constraints.horizontalHistory);
1094         }
1095         if (constraints.verticalHistory.size() < 2) {
1096             applyDefaults(constraints, NORTH, Spring.constant(0), HEIGHT,
1097                           Spring.height(c), constraints.verticalHistory);
1098         }
1099         return constraints;
1100     }
1101 
1102     private void applyDefaults(Constraints constraints, String name1,
1103                                Spring spring1, String name2, Spring spring2,
1104                                List<String> history) {
1105         if (history.size() == 0) {
1106             constraints.setConstraint(name1, spring1);
1107             constraints.setConstraint(name2, spring2);
1108         } else {
1109             // At this point there must be exactly one constraint defined already.
1110             // Check width/height first.
1111             if (constraints.getConstraint(name2) == null) {
1112                 constraints.setConstraint(name2, spring2);
1113             } else {
1114                 // If width/height is already defined, install a default for x/y.
1115                 constraints.setConstraint(name1, spring1);
1116             }
1117             // Either way, leave the user's constraint topmost on the stack.
1118             Collections.rotate(history, 1);
1119         }
1120     }
1121 
1122     private void putConstraints(Component component, Constraints constraints) {
1123         componentConstraints.put(component, applyDefaults(component, constraints));
1124     }
1125 
1126     /**
1127      * Returns the constraints for the specified component.
1128      * Note that,
1129      * unlike the <code>GridBagLayout</code>
1130      * <code>getConstraints</code> method,
1131      * this method does not clone constraints.
1132      * If no constraints
1133      * have been associated with this component,
1134      * this method
1135      * returns a default constraints object positioned at
1136      * 0,0 relative to the parent's Insets and its width/height
1137      * constrained to the minimum, maximum, and preferred sizes of the
1138      * component. The size characteristics
1139      * are not frozen at the time this method is called;
1140      * instead this method returns a constraints object
1141      * whose characteristics track the characteristics
1142      * of the component as they change.
1143      *
1144      * @param       c the component whose constraints will be returned
1145      *
1146      * @return      the constraints for the specified component
1147      */
1148     public Constraints getConstraints(Component c) {
1149        Constraints result = componentConstraints.get(c);
1150        if (result == null) {
1151            if (c instanceof javax.swing.JComponent) {
1152                 Object cp = ((javax.swing.JComponent)c).getClientProperty(SpringLayout.class);
1153                 if (cp instanceof Constraints) {
1154                     return applyDefaults(c, (Constraints)cp);
1155                 }
1156             }
1157             result = new Constraints();
1158             putConstraints(c, result);
1159        }
1160        return result;
1161     }
1162 
1163     /**
1164      * Returns the spring controlling the distance between
1165      * the specified edge of
1166      * the component and the top or left edge of its parent. This
1167      * method, instead of returning the current binding for the
1168      * edge, returns a proxy that tracks the characteristics
1169      * of the edge even if the edge is subsequently rebound.
1170      * Proxies are intended to be used in builder envonments
1171      * where it is useful to allow the user to define the
1172      * constraints for a layout in any order. Proxies do, however,
1173      * provide the means to create cyclic dependencies amongst
1174      * the constraints of a layout. Such cycles are detected
1175      * internally by <code>SpringLayout</code> so that
1176      * the layout operation always terminates.
1177      *
1178      * @param edgeName must be one of
1179      * <code>SpringLayout.NORTH</code>,
1180      * <code>SpringLayout.SOUTH</code>,
1181      * <code>SpringLayout.EAST</code>,
1182      * <code>SpringLayout.WEST</code>,
1183      * <code>SpringLayout.VERTICAL_CENTER</code>,
1184      * <code>SpringLayout.HORIZONTAL_CENTER</code> or
1185      * <code>SpringLayout.BASELINE</code>
1186      * @param c the component whose edge spring is desired
1187      *
1188      * @return a proxy for the spring controlling the distance between the
1189      *         specified edge and the top or left edge of its parent
1190      *
1191      * @see #NORTH
1192      * @see #SOUTH
1193      * @see #EAST
1194      * @see #WEST
1195      * @see #VERTICAL_CENTER
1196      * @see #HORIZONTAL_CENTER
1197      * @see #BASELINE
1198      */
1199     public Spring getConstraint(String edgeName, Component c) {
1200         // The interning here is unnecessary; it was added for efficiency.
1201         edgeName = edgeName.intern();
1202         return new SpringProxy(edgeName, c, this);
1203     }
1204 
1205     public void layoutContainer(Container parent) {
1206         setParent(parent);
1207 
1208         int n = parent.getComponentCount();
1209         getConstraints(parent).reset();
1210         for (int i = 0 ; i < n ; i++) {
1211             getConstraints(parent.getComponent(i)).reset();
1212         }
1213 
1214         Insets insets = parent.getInsets();
1215         Constraints pc = getConstraints(parent);
1216         abandonCycles(pc.getX()).setValue(0);
1217         abandonCycles(pc.getY()).setValue(0);
1218         abandonCycles(pc.getWidth()).setValue(parent.getWidth() -
1219                                               insets.left - insets.right);
1220         abandonCycles(pc.getHeight()).setValue(parent.getHeight() -
1221                                                insets.top - insets.bottom);
1222 
1223         for (int i = 0 ; i < n ; i++) {
1224             Component c = parent.getComponent(i);
1225             Constraints cc = getConstraints(c);
1226             int x = abandonCycles(cc.getX()).getValue();
1227             int y = abandonCycles(cc.getY()).getValue();
1228             int width = abandonCycles(cc.getWidth()).getValue();
1229             int height = abandonCycles(cc.getHeight()).getValue();
1230             c.setBounds(insets.left + x, insets.top + y, width, height);
1231         }
1232     }
1233 }