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