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         paintText(ss, g, text, bounds.x, bounds.y, mnemonicIndex);
 330     }
 331 
 332     /**
 333      * Paints text at the specified location. This will not attempt to
 334      * render the text as html nor will it offset by the insets of the
 335      * component.
 336      *
 337      * @param ss SynthContext
 338      * @param g Graphics used to render string in.
 339      * @param text Text to render
 340      * @param x X location to draw text at.
 341      * @param y Upper left corner to draw text at.
 342      * @param mnemonicIndex Index to draw string at.
 343      */
 344     public void paintText(SynthContext ss, Graphics g, String text,
 345                           int x, int y, int mnemonicIndex) {
 346         if (text != null) {
 347             JComponent c = ss.getComponent();
 348             FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
 349             y += fm.getAscent();
 350             SwingUtilities2.drawStringUnderlineCharAt(c, g, text,
 351                                                       mnemonicIndex, x, y);
 352         }
 353     }
 354 
 355     /**
 356      * Paints an icon and text. This will render the text as html, if
 357      * necessary, and offset the location by the insets of the component.
 358      *
 359      * @param ss SynthContext
 360      * @param g Graphics to render string and icon into
 361      * @param text Text to layout
 362      * @param icon Icon to layout
 363      * @param hAlign horizontal alignment
 364      * @param vAlign vertical alignment
 365      * @param hTextPosition horizontal text position
 366      * @param vTextPosition vertical text position
 367      * @param iconTextGap gap between icon and text
 368      * @param mnemonicIndex Index into text to render the mnemonic at, -1
 369      *        indicates no mnemonic.
 370      * @param textOffset Amount to offset the text when painting
 371      */
 372     public void paintText(SynthContext ss, Graphics g, String text,
 373                       Icon icon, int hAlign, int vAlign, int hTextPosition,
 374                       int vTextPosition, int iconTextGap, int mnemonicIndex,
 375                       int textOffset) {
 376         if ((icon == null) && (text == null)) {
 377             return;
 378         }
 379         JComponent c = ss.getComponent();
 380         FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
 381         Insets insets = SynthLookAndFeel.getPaintingInsets(ss, paintInsets);
 382 
 383         paintViewR.x = insets.left;
 384         paintViewR.y = insets.top;
 385         paintViewR.width = c.getWidth() - (insets.left + insets.right);
 386         paintViewR.height = c.getHeight() - (insets.top + insets.bottom);
 387 
 388         paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
 389         paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
 390 
 391         String clippedText =
 392             layoutText(ss, fm, text, icon, hAlign, vAlign,
 393                    hTextPosition, vTextPosition, paintViewR, paintIconR,
 394                    paintTextR, iconTextGap);
 395 
 396         if (icon != null) {
 397             Color color = g.getColor();
 398 
 399             if (ss.getStyle().getBoolean(ss, "TableHeader.alignSorterArrow", false) &&
 400                 "TableHeader.renderer".equals(c.getName())) {
 401                 paintIconR.x = paintViewR.width - paintIconR.width;
 402             } else {
 403                 paintIconR.x += textOffset;
 404             }
 405             paintIconR.y += textOffset;
 406             paintIcon(icon, ss, g, paintIconR.x, paintIconR.y,
 407                                 paintIconR.width, paintIconR.height);
 408             g.setColor(color);
 409         }
 410 
 411         if (text != null) {
 412             View v = (View) c.getClientProperty(BasicHTML.propertyKey);
 413 
 414             if (v != null) {
 415                 v.paint(g, paintTextR);
 416             } else {
 417                 paintTextR.x += textOffset;
 418                 paintTextR.y += textOffset;
 419 
 420                 paintText(ss, g, clippedText, paintTextR, mnemonicIndex);
 421             }
 422         }
 423     }
 424 
 425     /**
 426      * Returns the icon's width.
 427      * The {@code getIconWidth(context)} method is called for {@code SynthIcon}.
 428      *
 429      * @param icon the icon
 430      * @param context {@code SynthContext} requesting the icon, may be null.
 431      * @return an int specifying the width of the icon.
 432      */
 433     public static int getIconWidth(Icon icon, SynthContext context) {
 434         if (icon == null) {
 435             return 0;
 436         }
 437         if (icon instanceof SynthIcon) {
 438             return ((SynthIcon) icon).getIconWidth(context);
 439         }
 440         return icon.getIconWidth();
 441     }
 442 
 443     /**
 444      * Returns the icon's height.
 445      * The {@code getIconHeight(context)} method is called for {@code SynthIcon}.
 446      *
 447      * @param icon the icon
 448      * @param context {@code SynthContext} requesting the icon, may be null.
 449      * @return an int specifying the height of the icon.
 450      */
 451     public static int getIconHeight(Icon icon, SynthContext context) {
 452         if (icon == null) {
 453             return 0;
 454         }
 455         if (icon instanceof SynthIcon) {
 456             return ((SynthIcon) icon).getIconHeight(context);
 457         }
 458         return icon.getIconHeight();
 459     }
 460 
 461     /**
 462      * Paints the icon. The {@code paintIcon(context, g, x, y, width, height)}
 463      * method is called for {@code SynthIcon}.
 464      *
 465      * @param icon the icon
 466      * @param context identifies hosting region, may be null.
 467      * @param g the graphics context
 468      * @param x the x location to paint to
 469      * @param y the y location to paint to
 470      * @param width the width of the region to paint to, may be 0
 471      * @param height the height of the region to paint to, may be 0
 472      */
 473     public static void paintIcon(Icon icon, SynthContext context, Graphics g,
 474             int x, int y, int width, int height) {
 475         if (icon instanceof SynthIcon) {
 476             ((SynthIcon) icon).paintIcon(context, g, x, y, width, height);
 477         } else if (icon != null) {
 478             icon.paintIcon(context.getComponent(), g, x, y);
 479         }
 480     }
 481 
 482      /**
 483       * A quick note about how preferred sizes are calculated... Generally
 484       * speaking, SynthPopupMenuUI will run through the list of its children
 485       * (from top to bottom) and ask each for its preferred size.  Each menu
 486       * item will add up the max width of each element (icons, text,
 487       * accelerator spacing, accelerator text or arrow icon) encountered thus
 488       * far, so by the time all menu items have been calculated, we will
 489       * know the maximum (preferred) menu item size for that popup menu.
 490       * Later when it comes time to paint each menu item, we can use those
 491       * same accumulated max element sizes in order to layout the item.
 492       */
 493     static Dimension getPreferredMenuItemSize(SynthContext context,
 494            SynthContext accContext, JComponent c,
 495            Icon checkIcon, Icon arrowIcon, int defaultTextIconGap,
 496            String acceleratorDelimiter, boolean useCheckAndArrow,
 497            String propertyPrefix) {
 498 
 499          JMenuItem mi = (JMenuItem) c;
 500          SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper(
 501                  context, accContext, mi, checkIcon, arrowIcon,
 502                  MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap,
 503                  acceleratorDelimiter, SynthLookAndFeel.isLeftToRight(mi),
 504                  useCheckAndArrow, propertyPrefix);
 505 
 506          Dimension result = new Dimension();
 507 
 508          // Calculate the result width
 509          int gap = lh.getGap();
 510          result.width = 0;
 511          MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(), gap, result);
 512          MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), gap, result);
 513          MenuItemLayoutHelper.addWidth(lh.getMaxAccOrArrowWidth(), 5 * gap, result);
 514          // The last gap is unnecessary
 515          result.width -= gap;
 516 
 517          // Calculate the result height
 518          result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(),
 519                  lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(),
 520                  lh.getArrowSize().getHeight());
 521 
 522          // Take into account menu item insets
 523          Insets insets = lh.getMenuItem().getInsets();
 524          if (insets != null) {
 525              result.width += insets.left + insets.right;
 526              result.height += insets.top + insets.bottom;
 527          }
 528 
 529          // if the width is even, bump it up one. This is critical
 530          // for the focus dash lhne to draw properly
 531          if (result.width % 2 == 0) {
 532              result.width++;
 533          }
 534 
 535          // if the height is even, bump it up one. This is critical
 536          // for the text to center properly
 537          if (result.height % 2 == 0) {
 538              result.height++;
 539          }
 540 
 541          return result;
 542      }
 543 
 544     static void applyInsets(Rectangle rect, Insets insets, boolean leftToRight) {
 545         if (insets != null) {
 546             rect.x += (leftToRight ? insets.left : insets.right);
 547             rect.y += insets.top;
 548             rect.width -= (leftToRight ? insets.right : insets.left) + rect.x;
 549             rect.height -= (insets.bottom + rect.y);
 550         }
 551     }
 552 
 553     static void paint(SynthContext context, SynthContext accContext, Graphics g,
 554                Icon checkIcon, Icon arrowIcon, String acceleratorDelimiter,
 555                int defaultTextIconGap, String propertyPrefix) {
 556         JMenuItem mi = (JMenuItem) context.getComponent();
 557         SynthStyle style = context.getStyle();
 558         g.setFont(style.getFont(context));
 559 
 560         Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight());
 561         boolean leftToRight = SynthLookAndFeel.isLeftToRight(mi);
 562         applyInsets(viewRect, mi.getInsets(), leftToRight);
 563 
 564         SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper(
 565                 context, accContext, mi, checkIcon, arrowIcon, viewRect,
 566                 defaultTextIconGap, acceleratorDelimiter, leftToRight,
 567                 MenuItemLayoutHelper.useCheckAndArrow(mi), propertyPrefix);
 568         MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem();
 569 
 570         paintMenuItem(g, lh, lr);
 571     }
 572 
 573     static void paintMenuItem(Graphics g, SynthMenuItemLayoutHelper lh,
 574                               MenuItemLayoutHelper.LayoutResult lr) {
 575         // Save original graphics font and color
 576         Font holdf = g.getFont();
 577         Color holdc = g.getColor();
 578 
 579         paintCheckIcon(g, lh, lr);
 580         paintIcon(g, lh, lr);
 581         paintText(g, lh, lr);
 582         paintAccText(g, lh, lr);
 583         paintArrowIcon(g, lh, lr);
 584 
 585         // Restore original graphics font and color
 586         g.setColor(holdc);
 587         g.setFont(holdf);
 588     }
 589 
 590     static void paintBackground(Graphics g, SynthMenuItemLayoutHelper lh) {
 591         paintBackground(lh.getContext(), g, lh.getMenuItem());
 592     }
 593 
 594     static void paintBackground(SynthContext context, Graphics g, JComponent c) {
 595         context.getPainter().paintMenuItemBackground(context, g, 0, 0,
 596                 c.getWidth(), c.getHeight());
 597     }
 598 
 599     static void paintIcon(Graphics g, SynthMenuItemLayoutHelper lh,
 600                           MenuItemLayoutHelper.LayoutResult lr) {
 601         if (lh.getIcon() != null) {
 602             Icon icon;
 603             JMenuItem mi = lh.getMenuItem();
 604             ButtonModel model = mi.getModel();
 605             if (!model.isEnabled()) {
 606                 icon = mi.getDisabledIcon();
 607             } else if (model.isPressed() && model.isArmed()) {
 608                 icon = mi.getPressedIcon();
 609                 if (icon == null) {
 610                     // Use default icon
 611                     icon = mi.getIcon();
 612                 }
 613             } else {
 614                 icon = mi.getIcon();
 615             }
 616 
 617             if (icon != null) {
 618                 Rectangle iconRect = lr.getIconRect();
 619                 paintIcon(icon, lh.getContext(), g, iconRect.x,
 620                         iconRect.y, iconRect.width, iconRect.height);
 621             }
 622         }
 623     }
 624 
 625     static void paintCheckIcon(Graphics g, SynthMenuItemLayoutHelper lh,
 626                                MenuItemLayoutHelper.LayoutResult lr) {
 627         if (lh.getCheckIcon() != null) {
 628             Rectangle checkRect = lr.getCheckRect();
 629             paintIcon(lh.getCheckIcon(), lh.getContext(), g,
 630                     checkRect.x, checkRect.y, checkRect.width, checkRect.height);
 631         }
 632     }
 633 
 634     static void paintAccText(Graphics g, SynthMenuItemLayoutHelper lh,
 635                              MenuItemLayoutHelper.LayoutResult lr) {
 636         String accText = lh.getAccText();
 637         if (accText != null && !accText.equals("")) {
 638             g.setColor(lh.getAccStyle().getColor(lh.getAccContext(),
 639                     ColorType.TEXT_FOREGROUND));
 640             g.setFont(lh.getAccStyle().getFont(lh.getAccContext()));
 641             lh.getAccGraphicsUtils().paintText(lh.getAccContext(), g, accText,
 642                     lr.getAccRect().x, lr.getAccRect().y, -1);
 643         }
 644     }
 645 
 646     static void paintText(Graphics g, SynthMenuItemLayoutHelper lh,
 647                           MenuItemLayoutHelper.LayoutResult lr) {
 648         if (!lh.getText().equals("")) {
 649             if (lh.getHtmlView() != null) {
 650                 // Text is HTML
 651                 lh.getHtmlView().paint(g, lr.getTextRect());
 652             } else {
 653                 // Text isn't HTML
 654                 g.setColor(lh.getStyle().getColor(
 655                         lh.getContext(), ColorType.TEXT_FOREGROUND));
 656                 g.setFont(lh.getStyle().getFont(lh.getContext()));
 657                 lh.getGraphicsUtils().paintText(lh.getContext(), g, lh.getText(),
 658                         lr.getTextRect().x, lr.getTextRect().y,
 659                         lh.getMenuItem().getDisplayedMnemonicIndex());
 660             }
 661         }
 662     }
 663 
 664     static void paintArrowIcon(Graphics g, SynthMenuItemLayoutHelper lh,
 665                                MenuItemLayoutHelper.LayoutResult lr) {
 666         if (lh.getArrowIcon() != null) {
 667             Rectangle arrowRect = lr.getArrowRect();
 668             paintIcon(lh.getArrowIcon(), lh.getContext(), g,
 669                     arrowRect.x, arrowRect.y, arrowRect.width, arrowRect.height);
 670         }
 671     }
 672 
 673     /**
 674      * Wraps a SynthIcon around the Icon interface, forwarding calls to
 675      * the SynthIcon with a given SynthContext.
 676      */
 677     private static class SynthIconWrapper implements Icon {
 678         private static final java.util.List<SynthIconWrapper> CACHE = new java.util.ArrayList<SynthIconWrapper>(1);
 679 
 680         private SynthIcon synthIcon;
 681         private SynthContext context;
 682 
 683         static SynthIconWrapper get(SynthIcon icon, SynthContext context) {
 684             synchronized(CACHE) {
 685                 int size = CACHE.size();
 686                 if (size > 0) {
 687                     SynthIconWrapper wrapper = CACHE.remove(size - 1);
 688                     wrapper.reset(icon, context);
 689                     return wrapper;
 690                 }
 691             }
 692             return new SynthIconWrapper(icon, context);
 693         }
 694 
 695         static void release(SynthIconWrapper wrapper) {
 696             wrapper.reset(null, null);
 697             synchronized(CACHE) {
 698                 CACHE.add(wrapper);
 699             }
 700         }
 701 
 702         SynthIconWrapper(SynthIcon icon, SynthContext context) {
 703             reset(icon, context);
 704         }
 705 
 706         void reset(SynthIcon icon, SynthContext context) {
 707             synthIcon = icon;
 708             this.context = context;
 709         }
 710 
 711         public void paintIcon(Component c, Graphics g, int x, int y) {
 712             // This is a noop as this should only be for sizing calls.
 713         }
 714 
 715         public int getIconWidth() {
 716             return synthIcon.getIconWidth(context);
 717         }
 718 
 719         public int getIconHeight() {
 720             return synthIcon.getIconHeight(context);
 721         }
 722     }
 723 }