1: /* SpringLayout.java -- 2: Copyright (C) 2004, 2006, Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing; 40: 41: import java.awt.Component; 42: import java.awt.Container; 43: import java.awt.Dimension; 44: import java.awt.LayoutManager2; 45: import java.util.HashMap; 46: import java.util.Map; 47: 48: /** 49: * A very flexible layout manager. Components are laid out by defining the 50: * relationships between them. The relationships are expressed as 51: * {@link Spring}s. You can attach a Spring for each edge of a component and 52: * link it to an edge of a different component. For example, you can say, 53: * the northern edge of component A should be attached to the southern edge 54: * of component B, and the space between them should be something between 55: * x and y pixels, and preferably z pixels. 56: * <p>While quite simple, this layout manager can be used to emulate most other 57: * layout managers, and can also be used to solve some layout problems, which 58: * would be hard to solve with other layout managers.</p> 59: * 60: * @author Roman Kennke (roman@ontographics.com) 61: */ 62: public class SpringLayout implements LayoutManager2 63: { 64: 65: /** The right edge of a component. */ 66: public static final String EAST = "East"; 67: 68: /** The top edge of a component. */ 69: public static final String NORTH = "North"; 70: 71: /** The bottom edge of a component. */ 72: public static final String SOUTH = "South"; 73: 74: /** The left edge of a component. */ 75: public static final String WEST = "West"; 76: 77: /** maps components to their constraints. */ 78: private Map constraintsMap; 79: 80: /** 81: * The constraints that define the relationships between components. 82: * Each Constraints object can hold 4 Springs: one for each edge of the 83: * component. Additionally it can hold Springs for the components width 84: * and the components height. Since the height and width constraints are 85: * dependend on the other constraints, a component can be over-constraint. 86: * In this case (like when all of NORTH, SOUTH and HEIGHT are constraint), 87: * the values are adjusted, so that the mathematics still hold true. 88: * 89: * @author Roman Kennke (roman@ontographics.com) 90: */ 91: public static class Constraints 92: { 93: 94: // The constraints for each edge, and width and height. 95: /** The Spring for the left edge. */ 96: private Spring x; 97: 98: /** The Spring for the upper edge. */ 99: 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: }