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