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