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