Source for javax.swing.border.TitledBorder

   1: /* TitledBorder.java -- 
   2:    Copyright (C) 2003, 2004, 2005  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.border;
  40: 
  41: import java.awt.Color;
  42: import java.awt.Component;
  43: import java.awt.Dimension;
  44: import java.awt.Font;
  45: import java.awt.FontMetrics;
  46: import java.awt.Graphics;
  47: import java.awt.Insets;
  48: import java.awt.Shape;
  49: import java.awt.font.FontRenderContext;
  50: import java.awt.font.LineMetrics;
  51: import java.awt.geom.AffineTransform;
  52: 
  53: import javax.swing.UIManager;
  54: 
  55: 
  56: /**
  57:  * A border that paints a title on top of another border.
  58:  *
  59:  * @author Sascha Brawer (brawer@dandelis.ch)
  60:  */
  61: public class TitledBorder extends AbstractBorder
  62: {
  63:   /**
  64:    * A value for the <code>titlePosition</code> property that vertically
  65:    * positions the title text at the default vertical position, which
  66:    * is in the middle of the top line of the border.
  67:    *
  68:    * @see #getTitlePosition()
  69:    * @see #setTitlePosition(int)
  70:    */
  71:   public static final int DEFAULT_POSITION = 0;
  72: 
  73: 
  74:   /**
  75:    * A value for the <code>titlePosition</code> property that vertically
  76:    * positions the title text above the top line of the border.
  77:    *
  78:    * @see #getTitlePosition()
  79:    * @see #setTitlePosition(int)
  80:    */
  81:   public static final int ABOVE_TOP = 1;
  82: 
  83: 
  84:   /**
  85:    * A value for the <code>titlePosition</code> property that vertically
  86:    * positions the title text at the middle of the top line
  87:    * of the border.
  88:    *
  89:    * @see #getTitlePosition()
  90:    * @see #setTitlePosition(int)
  91:    */
  92:   public static final int TOP = 2;
  93: 
  94: 
  95:   /**
  96:    * A value for the <code>titlePosition</code> property that vertically
  97:    * positions the title text below the top line of the border.
  98:    *
  99:    * @see #getTitlePosition()
 100:    * @see #setTitlePosition(int)
 101:    */
 102:   public static final int BELOW_TOP = 3;
 103: 
 104: 
 105:   /**
 106:    * A value for the <code>titlePosition</code> property that vertically
 107:    * positions the title text above the bottom line of the border.
 108:    *
 109:    * @see #getTitlePosition()
 110:    * @see #setTitlePosition(int)
 111:    */
 112:   public static final int ABOVE_BOTTOM = 4;
 113: 
 114: 
 115:   /**
 116:    * A value for the <code>titlePosition</code> property that vertically
 117:    * positions the title text at the center of the bottom line
 118:    * of the border.
 119:    *
 120:    * @see #getTitlePosition()
 121:    * @see #setTitlePosition(int)
 122:    */
 123:   public static final int BOTTOM = 5;
 124: 
 125: 
 126:   /**
 127:    * A value for the <code>titlePosition</code> property that vertically
 128:    * positions the title text below the bottom line of the border.
 129:    *
 130:    * @see #getTitlePosition()
 131:    * @see #setTitlePosition(int)
 132:    */
 133:   public static final int BELOW_BOTTOM = 6;
 134: 
 135: 
 136:   /**
 137:    * A value for the <code>titleJustification</code> property that
 138:    * horizontally aligns the title text with either the left or the
 139:    * right edge of the border, depending on the orientation of the
 140:    * component nested into the border. If the component orientation
 141:    * is left-to-right, the title text is aligned with the left edge;
 142:    * otherwise, it is aligned with the right edge.  This is the same
 143:    * behavior as with {@link #LEADING}.
 144:    *
 145:    * @see #getTitleJustification()
 146:    * @see #setTitleJustification(int)
 147:    * @see java.awt.ComponentOrientation#isLeftToRight()
 148:    */
 149:   public static final int DEFAULT_JUSTIFICATION = 0;
 150: 
 151: 
 152:   /**
 153:    * A value for the <code>titleJustification</code> property that
 154:    * horizontally aligns the title text with the left-hand edge of
 155:    * the border.
 156:    *
 157:    * @see #getTitleJustification()
 158:    * @see #setTitleJustification(int)
 159:    */
 160:   public static final int LEFT = 1;
 161: 
 162: 
 163:   /**
 164:    * A value for the <code>titleJustification</code> property that
 165:    * horizontally aligns the title text with the center of the border.
 166:    *
 167:    * @see #getTitleJustification()
 168:    * @see #setTitleJustification(int)
 169:    */
 170:   public static final int CENTER = 2;
 171: 
 172: 
 173:   /**
 174:    * A value for the <code>titleJustification</code> property that
 175:    * horizontally aligns the title text with the right-hand edge of
 176:    * the border.
 177:    *
 178:    * @see #getTitleJustification()
 179:    * @see #setTitleJustification(int)
 180:    */
 181:   public static final int RIGHT = 3;
 182: 
 183: 
 184:   /**
 185:    * A value for the <code>titleJustification</code> property that
 186:    * horizontally aligns the title text with either the left or the
 187:    * right edge of the border, depending on the orientation of the
 188:    * component nested into the border. If the component orientation
 189:    * is left-to-right, the title text is aligned with the left edge;
 190:    * otherwise, it is aligned with the right edge. This is the same
 191:    * behavior as with {@link #DEFAULT_JUSTIFICATION}.
 192:    *
 193:    * @see #getTitleJustification()
 194:    * @see #setTitleJustification(int)
 195:    * @see java.awt.ComponentOrientation#isLeftToRight()
 196:    */
 197:   public static final int LEADING = 4;
 198: 
 199: 
 200:   /**
 201:    * A value for the <code>titleJustification</code> property that
 202:    * horizontally aligns the title text with either the right or the
 203:    * left edge of the border, depending on the orientation of the
 204:    * component nested into the border. If the component orientation
 205:    * is left-to-right, the title text is aligned with the right edge;
 206:    * otherwise, it is aligned with the left edge.
 207:    *
 208:    * @see #getTitleJustification()
 209:    * @see #setTitleJustification(int)
 210:    * @see java.awt.ComponentOrientation#isLeftToRight()
 211:    */
 212:   public static final int TRAILING = 5;
 213: 
 214: 
 215:   /**
 216:    * The number of pixels between the inside of {@link #border}
 217:    * and the bordered component.
 218:    */
 219:   protected static final int EDGE_SPACING = 2;
 220: 
 221: 
 222:   /**
 223:    * The number of pixels between the outside of this TitledBorder
 224:    * and the beginning (if left-aligned) or end (if right-aligned)
 225:    * of the title text.
 226:    */
 227:   protected static final int TEXT_INSET_H = 5;
 228: 
 229: 
 230:   /**
 231:    * The number of pixels between the title text and {@link #border}.
 232:    * This value is only relevant if the title text does not intersect
 233:    * {@link #border}. No intersection occurs if {@link #titlePosition}
 234:    * is one of {@link #ABOVE_TOP}, {@link #BELOW_TOP}, {@link #ABOVE_BOTTOM},
 235:    * or {@link #BELOW_BOTTOM}.
 236:    */
 237:   protected static final int TEXT_SPACING = 2;
 238: 
 239: 
 240:   /**
 241:    * Determined using the <code>serialver</code> tool of Apple/Sun JDK 1.3.1
 242:    * on MacOS X 10.1.5.
 243:    */
 244:   static final long serialVersionUID = 8012999415147721601L;
 245:   
 246: 
 247:   /**
 248:    * The title, or <code>null</code> to display no title.
 249:    */
 250:   protected String title;
 251: 
 252: 
 253:   /**
 254:    * The border underneath the title. If this value is
 255:    * <code>null</code>, the border will be retrieved from the {@link
 256:    * javax.swing.UIManager}&#x2019;s defaults table using the key
 257:    * <code>TitledBorder.border</code>.
 258:    */
 259:   protected Border border;
 260: 
 261:   
 262:   /**
 263:    * The vertical position of the title text relative to the border,
 264:    * which is one of {@link #ABOVE_TOP}, {@link #TOP}, {@link
 265:    * #BELOW_TOP}, {@link #ABOVE_BOTTOM}, {@link #BOTTOM}, {@link
 266:    * #BELOW_BOTTOM}, or {@link #DEFAULT_POSITION}.
 267:    */
 268:   protected int titlePosition;
 269: 
 270: 
 271:   /**
 272:    * The horizontal alignment of the title text in relation to the
 273:    * border, which is one of {@link #LEFT}, {@link #CENTER}, {@link
 274:    * #RIGHT}, {@link #LEADING}, {@link #TRAILING}, or {@link
 275:    * #DEFAULT_JUSTIFICATION}.
 276:    */
 277:   protected int titleJustification;
 278: 
 279: 
 280:   /**
 281:    * The font for displaying the title text. If this value is
 282:    * <code>null</code>, the font will be retrieved from the {@link
 283:    * javax.swing.UIManager}&#x2019;s defaults table using the key
 284:    * <code>TitledBorder.font</code>.
 285:    */
 286:   protected Font titleFont;
 287: 
 288: 
 289:   /**
 290:    * The color for displaying the title text. If this value is
 291:    * <code>null</code>, the color will be retrieved from the {@link
 292:    * javax.swing.UIManager}&#x2019;s defaults table using the key
 293:    * <code>TitledBorder.titleColor</code>.
 294:    */
 295:   protected Color titleColor;
 296: 
 297: 
 298:   /**
 299:    * Constructs a TitledBorder given the text of its title.
 300:    *
 301:    * @param title the title text, or <code>null</code> to use no title text.
 302:    */
 303:   public TitledBorder(String title)
 304:   {
 305:     this(/* border */ null,
 306:          title, LEADING, TOP,
 307:          /* titleFont */ null, /* titleColor */ null);
 308:   }
 309: 
 310: 
 311:   /**
 312:    * Constructs an initially untitled TitledBorder given another border.
 313:    *
 314:    * @param border the border underneath the title, or <code>null</code>
 315:    *        to use a default from the current look and feel.
 316:    */
 317:   public TitledBorder(Border border)
 318:   {
 319:     this(border, /* title */ "", LEADING, TOP,
 320:          /* titleFont */ null, /* titleColor */ null);
 321:   }
 322:   
 323: 
 324:   /**
 325:    * Constructs a TitledBorder given its border and title text.
 326:    *
 327:    * @param border the border underneath the title, or <code>null</code>
 328:    *        to use a default from the current look and feel.
 329:    *
 330:    * @param title the title text, or <code>null</code> to use no title
 331:    *        text.
 332:    */
 333:   public TitledBorder(Border border, String title)
 334:   {
 335:     this(border, title, LEADING, TOP,
 336:          /* titleFont */ null, /* titleColor */ null);
 337:   }
 338:   
 339: 
 340:   /**
 341:    * Constructs a TitledBorder given its border, title text, horizontal
 342:    * alignment, and vertical position.
 343:    *
 344:    * @param border the border underneath the title, or <code>null</code>
 345:    *        to use a default from the current look and feel.
 346:    *
 347:    * @param title the title text, or <code>null</code> to use no title
 348:    *        text.
 349:    *
 350:    * @param titleJustification the horizontal alignment of the title
 351:    *        text in relation to the border. The value must be one of
 352:    *        {@link #LEFT}, {@link #CENTER}, {@link #RIGHT}, {@link #LEADING},
 353:    *        {@link #TRAILING}, or {@link #DEFAULT_JUSTIFICATION}.
 354:    
 355:    * @param titlePosition the vertical position of the title text
 356:    *        in relation to the border. The value must be one of
 357:    *        {@link #ABOVE_TOP}, {@link #TOP}, {@link #BELOW_TOP},
 358:    *        {@link #ABOVE_BOTTOM}, {@link #BOTTOM}, {@link #BELOW_BOTTOM},
 359:    *        or {@link #DEFAULT_POSITION}.
 360:    *
 361:    * @throws IllegalArgumentException if <code>titleJustification</code>
 362:    *         or <code>titlePosition</code> have an unsupported value.
 363:    */
 364:   public TitledBorder(Border border, String title, int titleJustification,
 365:                       int titlePosition)
 366:   {
 367:     this(border, title, titleJustification, titlePosition,
 368:          /* titleFont */ null, /* titleColor */ null);
 369:   }
 370:   
 371: 
 372:   /**
 373:    * Constructs a TitledBorder given its border, title text, horizontal
 374:    * alignment, vertical position, and font.
 375:    *
 376:    * @param border the border underneath the title, or <code>null</code>
 377:    *        to use a default from the current look and feel.
 378:    *
 379:    * @param title the title text, or <code>null</code> to use no title
 380:    *        text.
 381:    *
 382:    * @param titleJustification the horizontal alignment of the title
 383:    *        text in relation to the border. The value must be one of
 384:    *        {@link #LEFT}, {@link #CENTER}, {@link #RIGHT}, {@link #LEADING},
 385:    *        {@link #TRAILING}, or {@link #DEFAULT_JUSTIFICATION}.
 386:    *
 387:    * @param titlePosition the vertical position of the title text
 388:    *        in relation to the border. The value must be one of
 389:    *        {@link #ABOVE_TOP}, {@link #TOP}, {@link #BELOW_TOP},
 390:    *        {@link #ABOVE_BOTTOM}, {@link #BOTTOM}, {@link #BELOW_BOTTOM},
 391:    *        or {@link #DEFAULT_POSITION}.
 392:    *
 393:    * @param titleFont the font for the title text, or <code>null</code>
 394:    *        to use a default from the current look and feel.
 395:    *
 396:    * @throws IllegalArgumentException if <code>titleJustification</code>
 397:    *         or <code>titlePosition</code> have an unsupported value.
 398:    */
 399:   public TitledBorder(Border border, String title, int titleJustification,
 400:                       int titlePosition, Font titleFont)
 401:   {
 402:     this(border, title, titleJustification, titlePosition, titleFont,
 403:          /* titleColor */ null);
 404:   }
 405:   
 406: 
 407:   /**
 408:    * Constructs a TitledBorder given its border, title text, horizontal
 409:    * alignment, vertical position, font, and color.
 410:    *
 411:    * @param border the border underneath the title, or <code>null</code>
 412:    *        to use a default from the current look and feel.
 413:    *
 414:    * @param title the title text, or <code>null</code> to use no title
 415:    *        text.
 416:    *
 417:    * @param titleJustification the horizontal alignment of the title
 418:    *        text in relation to the border. The value must be one of
 419:    *        {@link #LEFT}, {@link #CENTER}, {@link #RIGHT}, {@link #LEADING},
 420:    *        {@link #TRAILING}, or {@link #DEFAULT_JUSTIFICATION}.
 421:    *
 422:    * @param titlePosition the vertical position of the title text
 423:    *        in relation to the border. The value must be one of
 424:    *        {@link #ABOVE_TOP}, {@link #TOP}, {@link #BELOW_TOP},
 425:    *        {@link #ABOVE_BOTTOM}, {@link #BOTTOM}, {@link #BELOW_BOTTOM},
 426:    *        or {@link #DEFAULT_POSITION}.
 427:    *
 428:    * @param titleFont the font for the title text, or <code>null</code>
 429:    *        to use a default from the current look and feel.
 430:    *
 431:    * @param titleColor the color for the title text, or <code>null</code>
 432:    *        to use a default from the current look and feel.
 433:    *
 434:    * @throws IllegalArgumentException if <code>titleJustification</code>
 435:    *         or <code>titlePosition</code> have an unsupported value.
 436:    */
 437:   public TitledBorder(Border border, String title, int titleJustification,
 438:                       int titlePosition, Font titleFont, Color titleColor)
 439:   {
 440:     this.border = border;
 441:     this.title = title;
 442: 
 443:     /* Invoking the setter methods ensures that the newly constructed
 444:      * TitledBorder has valid property values.
 445:      */
 446:     setTitleJustification(titleJustification);
 447:     setTitlePosition(titlePosition);
 448: 
 449:     this.titleFont = titleFont;
 450:     this.titleColor = titleColor;
 451:   }
 452:   
 453:   
 454:   /**
 455:    * Paints the border and the title text.
 456:    *
 457:    * @param c the component whose border is to be painted.
 458:    * @param g the graphics for painting.
 459:    * @param x the horizontal position for painting the border.
 460:    * @param y the vertical position for painting the border.
 461:    * @param width the width of the available area for painting the border.
 462:    * @param height the height of the available area for painting the border.
 463:    */
 464:   public void paintBorder(Component c, Graphics  g, 
 465:                           int x, int y, int width, int height)
 466:   {
 467:     Measurements mes = getMeasurements(c);
 468:     Font oldFont = g.getFont();
 469:     Color oldColor = g.getColor();
 470: 
 471:     /**
 472:      * A local helper class for painting the border without changing
 473:      * any pixels inside the rectangle of the title text.
 474:      */
 475:     class BorderPainter
 476:     {
 477:       private Component c;
 478:       private Border b;
 479:       private int x, y, width, height;
 480: 
 481:       /**
 482:        * Constructs a BorderPainter.
 483:        *
 484:        * @param c the component whose border is being painted.
 485:        * @param b the border object.
 486:        * @param x the x coordinate of the rectangle delimiting the border.
 487:        * @param y the y coordinate of the rectangle delimiting the border.
 488:        * @param width the width of the rectangle delimiting the border.
 489:        * @param height the width of the rectangle delimiting the border.
 490:        */
 491:       public BorderPainter(Component c, Border b,
 492:                            int x, int y, int width, int height)
 493:       {
 494:         this.c = c;
 495:         this.b = b;
 496:         this.x = x;
 497:         this.y = y;
 498:         this.width = width;
 499:         this.height = height;
 500:       }
 501: 
 502: 
 503:       /**
 504:        * Paints the entire border.
 505:        */
 506:       public void paint(Graphics g)
 507:       {
 508:         if (b != null)
 509:           b.paintBorder(c, g, x, y, width, height);
 510:       }
 511: 
 512: 
 513:       /**
 514:        * Paints the border, clipping the drawing operation to a
 515:        * given rectangular area.
 516:        */
 517:       private void paint(Graphics g,
 518:                          int clipX, int clipY, int clipWidth, int clipHeight)
 519:       {
 520:         Shape oldClip = g.getClip();
 521:         try
 522:         {
 523:           g.clipRect(clipX, clipY, clipWidth, clipHeight);
 524:           paint(g);
 525:         }
 526:         finally
 527:         {
 528:           g.setClip(oldClip);
 529:         }
 530:       }
 531: 
 532: 
 533:       /**
 534:        * Paints the border without affecting a given rectangular area.
 535:        * This is used for painting the border without drawing anything
 536:        * underneath the title text.
 537:        *
 538:        * <p>Since we do not want to introduce unnecessary dependencies
 539:        * on Java 2D, we perform the clipping without constructive geometry
 540:        * (provided by java.awt.geom.Area). Instead, the border&#x2019;s
 541:        * bounding rectangle is split into smaller parts, which are then
 542:        * clipped and painted individually.:
 543:        *
 544:        * <p><pre>
 545:        *    +--------------------+          +--------------------+
 546:        *    |                    |          |        1           |
 547:        *    |   +--------+       |          +---+--------+-------+
 548:        *    |   | hole   |       |  |====>  | 2 | hole   |   3   |
 549:        *    |   +--------+       |          |---+--------+-------+
 550:        *    |                    |          |        4           |
 551:        *    +--------------------+          +--------------------+</pre>
 552:        *
 553:        */
 554:       public void paintExcept(Graphics g,
 555:                               int holeX, int holeY, int holeWidth, int holeHeight)
 556:       {
 557:         int stripeHeight;
 558: 
 559:         stripeHeight = holeY - y;
 560:         if (stripeHeight > 0)
 561:           paint(g, x, y, width, stripeHeight);   // patch #1 in the image above
 562: 
 563:         stripeHeight = holeHeight;
 564:         if (stripeHeight > 0)
 565:         {
 566:           paint(g, x, holeY, holeX - x, stripeHeight);  // patches #2 and #3
 567:           paint(g, holeX + holeWidth, holeY, x + width - (holeX + holeWidth), stripeHeight);
 568:         }
 569: 
 570:         stripeHeight = height - (holeY - y + holeHeight);
 571:         if (stripeHeight > 0)
 572:           paint(g, x, y + height - stripeHeight, width, stripeHeight); // #4
 573:       }
 574:     };
 575: 
 576:     BorderPainter bp;
 577:     int textX, textY, borderWidth, borderHeight;
 578: 
 579:     borderWidth = width - (mes.outerSpacing.left + mes.outerSpacing.right);
 580:     borderHeight = height - (mes.outerSpacing.top + mes.outerSpacing.bottom);
 581:     bp = new BorderPainter(c, getBorder(),
 582:                            x + mes.outerSpacing.left, y + mes.outerSpacing.top,
 583:                            borderWidth, borderHeight);
 584: 
 585:     switch (getRealTitleJustification(c))
 586:     {
 587:     case LEFT:
 588:       textX = x + EDGE_SPACING + TEXT_INSET_H;
 589:       break;
 590: 
 591:     case CENTER:
 592:       textX = x + (borderWidth - mes.textWidth) / 2;
 593:       break;
 594: 
 595:     case RIGHT:
 596:       textX = x + borderWidth - (mes.textWidth + TEXT_INSET_H);
 597:       break;
 598: 
 599:     default:
 600:       throw new IllegalStateException();
 601:     }
 602: 
 603:     switch (titlePosition)
 604:     {
 605:     case ABOVE_TOP:
 606:       textY = y + EDGE_SPACING;
 607:       break;
 608: 
 609:     case TOP:
 610:     case DEFAULT_POSITION:
 611:     default:
 612:       textY = y + mes.outerSpacing.top + mes.borderInsets.top - mes.textAscent
 613:               + mes.lineHeight;
 614:       break;
 615: 
 616:     case BELOW_TOP:
 617:       textY = y + mes.outerSpacing.top + mes.borderInsets.top + TEXT_SPACING;
 618:       break;
 619: 
 620:     case ABOVE_BOTTOM:
 621:       textY = y + height - mes.outerSpacing.bottom - mes.borderInsets.bottom
 622:         - TEXT_SPACING - (mes.textAscent + mes.textDescent);
 623:       break;
 624: 
 625:     case BOTTOM:
 626:     case BELOW_BOTTOM:
 627:       textY = y + height - (mes.textAscent + mes.textDescent);
 628:       break;
 629:     }
 630: 
 631:     if (mes.trimmedText == null)
 632:       bp.paint(g);
 633:     else
 634:     {
 635:       try
 636:       {
 637:         g.setFont(mes.font);
 638:         g.setColor(getTitleColor());
 639:         g.drawString(mes.trimmedText, textX, textY + mes.textAscent);
 640:       }
 641:       finally
 642:       {
 643:         g.setFont(oldFont);
 644:         g.setColor(oldColor);
 645:       }
 646:       bp.paintExcept(g, textX, textY,
 647:                      mes.textWidth, mes.textAscent + mes.textDescent);
 648:     }
 649:   }
 650:   
 651:   
 652:   /**
 653:    * Measures the width of this border.
 654:    *
 655:    * @param c the component whose border is to be measured.
 656:    *
 657:    * @return an Insets object whose <code>left</code>, <code>right</code>,
 658:    *         <code>top</code> and <code>bottom</code> fields indicate the
 659:    *         width of the border at the respective edge.
 660:    *
 661:    * @see #getBorderInsets(java.awt.Component, java.awt.Insets)
 662:    */
 663:   public Insets getBorderInsets(Component c)
 664:   {
 665:     return getBorderInsets(c, new Insets(0, 0, 0, 0));
 666:   }
 667:   
 668: 
 669:   /**
 670:    * Measures the width of this border, storing the results into a
 671:    * pre-existing Insets object.
 672:    *
 673:    * @param insets an Insets object for holding the result values.
 674:    *        After invoking this method, the <code>left</code>,
 675:    *        <code>right</code>, <code>top</code> and
 676:    *        <code>bottom</code> fields indicate the width of the
 677:    *        border at the respective edge.
 678:    *
 679:    * @return the same object that was passed for <code>insets</code>.
 680:    *
 681:    * @see #getBorderInsets(Component)
 682:    */
 683:   public Insets getBorderInsets(Component c, Insets insets)
 684:   {
 685:     return getMeasurements(c).getContentInsets(insets);
 686:   }
 687:   
 688:   
 689:   /**
 690:    * Returns <code>false</code>, indicating that there are pixels inside
 691:    * the area of this border where the background shines through.
 692:    *
 693:    * @return <code>false</code>.
 694:    */
 695:   public boolean isBorderOpaque()
 696:   {
 697:     /* Note that the AbstractBorder.isBorderOpaque would also return
 698:      * false, so there is actually no need to override the inherited
 699:      * implementation. However, GNU Classpath strives for exact
 700:      * compatibility with the Sun reference implementation, which
 701:      * overrides isBorderOpaque for unknown reasons.
 702:      */
 703:     return false;
 704:   }
 705: 
 706: 
 707:   /**
 708:    * Returns the text of the title.
 709:    *
 710:    * @return the title text, or <code>null</code> if no title is
 711:    *         displayed.
 712:    */
 713:   public String getTitle()
 714:   {
 715:     return title;
 716:   }
 717: 
 718: 
 719:   /**
 720:    * Retrieves the border underneath the title. If no border has been
 721:    * set, or if it has been set to<code>null</code>, the current
 722:    * {@link javax.swing.LookAndFeel} will be asked for a border
 723:    * using the key <code>TitledBorder.border</code>.
 724:    *
 725:    * @return a border, or <code>null</code> if the current LookAndFeel
 726:    *         does not provide a border for the key
 727:    *         <code>TitledBorder.border</code>.
 728:    *
 729:    * @see javax.swing.UIManager#getBorder(Object)
 730:    */
 731:   public Border getBorder()
 732:   {
 733:     if (border != null)
 734:       return border;
 735: 
 736:     return UIManager.getBorder("TitledBorder.border");
 737:   }
 738: 
 739: 
 740:   /**
 741:    * Returns the vertical position of the title text in relation
 742:    * to the border.
 743:    *
 744:    * @return one of the values {@link #ABOVE_TOP}, {@link #TOP},
 745:    *         {@link #BELOW_TOP}, {@link #ABOVE_BOTTOM}, {@link #BOTTOM},
 746:    *         {@link #BELOW_BOTTOM}, or {@link #DEFAULT_POSITION}.
 747:    */
 748:   public int getTitlePosition()
 749:   {
 750:     return titlePosition;
 751:   }
 752: 
 753: 
 754:   /**
 755:    * Returns the horizontal alignment of the title text in relation to
 756:    * the border.
 757:    *
 758:    * @return one of the values {@link #LEFT}, {@link #CENTER}, {@link
 759:    *         #RIGHT}, {@link #LEADING}, {@link #TRAILING}, or {@link
 760:    *         #DEFAULT_JUSTIFICATION}.
 761:    */
 762:   public int getTitleJustification()
 763:   {
 764:     return titleJustification;
 765:   }
 766: 
 767: 
 768:   /**
 769:    * Retrieves the font for displaying the title text. If no font has
 770:    * been set, or if it has been set to<code>null</code>, the current
 771:    * {@link javax.swing.LookAndFeel} will be asked for a font
 772:    * using the key <code>TitledBorder.font</code>.
 773:    *
 774:    * @return a font, or <code>null</code> if the current LookAndFeel
 775:    *         does not provide a font for the key
 776:    *         <code>TitledBorder.font</code>.
 777:    *
 778:    * @see javax.swing.UIManager#getFont(Object)
 779:    */
 780:   public Font getTitleFont()
 781:   {
 782:     if (titleFont != null)
 783:       return titleFont;
 784: 
 785:     return UIManager.getFont("TitledBorder.font");
 786:   }
 787: 
 788: 
 789:   /**
 790:    * Retrieves the color for displaying the title text. If no color has
 791:    * been set, or if it has been set to<code>null</code>, the current
 792:    * {@link javax.swing.LookAndFeel} will be asked for a color
 793:    * using the key <code>TitledBorder.titleColor</code>.
 794:    *
 795:    * @return a color, or <code>null</code> if the current LookAndFeel
 796:    *         does not provide a color for the key
 797:    *         <code>TitledBorder.titleColor</code>.
 798:    *
 799:    * @see javax.swing.UIManager#getColor(Object)
 800:    */
 801:   public Color getTitleColor()
 802:   {
 803:     if (titleColor != null)
 804:       return titleColor;
 805: 
 806:     return UIManager.getColor("TitledBorder.titleColor");
 807:   }
 808: 
 809: 
 810:   /**
 811:    * Sets the text of the title.
 812:    *
 813:    * @param title the new title text, or <code>null</code> for displaying
 814:    *        no text at all.
 815:    */
 816:   public void setTitle(String title)
 817:   {
 818:     // Swing borders are not JavaBeans, thus no need to fire an event.
 819:     this.title = title;
 820:   }
 821: 
 822: 
 823:   /**
 824:    * Sets the border underneath the title.
 825:    *
 826:    * @param border a border, or <code>null</code> to use the
 827:    *        border that is supplied by the current LookAndFeel.
 828:    *
 829:    * @see #getBorder()
 830:    */
 831:   public void setBorder(Border border)
 832:   {
 833:     // Swing borders are not JavaBeans, thus no need to fire an event.
 834:     this.border = border;
 835:   }
 836: 
 837: 
 838:   /**
 839:    * Sets the vertical position of the title text in relation
 840:    * to the border.
 841:    *
 842:    * @param titlePosition one of the values {@link #ABOVE_TOP},
 843:    *        {@link #TOP}, {@link #BELOW_TOP}, {@link #ABOVE_BOTTOM},
 844:    *        {@link #BOTTOM}, {@link #BELOW_BOTTOM},
 845:    *        or {@link #DEFAULT_POSITION}.
 846:    *
 847:    * @throws IllegalArgumentException if an unsupported value is passed
 848:    *         for <code>titlePosition</code>.
 849:    */
 850:   public void setTitlePosition(int titlePosition)
 851:   {
 852:     if ((titlePosition < DEFAULT_POSITION) || (titlePosition > BELOW_BOTTOM))
 853:       throw new IllegalArgumentException();
 854: 
 855:     // Swing borders are not JavaBeans, thus no need to fire an event.
 856:     this.titlePosition = titlePosition;
 857:   }
 858: 
 859: 
 860:   /**
 861:    * Sets the horizontal alignment of the title text in relation to the border.
 862:    *
 863:    * @param titleJustification the new alignment, which must be one of
 864:    *        {@link #LEFT}, {@link #CENTER}, {@link #RIGHT}, {@link #LEADING},
 865:    *        {@link #TRAILING}, or {@link #DEFAULT_JUSTIFICATION}.
 866:    *
 867:    * @throws IllegalArgumentException if an unsupported value is passed
 868:    *         for <code>titleJustification</code>.
 869:    */
 870:   public void setTitleJustification(int titleJustification)
 871:   {
 872:     if ((titleJustification < DEFAULT_JUSTIFICATION)
 873:         || (titleJustification > TRAILING))
 874:       throw new IllegalArgumentException();
 875: 
 876:     // Swing borders are not JavaBeans, thus no need to fire an event.
 877:     this.titleJustification = titleJustification;
 878:   }
 879: 
 880: 
 881:   /**
 882:    * Sets the font for displaying the title text.
 883:    *
 884:    * @param titleFont the font, or <code>null</code> to use the font
 885:    *        provided by the current {@link javax.swing.LookAndFeel}.
 886:    *
 887:    * @see #getTitleFont()
 888:    */
 889:   public void setTitleFont(Font titleFont)
 890:   {
 891:     // Swing borders are not JavaBeans, thus no need to fire an event.
 892:     this.titleFont = titleFont;
 893:   }
 894: 
 895: 
 896:   /**
 897:    * Sets the color for displaying the title text.
 898:    *
 899:    * @param titleColor the color, or <code>null</code> to use the color
 900:    *        provided by the current {@link javax.swing.LookAndFeel}.
 901:    *
 902:    * @see #getTitleColor()
 903:    */
 904:   public void setTitleColor(Color titleColor)
 905:   {
 906:     // Swing borders are not JavaBeans, thus no need to fire an event.
 907:     this.titleColor = titleColor;
 908:   }
 909: 
 910: 
 911:   /**
 912:    * Calculates the minimum size needed for displaying the border
 913:    * and its title.
 914:    *
 915:    * @param c the Component for which this TitledBorder consitutes
 916:    *        a border.
 917:    */
 918:   public Dimension getMinimumSize(Component c)
 919:   {
 920:     return getMeasurements(c).getMinimumSize();
 921:   }
 922: 
 923: 
 924:   /**
 925:    * Returns the font that is used for displaying the title text for
 926:    * a given Component.
 927:    *
 928:    * @param c the Component for which this TitledBorder is the border.
 929:    *
 930:    * @return The font returned by {@link #getTitleFont()}, or a fallback
 931:    *         if {@link #getTitleFont()} returned <code>null</code>.
 932:    */
 933:   protected Font getFont(Component c)
 934:   {
 935:     Font f;
 936: 
 937:     f = getTitleFont();
 938:     if (f != null)
 939:       return f;
 940: 
 941:     return new Font("Dialog", Font.PLAIN, 12);
 942:   }
 943: 
 944: 
 945:   /**
 946:    * Returns the horizontal alignment of the title text in relation to
 947:    * the border, mapping the component-dependent alignment constants
 948:    * {@link #LEADING}, {@link #TRAILING} and {@link #DEFAULT_JUSTIFICATION}
 949:    * to the correct value according to the embedded component&#x2019;s
 950:    * orientation.
 951:    *
 952:    * @param c the Component for which this TitledBorder is the border.
 953:    *
 954:    * @return one of the values {@link #LEFT}, {@link #CENTER}, or {@link
 955:    *         #RIGHT}.
 956:    */
 957:   private int getRealTitleJustification(Component c)
 958:   {
 959:     switch (titleJustification)
 960:     {
 961:     case DEFAULT_JUSTIFICATION:
 962:     case LEADING:
 963:       if ((c == null) || c.getComponentOrientation().isLeftToRight())
 964:         return LEFT;
 965:       else
 966:         return RIGHT;
 967: 
 968:     case TRAILING:
 969:       if ((c == null) || c.getComponentOrientation().isLeftToRight())
 970:         return RIGHT;
 971:       else
 972:         return LEFT;
 973: 
 974:     default:
 975:       return titleJustification;
 976:     }
 977:   }
 978: 
 979: 
 980:   /**
 981:    * Performs various measurements for the current state of this TitledBorder
 982:    * and the given Component.
 983:    */
 984:   private Measurements getMeasurements(Component c)
 985:   {
 986:     Measurements m = new Measurements();
 987:     FontMetrics fmet;
 988: 
 989:     m.font = getFont(c);
 990:     fmet = c.getFontMetrics(m.font);
 991:     m.border = getBorder();
 992:     if (m.border != null)
 993:       m.borderInsets = m.border.getBorderInsets(c);
 994:     else
 995:       m.borderInsets = new Insets(0, 0, 0, 0);
 996: 
 997:     if (title != null)
 998:     {
 999:       m.trimmedText = title.trim();
1000:       if (m.trimmedText.length() == 0)
1001:         m.trimmedText = null;
1002:     }
1003:     
1004:     if (m.trimmedText != null)
1005:       {
1006:         m.textAscent = fmet.getAscent();
1007:         m.textDescent = fmet.getDescent() + fmet.getLeading();
1008: 
1009:         FontRenderContext frc = new FontRenderContext(new AffineTransform(), 
1010:             false, false);
1011:         LineMetrics lmet = m.font.getLineMetrics(m.trimmedText, 0,
1012:             m.trimmedText.length(), frc);
1013:         m.lineHeight = (int) lmet.getStrikethroughOffset();
1014:         
1015:         // Fallback in case that LineMetrics is not available/working.
1016:         if (m.lineHeight == 0)
1017:           m.lineHeight = (int) (0.3333 * (double) m.textAscent);
1018:         m.textWidth = fmet.stringWidth(m.trimmedText) + 3;
1019:       }
1020:     else
1021:       {
1022:         m.textAscent = 0;
1023:         m.textDescent = 0;
1024:       }
1025: 
1026:     m.innerSpacing = new Insets(EDGE_SPACING, EDGE_SPACING, EDGE_SPACING, 
1027:             EDGE_SPACING);
1028:     m.outerSpacing = new Insets(EDGE_SPACING, EDGE_SPACING, EDGE_SPACING, 
1029:             EDGE_SPACING);
1030: 
1031:     switch (titlePosition)
1032:     {
1033:     case ABOVE_TOP:
1034:       m.outerSpacing.top += m.textAscent + m.textDescent + TEXT_SPACING;
1035:       break;
1036: 
1037:     case TOP:
1038:       m.outerSpacing.top += m.textDescent + m.lineHeight;
1039:       m.innerSpacing.top += m.textAscent - m.lineHeight;
1040:       break;
1041:       
1042:     case BELOW_TOP:
1043:       m.innerSpacing.top += m.textAscent + m.textDescent + TEXT_SPACING;
1044:       break;
1045: 
1046:     case ABOVE_BOTTOM:
1047:       m.innerSpacing.bottom += m.textAscent + m.textDescent + TEXT_SPACING;
1048:       break;
1049: 
1050:     case BOTTOM:
1051:       m.innerSpacing.bottom += Math.max(m.textAscent - m.lineHeight, 0);
1052:       m.outerSpacing.bottom += m.textDescent + m.lineHeight;
1053:       break;
1054: 
1055:     case BELOW_BOTTOM:
1056:       m.outerSpacing.bottom += m.textAscent + m.textDescent;
1057:       break;
1058: 
1059:     default:
1060:       m.outerSpacing.top += m.textAscent;
1061:     }
1062: 
1063:     return m;
1064:   }
1065: 
1066: 
1067:   /**
1068:    * A private helper class for holding the result of measuring the
1069:    * distances of a TitledBorder.  While it would be possible to cache
1070:    * these objects, it does not seem to be worth the effort. Note that
1071:    * invalidating the cache would be tricky, especially since there is
1072:    * no notification mechanism that would inform the cache when
1073:    * border has changed, so it would return different insets.
1074:    */
1075:   private static class Measurements
1076:   {
1077:     /**
1078:      * The font used for displaying the title text. Note that it can
1079:      * well be that the TitledBorder&#x2019;s font is <code>null</code>,
1080:      * which means that the font is to be retrieved from the current
1081:      * LookAndFeel. In this case, this <code>font</code> field will
1082:      * contain the result of the retrieval. Therefore, it is safe
1083:      * to assume that this <code>font</code> field will never have
1084:      * a <code>null</code> value.
1085:      */
1086:     Font font;
1087: 
1088: 
1089:     /**
1090:      * The number of pixels between the base line and the top of the
1091:      * text box.
1092:      */
1093:     int textAscent;
1094: 
1095: 
1096:     /**
1097:      * The number of pixels between the base line and the bottom of
1098:      * the text box.
1099:      */
1100:     int textDescent;
1101: 
1102:     /**
1103:      * The number of pixels between the base line and the height where
1104:      * a strike-through would be drawn.
1105:      */
1106:     int lineHeight;
1107: 
1108:     /**
1109:      * The title text after removing leading and trailing white space
1110:      * characters. If the title consists only of white space, the
1111:      * value of <code>trimmedText</code> will be <code>null</code>.
1112:      */
1113:     String trimmedText;
1114: 
1115: 
1116:     /**
1117:      * The width of the trimmed title text in pixels.
1118:      */
1119:     int textWidth;
1120: 
1121: 
1122:     /**
1123:      * The border that constitutes the interior border
1124:      * underneath the title text.
1125:      */
1126:     Border border;
1127: 
1128: 
1129:     /**
1130:      * The distance between the TitledBorder and the interior border.
1131:      */
1132:     Insets outerSpacing;
1133:     
1134:     /**
1135:      * The width of the interior border, as returned by
1136:      * <code>border.getBorderInsets()</code>.
1137:      */
1138:     Insets borderInsets;
1139: 
1140:     
1141:     /**
1142:      * The distance between the interior border and the nested
1143:      * Component for which this TitledBorder is a border.
1144:      */
1145:     Insets innerSpacing;
1146: 
1147: 
1148:     /**
1149:      * Determines the insets of the nested component when it has a
1150:      * TitledBorder as its border. Used by {@link
1151:      * TitledBorder#getBorderInsets(Component, Insets)}.
1152:      *
1153:      * @param i an Insets object for storing the results into, or
1154:      *        <code>null</code> to cause the creation of a
1155:      *        new instance.
1156:      *
1157:      * @return the <code>i</code> object, or a new Insets object
1158:      *         if <code>null</code> was passed for <code>i</code>.
1159:      */
1160:     public Insets getContentInsets(Insets i)
1161:     {
1162:       if (i == null)
1163:         i = new Insets(0, 0, 0, 0);
1164:       i.left = outerSpacing.left + borderInsets.left + innerSpacing.left;
1165:       i.right = outerSpacing.right + borderInsets.right + innerSpacing.right;
1166:       i.top = outerSpacing.top + borderInsets.top + innerSpacing.top;
1167:       i.bottom = outerSpacing.bottom + borderInsets.bottom + innerSpacing.bottom;
1168:       return i;
1169:     }
1170: 
1171: 
1172:     /**
1173:      * Calculates the minimum size needed for displaying the border
1174:      * and its title. Used by {@link TitledBorder#getMinimumSize(Component)}.
1175:      */
1176:     public Dimension getMinimumSize()
1177:     {
1178:       int width;
1179:       Insets insets;
1180: 
1181:       insets = getContentInsets(null);
1182:       width = Math.max(insets.left + insets.right, textWidth + 2 
1183:               * TEXT_INSET_H);
1184:       return new Dimension(width, insets.top + insets.bottom);
1185:     }
1186:   }
1187: }