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