/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javax.swing.plaf.basic;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import javax.swing.border.*;

import java.applet.Applet;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.*;
import java.awt.AWTEvent;
import java.awt.Toolkit;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import java.util.*;

import sun.swing.DefaultLookup;
import sun.swing.UIAction;

import sun.awt.AppContext;

A Windows L&F implementation of PopupMenuUI. This implementation is a "combined" view/controller.
Author:Georges Saab, David Karlton, Arnaud Weber
/** * A Windows L&F implementation of PopupMenuUI. This implementation * is a "combined" view/controller. * * @author Georges Saab * @author David Karlton * @author Arnaud Weber */
public class BasicPopupMenuUI extends PopupMenuUI { static final StringBuilder MOUSE_GRABBER_KEY = new StringBuilder( "javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber"); static final StringBuilder MENU_KEYBOARD_HELPER_KEY = new StringBuilder( "javax.swing.plaf.basic.BasicPopupMenuUI.MenuKeyboardHelper"); protected JPopupMenu popupMenu = null; private transient PopupMenuListener popupMenuListener = null; private MenuKeyListener menuKeyListener = null; private static boolean checkedUnpostPopup; private static boolean unpostPopup; public static ComponentUI createUI(JComponent x) { return new BasicPopupMenuUI(); } public BasicPopupMenuUI() { BasicLookAndFeel.needsEventHelper = true; LookAndFeel laf = UIManager.getLookAndFeel(); if (laf instanceof BasicLookAndFeel) { ((BasicLookAndFeel)laf).installAWTEventListener(); } } public void installUI(JComponent c) { popupMenu = (JPopupMenu) c; installDefaults(); installListeners(); installKeyboardActions(); } public void installDefaults() { if (popupMenu.getLayout() == null || popupMenu.getLayout() instanceof UIResource) popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS)); LookAndFeel.installProperty(popupMenu, "opaque", Boolean.TRUE); LookAndFeel.installBorder(popupMenu, "PopupMenu.border"); LookAndFeel.installColorsAndFont(popupMenu, "PopupMenu.background", "PopupMenu.foreground", "PopupMenu.font"); } protected void installListeners() { if (popupMenuListener == null) { popupMenuListener = new BasicPopupMenuListener(); } popupMenu.addPopupMenuListener(popupMenuListener); if (menuKeyListener == null) { menuKeyListener = new BasicMenuKeyListener(); } popupMenu.addMenuKeyListener(menuKeyListener); AppContext context = AppContext.getAppContext(); synchronized (MOUSE_GRABBER_KEY) { MouseGrabber mouseGrabber = (MouseGrabber)context.get( MOUSE_GRABBER_KEY); if (mouseGrabber == null) { mouseGrabber = new MouseGrabber(); context.put(MOUSE_GRABBER_KEY, mouseGrabber); } } synchronized (MENU_KEYBOARD_HELPER_KEY) { MenuKeyboardHelper helper = (MenuKeyboardHelper)context.get(MENU_KEYBOARD_HELPER_KEY); if (helper == null) { helper = new MenuKeyboardHelper(); context.put(MENU_KEYBOARD_HELPER_KEY, helper); MenuSelectionManager msm = MenuSelectionManager.defaultManager(); msm.addChangeListener(helper); } } } protected void installKeyboardActions() { } static InputMap getInputMap(JPopupMenu popup, JComponent c) { InputMap windowInputMap = null; Object[] bindings = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings"); if (bindings != null) { windowInputMap = LookAndFeel.makeComponentInputMap(c, bindings); if (!popup.getComponentOrientation().isLeftToRight()) { Object[] km = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings.RightToLeft"); if (km != null) { InputMap rightToLeftInputMap = LookAndFeel.makeComponentInputMap(c, km); rightToLeftInputMap.setParent(windowInputMap); windowInputMap = rightToLeftInputMap; } } } return windowInputMap; } static ActionMap getActionMap() { return LazyActionMap.getActionMap(BasicPopupMenuUI.class, "PopupMenu.actionMap"); } static void loadActionMap(LazyActionMap map) { map.put(new Actions(Actions.CANCEL)); map.put(new Actions(Actions.SELECT_NEXT)); map.put(new Actions(Actions.SELECT_PREVIOUS)); map.put(new Actions(Actions.SELECT_PARENT)); map.put(new Actions(Actions.SELECT_CHILD)); map.put(new Actions(Actions.RETURN)); BasicLookAndFeel.installAudioActionMap(map); } public void uninstallUI(JComponent c) { uninstallDefaults(); uninstallListeners(); uninstallKeyboardActions(); popupMenu = null; } protected void uninstallDefaults() { LookAndFeel.uninstallBorder(popupMenu); } protected void uninstallListeners() { if (popupMenuListener != null) { popupMenu.removePopupMenuListener(popupMenuListener); } if (menuKeyListener != null) { popupMenu.removeMenuKeyListener(menuKeyListener); } } protected void uninstallKeyboardActions() { SwingUtilities.replaceUIActionMap(popupMenu, null); SwingUtilities.replaceUIInputMap(popupMenu, JComponent.WHEN_IN_FOCUSED_WINDOW, null); } static MenuElement getFirstPopup() { MenuSelectionManager msm = MenuSelectionManager.defaultManager(); MenuElement[] p = msm.getSelectedPath(); MenuElement me = null; for(int i = 0 ; me == null && i < p.length ; i++) { if (p[i] instanceof JPopupMenu) me = p[i]; } return me; } static JPopupMenu getLastPopup() { MenuSelectionManager msm = MenuSelectionManager.defaultManager(); MenuElement[] p = msm.getSelectedPath(); JPopupMenu popup = null; for(int i = p.length - 1; popup == null && i >= 0; i--) { if (p[i] instanceof JPopupMenu) popup = (JPopupMenu)p[i]; } return popup; } static List<JPopupMenu> getPopups() { MenuSelectionManager msm = MenuSelectionManager.defaultManager(); MenuElement[] p = msm.getSelectedPath(); List<JPopupMenu> list = new ArrayList<JPopupMenu>(p.length); for (MenuElement element : p) { if (element instanceof JPopupMenu) { list.add((JPopupMenu) element); } } return list; } public boolean isPopupTrigger(MouseEvent e) { return ((e.getID()==MouseEvent.MOUSE_RELEASED) && ((e.getModifiers() & MouseEvent.BUTTON3_MASK)!=0)); } private static boolean checkInvokerEqual(MenuElement present, MenuElement last) { Component invokerPresent = present.getComponent(); Component invokerLast = last.getComponent(); if (invokerPresent instanceof JPopupMenu) { invokerPresent = ((JPopupMenu)invokerPresent).getInvoker(); } if (invokerLast instanceof JPopupMenu) { invokerLast = ((JPopupMenu)invokerLast).getInvoker(); } return (invokerPresent == invokerLast); }
This Listener fires the Action that provides the correct auditory feedback.
Since:1.4
/** * This Listener fires the Action that provides the correct auditory * feedback. * * @since 1.4 */
private class BasicPopupMenuListener implements PopupMenuListener { public void popupMenuCanceled(PopupMenuEvent e) { } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } public void popupMenuWillBecomeVisible(PopupMenuEvent e) { BasicLookAndFeel.playSound((JPopupMenu)e.getSource(), "PopupMenu.popupSound"); } }
Handles mnemonic for children JMenuItems.
Since:1.5
/** * Handles mnemonic for children JMenuItems. * @since 1.5 */
private class BasicMenuKeyListener implements MenuKeyListener { MenuElement menuToOpen = null; public void menuKeyTyped(MenuKeyEvent e) { if (menuToOpen != null) { // we have a submenu to open JPopupMenu subpopup = ((JMenu)menuToOpen).getPopupMenu(); MenuElement subitem = findEnabledChild( subpopup.getSubElements(), -1, true); ArrayList<MenuElement> lst = new ArrayList<MenuElement>(Arrays.asList(e.getPath())); lst.add(menuToOpen); lst.add(subpopup); if (subitem != null) { lst.add(subitem); } MenuElement newPath[] = new MenuElement[0]; newPath = lst.toArray(newPath); MenuSelectionManager.defaultManager().setSelectedPath(newPath); e.consume(); } menuToOpen = null; } public void menuKeyPressed(MenuKeyEvent e) { char keyChar = e.getKeyChar(); // Handle the case for Escape or Enter... if (!Character.isLetterOrDigit(keyChar)) { return; } MenuSelectionManager manager = e.getMenuSelectionManager(); MenuElement path[] = e.getPath(); MenuElement items[] = popupMenu.getSubElements(); int currentIndex = -1; int matches = 0; int firstMatch = -1; int indexes[] = null; for (int j = 0; j < items.length; j++) { if (! (items[j] instanceof JMenuItem)) { continue; } JMenuItem item = (JMenuItem)items[j]; int mnemonic = item.getMnemonic(); if (item.isEnabled() && item.isVisible() && lower(keyChar) == lower(mnemonic)) { if (matches == 0) { firstMatch = j; matches++; } else { if (indexes == null) { indexes = new int[items.length]; indexes[0] = firstMatch; } indexes[matches++] = j; } } if (item.isArmed() || item.isSelected()) { currentIndex = matches - 1; } } if (matches == 0) { // no op } else if (matches == 1) { // Invoke the menu action JMenuItem item = (JMenuItem)items[firstMatch]; if (item instanceof JMenu) { // submenus are handled in menuKeyTyped menuToOpen = item; } else if (item.isEnabled()) { // we have a menu item manager.clearSelectedPath(); item.doClick(); } e.consume(); } else { // Select the menu item with the matching mnemonic. If // the same mnemonic has been invoked then select the next // menu item in the cycle. MenuElement newItem; newItem = items[indexes[(currentIndex + 1) % matches]]; MenuElement newPath[] = new MenuElement[path.length+1]; System.arraycopy(path, 0, newPath, 0, path.length); newPath[path.length] = newItem; manager.setSelectedPath(newPath); e.consume(); } } public void menuKeyReleased(MenuKeyEvent e) { } private char lower(char keyChar) { return Character.toLowerCase(keyChar); } private char lower(int mnemonic) { return Character.toLowerCase((char) mnemonic); } } private static class Actions extends UIAction { // Types of actions private static final String CANCEL = "cancel"; private static final String SELECT_NEXT = "selectNext"; private static final String SELECT_PREVIOUS = "selectPrevious"; private static final String SELECT_PARENT = "selectParent"; private static final String SELECT_CHILD = "selectChild"; private static final String RETURN = "return"; // Used for next/previous actions private static final boolean FORWARD = true; private static final boolean BACKWARD = false; // Used for parent/child actions private static final boolean PARENT = false; private static final boolean CHILD = true; Actions(String key) { super(key); } public void actionPerformed(ActionEvent e) { String key = getName(); if (key == CANCEL) { cancel(); } else if (key == SELECT_NEXT) { selectItem(FORWARD); } else if (key == SELECT_PREVIOUS) { selectItem(BACKWARD); } else if (key == SELECT_PARENT) { selectParentChild(PARENT); } else if (key == SELECT_CHILD) { selectParentChild(CHILD); } else if (key == RETURN) { doReturn(); } } private void doReturn() { KeyboardFocusManager fmgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); Component focusOwner = fmgr.getFocusOwner(); if(focusOwner != null && !(focusOwner instanceof JRootPane)) { return; } MenuSelectionManager msm = MenuSelectionManager.defaultManager(); MenuElement path[] = msm.getSelectedPath(); MenuElement lastElement; if(path.length > 0) { lastElement = path[path.length-1]; if(lastElement instanceof JMenu) { MenuElement newPath[] = new MenuElement[path.length+1]; System.arraycopy(path,0,newPath,0,path.length); newPath[path.length] = ((JMenu)lastElement).getPopupMenu(); msm.setSelectedPath(newPath); } else if(lastElement instanceof JMenuItem) { JMenuItem mi = (JMenuItem)lastElement; if (mi.getUI() instanceof BasicMenuItemUI) { ((BasicMenuItemUI)mi.getUI()).doClick(msm); } else { msm.clearSelectedPath(); mi.doClick(0); } } } } private void selectParentChild(boolean direction) { MenuSelectionManager msm = MenuSelectionManager.defaultManager(); MenuElement path[] = msm.getSelectedPath(); int len = path.length; if (direction == PARENT) { // selecting parent int popupIndex = len-1; if (len > 2 && // check if we have an open submenu. A submenu item may or // may not be selected, so submenu popup can be either the // last or next to the last item. (path[popupIndex] instanceof JPopupMenu || path[--popupIndex] instanceof JPopupMenu) && !((JMenu)path[popupIndex-1]).isTopLevelMenu()) { // we have a submenu, just close it MenuElement newPath[] = new MenuElement[popupIndex]; System.arraycopy(path, 0, newPath, 0, popupIndex); msm.setSelectedPath(newPath); return; } } else { // selecting child if (len > 0 && path[len-1] instanceof JMenu && !((JMenu)path[len-1]).isTopLevelMenu()) { // we have a submenu, open it JMenu menu = (JMenu)path[len-1]; JPopupMenu popup = menu.getPopupMenu(); MenuElement[] subs = popup.getSubElements(); MenuElement item = findEnabledChild(subs, -1, true); MenuElement[] newPath; if (item == null) { newPath = new MenuElement[len+1]; } else { newPath = new MenuElement[len+2]; newPath[len+1] = item; } System.arraycopy(path, 0, newPath, 0, len); newPath[len] = popup; msm.setSelectedPath(newPath); return; } } // check if we have a toplevel menu selected. // If this is the case, we select another toplevel menu if (len > 1 && path[0] instanceof JMenuBar) { MenuElement currentMenu = path[1]; MenuElement nextMenu = findEnabledChild( path[0].getSubElements(), currentMenu, direction); if (nextMenu != null && nextMenu != currentMenu) { MenuElement newSelection[]; if (len == 2) { // menu is selected but its popup not shown newSelection = new MenuElement[2]; newSelection[0] = path[0]; newSelection[1] = nextMenu; } else { // menu is selected and its popup is shown newSelection = new MenuElement[3]; newSelection[0] = path[0]; newSelection[1] = nextMenu; newSelection[2] = ((JMenu)nextMenu).getPopupMenu(); } msm.setSelectedPath(newSelection); } } } private void selectItem(boolean direction) { MenuSelectionManager msm = MenuSelectionManager.defaultManager(); MenuElement path[] = msm.getSelectedPath(); if (path.length == 0) { return; } int len = path.length; if (len == 1 && path[0] instanceof JPopupMenu) { JPopupMenu popup = (JPopupMenu) path[0]; MenuElement[] newPath = new MenuElement[2]; newPath[0] = popup; newPath[1] = findEnabledChild(popup.getSubElements(), -1, direction); msm.setSelectedPath(newPath); } else if (len == 2 && path[0] instanceof JMenuBar && path[1] instanceof JMenu) { // a toplevel menu is selected, but its popup not shown. // Show the popup and select the first item JPopupMenu popup = ((JMenu)path[1]).getPopupMenu(); MenuElement next = findEnabledChild(popup.getSubElements(), -1, FORWARD); MenuElement[] newPath; if (next != null) { // an enabled item found -- include it in newPath newPath = new MenuElement[4]; newPath[3] = next; } else { // menu has no enabled items -- still must show the popup newPath = new MenuElement[3]; } System.arraycopy(path, 0, newPath, 0, 2); newPath[2] = popup; msm.setSelectedPath(newPath); } else if (path[len-1] instanceof JPopupMenu && path[len-2] instanceof JMenu) { // a menu (not necessarily toplevel) is open and its popup // shown. Select the appropriate menu item JMenu menu = (JMenu)path[len-2]; JPopupMenu popup = menu.getPopupMenu(); MenuElement next = findEnabledChild(popup.getSubElements(), -1, direction); if (next != null) { MenuElement[] newPath = new MenuElement[len+1]; System.arraycopy(path, 0, newPath, 0, len); newPath[len] = next; msm.setSelectedPath(newPath); } else { // all items in the popup are disabled. // We're going to find the parent popup menu and select // its next item. If there's no parent popup menu (i.e. // current menu is toplevel), do nothing if (len > 2 && path[len-3] instanceof JPopupMenu) { popup = ((JPopupMenu)path[len-3]); next = findEnabledChild(popup.getSubElements(), menu, direction); if (next != null && next != menu) { MenuElement[] newPath = new MenuElement[len-1]; System.arraycopy(path, 0, newPath, 0, len-2); newPath[len-2] = next; msm.setSelectedPath(newPath); } } } } else { // just select the next item, no path expansion needed MenuElement subs[] = path[len-2].getSubElements(); MenuElement nextChild = findEnabledChild(subs, path[len-1], direction); if (nextChild == null) { nextChild = findEnabledChild(subs, -1, direction); } if (nextChild != null) { path[len-1] = nextChild; msm.setSelectedPath(path); } } } private void cancel() { // 4234793: This action should call JPopupMenu.firePopupMenuCanceled but it's // a protected method. The real solution could be to make // firePopupMenuCanceled public and call it directly. JPopupMenu lastPopup = getLastPopup(); if (lastPopup != null) { lastPopup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE); } String mode = UIManager.getString("Menu.cancelMode"); if ("hideMenuTree".equals(mode)) { MenuSelectionManager.defaultManager().clearSelectedPath(); } else { shortenSelectedPath(); } } private void shortenSelectedPath() { MenuElement path[] = MenuSelectionManager.defaultManager().getSelectedPath(); if (path.length <= 2) { MenuSelectionManager.defaultManager().clearSelectedPath(); return; } // unselect MenuItem and its Popup by default int value = 2; MenuElement lastElement = path[path.length - 1]; JPopupMenu lastPopup = getLastPopup(); if (lastElement == lastPopup) { MenuElement previousElement = path[path.length - 2]; if (previousElement instanceof JMenu) { JMenu lastMenu = (JMenu) previousElement; if (lastMenu.isEnabled() && lastPopup.getComponentCount() > 0) { // unselect the last visible popup only value = 1; } else { // unselect invisible popup and two visible elements value = 3; } } } if (path.length - value <= 2 && !UIManager.getBoolean("Menu.preserveTopLevelSelection")) { // clear selection for the topLevelMenu value = path.length; } MenuElement newPath[] = new MenuElement[path.length - value]; System.arraycopy(path, 0, newPath, 0, path.length - value); MenuSelectionManager.defaultManager().setSelectedPath(newPath); } } private static MenuElement nextEnabledChild(MenuElement e[], int fromIndex, int toIndex) { for (int i=fromIndex; i<=toIndex; i++) { if (e[i] != null) { Component comp = e[i].getComponent(); if ( comp != null && (comp.isEnabled() || UIManager.getBoolean("MenuItem.disabledAreNavigable")) && comp.isVisible()) { return e[i]; } } } return null; } private static MenuElement previousEnabledChild(MenuElement e[], int fromIndex, int toIndex) { for (int i=fromIndex; i>=toIndex; i--) { if (e[i] != null) { Component comp = e[i].getComponent(); if ( comp != null && (comp.isEnabled() || UIManager.getBoolean("MenuItem.disabledAreNavigable")) && comp.isVisible()) { return e[i]; } } } return null; } static MenuElement findEnabledChild(MenuElement e[], int fromIndex, boolean forward) { MenuElement result; if (forward) { result = nextEnabledChild(e, fromIndex+1, e.length-1); if (result == null) result = nextEnabledChild(e, 0, fromIndex-1); } else { result = previousEnabledChild(e, fromIndex-1, 0); if (result == null) result = previousEnabledChild(e, e.length-1, fromIndex+1); } return result; } static MenuElement findEnabledChild(MenuElement e[], MenuElement elem, boolean forward) { for (int i=0; i<e.length; i++) { if (e[i] == elem) { return findEnabledChild(e, i, forward); } } return null; } static class MouseGrabber implements ChangeListener, AWTEventListener, ComponentListener, WindowListener { Window grabbedWindow; MenuElement[] lastPathSelected; public MouseGrabber() { MenuSelectionManager msm = MenuSelectionManager.defaultManager(); msm.addChangeListener(this); this.lastPathSelected = msm.getSelectedPath(); if(this.lastPathSelected.length != 0) { grabWindow(this.lastPathSelected); } } void uninstall() { synchronized (MOUSE_GRABBER_KEY) { MenuSelectionManager.defaultManager().removeChangeListener(this); ungrabWindow(); AppContext.getAppContext().remove(MOUSE_GRABBER_KEY); } } void grabWindow(MenuElement[] newPath) { // A grab needs to be added final Toolkit tk = Toolkit.getDefaultToolkit(); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Object>() { public Object run() { tk.addAWTEventListener(MouseGrabber.this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK | AWTEvent.WINDOW_EVENT_MASK | sun.awt.SunToolkit.GRAB_EVENT_MASK); return null; } } ); Component invoker = newPath[0].getComponent(); if (invoker instanceof JPopupMenu) { invoker = ((JPopupMenu)invoker).getInvoker(); } grabbedWindow = invoker instanceof Window? (Window)invoker : SwingUtilities.getWindowAncestor(invoker); if(grabbedWindow != null) { if(tk instanceof sun.awt.SunToolkit) { ((sun.awt.SunToolkit)tk).grab(grabbedWindow); } else { grabbedWindow.addComponentListener(this); grabbedWindow.addWindowListener(this); } } } void ungrabWindow() { final Toolkit tk = Toolkit.getDefaultToolkit(); // The grab should be removed java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Object>() { public Object run() { tk.removeAWTEventListener(MouseGrabber.this); return null; } } ); realUngrabWindow(); } void realUngrabWindow() { Toolkit tk = Toolkit.getDefaultToolkit(); if(grabbedWindow != null) { if(tk instanceof sun.awt.SunToolkit) { ((sun.awt.SunToolkit)tk).ungrab(grabbedWindow); } else { grabbedWindow.removeComponentListener(this); grabbedWindow.removeWindowListener(this); } grabbedWindow = null; } } public void stateChanged(ChangeEvent e) { MenuSelectionManager msm = MenuSelectionManager.defaultManager(); MenuElement[] p = msm.getSelectedPath(); if (lastPathSelected.length == 0 && p.length != 0) { grabWindow(p); } if (lastPathSelected.length != 0 && p.length == 0) { ungrabWindow(); } lastPathSelected = p; } public void eventDispatched(AWTEvent ev) { if(ev instanceof sun.awt.UngrabEvent) { // Popup should be canceled in case of ungrab event cancelPopupMenu( ); return; } if (!(ev instanceof MouseEvent)) { // We are interested in MouseEvents only return; } MouseEvent me = (MouseEvent) ev; Component src = me.getComponent(); switch (me.getID()) { case MouseEvent.MOUSE_PRESSED: if (isInPopup(src) || (src instanceof JMenu && ((JMenu)src).isSelected())) { return; } if (!(src instanceof JComponent) || ! (((JComponent)src).getClientProperty("doNotCancelPopup") == BasicComboBoxUI.HIDE_POPUP_KEY)) { // Cancel popup only if this property was not set. // If this property is set to TRUE component wants // to deal with this event by himself. cancelPopupMenu(); // Ask UIManager about should we consume event that closes // popup. This made to match native apps behaviour. boolean consumeEvent = UIManager.getBoolean("PopupMenu.consumeEventOnClose"); // Consume the event so that normal processing stops. if(consumeEvent && !(src instanceof MenuElement)) { me.consume(); } } break; case MouseEvent.MOUSE_RELEASED: if(!(src instanceof MenuElement)) { // Do not forward event to MSM, let component handle it if (isInPopup(src)) { break; } } if(src instanceof JMenu || !(src instanceof JMenuItem)) { MenuSelectionManager.defaultManager(). processMouseEvent(me); } break; case MouseEvent.MOUSE_DRAGGED: if(!(src instanceof MenuElement)) { // For the MOUSE_DRAGGED event the src is // the Component in which mouse button was pressed. // If the src is in popupMenu, // do not forward event to MSM, let component handle it. if (isInPopup(src)) { break; } } MenuSelectionManager.defaultManager(). processMouseEvent(me); break; case MouseEvent.MOUSE_WHEEL: if (isInPopup(src) || ((src instanceof JComboBox) && ((JComboBox) src).isPopupVisible())) { return; } cancelPopupMenu(); break; } } boolean isInPopup(Component src) { for (Component c=src; c!=null; c=c.getParent()) { if (c instanceof Applet || c instanceof Window) { break; } else if (c instanceof JPopupMenu) { return true; } } return false; } void cancelPopupMenu() { // We should ungrab window if a user code throws // an unexpected runtime exception. See 6495920. try { // 4234793: This action should call firePopupMenuCanceled but it's // a protected method. The real solution could be to make // firePopupMenuCanceled public and call it directly. List<JPopupMenu> popups = getPopups(); for (JPopupMenu popup : popups) { popup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE); } MenuSelectionManager.defaultManager().clearSelectedPath(); } catch (RuntimeException ex) { realUngrabWindow(); throw ex; } catch (Error err) { realUngrabWindow(); throw err; } } public void componentResized(ComponentEvent e) { cancelPopupMenu(); } public void componentMoved(ComponentEvent e) { cancelPopupMenu(); } public void componentShown(ComponentEvent e) { cancelPopupMenu(); } public void componentHidden(ComponentEvent e) { cancelPopupMenu(); } public void windowClosing(WindowEvent e) { cancelPopupMenu(); } public void windowClosed(WindowEvent e) { cancelPopupMenu(); } public void windowIconified(WindowEvent e) { cancelPopupMenu(); } public void windowDeactivated(WindowEvent e) { cancelPopupMenu(); } public void windowOpened(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} }
This helper is added to MenuSelectionManager as a ChangeListener to listen to menu selection changes. When a menu is activated, it passes focus to its parent JRootPane, and installs an ActionMap/InputMap pair on that JRootPane. Those maps are necessary in order for menu navigation to work. When menu is being deactivated, it restores focus to the component that has had it before menu activation, and uninstalls the maps. This helper is also installed as a KeyListener on root pane when menu is active. It forwards key events to MenuSelectionManager for mnemonic keys handling.
/** * This helper is added to MenuSelectionManager as a ChangeListener to * listen to menu selection changes. When a menu is activated, it passes * focus to its parent JRootPane, and installs an ActionMap/InputMap pair * on that JRootPane. Those maps are necessary in order for menu * navigation to work. When menu is being deactivated, it restores focus * to the component that has had it before menu activation, and uninstalls * the maps. * This helper is also installed as a KeyListener on root pane when menu * is active. It forwards key events to MenuSelectionManager for mnemonic * keys handling. */
static class MenuKeyboardHelper implements ChangeListener, KeyListener { private Component lastFocused = null; private MenuElement[] lastPathSelected = new MenuElement[0]; private JPopupMenu lastPopup; private JRootPane invokerRootPane; private ActionMap menuActionMap = getActionMap(); private InputMap menuInputMap; private boolean focusTraversalKeysEnabled; /* * Fix for 4213634 * If this is false, KEY_TYPED and KEY_RELEASED events are NOT * processed. This is needed to avoid activating a menuitem when * the menu and menuitem share the same mnemonic. */ private boolean receivedKeyPressed = false; void removeItems() { if (lastFocused != null) { if(!lastFocused.requestFocusInWindow()) { // Workarounr for 4810575. // If lastFocused is not in currently focused window // requestFocusInWindow will fail. In this case we must // request focus by requestFocus() if it was not // transferred from our popup. Window cfw = KeyboardFocusManager .getCurrentKeyboardFocusManager() .getFocusedWindow(); if(cfw != null && "###focusableSwingPopup###".equals(cfw.getName())) { lastFocused.requestFocus(); } } lastFocused = null; } if (invokerRootPane != null) { invokerRootPane.removeKeyListener(this); invokerRootPane.setFocusTraversalKeysEnabled(focusTraversalKeysEnabled); removeUIInputMap(invokerRootPane, menuInputMap); removeUIActionMap(invokerRootPane, menuActionMap); invokerRootPane = null; } receivedKeyPressed = false; } private FocusListener rootPaneFocusListener = new FocusAdapter() { public void focusGained(FocusEvent ev) { Component opposite = ev.getOppositeComponent(); if (opposite != null) { lastFocused = opposite; } ev.getComponent().removeFocusListener(this); } };
Return the last JPopupMenu in path, or null if none found
/** * Return the last JPopupMenu in <code>path</code>, * or <code>null</code> if none found */
JPopupMenu getActivePopup(MenuElement[] path) { for (int i=path.length-1; i>=0; i--) { MenuElement elem = path[i]; if (elem instanceof JPopupMenu) { return (JPopupMenu)elem; } } return null; } void addUIInputMap(JComponent c, InputMap map) { InputMap lastNonUI = null; InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); while (parent != null && !(parent instanceof UIResource)) { lastNonUI = parent; parent = parent.getParent(); } if (lastNonUI == null) { c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map); } else { lastNonUI.setParent(map); } map.setParent(parent); } void addUIActionMap(JComponent c, ActionMap map) { ActionMap lastNonUI = null; ActionMap parent = c.getActionMap(); while (parent != null && !(parent instanceof UIResource)) { lastNonUI = parent; parent = parent.getParent(); } if (lastNonUI == null) { c.setActionMap(map); } else { lastNonUI.setParent(map); } map.setParent(parent); } void removeUIInputMap(JComponent c, InputMap map) { InputMap im = null; InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); while (parent != null) { if (parent == map) { if (im == null) { c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map.getParent()); } else { im.setParent(map.getParent()); } break; } im = parent; parent = parent.getParent(); } } void removeUIActionMap(JComponent c, ActionMap map) { ActionMap im = null; ActionMap parent = c.getActionMap(); while (parent != null) { if (parent == map) { if (im == null) { c.setActionMap(map.getParent()); } else { im.setParent(map.getParent()); } break; } im = parent; parent = parent.getParent(); } } public void stateChanged(ChangeEvent ev) { if (!(UIManager.getLookAndFeel() instanceof BasicLookAndFeel)) { uninstall(); return; } MenuSelectionManager msm = (MenuSelectionManager)ev.getSource(); MenuElement[] p = msm.getSelectedPath(); JPopupMenu popup = getActivePopup(p); if (popup != null && !popup.isFocusable()) { // Do nothing for non-focusable popups return; } if (lastPathSelected.length != 0 && p.length != 0 ) { if (!checkInvokerEqual(p[0],lastPathSelected[0])) { removeItems(); lastPathSelected = new MenuElement[0]; } } if (lastPathSelected.length == 0 && p.length > 0) { // menu posted JComponent invoker; if (popup == null) { if (p.length == 2 && p[0] instanceof JMenuBar && p[1] instanceof JMenu) { // a menu has been selected but not open invoker = (JComponent)p[1]; popup = ((JMenu)invoker).getPopupMenu(); } else { return; } } else { Component c = popup.getInvoker(); if(c instanceof JFrame) { invoker = ((JFrame)c).getRootPane(); } else if(c instanceof JDialog) { invoker = ((JDialog)c).getRootPane(); } else if(c instanceof JApplet) { invoker = ((JApplet)c).getRootPane(); } else { while (!(c instanceof JComponent)) { if (c == null) { return; } c = c.getParent(); } invoker = (JComponent)c; } } // remember current focus owner lastFocused = KeyboardFocusManager. getCurrentKeyboardFocusManager().getFocusOwner(); // request focus on root pane and install keybindings // used for menu navigation invokerRootPane = SwingUtilities.getRootPane(invoker); if (invokerRootPane != null) { invokerRootPane.addFocusListener(rootPaneFocusListener); invokerRootPane.requestFocus(true); invokerRootPane.addKeyListener(this); focusTraversalKeysEnabled = invokerRootPane. getFocusTraversalKeysEnabled(); invokerRootPane.setFocusTraversalKeysEnabled(false); menuInputMap = getInputMap(popup, invokerRootPane); addUIInputMap(invokerRootPane, menuInputMap); addUIActionMap(invokerRootPane, menuActionMap); } } else if (lastPathSelected.length != 0 && p.length == 0) { // menu hidden -- return focus to where it had been before // and uninstall menu keybindings removeItems(); } else { if (popup != lastPopup) { receivedKeyPressed = false; } } // Remember the last path selected lastPathSelected = p; lastPopup = popup; } public void keyPressed(KeyEvent ev) { receivedKeyPressed = true; MenuSelectionManager.defaultManager().processKeyEvent(ev); } public void keyReleased(KeyEvent ev) { if (receivedKeyPressed) { receivedKeyPressed = false; MenuSelectionManager.defaultManager().processKeyEvent(ev); } } public void keyTyped(KeyEvent ev) { if (receivedKeyPressed) { MenuSelectionManager.defaultManager().processKeyEvent(ev); } } void uninstall() { synchronized (MENU_KEYBOARD_HELPER_KEY) { MenuSelectionManager.defaultManager().removeChangeListener(this); AppContext.getAppContext().remove(MENU_KEYBOARD_HELPER_KEY); } } } }