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