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