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