0001 /*
0002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package javax.swing.plaf.basic;
0027
0028 import javax.swing.*;
0029 import javax.swing.event.*;
0030 import javax.swing.plaf.*;
0031 import javax.swing.plaf.basic.*;
0032 import javax.swing.border.*;
0033
0034 import java.applet.Applet;
0035
0036 import java.awt.Component;
0037 import java.awt.Container;
0038 import java.awt.Dimension;
0039 import java.awt.KeyboardFocusManager;
0040 import java.awt.Window;
0041 import java.awt.event.*;
0042 import java.awt.AWTEvent;
0043 import java.awt.Toolkit;
0044
0045 import java.beans.PropertyChangeListener;
0046 import java.beans.PropertyChangeEvent;
0047
0048 import java.util.*;
0049
0050 import sun.swing.DefaultLookup;
0051 import sun.swing.UIAction;
0052
0053 import sun.awt.AppContext;
0054
0055 /**
0056 * A Windows L&F implementation of PopupMenuUI. This implementation
0057 * is a "combined" view/controller.
0058 *
0059 * @version 1.144 06/14/07
0060 * @author Georges Saab
0061 * @author David Karlton
0062 * @author Arnaud Weber
0063 */
0064 public class BasicPopupMenuUI extends PopupMenuUI {
0065 static final StringBuilder MOUSE_GRABBER_KEY = new StringBuilder(
0066 "javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber");
0067 static final StringBuilder MENU_KEYBOARD_HELPER_KEY = new StringBuilder(
0068 "javax.swing.plaf.basic.BasicPopupMenuUI.MenuKeyboardHelper");
0069
0070 protected JPopupMenu popupMenu = null;
0071 private transient PopupMenuListener popupMenuListener = null;
0072 private MenuKeyListener menuKeyListener = null;
0073
0074 private static boolean checkedUnpostPopup;
0075 private static boolean unpostPopup;
0076
0077 public static ComponentUI createUI(JComponent x) {
0078 return new BasicPopupMenuUI();
0079 }
0080
0081 public BasicPopupMenuUI() {
0082 BasicLookAndFeel.needsEventHelper = true;
0083 LookAndFeel laf = UIManager.getLookAndFeel();
0084 if (laf instanceof BasicLookAndFeel) {
0085 ((BasicLookAndFeel) laf).installAWTEventListener();
0086 }
0087 }
0088
0089 public void installUI(JComponent c) {
0090 popupMenu = (JPopupMenu) c;
0091
0092 installDefaults();
0093 installListeners();
0094 installKeyboardActions();
0095 }
0096
0097 public void installDefaults() {
0098 if (popupMenu.getLayout() == null
0099 || popupMenu.getLayout() instanceof UIResource)
0100 popupMenu.setLayout(new DefaultMenuLayout(popupMenu,
0101 BoxLayout.Y_AXIS));
0102
0103 LookAndFeel.installProperty(popupMenu, "opaque", Boolean.TRUE);
0104 LookAndFeel.installBorder(popupMenu, "PopupMenu.border");
0105 LookAndFeel.installColorsAndFont(popupMenu,
0106 "PopupMenu.background", "PopupMenu.foreground",
0107 "PopupMenu.font");
0108 }
0109
0110 protected void installListeners() {
0111 if (popupMenuListener == null) {
0112 popupMenuListener = new BasicPopupMenuListener();
0113 }
0114 popupMenu.addPopupMenuListener(popupMenuListener);
0115
0116 if (menuKeyListener == null) {
0117 menuKeyListener = new BasicMenuKeyListener();
0118 }
0119 popupMenu.addMenuKeyListener(menuKeyListener);
0120
0121 AppContext context = AppContext.getAppContext();
0122 synchronized (MOUSE_GRABBER_KEY) {
0123 MouseGrabber mouseGrabber = (MouseGrabber) context
0124 .get(MOUSE_GRABBER_KEY);
0125 if (mouseGrabber == null) {
0126 mouseGrabber = new MouseGrabber();
0127 context.put(MOUSE_GRABBER_KEY, mouseGrabber);
0128 }
0129 }
0130 synchronized (MENU_KEYBOARD_HELPER_KEY) {
0131 MenuKeyboardHelper helper = (MenuKeyboardHelper) context
0132 .get(MENU_KEYBOARD_HELPER_KEY);
0133 if (helper == null) {
0134 helper = new MenuKeyboardHelper();
0135 context.put(MENU_KEYBOARD_HELPER_KEY, helper);
0136 MenuSelectionManager msm = MenuSelectionManager
0137 .defaultManager();
0138 msm.addChangeListener(helper);
0139 }
0140 }
0141 }
0142
0143 protected void installKeyboardActions() {
0144 }
0145
0146 static InputMap getInputMap(JPopupMenu popup, JComponent c) {
0147 InputMap windowInputMap = null;
0148 Object[] bindings = (Object[]) UIManager
0149 .get("PopupMenu.selectedWindowInputMapBindings");
0150 if (bindings != null) {
0151 windowInputMap = LookAndFeel.makeComponentInputMap(c,
0152 bindings);
0153 if (!popup.getComponentOrientation().isLeftToRight()) {
0154 Object[] km = (Object[]) UIManager
0155 .get("PopupMenu.selectedWindowInputMapBindings.RightToLeft");
0156 if (km != null) {
0157 InputMap rightToLeftInputMap = LookAndFeel
0158 .makeComponentInputMap(c, km);
0159 rightToLeftInputMap.setParent(windowInputMap);
0160 windowInputMap = rightToLeftInputMap;
0161 }
0162 }
0163 }
0164 return windowInputMap;
0165 }
0166
0167 static ActionMap getActionMap() {
0168 return LazyActionMap.getActionMap(BasicPopupMenuUI.class,
0169 "PopupMenu.actionMap");
0170 }
0171
0172 static void loadActionMap(LazyActionMap map) {
0173 map.put(new Actions(Actions.CANCEL));
0174 map.put(new Actions(Actions.SELECT_NEXT));
0175 map.put(new Actions(Actions.SELECT_PREVIOUS));
0176 map.put(new Actions(Actions.SELECT_PARENT));
0177 map.put(new Actions(Actions.SELECT_CHILD));
0178 map.put(new Actions(Actions.RETURN));
0179 BasicLookAndFeel.installAudioActionMap(map);
0180 }
0181
0182 public void uninstallUI(JComponent c) {
0183 uninstallDefaults();
0184 uninstallListeners();
0185 uninstallKeyboardActions();
0186
0187 popupMenu = null;
0188 }
0189
0190 protected void uninstallDefaults() {
0191 LookAndFeel.uninstallBorder(popupMenu);
0192 }
0193
0194 protected void uninstallListeners() {
0195 if (popupMenuListener != null) {
0196 popupMenu.removePopupMenuListener(popupMenuListener);
0197 }
0198 if (menuKeyListener != null) {
0199 popupMenu.removeMenuKeyListener(menuKeyListener);
0200 }
0201 }
0202
0203 protected void uninstallKeyboardActions() {
0204 SwingUtilities.replaceUIActionMap(popupMenu, null);
0205 SwingUtilities.replaceUIInputMap(popupMenu,
0206 JComponent.WHEN_IN_FOCUSED_WINDOW, null);
0207 }
0208
0209 static MenuElement getFirstPopup() {
0210 MenuSelectionManager msm = MenuSelectionManager
0211 .defaultManager();
0212 MenuElement[] p = msm.getSelectedPath();
0213 MenuElement me = null;
0214
0215 for (int i = 0; me == null && i < p.length; i++) {
0216 if (p[i] instanceof JPopupMenu)
0217 me = p[i];
0218 }
0219
0220 return me;
0221 }
0222
0223 static JPopupMenu getLastPopup() {
0224 MenuSelectionManager msm = MenuSelectionManager
0225 .defaultManager();
0226 MenuElement[] p = msm.getSelectedPath();
0227 JPopupMenu popup = null;
0228
0229 for (int i = p.length - 1; popup == null && i >= 0; i--) {
0230 if (p[i] instanceof JPopupMenu)
0231 popup = (JPopupMenu) p[i];
0232 }
0233 return popup;
0234 }
0235
0236 static List getPopups() {
0237 MenuSelectionManager msm = MenuSelectionManager
0238 .defaultManager();
0239 MenuElement[] p = msm.getSelectedPath();
0240
0241 List list = new ArrayList(p.length);
0242 for (int i = 0; i < p.length; i++) {
0243 if (p[i] instanceof JPopupMenu) {
0244 list.add((JPopupMenu) p[i]);
0245 }
0246 }
0247 return list;
0248 }
0249
0250 public boolean isPopupTrigger(MouseEvent e) {
0251 return ((e.getID() == MouseEvent.MOUSE_RELEASED) && ((e
0252 .getModifiers() & MouseEvent.BUTTON3_MASK) != 0));
0253 }
0254
0255 private static boolean checkInvokerEqual(MenuElement present,
0256 MenuElement last) {
0257 Component invokerPresent = present.getComponent();
0258 Component invokerLast = last.getComponent();
0259
0260 if (invokerPresent instanceof JPopupMenu) {
0261 invokerPresent = ((JPopupMenu) invokerPresent).getInvoker();
0262 }
0263 if (invokerLast instanceof JPopupMenu) {
0264 invokerLast = ((JPopupMenu) invokerLast).getInvoker();
0265 }
0266 return (invokerPresent == invokerLast);
0267 }
0268
0269 /**
0270 * This Listener fires the Action that provides the correct auditory
0271 * feedback.
0272 *
0273 * @since 1.4
0274 */
0275 private class BasicPopupMenuListener implements PopupMenuListener {
0276 public void popupMenuCanceled(PopupMenuEvent e) {
0277 }
0278
0279 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
0280 }
0281
0282 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
0283 BasicLookAndFeel.playSound((JPopupMenu) e.getSource(),
0284 "PopupMenu.popupSound");
0285 }
0286 }
0287
0288 /**
0289 * Handles mnemonic for children JMenuItems.
0290 * @since 1.5
0291 */
0292 private class BasicMenuKeyListener implements MenuKeyListener {
0293 MenuElement menuToOpen = null;
0294
0295 public void menuKeyTyped(MenuKeyEvent e) {
0296 if (menuToOpen != null) {
0297 // we have a submenu to open
0298 JPopupMenu subpopup = ((JMenu) menuToOpen)
0299 .getPopupMenu();
0300 MenuElement subitem = findEnabledChild(subpopup
0301 .getSubElements(), -1, true);
0302
0303 ArrayList lst = new ArrayList(Arrays
0304 .asList(e.getPath()));
0305 lst.add(menuToOpen);
0306 lst.add(subpopup);
0307 if (subitem != null) {
0308 lst.add(subitem);
0309 }
0310 MenuElement newPath[] = new MenuElement[0];
0311 ;
0312 newPath = (MenuElement[]) lst.toArray(newPath);
0313 MenuSelectionManager.defaultManager().setSelectedPath(
0314 newPath);
0315 e.consume();
0316 }
0317 menuToOpen = null;
0318 }
0319
0320 public void menuKeyPressed(MenuKeyEvent e) {
0321 char keyChar = e.getKeyChar();
0322
0323 // Handle the case for Escape or Enter...
0324 if (!Character.isLetterOrDigit(keyChar)) {
0325 return;
0326 }
0327
0328 MenuSelectionManager manager = e.getMenuSelectionManager();
0329 MenuElement path[] = e.getPath();
0330 MenuElement items[] = popupMenu.getSubElements();
0331 int currentIndex = -1;
0332 int matches = 0;
0333 int firstMatch = -1;
0334 int indexes[] = null;
0335
0336 for (int j = 0; j < items.length; j++) {
0337 if (!(items[j] instanceof JMenuItem)) {
0338 continue;
0339 }
0340 JMenuItem item = (JMenuItem) items[j];
0341 int mnemonic = item.getMnemonic();
0342 if (item.isEnabled() && item.isVisible()
0343 && lower(keyChar) == lower(mnemonic)) {
0344 if (matches == 0) {
0345 firstMatch = j;
0346 matches++;
0347 } else {
0348 if (indexes == null) {
0349 indexes = new int[items.length];
0350 indexes[0] = firstMatch;
0351 }
0352 indexes[matches++] = j;
0353 }
0354 }
0355 if (item.isArmed()) {
0356 currentIndex = matches - 1;
0357 }
0358 }
0359
0360 if (matches == 0) {
0361 ; // no op
0362 } else if (matches == 1) {
0363 // Invoke the menu action
0364 JMenuItem item = (JMenuItem) items[firstMatch];
0365 if (item instanceof JMenu) {
0366 // submenus are handled in menuKeyTyped
0367 menuToOpen = item;
0368 } else if (item.isEnabled()) {
0369 // we have a menu item
0370 manager.clearSelectedPath();
0371 item.doClick();
0372 }
0373 e.consume();
0374 } else {
0375 // Select the menu item with the matching mnemonic. If
0376 // the same mnemonic has been invoked then select the next
0377 // menu item in the cycle.
0378 MenuElement newItem = null;
0379
0380 newItem = items[indexes[(currentIndex + 1) % matches]];
0381
0382 MenuElement newPath[] = new MenuElement[path.length + 1];
0383 System.arraycopy(path, 0, newPath, 0, path.length);
0384 newPath[path.length] = newItem;
0385 manager.setSelectedPath(newPath);
0386 e.consume();
0387 }
0388 return;
0389 }
0390
0391 public void menuKeyReleased(MenuKeyEvent e) {
0392 }
0393
0394 private char lower(char keyChar) {
0395 return Character.toLowerCase(keyChar);
0396 }
0397
0398 private char lower(int mnemonic) {
0399 return Character.toLowerCase((char) mnemonic);
0400 }
0401 }
0402
0403 private static class Actions extends UIAction {
0404 // Types of actions
0405 private static final String CANCEL = "cancel";
0406 private static final String SELECT_NEXT = "selectNext";
0407 private static final String SELECT_PREVIOUS = "selectPrevious";
0408 private static final String SELECT_PARENT = "selectParent";
0409 private static final String SELECT_CHILD = "selectChild";
0410 private static final String RETURN = "return";
0411
0412 // Used for next/previous actions
0413 private static final boolean FORWARD = true;
0414 private static final boolean BACKWARD = false;
0415
0416 // Used for parent/child actions
0417 private static final boolean PARENT = false;
0418 private static final boolean CHILD = true;
0419
0420 Actions(String key) {
0421 super (key);
0422 }
0423
0424 public void actionPerformed(ActionEvent e) {
0425 String key = getName();
0426 if (key == CANCEL) {
0427 cancel();
0428 } else if (key == SELECT_NEXT) {
0429 selectItem(FORWARD);
0430 } else if (key == SELECT_PREVIOUS) {
0431 selectItem(BACKWARD);
0432 } else if (key == SELECT_PARENT) {
0433 selectParentChild(PARENT);
0434 } else if (key == SELECT_CHILD) {
0435 selectParentChild(CHILD);
0436 } else if (key == RETURN) {
0437 doReturn();
0438 }
0439 }
0440
0441 private void doReturn() {
0442 KeyboardFocusManager fmgr = KeyboardFocusManager
0443 .getCurrentKeyboardFocusManager();
0444 Component focusOwner = fmgr.getFocusOwner();
0445 if (focusOwner != null
0446 && !(focusOwner instanceof JRootPane)) {
0447 return;
0448 }
0449
0450 MenuSelectionManager msm = MenuSelectionManager
0451 .defaultManager();
0452 MenuElement path[] = msm.getSelectedPath();
0453 MenuElement lastElement;
0454 if (path.length > 0) {
0455 lastElement = path[path.length - 1];
0456 if (lastElement instanceof JMenu) {
0457 MenuElement newPath[] = new MenuElement[path.length + 1];
0458 System.arraycopy(path, 0, newPath, 0, path.length);
0459 newPath[path.length] = ((JMenu) lastElement)
0460 .getPopupMenu();
0461 msm.setSelectedPath(newPath);
0462 } else if (lastElement instanceof JMenuItem) {
0463 JMenuItem mi = (JMenuItem) lastElement;
0464
0465 if (mi.getUI() instanceof BasicMenuItemUI) {
0466 ((BasicMenuItemUI) mi.getUI()).doClick(msm);
0467 } else {
0468 msm.clearSelectedPath();
0469 mi.doClick(0);
0470 }
0471 }
0472 }
0473 }
0474
0475 private void selectParentChild(boolean direction) {
0476 MenuSelectionManager msm = MenuSelectionManager
0477 .defaultManager();
0478 MenuElement path[] = msm.getSelectedPath();
0479 int len = path.length;
0480
0481 if (direction == PARENT) {
0482 // selecting parent
0483 int popupIndex = len - 1;
0484
0485 if (len > 2
0486 &&
0487 // check if we have an open submenu. A submenu item may or
0488 // may not be selected, so submenu popup can be either the
0489 // last or next to the last item.
0490 (path[popupIndex] instanceof JPopupMenu || path[--popupIndex] instanceof JPopupMenu)
0491 && !((JMenu) path[popupIndex - 1])
0492 .isTopLevelMenu()) {
0493
0494 // we have a submenu, just close it
0495 MenuElement newPath[] = new MenuElement[popupIndex];
0496 System.arraycopy(path, 0, newPath, 0, popupIndex);
0497 msm.setSelectedPath(newPath);
0498 return;
0499 }
0500 } else {
0501 // selecting child
0502 if (len > 0 && path[len - 1] instanceof JMenu
0503 && !((JMenu) path[len - 1]).isTopLevelMenu()) {
0504
0505 // we have a submenu, open it
0506 JMenu menu = (JMenu) path[len - 1];
0507 JPopupMenu popup = menu.getPopupMenu();
0508 MenuElement[] subs = popup.getSubElements();
0509 MenuElement item = findEnabledChild(subs, -1, true);
0510 MenuElement[] newPath;
0511
0512 if (item == null) {
0513 newPath = new MenuElement[len + 1];
0514 } else {
0515 newPath = new MenuElement[len + 2];
0516 newPath[len + 1] = item;
0517 }
0518 System.arraycopy(path, 0, newPath, 0, len);
0519 newPath[len] = popup;
0520 msm.setSelectedPath(newPath);
0521 return;
0522 }
0523 }
0524
0525 // check if we have a toplevel menu selected.
0526 // If this is the case, we select another toplevel menu
0527 if (len > 1 && path[0] instanceof JMenuBar) {
0528 MenuElement currentMenu = path[1];
0529 MenuElement nextMenu = findEnabledChild(path[0]
0530 .getSubElements(), currentMenu, direction);
0531
0532 if (nextMenu != null && nextMenu != currentMenu) {
0533 MenuElement newSelection[];
0534 if (len == 2) {
0535 // menu is selected but its popup not shown
0536 newSelection = new MenuElement[2];
0537 newSelection[0] = path[0];
0538 newSelection[1] = nextMenu;
0539 } else {
0540 // menu is selected and its popup is shown
0541 newSelection = new MenuElement[3];
0542 newSelection[0] = path[0];
0543 newSelection[1] = nextMenu;
0544 newSelection[2] = ((JMenu) nextMenu)
0545 .getPopupMenu();
0546 }
0547 msm.setSelectedPath(newSelection);
0548 }
0549 }
0550 }
0551
0552 private void selectItem(boolean direction) {
0553 MenuSelectionManager msm = MenuSelectionManager
0554 .defaultManager();
0555 MenuElement path[] = msm.getSelectedPath();
0556 if (path.length < 2) {
0557 return;
0558 }
0559 int len = path.length;
0560
0561 if (path[0] instanceof JMenuBar && path[1] instanceof JMenu
0562 && len == 2) {
0563
0564 // a toplevel menu is selected, but its popup not shown.
0565 // Show the popup and select the first item
0566 JPopupMenu popup = ((JMenu) path[1]).getPopupMenu();
0567 MenuElement next = findEnabledChild(popup
0568 .getSubElements(), -1, FORWARD);
0569 MenuElement[] newPath;
0570
0571 if (next != null) {
0572 // an enabled item found -- include it in newPath
0573 newPath = new MenuElement[4];
0574 newPath[3] = next;
0575 } else {
0576 // menu has no enabled items -- still must show the popup
0577 newPath = new MenuElement[3];
0578 }
0579 System.arraycopy(path, 0, newPath, 0, 2);
0580 newPath[2] = popup;
0581 msm.setSelectedPath(newPath);
0582
0583 } else if (path[len - 1] instanceof JPopupMenu
0584 && path[len - 2] instanceof JMenu) {
0585
0586 // a menu (not necessarily toplevel) is open and its popup
0587 // shown. Select the appropriate menu item
0588 JMenu menu = (JMenu) path[len - 2];
0589 JPopupMenu popup = menu.getPopupMenu();
0590 MenuElement next = findEnabledChild(popup
0591 .getSubElements(), -1, direction);
0592
0593 if (next != null) {
0594 MenuElement[] newPath = new MenuElement[len + 1];
0595 System.arraycopy(path, 0, newPath, 0, len);
0596 newPath[len] = next;
0597 msm.setSelectedPath(newPath);
0598 } else {
0599 // all items in the popup are disabled.
0600 // We're going to find the parent popup menu and select
0601 // its next item. If there's no parent popup menu (i.e.
0602 // current menu is toplevel), do nothing
0603 if (len > 2 && path[len - 3] instanceof JPopupMenu) {
0604 popup = ((JPopupMenu) path[len - 3]);
0605 next = findEnabledChild(popup.getSubElements(),
0606 menu, direction);
0607
0608 if (next != null && next != menu) {
0609 MenuElement[] newPath = new MenuElement[len - 1];
0610 System.arraycopy(path, 0, newPath, 0,
0611 len - 2);
0612 newPath[len - 2] = next;
0613 msm.setSelectedPath(newPath);
0614 }
0615 }
0616 }
0617
0618 } else {
0619 // just select the next item, no path expansion needed
0620 MenuElement subs[] = path[len - 2].getSubElements();
0621 MenuElement nextChild = findEnabledChild(subs,
0622 path[len - 1], direction);
0623 if (nextChild == null) {
0624 nextChild = findEnabledChild(subs, -1, direction);
0625 }
0626 if (nextChild != null) {
0627 path[len - 1] = nextChild;
0628 msm.setSelectedPath(path);
0629 }
0630 }
0631 }
0632
0633 private void cancel() {
0634 // 4234793: This action should call JPopupMenu.firePopupMenuCanceled but it's
0635 // a protected method. The real solution could be to make
0636 // firePopupMenuCanceled public and call it directly.
0637 JPopupMenu lastPopup = (JPopupMenu) getLastPopup();
0638 if (lastPopup != null) {
0639 lastPopup.putClientProperty(
0640 "JPopupMenu.firePopupMenuCanceled",
0641 Boolean.TRUE);
0642 }
0643 String mode = UIManager.getString("Menu.cancelMode");
0644 if ("hideMenuTree".equals(mode)) {
0645 MenuSelectionManager.defaultManager()
0646 .clearSelectedPath();
0647 } else {
0648 shortenSelectedPath();
0649 }
0650 }
0651
0652 private void shortenSelectedPath() {
0653 MenuElement path[] = MenuSelectionManager.defaultManager()
0654 .getSelectedPath();
0655 if (path.length <= 2) {
0656 MenuSelectionManager.defaultManager()
0657 .clearSelectedPath();
0658 return;
0659 }
0660 // unselect MenuItem and its Popup by default
0661 int value = 2;
0662 MenuElement lastElement = path[path.length - 1];
0663 JPopupMenu lastPopup = getLastPopup();
0664 if (lastElement == lastPopup) {
0665 MenuElement previousElement = path[path.length - 2];
0666 if (previousElement instanceof JMenu) {
0667 JMenu lastMenu = (JMenu) previousElement;
0668 if (lastMenu.isEnabled()
0669 && lastPopup.getComponentCount() > 0) {
0670 // unselect the last visible popup only
0671 value = 1;
0672 } else {
0673 // unselect invisible popup and two visible elements
0674 value = 3;
0675 }
0676 }
0677 }
0678 if (path.length - value <= 2
0679 && !UIManager
0680 .getBoolean("Menu.preserveTopLevelSelection")) {
0681 // clear selection for the topLevelMenu
0682 value = path.length;
0683 }
0684 MenuElement newPath[] = new MenuElement[path.length - value];
0685 System.arraycopy(path, 0, newPath, 0, path.length - value);
0686 MenuSelectionManager.defaultManager().setSelectedPath(
0687 newPath);
0688 }
0689 }
0690
0691 private static MenuElement nextEnabledChild(MenuElement e[],
0692 int fromIndex, int toIndex) {
0693 for (int i = fromIndex; i <= toIndex; i++) {
0694 if (e[i] != null) {
0695 Component comp = e[i].getComponent();
0696 if (comp != null
0697 && (comp.isEnabled() || UIManager
0698 .getBoolean("MenuItem.disabledAreNavigable"))
0699 && comp.isVisible()) {
0700 return e[i];
0701 }
0702 }
0703 }
0704 return null;
0705 }
0706
0707 private static MenuElement previousEnabledChild(MenuElement e[],
0708 int fromIndex, int toIndex) {
0709 for (int i = fromIndex; i >= toIndex; i--) {
0710 if (e[i] != null) {
0711 Component comp = e[i].getComponent();
0712 if (comp != null
0713 && (comp.isEnabled() || UIManager
0714 .getBoolean("MenuItem.disabledAreNavigable"))
0715 && comp.isVisible()) {
0716 return e[i];
0717 }
0718 }
0719 }
0720 return null;
0721 }
0722
0723 static MenuElement findEnabledChild(MenuElement e[], int fromIndex,
0724 boolean forward) {
0725 MenuElement result = null;
0726 if (forward) {
0727 result = nextEnabledChild(e, fromIndex + 1, e.length - 1);
0728 if (result == null)
0729 result = nextEnabledChild(e, 0, fromIndex - 1);
0730 } else {
0731 result = previousEnabledChild(e, fromIndex - 1, 0);
0732 if (result == null)
0733 result = previousEnabledChild(e, e.length - 1,
0734 fromIndex + 1);
0735 }
0736 return result;
0737 }
0738
0739 static MenuElement findEnabledChild(MenuElement e[],
0740 MenuElement elem, boolean forward) {
0741 for (int i = 0; i < e.length; i++) {
0742 if (e[i] == elem) {
0743 return findEnabledChild(e, i, forward);
0744 }
0745 }
0746 return null;
0747 }
0748
0749 static class MouseGrabber implements ChangeListener,
0750 AWTEventListener, ComponentListener, WindowListener {
0751
0752 Window grabbedWindow;
0753 MenuElement[] lastPathSelected;
0754
0755 public MouseGrabber() {
0756 MenuSelectionManager msm = MenuSelectionManager
0757 .defaultManager();
0758 msm.addChangeListener(this );
0759 this .lastPathSelected = msm.getSelectedPath();
0760 if (this .lastPathSelected.length != 0) {
0761 grabWindow(this .lastPathSelected);
0762 }
0763 }
0764
0765 void uninstall() {
0766 synchronized (MOUSE_GRABBER_KEY) {
0767 MenuSelectionManager.defaultManager()
0768 .removeChangeListener(this );
0769 ungrabWindow();
0770 AppContext.getAppContext().remove(MOUSE_GRABBER_KEY);
0771 }
0772 }
0773
0774 void grabWindow(MenuElement[] newPath) {
0775 // A grab needs to be added
0776 final Toolkit tk = Toolkit.getDefaultToolkit();
0777 java.security.AccessController
0778 .doPrivileged(new java.security.PrivilegedAction() {
0779 public Object run() {
0780 tk
0781 .addAWTEventListener(
0782 MouseGrabber.this ,
0783 AWTEvent.MOUSE_EVENT_MASK
0784 | AWTEvent.MOUSE_MOTION_EVENT_MASK
0785 | AWTEvent.MOUSE_WHEEL_EVENT_MASK
0786 | AWTEvent.WINDOW_EVENT_MASK
0787 | sun.awt.SunToolkit.GRAB_EVENT_MASK);
0788 return null;
0789 }
0790 });
0791
0792 Component invoker = newPath[0].getComponent();
0793 if (invoker instanceof JPopupMenu) {
0794 invoker = ((JPopupMenu) invoker).getInvoker();
0795 }
0796 grabbedWindow = invoker instanceof Window ? (Window) invoker
0797 : SwingUtilities.getWindowAncestor(invoker);
0798 if (grabbedWindow != null) {
0799 if (tk instanceof sun.awt.SunToolkit) {
0800 ((sun.awt.SunToolkit) tk).grab(grabbedWindow);
0801 } else {
0802 grabbedWindow.addComponentListener(this );
0803 grabbedWindow.addWindowListener(this );
0804 }
0805 }
0806 }
0807
0808 void ungrabWindow() {
0809 final Toolkit tk = Toolkit.getDefaultToolkit();
0810 // The grab should be removed
0811 java.security.AccessController
0812 .doPrivileged(new java.security.PrivilegedAction() {
0813 public Object run() {
0814 tk
0815 .removeAWTEventListener(MouseGrabber.this );
0816 return null;
0817 }
0818 });
0819 realUngrabWindow();
0820 }
0821
0822 void realUngrabWindow() {
0823 Toolkit tk = Toolkit.getDefaultToolkit();
0824 if (grabbedWindow != null) {
0825 if (tk instanceof sun.awt.SunToolkit) {
0826 ((sun.awt.SunToolkit) tk).ungrab(grabbedWindow);
0827 } else {
0828 grabbedWindow.removeComponentListener(this );
0829 grabbedWindow.removeWindowListener(this );
0830 }
0831 grabbedWindow = null;
0832 }
0833 }
0834
0835 public void stateChanged(ChangeEvent e) {
0836 MenuSelectionManager msm = MenuSelectionManager
0837 .defaultManager();
0838 MenuElement[] p = msm.getSelectedPath();
0839
0840 if (lastPathSelected.length == 0 && p.length != 0) {
0841 grabWindow(p);
0842 }
0843
0844 if (lastPathSelected.length != 0 && p.length == 0) {
0845 ungrabWindow();
0846 }
0847
0848 lastPathSelected = p;
0849 }
0850
0851 public void eventDispatched(AWTEvent ev) {
0852 if (ev instanceof sun.awt.UngrabEvent) {
0853 // Popup should be canceled in case of ungrab event
0854 cancelPopupMenu();
0855 return;
0856 }
0857 switch (ev.getID()) {
0858 case MouseEvent.MOUSE_PRESSED:
0859 Component src = (Component) ev.getSource();
0860 if (isInPopup(src)
0861 || (src instanceof JMenu && ((JMenu) src)
0862 .isSelected())) {
0863 return;
0864 }
0865 if (!(src instanceof JComponent)
0866 || !(((JComponent) src)
0867 .getClientProperty("doNotCancelPopup") == BasicComboBoxUI.HIDE_POPUP_KEY)) {
0868 // Cancel popup only if this property was not set.
0869 // If this property is set to TRUE component wants
0870 // to deal with this event by himself.
0871 cancelPopupMenu();
0872 // Ask UIManager about should we consume event that closes
0873 // popup. This made to match native apps behaviour.
0874 boolean consumeEvent = UIManager
0875 .getBoolean("PopupMenu.consumeEventOnClose");
0876 // Consume the event so that normal processing stops.
0877 if (consumeEvent && !(src instanceof MenuElement)) {
0878 ((MouseEvent) ev).consume();
0879 }
0880 }
0881 break;
0882
0883 case MouseEvent.MOUSE_RELEASED:
0884 src = (Component) ev.getSource();
0885 if (!(src instanceof MenuElement)) {
0886 // Do not forward event to MSM, let component handle it
0887 if (isInPopup(src)) {
0888 break;
0889 }
0890 }
0891 if (src instanceof JMenu || !(src instanceof JMenuItem)) {
0892 MenuSelectionManager.defaultManager()
0893 .processMouseEvent((MouseEvent) ev);
0894 }
0895 break;
0896 case MouseEvent.MOUSE_DRAGGED:
0897 src = (Component) ev.getSource();
0898 if (!(src instanceof MenuElement)) {
0899 // For the MOUSE_DRAGGED event the src is
0900 // the Component in which mouse button was pressed.
0901 // If the src is in popupMenu,
0902 // do not forward event to MSM, let component handle it.
0903 if (isInPopup(src)) {
0904 break;
0905 }
0906 }
0907 MenuSelectionManager.defaultManager()
0908 .processMouseEvent((MouseEvent) ev);
0909 break;
0910 case MouseEvent.MOUSE_WHEEL:
0911 if (isInPopup((Component) ev.getSource())) {
0912 return;
0913 }
0914 cancelPopupMenu();
0915 break;
0916 }
0917 }
0918
0919 boolean isInPopup(Component src) {
0920 for (Component c = src; c != null; c = c.getParent()) {
0921 if (c instanceof Applet || c instanceof Window) {
0922 break;
0923 } else if (c instanceof JPopupMenu) {
0924 return true;
0925 }
0926 }
0927 return false;
0928 }
0929
0930 void cancelPopupMenu() {
0931 // We should ungrab window if a user code throws
0932 // an unexpected runtime exception. See 6495920.
0933 try {
0934 // 4234793: This action should call firePopupMenuCanceled but it's
0935 // a protected method. The real solution could be to make
0936 // firePopupMenuCanceled public and call it directly.
0937 List popups = getPopups();
0938 Iterator iter = popups.iterator();
0939 while (iter.hasNext()) {
0940 JPopupMenu popup = (JPopupMenu) iter.next();
0941 popup.putClientProperty(
0942 "JPopupMenu.firePopupMenuCanceled",
0943 Boolean.TRUE);
0944 }
0945 MenuSelectionManager.defaultManager()
0946 .clearSelectedPath();
0947 } catch (RuntimeException ex) {
0948 realUngrabWindow();
0949 throw ex;
0950 } catch (Error err) {
0951 realUngrabWindow();
0952 throw err;
0953 }
0954 }
0955
0956 public void componentResized(ComponentEvent e) {
0957 cancelPopupMenu();
0958 }
0959
0960 public void componentMoved(ComponentEvent e) {
0961 cancelPopupMenu();
0962 }
0963
0964 public void componentShown(ComponentEvent e) {
0965 cancelPopupMenu();
0966 }
0967
0968 public void componentHidden(ComponentEvent e) {
0969 cancelPopupMenu();
0970 }
0971
0972 public void windowClosing(WindowEvent e) {
0973 cancelPopupMenu();
0974 }
0975
0976 public void windowClosed(WindowEvent e) {
0977 cancelPopupMenu();
0978 }
0979
0980 public void windowIconified(WindowEvent e) {
0981 cancelPopupMenu();
0982 }
0983
0984 public void windowDeactivated(WindowEvent e) {
0985 cancelPopupMenu();
0986 }
0987
0988 public void windowOpened(WindowEvent e) {
0989 }
0990
0991 public void windowDeiconified(WindowEvent e) {
0992 }
0993
0994 public void windowActivated(WindowEvent e) {
0995 }
0996 }
0997
0998 /**
0999 * This helper is added to MenuSelectionManager as a ChangeListener to
1000 * listen to menu selection changes. When a menu is activated, it passes
1001 * focus to its parent JRootPane, and installs an ActionMap/InputMap pair
1002 * on that JRootPane. Those maps are necessary in order for menu
1003 * navigation to work. When menu is being deactivated, it restores focus
1004 * to the component that has had it before menu activation, and uninstalls
1005 * the maps.
1006 * This helper is also installed as a KeyListener on root pane when menu
1007 * is active. It forwards key events to MenuSelectionManager for mnemonic
1008 * keys handling.
1009 */
1010 static class MenuKeyboardHelper implements ChangeListener,
1011 KeyListener {
1012
1013 private Component lastFocused = null;
1014 private MenuElement[] lastPathSelected = new MenuElement[0];
1015 private JPopupMenu lastPopup;
1016
1017 private JRootPane invokerRootPane;
1018 private ActionMap menuActionMap = getActionMap();
1019 private InputMap menuInputMap;
1020 private boolean focusTraversalKeysEnabled;
1021
1022 /*
1023 * Fix for 4213634
1024 * If this is false, KEY_TYPED and KEY_RELEASED events are NOT
1025 * processed. This is needed to avoid activating a menuitem when
1026 * the menu and menuitem share the same mnemonic.
1027 */
1028 private boolean receivedKeyPressed = false;
1029
1030 void removeItems() {
1031 if (lastFocused != null) {
1032 if (!lastFocused.requestFocusInWindow()) {
1033 // Workarounr for 4810575.
1034 // If lastFocused is not in currently focused window
1035 // requestFocusInWindow will fail. In this case we must
1036 // request focus by requestFocus() if it was not
1037 // transferred from our popup.
1038 Window cfw = KeyboardFocusManager
1039 .getCurrentKeyboardFocusManager()
1040 .getFocusedWindow();
1041 if (cfw != null
1042 && "###focusableSwingPopup###".equals(cfw
1043 .getName())) {
1044 lastFocused.requestFocus();
1045 }
1046
1047 }
1048 lastFocused = null;
1049 }
1050 if (invokerRootPane != null) {
1051 invokerRootPane.removeKeyListener(this );
1052 invokerRootPane
1053 .setFocusTraversalKeysEnabled(focusTraversalKeysEnabled);
1054 removeUIInputMap(invokerRootPane, menuInputMap);
1055 removeUIActionMap(invokerRootPane, menuActionMap);
1056 invokerRootPane = null;
1057 }
1058 receivedKeyPressed = false;
1059 }
1060
1061 private FocusListener rootPaneFocusListener = new FocusAdapter() {
1062 public void focusGained(FocusEvent ev) {
1063 Component opposite = ev.getOppositeComponent();
1064 if (opposite != null) {
1065 lastFocused = opposite;
1066 }
1067 ev.getComponent().removeFocusListener(this );
1068 }
1069 };
1070
1071 /**
1072 * Return the last JPopupMenu in <code>path</code>,
1073 * or <code>null</code> if none found
1074 */
1075 JPopupMenu getActivePopup(MenuElement[] path) {
1076 for (int i = path.length - 1; i >= 0; i--) {
1077 MenuElement elem = path[i];
1078 if (elem instanceof JPopupMenu) {
1079 return (JPopupMenu) elem;
1080 }
1081 }
1082 return null;
1083 }
1084
1085 void addUIInputMap(JComponent c, InputMap map) {
1086 InputMap lastNonUI = null;
1087 InputMap parent = c
1088 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1089
1090 while (parent != null && !(parent instanceof UIResource)) {
1091 lastNonUI = parent;
1092 parent = parent.getParent();
1093 }
1094
1095 if (lastNonUI == null) {
1096 c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map);
1097 } else {
1098 lastNonUI.setParent(map);
1099 }
1100 map.setParent(parent);
1101 }
1102
1103 void addUIActionMap(JComponent c, ActionMap map) {
1104 ActionMap lastNonUI = null;
1105 ActionMap parent = c.getActionMap();
1106
1107 while (parent != null && !(parent instanceof UIResource)) {
1108 lastNonUI = parent;
1109 parent = parent.getParent();
1110 }
1111
1112 if (lastNonUI == null) {
1113 c.setActionMap(map);
1114 } else {
1115 lastNonUI.setParent(map);
1116 }
1117 map.setParent(parent);
1118 }
1119
1120 void removeUIInputMap(JComponent c, InputMap map) {
1121 InputMap im = null;
1122 InputMap parent = c
1123 .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1124
1125 while (parent != null) {
1126 if (parent == map) {
1127 if (im == null) {
1128 c.setInputMap(
1129 JComponent.WHEN_IN_FOCUSED_WINDOW, map
1130 .getParent());
1131 } else {
1132 im.setParent(map.getParent());
1133 }
1134 break;
1135 }
1136 im = parent;
1137 parent = parent.getParent();
1138 }
1139 }
1140
1141 void removeUIActionMap(JComponent c, ActionMap map) {
1142 ActionMap im = null;
1143 ActionMap parent = c.getActionMap();
1144
1145 while (parent != null) {
1146 if (parent == map) {
1147 if (im == null) {
1148 c.setActionMap(map.getParent());
1149 } else {
1150 im.setParent(map.getParent());
1151 }
1152 break;
1153 }
1154 im = parent;
1155 parent = parent.getParent();
1156 }
1157 }
1158
1159 public void stateChanged(ChangeEvent ev) {
1160 if (!(UIManager.getLookAndFeel() instanceof BasicLookAndFeel)) {
1161 uninstall();
1162 return;
1163 }
1164 MenuSelectionManager msm = (MenuSelectionManager) ev
1165 .getSource();
1166 MenuElement[] p = msm.getSelectedPath();
1167 JPopupMenu popup = getActivePopup(p);
1168 if (popup != null && !popup.isFocusable()) {
1169 // Do nothing for non-focusable popups
1170 return;
1171 }
1172
1173 if (lastPathSelected.length != 0 && p.length != 0) {
1174 if (!checkInvokerEqual(p[0], lastPathSelected[0])) {
1175 removeItems();
1176 lastPathSelected = new MenuElement[0];
1177 }
1178 }
1179
1180 if (lastPathSelected.length == 0 && p.length > 0) {
1181 // menu posted
1182 JComponent invoker;
1183
1184 if (popup == null) {
1185 if (p.length == 2 && p[0] instanceof JMenuBar
1186 && p[1] instanceof JMenu) {
1187 // a menu has been selected but not open
1188 invoker = (JComponent) p[1];
1189 popup = ((JMenu) invoker).getPopupMenu();
1190 } else {
1191 return;
1192 }
1193 } else {
1194 Component c = popup.getInvoker();
1195 if (c instanceof JFrame) {
1196 invoker = ((JFrame) c).getRootPane();
1197 } else if (c instanceof JDialog) {
1198 invoker = ((JDialog) c).getRootPane();
1199 } else if (c instanceof JApplet) {
1200 invoker = ((JApplet) c).getRootPane();
1201 } else {
1202 while (!(c instanceof JComponent)) {
1203 if (c == null) {
1204 return;
1205 }
1206 c = c.getParent();
1207 }
1208 invoker = (JComponent) c;
1209 }
1210 }
1211
1212 // remember current focus owner
1213 lastFocused = KeyboardFocusManager
1214 .getCurrentKeyboardFocusManager()
1215 .getFocusOwner();
1216
1217 // request focus on root pane and install keybindings
1218 // used for menu navigation
1219 invokerRootPane = SwingUtilities.getRootPane(invoker);
1220 if (invokerRootPane != null) {
1221 invokerRootPane
1222 .addFocusListener(rootPaneFocusListener);
1223 invokerRootPane.requestFocus(true);
1224 invokerRootPane.addKeyListener(this );
1225 focusTraversalKeysEnabled = invokerRootPane
1226 .getFocusTraversalKeysEnabled();
1227 invokerRootPane.setFocusTraversalKeysEnabled(false);
1228
1229 menuInputMap = getInputMap(popup, invokerRootPane);
1230 addUIInputMap(invokerRootPane, menuInputMap);
1231 addUIActionMap(invokerRootPane, menuActionMap);
1232 }
1233 } else if (lastPathSelected.length != 0 && p.length == 0) {
1234 // menu hidden -- return focus to where it had been before
1235 // and uninstall menu keybindings
1236 removeItems();
1237 } else {
1238 if (popup != lastPopup) {
1239 receivedKeyPressed = false;
1240 }
1241 }
1242
1243 // Remember the last path selected
1244 lastPathSelected = p;
1245 lastPopup = popup;
1246 }
1247
1248 public void keyPressed(KeyEvent ev) {
1249 receivedKeyPressed = true;
1250 MenuSelectionManager.defaultManager().processKeyEvent(ev);
1251 }
1252
1253 public void keyReleased(KeyEvent ev) {
1254 if (receivedKeyPressed) {
1255 receivedKeyPressed = false;
1256 MenuSelectionManager.defaultManager().processKeyEvent(
1257 ev);
1258 }
1259 }
1260
1261 public void keyTyped(KeyEvent ev) {
1262 if (receivedKeyPressed) {
1263 MenuSelectionManager.defaultManager().processKeyEvent(
1264 ev);
1265 }
1266 }
1267
1268 void uninstall() {
1269 synchronized (MENU_KEYBOARD_HELPER_KEY) {
1270 MenuSelectionManager.defaultManager()
1271 .removeChangeListener(this);
1272 AppContext.getAppContext().remove(
1273 MENU_KEYBOARD_HELPER_KEY);
1274 }
1275 }
1276 }
1277 }
|