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