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