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: 
 399:     if (constraints == null)
 400:       {
 401:         Container parent = c.getParent();
 402:         constraints = new Constraints();
 403: 
 404:         if (parent != null)
 405:           {
 406:             constraints.setX(Spring.constant(parent.getInsets().left));
 407:             constraints.setY(Spring.constant(parent.getInsets().top));
 408:           }
 409:         else
 410:           {
 411:             constraints.setX(Spring.constant(0));
 412:             constraints.setY(Spring.constant(0));
 413:           }
 414:       }
 415:     constraints.setWidth(Spring.constant(c.getMinimumSize().width,
 416:                                          c.getPreferredSize().width,
 417:                                          c.getMaximumSize().width));
 418:     constraints.setHeight(Spring.constant(c.getMinimumSize().height,
 419:                                           c.getPreferredSize().height,
 420:                                           c.getMaximumSize().height));
 421:     constraintsMap.put(c, constraints);
 422: 
 423:     return constraints;
 424:   }
 425: 
 426:   /**
 427:    * Returns the X alignment of the Container <code>p</code>.
 428:    * 
 429:    * @param p
 430:    *          the {@link java.awt.Container} for which to determine the X
 431:    *          alignment.
 432:    * @return always 0.0
 433:    */
 434:   public float getLayoutAlignmentX(Container p)
 435:   {
 436:     return 0.0F;
 437:   }
 438: 
 439:   /**
 440:    * Returns the Y alignment of the Container <code>p</code>.
 441:    *
 442:    * @param p the {@link java.awt.Container} for which to determine the Y
 443:    *     alignment.
 444:    * @return always 0.0
 445:    */
 446:   public float getLayoutAlignmentY(Container p)
 447:   {
 448:     return 0.0F;
 449:   }
 450: 
 451:   /**
 452:    * Recalculate a possibly cached layout.
 453:    */
 454:   public void invalidateLayout(Container p)
 455:   {
 456:     // nothing to do here yet
 457:   }
 458: 
 459:   /**
 460:    * Lays out the container <code>p</code>.
 461:    *
 462:    * @param p the container to be laid out.
 463:    */
 464:   public void layoutContainer(Container p)
 465:   {
 466: 
 467:     addLayoutComponent(p, new Constraints(Spring.constant(0),
 468:                                           Spring.constant(0)));
 469: 
 470:     int offsetX = p.getInsets().left;
 471:     int offsetY = p.getInsets().right;
 472: 
 473:     Component[] components = p.getComponents();
 474:     for (int index = 0; index < components.length; index++)
 475:       {
 476:         Component c = components[index];
 477: 
 478:         Constraints constraints = getConstraints(c);
 479:         int x = constraints.getX().getValue();
 480:         int y = constraints.getY().getValue();
 481:         int width = constraints.getWidth().getValue();
 482:         int height = constraints.getHeight().getValue();
 483: 
 484:         c.setLocation(x + offsetX, y + offsetY);
 485:         c.setSize(width, height);
 486:       }
 487: 
 488:   }
 489: 
 490:   /**
 491:    * Calculates the maximum size of the layed out container. This
 492:    * respects the maximum sizes of all contained components.
 493:    *
 494:    * @param p the container to be laid out.
 495:    * @return the maximum size of the container.
 496:    */
 497:   public Dimension maximumLayoutSize(Container p)
 498:   {
 499:     int maxX = 0;
 500:     int maxY = 0;
 501: 
 502:     int offsetX = p.getInsets().left;
 503:     int offsetY = p.getInsets().right;
 504: 
 505:     Component[] components = p.getComponents();
 506:     for (int index = 0; index < components.length; index++)
 507:       {
 508:         Component c = components[index];
 509:         Constraints constraints = getConstraints(c);
 510:         int x = constraints.getX().getMaximumValue();
 511:         int y = constraints.getY().getMaximumValue();
 512:         int width = constraints.getWidth().getMaximumValue();
 513:         int height = constraints.getHeight().getMaximumValue();
 514: 
 515:         int rightEdge = offsetX + x + width;
 516:         if (rightEdge > maxX)
 517:           maxX = rightEdge;
 518:         int bottomEdge = offsetY + y + height;
 519:         if (bottomEdge > maxY)
 520:           maxY = bottomEdge;
 521:       }
 522: 
 523:     return new Dimension(maxX, maxY);
 524:   }
 525: 
 526: 
 527:   /**
 528:    * Calculates the minimum size of the layed out container. This
 529:    * respects the minimum sizes of all contained components.
 530:    *
 531:    * @param p the container to be laid out.
 532:    * @return the minimum size of the container.
 533:    */
 534:   public Dimension minimumLayoutSize(Container p)
 535:   {
 536:     int maxX = 0;
 537:     int maxY = 0;
 538: 
 539:     int offsetX = p.getInsets().left;
 540:     int offsetY = p.getInsets().right;
 541: 
 542:     Component[] components = p.getComponents();
 543:     for (int index = 0; index < components.length; index++)
 544:       {
 545:         Component c = components[index];
 546:         Constraints constraints = getConstraints(c);
 547:         int x = constraints.getX().getMinimumValue();
 548:         int y = constraints.getY().getMinimumValue();
 549:         int width = constraints.getWidth().getMinimumValue();
 550:         int height = constraints.getHeight().getMinimumValue();
 551: 
 552:         int rightEdge = offsetX + x + width;
 553:         if (rightEdge > maxX)
 554:           maxX = rightEdge;
 555:         int bottomEdge = offsetY + y + height;
 556:         if (bottomEdge > maxY)
 557:           maxY = bottomEdge;
 558:       }
 559: 
 560:     return new Dimension(maxX, maxY);
 561:   }
 562: 
 563:   /**
 564:    * Calculates the preferred size of the layed out container. This
 565:    * respects the preferred sizes of all contained components.
 566:    *
 567:    * @param p the container to be laid out.
 568:    * @return the preferred size of the container.
 569:    */
 570:   public Dimension preferredLayoutSize(Container p)
 571:   {
 572:     int maxX = 0;
 573:     int maxY = 0;
 574: 
 575:     int offsetX = p.getInsets().left;
 576:     int offsetY = p.getInsets().right;
 577: 
 578:     Component[] components = p.getComponents();
 579:     for (int index = 0; index < components.length; index++)
 580:       {
 581:         Component c = components[index];
 582:         Constraints constraints = getConstraints(c);
 583:         int x = constraints.getX().getPreferredValue();
 584:         int y = constraints.getY().getPreferredValue();
 585:         int width = constraints.getWidth().getPreferredValue();
 586:         int height = constraints.getHeight().getPreferredValue();
 587: 
 588:         int rightEdge = offsetX + x + width;
 589:         if (rightEdge > maxX)
 590:           maxX = rightEdge;
 591:         int bottomEdge = offsetY + y + height;
 592:         if (bottomEdge > maxY)
 593:           maxY = bottomEdge;
 594:       }
 595:     return new Dimension(maxX, maxY);
 596:   }
 597: 
 598:   /**
 599:    * Attaches the edge <code>e1</code> of component <code>c1</code> to
 600:    * the edge <code>e2</code> of component <code>c2</code> width the
 601:    * fixed strut <code>pad</code>.
 602:    *
 603:    * @param e1 the edge of component 1.
 604:    * @param c1 the component 1.
 605:    * @param pad the space between the components in pixels.
 606:    * @param e2 the edge of component 2.
 607:    * @param c2 the component 2.
 608:    */
 609:   public void putConstraint(String e1, Component c1, int pad, String e2, 
 610:                             Component c2)
 611:   {
 612:     Constraints constraints1 = getConstraints(c1);
 613:     Constraints constraints2 = getConstraints(c2);
 614: 
 615:     Spring strut = Spring.constant(pad);
 616:     Spring otherEdge = constraints2.getConstraint(e2);
 617:     constraints1.setConstraint(e1, Spring.sum(strut, otherEdge));
 618:   }
 619: 
 620:   /**
 621:    * Attaches the edge <code>e1</code> of component <code>c1</code> to
 622:    * the edge <code>e2</code> of component <code>c2</code> width the
 623:    * {@link Spring} <code>s</code>.
 624:    *
 625:    * @param e1 the edge of component 1.
 626:    * @param c1 the component 1.
 627:    * @param s the space between the components as a {@link Spring} object.
 628:    * @param e2 the edge of component 2.
 629:    * @param c2 the component 2.
 630:    */
 631:   public void putConstraint(String e1, Component c1, Spring s, String e2, 
 632:                             Component c2)
 633:   {
 634:     Constraints constraints1 = getConstraints(c1);
 635:     Constraints constraints2 = getConstraints(c2);
 636: 
 637:     Spring otherEdge = constraints2.getConstraint(e2);
 638:     constraints1.setConstraint(e1, Spring.sum(s, otherEdge));
 639: 
 640:   }
 641: 
 642:   /**
 643:    * Removes a layout component.
 644:    * @param c the layout component to remove.
 645:    */
 646:   public void removeLayoutComponent(Component c)
 647:   {
 648:     // do nothing here
 649:   }
 650: }