1 /*
   2  * Copyright (c) 2011, 2012, 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 com.apple.laf;
  27 
  28 import java.awt.*;
  29 import java.awt.event.MouseEvent;
  30 
  31 import javax.swing.*;
  32 import javax.swing.event.*;
  33 import javax.swing.plaf.ComponentUI;
  34 import javax.swing.plaf.basic.BasicMenuUI;
  35 
  36 public class AquaMenuUI extends BasicMenuUI implements AquaMenuPainter.Client {
  37     public static ComponentUI createUI(final JComponent x) {
  38         return new AquaMenuUI();
  39     }
  40 
  41     protected ChangeListener createChangeListener(final JComponent c) {
  42         return new ChangeHandler((JMenu)c, this);
  43     }
  44 
  45     protected void installDefaults() {
  46         super.installDefaults();
  47 
  48         // [3361625]
  49         // In Aqua, the menu delay is 8 ticks, according to Eric Schlegel.
  50         // That makes the millisecond delay 8 ticks * 1 second / 60 ticks * 1000 milliseconds/second
  51         ((JMenu)menuItem).setDelay(8 * 1000 / 60);
  52     }
  53 
  54     protected void paintMenuItem(final Graphics g, final JComponent c, final Icon localCheckIcon, final Icon localArrowIcon, final Color background, final Color foreground, final int localDefaultTextIconGap) {
  55         AquaMenuPainter.instance().paintMenuItem(this, g, c, localCheckIcon, localArrowIcon, background, foreground, disabledForeground, selectionForeground, localDefaultTextIconGap, acceleratorFont);
  56     }
  57 
  58     protected Dimension getPreferredMenuItemSize(final JComponent c, final Icon localCheckIcon, final Icon localArrowIcon, final int localDefaultTextIconGap) {
  59         final Dimension d = AquaMenuPainter.instance().getPreferredMenuItemSize(c, localCheckIcon, localArrowIcon, localDefaultTextIconGap, acceleratorFont);
  60         if (c.getParent() instanceof JMenuBar) d.height = Math.max(d.height, 21);
  61         return d;
  62     }
  63 
  64     public void paintBackground(final Graphics g, final JComponent c, final int menuWidth, final int menuHeight) {
  65         final Container parent = c.getParent();
  66         final boolean parentIsMenuBar = parent instanceof JMenuBar;
  67 
  68         final ButtonModel model = ((JMenuItem)c).getModel();
  69         if (model.isArmed() || model.isSelected()) {
  70             if (parentIsMenuBar) {
  71                 AquaMenuPainter.instance().paintSelectedMenuTitleBackground(g, menuWidth, menuHeight);
  72             } else {
  73                 AquaMenuPainter.instance().paintSelectedMenuItemBackground(g, menuWidth, menuHeight);
  74             }
  75         } else {
  76             if (parentIsMenuBar) {
  77                 AquaMenuPainter.instance().paintMenuBarBackground(g, menuWidth, menuHeight, c);
  78             } else {
  79                 g.setColor(c.getBackground());
  80                 g.fillRect(0, 0, menuWidth, menuHeight);
  81             }
  82         }
  83     }
  84 
  85     protected MouseInputListener createMouseInputListener(final JComponent c) {
  86         return new AquaMouseInputHandler();
  87     }
  88 
  89     protected MenuDragMouseListener createMenuDragMouseListener(final JComponent c) {
  90         //return super.createMenuDragMouseListener(c);
  91         return new MenuDragMouseHandler();
  92     }
  93 
  94     class MenuDragMouseHandler implements MenuDragMouseListener {
  95         public void menuDragMouseDragged(final MenuDragMouseEvent e) {
  96             if (menuItem.isEnabled() == false) return;
  97 
  98             final MenuSelectionManager manager = e.getMenuSelectionManager();
  99             final MenuElement path[] = e.getPath();
 100 
 101             // In Aqua, we always respect the menu's delay, if one is set.
 102             // Doesn't matter how the menu is clicked on or otherwise moused over.
 103             final Point p = e.getPoint();
 104             if (p.x >= 0 && p.x < menuItem.getWidth() && p.y >= 0 && p.y < menuItem.getHeight()) {
 105                 final JMenu menu = (JMenu)menuItem;
 106                 final MenuElement selectedPath[] = manager.getSelectedPath();
 107                 if (!(selectedPath.length > 0 && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) {
 108                     if (menu.getDelay() == 0) {
 109                         appendPath(path, menu.getPopupMenu());
 110                     } else {
 111                         manager.setSelectedPath(path);
 112                         setupPostTimer(menu);
 113                     }
 114                 }
 115             } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
 116                 final Component comp = manager.componentForPoint(e.getComponent(), e.getPoint());
 117                 if (comp == null) manager.clearSelectedPath();
 118             }
 119         }
 120 
 121         public void menuDragMouseEntered(final MenuDragMouseEvent e) { }
 122         public void menuDragMouseExited(final MenuDragMouseEvent e) { }
 123         public void menuDragMouseReleased(final MenuDragMouseEvent e) { }
 124     }
 125 
 126     static void appendPath(final MenuElement[] path, final MenuElement elem) {
 127         final MenuElement newPath[] = new MenuElement[path.length + 1];
 128         System.arraycopy(path, 0, newPath, 0, path.length);
 129         newPath[path.length] = elem;
 130         MenuSelectionManager.defaultManager().setSelectedPath(newPath);
 131     }
 132 
 133     protected class AquaMouseInputHandler extends MouseInputHandler {
 134         /**
 135          * Invoked when the cursor enters the menu. This method sets the selected
 136          * path for the MenuSelectionManager and handles the case
 137          * in which a menu item is used to pop up an additional menu, as in a
 138          * hierarchical menu system.
 139          *
 140          * @param e the mouse event; not used
 141          */
 142         public void mouseEntered(final MouseEvent e) {
 143             final JMenu menu = (JMenu)menuItem;
 144             if (!menu.isEnabled()) return;
 145 
 146             final MenuSelectionManager manager = MenuSelectionManager.defaultManager();
 147             final MenuElement selectedPath[] = manager.getSelectedPath();
 148 
 149             // In Aqua, we always have a menu delay, regardless of where the menu is.
 150             if (!(selectedPath.length > 0 && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) {
 151                 // the condition below prevents from activating menu in other frame
 152                 if (!menu.isTopLevelMenu() || (selectedPath.length > 0 &&
 153                         selectedPath[0] == menu.getParent())) {
 154                     if (menu.getDelay() == 0) {
 155                         appendPath(getPath(), menu.getPopupMenu());
 156                     } else {
 157                         manager.setSelectedPath(getPath());
 158                         setupPostTimer(menu);
 159                     }
 160                 }
 161             }
 162         }
 163     }
 164 }