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://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™
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
188 public 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 /**
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;
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) {
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() :
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
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
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) {
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) {
|
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} 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}.
42 *
43 * <p>
44 * Each constraint,
45 * represented by a {@code Spring} 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}-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} object.
68 * By default,
69 * {@code SpringLayout} 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}
78 * will be positioned at 0,0 relative to the {@code Insets} of the
79 * parent {@code Container}.
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}
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} 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} 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} 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}, 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} 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} doesn't automatically set the location of
140 * the components it manages.
141 * If you hand-code a GUI that uses {@code SpringLayout},
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},
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},
155 * you'll need to
156 * create a special-purpose subclass of the {@code Spring} class.
157 *
158 * <p>
159 * {@code SpringLayout} 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}es. That said, {@code SpringLayout} honors the
163 * {@code LayoutManager2} 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}
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™
176 * has been added to the {@code java.beans} 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
188 public 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 /**
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} 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}.
259 * A {@code Constraints} object is
260 * like a {@code Rectangle}, in that it
261 * has {@code x}, {@code y},
262 * {@code width}, and {@code height} properties.
263 * In the {@code Constraints} object, however,
264 * these properties have
265 * {@code Spring} values instead of integers.
266 * In addition,
267 * a {@code Constraints} object
268 * can be manipulated as four edges
269 * -- north, south, east, and west --
270 * using the {@code constraint} property.
271 *
272 * <p>
273 * The following formulas are always true
274 * for a {@code Constraints} object (here WEST and {@code x} are synonyms, as are and NORTH and {@code y}):
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} class.
296 * For example, "a + b" is equal to
297 * {@code Spring.sum(a, b)},
298 * and "a - b" is equal to
299 * {@code Spring.sum(a, Spring.minus(b))}.
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} object's properties --
308 * representing its edges, size, and location -- can all be set
309 * independently and yet are interrelated,
310 * a {@code Constraints} object can become <em>over-constrained</em>.
311 * For example, if the {@code WEST}, {@code WIDTH} and
312 * {@code EAST} 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}
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} object.
338 */
339 public Constraints() {
340 }
341
342 /**
343 * Creates a {@code Constraints} object with the
344 * specified values for its
345 * {@code x} and {@code y} properties.
346 * The {@code height} and {@code width} springs
347 * have {@code null} 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} object with the
359 * specified values for its
360 * {@code x}, {@code y}, {@code width},
361 * and {@code height} properties.
362 * Note: If the {@code SpringLayout} class
363 * encounters {@code null} values in the
364 * {@code Constraints} object of a given component,
365 * it replaces them with suitable defaults.
366 *
367 * @param x the spring value for the {@code x} property
368 * @param y the spring value for the {@code y} property
369 * @param width the spring value for the {@code width} property
370 * @param height the spring value for the {@code height} 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} object with
381 * suitable {@code x}, {@code y}, {@code width} and
382 * {@code height} springs for component, {@code c}.
383 * The {@code x} and {@code y} springs are constant
384 * springs initialised with the component's location at
385 * the time this method is called. The {@code width} and
386 * {@code height} springs are special springs, created by
387 * the {@code Spring.width()} and {@code Spring.height()}
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} 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;
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} property,
504 * which controls the {@code x} value
505 * of a component's location.
506 *
507 * @param x the spring controlling the {@code x} 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} property.
520 *
521 * @return the spring controlling the {@code x} 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} property,
542 * which controls the {@code y} value
543 * of a component's location.
544 *
545 * @param y the spring controlling the {@code y} 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} property.
558 *
559 * @return the spring controlling the {@code y} 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} property,
588 * which controls the width of a component.
589 *
590 * @param width the spring controlling the width of this
591 * {@code Constraints} 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} 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} property,
622 * which controls the height of a component.
623 *
624 * @param height the spring controlling the height of this {@code Constraints}
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} 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) {
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},
720 * {@code SpringLayout.SOUTH},
721 * {@code SpringLayout.EAST},
722 * {@code SpringLayout.WEST},
723 * {@code SpringLayout.HORIZONTAL_CENTER},
724 * {@code SpringLayout.VERTICAL_CENTER},
725 * {@code SpringLayout.BASELINE},
726 * {@code SpringLayout.WIDTH} or
727 * {@code SpringLayout.HEIGHT}.
728 * For any other {@code String} value passed as the edge,
729 * no action is taken. For a {@code null} edge, a
730 * {@code NullPointerException} 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} is {@code null}
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}.
778 * The edge must have one of the following values:
779 * {@code SpringLayout.NORTH},
780 * {@code SpringLayout.SOUTH},
781 * {@code SpringLayout.EAST},
782 * {@code SpringLayout.WEST},
783 * {@code SpringLayout.HORIZONTAL_CENTER},
784 * {@code SpringLayout.VERTICAL_CENTER},
785 * {@code SpringLayout.BASELINE},
786 * {@code SpringLayout.WIDTH} or
787 * {@code SpringLayout.HEIGHT}.
788 * For any other {@code String} value passed as the edge,
789 * {@code null} will be returned. Throws
790 * {@code NullPointerException} for a {@code null} 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}
796 *
797 * @throws NullPointerException if {@code edgeName} is {@code null}
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() :
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}.
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
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} is an instance of
992 * {@code SpringLayout.Constraints},
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} of component {@code c1} to
1026 * edge {@code e2} of component {@code c2},
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} of component {@code c1} to
1047 * edge {@code e2} of component {@code c2}. As edge
1048 * {@code (e2, c2)} changes value, edge {@code (e1, c1)} will
1049 * be calculated by taking the (spring) sum of {@code (e2, c2)}
1050 * and {@code s}.
1051 * Each edge must have one of the following values:
1052 * {@code SpringLayout.NORTH},
1053 * {@code SpringLayout.SOUTH},
1054 * {@code SpringLayout.EAST},
1055 * {@code SpringLayout.WEST},
1056 * {@code SpringLayout.VERTICAL_CENTER},
1057 * {@code SpringLayout.HORIZONTAL_CENTER} or
1058 * {@code SpringLayout.BASELINE}.
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
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}
1131 * {@code getConstraints} 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) {
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} so that
1177 * the layout operation always terminates.
1178 *
1179 * @param edgeName must be one of
1180 * {@code SpringLayout.NORTH},
1181 * {@code SpringLayout.SOUTH},
1182 * {@code SpringLayout.EAST},
1183 * {@code SpringLayout.WEST},
1184 * {@code SpringLayout.VERTICAL_CENTER},
1185 * {@code SpringLayout.HORIZONTAL_CENTER} or
1186 * {@code SpringLayout.BASELINE}
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) {
|