History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/SpringLayout.java (1 of 18352 files). Show all

Unified · Split

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