1. /*
  2. * @(#)BasicPopupMenuUI.java 1.119 04/04/16
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import javax.swing.*;
  9. import javax.swing.event.*;
  10. import javax.swing.plaf.*;
  11. import javax.swing.plaf.basic.*;
  12. import javax.swing.border.*;
  13. import java.applet.Applet;
  14. import java.awt.Component;
  15. import java.awt.Container;
  16. import java.awt.Dimension;
  17. import java.awt.KeyboardFocusManager;
  18. import java.awt.Window;
  19. import java.awt.event.*;
  20. import java.awt.AWTEvent;
  21. import java.awt.Toolkit;
  22. import java.beans.PropertyChangeListener;
  23. import java.beans.PropertyChangeEvent;
  24. import java.util.*;
  25. import sun.swing.DefaultLookup;
  26. import sun.swing.UIAction;
  27. /**
  28. * A Windows L&F implementation of PopupMenuUI. This implementation
  29. * is a "combined" view/controller.
  30. *
  31. * @version 1.119 04/16/04
  32. * @author Georges Saab
  33. * @author David Karlton
  34. * @author Arnaud Weber
  35. */
  36. public class BasicPopupMenuUI extends PopupMenuUI {
  37. protected JPopupMenu popupMenu = null;
  38. private transient PopupMenuListener popupMenuListener = null;
  39. private MenuKeyListener menuKeyListener = null;
  40. static boolean menuKeyboardHelperInstalled = false;
  41. static MenuKeyboardHelper menuKeyboardHelper = null;
  42. public static ComponentUI createUI(JComponent x) {
  43. return new BasicPopupMenuUI();
  44. }
  45. public BasicPopupMenuUI() {
  46. BasicLookAndFeel.hasPopups = true;
  47. LookAndFeel laf = UIManager.getLookAndFeel();
  48. if (laf instanceof BasicLookAndFeel) {
  49. ((BasicLookAndFeel)laf).createdPopup();
  50. }
  51. }
  52. public void installUI(JComponent c) {
  53. popupMenu = (JPopupMenu) c;
  54. installDefaults();
  55. installListeners();
  56. installKeyboardActions();
  57. }
  58. public void installDefaults() {
  59. if (popupMenu.getLayout() == null ||
  60. popupMenu.getLayout() instanceof UIResource)
  61. popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS));
  62. LookAndFeel.installProperty(popupMenu, "opaque", Boolean.TRUE);
  63. LookAndFeel.installBorder(popupMenu, "PopupMenu.border");
  64. LookAndFeel.installColorsAndFont(popupMenu,
  65. "PopupMenu.background",
  66. "PopupMenu.foreground",
  67. "PopupMenu.font");
  68. }
  69. protected void installListeners() {
  70. if (popupMenuListener == null) {
  71. popupMenuListener = new BasicPopupMenuListener();
  72. }
  73. popupMenu.addPopupMenuListener(popupMenuListener);
  74. if (menuKeyListener == null) {
  75. menuKeyListener = new BasicMenuKeyListener();
  76. }
  77. popupMenu.addMenuKeyListener(menuKeyListener);
  78. if (mouseGrabber == null) {
  79. mouseGrabber = new MouseGrabber();
  80. }
  81. if (!menuKeyboardHelperInstalled) {
  82. if (menuKeyboardHelper == null) {
  83. menuKeyboardHelper = new MenuKeyboardHelper();
  84. }
  85. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  86. msm.addChangeListener(menuKeyboardHelper);
  87. }
  88. }
  89. protected void installKeyboardActions() {
  90. }
  91. static InputMap getInputMap(JPopupMenu popup, JComponent c) {
  92. InputMap windowInputMap = null;
  93. Object[] bindings = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings");
  94. if (bindings != null) {
  95. windowInputMap = LookAndFeel.makeComponentInputMap(c, bindings);
  96. if (!popup.getComponentOrientation().isLeftToRight()) {
  97. Object[] km = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings.RightToLeft");
  98. if (km != null) {
  99. InputMap rightToLeftInputMap = LookAndFeel.makeComponentInputMap(c, km);
  100. rightToLeftInputMap.setParent(windowInputMap);
  101. windowInputMap = rightToLeftInputMap;
  102. }
  103. }
  104. }
  105. return windowInputMap;
  106. }
  107. static ActionMap getActionMap() {
  108. return LazyActionMap.getActionMap(BasicPopupMenuUI.class,
  109. "PopupMenu.actionMap");
  110. }
  111. static void loadActionMap(LazyActionMap map) {
  112. map.put(new Actions(Actions.CANCEL));
  113. map.put(new Actions(Actions.SELECT_NEXT));
  114. map.put(new Actions(Actions.SELECT_PREVIOUS));
  115. map.put(new Actions(Actions.SELECT_PARENT));
  116. map.put(new Actions(Actions.SELECT_CHILD));
  117. map.put(new Actions(Actions.RETURN));
  118. BasicLookAndFeel.installAudioActionMap(map);
  119. }
  120. public void uninstallUI(JComponent c) {
  121. uninstallDefaults();
  122. uninstallListeners();
  123. uninstallKeyboardActions();
  124. popupMenu = null;
  125. }
  126. protected void uninstallDefaults() {
  127. LookAndFeel.uninstallBorder(popupMenu);
  128. }
  129. protected void uninstallListeners() {
  130. if (popupMenuListener != null) {
  131. popupMenu.removePopupMenuListener(popupMenuListener);
  132. }
  133. if (menuKeyListener != null) {
  134. popupMenu.removeMenuKeyListener(menuKeyListener);
  135. }
  136. if(mouseGrabber != null) {
  137. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  138. msm.removeChangeListener(mouseGrabber);
  139. mouseGrabber.ungrabWindow();
  140. mouseGrabber = null;
  141. }
  142. }
  143. protected void uninstallKeyboardActions() {
  144. SwingUtilities.replaceUIActionMap(popupMenu, null);
  145. SwingUtilities.replaceUIInputMap(popupMenu,
  146. JComponent.WHEN_IN_FOCUSED_WINDOW, null);
  147. }
  148. static MenuElement getFirstPopup() {
  149. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  150. MenuElement[] p = msm.getSelectedPath();
  151. MenuElement me = null;
  152. for(int i = 0 ; me == null && i < p.length ; i++) {
  153. if (p[i] instanceof JPopupMenu)
  154. me = p[i];
  155. }
  156. return me;
  157. }
  158. static JPopupMenu getLastPopup() {
  159. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  160. MenuElement[] p = msm.getSelectedPath();
  161. JPopupMenu popup = null;
  162. for(int i = p.length - 1; popup == null && i >= 0; i--) {
  163. if (p[i] instanceof JPopupMenu)
  164. popup = (JPopupMenu)p[i];
  165. }
  166. return popup;
  167. }
  168. static List getPopups() {
  169. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  170. MenuElement[] p = msm.getSelectedPath();
  171. List list = new ArrayList(p.length);
  172. for(int i = 0; i < p.length; i++) {
  173. if (p[i] instanceof JPopupMenu) {
  174. list.add((JPopupMenu)p[i]);
  175. }
  176. }
  177. return list;
  178. }
  179. public boolean isPopupTrigger(MouseEvent e) {
  180. return ((e.getID()==MouseEvent.MOUSE_RELEASED)
  181. && ((e.getModifiers() & MouseEvent.BUTTON3_MASK)!=0));
  182. }
  183. private static boolean checkInvokerEqual(MenuElement present, MenuElement last) {
  184. Component invokerPresent = present.getComponent();
  185. Component invokerLast = last.getComponent();
  186. if (invokerPresent instanceof JPopupMenu) {
  187. invokerPresent = ((JPopupMenu)invokerPresent).getInvoker();
  188. }
  189. if (invokerLast instanceof JPopupMenu) {
  190. invokerLast = ((JPopupMenu)invokerLast).getInvoker();
  191. }
  192. return (invokerPresent == invokerLast);
  193. }
  194. /**
  195. * This Listener fires the Action that provides the correct auditory
  196. * feedback.
  197. *
  198. * @since 1.4
  199. */
  200. private class BasicPopupMenuListener implements PopupMenuListener {
  201. public void popupMenuCanceled(PopupMenuEvent e) {
  202. }
  203. public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
  204. }
  205. public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
  206. BasicLookAndFeel.playSound((JPopupMenu)e.getSource(),
  207. "PopupMenu.popupSound");
  208. }
  209. }
  210. /**
  211. * Handles mnemonic for children JMenuItems.
  212. * @since 1.5
  213. */
  214. private class BasicMenuKeyListener implements MenuKeyListener {
  215. MenuElement menuToOpen = null;
  216. public void menuKeyTyped(MenuKeyEvent e) {
  217. if (menuToOpen != null) {
  218. // we have a submenu to open
  219. JPopupMenu subpopup = ((JMenu)menuToOpen).getPopupMenu();
  220. MenuElement subitem = findEnabledChild(
  221. subpopup.getSubElements(), -1, true);
  222. ArrayList lst = new ArrayList(Arrays.asList(e.getPath()));
  223. lst.add(menuToOpen);
  224. lst.add(subpopup);
  225. if (subitem != null) {
  226. lst.add(subitem);
  227. }
  228. MenuElement newPath[] = new MenuElement[0];;
  229. newPath = (MenuElement[])lst.toArray(newPath);
  230. MenuSelectionManager.defaultManager().setSelectedPath(newPath);
  231. e.consume();
  232. }
  233. menuToOpen = null;
  234. }
  235. public void menuKeyPressed(MenuKeyEvent e) {
  236. // Handle the case for Escape or Enter...
  237. if (!Character.isLetterOrDigit(e.getKeyChar())) {
  238. return;
  239. }
  240. int keyCode = e.getKeyCode();
  241. MenuSelectionManager manager = e.getMenuSelectionManager();
  242. MenuElement path[] = e.getPath();
  243. MenuElement items[] = popupMenu.getSubElements();
  244. int currentIndex = -1;
  245. int matches = 0;
  246. int firstMatch = -1;
  247. int indexes[] = null;
  248. for (int j = 0; j < items.length; j++) {
  249. if (! (items[j] instanceof JMenuItem)) {
  250. continue;
  251. }
  252. JMenuItem item = (JMenuItem)items[j];
  253. if (item.isEnabled() &&
  254. item.isVisible() && keyCode == item.getMnemonic()) {
  255. if (matches == 0) {
  256. firstMatch = j;
  257. matches++;
  258. } else {
  259. if (indexes == null) {
  260. indexes = new int[items.length];
  261. indexes[0] = firstMatch;
  262. }
  263. indexes[matches++] = j;
  264. }
  265. }
  266. if (item.isArmed()) {
  267. currentIndex = matches - 1;
  268. }
  269. }
  270. if (matches == 0) {
  271. ; // no op
  272. } else if (matches == 1) {
  273. // Invoke the menu action
  274. JMenuItem item = (JMenuItem)items[firstMatch];
  275. if (item instanceof JMenu) {
  276. // submenus are handled in menuKeyTyped
  277. menuToOpen = item;
  278. } else if (item.isEnabled()) {
  279. // we have a menu item
  280. manager.clearSelectedPath();
  281. item.doClick();
  282. }
  283. e.consume();
  284. } else {
  285. // Select the menu item with the matching mnemonic. If
  286. // the same mnemonic has been invoked then select the next
  287. // menu item in the cycle.
  288. MenuElement newItem = null;
  289. newItem = items[indexes[(currentIndex + 1) % matches]];
  290. MenuElement newPath[] = new MenuElement[path.length+1];
  291. System.arraycopy(path, 0, newPath, 0, path.length);
  292. newPath[path.length] = newItem;
  293. manager.setSelectedPath(newPath);
  294. e.consume();
  295. }
  296. return;
  297. }
  298. public void menuKeyReleased(MenuKeyEvent e) {
  299. }
  300. }
  301. private static class Actions extends UIAction {
  302. // Types of actions
  303. private static final String CANCEL = "cancel";
  304. private static final String SELECT_NEXT = "selectNext";
  305. private static final String SELECT_PREVIOUS = "selectPrevious";
  306. private static final String SELECT_PARENT = "selectParent";
  307. private static final String SELECT_CHILD = "selectChild";
  308. private static final String RETURN = "return";
  309. // Used for next/previous actions
  310. private static final boolean FORWARD = true;
  311. private static final boolean BACKWARD = false;
  312. // Used for parent/child actions
  313. private static final boolean PARENT = false;
  314. private static final boolean CHILD = true;
  315. Actions(String key) {
  316. super(key);
  317. }
  318. public void actionPerformed(ActionEvent e) {
  319. String key = getName();
  320. if (key == CANCEL) {
  321. cancel();
  322. }
  323. else if (key == SELECT_NEXT) {
  324. selectItem(FORWARD);
  325. }
  326. else if (key == SELECT_PREVIOUS) {
  327. selectItem(BACKWARD);
  328. }
  329. else if (key == SELECT_PARENT) {
  330. selectParentChild(PARENT);
  331. }
  332. else if (key == SELECT_CHILD) {
  333. selectParentChild(CHILD);
  334. }
  335. else if (key == RETURN) {
  336. doReturn();
  337. }
  338. }
  339. private void doReturn() {
  340. KeyboardFocusManager fmgr =
  341. KeyboardFocusManager.getCurrentKeyboardFocusManager();
  342. Component focusOwner = fmgr.getFocusOwner();
  343. if(focusOwner != null && !(focusOwner instanceof JRootPane)) {
  344. return;
  345. }
  346. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  347. MenuElement path[] = msm.getSelectedPath();
  348. MenuElement lastElement;
  349. if(path.length > 0) {
  350. lastElement = path[path.length-1];
  351. if(lastElement instanceof JMenu) {
  352. MenuElement newPath[] = new MenuElement[path.length+1];
  353. System.arraycopy(path,0,newPath,0,path.length);
  354. newPath[path.length] = ((JMenu)lastElement).getPopupMenu();
  355. msm.setSelectedPath(newPath);
  356. } else if(lastElement instanceof JMenuItem) {
  357. JMenuItem mi = (JMenuItem)lastElement;
  358. if (mi.getUI() instanceof BasicMenuItemUI) {
  359. ((BasicMenuItemUI)mi.getUI()).doClick(msm);
  360. }
  361. else {
  362. msm.clearSelectedPath();
  363. mi.doClick(0);
  364. }
  365. }
  366. }
  367. }
  368. private void selectParentChild(boolean direction) {
  369. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  370. MenuElement path[] = msm.getSelectedPath();
  371. int len = path.length;
  372. if (direction == PARENT) {
  373. // selecting parent
  374. int popupIndex = len-1;
  375. if (len > 2 &&
  376. // check if we have an open submenu. A submenu item may or
  377. // may not be selected, so submenu popup can be either the
  378. // last or next to the last item.
  379. (path[popupIndex] instanceof JPopupMenu ||
  380. path[--popupIndex] instanceof JPopupMenu) &&
  381. !((JMenu)path[popupIndex-1]).isTopLevelMenu()) {
  382. // we have a submenu, just close it
  383. MenuElement newPath[] = new MenuElement[popupIndex];
  384. System.arraycopy(path, 0, newPath, 0, popupIndex);
  385. msm.setSelectedPath(newPath);
  386. return;
  387. }
  388. } else {
  389. // selecting child
  390. if (len > 0 && path[len-1] instanceof JMenu &&
  391. !((JMenu)path[len-1]).isTopLevelMenu()) {
  392. // we have a submenu, open it
  393. JMenu menu = (JMenu)path[len-1];
  394. JPopupMenu popup = menu.getPopupMenu();
  395. MenuElement[] subs = popup.getSubElements();
  396. MenuElement item = findEnabledChild(subs, -1, true);
  397. MenuElement[] newPath;
  398. if (item == null) {
  399. newPath = new MenuElement[len+1];
  400. } else {
  401. newPath = new MenuElement[len+2];
  402. newPath[len+1] = item;
  403. }
  404. System.arraycopy(path, 0, newPath, 0, len);
  405. newPath[len] = popup;
  406. msm.setSelectedPath(newPath);
  407. return;
  408. }
  409. }
  410. // check if we have a toplevel menu selected.
  411. // If this is the case, we select another toplevel menu
  412. if (len > 1 && path[0] instanceof JMenuBar) {
  413. MenuElement currentMenu = path[1];
  414. MenuElement nextMenu = findEnabledChild(
  415. path[0].getSubElements(), currentMenu, direction);
  416. if (nextMenu != null && nextMenu != currentMenu) {
  417. MenuElement newSelection[];
  418. if (len == 2) {
  419. // menu is selected but its popup not shown
  420. newSelection = new MenuElement[2];
  421. newSelection[0] = path[0];
  422. newSelection[1] = nextMenu;
  423. } else {
  424. // menu is selected and its popup is shown
  425. newSelection = new MenuElement[3];
  426. newSelection[0] = path[0];
  427. newSelection[1] = nextMenu;
  428. newSelection[2] = ((JMenu)nextMenu).getPopupMenu();
  429. }
  430. msm.setSelectedPath(newSelection);
  431. }
  432. }
  433. }
  434. private void selectItem(boolean direction) {
  435. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  436. MenuElement path[] = msm.getSelectedPath();
  437. if (path.length < 2) {
  438. return;
  439. }
  440. int len = path.length;
  441. if (path[0] instanceof JMenuBar &&
  442. path[1] instanceof JMenu && len == 2) {
  443. // a toplevel menu is selected, but its popup not shown.
  444. // Show the popup and select the first item
  445. JPopupMenu popup = ((JMenu)path[1]).getPopupMenu();
  446. MenuElement next =
  447. findEnabledChild(popup.getSubElements(), -1, FORWARD);
  448. MenuElement[] newPath;
  449. if (next != null) {
  450. // an enabled item found -- include it in newPath
  451. newPath = new MenuElement[4];
  452. newPath[3] = next;
  453. } else {
  454. // menu has no enabled items -- still must show the popup
  455. newPath = new MenuElement[3];
  456. }
  457. System.arraycopy(path, 0, newPath, 0, 2);
  458. newPath[2] = popup;
  459. msm.setSelectedPath(newPath);
  460. } else if (path[len-1] instanceof JPopupMenu &&
  461. path[len-2] instanceof JMenu) {
  462. // a menu (not necessarily toplevel) is open and its popup
  463. // shown. Select the appropriate menu item
  464. JMenu menu = (JMenu)path[len-2];
  465. JPopupMenu popup = menu.getPopupMenu();
  466. MenuElement next =
  467. findEnabledChild(popup.getSubElements(), -1, direction);
  468. if (next != null) {
  469. MenuElement[] newPath = new MenuElement[len+1];
  470. System.arraycopy(path, 0, newPath, 0, len);
  471. newPath[len] = next;
  472. msm.setSelectedPath(newPath);
  473. } else {
  474. // all items in the popup are disabled.
  475. // We're going to find the parent popup menu and select
  476. // its next item. If there's no parent popup menu (i.e.
  477. // current menu is toplevel), do nothing
  478. if (len > 2 && path[len-3] instanceof JPopupMenu) {
  479. popup = ((JPopupMenu)path[len-3]);
  480. next = findEnabledChild(popup.getSubElements(),
  481. menu, direction);
  482. if (next != null && next != menu) {
  483. MenuElement[] newPath = new MenuElement[len-1];
  484. System.arraycopy(path, 0, newPath, 0, len-2);
  485. newPath[len-2] = next;
  486. msm.setSelectedPath(newPath);
  487. }
  488. }
  489. }
  490. } else {
  491. // just select the next item, no path expansion needed
  492. MenuElement subs[] = path[len-2].getSubElements();
  493. MenuElement nextChild =
  494. findEnabledChild(subs, path[len-1], direction);
  495. if (nextChild == null) {
  496. nextChild = findEnabledChild(subs, -1, direction);
  497. }
  498. if (nextChild != null) {
  499. path[len-1] = nextChild;
  500. msm.setSelectedPath(path);
  501. }
  502. }
  503. }
  504. private void cancel() {
  505. // 4234793: This action should call JPopupMenu.firePopupMenuCanceled but it's
  506. // a protected method. The real solution could be to make
  507. // firePopupMenuCanceled public and call it directly.
  508. JPopupMenu lastPopup = (JPopupMenu)getLastPopup();
  509. if (lastPopup != null) {
  510. lastPopup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE);
  511. }
  512. MenuElement path[] = MenuSelectionManager.defaultManager().getSelectedPath();
  513. if(path.length > 4) { /* PENDING(arnaud) Change this to 2 when a mouse grabber is available for MenuBar */
  514. MenuElement newPath[] = new MenuElement[path.length - 2];
  515. System.arraycopy(path,0,newPath,0,path.length-2);
  516. MenuSelectionManager.defaultManager().setSelectedPath(newPath);
  517. } else
  518. MenuSelectionManager.defaultManager().clearSelectedPath();
  519. }
  520. }
  521. private static MenuElement nextEnabledChild(MenuElement e[],
  522. int fromIndex, int toIndex) {
  523. for (int i=fromIndex; i<=toIndex; i++) {
  524. if (e[i] != null) {
  525. Component comp = e[i].getComponent();
  526. if (comp != null && comp.isEnabled() && comp.isVisible()) {
  527. return e[i];
  528. }
  529. }
  530. }
  531. return null;
  532. }
  533. private static MenuElement previousEnabledChild(MenuElement e[],
  534. int fromIndex, int toIndex) {
  535. for (int i=fromIndex; i>=toIndex; i--) {
  536. if (e[i] != null) {
  537. Component comp = e[i].getComponent();
  538. if (comp != null && comp.isEnabled() && comp.isVisible()) {
  539. return e[i];
  540. }
  541. }
  542. }
  543. return null;
  544. }
  545. static MenuElement findEnabledChild(MenuElement e[], int fromIndex,
  546. boolean forward) {
  547. MenuElement result = null;
  548. if (forward) {
  549. result = nextEnabledChild(e, fromIndex+1, e.length-1);
  550. if (result == null) result = nextEnabledChild(e, 0, fromIndex-1);
  551. } else {
  552. result = previousEnabledChild(e, fromIndex-1, 0);
  553. if (result == null) result = previousEnabledChild(e, e.length-1,
  554. fromIndex+1);
  555. }
  556. return result;
  557. }
  558. static MenuElement findEnabledChild(MenuElement e[],
  559. MenuElement elem, boolean forward) {
  560. for (int i=0; i<e.length; i++) {
  561. if (e[i] == elem) {
  562. return findEnabledChild(e, i, forward);
  563. }
  564. }
  565. return null;
  566. }
  567. private transient static MouseGrabber mouseGrabber = null;
  568. private static class MouseGrabber implements ChangeListener,
  569. AWTEventListener, ComponentListener, WindowListener {
  570. Window grabbedWindow;
  571. MenuElement[] lastPathSelected;
  572. public MouseGrabber() {
  573. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  574. msm.addChangeListener(this);
  575. this.lastPathSelected = msm.getSelectedPath();
  576. if(this.lastPathSelected.length != 0) {
  577. grabWindow(this.lastPathSelected);
  578. }
  579. }
  580. void grabWindow(MenuElement[] newPath) {
  581. // A grab needs to be added
  582. java.security.AccessController.doPrivileged(
  583. new java.security.PrivilegedAction() {
  584. public Object run() {
  585. Toolkit.getDefaultToolkit()
  586. .addAWTEventListener(MouseGrabber.this,
  587. AWTEvent.MOUSE_EVENT_MASK |
  588. AWTEvent.MOUSE_MOTION_EVENT_MASK |
  589. AWTEvent.MOUSE_WHEEL_EVENT_MASK);
  590. return null;
  591. }
  592. }
  593. );
  594. Component invoker = newPath[0].getComponent();
  595. if (invoker instanceof JPopupMenu) {
  596. invoker = ((JPopupMenu)invoker).getInvoker();
  597. }
  598. grabbedWindow = invoker instanceof Window?
  599. (Window)invoker :
  600. SwingUtilities.getWindowAncestor(invoker);
  601. if(grabbedWindow != null) {
  602. grabbedWindow.addComponentListener(this);
  603. grabbedWindow.addWindowListener(this);
  604. }
  605. }
  606. void ungrabWindow() {
  607. // The grab should be removed
  608. java.security.AccessController.doPrivileged(
  609. new java.security.PrivilegedAction() {
  610. public Object run() {
  611. Toolkit.getDefaultToolkit()
  612. .removeAWTEventListener(MouseGrabber.this);
  613. return null;
  614. }
  615. }
  616. );
  617. if(grabbedWindow != null) {
  618. grabbedWindow.removeComponentListener(this);
  619. grabbedWindow.removeWindowListener(this);
  620. grabbedWindow = null;
  621. }
  622. }
  623. public void stateChanged(ChangeEvent e) {
  624. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  625. MenuElement[] p = msm.getSelectedPath();
  626. if (lastPathSelected.length == 0 && p.length != 0) {
  627. grabWindow(p);
  628. }
  629. if (lastPathSelected.length != 0 && p.length == 0) {
  630. ungrabWindow();
  631. }
  632. lastPathSelected = p;
  633. }
  634. public void eventDispatched(AWTEvent ev) {
  635. switch (ev.getID()) {
  636. case MouseEvent.MOUSE_PRESSED:
  637. Component src = (Component)ev.getSource();
  638. if (isInPopup(src) ||
  639. (src instanceof JMenu && ((JMenu)src).isSelected())) {
  640. return;
  641. }
  642. if (!(src instanceof JComponent) ||
  643. ! (((JComponent)src).getClientProperty("doNotCancelPopup")
  644. == BasicComboBoxUI.HIDE_POPUP_KEY)) {
  645. // Cancel popup only if this property was not set.
  646. // If this property is set to TRUE component wants
  647. // to deal with this event by himself.
  648. cancelPopupMenu();
  649. // Ask UIManager about should we consume event that closes
  650. // popup. This made to match native apps behaviour.
  651. boolean consumeEvent =
  652. UIManager.getBoolean("PopupMenu.consumeEventOnClose");
  653. // Consume the event so that normal processing stops.
  654. if(consumeEvent && !(src instanceof MenuElement)) {
  655. ((MouseEvent)ev).consume();
  656. }
  657. }
  658. break;
  659. case MouseEvent.MOUSE_RELEASED:
  660. src = (Component)ev.getSource();
  661. if(src instanceof JMenu || !(src instanceof JMenuItem)) {
  662. MenuSelectionManager.defaultManager().
  663. processMouseEvent((MouseEvent)ev);
  664. }
  665. break;
  666. case MouseEvent.MOUSE_DRAGGED:
  667. MenuSelectionManager.defaultManager().
  668. processMouseEvent((MouseEvent)ev);
  669. break;
  670. case MouseEvent.MOUSE_WHEEL:
  671. if (isInPopup((Component)ev.getSource())) {
  672. return;
  673. }
  674. cancelPopupMenu();
  675. break;
  676. }
  677. }
  678. boolean isInPopup(Component src) {
  679. for (Component c=src; c!=null; c=c.getParent()) {
  680. if (c instanceof Applet || c instanceof Window) {
  681. break;
  682. } else if (c instanceof JPopupMenu) {
  683. return true;
  684. }
  685. }
  686. return false;
  687. }
  688. void cancelPopupMenu() {
  689. JPopupMenu firstPopup = (JPopupMenu)getFirstPopup();
  690. // 4234793: This action should call firePopupMenuCanceled but it's
  691. // a protected method. The real solution could be to make
  692. // firePopupMenuCanceled public and call it directly.
  693. List popups = getPopups();
  694. Iterator iter = popups.iterator();
  695. while (iter.hasNext()) {
  696. JPopupMenu popup = (JPopupMenu)iter.next();
  697. popup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE);
  698. }
  699. MenuSelectionManager.defaultManager().clearSelectedPath();
  700. }
  701. public void componentResized(ComponentEvent e) {
  702. cancelPopupMenu();
  703. }
  704. public void componentMoved(ComponentEvent e) {
  705. cancelPopupMenu();
  706. }
  707. public void componentShown(ComponentEvent e) {
  708. cancelPopupMenu();
  709. }
  710. public void componentHidden(ComponentEvent e) {
  711. cancelPopupMenu();
  712. }
  713. public void windowClosing(WindowEvent e) {
  714. cancelPopupMenu();
  715. }
  716. public void windowClosed(WindowEvent e) {
  717. cancelPopupMenu();
  718. }
  719. public void windowIconified(WindowEvent e) {
  720. cancelPopupMenu();
  721. }
  722. public void windowDeactivated(WindowEvent e) {
  723. cancelPopupMenu();
  724. }
  725. public void windowOpened(WindowEvent e) {}
  726. public void windowDeiconified(WindowEvent e) {}
  727. public void windowActivated(WindowEvent e) {}
  728. }
  729. /**
  730. * This helper is added to MenuSelectionManager as a ChangeListener to
  731. * listen to menu selection changes. When a menu is activated, it passes
  732. * focus to its parent JRootPane, and installs an ActionMap/InputMap pair
  733. * on that JRootPane. Those maps are necessary in order for menu
  734. * navigation to work. When menu is being deactivated, it restores focus
  735. * to the component that has had it before menu activation, and uninstalls
  736. * the maps.
  737. * This helper is also installed as a KeyListener on root pane when menu
  738. * is active. It forwards key events to MenuSelectionManager for mnemonic
  739. * keys handling.
  740. */
  741. private static class MenuKeyboardHelper
  742. implements ChangeListener, KeyListener {
  743. private Component lastFocused = null;
  744. private MenuElement[] lastPathSelected = new MenuElement[0];
  745. private JPopupMenu lastPopup;
  746. private JRootPane invokerRootPane;
  747. private ActionMap menuActionMap = getActionMap();
  748. private InputMap menuInputMap;
  749. private boolean focusTraversalKeysEnabled;
  750. /*
  751. * Fix for 4213634
  752. * If this is false, KEY_TYPED and KEY_RELEASED events are NOT
  753. * processed. This is needed to avoid activating a menuitem when
  754. * the menu and menuitem share the same mnemonic.
  755. */
  756. private boolean receivedKeyPressed = false;
  757. void removeItems() {
  758. if (lastFocused != null) {
  759. if(!lastFocused.requestFocusInWindow()) {
  760. // Workarounr for 4810575.
  761. // If lastFocused is not in currently focused window
  762. // requestFocusInWindow will fail. In this case we must
  763. // request focus by requestFocus() if it was not
  764. // transferred from our popup.
  765. Window cfw = KeyboardFocusManager
  766. .getCurrentKeyboardFocusManager()
  767. .getFocusedWindow();
  768. if(cfw != null &&
  769. "###focusableSwingPopup###".equals(cfw.getName())) {
  770. lastFocused.requestFocus();
  771. }
  772. }
  773. lastFocused = null;
  774. }
  775. if (invokerRootPane != null) {
  776. invokerRootPane.removeKeyListener(menuKeyboardHelper);
  777. invokerRootPane.setFocusTraversalKeysEnabled(focusTraversalKeysEnabled);
  778. removeUIInputMap(invokerRootPane, menuInputMap);
  779. removeUIActionMap(invokerRootPane, menuActionMap);
  780. invokerRootPane = null;
  781. }
  782. receivedKeyPressed = false;
  783. }
  784. private FocusListener rootPaneFocusListener = new FocusAdapter() {
  785. public void focusGained(FocusEvent ev) {
  786. Component opposite = ev.getOppositeComponent();
  787. if (opposite != null) {
  788. lastFocused = opposite;
  789. }
  790. ev.getComponent().removeFocusListener(this);
  791. }
  792. };
  793. /**
  794. * Return the last JPopupMenu in <code>path</code>,
  795. * or <code>null</code> if none found
  796. */
  797. JPopupMenu getActivePopup(MenuElement[] path) {
  798. for (int i=path.length-1; i>=0; i--) {
  799. MenuElement elem = path[i];
  800. if (elem instanceof JPopupMenu) {
  801. return (JPopupMenu)elem;
  802. }
  803. }
  804. return null;
  805. }
  806. void addUIInputMap(JComponent c, InputMap map) {
  807. InputMap lastNonUI = null;
  808. InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  809. while (parent != null && !(parent instanceof UIResource)) {
  810. lastNonUI = parent;
  811. parent = parent.getParent();
  812. }
  813. if (lastNonUI == null) {
  814. c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map);
  815. } else {
  816. lastNonUI.setParent(map);
  817. }
  818. map.setParent(parent);
  819. }
  820. void addUIActionMap(JComponent c, ActionMap map) {
  821. ActionMap lastNonUI = null;
  822. ActionMap parent = c.getActionMap();
  823. while (parent != null && !(parent instanceof UIResource)) {
  824. lastNonUI = parent;
  825. parent = parent.getParent();
  826. }
  827. if (lastNonUI == null) {
  828. c.setActionMap(map);
  829. } else {
  830. lastNonUI.setParent(map);
  831. }
  832. map.setParent(parent);
  833. }
  834. void removeUIInputMap(JComponent c, InputMap map) {
  835. InputMap im = null;
  836. InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  837. while (parent != null) {
  838. if (parent == map) {
  839. if (im == null) {
  840. c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW,
  841. map.getParent());
  842. } else {
  843. im.setParent(map.getParent());
  844. }
  845. break;
  846. }
  847. im = parent;
  848. parent = parent.getParent();
  849. }
  850. }
  851. void removeUIActionMap(JComponent c, ActionMap map) {
  852. ActionMap im = null;
  853. ActionMap parent = c.getActionMap();
  854. while (parent != null) {
  855. if (parent == map) {
  856. if (im == null) {
  857. c.setActionMap(map.getParent());
  858. } else {
  859. im.setParent(map.getParent());
  860. }
  861. break;
  862. }
  863. im = parent;
  864. parent = parent.getParent();
  865. }
  866. }
  867. public void stateChanged(ChangeEvent ev) {
  868. if (!(UIManager.getLookAndFeel() instanceof BasicLookAndFeel)) {
  869. MenuSelectionManager msm = MenuSelectionManager.
  870. defaultManager();
  871. msm.removeChangeListener(this);
  872. menuKeyboardHelperInstalled = false;
  873. return;
  874. }
  875. MenuSelectionManager msm = (MenuSelectionManager)ev.getSource();
  876. MenuElement[] p = msm.getSelectedPath();
  877. JPopupMenu popup = getActivePopup(p);
  878. if (popup != null && !popup.isFocusable()) {
  879. // Do nothing for non-focusable popups
  880. return;
  881. }
  882. if (lastPathSelected.length != 0 && p.length != 0 ) {
  883. if (!checkInvokerEqual(p[0],lastPathSelected[0])) {
  884. removeItems();
  885. lastPathSelected = new MenuElement[0];
  886. }
  887. }
  888. if (lastPathSelected.length == 0 && p.length > 0) {
  889. // menu posted
  890. JComponent invoker;
  891. if (popup == null) {
  892. if (p.length == 2 && p[0] instanceof JMenuBar &&
  893. p[1] instanceof JMenu) {
  894. // a menu has been selected but not open
  895. invoker = (JComponent)p[1];
  896. popup = ((JMenu)invoker).getPopupMenu();
  897. } else {
  898. return;
  899. }
  900. } else {
  901. Component c = popup.getInvoker();
  902. if(c instanceof JFrame) {
  903. invoker = ((JFrame)c).getRootPane();
  904. } else if(c instanceof JApplet) {
  905. invoker = ((JApplet)c).getRootPane();
  906. } else {
  907. while (!(c instanceof JComponent)) {
  908. if (c == null) {
  909. return;
  910. }
  911. c = c.getParent();
  912. }
  913. invoker = (JComponent)c;
  914. }
  915. }
  916. // remember current focus owner
  917. lastFocused = KeyboardFocusManager.
  918. getCurrentKeyboardFocusManager().getFocusOwner();
  919. // request focus on root pane and install keybindings
  920. // used for menu navigation
  921. invokerRootPane = SwingUtilities.getRootPane(invoker);
  922. if (invokerRootPane != null) {
  923. invokerRootPane.addFocusListener(rootPaneFocusListener);
  924. invokerRootPane.requestFocus(true);
  925. invokerRootPane.addKeyListener(menuKeyboardHelper);
  926. focusTraversalKeysEnabled = invokerRootPane.
  927. getFocusTraversalKeysEnabled();
  928. invokerRootPane.setFocusTraversalKeysEnabled(false);
  929. menuInputMap = getInputMap(popup, invokerRootPane);
  930. addUIInputMap(invokerRootPane, menuInputMap);
  931. addUIActionMap(invokerRootPane, menuActionMap);
  932. }
  933. } else if (lastPathSelected.length != 0 && p.length == 0) {
  934. // menu hidden -- return focus to where it had been before
  935. // and uninstall menu keybindings
  936. removeItems();
  937. } else {
  938. if (popup != lastPopup) {
  939. receivedKeyPressed = false;
  940. }
  941. }
  942. // Remember the last path selected
  943. lastPathSelected = p;
  944. lastPopup = popup;
  945. }
  946. public void keyPressed(KeyEvent ev) {
  947. receivedKeyPressed = true;
  948. MenuSelectionManager.defaultManager().processKeyEvent(ev);
  949. }
  950. public void keyReleased(KeyEvent ev) {
  951. if (receivedKeyPressed) {
  952. receivedKeyPressed = false;
  953. MenuSelectionManager.defaultManager().processKeyEvent(ev);
  954. }
  955. }
  956. public void keyTyped(KeyEvent ev) {
  957. if (receivedKeyPressed) {
  958. MenuSelectionManager.defaultManager().processKeyEvent(ev);
  959. }
  960. }
  961. }
  962. }