1/* 2 * Copyright (c) 1997, 2014, 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 26package javax.swing.plaf.basic; 27 28import javax.swing.*; 29import javax.swing.event.*; 30import javax.swing.plaf.*; 31import javax.swing.plaf.basic.*; 32import javax.swing.border.*; 33 34import java.applet.Applet; 35 36import java.awt.Component; 37import java.awt.Container; 38import java.awt.Dimension; 39import java.awt.KeyboardFocusManager; 40import java.awt.Window; 41import java.awt.event.*; 42import java.awt.AWTEvent; 43import java.awt.Toolkit; 44 45import java.beans.PropertyChangeListener; 46import java.beans.PropertyChangeEvent; 47 48import java.util.*; 49 50import sun.swing.DefaultLookup; 51import sun.swing.UIAction; 52 53import sun.awt.AppContext; 54 55/** 56 * A Windows L&F implementation of PopupMenuUI. This implementation 57 * is a "combined" view/controller. 58 * 59 * @author Georges Saab 60 * @author David Karlton 61 * @author Arnaud Weber 62 */ 63public class BasicPopupMenuUI extends PopupMenuUI { 64 static final StringBuilder MOUSE_GRABBER_KEY = new StringBuilder( 65 "javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber"); 66 static final StringBuilder MENU_KEYBOARD_HELPER_KEY = new StringBuilder( 67 "javax.swing.plaf.basic.BasicPopupMenuUI.MenuKeyboardHelper"); 68 69 /** 70 * The instance of {@code JPopupMenu}. 71 */ 72 protected JPopupMenu popupMenu = null; 73 private transient PopupMenuListener popupMenuListener = null; 74 private MenuKeyListener menuKeyListener = null; 75 76 private static boolean checkedUnpostPopup; 77 private static boolean unpostPopup; 78 79 /** 80 * Constructs a new instance of {@code BasicPopupMenuUI}. 81 * 82 * @param x a component 83 * @return a new instance of {@code BasicPopupMenuUI} 84 */ 85 public static ComponentUI createUI(JComponent x) { 86 return new BasicPopupMenuUI(); 87 } 88 89 /** 90 * Constructs a new instance of {@code BasicPopupMenuUI}. 91 */ 92 public BasicPopupMenuUI() { 93 BasicLookAndFeel.needsEventHelper = true; 94 LookAndFeel laf = UIManager.getLookAndFeel(); 95 if (laf instanceof BasicLookAndFeel) { 96 ((BasicLookAndFeel)laf).installAWTEventListener(); 97 } 98 } 99 100 public void installUI(JComponent c) { 101 popupMenu = (JPopupMenu) c; 102 103 installDefaults(); 104 installListeners(); 105 installKeyboardActions(); 106 } 107 108 /** 109 * Installs default properties. 110 */ 111 public void installDefaults() { 112 if (popupMenu.getLayout() == null || 113 popupMenu.getLayout() instanceof UIResource) 114 popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS)); 115 116 LookAndFeel.installProperty(popupMenu, "opaque", Boolean.TRUE); 117 LookAndFeel.installBorder(popupMenu, "PopupMenu.border"); 118 LookAndFeel.installColorsAndFont(popupMenu, 119 "PopupMenu.background", 120 "PopupMenu.foreground", 121 "PopupMenu.font"); 122 } 123 124 /** 125 * Registers listeners. 126 */ 127 protected void installListeners() { 128 if (popupMenuListener == null) { 129 popupMenuListener = new BasicPopupMenuListener(); 130 } 131 popupMenu.addPopupMenuListener(popupMenuListener); 132 133 if (menuKeyListener == null) { 134 menuKeyListener = new BasicMenuKeyListener(); 135 } 136 popupMenu.addMenuKeyListener(menuKeyListener); 137 138 AppContext context = AppContext.getAppContext(); 139 synchronized (MOUSE_GRABBER_KEY) { 140 MouseGrabber mouseGrabber = (MouseGrabber)context.get( 141 MOUSE_GRABBER_KEY); 142 if (mouseGrabber == null) { 143 mouseGrabber = new MouseGrabber(); 144 context.put(MOUSE_GRABBER_KEY, mouseGrabber); 145 } 146 } 147 synchronized (MENU_KEYBOARD_HELPER_KEY) { 148 MenuKeyboardHelper helper = 149 (MenuKeyboardHelper)context.get(MENU_KEYBOARD_HELPER_KEY); 150 if (helper == null) { 151 helper = new MenuKeyboardHelper(); 152 context.put(MENU_KEYBOARD_HELPER_KEY, helper); 153 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 154 msm.addChangeListener(helper); 155 } 156 } 157 } 158 159 /** 160 * Registers keyboard actions. 161 */ 162 protected void installKeyboardActions() { 163 } 164 165 static InputMap getInputMap(JPopupMenu popup, JComponent c) { 166 InputMap windowInputMap = null; 167 Object[] bindings = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings"); 168 if (bindings != null) { 169 windowInputMap = LookAndFeel.makeComponentInputMap(c, bindings); 170 if (!popup.getComponentOrientation().isLeftToRight()) { 171 Object[] km = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings.RightToLeft"); 172 if (km != null) { 173 InputMap rightToLeftInputMap = LookAndFeel.makeComponentInputMap(c, km); 174 rightToLeftInputMap.setParent(windowInputMap); 175 windowInputMap = rightToLeftInputMap; 176 } 177 } 178 } 179 return windowInputMap; 180 } 181 182 static ActionMap getActionMap() { 183 return LazyActionMap.getActionMap(BasicPopupMenuUI.class, 184 "PopupMenu.actionMap"); 185 } 186 187 static void loadActionMap(LazyActionMap map) { 188 map.put(new Actions(Actions.CANCEL)); 189 map.put(new Actions(Actions.SELECT_NEXT)); 190 map.put(new Actions(Actions.SELECT_PREVIOUS)); 191 map.put(new Actions(Actions.SELECT_PARENT)); 192 map.put(new Actions(Actions.SELECT_CHILD)); 193 map.put(new Actions(Actions.RETURN)); 194 BasicLookAndFeel.installAudioActionMap(map); 195 } 196 197 public void uninstallUI(JComponent c) { 198 uninstallDefaults(); 199 uninstallListeners(); 200 uninstallKeyboardActions(); 201 202 popupMenu = null; 203 } 204 205 /** 206 * Uninstalls default properties. 207 */ 208 protected void uninstallDefaults() { 209 LookAndFeel.uninstallBorder(popupMenu); 210 } 211 212 /** 213 * Unregisters listeners. 214 */ 215 protected void uninstallListeners() { 216 if (popupMenuListener != null) { 217 popupMenu.removePopupMenuListener(popupMenuListener); 218 } 219 if (menuKeyListener != null) { 220 popupMenu.removeMenuKeyListener(menuKeyListener); 221 } 222 } 223 224 /** 225 * Unregisters keyboard actions. 226 */ 227 protected void uninstallKeyboardActions() { 228 SwingUtilities.replaceUIActionMap(popupMenu, null); 229 SwingUtilities.replaceUIInputMap(popupMenu, 230 JComponent.WHEN_IN_FOCUSED_WINDOW, null); 231 } 232 233 static MenuElement getFirstPopup() { 234 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 235 MenuElement[] p = msm.getSelectedPath(); 236 MenuElement me = null; 237 238 for(int i = 0 ; me == null && i < p.length ; i++) { 239 if (p[i] instanceof JPopupMenu) 240 me = p[i]; 241 } 242 243 return me; 244 } 245 246 static JPopupMenu getLastPopup() { 247 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 248 MenuElement[] p = msm.getSelectedPath(); 249 JPopupMenu popup = null; 250 251 for(int i = p.length - 1; popup == null && i >= 0; i--) { 252 if (p[i] instanceof JPopupMenu) 253 popup = (JPopupMenu)p[i]; 254 } 255 return popup; 256 } 257 258 static List<JPopupMenu> getPopups() { 259 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 260 MenuElement[] p = msm.getSelectedPath(); 261 262 List<JPopupMenu> list = new ArrayList<JPopupMenu>(p.length); 263 for (MenuElement element : p) { 264 if (element instanceof JPopupMenu) { 265 list.add((JPopupMenu) element); 266 } 267 } 268 return list; 269 } 270 271 @SuppressWarnings("deprecation") 272 public boolean isPopupTrigger(MouseEvent e) { 273 return ((e.getID()==MouseEvent.MOUSE_RELEASED) 274 && ((e.getModifiers() & MouseEvent.BUTTON3_MASK)!=0)); 275 } 276 277 private static boolean checkInvokerEqual(MenuElement present, MenuElement last) { 278 Component invokerPresent = present.getComponent(); 279 Component invokerLast = last.getComponent(); 280 281 if (invokerPresent instanceof JPopupMenu) { 282 invokerPresent = ((JPopupMenu)invokerPresent).getInvoker(); 283 } 284 if (invokerLast instanceof JPopupMenu) { 285 invokerLast = ((JPopupMenu)invokerLast).getInvoker(); 286 } 287 return (invokerPresent == invokerLast); 288 } 289 290 291 /** 292 * This Listener fires the Action that provides the correct auditory 293 * feedback. 294 * 295 * @since 1.4 296 */ 297 private class BasicPopupMenuListener implements PopupMenuListener { 298 public void popupMenuCanceled(PopupMenuEvent e) { 299 } 300 301 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 302 } 303 304 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 305 BasicLookAndFeel.playSound((JPopupMenu)e.getSource(), 306 "PopupMenu.popupSound"); 307 } 308 } 309 310 /** 311 * Handles mnemonic for children JMenuItems. 312 * @since 1.5 313 */ 314 private class BasicMenuKeyListener implements MenuKeyListener { 315 MenuElement menuToOpen = null; 316 317 public void menuKeyTyped(MenuKeyEvent e) { 318 if (menuToOpen != null) { 319 // we have a submenu to open 320 JPopupMenu subpopup = ((JMenu)menuToOpen).getPopupMenu(); 321 MenuElement subitem = findEnabledChild( 322 subpopup.getSubElements(), -1, true); 323 324 ArrayList<MenuElement> lst = new ArrayList<MenuElement>(Arrays.asList(e.getPath())); 325 lst.add(menuToOpen); 326 lst.add(subpopup); 327 if (subitem != null) { 328 lst.add(subitem); 329 } 330 MenuElement newPath[] = new MenuElement[0]; 331 newPath = lst.toArray(newPath); 332 MenuSelectionManager.defaultManager().setSelectedPath(newPath); 333 e.consume(); 334 } 335 menuToOpen = null; 336 } 337 338 public void menuKeyPressed(MenuKeyEvent e) { 339 char keyChar = e.getKeyChar(); 340 341 // Handle the case for Escape or Enter... 342 if (!Character.isLetterOrDigit(keyChar)) { 343 return; 344 } 345 346 MenuSelectionManager manager = e.getMenuSelectionManager(); 347 MenuElement path[] = e.getPath(); 348 MenuElement items[] = popupMenu.getSubElements(); 349 int currentIndex = -1; 350 int matches = 0; 351 int firstMatch = -1; 352 int indexes[] = null; 353 354 for (int j = 0; j < items.length; j++) { 355 if (! (items[j] instanceof JMenuItem)) { 356 continue; 357 } 358 JMenuItem item = (JMenuItem)items[j]; 359 int mnemonic = item.getMnemonic(); 360 if (item.isEnabled() && 361 item.isVisible() && lower(keyChar) == lower(mnemonic)) { 362 if (matches == 0) { 363 firstMatch = j; 364 matches++; 365 } else { 366 if (indexes == null) { 367 indexes = new int[items.length]; 368 indexes[0] = firstMatch; 369 } 370 indexes[matches++] = j; 371 } 372 } 373 if (item.isArmed() || item.isSelected()) { 374 currentIndex = matches - 1; 375 } 376 } 377 378 if (matches == 0) { 379 // no op 380 } else if (matches == 1) { 381 // Invoke the menu action 382 JMenuItem item = (JMenuItem)items[firstMatch]; 383 if (item instanceof JMenu) { 384 // submenus are handled in menuKeyTyped 385 menuToOpen = item; 386 } else if (item.isEnabled()) { 387 // we have a menu item 388 manager.clearSelectedPath(); 389 item.doClick(); 390 } 391 e.consume(); 392 } else { 393 // Select the menu item with the matching mnemonic. If 394 // the same mnemonic has been invoked then select the next 395 // menu item in the cycle. 396 MenuElement newItem; 397 398 newItem = items[indexes[(currentIndex + 1) % matches]]; 399 400 MenuElement newPath[] = new MenuElement[path.length+1]; 401 System.arraycopy(path, 0, newPath, 0, path.length); 402 newPath[path.length] = newItem; 403 manager.setSelectedPath(newPath); 404 e.consume(); 405 } 406 } 407 408 public void menuKeyReleased(MenuKeyEvent e) { 409 } 410 411 private char lower(char keyChar) { 412 return Character.toLowerCase(keyChar); 413 } 414 415 private char lower(int mnemonic) { 416 return Character.toLowerCase((char) mnemonic); 417 } 418 } 419 420 private static class Actions extends UIAction { 421 // Types of actions 422 private static final String CANCEL = "cancel"; 423 private static final String SELECT_NEXT = "selectNext"; 424 private static final String SELECT_PREVIOUS = "selectPrevious"; 425 private static final String SELECT_PARENT = "selectParent"; 426 private static final String SELECT_CHILD = "selectChild"; 427 private static final String RETURN = "return"; 428 429 // Used for next/previous actions 430 private static final boolean FORWARD = true; 431 private static final boolean BACKWARD = false; 432 433 // Used for parent/child actions 434 private static final boolean PARENT = false; 435 private static final boolean CHILD = true; 436 437 438 Actions(String key) { 439 super(key); 440 } 441 442 public void actionPerformed(ActionEvent e) { 443 String key = getName(); 444 if (key == CANCEL) { 445 cancel(); 446 } 447 else if (key == SELECT_NEXT) { 448 selectItem(FORWARD); 449 } 450 else if (key == SELECT_PREVIOUS) { 451 selectItem(BACKWARD); 452 } 453 else if (key == SELECT_PARENT) { 454 selectParentChild(PARENT); 455 } 456 else if (key == SELECT_CHILD) { 457 selectParentChild(CHILD); 458 } 459 else if (key == RETURN) { 460 doReturn(); 461 } 462 } 463 464 private void doReturn() { 465 KeyboardFocusManager fmgr = 466 KeyboardFocusManager.getCurrentKeyboardFocusManager(); 467 Component focusOwner = fmgr.getFocusOwner(); 468 if(focusOwner != null && !(focusOwner instanceof JRootPane)) { 469 return; 470 } 471 472 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 473 MenuElement path[] = msm.getSelectedPath(); 474 MenuElement lastElement; 475 if(path.length > 0) { 476 lastElement = path[path.length-1]; 477 if(lastElement instanceof JMenu) { 478 MenuElement newPath[] = new MenuElement[path.length+1]; 479 System.arraycopy(path,0,newPath,0,path.length); 480 newPath[path.length] = ((JMenu)lastElement).getPopupMenu(); 481 msm.setSelectedPath(newPath); 482 } else if(lastElement instanceof JMenuItem) { 483 JMenuItem mi = (JMenuItem)lastElement; 484 485 if (mi.getUI() instanceof BasicMenuItemUI) { 486 ((BasicMenuItemUI)mi.getUI()).doClick(msm); 487 } 488 else { 489 msm.clearSelectedPath(); 490 mi.doClick(0); 491 } 492 } 493 } 494 } 495 private void selectParentChild(boolean direction) { 496 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 497 MenuElement path[] = msm.getSelectedPath(); 498 int len = path.length; 499 500 if (direction == PARENT) { 501 // selecting parent 502 int popupIndex = len-1; 503 504 if (len > 2 && 505 // check if we have an open submenu. A submenu item may or 506 // may not be selected, so submenu popup can be either the 507 // last or next to the last item. 508 (path[popupIndex] instanceof JPopupMenu || 509 path[--popupIndex] instanceof JPopupMenu) && 510 !((JMenu)path[popupIndex-1]).isTopLevelMenu()) { 511 512 // we have a submenu, just close it 513 MenuElement newPath[] = new MenuElement[popupIndex]; 514 System.arraycopy(path, 0, newPath, 0, popupIndex); 515 msm.setSelectedPath(newPath); 516 return; 517 } 518 } else { 519 // selecting child 520 if (len > 0 && path[len-1] instanceof JMenu && 521 !((JMenu)path[len-1]).isTopLevelMenu()) { 522 523 // we have a submenu, open it 524 JMenu menu = (JMenu)path[len-1]; 525 JPopupMenu popup = menu.getPopupMenu(); 526 MenuElement[] subs = popup.getSubElements(); 527 MenuElement item = findEnabledChild(subs, -1, true); 528 MenuElement[] newPath; 529 530 if (item == null) { 531 newPath = new MenuElement[len+1]; 532 } else { 533 newPath = new MenuElement[len+2]; 534 newPath[len+1] = item; 535 } 536 System.arraycopy(path, 0, newPath, 0, len); 537 newPath[len] = popup; 538 msm.setSelectedPath(newPath); 539 return; 540 } 541 } 542 543 // check if we have a toplevel menu selected. 544 // If this is the case, we select another toplevel menu 545 if (len > 1 && path[0] instanceof JMenuBar) { 546 MenuElement currentMenu = path[1]; 547 MenuElement nextMenu = findEnabledChild( 548 path[0].getSubElements(), currentMenu, direction); 549 550 if (nextMenu != null && nextMenu != currentMenu) { 551 MenuElement newSelection[]; 552 if (len == 2) { 553 // menu is selected but its popup not shown 554 newSelection = new MenuElement[2]; 555 newSelection[0] = path[0]; 556 newSelection[1] = nextMenu; 557 } else { 558 // menu is selected and its popup is shown 559 newSelection = new MenuElement[3]; 560 newSelection[0] = path[0]; 561 newSelection[1] = nextMenu; 562 newSelection[2] = ((JMenu)nextMenu).getPopupMenu(); 563 } 564 msm.setSelectedPath(newSelection); 565 } 566 } 567 } 568 569 private void selectItem(boolean direction) { 570 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 571 MenuElement path[] = msm.getSelectedPath(); 572 if (path.length == 0) { 573 return; 574 } 575 int len = path.length; 576 if (len == 1 && path[0] instanceof JPopupMenu) { 577 578 JPopupMenu popup = (JPopupMenu) path[0]; 579 MenuElement[] newPath = new MenuElement[2]; 580 newPath[0] = popup; 581 newPath[1] = findEnabledChild(popup.getSubElements(), -1, direction); 582 msm.setSelectedPath(newPath); 583 } else if (len == 2 && 584 path[0] instanceof JMenuBar && path[1] instanceof JMenu) { 585 586 // a toplevel menu is selected, but its popup not shown. 587 // Show the popup and select the first item 588 JPopupMenu popup = ((JMenu)path[1]).getPopupMenu(); 589 MenuElement next = 590 findEnabledChild(popup.getSubElements(), -1, FORWARD); 591 MenuElement[] newPath; 592 593 if (next != null) { 594 // an enabled item found -- include it in newPath 595 newPath = new MenuElement[4]; 596 newPath[3] = next; 597 } else { 598 // menu has no enabled items -- still must show the popup 599 newPath = new MenuElement[3]; 600 } 601 System.arraycopy(path, 0, newPath, 0, 2); 602 newPath[2] = popup; 603 msm.setSelectedPath(newPath); 604 605 } else if (path[len-1] instanceof JPopupMenu && 606 path[len-2] instanceof JMenu) { 607 608 // a menu (not necessarily toplevel) is open and its popup 609 // shown. Select the appropriate menu item 610 JMenu menu = (JMenu)path[len-2]; 611 JPopupMenu popup = menu.getPopupMenu(); 612 MenuElement next = 613 findEnabledChild(popup.getSubElements(), -1, direction); 614 615 if (next != null) { 616 MenuElement[] newPath = new MenuElement[len+1]; 617 System.arraycopy(path, 0, newPath, 0, len); 618 newPath[len] = next; 619 msm.setSelectedPath(newPath); 620 } else { 621 // all items in the popup are disabled. 622 // We're going to find the parent popup menu and select 623 // its next item. If there's no parent popup menu (i.e. 624 // current menu is toplevel), do nothing 625 if (len > 2 && path[len-3] instanceof JPopupMenu) { 626 popup = ((JPopupMenu)path[len-3]); 627 next = findEnabledChild(popup.getSubElements(), 628 menu, direction); 629 630 if (next != null && next != menu) { 631 MenuElement[] newPath = new MenuElement[len-1]; 632 System.arraycopy(path, 0, newPath, 0, len-2); 633 newPath[len-2] = next; 634 msm.setSelectedPath(newPath); 635 } 636 } 637 } 638 639 } else { 640 // just select the next item, no path expansion needed 641 MenuElement subs[] = path[len-2].getSubElements(); 642 MenuElement nextChild = 643 findEnabledChild(subs, path[len-1], direction); 644 if (nextChild == null) { 645 nextChild = findEnabledChild(subs, -1, direction); 646 } 647 if (nextChild != null) { 648 path[len-1] = nextChild; 649 msm.setSelectedPath(path); 650 } 651 } 652 } 653 654 private void cancel() { 655 // 4234793: This action should call JPopupMenu.firePopupMenuCanceled but it's 656 // a protected method. The real solution could be to make 657 // firePopupMenuCanceled public and call it directly. 658 JPopupMenu lastPopup = getLastPopup(); 659 if (lastPopup != null) { 660 lastPopup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE); 661 } 662 String mode = UIManager.getString("Menu.cancelMode"); 663 if ("hideMenuTree".equals(mode)) { 664 MenuSelectionManager.defaultManager().clearSelectedPath(); 665 } else { 666 shortenSelectedPath(); 667 } 668 } 669 670 private void shortenSelectedPath() { 671 MenuElement path[] = MenuSelectionManager.defaultManager().getSelectedPath(); 672 if (path.length <= 2) { 673 MenuSelectionManager.defaultManager().clearSelectedPath(); 674 return; 675 } 676 // unselect MenuItem and its Popup by default 677 int value = 2; 678 MenuElement lastElement = path[path.length - 1]; 679 JPopupMenu lastPopup = getLastPopup(); 680 if (lastElement == lastPopup) { 681 MenuElement previousElement = path[path.length - 2]; 682 if (previousElement instanceof JMenu) { 683 JMenu lastMenu = (JMenu) previousElement; 684 if (lastMenu.isEnabled() && lastPopup.getComponentCount() > 0) { 685 // unselect the last visible popup only 686 value = 1; 687 } else { 688 // unselect invisible popup and two visible elements 689 value = 3; 690 } 691 } 692 } 693 if (path.length - value <= 2 694 && !UIManager.getBoolean("Menu.preserveTopLevelSelection")) { 695 // clear selection for the topLevelMenu 696 value = path.length; 697 } 698 MenuElement newPath[] = new MenuElement[path.length - value]; 699 System.arraycopy(path, 0, newPath, 0, path.length - value); 700 MenuSelectionManager.defaultManager().setSelectedPath(newPath); 701 } 702 } 703 704 private static MenuElement nextEnabledChild(MenuElement e[], 705 int fromIndex, int toIndex) { 706 for (int i=fromIndex; i<=toIndex; i++) { 707 if (e[i] != null) { 708 Component comp = e[i].getComponent(); 709 if ( comp != null 710 && (comp.isEnabled() || UIManager.getBoolean("MenuItem.disabledAreNavigable")) 711 && comp.isVisible()) { 712 return e[i]; 713 } 714 } 715 } 716 return null; 717 } 718 719 private static MenuElement previousEnabledChild(MenuElement e[], 720 int fromIndex, int toIndex) { 721 for (int i=fromIndex; i>=toIndex; i--) { 722 if (e[i] != null) { 723 Component comp = e[i].getComponent(); 724 if ( comp != null 725 && (comp.isEnabled() || UIManager.getBoolean("MenuItem.disabledAreNavigable")) 726 && comp.isVisible()) { 727 return e[i]; 728 } 729 } 730 } 731 return null; 732 } 733 734 static MenuElement findEnabledChild(MenuElement e[], int fromIndex, 735 boolean forward) { 736 MenuElement result; 737 if (forward) { 738 result = nextEnabledChild(e, fromIndex+1, e.length-1); 739 if (result == null) result = nextEnabledChild(e, 0, fromIndex-1); 740 } else { 741 result = previousEnabledChild(e, fromIndex-1, 0); 742 if (result == null) result = previousEnabledChild(e, e.length-1, 743 fromIndex+1); 744 } 745 return result; 746 } 747 748 static MenuElement findEnabledChild(MenuElement e[], 749 MenuElement elem, boolean forward) { 750 for (int i=0; i<e.length; i++) { 751 if (e[i] == elem) { 752 return findEnabledChild(e, i, forward); 753 } 754 } 755 return null; 756 } 757 758 static class MouseGrabber implements ChangeListener, 759 AWTEventListener, ComponentListener, WindowListener { 760 761 Window grabbedWindow; 762 MenuElement[] lastPathSelected; 763 764 public MouseGrabber() { 765 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 766 msm.addChangeListener(this); 767 this.lastPathSelected = msm.getSelectedPath(); 768 if(this.lastPathSelected.length != 0) { 769 grabWindow(this.lastPathSelected); 770 } 771 } 772 773 void uninstall() { 774 synchronized (MOUSE_GRABBER_KEY) { 775 MenuSelectionManager.defaultManager().removeChangeListener(this); 776 ungrabWindow(); 777 AppContext.getAppContext().remove(MOUSE_GRABBER_KEY); 778 } 779 } 780 781 void grabWindow(MenuElement[] newPath) { 782 // A grab needs to be added 783 final Toolkit tk = Toolkit.getDefaultToolkit(); 784 java.security.AccessController.doPrivileged( 785 new java.security.PrivilegedAction<Object>() { 786 public Object run() { 787 tk.addAWTEventListener(MouseGrabber.this, 788 AWTEvent.MOUSE_EVENT_MASK | 789 AWTEvent.MOUSE_MOTION_EVENT_MASK | 790 AWTEvent.MOUSE_WHEEL_EVENT_MASK | 791 AWTEvent.WINDOW_EVENT_MASK | sun.awt.SunToolkit.GRAB_EVENT_MASK); 792 return null; 793 } 794 } 795 ); 796 797 Component invoker = newPath[0].getComponent(); 798 if (invoker instanceof JPopupMenu) { 799 invoker = ((JPopupMenu)invoker).getInvoker(); 800 } 801 grabbedWindow = (invoker == null) 802 ? null 803 : ((invoker instanceof Window) 804 ? (Window) invoker 805 : SwingUtilities.getWindowAncestor(invoker)); 806 if(grabbedWindow != null) { 807 if(tk instanceof sun.awt.SunToolkit) { 808 ((sun.awt.SunToolkit)tk).grab(grabbedWindow); 809 } else { 810 grabbedWindow.addComponentListener(this); 811 grabbedWindow.addWindowListener(this); 812 } 813 } 814 } 815 816 void ungrabWindow() { 817 final Toolkit tk = Toolkit.getDefaultToolkit(); 818 // The grab should be removed 819 java.security.AccessController.doPrivileged( 820 new java.security.PrivilegedAction<Object>() { 821 public Object run() { 822 tk.removeAWTEventListener(MouseGrabber.this); 823 return null; 824 } 825 } 826 ); 827 realUngrabWindow(); 828 } 829 830 void realUngrabWindow() { 831 Toolkit tk = Toolkit.getDefaultToolkit(); 832 if(grabbedWindow != null) { 833 if(tk instanceof sun.awt.SunToolkit) { 834 ((sun.awt.SunToolkit)tk).ungrab(grabbedWindow); 835 } else { 836 grabbedWindow.removeComponentListener(this); 837 grabbedWindow.removeWindowListener(this); 838 } 839 grabbedWindow = null; 840 } 841 } 842 843 public void stateChanged(ChangeEvent e) { 844 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 845 MenuElement[] p = msm.getSelectedPath(); 846 847 if (lastPathSelected.length == 0 && p.length != 0) { 848 grabWindow(p); 849 } 850 851 if (lastPathSelected.length != 0 && p.length == 0) { 852 ungrabWindow(); 853 } 854 855 lastPathSelected = p; 856 } 857 858 public void eventDispatched(AWTEvent ev) { 859 if(ev instanceof sun.awt.UngrabEvent) { 860 // Popup should be canceled in case of ungrab event 861 cancelPopupMenu( ); 862 return; 863 } 864 if (!(ev instanceof MouseEvent)) { 865 // We are interested in MouseEvents only 866 return; 867 } 868 MouseEvent me = (MouseEvent) ev; 869 Component src = me.getComponent(); 870 switch (me.getID()) { 871 case MouseEvent.MOUSE_PRESSED: 872 if (isInPopup(src) || 873 (src instanceof JMenu && ((JMenu)src).isSelected())) { 874 return; 875 } 876 if (!(src instanceof JComponent) || 877 ! (((JComponent)src).getClientProperty("doNotCancelPopup") 878 == BasicComboBoxUI.HIDE_POPUP_KEY)) { 879 // Cancel popup only if this property was not set. 880 // If this property is set to TRUE component wants 881 // to deal with this event by himself. 882 cancelPopupMenu(); 883 // Ask UIManager about should we consume event that closes 884 // popup. This made to match native apps behaviour. 885 boolean consumeEvent = 886 UIManager.getBoolean("PopupMenu.consumeEventOnClose"); 887 // Consume the event so that normal processing stops. 888 if(consumeEvent && !(src instanceof MenuElement)) { 889 me.consume(); 890 } 891 } 892 break; 893 894 case MouseEvent.MOUSE_RELEASED: 895 if(!(src instanceof MenuElement)) { 896 // Do not forward event to MSM, let component handle it 897 if (isInPopup(src)) { 898 break; 899 } 900 } 901 if(src instanceof JMenu || !(src instanceof JMenuItem)) { 902 MenuSelectionManager.defaultManager(). 903 processMouseEvent(me); 904 } 905 break; 906 case MouseEvent.MOUSE_DRAGGED: 907 if(!(src instanceof MenuElement)) { 908 // For the MOUSE_DRAGGED event the src is 909 // the Component in which mouse button was pressed. 910 // If the src is in popupMenu, 911 // do not forward event to MSM, let component handle it. 912 if (isInPopup(src)) { 913 break; 914 } 915 } 916 MenuSelectionManager.defaultManager(). 917 processMouseEvent(me); 918 break; 919 case MouseEvent.MOUSE_WHEEL: 920 if (isInPopup(src) 921 || ((src instanceof JComboBox) && ((JComboBox) src).isPopupVisible())) { 922 923 return; 924 } 925 cancelPopupMenu(); 926 break; 927 } 928 } 929 930 @SuppressWarnings("deprecation") 931 boolean isInPopup(Component src) { 932 for (Component c=src; c!=null; c=c.getParent()) { 933 if (c instanceof Applet || c instanceof Window) { 934 break; 935 } else if (c instanceof JPopupMenu) { 936 return true; 937 } 938 } 939 return false; 940 } 941 942 void cancelPopupMenu() { 943 // We should ungrab window if a user code throws 944 // an unexpected runtime exception. See 6495920. 945 try { 946 // 4234793: This action should call firePopupMenuCanceled but it's 947 // a protected method. The real solution could be to make 948 // firePopupMenuCanceled public and call it directly. 949 List<JPopupMenu> popups = getPopups(); 950 for (JPopupMenu popup : popups) { 951 popup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE); 952 } 953 MenuSelectionManager.defaultManager().clearSelectedPath(); 954 } catch (RuntimeException ex) { 955 realUngrabWindow(); 956 throw ex; 957 } catch (Error err) { 958 realUngrabWindow(); 959 throw err; 960 } 961 } 962 963 public void componentResized(ComponentEvent e) { 964 cancelPopupMenu(); 965 } 966 public void componentMoved(ComponentEvent e) { 967 cancelPopupMenu(); 968 } 969 public void componentShown(ComponentEvent e) { 970 cancelPopupMenu(); 971 } 972 public void componentHidden(ComponentEvent e) { 973 cancelPopupMenu(); 974 } 975 public void windowClosing(WindowEvent e) { 976 cancelPopupMenu(); 977 } 978 public void windowClosed(WindowEvent e) { 979 cancelPopupMenu(); 980 } 981 public void windowIconified(WindowEvent e) { 982 cancelPopupMenu(); 983 } 984 public void windowDeactivated(WindowEvent e) { 985 cancelPopupMenu(); 986 } 987 public void windowOpened(WindowEvent e) {} 988 public void windowDeiconified(WindowEvent e) {} 989 public void windowActivated(WindowEvent e) {} 990 } 991 992 /** 993 * This helper is added to MenuSelectionManager as a ChangeListener to 994 * listen to menu selection changes. When a menu is activated, it passes 995 * focus to its parent JRootPane, and installs an ActionMap/InputMap pair 996 * on that JRootPane. Those maps are necessary in order for menu 997 * navigation to work. When menu is being deactivated, it restores focus 998 * to the component that has had it before menu activation, and uninstalls 999 * the maps. 1000 * This helper is also installed as a KeyListener on root pane when menu 1001 * is active. It forwards key events to MenuSelectionManager for mnemonic 1002 * keys handling. 1003 */ 1004 static class MenuKeyboardHelper 1005 implements ChangeListener, KeyListener { 1006 1007 private Component lastFocused = null; 1008 private MenuElement[] lastPathSelected = new MenuElement[0]; 1009 private JPopupMenu lastPopup; 1010 1011 private JRootPane invokerRootPane; 1012 private ActionMap menuActionMap = getActionMap(); 1013 private InputMap menuInputMap; 1014 private boolean focusTraversalKeysEnabled; 1015 1016 /* 1017 * Fix for 4213634 1018 * If this is false, KEY_TYPED and KEY_RELEASED events are NOT 1019 * processed. This is needed to avoid activating a menuitem when 1020 * the menu and menuitem share the same mnemonic. 1021 */ 1022 private boolean receivedKeyPressed = false; 1023 1024 void removeItems() { 1025 if (lastFocused != null) { 1026 if(!lastFocused.requestFocusInWindow()) { 1027 // Workarounr for 4810575. 1028 // If lastFocused is not in currently focused window 1029 // requestFocusInWindow will fail. In this case we must 1030 // request focus by requestFocus() if it was not 1031 // transferred from our popup. 1032 Window cfw = KeyboardFocusManager 1033 .getCurrentKeyboardFocusManager() 1034 .getFocusedWindow(); 1035 if(cfw != null && 1036 "###focusableSwingPopup###".equals(cfw.getName())) { 1037 lastFocused.requestFocus(); 1038 } 1039 1040 } 1041 lastFocused = null; 1042 } 1043 if (invokerRootPane != null) { 1044 invokerRootPane.removeKeyListener(this); 1045 invokerRootPane.setFocusTraversalKeysEnabled(focusTraversalKeysEnabled); 1046 removeUIInputMap(invokerRootPane, menuInputMap); 1047 removeUIActionMap(invokerRootPane, menuActionMap); 1048 invokerRootPane = null; 1049 } 1050 receivedKeyPressed = false; 1051 } 1052 1053 private FocusListener rootPaneFocusListener = new FocusAdapter() { 1054 public void focusGained(FocusEvent ev) { 1055 Component opposite = ev.getOppositeComponent(); 1056 if (opposite != null) { 1057 lastFocused = opposite; 1058 } 1059 ev.getComponent().removeFocusListener(this); 1060 } 1061 }; 1062 1063 /** 1064 * Return the last JPopupMenu in <code>path</code>, 1065 * or <code>null</code> if none found 1066 */ 1067 JPopupMenu getActivePopup(MenuElement[] path) { 1068 for (int i=path.length-1; i>=0; i--) { 1069 MenuElement elem = path[i]; 1070 if (elem instanceof JPopupMenu) { 1071 return (JPopupMenu)elem; 1072 } 1073 } 1074 return null; 1075 } 1076 1077 void addUIInputMap(JComponent c, InputMap map) { 1078 InputMap lastNonUI = null; 1079 InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 1080 1081 while (parent != null && !(parent instanceof UIResource)) { 1082 lastNonUI = parent; 1083 parent = parent.getParent(); 1084 } 1085 1086 if (lastNonUI == null) { 1087 c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map); 1088 } else { 1089 lastNonUI.setParent(map); 1090 } 1091 map.setParent(parent); 1092 } 1093 1094 void addUIActionMap(JComponent c, ActionMap map) { 1095 ActionMap lastNonUI = null; 1096 ActionMap parent = c.getActionMap(); 1097 1098 while (parent != null && !(parent instanceof UIResource)) { 1099 lastNonUI = parent; 1100 parent = parent.getParent(); 1101 } 1102 1103 if (lastNonUI == null) { 1104 c.setActionMap(map); 1105 } else { 1106 lastNonUI.setParent(map); 1107 } 1108 map.setParent(parent); 1109 } 1110 1111 void removeUIInputMap(JComponent c, InputMap map) { 1112 InputMap im = null; 1113 InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 1114 1115 while (parent != null) { 1116 if (parent == map) { 1117 if (im == null) { 1118 c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, 1119 map.getParent()); 1120 } else { 1121 im.setParent(map.getParent()); 1122 } 1123 break; 1124 } 1125 im = parent; 1126 parent = parent.getParent(); 1127 } 1128 } 1129 1130 void removeUIActionMap(JComponent c, ActionMap map) { 1131 ActionMap im = null; 1132 ActionMap parent = c.getActionMap(); 1133 1134 while (parent != null) { 1135 if (parent == map) { 1136 if (im == null) { 1137 c.setActionMap(map.getParent()); 1138 } else { 1139 im.setParent(map.getParent()); 1140 } 1141 break; 1142 } 1143 im = parent; 1144 parent = parent.getParent(); 1145 } 1146 } 1147 1148 @SuppressWarnings("deprecation") 1149 public void stateChanged(ChangeEvent ev) { 1150 if (!(UIManager.getLookAndFeel() instanceof BasicLookAndFeel)) { 1151 uninstall(); 1152 return; 1153 } 1154 MenuSelectionManager msm = (MenuSelectionManager)ev.getSource(); 1155 MenuElement[] p = msm.getSelectedPath(); 1156 JPopupMenu popup = getActivePopup(p); 1157 if (popup != null && !popup.isFocusable()) { 1158 // Do nothing for non-focusable popups 1159 return; 1160 } 1161 1162 if (lastPathSelected.length != 0 && p.length != 0 ) { 1163 if (!checkInvokerEqual(p[0],lastPathSelected[0])) { 1164 removeItems(); 1165 lastPathSelected = new MenuElement[0]; 1166 } 1167 } 1168 1169 if (lastPathSelected.length == 0 && p.length > 0) { 1170 // menu posted 1171 JComponent invoker; 1172 1173 if (popup == null) { 1174 if (p.length == 2 && p[0] instanceof JMenuBar && 1175 p[1] instanceof JMenu) { 1176 // a menu has been selected but not open 1177 invoker = (JComponent)p[1]; 1178 popup = ((JMenu)invoker).getPopupMenu(); 1179 } else { 1180 return; 1181 } 1182 } else { 1183 Component c = popup.getInvoker(); 1184 if(c instanceof JFrame) { 1185 invoker = ((JFrame)c).getRootPane(); 1186 } else if(c instanceof JDialog) { 1187 invoker = ((JDialog)c).getRootPane(); 1188 } else if(c instanceof JApplet) { 1189 invoker = ((JApplet)c).getRootPane(); 1190 } else { 1191 while (!(c instanceof JComponent)) { 1192 if (c == null) { 1193 return; 1194 } 1195 c = c.getParent(); 1196 } 1197 invoker = (JComponent)c; 1198 } 1199 } 1200 1201 // remember current focus owner 1202 lastFocused = KeyboardFocusManager. 1203 getCurrentKeyboardFocusManager().getFocusOwner(); 1204 1205 // request focus on root pane and install keybindings 1206 // used for menu navigation 1207 invokerRootPane = SwingUtilities.getRootPane(invoker); 1208 if (invokerRootPane != null) { 1209 invokerRootPane.addFocusListener(rootPaneFocusListener); 1210 invokerRootPane.requestFocus(true); 1211 invokerRootPane.addKeyListener(this); 1212 focusTraversalKeysEnabled = invokerRootPane. 1213 getFocusTraversalKeysEnabled(); 1214 invokerRootPane.setFocusTraversalKeysEnabled(false); 1215 1216 menuInputMap = getInputMap(popup, invokerRootPane); 1217 addUIInputMap(invokerRootPane, menuInputMap); 1218 addUIActionMap(invokerRootPane, menuActionMap); 1219 } 1220 } else if (lastPathSelected.length != 0 && p.length == 0) { 1221 // menu hidden -- return focus to where it had been before 1222 // and uninstall menu keybindings 1223 removeItems(); 1224 } else { 1225 if (popup != lastPopup) { 1226 receivedKeyPressed = false; 1227 } 1228 } 1229 1230 // Remember the last path selected 1231 lastPathSelected = p; 1232 lastPopup = popup; 1233 } 1234 1235 public void keyPressed(KeyEvent ev) { 1236 receivedKeyPressed = true; 1237 MenuSelectionManager.defaultManager().processKeyEvent(ev); 1238 } 1239 1240 public void keyReleased(KeyEvent ev) { 1241 if (receivedKeyPressed) { 1242 receivedKeyPressed = false; 1243 MenuSelectionManager.defaultManager().processKeyEvent(ev); 1244 } 1245 } 1246 1247 public void keyTyped(KeyEvent ev) { 1248 if (receivedKeyPressed) { 1249 MenuSelectionManager.defaultManager().processKeyEvent(ev); 1250 } 1251 } 1252 1253 void uninstall() { 1254 synchronized (MENU_KEYBOARD_HELPER_KEY) { 1255 MenuSelectionManager.defaultManager().removeChangeListener(this); 1256 AppContext.getAppContext().remove(MENU_KEYBOARD_HELPER_KEY); 1257 } 1258 } 1259 } 1260} 1261