1 /*
   2  * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.plaf.synth;
  26 
  27 import sun.swing.SwingUtilities2;
  28 import sun.swing.MenuItemLayoutHelper;
  29 
  30 import java.awt.*;
  31 import javax.swing.*;
  32 import javax.swing.plaf.basic.BasicHTML;
  33 import javax.swing.text.*;
  34 import sun.swing.plaf.synth.*;
  35 
  36 /**
  37  * Wrapper for primitive graphics calls.
  38  *
  39  * @since 1.5
  40  * @author Scott Violet
  41  */
  42 public class SynthGraphicsUtils {
  43     // These are used in the text painting code to avoid allocating a bunch of
  44     // garbage.
  45     private Rectangle paintIconR = new Rectangle();
  46     private Rectangle paintTextR = new Rectangle();
  47     private Rectangle paintViewR = new Rectangle();
  48     private Insets paintInsets = new Insets(0, 0, 0, 0);
  49 
  50     // These Rectangles/Insets are used in the text size calculation to avoid a
  51     // a bunch of garbage.
  52     private Rectangle iconR = new Rectangle();
  53     private Rectangle textR = new Rectangle();
  54     private Rectangle viewR = new Rectangle();
  55     private Insets viewSizingInsets = new Insets(0, 0, 0, 0);
  56 
  57     /**
  58      * Creates a <code>SynthGraphicsUtils</code>.
  59      */
  60     public SynthGraphicsUtils() {
  61     }
  62 
  63     /**
  64      * Draws a line between the two end points.
  65      *
  66      * @param context Identifies hosting region.
  67      * @param paintKey Identifies the portion of the component being asked
  68      *                 to paint, may be null.
  69      * @param g Graphics object to paint to
  70      * @param x1 x origin
  71      * @param y1 y origin
  72      * @param x2 x destination
  73      * @param y2 y destination
  74      */
  75     public void drawLine(SynthContext context, Object paintKey,
  76                          Graphics g, int x1, int y1, int x2, int y2) {
  77         g.drawLine(x1, y1, x2, y2);
  78     }
  79 
  80     /**
  81      * Draws a line between the two end points.
  82      * <p>This implementation supports only one line style key,
  83      * <code>"dashed"</code>. The <code>"dashed"</code> line style is applied
  84      * only to vertical and horizontal lines.
  85      * <p>Specifying <code>null</code> or any key different from
  86      * <code>"dashed"</code> will draw solid lines.
  87      *
  88      * @param context identifies hosting region
  89      * @param paintKey identifies the portion of the component being asked
  90      *                 to paint, may be null
  91      * @param g Graphics object to paint to
  92      * @param x1 x origin
  93      * @param y1 y origin
  94      * @param x2 x destination
  95      * @param y2 y destination
  96      * @param styleKey identifies the requested style of the line (e.g. "dashed")
  97      * @since 1.6
  98      */
  99     public void drawLine(SynthContext context, Object paintKey,
 100                          Graphics g, int x1, int y1, int x2, int y2,
 101                          Object styleKey) {
 102         if ("dashed".equals(styleKey)) {
 103             // draw vertical line
 104             if (x1 == x2) {
 105                 y1 += (y1 % 2);
 106 
 107                 for (int y = y1; y <= y2; y+=2) {
 108                     g.drawLine(x1, y, x2, y);
 109                 }
 110             // draw horizontal line
 111             } else if (y1 == y2) {
 112                 x1 += (x1 % 2);
 113 
 114                 for (int x = x1; x <= x2; x+=2) {
 115                     g.drawLine(x, y1, x, y2);
 116                 }
 117             // oblique lines are not supported
 118             }
 119         } else {
 120             drawLine(context, paintKey, g, x1, y1, x2, y2);
 121         }
 122     }
 123 
 124     /**
 125      * Lays out text and an icon returning, by reference, the location to
 126      * place the icon and text.
 127      *
 128      * @param ss SynthContext
 129      * @param fm FontMetrics for the Font to use, this may be ignored
 130      * @param text Text to layout
 131      * @param icon Icon to layout
 132      * @param hAlign horizontal alignment
 133      * @param vAlign vertical alignment
 134      * @param hTextPosition horizontal text position
 135      * @param vTextPosition vertical text position
 136      * @param viewR Rectangle to layout text and icon in.
 137      * @param iconR Rectangle to place icon bounds in
 138      * @param textR Rectangle to place text in
 139      * @param iconTextGap gap between icon and text
 140      */
 141     public String layoutText(SynthContext ss, FontMetrics fm,
 142                          String text, Icon icon, int hAlign,
 143                          int vAlign, int hTextPosition,
 144                          int vTextPosition, Rectangle viewR,
 145                          Rectangle iconR, Rectangle textR, int iconTextGap) {
 146         if (icon instanceof SynthIcon) {
 147             SynthIconWrapper wrapper = SynthIconWrapper.get((SynthIcon)icon,
 148                                                             ss);
 149             String formattedText = SwingUtilities.layoutCompoundLabel(
 150                       ss.getComponent(), fm, text, wrapper, vAlign, hAlign,
 151                       vTextPosition, hTextPosition, viewR, iconR, textR,
 152                       iconTextGap);
 153             SynthIconWrapper.release(wrapper);
 154             return formattedText;
 155         }
 156         return SwingUtilities.layoutCompoundLabel(
 157                       ss.getComponent(), fm, text, icon, vAlign, hAlign,
 158                       vTextPosition, hTextPosition, viewR, iconR, textR,
 159                       iconTextGap);
 160     }
 161 
 162     /**
 163      * Returns the size of the passed in string.
 164      *
 165      * @param ss SynthContext
 166      * @param font Font to use
 167      * @param metrics FontMetrics, may be ignored
 168      * @param text Text to get size of.
 169      */
 170     public int computeStringWidth(SynthContext ss, Font font,
 171                                   FontMetrics metrics, String text) {
 172         return SwingUtilities2.stringWidth(ss.getComponent(), metrics,
 173                                           text);
 174     }
 175 
 176     /**
 177      * Returns the minimum size needed to properly render an icon and text.
 178      *
 179      * @param ss SynthContext
 180      * @param font Font to use
 181      * @param text Text to layout
 182      * @param icon Icon to layout
 183      * @param hAlign horizontal alignment
 184      * @param vAlign vertical alignment
 185      * @param hTextPosition horizontal text position
 186      * @param vTextPosition vertical text position
 187      * @param iconTextGap gap between icon and text
 188      * @param mnemonicIndex Index into text to render the mnemonic at, -1
 189      *        indicates no mnemonic.
 190      */
 191     public Dimension getMinimumSize(SynthContext ss, Font font, String text,
 192                       Icon icon, int hAlign, int vAlign, int hTextPosition,
 193                       int vTextPosition, int iconTextGap, int mnemonicIndex) {
 194         JComponent c = ss.getComponent();
 195         Dimension size = getPreferredSize(ss, font, text, icon, hAlign,
 196                                           vAlign, hTextPosition, vTextPosition,
 197                                           iconTextGap, mnemonicIndex);
 198         View v = (View) c.getClientProperty(BasicHTML.propertyKey);
 199 
 200         if (v != null) {
 201             size.width -= v.getPreferredSpan(View.X_AXIS) -
 202                           v.getMinimumSpan(View.X_AXIS);
 203         }
 204         return size;
 205     }
 206 
 207     /**
 208      * Returns the maximum size needed to properly render an icon and text.
 209      *
 210      * @param ss SynthContext
 211      * @param font Font to use
 212      * @param text Text to layout
 213      * @param icon Icon to layout
 214      * @param hAlign horizontal alignment
 215      * @param vAlign vertical alignment
 216      * @param hTextPosition horizontal text position
 217      * @param vTextPosition vertical text position
 218      * @param iconTextGap gap between icon and text
 219      * @param mnemonicIndex Index into text to render the mnemonic at, -1
 220      *        indicates no mnemonic.
 221      */
 222     public Dimension getMaximumSize(SynthContext ss, Font font, String text,
 223                       Icon icon, int hAlign, int vAlign, int hTextPosition,
 224                       int vTextPosition, int iconTextGap, int mnemonicIndex) {
 225         JComponent c = ss.getComponent();
 226         Dimension size = getPreferredSize(ss, font, text, icon, hAlign,
 227                                           vAlign, hTextPosition, vTextPosition,
 228                                           iconTextGap, mnemonicIndex);
 229         View v = (View) c.getClientProperty(BasicHTML.propertyKey);
 230 
 231         if (v != null) {
 232             size.width += v.getMaximumSpan(View.X_AXIS) -
 233                           v.getPreferredSpan(View.X_AXIS);
 234         }
 235         return size;
 236     }
 237 
 238     /**
 239      * Returns the maximum height of the the Font from the passed in
 240      * SynthContext.
 241      *
 242      * @param context SynthContext used to determine font.
 243      * @return maximum height of the characters for the font from the passed
 244      *         in context.
 245      */
 246     public int getMaximumCharHeight(SynthContext context) {
 247         FontMetrics fm = context.getComponent().getFontMetrics(
 248             context.getStyle().getFont(context));
 249         return (fm.getAscent() + fm.getDescent());
 250     }
 251 
 252     /**
 253      * Returns the preferred size needed to properly render an icon and text.
 254      *
 255      * @param ss SynthContext
 256      * @param font Font to use
 257      * @param text Text to layout
 258      * @param icon Icon to layout
 259      * @param hAlign horizontal alignment
 260      * @param vAlign vertical alignment
 261      * @param hTextPosition horizontal text position
 262      * @param vTextPosition vertical text position
 263      * @param iconTextGap gap between icon and text
 264      * @param mnemonicIndex Index into text to render the mnemonic at, -1
 265      *        indicates no mnemonic.
 266      */
 267     public Dimension getPreferredSize(SynthContext ss, Font font, String text,
 268                       Icon icon, int hAlign, int vAlign, int hTextPosition,
 269                       int vTextPosition, int iconTextGap, int mnemonicIndex) {
 270         JComponent c = ss.getComponent();
 271         Insets insets = c.getInsets(viewSizingInsets);
 272         int dx = insets.left + insets.right;
 273         int dy = insets.top + insets.bottom;
 274 
 275         if (icon == null && (text == null || font == null)) {
 276             return new Dimension(dx, dy);
 277         }
 278         else if ((text == null) || ((icon != null) && (font == null))) {
 279             return new Dimension(SynthIcon.getIconWidth(icon, ss) + dx,
 280                                  SynthIcon.getIconHeight(icon, ss) + dy);
 281         }
 282         else {
 283             FontMetrics fm = c.getFontMetrics(font);
 284 
 285             iconR.x = iconR.y = iconR.width = iconR.height = 0;
 286             textR.x = textR.y = textR.width = textR.height = 0;
 287             viewR.x = dx;
 288             viewR.y = dy;
 289             viewR.width = viewR.height = Short.MAX_VALUE;
 290 
 291             layoutText(ss, fm, text, icon, hAlign, vAlign,
 292                    hTextPosition, vTextPosition, viewR, iconR, textR,
 293                    iconTextGap);
 294             int x1 = Math.min(iconR.x, textR.x);
 295             int x2 = Math.max(iconR.x + iconR.width, textR.x + textR.width);
 296             int y1 = Math.min(iconR.y, textR.y);
 297             int y2 = Math.max(iconR.y + iconR.height, textR.y + textR.height);
 298             Dimension rv = new Dimension(x2 - x1, y2 - y1);
 299 
 300             rv.width += dx;
 301             rv.height += dy;
 302             return rv;
 303         }
 304     }
 305 
 306     /**
 307      * Paints text at the specified location. This will not attempt to
 308      * render the text as html nor will it offset by the insets of the
 309      * component.
 310      *
 311      * @param ss SynthContext
 312      * @param g Graphics used to render string in.
 313      * @param text Text to render
 314      * @param bounds Bounds of the text to be drawn.
 315      * @param mnemonicIndex Index to draw string at.
 316      */
 317     public void paintText(SynthContext ss, Graphics g, String text,
 318                           Rectangle bounds, int mnemonicIndex) {
 319         paintText(ss, g, text, bounds.x, bounds.y, mnemonicIndex);
 320     }
 321 
 322     /**
 323      * Paints text at the specified location. This will not attempt to
 324      * render the text as html nor will it offset by the insets of the
 325      * component.
 326      *
 327      * @param ss SynthContext
 328      * @param g Graphics used to render string in.
 329      * @param text Text to render
 330      * @param x X location to draw text at.
 331      * @param y Upper left corner to draw text at.
 332      * @param mnemonicIndex Index to draw string at.
 333      */
 334     public void paintText(SynthContext ss, Graphics g, String text,
 335                           int x, int y, int mnemonicIndex) {
 336         if (text != null) {
 337             JComponent c = ss.getComponent();
 338             FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
 339             y += fm.getAscent();
 340             SwingUtilities2.drawStringUnderlineCharAt(c, g, text,
 341                                                       mnemonicIndex, x, y);
 342         }
 343     }
 344 
 345     /**
 346      * Paints an icon and text. This will render the text as html, if
 347      * necessary, and offset the location by the insets of the component.
 348      *
 349      * @param ss SynthContext
 350      * @param g Graphics to render string and icon into
 351      * @param text Text to layout
 352      * @param icon Icon to layout
 353      * @param hAlign horizontal alignment
 354      * @param vAlign vertical alignment
 355      * @param hTextPosition horizontal text position
 356      * @param vTextPosition vertical text position
 357      * @param iconTextGap gap between icon and text
 358      * @param mnemonicIndex Index into text to render the mnemonic at, -1
 359      *        indicates no mnemonic.
 360      * @param textOffset Amount to offset the text when painting
 361      */
 362     public void paintText(SynthContext ss, Graphics g, String text,
 363                       Icon icon, int hAlign, int vAlign, int hTextPosition,
 364                       int vTextPosition, int iconTextGap, int mnemonicIndex,
 365                       int textOffset) {
 366         if ((icon == null) && (text == null)) {
 367             return;
 368         }
 369         JComponent c = ss.getComponent();
 370         FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
 371         Insets insets = SynthLookAndFeel.getPaintingInsets(ss, paintInsets);
 372 
 373         paintViewR.x = insets.left;
 374         paintViewR.y = insets.top;
 375         paintViewR.width = c.getWidth() - (insets.left + insets.right);
 376         paintViewR.height = c.getHeight() - (insets.top + insets.bottom);
 377 
 378         paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
 379         paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
 380 
 381         String clippedText =
 382             layoutText(ss, fm, text, icon, hAlign, vAlign,
 383                    hTextPosition, vTextPosition, paintViewR, paintIconR,
 384                    paintTextR, iconTextGap);
 385 
 386         if (icon != null) {
 387             Color color = g.getColor();
 388 
 389             if (ss.getStyle().getBoolean(ss, "TableHeader.alignSorterArrow", false) &&
 390                 "TableHeader.renderer".equals(c.getName())) {
 391                 paintIconR.x = paintViewR.width - paintIconR.width;
 392             } else {
 393                 paintIconR.x += textOffset;
 394             }
 395             paintIconR.y += textOffset;
 396             SynthIcon.paintIcon(icon, ss, g, paintIconR.x, paintIconR.y,
 397                                 paintIconR.width, paintIconR.height);
 398             g.setColor(color);
 399         }
 400 
 401         if (text != null) {
 402             View v = (View) c.getClientProperty(BasicHTML.propertyKey);
 403 
 404             if (v != null) {
 405                 v.paint(g, paintTextR);
 406             } else {
 407                 paintTextR.x += textOffset;
 408                 paintTextR.y += textOffset;
 409 
 410                 paintText(ss, g, clippedText, paintTextR, mnemonicIndex);
 411             }
 412         }
 413     }
 414 
 415 
 416      /**
 417       * A quick note about how preferred sizes are calculated... Generally
 418       * speaking, SynthPopupMenuUI will run through the list of its children
 419       * (from top to bottom) and ask each for its preferred size.  Each menu
 420       * item will add up the max width of each element (icons, text,
 421       * accelerator spacing, accelerator text or arrow icon) encountered thus
 422       * far, so by the time all menu items have been calculated, we will
 423       * know the maximum (preferred) menu item size for that popup menu.
 424       * Later when it comes time to paint each menu item, we can use those
 425       * same accumulated max element sizes in order to layout the item.
 426       */
 427     static Dimension getPreferredMenuItemSize(SynthContext context,
 428            SynthContext accContext, JComponent c,
 429            Icon checkIcon, Icon arrowIcon, int defaultTextIconGap,
 430            String acceleratorDelimiter, boolean useCheckAndArrow,
 431            String propertyPrefix) {
 432 
 433          JMenuItem mi = (JMenuItem) c;
 434          SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper(
 435                  context, accContext, mi, checkIcon, arrowIcon,
 436                  MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap,
 437                  acceleratorDelimiter, SynthLookAndFeel.isLeftToRight(mi),
 438                  useCheckAndArrow, propertyPrefix);
 439 
 440          Dimension result = new Dimension();
 441 
 442          // Calculate the result width
 443          int gap = lh.getGap();
 444          result.width = 0;
 445          MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(), gap, result);
 446          MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), gap, result);
 447          MenuItemLayoutHelper.addWidth(lh.getMaxAccOrArrowWidth(), 5 * gap, result);
 448          // The last gap is unnecessary
 449          result.width -= gap;
 450 
 451          // Calculate the result height
 452          result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(),
 453                  lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(),
 454                  lh.getArrowSize().getHeight());
 455 
 456          // Take into account menu item insets
 457          Insets insets = lh.getMenuItem().getInsets();
 458          if (insets != null) {
 459              result.width += insets.left + insets.right;
 460              result.height += insets.top + insets.bottom;
 461          }
 462 
 463          // if the width is even, bump it up one. This is critical
 464          // for the focus dash lhne to draw properly
 465          if (result.width % 2 == 0) {
 466              result.width++;
 467          }
 468 
 469          // if the height is even, bump it up one. This is critical
 470          // for the text to center properly
 471          if (result.height % 2 == 0) {
 472              result.height++;
 473          }
 474 
 475          return result;
 476      }
 477 
 478     static void applyInsets(Rectangle rect, Insets insets, boolean leftToRight) {
 479         if (insets != null) {
 480             rect.x += (leftToRight ? insets.left : insets.right);
 481             rect.y += insets.top;
 482             rect.width -= (leftToRight ? insets.right : insets.left) + rect.x;
 483             rect.height -= (insets.bottom + rect.y);
 484         }
 485     }
 486 
 487     static void paint(SynthContext context, SynthContext accContext, Graphics g,
 488                Icon checkIcon, Icon arrowIcon, String acceleratorDelimiter,
 489                int defaultTextIconGap, String propertyPrefix) {
 490         JMenuItem mi = (JMenuItem) context.getComponent();
 491         SynthStyle style = context.getStyle();
 492         g.setFont(style.getFont(context));
 493 
 494         Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight());
 495         boolean leftToRight = SynthLookAndFeel.isLeftToRight(mi);
 496         applyInsets(viewRect, mi.getInsets(), leftToRight);
 497 
 498         SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper(
 499                 context, accContext, mi, checkIcon, arrowIcon, viewRect,
 500                 defaultTextIconGap, acceleratorDelimiter, leftToRight,
 501                 MenuItemLayoutHelper.useCheckAndArrow(mi), propertyPrefix);
 502         MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem();
 503 
 504         paintMenuItem(g, lh, lr);
 505     }
 506 
 507     static void paintMenuItem(Graphics g, SynthMenuItemLayoutHelper lh,
 508                               MenuItemLayoutHelper.LayoutResult lr) {
 509         // Save original graphics font and color
 510         Font holdf = g.getFont();
 511         Color holdc = g.getColor();
 512 
 513         paintBackground(g, lh);
 514         paintCheckIcon(g, lh, lr);
 515         paintIcon(g, lh, lr);
 516         paintText(g, lh, lr);
 517         paintAccText(g, lh, lr);
 518         paintArrowIcon(g, lh, lr);
 519 
 520         // Restore original graphics font and color
 521         g.setColor(holdc);
 522         g.setFont(holdf);
 523     }
 524 
 525     static void paintBackground(Graphics g, SynthMenuItemLayoutHelper lh) {
 526         paintBackground(lh.getContext(), g, lh.getMenuItem());
 527     }
 528 
 529     static void paintBackground(SynthContext context, Graphics g, JComponent c) {
 530         context.getPainter().paintMenuItemBackground(context, g, 0, 0,
 531                 c.getWidth(), c.getHeight());
 532     }
 533 
 534     static void paintIcon(Graphics g, SynthMenuItemLayoutHelper lh,
 535                           MenuItemLayoutHelper.LayoutResult lr) {
 536         if (lh.getIcon() != null) {
 537             Icon icon;
 538             JMenuItem mi = lh.getMenuItem();
 539             ButtonModel model = mi.getModel();
 540             if (!model.isEnabled()) {
 541                 icon = mi.getDisabledIcon();
 542             } else if (model.isPressed() && model.isArmed()) {
 543                 icon = mi.getPressedIcon();
 544                 if (icon == null) {
 545                     // Use default icon
 546                     icon = mi.getIcon();
 547                 }
 548             } else {
 549                 icon = mi.getIcon();
 550             }
 551 
 552             if (icon != null) {
 553                 Rectangle iconRect = lr.getIconRect();
 554                 SynthIcon.paintIcon(icon, lh.getContext(), g, iconRect.x,
 555                         iconRect.y, iconRect.width, iconRect.height);
 556             }
 557         }
 558     }
 559 
 560     static void paintCheckIcon(Graphics g, SynthMenuItemLayoutHelper lh,
 561                                MenuItemLayoutHelper.LayoutResult lr) {
 562         if (lh.getCheckIcon() != null) {
 563             Rectangle checkRect = lr.getCheckRect();
 564             SynthIcon.paintIcon(lh.getCheckIcon(), lh.getContext(), g,
 565                     checkRect.x, checkRect.y, checkRect.width, checkRect.height);
 566         }
 567     }
 568 
 569     static void paintAccText(Graphics g, SynthMenuItemLayoutHelper lh,
 570                              MenuItemLayoutHelper.LayoutResult lr) {
 571         String accText = lh.getAccText();
 572         if (accText != null && !accText.equals("")) {
 573             g.setColor(lh.getAccStyle().getColor(lh.getAccContext(),
 574                     ColorType.TEXT_FOREGROUND));
 575             g.setFont(lh.getAccStyle().getFont(lh.getAccContext()));
 576             lh.getAccGraphicsUtils().paintText(lh.getAccContext(), g, accText,
 577                     lr.getAccRect().x, lr.getAccRect().y, -1);
 578         }
 579     }
 580 
 581     static void paintText(Graphics g, SynthMenuItemLayoutHelper lh,
 582                           MenuItemLayoutHelper.LayoutResult lr) {
 583         if (!lh.getText().equals("")) {
 584             if (lh.getHtmlView() != null) {
 585                 // Text is HTML
 586                 lh.getHtmlView().paint(g, lr.getTextRect());
 587             } else {
 588                 // Text isn't HTML
 589                 g.setColor(lh.getStyle().getColor(
 590                         lh.getContext(), ColorType.TEXT_FOREGROUND));
 591                 g.setFont(lh.getStyle().getFont(lh.getContext()));
 592                 lh.getGraphicsUtils().paintText(lh.getContext(), g, lh.getText(),
 593                         lr.getTextRect().x, lr.getTextRect().y,
 594                         lh.getMenuItem().getDisplayedMnemonicIndex());
 595             }
 596         }
 597     }
 598 
 599     static void paintArrowIcon(Graphics g, SynthMenuItemLayoutHelper lh,
 600                                MenuItemLayoutHelper.LayoutResult lr) {
 601         if (lh.getArrowIcon() != null) {
 602             Rectangle arrowRect = lr.getArrowRect();
 603             SynthIcon.paintIcon(lh.getArrowIcon(), lh.getContext(), g,
 604                     arrowRect.x, arrowRect.y, arrowRect.width, arrowRect.height);
 605         }
 606     }
 607 
 608     /**
 609      * Wraps a SynthIcon around the Icon interface, forwarding calls to
 610      * the SynthIcon with a given SynthContext.
 611      */
 612     private static class SynthIconWrapper implements Icon {
 613         private static final java.util.List<SynthIconWrapper> CACHE = new java.util.ArrayList<SynthIconWrapper>(1);
 614 
 615         private SynthIcon synthIcon;
 616         private SynthContext context;
 617 
 618         static SynthIconWrapper get(SynthIcon icon, SynthContext context) {
 619             synchronized(CACHE) {
 620                 int size = CACHE.size();
 621                 if (size > 0) {
 622                     SynthIconWrapper wrapper = CACHE.remove(size - 1);
 623                     wrapper.reset(icon, context);
 624                     return wrapper;
 625                 }
 626             }
 627             return new SynthIconWrapper(icon, context);
 628         }
 629 
 630         static void release(SynthIconWrapper wrapper) {
 631             wrapper.reset(null, null);
 632             synchronized(CACHE) {
 633                 CACHE.add(wrapper);
 634             }
 635         }
 636 
 637         SynthIconWrapper(SynthIcon icon, SynthContext context) {
 638             reset(icon, context);
 639         }
 640 
 641         void reset(SynthIcon icon, SynthContext context) {
 642             synthIcon = icon;
 643             this.context = context;
 644         }
 645 
 646         public void paintIcon(Component c, Graphics g, int x, int y) {
 647             // This is a noop as this should only be for sizing calls.
 648         }
 649 
 650         public int getIconWidth() {
 651             return synthIcon.getIconWidth(context);
 652         }
 653 
 654         public int getIconHeight() {
 655             return synthIcon.getIconHeight(context);
 656         }
 657     }
 658 }