001 /* SpringLayout.java -- 002 Copyright (C) 2004, 2006, Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing; 040 041 import java.awt.Component; 042 import java.awt.Container; 043 import java.awt.Dimension; 044 import java.awt.LayoutManager2; 045 import java.util.HashMap; 046 import java.util.Map; 047 048 /** 049 * A very flexible layout manager. Components are laid out by defining the 050 * relationships between them. The relationships are expressed as 051 * {@link Spring}s. You can attach a Spring for each edge of a component and 052 * link it to an edge of a different component. For example, you can say, 053 * the northern edge of component A should be attached to the southern edge 054 * of component B, and the space between them should be something between 055 * x and y pixels, and preferably z pixels. 056 * <p>While quite simple, this layout manager can be used to emulate most other 057 * layout managers, and can also be used to solve some layout problems, which 058 * would be hard to solve with other layout managers.</p> 059 * 060 * @author Roman Kennke (roman@ontographics.com) 061 */ 062 public class SpringLayout implements LayoutManager2 063 { 064 065 /** The right edge of a component. */ 066 public static final String EAST = "East"; 067 068 /** The top edge of a component. */ 069 public static final String NORTH = "North"; 070 071 /** The bottom edge of a component. */ 072 public static final String SOUTH = "South"; 073 074 /** The left edge of a component. */ 075 public static final String WEST = "West"; 076 077 /** maps components to their constraints. */ 078 private Map constraintsMap; 079 080 /** 081 * The constraints that define the relationships between components. 082 * Each Constraints object can hold 4 Springs: one for each edge of the 083 * component. Additionally it can hold Springs for the components width 084 * and the components height. Since the height and width constraints are 085 * dependend on the other constraints, a component can be over-constraint. 086 * In this case (like when all of NORTH, SOUTH and HEIGHT are constraint), 087 * the values are adjusted, so that the mathematics still hold true. 088 * 089 * @author Roman Kennke (roman@ontographics.com) 090 */ 091 public static class Constraints 092 { 093 094 // The constraints for each edge, and width and height. 095 /** The Spring for the left edge. */ 096 private Spring x; 097 098 /** The Spring for the upper edge. */ 099 private Spring y; 100 101 /** The Spring for the height. */ 102 private Spring height; 103 104 /** The Spring for the width. */ 105 private Spring width; 106 107 /** The Spring for the right edge. */ 108 private Spring east; 109 110 /** The Spring for the bottom edge. */ 111 private Spring south; 112 113 /** 114 In each axis the user can set three values, i.e. x, width, east, if all 115 three are set, then there's no room for manoeuvre so in those cases the 116 third will be described by the below spring which is calculated in terms 117 of the other two 118 */ 119 private Spring v; 120 private Spring h; 121 122 /** 123 * Creates a new Constraints object. 124 * There is no constraint set. 125 */ 126 public Constraints() 127 { 128 x = y = height = width = east = south = v = h = null; 129 } 130 131 /** 132 * Creates a new Constraints object. 133 * 134 * @param x the constraint for the left edge of the component. 135 * @param y the constraint for the upper edge of the component. 136 */ 137 public Constraints(Spring x, Spring y) 138 { 139 this.x = x; 140 this.y = y; 141 width = height = east = south = v = h = null; 142 } 143 144 /** 145 * Creates a new Constraints object. 146 * 147 * @param x the constraint for the left edge of the component. 148 * @param y the constraint for the upper edge of the component. 149 * @param width the constraint for the width of the component. 150 * @param height the constraint for the height of the component. 151 */ 152 public Constraints(Spring x, Spring y, Spring width, Spring height) 153 { 154 this.x = x; 155 this.y = y; 156 this.width = width; 157 this.height = height; 158 east = south = v = h = null; 159 } 160 161 /** 162 * Create a new Constraints object which tracks the indicated 163 * component. The x and y positions for this Constraints object 164 * are constant Springs created with the component's location at 165 * the time this constructor is called. The width and height 166 * of this Constraints are Springs created using 167 * {@link Spring#width(Component)} and {@link Spring#height(Component)}, 168 * respectively. 169 * @param component the component to track 170 * @since 1.5 171 */ 172 public Constraints(Component component) 173 { 174 this(Spring.constant(component.getX()), 175 Spring.constant(component.getY()), 176 Spring.width(component), 177 Spring.height(component)); 178 } 179 180 /** 181 * Returns the constraint for the edge with the <code>edgeName</code>. 182 * This is expected to be one of 183 * {@link #EAST}, {@link #WEST}, {@link #NORTH} or {@link #SOUTH}. 184 * 185 * @param edgeName the name of the edge. 186 * @return the constraint for the specified edge. 187 */ 188 public Spring getConstraint(String edgeName) 189 { 190 Spring retVal = null; 191 if (edgeName.equals(SpringLayout.NORTH)) 192 retVal = getY(); 193 else if (edgeName.equals(SpringLayout.WEST)) 194 retVal = getX(); 195 else if (edgeName.equals(SpringLayout.SOUTH)) 196 retVal = getSouth(); 197 else if (edgeName.equals(SpringLayout.EAST)) 198 retVal = getEast(); 199 return retVal; 200 } 201 202 /** 203 * Returns the constraint for the height of the component. 204 * 205 * @return the height constraint. 206 */ 207 public Spring getHeight() 208 { 209 if (height != null) 210 return height; 211 else if ((v == null) && (y != null) && (south != null)) 212 v = Spring.sum(south, Spring.minus(y)); 213 return v; 214 } 215 216 /** 217 * Returns the constraint for the width of the component. 218 * 219 * @return the width constraint. 220 */ 221 public Spring getWidth() 222 { 223 if (width != null) 224 return width; 225 else if ((h == null) && (x != null) && (east != null)) 226 h = Spring.sum(east, Spring.minus(x)); 227 return h; 228 } 229 230 /** 231 * Returns the constraint for the left edge of the component. 232 * 233 * @return the left-edge constraint (== WEST). 234 */ 235 public Spring getX() 236 { 237 if (x != null) 238 return x; 239 else if ((h == null) && (width != null) && (east != null)) 240 h = Spring.sum(east, Spring.minus(width)); 241 return h; 242 } 243 244 /** 245 * Returns the constraint for the upper edge of the component. 246 * 247 * @return the upper-edge constraint (== NORTH). 248 */ 249 public Spring getY() 250 { 251 if (y != null) 252 return y; 253 else if ((v == null) && (height != null) && (south != null)) 254 v = Spring.sum(south, Spring.minus(height)); 255 return v; 256 } 257 258 /** 259 * Returns the constraint for the lower edge of the component. 260 * 261 * @return the lower-edge constraint (== SOUTH). 262 */ 263 public Spring getSouth() 264 { 265 if (south != null) 266 return south; 267 else if ((v == null) && (height != null) && (y != null)) 268 v = Spring.sum(y, height); 269 return v; 270 } 271 272 /** 273 * Returns the constraint for the right edge of the component. 274 * 275 * @return the right-edge constraint (== EAST). 276 */ 277 public Spring getEast() 278 { 279 if (east != null) 280 return east; 281 else if ((h == null) && (width != null) && (x != null)) 282 h = Spring.sum(x, width); 283 return h; 284 } 285 286 /** 287 * Sets a constraint for the specified edge. If this leads to an 288 * over-constrained situation, the constraints get adjusted, so that 289 * the mathematics still hold true. 290 * 291 * @param edgeName the name of the edge, one of {@link #EAST}, 292 * {@link #WEST}, {@link #NORTH} or {@link #SOUTH}. 293 * @param s the constraint to be set. 294 */ 295 public void setConstraint(String edgeName, Spring s) 296 { 297 298 if (edgeName.equals(SpringLayout.WEST)) 299 setX(s); 300 else if (edgeName.equals(SpringLayout.NORTH)) 301 setY(s); 302 else if (edgeName.equals(SpringLayout.EAST)) 303 setEast(s); 304 else if (edgeName.equals(SpringLayout.SOUTH)) 305 setSouth(s); 306 307 } 308 309 /** 310 * Sets the height-constraint. 311 * 312 * @param s the constraint to be set. 313 */ 314 public void setHeight(Spring s) 315 { 316 height = s; 317 v = null; 318 if ((south != null) && (y != null) && (height != null)) 319 south = null; 320 } 321 322 /** 323 * Sets the width-constraint. 324 * 325 * @param s the constraint to be set. 326 */ 327 public void setWidth(Spring s) 328 { 329 width = s; 330 h = null; 331 if ((east != null) && (x != null) && (width != null)) 332 east = null; 333 } 334 335 /** 336 * Sets the WEST-constraint. 337 * 338 * @param s the constraint to be set. 339 */ 340 public void setX(Spring s) 341 { 342 x = s; 343 h = null; 344 if ((width != null) && (east != null) && (x != null)) 345 width = null; 346 } 347 348 /** 349 * Sets the NORTH-constraint. 350 * 351 * @param s the constraint to be set. 352 */ 353 public void setY(Spring s) 354 { 355 y = s; 356 v = null; 357 if ((height != null) && (south != null) && (y != null)) 358 height = null; 359 } 360 361 /** 362 * Sets the SOUTH-constraint. 363 * 364 * @param s the constraint to be set. 365 */ 366 public void setSouth(Spring s) 367 { 368 south = s; 369 v = null; 370 if ((height != null) && (south != null) && (y != null)) 371 y = null; 372 } 373 374 /** 375 * Sets the EAST-constraint. 376 * 377 * @param s the constraint to be set. 378 */ 379 public void setEast(Spring s) 380 { 381 east = s; 382 h = null; 383 if ((width != null) && (east != null) && (x != null)) 384 x = null; 385 } 386 387 public void dropCalcResult() 388 { 389 if (x != null) 390 x.setValue(Spring.UNSET); 391 if (y != null) 392 y.setValue(Spring.UNSET); 393 if (width != null) 394 width.setValue(Spring.UNSET); 395 if (height != null) 396 height.setValue(Spring.UNSET); 397 if (east != null) 398 east.setValue(Spring.UNSET); 399 if (south != null) 400 south.setValue(Spring.UNSET); 401 if (h != null) 402 h.setValue(Spring.UNSET); 403 if (v != null) 404 v.setValue(Spring.UNSET); 405 } 406 } 407 408 /** 409 * Creates a new SpringLayout. 410 */ 411 public SpringLayout() 412 { 413 constraintsMap = new HashMap(); 414 } 415 416 /** 417 * Adds a layout component and a constraint object to this layout. 418 * This method is usually only called by a {@link java.awt.Container}s add 419 * method. 420 * 421 * @param component the component to be added. 422 * @param constraint the constraint to be set. 423 */ 424 public void addLayoutComponent(Component component, Object constraint) 425 { 426 constraintsMap.put(component, constraint); 427 } 428 429 /** 430 * Adds a layout component and a constraint object to this layout. 431 * This method is usually only called by a {@link java.awt.Container}s add 432 * method. This method does nothing, since SpringLayout does not manage 433 * String-indexed components. 434 * 435 * @param name the name. 436 * @param c the component to be added. 437 */ 438 public void addLayoutComponent(String name, Component c) 439 { 440 // do nothing here. 441 } 442 443 /** 444 * The trick to SpringLayout is that the network of Springs needs to 445 * completely created before the positioning results are generated. 446 * 447 * Using the springs directly during network creation will set their values 448 * before the network is completed, Using Deferred Springs during creation of 449 * the network allows all the edges to be connected together and the network 450 * to be created without resolving the Springs until their results need to be 451 * known, at which point the network is complete and the spring addition and 452 * and substitution calculations will work on a complete and valid network. 453 * 454 * @author Caolan McNamara (caolanm@redhat.com) 455 */ 456 private static class DeferredSpring extends Spring 457 { 458 private SpringLayout sl; 459 private String edgeName; 460 private Component c; 461 462 public String toString() 463 { 464 return "DeferredSpring of edge" + edgeName + " of " + "something"; 465 } 466 467 public DeferredSpring(SpringLayout s, String edge, Component component) 468 { 469 sl = s; 470 edgeName = edge; 471 c = component; 472 } 473 474 private Spring resolveSpring() 475 { 476 return sl.getConstraints(c).getConstraint(edgeName); 477 } 478 479 public int getMaximumValue() 480 { 481 return resolveSpring().getMaximumValue(); 482 } 483 484 public int getMinimumValue() 485 { 486 return resolveSpring().getMinimumValue(); 487 } 488 489 public int getPreferredValue() 490 { 491 return resolveSpring().getPreferredValue(); 492 } 493 494 public int getValue() 495 { 496 int nRet = resolveSpring().getValue(); 497 if (nRet == Spring.UNSET) 498 nRet = getPreferredValue(); 499 return nRet; 500 } 501 502 public void setValue(int size) 503 { 504 resolveSpring().setValue(size); 505 } 506 } 507 508 private abstract static class DeferredDimension extends Spring 509 { 510 private int value; 511 512 public DeferredDimension() 513 { 514 value = Spring.UNSET; 515 } 516 517 public void setValue(int val) 518 { 519 value = val; 520 } 521 522 public int getValue() 523 { 524 if (value == Spring.UNSET) 525 return getPreferredValue(); 526 return value; 527 } 528 } 529 530 private static class DeferredWidth extends DeferredDimension 531 { 532 private Component c; 533 534 535 public DeferredWidth(Component component) 536 { 537 c = component; 538 } 539 540 public String toString() 541 { 542 return "DeferredWidth of " + "something"; 543 } 544 545 //clip max to a value we can do meaningful calculation with 546 public int getMaximumValue() 547 { 548 int widget_width = c.getMaximumSize().width; 549 return Math.min(Short.MAX_VALUE, widget_width); 550 } 551 552 public int getMinimumValue() 553 { 554 return c.getMinimumSize().width; 555 } 556 557 public int getPreferredValue() 558 { 559 return c.getPreferredSize().width; 560 } 561 } 562 563 private static class DeferredHeight extends DeferredDimension 564 { 565 private Component c; 566 567 public String toString() 568 { 569 return "DeferredHeight of " + "something"; 570 } 571 572 public DeferredHeight(Component component) 573 { 574 c = component; 575 } 576 577 //clip max to a value we can do meaningful calculations with it 578 public int getMaximumValue() 579 { 580 int widget_height = c.getMaximumSize().height; 581 return Math.min(Short.MAX_VALUE, widget_height); 582 } 583 584 public int getMinimumValue() 585 { 586 return c.getMinimumSize().height; 587 } 588 589 public int getPreferredValue() 590 { 591 return c.getPreferredSize().height; 592 } 593 } 594 595 /** 596 * Returns the constraint of the edge named by <code>edgeName</code>. 597 * 598 * @param c the component from which to get the constraint. 599 * @param edgeName the name of the edge, one of {@link #EAST}, 600 * {@link #WEST}, {@link #NORTH} or {@link #SOUTH}. 601 * @return the constraint of the edge <code>edgeName</code> of the 602 * component c. 603 */ 604 public Spring getConstraint(String edgeName, Component c) 605 { 606 return new DeferredSpring(this, edgeName, c); 607 } 608 609 /** 610 * Returns the {@link Constraints} object associated with the specified 611 * component. 612 * 613 * @param c the component for which to determine the constraint. 614 * @return the {@link Constraints} object associated with the specified 615 * component. 616 */ 617 public SpringLayout.Constraints getConstraints(Component c) 618 { 619 Constraints constraints = (Constraints) constraintsMap.get(c); 620 621 if (constraints == null) 622 { 623 constraints = new Constraints(); 624 625 constraints.setWidth(new DeferredWidth(c)); 626 constraints.setHeight(new DeferredHeight(c)); 627 constraints.setX(Spring.constant(0)); 628 constraints.setY(Spring.constant(0)); 629 630 constraintsMap.put(c, constraints); 631 } 632 633 return constraints; 634 } 635 636 /** 637 * Returns the X alignment of the Container <code>p</code>. 638 * 639 * @param p 640 * the {@link java.awt.Container} for which to determine the X 641 * alignment. 642 * @return always 0.0 643 */ 644 public float getLayoutAlignmentX(Container p) 645 { 646 return 0.0F; 647 } 648 649 /** 650 * Returns the Y alignment of the Container <code>p</code>. 651 * 652 * @param p the {@link java.awt.Container} for which to determine the Y 653 * alignment. 654 * @return always 0.0 655 */ 656 public float getLayoutAlignmentY(Container p) 657 { 658 return 0.0F; 659 } 660 661 /** 662 * Recalculate a possibly cached layout. 663 */ 664 public void invalidateLayout(Container p) 665 { 666 // nothing to do here yet 667 } 668 669 private Constraints initContainer(Container p) 670 { 671 Constraints c = getConstraints(p); 672 673 c.setX(Spring.constant(0)); 674 c.setY(Spring.constant(0)); 675 c.setWidth(null); 676 c.setHeight(null); 677 if (c.getEast() == null) 678 c.setEast(Spring.constant(0, 0, Integer.MAX_VALUE)); 679 if (c.getSouth() == null) 680 c.setSouth(Spring.constant(0, 0, Integer.MAX_VALUE)); 681 682 return c; 683 } 684 685 /** 686 * Lays out the container <code>p</code>. 687 * 688 * @param p the container to be laid out. 689 */ 690 public void layoutContainer(Container p) 691 { 692 java.awt.Insets insets = p.getInsets(); 693 694 Component[] components = p.getComponents(); 695 696 Constraints cs = initContainer(p); 697 cs.dropCalcResult(); 698 699 for (int index = 0 ; index < components.length; index++) 700 { 701 Component c = components[index]; 702 getConstraints(c).dropCalcResult(); 703 } 704 705 int offsetX = p.getInsets().left; 706 int offsetY = p.getInsets().right; 707 708 cs.getX().setValue(0); 709 cs.getY().setValue(0); 710 cs.getWidth().setValue(p.getWidth() - offsetX - insets.right); 711 cs.getHeight().setValue(p.getHeight() - offsetY - insets.bottom); 712 713 for (int index = 0; index < components.length; index++) 714 { 715 Component c = components[index]; 716 717 Constraints constraints = getConstraints(c); 718 719 int x = constraints.getX().getValue(); 720 int y = constraints.getY().getValue(); 721 int width = constraints.getWidth().getValue(); 722 int height = constraints.getHeight().getValue(); 723 724 c.setBounds(x + offsetX, y + offsetY, width, height); 725 } 726 } 727 728 /** 729 * Calculates the maximum size of the layed out container. This 730 * respects the maximum sizes of all contained components. 731 * 732 * @param p the container to be laid out. 733 * @return the maximum size of the container. 734 */ 735 public Dimension maximumLayoutSize(Container p) 736 { 737 java.awt.Insets insets = p.getInsets(); 738 739 Constraints cs = initContainer(p); 740 741 int maxX = cs.getWidth().getMaximumValue() + insets.left + insets.right; 742 int maxY = cs.getHeight().getMaximumValue() + insets.top + insets.bottom; 743 744 return new Dimension(maxX, maxY); 745 } 746 747 748 /** 749 * Calculates the minimum size of the layed out container. This 750 * respects the minimum sizes of all contained components. 751 * 752 * @param p the container to be laid out. 753 * @return the minimum size of the container. 754 */ 755 public Dimension minimumLayoutSize(Container p) 756 { 757 java.awt.Insets insets = p.getInsets(); 758 759 Constraints cs = initContainer(p); 760 761 int maxX = cs.getWidth().getMinimumValue() + insets.left + insets.right; 762 int maxY = cs.getHeight().getMinimumValue() + insets.top + insets.bottom; 763 764 return new Dimension(maxX, maxY); 765 } 766 767 /** 768 * Calculates the preferred size of the layed out container. This 769 * respects the preferred sizes of all contained components. 770 * 771 * @param p the container to be laid out. 772 * @return the preferred size of the container. 773 */ 774 public Dimension preferredLayoutSize(Container p) 775 { 776 java.awt.Insets insets = p.getInsets(); 777 778 Constraints cs = initContainer(p); 779 780 int maxX = cs.getWidth().getPreferredValue() + insets.left + insets.right; 781 int maxY = cs.getHeight().getPreferredValue() + insets.top + insets.bottom; 782 783 return new Dimension(maxX, maxY); 784 } 785 786 /** 787 * Attaches the edge <code>e1</code> of component <code>c1</code> to 788 * the edge <code>e2</code> of component <code>c2</code> width the 789 * fixed strut <code>pad</code>. 790 * 791 * @param e1 the edge of component 1. 792 * @param c1 the component 1. 793 * @param pad the space between the components in pixels. 794 * @param e2 the edge of component 2. 795 * @param c2 the component 2. 796 */ 797 public void putConstraint(String e1, Component c1, int pad, String e2, 798 Component c2) 799 { 800 putConstraint(e1, c1, Spring.constant(pad), e2, c2); 801 } 802 803 /** 804 * Attaches the edge <code>e1</code> of component <code>c1</code> to 805 * the edge <code>e2</code> of component <code>c2</code> width the 806 * {@link Spring} <code>s</code>. 807 * 808 * @param e1 the edge of component 1. 809 * @param c1 the component 1. 810 * @param s the space between the components as a {@link Spring} object. 811 * @param e2 the edge of component 2. 812 * @param c2 the component 2. 813 */ 814 public void putConstraint(String e1, Component c1, Spring s, String e2, 815 Component c2) 816 { 817 Constraints constraints1 = getConstraints(c1); 818 819 Spring otherEdge = getConstraint(e2, c2); 820 constraints1.setConstraint(e1, Spring.sum(s, otherEdge)); 821 822 } 823 824 /** 825 * Removes a layout component. 826 * @param c the layout component to remove. 827 */ 828 public void removeLayoutComponent(Component c) 829 { 830 // do nothing here 831 } 832 }