Source for javax.swing.plaf.basic.BasicGraphicsUtils

   1: /* BasicGraphicsUtils.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: package javax.swing.plaf.basic;
  39: 
  40: import java.awt.Color;
  41: import java.awt.Dimension;
  42: import java.awt.Font;
  43: import java.awt.FontMetrics;
  44: import java.awt.Graphics;
  45: import java.awt.Graphics2D;
  46: import java.awt.Insets;
  47: import java.awt.Rectangle;
  48: import java.awt.font.FontRenderContext;
  49: import java.awt.font.LineMetrics;
  50: import java.awt.font.TextLayout;
  51: import java.awt.geom.Rectangle2D;
  52: 
  53: import javax.swing.AbstractButton;
  54: import javax.swing.Icon;
  55: import javax.swing.JComponent;
  56: import javax.swing.SwingUtilities;
  57: 
  58: 
  59: /**
  60:  * A utility class providing commonly used drawing and measurement
  61:  * routines.
  62:  *
  63:  * @author Sascha Brawer (brawer@dandelis.ch)
  64:  */
  65: public class BasicGraphicsUtils
  66: {
  67:   /**
  68:    * Constructor. It is utterly unclear why this class should
  69:    * be constructable, but this is what the API specification
  70:    * says.
  71:    */
  72:   public BasicGraphicsUtils()
  73:   {
  74:     // Nothing to do here.
  75:   }
  76: 
  77: 
  78:   /**
  79:    * Draws a rectangle that appears etched into the surface, given
  80:    * four colors that are used for drawing.
  81:    *
  82:    * <p><img src="doc-files/BasicGraphicsUtils-1.png" width="360"
  83:    * height="200" alt="[An illustration that shows which pixels
  84:    * get painted in what color]" />
  85:    *
  86:    * @param g the graphics into which the rectangle is drawn.
  87:    * @param x the x coordinate of the rectangle.
  88:    * @param y the y coordinate of the rectangle.
  89:    * @param width the width of the rectangle in pixels.
  90:    * @param height the height of the rectangle in pixels.
  91:    *
  92:    * @param shadow the color that will be used for painting
  93:    *        the outer side of the top and left edges.
  94:    *
  95:    * @param darkShadow the color that will be used for painting
  96:    *        the inner side of the top and left edges.
  97:    *
  98:    * @param highlight the color that will be used for painting
  99:    *        the inner side of the bottom and right edges.
 100:    *
 101:    * @param lightHighlight the color that will be used for painting
 102:    *        the outer side of the bottom and right edges.
 103:    *
 104:    * @see #getEtchedInsets()
 105:    * @see javax.swing.border.EtchedBorder
 106:    */
 107:   public static void drawEtchedRect(Graphics g,
 108:                                     int x, int y, int width, int height,
 109:                                     Color shadow, Color darkShadow,
 110:                                     Color highlight, Color lightHighlight)
 111:   {
 112:     Color oldColor;
 113:     int x2, y2;
 114: 
 115:     oldColor = g.getColor();
 116:     x2 = x + width - 1;
 117:     y2 = y + height - 1;
 118: 
 119:     try
 120:     {
 121:       /* To understand this code, it might be helpful to look at the
 122:        * image "BasicGraphicsUtils-1.png" that is included with the
 123:        * JavaDoc. The file is located in the "doc-files" subdirectory.
 124:        *
 125:        * (x2, y2) is the coordinate of the most right and bottom pixel
 126:        * to be painted.
 127:        */
 128:       g.setColor(shadow);
 129:       g.drawLine(x, y, x2 - 1, y);                     // top, outer
 130:       g.drawLine(x, y + 1, x, y2 - 1);                 // left, outer
 131: 
 132:       g.setColor(darkShadow);
 133:       g.drawLine(x + 1, y + 1, x2 - 2, y + 1);         // top, inner
 134:       g.drawLine(x + 1, y + 2, x + 1, y2 - 2);         // left, inner
 135:       
 136:       g.setColor(highlight);
 137:       g.drawLine(x + 1, y2 - 1, x2 - 1, y2 - 1);       // bottom, inner
 138:       g.drawLine(x2 - 1, y + 1, x2 - 1, y2 - 2);       // right, inner
 139: 
 140:       g.setColor(lightHighlight);
 141:       g.drawLine(x, y2, x2, y2);                       // bottom, outer
 142:       g.drawLine(x2, y, x2, y2 - 1);                   // right, outer
 143:     }
 144:     finally
 145:     {
 146:       g.setColor(oldColor);
 147:     }
 148:   }
 149:   
 150:   
 151:   /**
 152:    * Determines the width of the border that gets painted by
 153:    * {@link #drawEtchedRect}.
 154:    *
 155:    * @return an <code>Insets</code> object whose <code>top</code>,
 156:    *         <code>left</code>, <code>bottom</code> and
 157:    *         <code>right</code> field contain the border width at the
 158:    *         respective edge in pixels.
 159:    */
 160:   public static Insets getEtchedInsets()
 161:   {
 162:     return new Insets(2, 2, 2, 2);
 163:   }
 164: 
 165: 
 166:   /**
 167:    * Draws a rectangle that appears etched into the surface, given
 168:    * two colors that are used for drawing.
 169:    *
 170:    * <p><img src="doc-files/BasicGraphicsUtils-2.png" width="360"
 171:    * height="200" alt="[An illustration that shows which pixels
 172:    * get painted in what color]" />
 173:    *
 174:    * @param g the graphics into which the rectangle is drawn.
 175:    * @param x the x coordinate of the rectangle.
 176:    * @param y the y coordinate of the rectangle.
 177:    * @param width the width of the rectangle in pixels.
 178:    * @param height the height of the rectangle in pixels.
 179:    *
 180:    * @param shadow the color that will be used for painting the outer
 181:    *        side of the top and left edges, and for the inner side of
 182:    *        the bottom and right ones.
 183:    *
 184:    * @param highlight the color that will be used for painting the
 185:    *        inner side of the top and left edges, and for the outer
 186:    *        side of the bottom and right ones.
 187:    *
 188:    * @see #getGrooveInsets()
 189:    * @see javax.swing.border.EtchedBorder
 190:    */
 191:   public static void drawGroove(Graphics g,
 192:                                 int x, int y, int width, int height,
 193:                                 Color shadow, Color highlight)
 194:   {
 195:     /* To understand this, it might be helpful to look at the image
 196:      * "BasicGraphicsUtils-2.png" that is included with the JavaDoc,
 197:      * and to compare it with "BasicGraphicsUtils-1.png" which shows
 198:      * the pixels painted by drawEtchedRect.  These image files are
 199:      * located in the "doc-files" subdirectory.
 200:      */
 201:     drawEtchedRect(g, x, y, width, height,
 202:                    /* outer topLeft */     shadow,
 203:                    /* inner topLeft */     highlight,
 204:                    /* inner bottomRight */ shadow,
 205:                    /* outer bottomRight */ highlight);
 206:   }
 207: 
 208: 
 209:   /**
 210:    * Determines the width of the border that gets painted by
 211:    * {@link #drawGroove}.
 212:    *
 213:    * @return an <code>Insets</code> object whose <code>top</code>,
 214:    *         <code>left</code>, <code>bottom</code> and
 215:    *         <code>right</code> field contain the border width at the
 216:    *         respective edge in pixels.
 217:    */
 218:   public static Insets getGrooveInsets()
 219:   {
 220:     return new Insets(2, 2, 2, 2);
 221:   }
 222:   
 223: 
 224:   /**
 225:    * Draws a border that is suitable for buttons of the Basic look and
 226:    * feel.
 227:    *
 228:    * <p><img src="doc-files/BasicGraphicsUtils-3.png" width="500"
 229:    * height="300" alt="[An illustration that shows which pixels
 230:    * get painted in what color]" />
 231:    *
 232:    * @param g the graphics into which the rectangle is drawn.
 233:    * @param x the x coordinate of the rectangle.
 234:    * @param y the y coordinate of the rectangle.
 235:    * @param width the width of the rectangle in pixels.
 236:    * @param height the height of the rectangle in pixels.
 237:    *
 238:    * @param isPressed <code>true</code> to draw the button border
 239:    *        with a pressed-in appearance; <code>false</code> for
 240:    *        normal (unpressed) appearance.
 241:    *
 242:    * @param isDefault <code>true</code> to draw the border with
 243:    *        the appearance it has when hitting the enter key in a
 244:    *        dialog will simulate a click to this button;
 245:    *        <code>false</code> for normal appearance.
 246:    *
 247:    * @param shadow the shadow color.
 248:    * @param darkShadow a darker variant of the shadow color.
 249:    * @param highlight the highlight color.
 250:    * @param lightHighlight a brighter variant of the highlight  color.
 251:    */
 252:   public static void drawBezel(Graphics g,
 253:                                int x, int y, int width, int height,
 254:                                boolean isPressed, boolean isDefault,
 255:                                Color shadow, Color darkShadow,
 256:                                Color highlight, Color lightHighlight)
 257:   {
 258:     Color oldColor = g.getColor();
 259: 
 260:     /* To understand this, it might be helpful to look at the image
 261:      * "BasicGraphicsUtils-3.png" that is included with the JavaDoc,
 262:      * and to compare it with "BasicGraphicsUtils-1.png" which shows
 263:      * the pixels painted by drawEtchedRect.  These image files are
 264:      * located in the "doc-files" subdirectory.
 265:      */
 266:     try
 267:     {
 268:       if ((isPressed == false) && (isDefault == false))
 269:       {
 270:         drawEtchedRect(g, x, y, width, height,
 271:                        lightHighlight, highlight,
 272:                        shadow, darkShadow);
 273:       }
 274: 
 275:       if ((isPressed == true) && (isDefault == false))
 276:       {
 277:         g.setColor(shadow);
 278:         g.drawRect(x + 1, y + 1, width - 2, height - 2);
 279:       }
 280: 
 281:       if ((isPressed == false) && (isDefault == true))
 282:       {
 283:         g.setColor(darkShadow);
 284:         g.drawRect(x, y, width - 1, height - 1);
 285:         drawEtchedRect(g, x + 1, y + 1, width - 2, height - 2,
 286:                        lightHighlight, highlight,
 287:                        shadow, darkShadow);
 288:       }
 289: 
 290:       if ((isPressed == true) && (isDefault == true))
 291:       {
 292:         g.setColor(darkShadow);
 293:         g.drawRect(x, y, width - 1, height - 1);
 294:         g.setColor(shadow);
 295:         g.drawRect(x + 1, y + 1, width - 3, height - 3);
 296:       }
 297:     }
 298:     finally
 299:     {
 300:       g.setColor(oldColor);
 301:     }
 302:   }
 303:   
 304:   
 305:   /**
 306:    * Draws a rectangle that appears lowered into the surface, given
 307:    * four colors that are used for drawing.
 308:    *
 309:    * <p><img src="doc-files/BasicGraphicsUtils-4.png" width="360"
 310:    * height="200" alt="[An illustration that shows which pixels
 311:    * get painted in what color]" />
 312:    *
 313:    * <p><strong>Compatibility with the Sun reference
 314:    * implementation:</strong> The Sun reference implementation seems
 315:    * to ignore the <code>x</code> and <code>y</code> arguments, at
 316:    * least in JDK 1.3.1 and 1.4.1_01.  The method always draws the
 317:    * rectangular area at location (0, 0). A bug report has been filed
 318:    * with Sun; its &#x201c;bug ID&#x201d; is 4880003.  The GNU Classpath
 319:    * implementation behaves correctly, thus not replicating this bug.
 320:    *
 321:    * @param g the graphics into which the rectangle is drawn.
 322:    * @param x the x coordinate of the rectangle.
 323:    * @param y the y coordinate of the rectangle.
 324:    * @param width the width of the rectangle in pixels.
 325:    * @param height the height of the rectangle in pixels.
 326:    *
 327:    * @param shadow the color that will be used for painting
 328:    *        the inner side of the top and left edges.
 329:    *
 330:    * @param darkShadow the color that will be used for painting
 331:    *        the outer side of the top and left edges.
 332:    *
 333:    * @param highlight the color that will be used for painting
 334:    *        the inner side of the bottom and right edges.
 335:    *
 336:    * @param lightHighlight the color that will be used for painting
 337:    *        the outer side of the bottom and right edges.
 338:    */
 339:   public static void drawLoweredBezel(Graphics g,
 340:                                       int x, int y, int width, int height,
 341:                                       Color shadow, Color darkShadow,
 342:                                       Color highlight, Color lightHighlight)
 343:   {
 344:     /* Like drawEtchedRect, but swapping darkShadow and shadow.
 345:      *
 346:      * To understand this, it might be helpful to look at the image
 347:      * "BasicGraphicsUtils-4.png" that is included with the JavaDoc,
 348:      * and to compare it with "BasicGraphicsUtils-1.png" which shows
 349:      * the pixels painted by drawEtchedRect.  These image files are
 350:      * located in the "doc-files" subdirectory.
 351:      */
 352:     drawEtchedRect(g, x, y, width, height,
 353:                    darkShadow, shadow,
 354:                    highlight, lightHighlight);
 355:   }
 356:   
 357:   
 358:   /**
 359:    * Draws a String at the given location, underlining the first
 360:    * occurence of a specified character. The algorithm for determining
 361:    * the underlined position is not sensitive to case. If the
 362:    * character is not part of <code>text</code>, the text will be
 363:    * drawn without underlining. Drawing is performed in the current
 364:    * color and font of <code>g</code>.
 365:    *
 366:    * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
 367:    * height="100" alt="[An illustration showing how to use the
 368:    * method]" />
 369:    *
 370:    * @param g the graphics into which the String is drawn.
 371:    *
 372:    * @param text the String to draw.
 373:    *
 374:    * @param underlinedChar the character whose first occurence in
 375:    *        <code>text</code> will be underlined. It is not clear
 376:    *        why the API specification declares this argument to be
 377:    *        of type <code>int</code> instead of <code>char</code>.
 378:    *        While this would allow to pass Unicode characters outside
 379:    *        Basic Multilingual Plane 0 (U+0000 .. U+FFFE), at least
 380:    *        the GNU Classpath implementation does not underline
 381:    *        anything if <code>underlinedChar</code> is outside
 382:    *        the range of <code>char</code>.
 383:    *        
 384:    * @param x the x coordinate of the text, as it would be passed to
 385:    *        {@link java.awt.Graphics#drawString(java.lang.String,
 386:    *        int, int)}.
 387:    *
 388:    * @param y the y coordinate of the text, as it would be passed to
 389:    *        {@link java.awt.Graphics#drawString(java.lang.String,
 390:    *        int, int)}.
 391:    */
 392:   public static void drawString(Graphics g, String text,
 393:                                 int underlinedChar, int x, int y)
 394:   {
 395:     int index = -1;
 396: 
 397:     /* It is intentional that lower case is used. In some languages,
 398:      * the set of lowercase characters is larger than the set of
 399:      * uppercase ones. Therefore, it is good practice to use lowercase
 400:      * for such comparisons (which really means that the author of this
 401:      * code can vaguely remember having read some Unicode techreport
 402:      * with this recommendation, but is too lazy to look for the URL).
 403:      */
 404:     if ((underlinedChar >= 0) || (underlinedChar <= 0xffff))
 405:       index = text.toLowerCase().indexOf(
 406:         Character.toLowerCase((char) underlinedChar));
 407: 
 408:     drawStringUnderlineCharAt(g, text, index, x, y);
 409:   }
 410: 
 411: 
 412:   /**
 413:    * Draws a String at the given location, underlining the character
 414:    * at the specified index. Drawing is performed in the current color
 415:    * and font of <code>g</code>.
 416:    *
 417:    * <p><img src="doc-files/BasicGraphicsUtils-5.png" width="500"
 418:    * height="100" alt="[An illustration showing how to use the
 419:    * method]" />
 420:    *
 421:    * @param g the graphics into which the String is drawn.
 422:    *
 423:    * @param text the String to draw.
 424:    *
 425:    * @param underlinedIndex the index of the underlined character in
 426:    *        <code>text</code>.  If <code>underlinedIndex</code> falls
 427:    *        outside the range <code>[0, text.length() - 1]</code>, the
 428:    *        text will be drawn without underlining anything.
 429:    *        
 430:    * @param x the x coordinate of the text, as it would be passed to
 431:    *        {@link java.awt.Graphics#drawString(java.lang.String,
 432:    *        int, int)}.
 433:    *
 434:    * @param y the y coordinate of the text, as it would be passed to
 435:    *        {@link java.awt.Graphics#drawString(java.lang.String,
 436:    *        int, int)}.
 437:    *
 438:    * @since 1.4
 439:    */
 440:   public static void drawStringUnderlineCharAt(Graphics g, String text,
 441:                                                int underlinedIndex,
 442:                                                int x, int y)
 443:   {
 444:     Graphics2D g2;
 445:     Rectangle2D.Double underline;
 446:     FontRenderContext frc;
 447:     FontMetrics fmet;
 448:     LineMetrics lineMetrics;
 449:     Font font;
 450:     TextLayout layout;
 451:     double underlineX1, underlineX2;
 452:     boolean drawUnderline;
 453:     int textLength;
 454: 
 455:     textLength = text.length();
 456:     if (textLength == 0)
 457:       return;
 458: 
 459:     drawUnderline = (underlinedIndex >= 0) && (underlinedIndex < textLength);
 460: 
 461:     // FIXME: unfortunately pango and cairo can't agree on metrics
 462:     // so for the time being we continue to *not* use TextLayouts.
 463:     if (true || !(g instanceof Graphics2D))
 464:     {
 465:       /* Fall-back. This is likely to produce garbage for any text
 466:        * containing right-to-left (Hebrew or Arabic) characters, even
 467:        * if the underlined character is left-to-right.
 468:        */
 469:       g.drawString(text, x, y);
 470:       if (drawUnderline)
 471:       {
 472:         fmet = g.getFontMetrics();
 473:         g.fillRect(
 474:           /* x */ x + fmet.stringWidth(text.substring(0, underlinedIndex)),
 475:           /* y */ y + fmet.getDescent() - 1,
 476:           /* width */ fmet.charWidth(text.charAt(underlinedIndex)),
 477:           /* height */ 1);
 478:       }
 479: 
 480:       return;
 481:     }
 482: 
 483:     g2 = (Graphics2D) g;
 484:     font = g2.getFont();
 485:     frc = g2.getFontRenderContext();
 486:     lineMetrics = font.getLineMetrics(text, frc);
 487:     layout = new TextLayout(text, font, frc);
 488: 
 489:     /* Draw the text. */
 490:     layout.draw(g2, x, y);
 491:     if (!drawUnderline)
 492:       return;
 493: 
 494:     underlineX1 = x + layout.getLogicalHighlightShape(
 495:      underlinedIndex, underlinedIndex).getBounds2D().getX();
 496:     underlineX2 = x + layout.getLogicalHighlightShape(
 497:      underlinedIndex + 1, underlinedIndex + 1).getBounds2D().getX();
 498: 
 499:     underline = new Rectangle2D.Double();
 500:     if (underlineX1 < underlineX2)
 501:     {
 502:       underline.x = underlineX1;
 503:       underline.width = underlineX2 - underlineX1;
 504:     }
 505:     else
 506:     {
 507:       underline.x = underlineX2;
 508:       underline.width = underlineX1 - underlineX2;
 509:     }
 510: 
 511:     
 512:     underline.height = lineMetrics.getUnderlineThickness();
 513:     underline.y = lineMetrics.getUnderlineOffset();
 514:     if (underline.y == 0)
 515:     {
 516:       /* Some fonts do not specify an underline offset, although they
 517:        * actually should do so. In that case, the result of calling
 518:        * lineMetrics.getUnderlineOffset() will be zero. Since it would
 519:        * look very ugly if the underline was be positioned immediately
 520:        * below the baseline, we check for this and move the underline
 521:        * below the descent, as shown in the following ASCII picture:
 522:        *
 523:        *   #####       ##### #
 524:        *  #     #     #     #
 525:        *  #     #     #     #
 526:        *  #     #     #     #
 527:        *   #####       ######        ---- baseline (0)
 528:        *                    #
 529:        *                    #
 530:        * ------------------###----------- lineMetrics.getDescent()
 531:        */
 532:       underline.y = lineMetrics.getDescent();
 533:     }
 534: 
 535:     underline.y += y;
 536:     g2.fill(underline);
 537:   }
 538: 
 539: 
 540:   /**
 541:    * Draws a rectangle, simulating a dotted stroke by painting only
 542:    * every second pixel along the one-pixel thick edge. The color of
 543:    * those pixels is the current color of the Graphics <code>g</code>.
 544:    * Any other pixels are left unchanged.
 545:    *
 546:    * <p><img src="doc-files/BasicGraphicsUtils-7.png" width="360"
 547:    * height="200" alt="[An illustration that shows which pixels
 548:    * get painted]" />
 549:    *
 550:    * @param g the graphics into which the rectangle is drawn.
 551:    * @param x the x coordinate of the rectangle.
 552:    * @param y the y coordinate of the rectangle.
 553:    * @param width the width of the rectangle in pixels.
 554:    * @param height the height of the rectangle in pixels.
 555:    */
 556:   public static void drawDashedRect(Graphics g,
 557:                                     int x, int y, int width, int height)
 558:   {
 559:     int right = x + width - 1;
 560:     int bottom = y + height - 1;
 561: 
 562:     /* Draw the top and bottom edge of the dotted rectangle. */
 563:     for (int i = x; i <= right; i += 2)
 564:     {
 565:       g.drawLine(i, y, i, y);
 566:       g.drawLine(i, bottom, i, bottom);
 567:     }
 568: 
 569:     /* Draw the left and right edge of the dotted rectangle. */
 570:     for (int i = y; i <= bottom; i += 2)
 571:     {
 572:       g.drawLine(x, i, x, i);
 573:       g.drawLine(right, i, right, i);
 574:     }
 575:   }
 576: 
 577: 
 578:   /**
 579:    * Determines the preferred width and height of an AbstractButton,
 580:    * given the gap between the button&#x2019;s text and icon.
 581:    *
 582:    * @param b the button whose preferred size is determined.
 583:    *
 584:    * @param textIconGap the gap between the button&#x2019;s text and
 585:    *        icon.
 586:    *
 587:    * @return a <code>Dimension</code> object whose <code>width</code>
 588:    *         and <code>height</code> fields indicate the preferred
 589:    *         extent in pixels.
 590:    *
 591:    * @see javax.swing.SwingUtilities#layoutCompoundLabel(JComponent, 
 592:    *      FontMetrics, String, Icon, int, int, int, int, Rectangle, Rectangle, 
 593:    *      Rectangle, int)
 594:    */
 595:   public static Dimension getPreferredButtonSize(AbstractButton b,
 596:                                                  int textIconGap)
 597:   {
 598:     Rectangle contentRect;
 599:     Rectangle viewRect;
 600:     Rectangle iconRect = new Rectangle();
 601:     Rectangle textRect = new Rectangle();
 602:     Insets insets = b.getInsets();
 603:     
 604:     viewRect = new Rectangle();
 605: 
 606:      /* java.awt.Toolkit.getFontMetrics is deprecated. However, it
 607:      * seems not obvious how to get to the correct FontMetrics object
 608:      * otherwise. The real problem probably is that the method
 609:      * javax.swing.SwingUtilities.layoutCompundLabel should take a
 610:      * LineMetrics, not a FontMetrics argument. But fixing this that
 611:      * would change the public API.
 612:      */
 613:    SwingUtilities.layoutCompoundLabel(
 614:       b, // for the component orientation
 615:       b.getToolkit().getFontMetrics(b.getFont()), // see comment above
 616:       b.getText(),
 617:       b.getIcon(),
 618:       b.getVerticalAlignment(), 
 619:       b.getHorizontalAlignment(),
 620:       b.getVerticalTextPosition(),
 621:       b.getHorizontalTextPosition(),
 622:       viewRect, iconRect, textRect,
 623:       textIconGap);
 624: 
 625:     /*  +------------------------+       +------------------------+
 626:      *  |                        |       |                        |
 627:      *  | ICON                   |       | CONTENTCONTENTCONTENT  |
 628:      *  |          TEXTTEXTTEXT  |  -->  | CONTENTCONTENTCONTENT  |
 629:      *  |          TEXTTEXTTEXT  |       | CONTENTCONTENTCONTENT  |
 630:      *  +------------------------+       +------------------------+
 631:      */
 632: 
 633:     contentRect = textRect.union(iconRect);
 634:     
 635:     return new Dimension(insets.left
 636:              + contentRect.width 
 637:              + insets.right + b.getHorizontalAlignment(),
 638:                          insets.top
 639:              + contentRect.height 
 640:              + insets.bottom);
 641:   }
 642: }