1/*
2 * Copyright (c) 2001, 2014, 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 */
25package javax.swing;
26
27import java.awt.Component;
28import java.awt.Container;
29import java.awt.Dimension;
30import java.awt.FontMetrics;
31import java.awt.Insets;
32import java.awt.LayoutManager2;
33import java.awt.Rectangle;
34import 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://docs.oracle.com/javase/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&trade;
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@SuppressWarnings("serial") // Same-version serialization only
188public class SpringLayout implements LayoutManager2 {
189    private Map<Component, Constraints> componentConstraints = new HashMap<Component, Constraints>();
190
191    private Spring cyclicReference = Spring.constant(Spring.UNSET);
192    private Set<Spring> cyclicSprings;
193    private Set<Spring> acyclicSprings;
194
195
196    /**
197     * Specifies the top edge of a component's bounding rectangle.
198     */
199    public static final String NORTH  = "North";
200
201    /**
202     * Specifies the bottom edge of a component's bounding rectangle.
203     */
204    public static final String SOUTH  = "South";
205
206    /**
207     * Specifies the right edge of a component's bounding rectangle.
208     */
209    public static final String EAST   = "East";
210
211    /**
212     * Specifies the left edge of a component's bounding rectangle.
213     */
214    public static final String WEST   = "West";
215
216    /**
217     * Specifies the horizontal center of a component's bounding rectangle.
218     *
219     * @since 1.6
220     */
221    public static final String HORIZONTAL_CENTER   = "HorizontalCenter";
222
223    /**
224     * Specifies the vertical center of a component's bounding rectangle.
225     *
226     * @since 1.6
227     */
228    public static final String VERTICAL_CENTER   = "VerticalCenter";
229
230    /**
231     * Specifies the baseline of a component.
232     *
233     * @since 1.6
234     */
235    public static final String BASELINE   = "Baseline";
236
237    /**
238     * Specifies the width of a component's bounding rectangle.
239     *
240     * @since 1.6
241     */
242    public static final String WIDTH = "Width";
243
244    /**
245     * Specifies the height of a component's bounding rectangle.
246     *
247     * @since 1.6
248     */
249    public static final String HEIGHT = "Height";
250
251    private static String[] ALL_HORIZONTAL = {WEST, WIDTH, EAST, HORIZONTAL_CENTER};
252
253    private static String[] ALL_VERTICAL = {NORTH, HEIGHT, SOUTH, VERTICAL_CENTER, BASELINE};
254
255    /**
256     * A <code>Constraints</code> object holds the
257     * constraints that govern the way a component's size and position
258     * change in a container controlled by a <code>SpringLayout</code>.
259     * A <code>Constraints</code> object is
260     * like a <code>Rectangle</code>, in that it
261     * has <code>x</code>, <code>y</code>,
262     * <code>width</code>, and <code>height</code> properties.
263     * In the <code>Constraints</code> object, however,
264     * these properties have
265     * <code>Spring</code> values instead of integers.
266     * In addition,
267     * a <code>Constraints</code> object
268     * can be manipulated as four edges
269     * -- north, south, east, and west --
270     * using the <code>constraint</code> property.
271     *
272     * <p>
273     * The following formulas are always true
274     * for a <code>Constraints</code> object (here WEST and <code>x</code> are synonyms, as are and NORTH and <code>y</code>):
275     *
276     * <pre>
277     *               EAST = WEST + WIDTH
278     *              SOUTH = NORTH + HEIGHT
279     *  HORIZONTAL_CENTER = WEST + WIDTH/2
280     *    VERTICAL_CENTER = NORTH + HEIGHT/2
281     *  ABSOLUTE_BASELINE = NORTH + RELATIVE_BASELINE*
282     * </pre>
283     * <p>
284     * For example, if you have specified the WIDTH and WEST (X) location
285     * the EAST is calculated as WEST + WIDTH.  If you instead specified
286     * the WIDTH and EAST locations the WEST (X) location is then calculated
287     * as EAST - WIDTH.
288     * <p>
289     * [RELATIVE_BASELINE is a private constraint that is set automatically when
290     * the SpringLayout.Constraints(Component) constructor is called or when
291     * a constraints object is registered with a SpringLayout object.]
292     * <p>
293     * <b>Note</b>: In this document,
294     * operators represent methods
295     * in the <code>Spring</code> class.
296     * For example, "a + b" is equal to
297     * <code>Spring.sum(a, b)</code>,
298     * and "a - b" is equal to
299     * <code>Spring.sum(a, Spring.minus(b))</code>.
300     * See the
301     * {@link Spring Spring API documentation}
302     * for further details
303     * of spring arithmetic.
304     *
305     * <p>
306     *
307     * Because a <code>Constraints</code> object's properties --
308     * representing its edges, size, and location -- can all be set
309     * independently and yet are interrelated,
310     * a <code>Constraints</code> object can become <em>over-constrained</em>.
311     * For example, if the <code>WEST</code>, <code>WIDTH</code> and
312     * <code>EAST</code> edges are all set, steps must be taken to ensure that
313     * the first of the formulas above holds.  To do this, the
314     * <code>Constraints</code>
315     * object throws away the <em>least recently set</em>
316     * constraint so as to make the formulas hold.
317     * @since 1.4
318     */
319    public static class Constraints {
320       private Spring x;
321       private Spring y;
322       private Spring width;
323       private Spring height;
324       private Spring east;
325       private Spring south;
326        private Spring horizontalCenter;
327        private Spring verticalCenter;
328        private Spring baseline;
329
330        private List<String> horizontalHistory = new ArrayList<String>(2);
331        private List<String> verticalHistory = new ArrayList<String>(2);
332
333        // Used for baseline calculations
334        private Component c;
335
336       /**
337        * Creates an empty <code>Constraints</code> object.
338        */
339       public Constraints() {
340       }
341
342       /**
343        * Creates a <code>Constraints</code> object with the
344        * specified values for its
345        * <code>x</code> and <code>y</code> properties.
346        * The <code>height</code> and <code>width</code> springs
347        * have <code>null</code> values.
348        *
349        * @param x  the spring controlling the component's <em>x</em> value
350        * @param y  the spring controlling the component's <em>y</em> value
351        */
352       public Constraints(Spring x, Spring y) {
353           setX(x);
354           setY(y);
355       }
356
357       /**
358        * Creates a <code>Constraints</code> object with the
359        * specified values for its
360        * <code>x</code>, <code>y</code>, <code>width</code>,
361        * and <code>height</code> properties.
362        * Note: If the <code>SpringLayout</code> class
363        * encounters <code>null</code> values in the
364        * <code>Constraints</code> object of a given component,
365        * it replaces them with suitable defaults.
366        *
367        * @param x  the spring value for the <code>x</code> property
368        * @param y  the spring value for the <code>y</code> property
369        * @param width  the spring value for the <code>width</code> property
370        * @param height  the spring value for the <code>height</code> property
371        */
372       public Constraints(Spring x, Spring y, Spring width, Spring height) {
373           setX(x);
374           setY(y);
375           setWidth(width);
376           setHeight(height);
377       }
378
379        /**
380         * Creates a <code>Constraints</code> object with
381         * suitable <code>x</code>, <code>y</code>, <code>width</code> and
382         * <code>height</code> springs for component, <code>c</code>.
383         * The <code>x</code> and <code>y</code> springs are constant
384         * springs  initialised with the component's location at
385         * the time this method is called. The <code>width</code> and
386         * <code>height</code> springs are special springs, created by
387         * the <code>Spring.width()</code> and <code>Spring.height()</code>
388         * methods, which track the size characteristics of the component
389         * when they change.
390         *
391         * @param c  the component whose characteristics will be reflected by this Constraints object
392         * @throws NullPointerException if <code>c</code> is null.
393         * @since 1.5
394         */
395        public Constraints(Component c) {
396            this.c = c;
397            setX(Spring.constant(c.getX()));
398            setY(Spring.constant(c.getY()));
399            setWidth(Spring.width(c));
400            setHeight(Spring.height(c));
401        }
402
403        private void pushConstraint(String name, Spring value, boolean horizontal) {
404            boolean valid = true;
405            List<String> history = horizontal ? horizontalHistory :
406                                                verticalHistory;
407            if (history.contains(name)) {
408                history.remove(name);
409                valid = false;
410            } else if (history.size() == 2 && value != null) {
411                history.remove(0);
412                valid = false;
413            }
414            if (value != null) {
415                history.add(name);
416            }
417            if (!valid) {
418                String[] all = horizontal ? ALL_HORIZONTAL : ALL_VERTICAL;
419                for (String s : all) {
420                    if (!history.contains(s)) {
421                        setConstraint(s, null);
422                    }
423                }
424            }
425        }
426
427       private Spring sum(Spring s1, Spring s2) {
428           return (s1 == null || s2 == null) ? null : Spring.sum(s1, s2);
429       }
430
431       private Spring difference(Spring s1, Spring s2) {
432           return (s1 == null || s2 == null) ? null : Spring.difference(s1, s2);
433       }
434
435        private Spring scale(Spring s, float factor) {
436            return (s == null) ? null : Spring.scale(s, factor);
437        }
438
439        private int getBaselineFromHeight(int height) {
440            if (height < 0) {
441                // Bad Scott, Bad Scott!
442                return -c.getBaseline(c.getPreferredSize().width,
443                                      -height);
444            }
445            return c.getBaseline(c.getPreferredSize().width, height);
446        }
447
448        private int getHeightFromBaseLine(int baseline) {
449            Dimension prefSize = c.getPreferredSize();
450            int prefHeight = prefSize.height;
451            int prefBaseline = c.getBaseline(prefSize.width, prefHeight);
452            if (prefBaseline == baseline) {
453                // If prefBaseline < 0, then no baseline, assume preferred
454                // height.
455                // If prefBaseline == baseline, then specified baseline
456                // matches preferred baseline, return preferred height
457                return prefHeight;
458            }
459            // Valid baseline
460            switch(c.getBaselineResizeBehavior()) {
461            case CONSTANT_DESCENT:
462                return prefHeight + (baseline - prefBaseline);
463            case CENTER_OFFSET:
464                return prefHeight + 2 * (baseline - prefBaseline);
465            case CONSTANT_ASCENT:
466                // Component baseline and specified baseline will NEVER
467                // match, fall through to default
468            default: // OTHER
469                // No way to map from baseline to height.
470            }
471            return Integer.MIN_VALUE;
472        }
473
474         private Spring heightToRelativeBaseline(Spring s) {
475            return new Spring.SpringMap(s) {
476                 protected int map(int i) {
477                    return getBaselineFromHeight(i);
478                 }
479
480                 protected int inv(int i) {
481                     return getHeightFromBaseLine(i);
482                 }
483            };
484        }
485
486        private Spring relativeBaselineToHeight(Spring s) {
487            return new Spring.SpringMap(s) {
488                protected int map(int i) {
489                    return getHeightFromBaseLine(i);
490                 }
491
492                 protected int inv(int i) {
493                    return getBaselineFromHeight(i);
494                 }
495            };
496        }
497
498        private boolean defined(List<?> history, String s1, String s2) {
499            return history.contains(s1) && history.contains(s2);
500        }
501
502       /**
503        * Sets the <code>x</code> property,
504        * which controls the <code>x</code> value
505        * of a component's location.
506        *
507        * @param x the spring controlling the <code>x</code> value
508        *          of a component's location
509        *
510        * @see #getX
511        * @see SpringLayout.Constraints
512        */
513       public void setX(Spring x) {
514           this.x = x;
515           pushConstraint(WEST, x, true);
516       }
517
518       /**
519        * Returns the value of the <code>x</code> property.
520        *
521        * @return the spring controlling the <code>x</code> value
522        *         of a component's location
523        *
524        * @see #setX
525        * @see SpringLayout.Constraints
526        */
527       public Spring getX() {
528           if (x == null) {
529               if (defined(horizontalHistory, EAST, WIDTH)) {
530                   x = difference(east, width);
531               } else if (defined(horizontalHistory, HORIZONTAL_CENTER, WIDTH)) {
532                   x = difference(horizontalCenter, scale(width, 0.5f));
533               } else if (defined(horizontalHistory, HORIZONTAL_CENTER, EAST)) {
534                   x = difference(scale(horizontalCenter, 2f), east);
535               }
536           }
537           return x;
538       }
539
540       /**
541        * Sets the <code>y</code> property,
542        * which controls the <code>y</code> value
543        * of a component's location.
544        *
545        * @param y the spring controlling the <code>y</code> value
546        *          of a component's location
547        *
548        * @see #getY
549        * @see SpringLayout.Constraints
550        */
551       public void setY(Spring y) {
552           this.y = y;
553           pushConstraint(NORTH, y, false);
554       }
555
556       /**
557        * Returns the value of the <code>y</code> property.
558        *
559        * @return the spring controlling the <code>y</code> value
560        *         of a component's location
561        *
562        * @see #setY
563        * @see SpringLayout.Constraints
564        */
565       public Spring getY() {
566           if (y == null) {
567               if (defined(verticalHistory, SOUTH, HEIGHT)) {
568                   y = difference(south, height);
569               } else if (defined(verticalHistory, VERTICAL_CENTER, HEIGHT)) {
570                   y = difference(verticalCenter, scale(height, 0.5f));
571               } else if (defined(verticalHistory, VERTICAL_CENTER, SOUTH)) {
572                   y = difference(scale(verticalCenter, 2f), south);
573               } else if (defined(verticalHistory, BASELINE, HEIGHT)) {
574                   y = difference(baseline, heightToRelativeBaseline(height));
575               } else if (defined(verticalHistory, BASELINE, SOUTH)) {
576                   y = scale(difference(baseline, heightToRelativeBaseline(south)), 2f);
577/*
578               } else if (defined(verticalHistory, BASELINE, VERTICAL_CENTER)) {
579                   y = scale(difference(baseline, heightToRelativeBaseline(scale(verticalCenter, 2))), 1f/(1-2*0.5f));
580*/
581               }
582           }
583           return y;
584       }
585
586       /**
587        * Sets the <code>width</code> property,
588        * which controls the width of a component.
589        *
590        * @param width the spring controlling the width of this
591        * <code>Constraints</code> object
592        *
593        * @see #getWidth
594        * @see SpringLayout.Constraints
595        */
596       public void setWidth(Spring width) {
597           this.width = width;
598           pushConstraint(WIDTH, width, true);
599       }
600
601       /**
602        * Returns the value of the <code>width</code> property.
603        *
604        * @return the spring controlling the width of a component
605        *
606        * @see #setWidth
607        * @see SpringLayout.Constraints
608        */
609       public Spring getWidth() {
610           if (width == null) {
611               if (horizontalHistory.contains(EAST)) {
612                   width = difference(east, getX());
613               } else if (horizontalHistory.contains(HORIZONTAL_CENTER)) {
614                   width = scale(difference(horizontalCenter, getX()), 2f);
615               }
616           }
617           return width;
618       }
619
620       /**
621        * Sets the <code>height</code> property,
622        * which controls the height of a component.
623        *
624        * @param height the spring controlling the height of this <code>Constraints</code>
625        * object
626        *
627        * @see #getHeight
628        * @see SpringLayout.Constraints
629        */
630       public void setHeight(Spring height) {
631           this.height = height;
632           pushConstraint(HEIGHT, height, false);
633       }
634
635       /**
636        * Returns the value of the <code>height</code> property.
637        *
638        * @return the spring controlling the height of a component
639        *
640        * @see #setHeight
641        * @see SpringLayout.Constraints
642        */
643       public Spring getHeight() {
644           if (height == null) {
645               if (verticalHistory.contains(SOUTH)) {
646                   height = difference(south, getY());
647               } else if (verticalHistory.contains(VERTICAL_CENTER)) {
648                   height = scale(difference(verticalCenter, getY()), 2f);
649               } else if (verticalHistory.contains(BASELINE)) {
650                   height = relativeBaselineToHeight(difference(baseline, getY()));
651               }
652           }
653           return height;
654       }
655
656       private void setEast(Spring east) {
657           this.east = east;
658           pushConstraint(EAST, east, true);
659       }
660
661       private Spring getEast() {
662           if (east == null) {
663               east = sum(getX(), getWidth());
664           }
665           return east;
666       }
667
668       private void setSouth(Spring south) {
669           this.south = south;
670           pushConstraint(SOUTH, south, false);
671       }
672
673       private Spring getSouth() {
674           if (south == null) {
675               south = sum(getY(), getHeight());
676           }
677           return south;
678       }
679
680        private Spring getHorizontalCenter() {
681            if (horizontalCenter == null) {
682                horizontalCenter = sum(getX(), scale(getWidth(), 0.5f));
683            }
684            return horizontalCenter;
685        }
686
687        private void setHorizontalCenter(Spring horizontalCenter) {
688            this.horizontalCenter = horizontalCenter;
689            pushConstraint(HORIZONTAL_CENTER, horizontalCenter, true);
690        }
691
692        private Spring getVerticalCenter() {
693            if (verticalCenter == null) {
694                verticalCenter = sum(getY(), scale(getHeight(), 0.5f));
695            }
696            return verticalCenter;
697        }
698
699        private void setVerticalCenter(Spring verticalCenter) {
700            this.verticalCenter = verticalCenter;
701            pushConstraint(VERTICAL_CENTER, verticalCenter, false);
702        }
703
704        private Spring getBaseline() {
705            if (baseline == null) {
706                baseline = sum(getY(), heightToRelativeBaseline(getHeight()));
707            }
708            return baseline;
709        }
710
711        private void setBaseline(Spring baseline) {
712            this.baseline = baseline;
713            pushConstraint(BASELINE, baseline, false);
714        }
715
716       /**
717        * Sets the spring controlling the specified edge.
718        * The edge must have one of the following values:
719        * <code>SpringLayout.NORTH</code>,
720        * <code>SpringLayout.SOUTH</code>,
721        * <code>SpringLayout.EAST</code>,
722        * <code>SpringLayout.WEST</code>,
723        * <code>SpringLayout.HORIZONTAL_CENTER</code>,
724        * <code>SpringLayout.VERTICAL_CENTER</code>,
725        * <code>SpringLayout.BASELINE</code>,
726        * <code>SpringLayout.WIDTH</code> or
727        * <code>SpringLayout.HEIGHT</code>.
728        * For any other <code>String</code> value passed as the edge,
729        * no action is taken. For a <code>null</code> edge, a
730        * <code>NullPointerException</code> is thrown.
731        * <p>
732        * <b>Note:</b> This method can affect {@code x} and {@code y} values
733        * previously set for this {@code Constraints}.
734        *
735        * @param edgeName the edge to be set
736        * @param s the spring controlling the specified edge
737        *
738        * @throws NullPointerException if <code>edgeName</code> is <code>null</code>
739        *
740        * @see #getConstraint
741        * @see #NORTH
742        * @see #SOUTH
743        * @see #EAST
744        * @see #WEST
745        * @see #HORIZONTAL_CENTER
746        * @see #VERTICAL_CENTER
747        * @see #BASELINE
748        * @see #WIDTH
749        * @see #HEIGHT
750        * @see SpringLayout.Constraints
751        */
752       public void setConstraint(String edgeName, Spring s) {
753           edgeName = edgeName.intern();
754           if (edgeName == WEST) {
755               setX(s);
756           } else if (edgeName == NORTH) {
757               setY(s);
758           } else if (edgeName == EAST) {
759               setEast(s);
760           } else if (edgeName == SOUTH) {
761               setSouth(s);
762           } else if (edgeName == HORIZONTAL_CENTER) {
763               setHorizontalCenter(s);
764           } else if (edgeName == WIDTH) {
765               setWidth(s);
766           } else if (edgeName == HEIGHT) {
767               setHeight(s);
768           } else if (edgeName == VERTICAL_CENTER) {
769               setVerticalCenter(s);
770           } else if (edgeName == BASELINE) {
771               setBaseline(s);
772           }
773       }
774
775       /**
776        * Returns the value of the specified edge, which may be
777        * a derived value, or even <code>null</code>.
778        * The edge must have one of the following values:
779        * <code>SpringLayout.NORTH</code>,
780        * <code>SpringLayout.SOUTH</code>,
781        * <code>SpringLayout.EAST</code>,
782        * <code>SpringLayout.WEST</code>,
783        * <code>SpringLayout.HORIZONTAL_CENTER</code>,
784        * <code>SpringLayout.VERTICAL_CENTER</code>,
785        * <code>SpringLayout.BASELINE</code>,
786        * <code>SpringLayout.WIDTH</code> or
787        * <code>SpringLayout.HEIGHT</code>.
788        * For any other <code>String</code> value passed as the edge,
789        * <code>null</code> will be returned. Throws
790        * <code>NullPointerException</code> for a <code>null</code> edge.
791        *
792        * @param edgeName the edge whose value
793        *                 is to be returned
794        *
795        * @return the spring controlling the specified edge, may be <code>null</code>
796        *
797        * @throws NullPointerException if <code>edgeName</code> is <code>null</code>
798        *
799        * @see #setConstraint
800        * @see #NORTH
801        * @see #SOUTH
802        * @see #EAST
803        * @see #WEST
804        * @see #HORIZONTAL_CENTER
805        * @see #VERTICAL_CENTER
806        * @see #BASELINE
807        * @see #WIDTH
808        * @see #HEIGHT
809        * @see SpringLayout.Constraints
810        */
811       public Spring getConstraint(String edgeName) {
812           edgeName = edgeName.intern();
813           return (edgeName == WEST)  ? getX() :
814                   (edgeName == NORTH) ? getY() :
815                   (edgeName == EAST)  ? getEast() :
816                   (edgeName == SOUTH) ? getSouth() :
817                   (edgeName == WIDTH)  ? getWidth() :
818                   (edgeName == HEIGHT) ? getHeight() :
819                   (edgeName == HORIZONTAL_CENTER) ? getHorizontalCenter() :
820                   (edgeName == VERTICAL_CENTER)  ? getVerticalCenter() :
821                   (edgeName == BASELINE) ? getBaseline() :
822                  null;
823       }
824
825       /*pp*/ void reset() {
826           Spring[] allSprings = {x, y, width, height, east, south,
827               horizontalCenter, verticalCenter, baseline};
828           for (Spring s : allSprings) {
829               if (s != null) {
830                   s.setValue(Spring.UNSET);
831               }
832           }
833       }
834   }
835
836   private static class SpringProxy extends Spring {
837       private String edgeName;
838       private Component c;
839       private SpringLayout l;
840
841       public SpringProxy(String edgeName, Component c, SpringLayout l) {
842           this.edgeName = edgeName;
843           this.c = c;
844           this.l = l;
845       }
846
847       private Spring getConstraint() {
848           return l.getConstraints(c).getConstraint(edgeName);
849       }
850
851       public int getMinimumValue() {
852           return getConstraint().getMinimumValue();
853       }
854
855       public int getPreferredValue() {
856           return getConstraint().getPreferredValue();
857       }
858
859       public int getMaximumValue() {
860           return getConstraint().getMaximumValue();
861       }
862
863       public int getValue() {
864           return getConstraint().getValue();
865       }
866
867       public void setValue(int size) {
868           getConstraint().setValue(size);
869       }
870
871       /*pp*/ boolean isCyclic(SpringLayout l) {
872           return l.isCyclic(getConstraint());
873       }
874
875       public String toString() {
876           return "SpringProxy for " + edgeName + " edge of " + c.getName() + ".";
877       }
878    }
879
880    /**
881     * Constructs a new <code>SpringLayout</code>.
882     */
883    public SpringLayout() {}
884
885    private void resetCyclicStatuses() {
886        cyclicSprings = new HashSet<Spring>();
887        acyclicSprings = new HashSet<Spring>();
888    }
889
890    private void setParent(Container p) {
891        resetCyclicStatuses();
892        Constraints pc = getConstraints(p);
893
894        pc.setX(Spring.constant(0));
895        pc.setY(Spring.constant(0));
896        // The applyDefaults() method automatically adds width and
897        // height springs that delegate their calculations to the
898        // getMinimumSize(), getPreferredSize() and getMaximumSize()
899        // methods of the relevant component. In the case of the
900        // parent this will cause an infinite loop since these
901        // methods, in turn, delegate their calculations to the
902        // layout manager. Check for this case and replace the
903        // the springs that would cause this problem with a
904        // constant springs that supply default values.
905        Spring width = pc.getWidth();
906        if (width instanceof Spring.WidthSpring && ((Spring.WidthSpring)width).c == p) {
907            pc.setWidth(Spring.constant(0, 0, Integer.MAX_VALUE));
908        }
909        Spring height = pc.getHeight();
910        if (height instanceof Spring.HeightSpring && ((Spring.HeightSpring)height).c == p) {
911            pc.setHeight(Spring.constant(0, 0, Integer.MAX_VALUE));
912        }
913    }
914
915    /*pp*/ boolean isCyclic(Spring s) {
916        if (s == null) {
917            return false;
918        }
919        if (cyclicSprings.contains(s)) {
920            return true;
921        }
922        if (acyclicSprings.contains(s)) {
923            return false;
924        }
925        cyclicSprings.add(s);
926        boolean result = s.isCyclic(this);
927        if (!result) {
928            acyclicSprings.add(s);
929            cyclicSprings.remove(s);
930        }
931        else {
932            System.err.println(s + " is cyclic. ");
933        }
934        return result;
935    }
936
937    private Spring abandonCycles(Spring s) {
938        return isCyclic(s) ? cyclicReference : s;
939    }
940
941    // LayoutManager methods.
942
943    /**
944     * Has no effect,
945     * since this layout manager does not
946     * use a per-component string.
947     */
948    public void addLayoutComponent(String name, Component c) {}
949
950    /**
951     * Removes the constraints associated with the specified component.
952     *
953     * @param c the component being removed from the container
954     */
955    public void removeLayoutComponent(Component c) {
956        componentConstraints.remove(c);
957    }
958
959    private static Dimension addInsets(int width, int height, Container p) {
960        Insets i = p.getInsets();
961        return new Dimension(width + i.left + i.right, height + i.top + i.bottom);
962    }
963
964    public Dimension minimumLayoutSize(Container parent) {
965        setParent(parent);
966        Constraints pc = getConstraints(parent);
967        return addInsets(abandonCycles(pc.getWidth()).getMinimumValue(),
968                         abandonCycles(pc.getHeight()).getMinimumValue(),
969                         parent);
970    }
971
972    public Dimension preferredLayoutSize(Container parent) {
973        setParent(parent);
974        Constraints pc = getConstraints(parent);
975        return addInsets(abandonCycles(pc.getWidth()).getPreferredValue(),
976                         abandonCycles(pc.getHeight()).getPreferredValue(),
977                         parent);
978    }
979
980    // LayoutManager2 methods.
981
982    public Dimension maximumLayoutSize(Container parent) {
983        setParent(parent);
984        Constraints pc = getConstraints(parent);
985        return addInsets(abandonCycles(pc.getWidth()).getMaximumValue(),
986                         abandonCycles(pc.getHeight()).getMaximumValue(),
987                         parent);
988    }
989
990    /**
991     * If <code>constraints</code> is an instance of
992     * <code>SpringLayout.Constraints</code>,
993     * associates the constraints with the specified component.
994     *
995     * @param   component the component being added
996     * @param   constraints the component's constraints
997     *
998     * @see SpringLayout.Constraints
999     */
1000    public void addLayoutComponent(Component component, Object constraints) {
1001        if (constraints instanceof Constraints) {
1002            putConstraints(component, (Constraints)constraints);
1003        }
1004    }
1005
1006    /**
1007     * Returns 0.5f (centered).
1008     */
1009    public float getLayoutAlignmentX(Container p) {
1010        return 0.5f;
1011    }
1012
1013    /**
1014     * Returns 0.5f (centered).
1015     */
1016    public float getLayoutAlignmentY(Container p) {
1017        return 0.5f;
1018    }
1019
1020    public void invalidateLayout(Container p) {}
1021
1022    // End of LayoutManger2 methods
1023
1024   /**
1025     * Links edge <code>e1</code> of component <code>c1</code> to
1026     * edge <code>e2</code> of component <code>c2</code>,
1027     * with a fixed distance between the edges. This
1028     * constraint will cause the assignment
1029     * <pre>
1030     *     value(e1, c1) = value(e2, c2) + pad</pre>
1031     * to take place during all subsequent layout operations.
1032     *
1033     * @param   e1 the edge of the dependent
1034     * @param   c1 the component of the dependent
1035     * @param   pad the fixed distance between dependent and anchor
1036     * @param   e2 the edge of the anchor
1037     * @param   c2 the component of the anchor
1038     *
1039     * @see #putConstraint(String, Component, Spring, String, Component)
1040     */
1041    public void putConstraint(String e1, Component c1, int pad, String e2, Component c2) {
1042        putConstraint(e1, c1, Spring.constant(pad), e2, c2);
1043    }
1044
1045    /**
1046     * Links edge <code>e1</code> of component <code>c1</code> to
1047     * edge <code>e2</code> of component <code>c2</code>. As edge
1048     * <code>(e2, c2)</code> changes value, edge <code>(e1, c1)</code> will
1049     * be calculated by taking the (spring) sum of <code>(e2, c2)</code>
1050     * and <code>s</code>.
1051     * Each edge must have one of the following values:
1052     * <code>SpringLayout.NORTH</code>,
1053     * <code>SpringLayout.SOUTH</code>,
1054     * <code>SpringLayout.EAST</code>,
1055     * <code>SpringLayout.WEST</code>,
1056     * <code>SpringLayout.VERTICAL_CENTER</code>,
1057     * <code>SpringLayout.HORIZONTAL_CENTER</code> or
1058     * <code>SpringLayout.BASELINE</code>.
1059     *
1060     * @param   e1 the edge of the dependent
1061     * @param   c1 the component of the dependent
1062     * @param   s the spring linking dependent and anchor
1063     * @param   e2 the edge of the anchor
1064     * @param   c2 the component of the anchor
1065     *
1066     * @see #putConstraint(String, Component, int, String, Component)
1067     * @see #NORTH
1068     * @see #SOUTH
1069     * @see #EAST
1070     * @see #WEST
1071     * @see #VERTICAL_CENTER
1072     * @see #HORIZONTAL_CENTER
1073     * @see #BASELINE
1074     */
1075    public void putConstraint(String e1, Component c1, Spring s, String e2, Component c2) {
1076        putConstraint(e1, c1, Spring.sum(s, getConstraint(e2, c2)));
1077    }
1078
1079    private void putConstraint(String e, Component c, Spring s) {
1080        if (s != null) {
1081            getConstraints(c).setConstraint(e, s);
1082        }
1083     }
1084
1085    private Constraints applyDefaults(Component c, Constraints constraints) {
1086        if (constraints == null) {
1087            constraints = new Constraints();
1088        }
1089        if (constraints.c == null) {
1090            constraints.c = c;
1091        }
1092        if (constraints.horizontalHistory.size() < 2) {
1093            applyDefaults(constraints, WEST, Spring.constant(0), WIDTH,
1094                          Spring.width(c), constraints.horizontalHistory);
1095        }
1096        if (constraints.verticalHistory.size() < 2) {
1097            applyDefaults(constraints, NORTH, Spring.constant(0), HEIGHT,
1098                          Spring.height(c), constraints.verticalHistory);
1099        }
1100        return constraints;
1101    }
1102
1103    private void applyDefaults(Constraints constraints, String name1,
1104                               Spring spring1, String name2, Spring spring2,
1105                               List<String> history) {
1106        if (history.size() == 0) {
1107            constraints.setConstraint(name1, spring1);
1108            constraints.setConstraint(name2, spring2);
1109        } else {
1110            // At this point there must be exactly one constraint defined already.
1111            // Check width/height first.
1112            if (constraints.getConstraint(name2) == null) {
1113                constraints.setConstraint(name2, spring2);
1114            } else {
1115                // If width/height is already defined, install a default for x/y.
1116                constraints.setConstraint(name1, spring1);
1117            }
1118            // Either way, leave the user's constraint topmost on the stack.
1119            Collections.rotate(history, 1);
1120        }
1121    }
1122
1123    private void putConstraints(Component component, Constraints constraints) {
1124        componentConstraints.put(component, applyDefaults(component, constraints));
1125    }
1126
1127    /**
1128     * Returns the constraints for the specified component.
1129     * Note that,
1130     * unlike the <code>GridBagLayout</code>
1131     * <code>getConstraints</code> method,
1132     * this method does not clone constraints.
1133     * If no constraints
1134     * have been associated with this component,
1135     * this method
1136     * returns a default constraints object positioned at
1137     * 0,0 relative to the parent's Insets and its width/height
1138     * constrained to the minimum, maximum, and preferred sizes of the
1139     * component. The size characteristics
1140     * are not frozen at the time this method is called;
1141     * instead this method returns a constraints object
1142     * whose characteristics track the characteristics
1143     * of the component as they change.
1144     *
1145     * @param       c the component whose constraints will be returned
1146     *
1147     * @return      the constraints for the specified component
1148     */
1149    public Constraints getConstraints(Component c) {
1150       Constraints result = componentConstraints.get(c);
1151       if (result == null) {
1152           if (c instanceof javax.swing.JComponent) {
1153                Object cp = ((javax.swing.JComponent)c).getClientProperty(SpringLayout.class);
1154                if (cp instanceof Constraints) {
1155                    return applyDefaults(c, (Constraints)cp);
1156                }
1157            }
1158            result = new Constraints();
1159            putConstraints(c, result);
1160       }
1161       return result;
1162    }
1163
1164    /**
1165     * Returns the spring controlling the distance between
1166     * the specified edge of
1167     * the component and the top or left edge of its parent. This
1168     * method, instead of returning the current binding for the
1169     * edge, returns a proxy that tracks the characteristics
1170     * of the edge even if the edge is subsequently rebound.
1171     * Proxies are intended to be used in builder environments
1172     * where it is useful to allow the user to define the
1173     * constraints for a layout in any order. Proxies do, however,
1174     * provide the means to create cyclic dependencies amongst
1175     * the constraints of a layout. Such cycles are detected
1176     * internally by <code>SpringLayout</code> so that
1177     * the layout operation always terminates.
1178     *
1179     * @param edgeName must be one of
1180     * <code>SpringLayout.NORTH</code>,
1181     * <code>SpringLayout.SOUTH</code>,
1182     * <code>SpringLayout.EAST</code>,
1183     * <code>SpringLayout.WEST</code>,
1184     * <code>SpringLayout.VERTICAL_CENTER</code>,
1185     * <code>SpringLayout.HORIZONTAL_CENTER</code> or
1186     * <code>SpringLayout.BASELINE</code>
1187     * @param c the component whose edge spring is desired
1188     *
1189     * @return a proxy for the spring controlling the distance between the
1190     *         specified edge and the top or left edge of its parent
1191     *
1192     * @see #NORTH
1193     * @see #SOUTH
1194     * @see #EAST
1195     * @see #WEST
1196     * @see #VERTICAL_CENTER
1197     * @see #HORIZONTAL_CENTER
1198     * @see #BASELINE
1199     */
1200    public Spring getConstraint(String edgeName, Component c) {
1201        // The interning here is unnecessary; it was added for efficiency.
1202        edgeName = edgeName.intern();
1203        return new SpringProxy(edgeName, c, this);
1204    }
1205
1206    public void layoutContainer(Container parent) {
1207        setParent(parent);
1208
1209        int n = parent.getComponentCount();
1210        getConstraints(parent).reset();
1211        for (int i = 0 ; i < n ; i++) {
1212            getConstraints(parent.getComponent(i)).reset();
1213        }
1214
1215        Insets insets = parent.getInsets();
1216        Constraints pc = getConstraints(parent);
1217        abandonCycles(pc.getX()).setValue(0);
1218        abandonCycles(pc.getY()).setValue(0);
1219        abandonCycles(pc.getWidth()).setValue(parent.getWidth() -
1220                                              insets.left - insets.right);
1221        abandonCycles(pc.getHeight()).setValue(parent.getHeight() -
1222                                               insets.top - insets.bottom);
1223
1224        for (int i = 0 ; i < n ; i++) {
1225            Component c = parent.getComponent(i);
1226            Constraints cc = getConstraints(c);
1227            int x = abandonCycles(cc.getX()).getValue();
1228            int y = abandonCycles(cc.getY()).getValue();
1229            int width = abandonCycles(cc.getWidth()).getValue();
1230            int height = abandonCycles(cc.getHeight()).getValue();
1231            c.setBounds(insets.left + x, insets.top + y, width, height);
1232        }
1233    }
1234}
1235