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