Source for javax.swing.SpringLayout

   1: /* SpringLayout.java -- 
   2:    Copyright (C) 2004 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:      * Creates a new Constraints object.
 115:      * There is no constraint set.
 116:      */
 117:     public Constraints()
 118:     {
 119:       x = y = height = width = east = south = null;
 120:     }
 121: 
 122:     /**
 123:      * Creates a new Constraints object.
 124:      *
 125:      * @param x the constraint for the left edge of the component.
 126:      * @param y the constraint for the upper edge of the component.
 127:      */
 128:     public Constraints(Spring x, Spring y)
 129:     {
 130:       this.x = x;
 131:       this.y = y;
 132:       width = height = east = south = null;
 133:     }
 134: 
 135:     /**
 136:      * Creates a new Constraints object.
 137:      *
 138:      * @param x the constraint for the left edge of the component.
 139:      * @param y the constraint for the upper edge of the component.
 140:      * @param width the constraint for the width of the component.
 141:      * @param height the constraint for the height of the component.
 142:      */
 143:     public Constraints(Spring x, Spring y, Spring width, Spring height)
 144:     {
 145:       this.x = x;
 146:       this.y = y;
 147:       this.width = width;
 148:       this.height = height;
 149:       east = south = null;
 150:     }
 151: 
 152:     /**
 153:      * Returns the constraint for the edge with the <code>edgeName</code>.
 154:      * This is expected to be one of
 155:      * {@link #EAST}, {@link #WEST}, {@link #NORTH} or {@link #SOUTH}.
 156:      *
 157:      * @param edgeName the name of the edge.
 158:      * @return the constraint for the specified edge.
 159:      */
 160:     public Spring getConstraint(String edgeName)
 161:     {
 162:       Spring retVal = null;
 163:       if (edgeName.equals(SpringLayout.NORTH))
 164:     retVal = y;
 165:       else if (edgeName.equals(SpringLayout.WEST))
 166:         retVal = x;
 167:       else if (edgeName.equals(SpringLayout.SOUTH))
 168:         {
 169:           retVal = south;
 170:       if ((retVal == null) && (y != null) && (height != null))
 171:             retVal = Spring.sum(y, height);
 172:         }
 173:       else if (edgeName.equals(SpringLayout.EAST))
 174:         {
 175:           retVal = east;
 176:           if ((retVal == null) && (x != null) && (width != null))
 177:             retVal = Spring.sum(x, width);
 178:     }
 179: 
 180:       return retVal;
 181:     }
 182: 
 183:     /**
 184:      * Returns the constraint for the height of the component.
 185:      *
 186:      * @return the height constraint. 
 187:      */
 188:     public Spring getHeight()
 189:     {
 190:       Spring retVal = height;
 191:       if ((retVal == null) && (y != null) && (south != null))
 192:         {
 193:           retVal = Spring.sum(south, Spring.minus(y));
 194:         }
 195:       return retVal;
 196:     }
 197: 
 198:     /**
 199:      * Returns the constraint for the width of the component.
 200:      *
 201:      * @return the width constraint.
 202:      */
 203:     public Spring getWidth()
 204:     {
 205:       Spring retVal = width;
 206:       if ((retVal == null) && (x != null) && (east != null))
 207:         {
 208:           retVal = Spring.sum(east, Spring.minus(x));
 209:     }
 210:       return retVal;
 211:     }
 212: 
 213:     /**
 214:      * Returns the constraint for the left edge of the component.
 215:      *
 216:      * @return the left-edge constraint (== WEST).
 217:      */
 218:     public Spring getX()
 219:     {
 220:       Spring retVal = x;
 221:       if ((retVal == null) && (width != null) && (east != null))
 222:         {
 223:           retVal = Spring.sum(east, Spring.minus(width));
 224:         }
 225:       return retVal;
 226:     }
 227: 
 228:     /**
 229:      * Returns the constraint for the upper edge of the component.
 230:      *
 231:      * @return the upper-edge constraint (== NORTH).
 232:      */
 233:     public Spring getY()
 234:     {
 235:       Spring retVal = y;
 236:       if ((retVal == null) && (height != null) && (south != null))
 237:         {
 238:           retVal = Spring.sum(south, Spring.minus(height));
 239:         }
 240:       return retVal;
 241:     }
 242: 
 243:     /**
 244:      * Sets a constraint for the specified edge. If this leads to an
 245:      * over-constrained situation, the constraints get adjusted, so that
 246:      * the mathematics still hold true.
 247:      *
 248:      * @param edgeName the name of the edge, one of {@link #EAST},
 249:      *     {@link #WEST}, {@link #NORTH} or {@link #SOUTH}.
 250:      * @param s the constraint to be set.
 251:      */
 252:     public void setConstraint(String edgeName, Spring s)
 253:     {
 254:     
 255:       if (edgeName.equals(SpringLayout.WEST))
 256:         {
 257:           x = s;
 258:       if ((width != null) && (east != null))
 259:             width = Spring.sum(east, Spring.minus(x));
 260:         }
 261:       else if (edgeName.equals(SpringLayout.NORTH))
 262:         {
 263:           y = s;
 264:           if ((height != null) && (south != null))
 265:           height = Spring.sum(south, Spring.minus(y));
 266:         }
 267:       else if (edgeName.equals(SpringLayout.EAST))
 268:         {
 269:           east = s;
 270:           if ((x != null) && (width != null))
 271:             x = Spring.sum(east, Spring.minus(width));
 272:         }
 273:       else if (edgeName.equals(SpringLayout.SOUTH))
 274:         {
 275:           south = s;
 276:           if ((height != null) && (y != null))
 277:         y = Spring.sum(south, Spring.minus(height));
 278:         }
 279: 
 280:     }
 281: 
 282:     /**
 283:      * Sets the height-constraint.
 284:      *
 285:      * @param s the constraint to be set.
 286:      */
 287:     public void setHeight(Spring s)
 288:     {
 289:       height = s;
 290:       if ((south != null) && (y != null))
 291:         south = Spring.sum(y, height);
 292: 
 293:     }
 294: 
 295:     /**
 296:      * Sets the width-constraint.
 297:      *
 298:      * @param s the constraint to be set.
 299:      */
 300:     public void setWidth(Spring s)
 301:     {
 302:       width = s;
 303:       if ((east != null) && (x != null))
 304:         east = Spring.sum(x, width);
 305: 
 306:     }
 307: 
 308:     /**
 309:      * Sets the WEST-constraint.
 310:      *
 311:      * @param s the constraint to be set.
 312:      */
 313:     public void setX(Spring s)
 314:     {
 315:       x = s;
 316:       if ((width != null) && (east != null))
 317:         width = Spring.sum(east, Spring.minus(x));
 318: 
 319:     }
 320: 
 321:     /**
 322:      * Sets the NORTH-constraint.
 323:      *
 324:      * @param s the constraint to be set.
 325:      */
 326:     public void setY(Spring s)
 327:     {
 328:       y = s;
 329:       if ((height != null) && (south != null))
 330:         height = Spring.sum(south, Spring.minus(y));
 331: 
 332:     }
 333:   }
 334: 
 335:   /**
 336:    * Creates a new SpringLayout.
 337:    */
 338:   public SpringLayout()
 339:   {
 340: 
 341:     constraintsMap = new HashMap();
 342:   }
 343: 
 344:   /**
 345:    * Adds a layout component and a constraint object to this layout.
 346:    * This method is usually only called by a {@java.awt.Container}s add
 347:    * Method.
 348:    *
 349:    * @param component the component to be added.
 350:    * @param constraint the constraint to be set.
 351:    */
 352:   public void addLayoutComponent(Component component, Object constraint)
 353:   {
 354:     constraintsMap.put(component, constraint);
 355:   }
 356: 
 357: 
 358:   /**
 359:    * Adds a layout component and a constraint object to this layout.
 360:    * This method is usually only called by a {@java.awt.Container}s add
 361:    * Method. This method does nothing, since SpringLayout does not manage
 362:    * String-indexed components.
 363:    *
 364:    * @param name  the name.
 365:    * @param c the component to be added.
 366:    */
 367:   public void addLayoutComponent(String name, Component c)
 368:   {
 369:     // do nothing here.
 370:   }
 371: 
 372:   /**
 373:    * Returns the constraint of the edge named by <code>edgeName</code>.
 374:    *
 375:    * @param c the component from which to get the constraint.
 376:    * @param edgeName the name of the edge, one of {@link #EAST},
 377:    *     {@link #WEST}, {@link #NORTH} or {@link #SOUTH}.
 378:    * @return the constraint of the edge <code>edgeName</code> of the
 379:    * component c.
 380:    */
 381:   public Spring getConstraint(String edgeName, Component c)
 382:   {
 383:     Constraints constraints = getConstraints(c);
 384:     return constraints.getConstraint(edgeName);
 385:   }
 386: 
 387:   /**
 388:    * Returns the {@link Constraints} object associated with the specified
 389:    * component.
 390:    *
 391:    * @param c the component for which to determine the constraint.
 392:    * @return the {@link Constraints} object associated with the specified
 393:    *      component.
 394:    */
 395:   public SpringLayout.Constraints getConstraints(Component c)
 396:   {
 397:     Constraints constraints = (Constraints) constraintsMap.get(c);
 398:     if (constraints == null)
 399:       {
 400:         Container parent = c.getParent();
 401:         constraints = new Constraints();
 402:         if (parent != null)
 403:           {
 404:             constraints.setX
 405:               (Spring.constant(parent.getInsets().left));
 406:             constraints.setY
 407:               (Spring.constant(parent.getInsets().top));
 408:           }
 409:         else
 410:           {
 411:             constraints.setX
 412:               (Spring.constant(0));
 413:             constraints.setY
 414:               (Spring.constant(0));
 415: 
 416:           }
 417:         constraints.setWidth
 418:           (Spring.constant(c.getMinimumSize().width,
 419:                            c.getPreferredSize().width,
 420:                            c.getMaximumSize().width));
 421:         constraints.setHeight
 422:           (Spring.constant(c.getMinimumSize().height,
 423:                            c.getPreferredSize().height,
 424:                            c.getMaximumSize().height));
 425: 
 426:         constraintsMap.put(c, constraints);
 427: 
 428:       }
 429: 
 430:     return constraints;
 431:   }
 432: 
 433:   /**
 434:    * Returns the X alignment of the Container <code>p</code>.
 435:    *
 436:    * @param p the {@link java.awt.Container} for which to determine the X
 437:    *     alignment.
 438:    * @return always 0.0
 439:    */
 440:   public float getLayoutAlignmentX(Container p)
 441:   {
 442:     return 0.0F;
 443:   }
 444: 
 445:   /**
 446:    * Returns the Y alignment of the Container <code>p</code>.
 447:    *
 448:    * @param p the {@link java.awt.Container} for which to determine the Y
 449:    *     alignment.
 450:    * @return always 0.0
 451:    */
 452:   public float getLayoutAlignmentY(Container p)
 453:   {
 454:     return 0.0F;
 455:   }
 456: 
 457:   /**
 458:    * Recalculate a possibly cached layout.
 459:    */
 460:   public void invalidateLayout(Container p)
 461:   {
 462:     // nothing to do here yet
 463:   }
 464: 
 465:   /**
 466:    * Lays out the container <code>p</code>.
 467:    *
 468:    * @param p the container to be laid out.
 469:    */
 470:   public void layoutContainer(Container p)
 471:   {
 472: 
 473:     addLayoutComponent(p, new Constraints(Spring.constant(0),
 474:                                           Spring.constant(0)));
 475: 
 476:     int offsetX = p.getInsets().left;
 477:     int offsetY = p.getInsets().right;
 478: 
 479:     Component[] components = p.getComponents();
 480:     for (int index = 0; index < components.length; index++)
 481:       {
 482:         Component c = components[index];
 483:         Constraints constraints = getConstraints(c);
 484:         int x = constraints.getX().getValue();
 485:         int y = constraints.getY().getValue();
 486:         int width = constraints.getWidth().getValue();
 487:         int height = constraints.getHeight().getValue();
 488: 
 489:         c.setLocation(x + offsetX, y + offsetY);
 490:         c.setSize(width, height);
 491:       }
 492: 
 493:   }
 494: 
 495:   /**
 496:    * Calculates the maximum size of the layed out container. This
 497:    * respects the maximum sizes of all contained components.
 498:    *
 499:    * @param p the container to be laid out.
 500:    * @return the maximum size of the container.
 501:    */
 502:   public Dimension maximumLayoutSize(Container p)
 503:   {
 504:     int maxX = 0;
 505:     int maxY = 0;
 506: 
 507:     int offsetX = p.getInsets().left;
 508:     int offsetY = p.getInsets().right;
 509: 
 510:     Component[] components = p.getComponents();
 511:     for (int index = 0; index < components.length; index++)
 512:       {
 513:         Component c = components[index];
 514:         Constraints constraints = getConstraints(c);
 515:         int x = constraints.getX().getMaximumValue();
 516:         int y = constraints.getY().getMaximumValue();
 517:         int width = constraints.getWidth().getMaximumValue();
 518:         int height = constraints.getHeight().getMaximumValue();
 519: 
 520:         int rightEdge = offsetX + x + width;
 521:         if (rightEdge > maxX)
 522:           maxX = rightEdge;
 523:         int bottomEdge = offsetY + y + height;
 524:         if (bottomEdge > maxY)
 525:           maxY = bottomEdge;
 526:       }
 527: 
 528:     return new Dimension(maxX, maxY);
 529:   }
 530: 
 531: 
 532:   /**
 533:    * Calculates the minimum size of the layed out container. This
 534:    * respects the minimum sizes of all contained components.
 535:    *
 536:    * @param p the container to be laid out.
 537:    * @return the minimum size of the container.
 538:    */
 539:   public Dimension minimumLayoutSize(Container p)
 540:   {
 541:     int maxX = 0;
 542:     int maxY = 0;
 543: 
 544:     int offsetX = p.getInsets().left;
 545:     int offsetY = p.getInsets().right;
 546: 
 547:     Component[] components = p.getComponents();
 548:     for (int index = 0; index < components.length; index++)
 549:       {
 550:         Component c = components[index];
 551:         Constraints constraints = getConstraints(c);
 552:         int x = constraints.getX().getMinimumValue();
 553:         int y = constraints.getY().getMinimumValue();
 554:         int width = constraints.getWidth().getMinimumValue();
 555:         int height = constraints.getHeight().getMinimumValue();
 556: 
 557:         int rightEdge = offsetX + x + width;
 558:         if (rightEdge > maxX)
 559:           maxX = rightEdge;
 560:         int bottomEdge = offsetY + y + height;
 561:         if (bottomEdge > maxY)
 562:           maxY = bottomEdge;
 563:       }
 564: 
 565:     return new Dimension(maxX, maxY);
 566:   }
 567: 
 568:   /**
 569:    * Calculates the preferred size of the layed out container. This
 570:    * respects the preferred sizes of all contained components.
 571:    *
 572:    * @param p the container to be laid out.
 573:    * @return the preferred size of the container.
 574:    */
 575:   public Dimension preferredLayoutSize(Container p)
 576:   {
 577:     int maxX = 0;
 578:     int maxY = 0;
 579: 
 580:     int offsetX = p.getInsets().left;
 581:     int offsetY = p.getInsets().right;
 582: 
 583:     Component[] components = p.getComponents();
 584:     for (int index = 0; index < components.length; index++)
 585:       {
 586:         Component c = components[index];
 587:         Constraints constraints = getConstraints(c);
 588:         int x = constraints.getX().getPreferredValue();
 589:         int y = constraints.getY().getPreferredValue();
 590:         int width = constraints.getWidth().getPreferredValue();
 591:         int height = constraints.getHeight().getPreferredValue();
 592: 
 593:         int rightEdge = offsetX + x + width;
 594:         if (rightEdge > maxX)
 595:           maxX = rightEdge;
 596:         int bottomEdge = offsetY + y + height;
 597:         if (bottomEdge > maxY)
 598:           maxY = bottomEdge;
 599:       }
 600: 
 601:     return new Dimension(maxX, maxY);
 602:   }
 603: 
 604:   /**
 605:    * Attaches the edge <code>e1</code> of component <code>c1</code> to
 606:    * the edge <code>e2</code> of component <code>c2</code> width the
 607:    * fixed strut <code>pad</code>.
 608:    *
 609:    * @param e1 the edge of component 1.
 610:    * @param c1 the component 1.
 611:    * @param pad the space between the components in pixels.
 612:    * @param e2 the edge of component 2.
 613:    * @param c2 the component 2.
 614:    */
 615:   public void putConstraint(String e1, Component c1, int pad, String e2, 
 616:                             Component c2)
 617:   {
 618:     Constraints constraints1 = getConstraints(c1);
 619:     Constraints constraints2 = getConstraints(c2);
 620: 
 621:     Spring strut = Spring.constant(pad);
 622:     Spring otherEdge = constraints2.getConstraint(e2);
 623:     constraints1.setConstraint(e1, Spring.sum(strut, otherEdge));
 624: 
 625:   }
 626: 
 627:   /**
 628:    * Attaches the edge <code>e1</code> of component <code>c1</code> to
 629:    * the edge <code>e2</code> of component <code>c2</code> width the
 630:    * {@link Spring} <code>s</code>.
 631:    *
 632:    * @param e1 the edge of component 1.
 633:    * @param c1 the component 1.
 634:    * @param s the space between the components as a {@link Spring} object.
 635:    * @param e2 the edge of component 2.
 636:    * @param c2 the component 2.
 637:    */
 638:   public void putConstraint(String e1, Component c1, Spring s, String e2, 
 639:                             Component c2)
 640:   {
 641:     Constraints constraints1 = getConstraints(c1);
 642:     Constraints constraints2 = getConstraints(c2);
 643: 
 644:     Spring otherEdge = constraints2.getConstraint(e2);
 645:     constraints1.setConstraint(e1, Spring.sum(s, otherEdge));
 646: 
 647:   }
 648: 
 649:   /**
 650:    * Removes a layout component.
 651:    * @param c the layout component to remove.
 652:    */
 653:   public void removeLayoutComponent(Component c)
 654:   {
 655:     // do nothing here
 656:   }
 657: }