View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, 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  
26  package javax.swing.plaf.basic;
27  
28  import java.awt.*;
29  import java.awt.event.*;
30  import java.beans.PropertyChangeEvent;
31  import java.beans.PropertyChangeListener;
32  
33  import javax.swing.*;
34  import javax.swing.event.*;
35  import javax.swing.plaf.*;
36  import javax.swing.text.View;
37  
38  import sun.swing.*;
39  
40  /**
41   * BasicMenuItem implementation
42   *
43   * @author Georges Saab
44   * @author David Karlton
45   * @author Arnaud Weber
46   * @author Fredrik Lagerblad
47   */
48  public class BasicMenuItemUI extends MenuItemUI
49  {
50      protected JMenuItem menuItem = null;
51      protected Color selectionBackground;
52      protected Color selectionForeground;
53      protected Color disabledForeground;
54      protected Color acceleratorForeground;
55      protected Color acceleratorSelectionForeground;
56  
57      /**
58       * Accelerator delimiter string, such as {@code '+'} in {@code 'Ctrl+C'}.
59       * @since 1.7
60       */
61      protected String acceleratorDelimiter;
62  
63      protected int defaultTextIconGap;
64      protected Font acceleratorFont;
65  
66      protected MouseInputListener mouseInputListener;
67      protected MenuDragMouseListener menuDragMouseListener;
68      protected MenuKeyListener menuKeyListener;
69      /**
70       * <code>PropertyChangeListener</code> returned from
71       * <code>createPropertyChangeListener</code>. You should not
72       * need to access this field, rather if you want to customize the
73       * <code>PropertyChangeListener</code> override
74       * <code>createPropertyChangeListener</code>.
75       *
76       * @since 1.6
77       * @see #createPropertyChangeListener
78       */
79      protected PropertyChangeListener propertyChangeListener;
80      // BasicMenuUI also uses this.
81      Handler handler;
82  
83      protected Icon arrowIcon = null;
84      protected Icon checkIcon = null;
85  
86      protected boolean oldBorderPainted;
87  
88      /* diagnostic aids -- should be false for production builds. */
89      private static final boolean TRACE =   false; // trace creates and disposes
90  
91      private static final boolean VERBOSE = false; // show reuse hits/misses
92      private static final boolean DEBUG =   false;  // show bad params, misc.
93  
94      static void loadActionMap(LazyActionMap map) {
95          // NOTE: BasicMenuUI also calls into this method.
96          map.put(new Actions(Actions.CLICK));
97          BasicLookAndFeel.installAudioActionMap(map);
98      }
99  
100     public static ComponentUI createUI(JComponent c) {
101         return new BasicMenuItemUI();
102     }
103 
104     public void installUI(JComponent c) {
105         menuItem = (JMenuItem) c;
106 
107         installDefaults();
108         installComponents(menuItem);
109         installListeners();
110         installKeyboardActions();
111     }
112 
113 
114     protected void installDefaults() {
115         String prefix = getPropertyPrefix();
116 
117         acceleratorFont = UIManager.getFont("MenuItem.acceleratorFont");
118         // use default if missing so that BasicMenuItemUI can be used in other
119         // LAFs like Nimbus
120         if (acceleratorFont == null) {
121             acceleratorFont = UIManager.getFont("MenuItem.font");
122         }
123 
124         Object opaque = UIManager.get(getPropertyPrefix() + ".opaque");
125         if (opaque != null) {
126             LookAndFeel.installProperty(menuItem, "opaque", opaque);
127         }
128         else {
129             LookAndFeel.installProperty(menuItem, "opaque", Boolean.TRUE);
130         }
131         if(menuItem.getMargin() == null ||
132            (menuItem.getMargin() instanceof UIResource)) {
133             menuItem.setMargin(UIManager.getInsets(prefix + ".margin"));
134         }
135 
136         LookAndFeel.installProperty(menuItem, "iconTextGap", Integer.valueOf(4));
137         defaultTextIconGap = menuItem.getIconTextGap();
138 
139         LookAndFeel.installBorder(menuItem, prefix + ".border");
140         oldBorderPainted = menuItem.isBorderPainted();
141         LookAndFeel.installProperty(menuItem, "borderPainted",
142                                     UIManager.getBoolean(prefix + ".borderPainted"));
143         LookAndFeel.installColorsAndFont(menuItem,
144                                          prefix + ".background",
145                                          prefix + ".foreground",
146                                          prefix + ".font");
147 
148         // MenuItem specific defaults
149         if (selectionBackground == null ||
150             selectionBackground instanceof UIResource) {
151             selectionBackground =
152                 UIManager.getColor(prefix + ".selectionBackground");
153         }
154         if (selectionForeground == null ||
155             selectionForeground instanceof UIResource) {
156             selectionForeground =
157                 UIManager.getColor(prefix + ".selectionForeground");
158         }
159         if (disabledForeground == null ||
160             disabledForeground instanceof UIResource) {
161             disabledForeground =
162                 UIManager.getColor(prefix + ".disabledForeground");
163         }
164         if (acceleratorForeground == null ||
165             acceleratorForeground instanceof UIResource) {
166             acceleratorForeground =
167                 UIManager.getColor(prefix + ".acceleratorForeground");
168         }
169         if (acceleratorSelectionForeground == null ||
170             acceleratorSelectionForeground instanceof UIResource) {
171             acceleratorSelectionForeground =
172                 UIManager.getColor(prefix + ".acceleratorSelectionForeground");
173         }
174         // Get accelerator delimiter
175         acceleratorDelimiter =
176             UIManager.getString("MenuItem.acceleratorDelimiter");
177         if (acceleratorDelimiter == null) { acceleratorDelimiter = "+"; }
178         // Icons
179         if (arrowIcon == null ||
180             arrowIcon instanceof UIResource) {
181             arrowIcon = UIManager.getIcon(prefix + ".arrowIcon");
182         }
183         if (checkIcon == null ||
184             checkIcon instanceof UIResource) {
185             checkIcon = UIManager.getIcon(prefix + ".checkIcon");
186             //In case of column layout, .checkIconFactory is defined for this UI,
187             //the icon is compatible with it and useCheckAndArrow() is true,
188             //then the icon is handled by the checkIcon.
189             boolean isColumnLayout = MenuItemLayoutHelper.isColumnLayout(
190                     BasicGraphicsUtils.isLeftToRight(menuItem), menuItem);
191             if (isColumnLayout) {
192                 MenuItemCheckIconFactory iconFactory =
193                     (MenuItemCheckIconFactory) UIManager.get(prefix
194                         + ".checkIconFactory");
195                 if (iconFactory != null
196                         && MenuItemLayoutHelper.useCheckAndArrow(menuItem)
197                         && iconFactory.isCompatible(checkIcon, prefix)) {
198                     checkIcon = iconFactory.getIcon(menuItem);
199                 }
200             }
201         }
202     }
203 
204     /**
205      * @since 1.3
206      */
207     protected void installComponents(JMenuItem menuItem){
208         BasicHTML.updateRenderer(menuItem, menuItem.getText());
209     }
210 
211     protected String getPropertyPrefix() {
212         return "MenuItem";
213     }
214 
215     protected void installListeners() {
216         if ((mouseInputListener = createMouseInputListener(menuItem)) != null) {
217             menuItem.addMouseListener(mouseInputListener);
218             menuItem.addMouseMotionListener(mouseInputListener);
219         }
220         if ((menuDragMouseListener = createMenuDragMouseListener(menuItem)) != null) {
221             menuItem.addMenuDragMouseListener(menuDragMouseListener);
222         }
223         if ((menuKeyListener = createMenuKeyListener(menuItem)) != null) {
224             menuItem.addMenuKeyListener(menuKeyListener);
225         }
226         if ((propertyChangeListener = createPropertyChangeListener(menuItem)) != null) {
227             menuItem.addPropertyChangeListener(propertyChangeListener);
228         }
229     }
230 
231     protected void installKeyboardActions() {
232         installLazyActionMap();
233         updateAcceleratorBinding();
234     }
235 
236     void installLazyActionMap() {
237         LazyActionMap.installLazyActionMap(menuItem, BasicMenuItemUI.class,
238                                            getPropertyPrefix() + ".actionMap");
239     }
240 
241     public void uninstallUI(JComponent c) {
242         menuItem = (JMenuItem)c;
243         uninstallDefaults();
244         uninstallComponents(menuItem);
245         uninstallListeners();
246         uninstallKeyboardActions();
247         MenuItemLayoutHelper.clearUsedParentClientProperties(menuItem);
248         menuItem = null;
249     }
250 
251 
252     protected void uninstallDefaults() {
253         LookAndFeel.uninstallBorder(menuItem);
254         LookAndFeel.installProperty(menuItem, "borderPainted", oldBorderPainted);
255         if (menuItem.getMargin() instanceof UIResource)
256             menuItem.setMargin(null);
257         if (arrowIcon instanceof UIResource)
258             arrowIcon = null;
259         if (checkIcon instanceof UIResource)
260             checkIcon = null;
261     }
262 
263     /**
264      * @since 1.3
265      */
266     protected void uninstallComponents(JMenuItem menuItem){
267         BasicHTML.updateRenderer(menuItem, "");
268     }
269 
270     protected void uninstallListeners() {
271         if (mouseInputListener != null) {
272             menuItem.removeMouseListener(mouseInputListener);
273             menuItem.removeMouseMotionListener(mouseInputListener);
274         }
275         if (menuDragMouseListener != null) {
276             menuItem.removeMenuDragMouseListener(menuDragMouseListener);
277         }
278         if (menuKeyListener != null) {
279             menuItem.removeMenuKeyListener(menuKeyListener);
280         }
281         if (propertyChangeListener != null) {
282             menuItem.removePropertyChangeListener(propertyChangeListener);
283         }
284 
285         mouseInputListener = null;
286         menuDragMouseListener = null;
287         menuKeyListener = null;
288         propertyChangeListener = null;
289         handler = null;
290     }
291 
292     protected void uninstallKeyboardActions() {
293         SwingUtilities.replaceUIActionMap(menuItem, null);
294         SwingUtilities.replaceUIInputMap(menuItem, JComponent.
295                                          WHEN_IN_FOCUSED_WINDOW, null);
296     }
297 
298     protected MouseInputListener createMouseInputListener(JComponent c) {
299         return getHandler();
300     }
301 
302     protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) {
303         return getHandler();
304     }
305 
306     protected MenuKeyListener createMenuKeyListener(JComponent c) {
307         return null;
308     }
309 
310     /**
311      * Creates a <code>PropertyChangeListener</code> which will be added to
312      * the menu item.
313      * If this method returns null then it will not be added to the menu item.
314      *
315      * @return an instance of a <code>PropertyChangeListener</code> or null
316      * @since 1.6
317      */
318     protected PropertyChangeListener
319                                   createPropertyChangeListener(JComponent c) {
320         return getHandler();
321     }
322 
323     Handler getHandler() {
324         if (handler == null) {
325             handler = new Handler();
326         }
327         return handler;
328     }
329 
330     InputMap createInputMap(int condition) {
331         if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
332             return new ComponentInputMapUIResource(menuItem);
333         }
334         return null;
335     }
336 
337     void updateAcceleratorBinding() {
338         KeyStroke accelerator = menuItem.getAccelerator();
339         InputMap windowInputMap = SwingUtilities.getUIInputMap(
340                        menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
341 
342         if (windowInputMap != null) {
343             windowInputMap.clear();
344         }
345         if (accelerator != null) {
346             if (windowInputMap == null) {
347                 windowInputMap = createInputMap(JComponent.
348                                                 WHEN_IN_FOCUSED_WINDOW);
349                 SwingUtilities.replaceUIInputMap(menuItem,
350                            JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap);
351             }
352             windowInputMap.put(accelerator, "doClick");
353         }
354     }
355 
356     public Dimension getMinimumSize(JComponent c) {
357         Dimension d = null;
358         View v = (View) c.getClientProperty(BasicHTML.propertyKey);
359         if (v != null) {
360             d = getPreferredSize(c);
361             d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);
362         }
363         return d;
364     }
365 
366     public Dimension getPreferredSize(JComponent c) {
367         return getPreferredMenuItemSize(c,
368                                         checkIcon,
369                                         arrowIcon,
370                                         defaultTextIconGap);
371     }
372 
373     public Dimension getMaximumSize(JComponent c) {
374         Dimension d = null;
375         View v = (View) c.getClientProperty(BasicHTML.propertyKey);
376         if (v != null) {
377             d = getPreferredSize(c);
378             d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);
379         }
380         return d;
381     }
382 
383     protected Dimension getPreferredMenuItemSize(JComponent c,
384                                                  Icon checkIcon,
385                                                  Icon arrowIcon,
386                                                  int defaultTextIconGap) {
387 
388         // The method also determines the preferred width of the
389         // parent popup menu (through DefaultMenuLayout class).
390         // The menu width equals to the maximal width
391         // among child menu items.
392 
393         // Menu item width will be a sum of the widest check icon, label,
394         // arrow icon and accelerator text among neighbor menu items.
395         // For the latest menu item we will know the maximal widths exactly.
396         // It will be the widest menu item and it will determine
397         // the width of the parent popup menu.
398 
399         // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
400         // There is a conceptual problem: if user sets preferred size manually
401         // for a menu item, this method won't be called for it
402         // (see JComponent.getPreferredSize()),
403         // maximal widths won't be calculated, other menu items won't be able
404         // to take them into account and will be layouted in such a way,
405         // as there is no the item with manual preferred size.
406         // But after the first paint() method call, all maximal widths
407         // will be correctly calculated and layout of some menu items
408         // can be changed. For example, it can cause a shift of
409         // the icon and text when user points a menu item by mouse.
410 
411         JMenuItem mi = (JMenuItem) c;
412         MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon,
413                 arrowIcon, MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap,
414                 acceleratorDelimiter, BasicGraphicsUtils.isLeftToRight(mi),
415                 mi.getFont(), acceleratorFont,
416                 MenuItemLayoutHelper.useCheckAndArrow(menuItem),
417                 getPropertyPrefix());
418 
419         Dimension result = new Dimension();
420 
421         // Calculate the result width
422         result.width = lh.getLeadingGap();
423         MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(),
424                 lh.getAfterCheckIconGap(), result);
425         // Take into account mimimal text offset.
426         if ((!lh.isTopLevelMenu())
427                 && (lh.getMinTextOffset() > 0)
428                 && (result.width < lh.getMinTextOffset())) {
429             result.width = lh.getMinTextOffset();
430         }
431         MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), lh.getGap(), result);
432         MenuItemLayoutHelper.addMaxWidth(lh.getAccSize(), lh.getGap(), result);
433         MenuItemLayoutHelper.addMaxWidth(lh.getArrowSize(), lh.getGap(), result);
434 
435         // Calculate the result height
436         result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(),
437                 lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(),
438                 lh.getArrowSize().getHeight());
439 
440         // Take into account menu item insets
441         Insets insets = lh.getMenuItem().getInsets();
442         if(insets != null) {
443             result.width += insets.left + insets.right;
444             result.height += insets.top + insets.bottom;
445         }
446 
447         // if the width is even, bump it up one. This is critical
448         // for the focus dash line to draw properly
449         if(result.width%2 == 0) {
450             result.width++;
451         }
452 
453         // if the height is even, bump it up one. This is critical
454         // for the text to center properly
455         if(result.height%2 == 0
456                 && Boolean.TRUE !=
457                     UIManager.get(getPropertyPrefix() + ".evenHeight")) {
458             result.height++;
459         }
460 
461         return result;
462     }
463 
464     /**
465      * We draw the background in paintMenuItem()
466      * so override update (which fills the background of opaque
467      * components by default) to just call paint().
468      *
469      */
470     public void update(Graphics g, JComponent c) {
471         paint(g, c);
472     }
473 
474     public void paint(Graphics g, JComponent c) {
475         paintMenuItem(g, c, checkIcon, arrowIcon,
476                       selectionBackground, selectionForeground,
477                       defaultTextIconGap);
478     }
479 
480     protected void paintMenuItem(Graphics g, JComponent c,
481                                      Icon checkIcon, Icon arrowIcon,
482                                      Color background, Color foreground,
483                                      int defaultTextIconGap) {
484         // Save original graphics font and color
485         Font holdf = g.getFont();
486         Color holdc = g.getColor();
487 
488         JMenuItem mi = (JMenuItem) c;
489         g.setFont(mi.getFont());
490 
491         Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight());
492         applyInsets(viewRect, mi.getInsets());
493 
494         MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon,
495                 arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter,
496                 BasicGraphicsUtils.isLeftToRight(mi), mi.getFont(),
497                 acceleratorFont, MenuItemLayoutHelper.useCheckAndArrow(menuItem),
498                 getPropertyPrefix());
499         MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem();
500 
501         paintBackground(g, mi, background);
502         paintCheckIcon(g, lh, lr, holdc, foreground);
503         paintIcon(g, lh, lr, holdc);
504         paintText(g, lh, lr);
505         paintAccText(g, lh, lr);
506         paintArrowIcon(g, lh, lr, foreground);
507 
508         // Restore original graphics font and color
509         g.setColor(holdc);
510         g.setFont(holdf);
511     }
512 
513     private void paintIcon(Graphics g, MenuItemLayoutHelper lh,
514                            MenuItemLayoutHelper.LayoutResult lr, Color holdc) {
515         if (lh.getIcon() != null) {
516             Icon icon;
517             ButtonModel model = lh.getMenuItem().getModel();
518             if (!model.isEnabled()) {
519                 icon = lh.getMenuItem().getDisabledIcon();
520             } else if (model.isPressed() && model.isArmed()) {
521                 icon = lh.getMenuItem().getPressedIcon();
522                 if (icon == null) {
523                     // Use default icon
524                     icon = lh.getMenuItem().getIcon();
525                 }
526             } else {
527                 icon = lh.getMenuItem().getIcon();
528             }
529 
530             if (icon != null) {
531                 icon.paintIcon(lh.getMenuItem(), g, lr.getIconRect().x,
532                         lr.getIconRect().y);
533                 g.setColor(holdc);
534             }
535         }
536     }
537 
538     private void paintCheckIcon(Graphics g, MenuItemLayoutHelper lh,
539                                 MenuItemLayoutHelper.LayoutResult lr,
540                                 Color holdc, Color foreground) {
541         if (lh.getCheckIcon() != null) {
542             ButtonModel model = lh.getMenuItem().getModel();
543             if (model.isArmed() || (lh.getMenuItem() instanceof JMenu
544                     && model.isSelected())) {
545                 g.setColor(foreground);
546             } else {
547                 g.setColor(holdc);
548             }
549             if (lh.useCheckAndArrow()) {
550                 lh.getCheckIcon().paintIcon(lh.getMenuItem(), g,
551                         lr.getCheckRect().x, lr.getCheckRect().y);
552             }
553             g.setColor(holdc);
554         }
555     }
556 
557     private void paintAccText(Graphics g, MenuItemLayoutHelper lh,
558                               MenuItemLayoutHelper.LayoutResult lr) {
559         if (!lh.getAccText().equals("")) {
560             ButtonModel model = lh.getMenuItem().getModel();
561             g.setFont(lh.getAccFontMetrics().getFont());
562             if (!model.isEnabled()) {
563                 // *** paint the accText disabled
564                 if (disabledForeground != null) {
565                     g.setColor(disabledForeground);
566                     SwingUtilities2.drawString(lh.getMenuItem(), g,
567                         lh.getAccText(), lr.getAccRect().x,
568                         lr.getAccRect().y + lh.getAccFontMetrics().getAscent());
569                 } else {
570                     g.setColor(lh.getMenuItem().getBackground().brighter());
571                     SwingUtilities2.drawString(lh.getMenuItem(), g,
572                         lh.getAccText(), lr.getAccRect().x,
573                         lr.getAccRect().y + lh.getAccFontMetrics().getAscent());
574                     g.setColor(lh.getMenuItem().getBackground().darker());
575                     SwingUtilities2.drawString(lh.getMenuItem(), g,
576                         lh.getAccText(), lr.getAccRect().x - 1,
577                         lr.getAccRect().y + lh.getFontMetrics().getAscent() - 1);
578                 }
579             } else {
580                 // *** paint the accText normally
581                 if (model.isArmed()
582                         || (lh.getMenuItem() instanceof JMenu
583                         && model.isSelected())) {
584                     g.setColor(acceleratorSelectionForeground);
585                 } else {
586                     g.setColor(acceleratorForeground);
587                 }
588                 SwingUtilities2.drawString(lh.getMenuItem(), g, lh.getAccText(),
589                         lr.getAccRect().x, lr.getAccRect().y +
590                         lh.getAccFontMetrics().getAscent());
591             }
592         }
593     }
594 
595     private void paintText(Graphics g, MenuItemLayoutHelper lh,
596                            MenuItemLayoutHelper.LayoutResult lr) {
597         if (!lh.getText().equals("")) {
598             if (lh.getHtmlView() != null) {
599                 // Text is HTML
600                 lh.getHtmlView().paint(g, lr.getTextRect());
601             } else {
602                 // Text isn't HTML
603                 paintText(g, lh.getMenuItem(), lr.getTextRect(), lh.getText());
604             }
605         }
606     }
607 
608     private void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh,
609                                 MenuItemLayoutHelper.LayoutResult lr,
610                                 Color foreground) {
611         if (lh.getArrowIcon() != null) {
612             ButtonModel model = lh.getMenuItem().getModel();
613             if (model.isArmed() || (lh.getMenuItem() instanceof JMenu
614                                 && model.isSelected())) {
615                 g.setColor(foreground);
616             }
617             if (lh.useCheckAndArrow()) {
618                 lh.getArrowIcon().paintIcon(lh.getMenuItem(), g,
619                         lr.getArrowRect().x, lr.getArrowRect().y);
620             }
621         }
622     }
623 
624     private void applyInsets(Rectangle rect, Insets insets) {
625         if(insets != null) {
626             rect.x += insets.left;
627             rect.y += insets.top;
628             rect.width -= (insets.right + rect.x);
629             rect.height -= (insets.bottom + rect.y);
630         }
631     }
632 
633     /**
634      * Draws the background of the menu item.
635      *
636      * @param g the paint graphics
637      * @param menuItem menu item to be painted
638      * @param bgColor selection background color
639      * @since 1.4
640      */
641     protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) {
642         ButtonModel model = menuItem.getModel();
643         Color oldColor = g.getColor();
644         int menuWidth = menuItem.getWidth();
645         int menuHeight = menuItem.getHeight();
646 
647         if(menuItem.isOpaque()) {
648             if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) {
649                 g.setColor(bgColor);
650                 g.fillRect(0,0, menuWidth, menuHeight);
651             } else {
652                 g.setColor(menuItem.getBackground());
653                 g.fillRect(0,0, menuWidth, menuHeight);
654             }
655             g.setColor(oldColor);
656         }
657         else if (model.isArmed() || (menuItem instanceof JMenu &&
658                                      model.isSelected())) {
659             g.setColor(bgColor);
660             g.fillRect(0,0, menuWidth, menuHeight);
661             g.setColor(oldColor);
662         }
663     }
664 
665     /**
666      * Renders the text of the current menu item.
667      * <p>
668      * @param g graphics context
669      * @param menuItem menu item to render
670      * @param textRect bounding rectangle for rendering the text
671      * @param text string to render
672      * @since 1.4
673      */
674     protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) {
675         ButtonModel model = menuItem.getModel();
676         FontMetrics fm = SwingUtilities2.getFontMetrics(menuItem, g);
677         int mnemIndex = menuItem.getDisplayedMnemonicIndex();
678 
679         if(!model.isEnabled()) {
680             // *** paint the text disabled
681             if ( UIManager.get("MenuItem.disabledForeground") instanceof Color ) {
682                 g.setColor( UIManager.getColor("MenuItem.disabledForeground") );
683                 SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text,
684                           mnemIndex, textRect.x,  textRect.y + fm.getAscent());
685             } else {
686                 g.setColor(menuItem.getBackground().brighter());
687                 SwingUtilities2.drawStringUnderlineCharAt(menuItem, g, text,
688                            mnemIndex, textRect.x, textRect.y + fm.getAscent());
689                 g.setColor(menuItem.getBackground().darker());
690                 SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text,
691                            mnemIndex,  textRect.x - 1, textRect.y +
692                            fm.getAscent() - 1);
693             }
694         } else {
695             // *** paint the text normally
696             if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) {
697                 g.setColor(selectionForeground); // Uses protected field.
698             }
699             SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text,
700                            mnemIndex, textRect.x, textRect.y + fm.getAscent());
701         }
702     }
703 
704     public MenuElement[] getPath() {
705         MenuSelectionManager m = MenuSelectionManager.defaultManager();
706         MenuElement oldPath[] = m.getSelectedPath();
707         MenuElement newPath[];
708         int i = oldPath.length;
709         if (i == 0)
710             return new MenuElement[0];
711         Component parent = menuItem.getParent();
712         if (oldPath[i-1].getComponent() == parent) {
713             // The parent popup menu is the last so far
714             newPath = new MenuElement[i+1];
715             System.arraycopy(oldPath, 0, newPath, 0, i);
716             newPath[i] = menuItem;
717         } else {
718             // A sibling menuitem is the current selection
719             //
720             //  This probably needs to handle 'exit submenu into
721             // a menu item.  Search backwards along the current
722             // selection until you find the parent popup menu,
723             // then copy up to that and add yourself...
724             int j;
725             for (j = oldPath.length-1; j >= 0; j--) {
726                 if (oldPath[j].getComponent() == parent)
727                     break;
728             }
729             newPath = new MenuElement[j+2];
730             System.arraycopy(oldPath, 0, newPath, 0, j+1);
731             newPath[j+1] = menuItem;
732             /*
733             System.out.println("Sibling condition -- ");
734             System.out.println("Old array : ");
735             printMenuElementArray(oldPath, false);
736             System.out.println("New array : ");
737             printMenuElementArray(newPath, false);
738             */
739         }
740         return newPath;
741     }
742 
743     void printMenuElementArray(MenuElement path[], boolean dumpStack) {
744         System.out.println("Path is(");
745         int i, j;
746         for(i=0,j=path.length; i<j ;i++){
747             for (int k=0; k<=i; k++)
748                 System.out.print("  ");
749             MenuElement me = path[i];
750             if(me instanceof JMenuItem)
751                 System.out.println(((JMenuItem)me).getText() + ", ");
752             else if (me == null)
753                 System.out.println("NULL , ");
754             else
755                 System.out.println("" + me + ", ");
756         }
757         System.out.println(")");
758 
759         if (dumpStack == true)
760             Thread.dumpStack();
761     }
762     protected class MouseInputHandler implements MouseInputListener {
763         // NOTE: This class exists only for backward compatibility. All
764         // its functionality has been moved into Handler. If you need to add
765         // new functionality add it to the Handler, but make sure this
766         // class calls into the Handler.
767 
768         public void mouseClicked(MouseEvent e) {
769             getHandler().mouseClicked(e);
770         }
771         public void mousePressed(MouseEvent e) {
772             getHandler().mousePressed(e);
773         }
774         public void mouseReleased(MouseEvent e) {
775             getHandler().mouseReleased(e);
776         }
777         public void mouseEntered(MouseEvent e) {
778             getHandler().mouseEntered(e);
779         }
780         public void mouseExited(MouseEvent e) {
781             getHandler().mouseExited(e);
782         }
783         public void mouseDragged(MouseEvent e) {
784             getHandler().mouseDragged(e);
785         }
786         public void mouseMoved(MouseEvent e) {
787             getHandler().mouseMoved(e);
788         }
789     }
790 
791 
792     private static class Actions extends UIAction {
793         private static final String CLICK = "doClick";
794 
795         Actions(String key) {
796             super(key);
797         }
798 
799         public void actionPerformed(ActionEvent e) {
800             JMenuItem mi = (JMenuItem)e.getSource();
801             MenuSelectionManager.defaultManager().clearSelectedPath();
802             mi.doClick();
803         }
804     }
805 
806     /**
807      * Call this method when a menu item is to be activated.
808      * This method handles some of the details of menu item activation
809      * such as clearing the selected path and messaging the
810      * JMenuItem's doClick() method.
811      *
812      * @param msm  A MenuSelectionManager. The visual feedback and
813      *             internal bookkeeping tasks are delegated to
814      *             this MenuSelectionManager. If <code>null</code> is
815      *             passed as this argument, the
816      *             <code>MenuSelectionManager.defaultManager</code> is
817      *             used.
818      * @see MenuSelectionManager
819      * @see JMenuItem#doClick(int)
820      * @since 1.4
821      */
822     protected void doClick(MenuSelectionManager msm) {
823         // Auditory cue
824         if (! isInternalFrameSystemMenu()) {
825             BasicLookAndFeel.playSound(menuItem, getPropertyPrefix() +
826                                        ".commandSound");
827         }
828         // Visual feedback
829         if (msm == null) {
830             msm = MenuSelectionManager.defaultManager();
831         }
832         msm.clearSelectedPath();
833         menuItem.doClick(0);
834     }
835 
836     /**
837      * This is to see if the menu item in question is part of the
838      * system menu on an internal frame.
839      * The Strings that are being checked can be found in
840      * MetalInternalFrameTitlePaneUI.java,
841      * WindowsInternalFrameTitlePaneUI.java, and
842      * MotifInternalFrameTitlePaneUI.java.
843      *
844      * @since 1.4
845      */
846     private boolean isInternalFrameSystemMenu() {
847         String actionCommand = menuItem.getActionCommand();
848         if ((actionCommand == "Close") ||
849             (actionCommand == "Minimize") ||
850             (actionCommand == "Restore") ||
851             (actionCommand == "Maximize")) {
852           return true;
853         } else {
854           return false;
855         }
856     }
857 
858 
859     // BasicMenuUI subclasses this.
860     class Handler implements MenuDragMouseListener,
861                           MouseInputListener, PropertyChangeListener {
862         //
863         // MouseInputListener
864         //
865         public void mouseClicked(MouseEvent e) {}
866         public void mousePressed(MouseEvent e) {
867         }
868         public void mouseReleased(MouseEvent e) {
869             if (!menuItem.isEnabled()) {
870                 return;
871             }
872             MenuSelectionManager manager =
873                 MenuSelectionManager.defaultManager();
874             Point p = e.getPoint();
875             if(p.x >= 0 && p.x < menuItem.getWidth() &&
876                p.y >= 0 && p.y < menuItem.getHeight()) {
877                 doClick(manager);
878             } else {
879                 manager.processMouseEvent(e);
880             }
881         }
882         public void mouseEntered(MouseEvent e) {
883             MenuSelectionManager manager = MenuSelectionManager.defaultManager();
884             int modifiers = e.getModifiers();
885             // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
886             if ((modifiers & (InputEvent.BUTTON1_MASK |
887                               InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) {
888                 MenuSelectionManager.defaultManager().processMouseEvent(e);
889             } else {
890             manager.setSelectedPath(getPath());
891              }
892         }
893         public void mouseExited(MouseEvent e) {
894             MenuSelectionManager manager = MenuSelectionManager.defaultManager();
895 
896             int modifiers = e.getModifiers();
897             // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
898             if ((modifiers & (InputEvent.BUTTON1_MASK |
899                               InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) {
900                 MenuSelectionManager.defaultManager().processMouseEvent(e);
901             } else {
902 
903                 MenuElement path[] = manager.getSelectedPath();
904                 if (path.length > 1 && path[path.length-1] == menuItem) {
905                     MenuElement newPath[] = new MenuElement[path.length-1];
906                     int i,c;
907                     for(i=0,c=path.length-1;i<c;i++)
908                         newPath[i] = path[i];
909                     manager.setSelectedPath(newPath);
910                 }
911                 }
912         }
913 
914         public void mouseDragged(MouseEvent e) {
915             MenuSelectionManager.defaultManager().processMouseEvent(e);
916         }
917         public void mouseMoved(MouseEvent e) {
918         }
919 
920         //
921         // MenuDragListener
922         //
923         public void menuDragMouseEntered(MenuDragMouseEvent e) {
924             MenuSelectionManager manager = e.getMenuSelectionManager();
925             MenuElement path[] = e.getPath();
926             manager.setSelectedPath(path);
927         }
928         public void menuDragMouseDragged(MenuDragMouseEvent e) {
929             MenuSelectionManager manager = e.getMenuSelectionManager();
930             MenuElement path[] = e.getPath();
931             manager.setSelectedPath(path);
932         }
933         public void menuDragMouseExited(MenuDragMouseEvent e) {}
934         public void menuDragMouseReleased(MenuDragMouseEvent e) {
935             if (!menuItem.isEnabled()) {
936                 return;
937             }
938             MenuSelectionManager manager = e.getMenuSelectionManager();
939             MenuElement path[] = e.getPath();
940             Point p = e.getPoint();
941             if (p.x >= 0 && p.x < menuItem.getWidth() &&
942                     p.y >= 0 && p.y < menuItem.getHeight()) {
943                 doClick(manager);
944             } else {
945                 manager.clearSelectedPath();
946             }
947         }
948 
949 
950         //
951         // PropertyChangeListener
952         //
953         public void propertyChange(PropertyChangeEvent e) {
954             String name = e.getPropertyName();
955 
956             if (name == "labelFor" || name == "displayedMnemonic" ||
957                 name == "accelerator") {
958                 updateAcceleratorBinding();
959             } else if (name == "text" || "font" == name ||
960                        "foreground" == name) {
961                 // remove the old html view client property if one
962                 // existed, and install a new one if the text installed
963                 // into the JLabel is html source.
964                 JMenuItem lbl = ((JMenuItem) e.getSource());
965                 String text = lbl.getText();
966                 BasicHTML.updateRenderer(lbl, text);
967             } else if (name  == "iconTextGap") {
968                 defaultTextIconGap = ((Number)e.getNewValue()).intValue();
969             }
970         }
971     }
972 }