1. /*
  2. * @(#)SynthPopupMenuUI.java 1.14 03/04/10
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.java.swing.plaf.gtk;
  8. import javax.swing.*;
  9. import javax.swing.event.*;
  10. import javax.swing.plaf.*;
  11. import javax.swing.border.*;
  12. import java.awt.Component;
  13. import java.awt.Container;
  14. import java.awt.Dimension;
  15. import java.awt.Graphics;
  16. import java.awt.KeyboardFocusManager;
  17. import java.awt.Window;
  18. import java.awt.event.*;
  19. import java.beans.PropertyChangeListener;
  20. import java.beans.PropertyChangeEvent;
  21. import java.util.*;
  22. /**
  23. * @version 1.14, 04/10/03 (based on BasicPopupMenuUI v 1.96)
  24. * @author Georges Saab
  25. * @author David Karlton
  26. * @author Arnaud Weber
  27. */
  28. class SynthPopupMenuUI extends PopupMenuUI implements PropertyChangeListener,
  29. SynthUI {
  30. /**
  31. * Maximum size of the text portion of the children menu items.
  32. */
  33. private int maxTextWidth;
  34. /**
  35. * Maximum size of the text for the acclerator portion of the children
  36. * menu items.
  37. */
  38. private int maxAcceleratorWidth;
  39. static boolean menuKeyboardHelperInstalled = false;
  40. static MenuKeyboardHelper menuKeyboardHelper = null;
  41. protected JPopupMenu popupMenu = null;
  42. private SynthStyle style;
  43. private static boolean checkInvokerEqual(MenuElement present, MenuElement last) {
  44. Component invokerPresent = present.getComponent();
  45. Component invokerLast = last.getComponent();
  46. if (invokerPresent instanceof JPopupMenu) {
  47. invokerPresent = ((JPopupMenu)invokerPresent).getInvoker();
  48. }
  49. if (invokerLast instanceof JPopupMenu) {
  50. invokerLast = ((JPopupMenu)invokerLast).getInvoker();
  51. }
  52. return (invokerPresent == invokerLast);
  53. }
  54. public static ComponentUI createUI(JComponent x) {
  55. return new SynthPopupMenuUI();
  56. }
  57. public void installUI(JComponent c) {
  58. popupMenu = (JPopupMenu) c;
  59. installDefaults();
  60. installListeners();
  61. installKeyboardActions();
  62. }
  63. public void installDefaults() {
  64. if (popupMenu.getLayout() == null ||
  65. popupMenu.getLayout() instanceof UIResource) {
  66. popupMenu.setLayout(new DefaultMenuLayout(
  67. popupMenu, BoxLayout.Y_AXIS));
  68. }
  69. fetchStyle(popupMenu);
  70. }
  71. private void fetchStyle(JComponent c) {
  72. SynthContext context = getContext(c, ENABLED);
  73. style = SynthLookAndFeel.updateStyle(context, this);
  74. context.dispose();
  75. }
  76. protected void installListeners() {
  77. if (mouseGrabber == null) {
  78. mouseGrabber = new MouseGrabber();
  79. }
  80. if (basicPopupMenuListener == null) {
  81. basicPopupMenuListener = createPopupMenuListener();
  82. }
  83. popupMenu.addPopupMenuListener(basicPopupMenuListener);
  84. if (!menuKeyboardHelperInstalled) {
  85. if (menuKeyboardHelper == null) {
  86. menuKeyboardHelper = new MenuKeyboardHelper();
  87. }
  88. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  89. msm.addChangeListener(menuKeyboardHelper);
  90. }
  91. popupMenu.addPropertyChangeListener(this);
  92. }
  93. protected void installKeyboardActions() {
  94. }
  95. static InputMap getInputMap(JPopupMenu popup, JComponent c) {
  96. InputMap windowInputMap = null;
  97. PopupMenuUI ui = popup.getUI();
  98. SynthContext context = null;
  99. Object[] bindings = null;
  100. if (ui instanceof SynthPopupMenuUI) {
  101. context = ((SynthPopupMenuUI)ui).getContext(popup, ENABLED);
  102. bindings = (Object[])context.getStyle().get(context,
  103. "PopupMenu.selectedWindowInputMapBindings");
  104. }
  105. if (bindings == null) {
  106. bindings = new Object[] {
  107. "ESCAPE", "cancel",
  108. "DOWN", "selectNext",
  109. "KP_DOWN", "selectNext",
  110. "UP", "selectPrevious",
  111. "KP_UP", "selectPrevious",
  112. "LEFT", "selectParent",
  113. "KP_LEFT", "selectParent",
  114. "RIGHT", "selectChild",
  115. "KP_RIGHT", "selectChild",
  116. "ENTER", "return",
  117. "SPACE", "return"
  118. };
  119. }
  120. windowInputMap = LookAndFeel.makeComponentInputMap(c, bindings);
  121. if (!popup.getComponentOrientation().isLeftToRight()) {
  122. Object[] km = null;
  123. if (context != null) {
  124. km = (Object[])context.getStyle().get(context,
  125. "PopupMenu.selectedWindowInputMapBindings.RightToLeft");
  126. }
  127. if (km == null) {
  128. km = new Object[] {
  129. "LEFT", "selectChild",
  130. "KP_LEFT", "selectChild",
  131. "RIGHT", "selectParent",
  132. "KP_RIGHT", "selectParent",
  133. };
  134. }
  135. InputMap rightToLeftInputMap = LookAndFeel.
  136. makeComponentInputMap(c, km);
  137. rightToLeftInputMap.setParent(windowInputMap);
  138. windowInputMap = rightToLeftInputMap;
  139. }
  140. if (context != null) {
  141. context.dispose();
  142. }
  143. return windowInputMap;
  144. }
  145. static ActionMap getActionMap() {
  146. return LazyActionMap.getActionMap(SynthPopupMenuUI.class,
  147. "PopupMenu.actionMap");
  148. }
  149. public static void loadActionMap(ActionMap map) {
  150. // NOTE: this needs to remain static. If you have a need to
  151. // have Actions that reference the UI in the ActionMap,
  152. // then you'll also need to change the registeration of the
  153. // ActionMap.
  154. // PENDING: need support for actions.
  155. /*
  156. // Set the ActionMap's parent to the Auditory Feedback Action Map
  157. BasicLookAndFeel lf = (SynthLookAndFeel)UIManager.getLookAndFeel();
  158. ActionMap audioMap = lf.getAudioActionMap();
  159. map.setParent(audioMap);
  160. */
  161. map.put("cancel", new CancelAction());
  162. map.put("selectNext",
  163. new SelectNextItemAction(SelectNextItemAction.FORWARD));
  164. map.put("selectPrevious",
  165. new SelectNextItemAction(SelectNextItemAction.BACKWARD));
  166. map.put("selectParent",
  167. new SelectParentChildAction(SelectParentChildAction.PARENT));
  168. map.put("selectChild",
  169. new SelectParentChildAction(SelectParentChildAction.CHILD));
  170. map.put("return", new ReturnAction());
  171. }
  172. public void uninstallUI(JComponent c) {
  173. uninstallDefaults();
  174. uninstallListeners();
  175. uninstallKeyboardActions();
  176. popupMenu = null;
  177. }
  178. protected void uninstallDefaults() {
  179. SynthContext context = getContext(popupMenu, ENABLED);
  180. style.uninstallDefaults(context);
  181. context.dispose();
  182. style = null;
  183. if (popupMenu.getLayout() instanceof UIResource) {
  184. popupMenu.setLayout(null);
  185. }
  186. }
  187. protected void uninstallListeners() {
  188. if (basicPopupMenuListener != null) {
  189. popupMenu.removePopupMenuListener(basicPopupMenuListener);
  190. }
  191. popupMenu.removePropertyChangeListener(this);
  192. }
  193. protected void uninstallKeyboardActions() {
  194. SwingUtilities.replaceUIActionMap(popupMenu, null);
  195. SwingUtilities.replaceUIInputMap(popupMenu,
  196. JComponent.WHEN_IN_FOCUSED_WINDOW, null);
  197. }
  198. public SynthContext getContext(JComponent c) {
  199. return getContext(c, getComponentState(c));
  200. }
  201. private SynthContext getContext(JComponent c, int state) {
  202. return SynthContext.getContext(SynthContext.class, c,
  203. SynthLookAndFeel.getRegion(c), style, state);
  204. }
  205. private Region getRegion(JComponent c) {
  206. return SynthLookAndFeel.getRegion(c);
  207. }
  208. private int getComponentState(JComponent c) {
  209. return SynthLookAndFeel.getComponentState(c);
  210. }
  211. /**
  212. * Resets the max text and accerator widths.
  213. */
  214. void resetAcceleratorWidths() {
  215. maxTextWidth = maxAcceleratorWidth = 0;
  216. }
  217. /**
  218. * Adjusts the width needed to display the maximum menu item string.
  219. *
  220. * @param width Text width.
  221. * @return max width
  222. */
  223. int adjustTextWidth(int width) {
  224. maxTextWidth = Math.max(maxTextWidth, width);
  225. return maxTextWidth;
  226. }
  227. /**
  228. * Adjusts the width needed to display the maximum accelerator.
  229. *
  230. * @param width Text width.
  231. * @return max width
  232. */
  233. int adjustAcceleratorWidth(int width) {
  234. maxAcceleratorWidth = Math.max(maxAcceleratorWidth, width);
  235. return maxAcceleratorWidth;
  236. }
  237. /**
  238. * Maximum size to display text of children menu items.
  239. */
  240. int getMaxTextWidth() {
  241. return maxTextWidth;
  242. }
  243. /**
  244. * Maximum size needed to display accelerators of children menu items.
  245. */
  246. int getMaxAcceleratorWidth() {
  247. return maxAcceleratorWidth;
  248. }
  249. public Dimension getMinimumSize(JComponent c) {
  250. return getPreferredSize(c);
  251. }
  252. public Dimension getPreferredSize(JComponent c) {
  253. return null;
  254. }
  255. public Dimension getMaximumSize(JComponent c) {
  256. return getPreferredSize(c);
  257. }
  258. public void update(Graphics g, JComponent c) {
  259. SynthContext context = getContext(c);
  260. SynthLookAndFeel.update(context, g);
  261. paint(context, g);
  262. context.dispose();
  263. }
  264. public void paint(Graphics g, JComponent c) {
  265. SynthContext context = getContext(c);
  266. paint(context, g);
  267. context.dispose();
  268. }
  269. protected void paint(SynthContext context, Graphics g) {
  270. }
  271. public void propertyChange(PropertyChangeEvent e) {
  272. if (SynthLookAndFeel.shouldUpdateStyle(e)) {
  273. fetchStyle(popupMenu);
  274. }
  275. }
  276. ///////////////////////////////////////////////////////////////////////////////
  277. //// Grab Code
  278. ///////////////////////////////////////////////////////////////////////////////
  279. private static Window getWindow(Component c) {
  280. Component w = c;
  281. while(!(w instanceof Window) && (w!=null)) {
  282. w = w.getParent();
  283. }
  284. return (Window)w;
  285. }
  286. private static MenuElement getFirstPopup() {
  287. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  288. MenuElement[] p = msm.getSelectedPath();
  289. MenuElement me = null;
  290. for(int i = 0 ; me == null && i < p.length ; i++) {
  291. if (p[i] instanceof JPopupMenu)
  292. me = p[i];
  293. }
  294. return me;
  295. }
  296. private static JPopupMenu getLastPopup() {
  297. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  298. MenuElement[] p = msm.getSelectedPath();
  299. JPopupMenu popup = null;
  300. for(int i = p.length - 1; popup == null && i >= 0; i--) {
  301. if (p[i] instanceof JPopupMenu)
  302. popup = (JPopupMenu)p[i];
  303. }
  304. return popup;
  305. }
  306. private static List getPopups() {
  307. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  308. MenuElement[] p = msm.getSelectedPath();
  309. List list = new ArrayList(p.length);
  310. for(int i = 0; i < p.length; i++) {
  311. if (p[i] instanceof JPopupMenu) {
  312. list.add((JPopupMenu)p[i]);
  313. }
  314. }
  315. return list;
  316. }
  317. private transient static MouseGrabber mouseGrabber = null;
  318. private static class MouseGrabber
  319. implements MouseListener, MouseMotionListener, MouseWheelListener,
  320. WindowListener, WindowFocusListener, ComponentListener, ChangeListener {
  321. Vector grabbed = new Vector();
  322. MenuElement lastGrabbed = null;
  323. boolean lastGrabbedMenuBarChild = false;
  324. public MouseGrabber() {
  325. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  326. msm.addChangeListener(this);
  327. }
  328. private void requestAddGrab(Component invoker) {
  329. Window ancestor;
  330. ancestor = getWindow(invoker);
  331. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  332. MenuElement[] p = msm.getSelectedPath();
  333. Component excluded = null;
  334. for(int i = 0 ; excluded == null && i < p.length ; i++) {
  335. if (p[i] instanceof JPopupMenu)
  336. excluded = p[i].getComponent();
  337. }
  338. grabContainer(ancestor, excluded);
  339. }
  340. private void requestRemoveGrab() {
  341. ungrabContainers();
  342. }
  343. void cancelPopupMenu() {
  344. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  345. MenuElement[] p = msm.getSelectedPath();
  346. JPopupMenu firstPopup = (JPopupMenu)getFirstPopup();
  347. if (lastGrabbed == firstPopup) {
  348. // 4234793: This action should call firePopupMenuCanceled but it's
  349. // a protected method. The real solution could be to make
  350. // firePopupMenuCanceled public and call it directly.
  351. List popups = getPopups();
  352. Iterator iter = popups.iterator();
  353. while (iter.hasNext()) {
  354. JPopupMenu popup = (JPopupMenu)iter.next();
  355. popup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE);
  356. }
  357. MenuSelectionManager.defaultManager().clearSelectedPath();
  358. ungrabContainers();
  359. } else {
  360. // The cancel has cause another menu selection
  361. lastGrabbed = firstPopup;
  362. if (p[0] instanceof JMenuBar) {
  363. lastGrabbedMenuBarChild = true;
  364. } else {
  365. lastGrabbedMenuBarChild = false;
  366. }
  367. }
  368. }
  369. MenuElement[] lastPathSelected = new MenuElement[0];
  370. public void stateChanged(ChangeEvent e) {
  371. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  372. MenuElement[] p = msm.getSelectedPath();
  373. if (lastPathSelected.length != 0 && p.length != 0 ) {
  374. if (!checkInvokerEqual(p[0],lastPathSelected[0])) {
  375. requestRemoveGrab();
  376. lastPathSelected = new MenuElement[0];
  377. }
  378. }
  379. if (lastPathSelected.length == 0 &&
  380. p.length != 0) {
  381. // A grab needs to be added
  382. Component invoker = p[0].getComponent();
  383. if (invoker instanceof JPopupMenu)
  384. invoker = ((JPopupMenu)invoker).getInvoker();
  385. requestAddGrab(invoker);
  386. }
  387. if (lastPathSelected.length != 0 &&
  388. p.length == 0) {
  389. // The grab should be removed
  390. requestRemoveGrab();
  391. }
  392. // Switch between menubar children
  393. if (p!=null && p.length>2 && (p[0] instanceof JMenuBar &&
  394. lastGrabbedMenuBarChild == true)) {
  395. if (!(lastGrabbed==getFirstPopup())) {
  396. lastGrabbed=getFirstPopup();
  397. if (p[0] instanceof JMenuBar) {
  398. lastGrabbedMenuBarChild = true;
  399. } else {
  400. lastGrabbedMenuBarChild = false;
  401. }
  402. }
  403. }
  404. // Remember the last path selected
  405. lastPathSelected = p;
  406. }
  407. void grabContainer(Container c, Component excluded) {
  408. if(c == excluded)
  409. return;
  410. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  411. MenuElement[] p = msm.getSelectedPath();
  412. lastGrabbed = getFirstPopup();
  413. if (p[0] instanceof JMenuBar) {
  414. lastGrabbedMenuBarChild = true;
  415. } else {
  416. lastGrabbedMenuBarChild = false;
  417. }
  418. Component ignore = null;
  419. if(c instanceof java.awt.Window) {
  420. ((java.awt.Window)c).addWindowListener(this);
  421. ((java.awt.Window)c).addWindowFocusListener(this);
  422. ((java.awt.Window)c).addComponentListener(this);
  423. grabbed.addElement(c);
  424. }
  425. else if (c instanceof JRootPane) {
  426. // Only add listeners to the glass pane if it listeners,
  427. // otherwise if the glass pane is visible no mouse move
  428. // events will come to us.
  429. ignore = ((JRootPane)c).getGlassPane();
  430. if (ignore.getMouseListeners().length > 0 ||
  431. ignore.getMouseMotionListeners().length > 0 ||
  432. ignore.getMouseWheelListeners().length > 0) {
  433. ignore = null;
  434. }
  435. }
  436. synchronized(c.getTreeLock()) {
  437. int ncomponents = c.getComponentCount();
  438. Component[] component = c.getComponents();
  439. for (int i = 0 ; i < ncomponents ; i++) {
  440. Component comp = component[i];
  441. if(!comp.isVisible())
  442. continue;
  443. if (comp != ignore) {
  444. comp.addMouseListener(this);
  445. comp.addMouseMotionListener(this);
  446. comp.addMouseWheelListener(this);
  447. grabbed.addElement(comp);
  448. }
  449. if (comp instanceof Container) {
  450. Container cont = (Container) comp;
  451. grabContainer(cont, excluded);
  452. }
  453. }
  454. }
  455. }
  456. void ungrabContainers() {
  457. int i,c;
  458. Component cpn;
  459. for(i=0,c=grabbed.size();i<c;i++) {
  460. cpn = (Component)grabbed.elementAt(i);
  461. if(cpn instanceof java.awt.Window) {
  462. ((java.awt.Window)cpn).removeWindowListener(this);
  463. ((java.awt.Window)cpn).removeWindowFocusListener(this);
  464. ((java.awt.Window)cpn).removeComponentListener(this);
  465. } else {
  466. cpn.removeMouseListener(this);
  467. cpn.removeMouseMotionListener(this);
  468. cpn.removeMouseWheelListener(this);
  469. }
  470. }
  471. grabbed = new Vector();
  472. lastGrabbed = null;
  473. lastGrabbedMenuBarChild = false;
  474. }
  475. public void mousePressed(MouseEvent e) {
  476. Component c = (Component)e.getSource();
  477. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  478. cancelPopupMenu();
  479. /*
  480. if(msm.isComponentPartOfCurrentMenu(popupMenu) && msm.isComponentPartOfCurrentMenu(c)) {
  481. return;
  482. } else {
  483. cancelPopupMenu();
  484. }
  485. */
  486. }
  487. public void mouseReleased(MouseEvent e) {
  488. MenuSelectionManager.defaultManager().processMouseEvent(e);
  489. }
  490. public void mouseEntered(MouseEvent e) {
  491. // MenuSelectionManager.defaultManager().processMouseEvent(e);
  492. }
  493. public void mouseExited(MouseEvent e) {
  494. // MenuSelectionManager.defaultManager().processMouseEvent(e);
  495. }
  496. public void mouseMoved(MouseEvent e) {
  497. // MenuSelectionManager.defaultManager().processMouseEvent(e);
  498. }
  499. public void mouseDragged(MouseEvent e) {
  500. MenuSelectionManager.defaultManager().processMouseEvent(e);
  501. }
  502. public void mouseClicked(MouseEvent e) {
  503. }
  504. public void mouseWheelMoved(MouseWheelEvent e) {
  505. cancelPopupMenu();
  506. }
  507. public void componentResized(ComponentEvent e) {
  508. cancelPopupMenu();
  509. }
  510. public void componentMoved(ComponentEvent e) {
  511. cancelPopupMenu();
  512. }
  513. public void componentShown(ComponentEvent e) {
  514. cancelPopupMenu();
  515. }
  516. public void componentHidden(ComponentEvent e) {
  517. cancelPopupMenu();
  518. }
  519. public void windowOpened(WindowEvent e) {}
  520. public void windowClosing(WindowEvent e) {
  521. cancelPopupMenu();
  522. }
  523. public void windowClosed(WindowEvent e) {
  524. cancelPopupMenu();
  525. }
  526. public void windowIconified(WindowEvent e) {
  527. cancelPopupMenu();
  528. }
  529. public void windowDeiconified(WindowEvent e) {}
  530. public void windowActivated(WindowEvent e) {
  531. }
  532. public void windowDeactivated(WindowEvent e) {
  533. }
  534. public void windowLostFocus(WindowEvent e) {
  535. }
  536. public void windowGainedFocus(WindowEvent e) {
  537. }
  538. }
  539. public boolean isPopupTrigger(MouseEvent e) {
  540. return ((e.getID()==MouseEvent.MOUSE_RELEASED)
  541. && ((e.getModifiers() & MouseEvent.BUTTON3_MASK)!=0));
  542. }
  543. // for auditory feedback
  544. private transient PopupMenuListener basicPopupMenuListener = null;
  545. // for auditory feedback
  546. private PopupMenuListener createPopupMenuListener() {
  547. return new BasicPopupMenuListener();
  548. }
  549. /**
  550. * This Listener fires the Action that provides the correct auditory
  551. * feedback.
  552. *
  553. * @since 1.4
  554. */
  555. private class BasicPopupMenuListener implements PopupMenuListener {
  556. public void popupMenuCanceled(PopupMenuEvent e) {
  557. }
  558. public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
  559. }
  560. public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
  561. // Fire the Action that renders the auditory cue.
  562. SynthLookAndFeel.playSound((JComponent)e.getSource(),
  563. "PopupMenu.popupSound");
  564. }
  565. }
  566. private static class CancelAction extends AbstractAction {
  567. public void actionPerformed(ActionEvent e) {
  568. // 4234793: This action should call JPopupMenu.firePopupMenuCanceled but it's
  569. // a protected method. The real solution could be to make
  570. // firePopupMenuCanceled public and call it directly.
  571. JPopupMenu lastPopup = (JPopupMenu)getLastPopup();
  572. if (lastPopup != null) {
  573. lastPopup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE);
  574. }
  575. MenuElement path[] = MenuSelectionManager.defaultManager().getSelectedPath();
  576. if(path.length > 4) { /* PENDING(arnaud) Change this to 2 when a mouse grabber is available for MenuBar */
  577. MenuElement newPath[] = new MenuElement[path.length - 2];
  578. System.arraycopy(path,0,newPath,0,path.length-2);
  579. MenuSelectionManager.defaultManager().setSelectedPath(newPath);
  580. } else
  581. MenuSelectionManager.defaultManager().clearSelectedPath();
  582. }
  583. }
  584. private static class ReturnAction extends AbstractAction {
  585. public void actionPerformed(ActionEvent e) {
  586. KeyboardFocusManager fmgr =
  587. KeyboardFocusManager.getCurrentKeyboardFocusManager();
  588. Component focusOwner = fmgr.getFocusOwner();
  589. if(focusOwner != null && !(focusOwner instanceof JRootPane)) {
  590. return;
  591. }
  592. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  593. MenuElement path[] = msm.getSelectedPath();
  594. MenuElement lastElement;
  595. if(path.length > 0) {
  596. lastElement = path[path.length-1];
  597. if(lastElement instanceof JMenu) {
  598. MenuElement newPath[] = new MenuElement[path.length+1];
  599. System.arraycopy(path,0,newPath,0,path.length);
  600. newPath[path.length] = ((JMenu)lastElement).getPopupMenu();
  601. msm.setSelectedPath(newPath);
  602. } else if(lastElement instanceof JMenuItem) {
  603. JMenuItem mi = (JMenuItem)lastElement;
  604. if (mi.getUI() instanceof SynthMenuItemUI) {
  605. ((SynthMenuItemUI)mi.getUI()).doClick(msm);
  606. }
  607. else {
  608. msm.clearSelectedPath();
  609. mi.doClick(0);
  610. }
  611. }
  612. }
  613. }
  614. }
  615. private static MenuElement nextEnabledChild(MenuElement e[],
  616. int fromIndex, int toIndex) {
  617. for (int i=fromIndex; i<=toIndex; i++) {
  618. if (e[i] != null) {
  619. Component comp = e[i].getComponent();
  620. if (comp != null && comp.isEnabled()) return e[i];
  621. }
  622. }
  623. return null;
  624. }
  625. private static MenuElement previousEnabledChild(MenuElement e[],
  626. int fromIndex, int toIndex) {
  627. for (int i=fromIndex; i>=toIndex; i--) {
  628. if (e[i] != null) {
  629. Component comp = e[i].getComponent();
  630. if (comp != null && comp.isEnabled()) return e[i];
  631. }
  632. }
  633. return null;
  634. }
  635. private static MenuElement findEnabledChild(MenuElement e[], int fromIndex,
  636. boolean forward) {
  637. MenuElement result = null;
  638. if (forward) {
  639. result = nextEnabledChild(e, fromIndex+1, e.length-1);
  640. if (result == null) result = nextEnabledChild(e, 0, fromIndex-1);
  641. } else {
  642. result = previousEnabledChild(e, fromIndex-1, 0);
  643. if (result == null) result = previousEnabledChild(e, e.length-1,
  644. fromIndex+1);
  645. }
  646. return result;
  647. }
  648. private static MenuElement findEnabledChild(MenuElement e[],
  649. MenuElement elem, boolean forward) {
  650. for (int i=0; i<e.length; i++) {
  651. if (e[i] == elem) {
  652. return findEnabledChild(e, i, forward);
  653. }
  654. }
  655. return null;
  656. }
  657. private static class SelectNextItemAction extends AbstractAction {
  658. static boolean FORWARD = true;
  659. static boolean BACKWARD = false;
  660. boolean direction;
  661. SelectNextItemAction(boolean dir) {
  662. direction = dir;
  663. }
  664. public void actionPerformed(ActionEvent e) {
  665. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  666. MenuElement path[] = msm.getSelectedPath();
  667. if (path.length < 2) {
  668. return;
  669. }
  670. int len = path.length;
  671. if (path[0] instanceof JMenuBar &&
  672. path[1] instanceof JMenu && len == 2) {
  673. // a toplevel menu is selected, but its popup not shown.
  674. // Show the popup and select the first item
  675. JPopupMenu popup = ((JMenu)path[1]).getPopupMenu();
  676. MenuElement next =
  677. findEnabledChild(popup.getSubElements(), -1, FORWARD);
  678. MenuElement[] newPath;
  679. if (next != null) {
  680. // an enabled item found -- include it in newPath
  681. newPath = new MenuElement[4];
  682. newPath[3] = next;
  683. } else {
  684. // menu has no enabled items -- still must show the popup
  685. newPath = new MenuElement[3];
  686. }
  687. System.arraycopy(path, 0, newPath, 0, 2);
  688. newPath[2] = popup;
  689. msm.setSelectedPath(newPath);
  690. } else if (path[len-1] instanceof JPopupMenu &&
  691. path[len-2] instanceof JMenu) {
  692. // a menu (not necessarily toplevel) is open and its popup
  693. // shown. Select the appropriate menu item
  694. JMenu menu = (JMenu)path[len-2];
  695. JPopupMenu popup = menu.getPopupMenu();
  696. MenuElement next =
  697. findEnabledChild(popup.getSubElements(), -1, direction);
  698. if (next != null) {
  699. MenuElement[] newPath = new MenuElement[len+1];
  700. System.arraycopy(path, 0, newPath, 0, len);
  701. newPath[len] = next;
  702. msm.setSelectedPath(newPath);
  703. } else {
  704. // all items in the popup are disabled.
  705. // We're going to find the parent popup menu and select
  706. // its next item. If there's no parent popup menu (i.e.
  707. // current menu is toplevel), do nothing
  708. if (len > 2 && path[len-3] instanceof JPopupMenu) {
  709. popup = ((JPopupMenu)path[len-3]);
  710. next = findEnabledChild(popup.getSubElements(),
  711. menu, direction);
  712. if (next != null && next != menu) {
  713. MenuElement[] newPath = new MenuElement[len-1];
  714. System.arraycopy(path, 0, newPath, 0, len-2);
  715. newPath[len-2] = next;
  716. msm.setSelectedPath(newPath);
  717. }
  718. }
  719. }
  720. } else {
  721. // just select the next item, no path expansion needed
  722. MenuElement subs[] = path[len-2].getSubElements();
  723. MenuElement nextChild =
  724. findEnabledChild(subs, path[len-1], direction);
  725. if (nextChild == null) {
  726. nextChild = findEnabledChild(subs, -1, direction);
  727. }
  728. if (nextChild != null) {
  729. path[len-1] = nextChild;
  730. msm.setSelectedPath(path);
  731. }
  732. }
  733. }
  734. }
  735. private static class SelectParentChildAction extends AbstractAction {
  736. static boolean PARENT = false;
  737. static boolean CHILD = true;
  738. boolean direction;
  739. SelectParentChildAction(boolean dir) {
  740. direction = dir;
  741. }
  742. public void actionPerformed(ActionEvent e) {
  743. MenuSelectionManager msm = MenuSelectionManager.defaultManager();
  744. MenuElement path[] = msm.getSelectedPath();
  745. int len = path.length;
  746. if (direction == PARENT) {
  747. // selecting parent
  748. int popupIndex = len-1;
  749. if (len > 2 &&
  750. // check if we have an open submenu. A submenu item may or
  751. // may not be selected, so submenu popup can be either the
  752. // last or next to the last item.
  753. (path[popupIndex] instanceof JPopupMenu ||
  754. path[--popupIndex] instanceof JPopupMenu) &&
  755. !((JMenu)path[popupIndex-1]).isTopLevelMenu()) {
  756. // we have a submenu, just close it
  757. MenuElement newPath[] = new MenuElement[popupIndex];
  758. System.arraycopy(path, 0, newPath, 0, popupIndex);
  759. msm.setSelectedPath(newPath);
  760. return;
  761. }
  762. } else {
  763. // selecting child
  764. if (len > 0 && path[len-1] instanceof JMenu &&
  765. !((JMenu)path[len-1]).isTopLevelMenu()) {
  766. // we have a submenu, open it
  767. JMenu menu = (JMenu)path[len-1];
  768. JPopupMenu popup = menu.getPopupMenu();
  769. MenuElement[] subs = popup.getSubElements();
  770. MenuElement item = findEnabledChild(subs, -1, true);
  771. MenuElement[] newPath;
  772. if (item == null) {
  773. newPath = new MenuElement[len+1];
  774. } else {
  775. newPath = new MenuElement[len+2];
  776. newPath[len+1] = item;
  777. }
  778. System.arraycopy(path, 0, newPath, 0, len);
  779. newPath[len] = popup;
  780. msm.setSelectedPath(newPath);
  781. return;
  782. }
  783. }
  784. // check if we have a toplevel menu selected.
  785. // If this is the case, we select another toplevel menu
  786. if (len > 1 && path[0] instanceof JMenuBar) {
  787. MenuElement currentMenu = path[1];
  788. MenuElement nextMenu = findEnabledChild(
  789. path[0].getSubElements(), currentMenu, direction);
  790. if (nextMenu != null && nextMenu != currentMenu) {
  791. MenuElement newSelection[];
  792. if (len == 2) {
  793. // menu is selected but its popup not shown
  794. newSelection = new MenuElement[2];
  795. newSelection[0] = path[0];
  796. newSelection[1] = nextMenu;
  797. } else {
  798. // menu is selected and its popup is shown
  799. newSelection = new MenuElement[3];
  800. newSelection[0] = path[0];
  801. newSelection[1] = nextMenu;
  802. newSelection[2] = ((JMenu)nextMenu).getPopupMenu();
  803. }
  804. msm.setSelectedPath(newSelection);
  805. }
  806. }
  807. }
  808. }
  809. /**
  810. * This helper is added to MenuSelectionManager as a ChangeListener to
  811. * listen to menu selection changes. When a menu is activated, it passes
  812. * focus to its parent JRootPane, and installs an ActionMap/InputMap pair
  813. * on that JRootPane. Those maps are necessary in order for menu
  814. * navigation to work. When menu is being deactivated, it restores focus
  815. * to the component that has had it before menu activation, and uninstalls
  816. * the maps.
  817. * This helper is also installed as a KeyListener on root pane when menu
  818. * is active. It forwards key events to MenuSelectionManager for mnemonic
  819. * keys handling.
  820. */
  821. private static class MenuKeyboardHelper
  822. implements ChangeListener, KeyListener {
  823. private Component lastFocused = null;
  824. private MenuElement[] lastPathSelected = new MenuElement[0];
  825. private JPopupMenu lastPopup;
  826. private JRootPane invokerRootPane;
  827. private ActionMap menuActionMap = getActionMap();
  828. private InputMap menuInputMap;
  829. private boolean focusTraversalKeysEnabled;
  830. /*
  831. * Fix for 4213634
  832. * If this is false, KEY_TYPED and KEY_RELEASED events are NOT
  833. * processed. This is needed to avoid activating a menuitem when
  834. * the menu and menuitem share the same mnemonic.
  835. */
  836. private boolean receivedKeyPressed = false;
  837. void removeItems() {
  838. if (lastFocused != null) {
  839. if(!lastFocused.requestFocusInWindow()) {
  840. // If lastFocused is not in currently focused window
  841. // requestFocusInWindow will fail. In this case we must
  842. // request focus by requestFocus() if it was not
  843. // transferred from our popup
  844. Window cfw = KeyboardFocusManager
  845. .getCurrentKeyboardFocusManager()
  846. .getFocusedWindow();
  847. if(cfw != null &&
  848. "###focusableSwingPopup###".equals(cfw.getName())) {
  849. lastFocused.requestFocus();
  850. }
  851. }
  852. lastFocused = null;
  853. }
  854. if (invokerRootPane != null) {
  855. invokerRootPane.removeKeyListener(menuKeyboardHelper);
  856. invokerRootPane.setFocusTraversalKeysEnabled(focusTraversalKeysEnabled);
  857. removeUIInputMap(invokerRootPane, menuInputMap);
  858. removeUIActionMap(invokerRootPane, menuActionMap);
  859. invokerRootPane = null;
  860. }
  861. receivedKeyPressed = false;
  862. }
  863. private FocusListener rootPaneFocusListener = new FocusAdapter() {
  864. public void focusGained(FocusEvent ev) {
  865. Component opposite = ev.getOppositeComponent();
  866. if (opposite != null) {
  867. lastFocused = opposite;
  868. }
  869. ev.getComponent().removeFocusListener(this);
  870. }
  871. };
  872. /**
  873. * Return the last JPopupMenu in <code>path</code>,
  874. * or <code>null</code> if none found
  875. */
  876. JPopupMenu getActivePopup(MenuElement[] path) {
  877. for (int i=path.length-1; i>=0; i--) {
  878. MenuElement elem = path[i];
  879. if (elem instanceof JPopupMenu) {
  880. return (JPopupMenu)elem;
  881. }
  882. }
  883. return null;
  884. }
  885. void addUIInputMap(JComponent c, InputMap map) {
  886. InputMap lastNonUI = null;
  887. InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  888. while (parent != null && !(parent instanceof UIResource)) {
  889. lastNonUI = parent;
  890. parent = parent.getParent();
  891. }
  892. if (lastNonUI == null) {
  893. c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map);
  894. } else {
  895. lastNonUI.setParent(map);
  896. }
  897. map.setParent(parent);
  898. }
  899. void addUIActionMap(JComponent c, ActionMap map) {
  900. ActionMap lastNonUI = null;
  901. ActionMap parent = c.getActionMap();
  902. while (parent != null && !(parent instanceof UIResource)) {
  903. lastNonUI = parent;
  904. parent = parent.getParent();
  905. }
  906. if (lastNonUI == null) {
  907. c.setActionMap(map);
  908. } else {
  909. lastNonUI.setParent(map);
  910. }
  911. map.setParent(parent);
  912. }
  913. void removeUIInputMap(JComponent c, InputMap map) {
  914. InputMap im = null;
  915. InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  916. while (parent != null) {
  917. if (parent == map) {
  918. if (im == null) {
  919. c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW,
  920. map.getParent());
  921. } else {
  922. im.setParent(map.getParent());
  923. }
  924. break;
  925. }
  926. im = parent;
  927. parent = parent.getParent();
  928. }
  929. }
  930. void removeUIActionMap(JComponent c, ActionMap map) {
  931. ActionMap im = null;
  932. ActionMap parent = c.getActionMap();
  933. while (parent != null) {
  934. if (parent == map) {
  935. if (im == null) {
  936. c.setActionMap(map.getParent());
  937. } else {
  938. im.setParent(map.getParent());
  939. }
  940. break;
  941. }
  942. im = parent;
  943. parent = parent.getParent();
  944. }
  945. }
  946. public void stateChanged(ChangeEvent ev) {
  947. if (!(UIManager.getLookAndFeel() instanceof SynthLookAndFeel)) {
  948. MenuSelectionManager msm = MenuSelectionManager.
  949. defaultManager();
  950. msm.removeChangeListener(this);
  951. menuKeyboardHelperInstalled = false;
  952. return;
  953. }
  954. MenuSelectionManager msm = (MenuSelectionManager)ev.getSource();
  955. MenuElement[] p = msm.getSelectedPath();
  956. JPopupMenu popup = getActivePopup(p);
  957. if (popup != null && !popup.isFocusable()) {
  958. // Do nothing for non-focusable popups
  959. return;
  960. }
  961. if (lastPathSelected.length != 0 && p.length != 0 ) {
  962. if (!checkInvokerEqual(p[0],lastPathSelected[0])) {
  963. removeItems();
  964. lastPathSelected = new MenuElement[0];
  965. }
  966. }
  967. if (lastPathSelected.length == 0 && p.length > 0) {
  968. // menu posted
  969. JComponent invoker;
  970. if (popup == null) {
  971. if (p.length == 2 && p[0] instanceof JMenuBar &&
  972. p[1] instanceof JMenu) {
  973. // a menu has been selected but not open
  974. invoker = (JComponent)p[1];
  975. popup = ((JMenu)invoker).getPopupMenu();
  976. } else {
  977. return;
  978. }
  979. } else {
  980. Component c = popup.getInvoker();
  981. if(c instanceof JFrame) {
  982. invoker = ((JFrame)c).getRootPane();
  983. } else if(c instanceof JApplet) {
  984. invoker = ((JApplet)c).getRootPane();
  985. } else {
  986. while (!(c instanceof JComponent)) {
  987. if (c == null) {
  988. return;
  989. }
  990. c = c.getParent();
  991. }
  992. invoker = (JComponent)c;
  993. }
  994. }
  995. // remember current focus owner
  996. lastFocused = KeyboardFocusManager.
  997. getCurrentKeyboardFocusManager().getFocusOwner();
  998. // request focus on root pane and install keybindings
  999. // used for menu navigation
  1000. invokerRootPane = SwingUtilities.getRootPane(invoker);
  1001. if (invokerRootPane != null) {
  1002. invokerRootPane.addFocusListener(rootPaneFocusListener);
  1003. invokerRootPane.requestFocus(true);
  1004. invokerRootPane.addKeyListener(menuKeyboardHelper);
  1005. focusTraversalKeysEnabled = invokerRootPane.
  1006. getFocusTraversalKeysEnabled();
  1007. invokerRootPane.setFocusTraversalKeysEnabled(false);
  1008. menuInputMap = getInputMap(popup, invokerRootPane);
  1009. addUIInputMap(invokerRootPane, menuInputMap);
  1010. addUIActionMap(invokerRootPane, menuActionMap);
  1011. }
  1012. } else if (lastPathSelected.length != 0 && p.length == 0) {
  1013. // menu hidden -- return focus to where it had been before
  1014. // and uninstall menu keybindings
  1015. removeItems();
  1016. } else {
  1017. if (popup != lastPopup) {
  1018. receivedKeyPressed = false;
  1019. }
  1020. }
  1021. // Remember the last path selected
  1022. lastPathSelected = p;
  1023. lastPopup = popup;
  1024. }
  1025. public void keyPressed(KeyEvent ev) {
  1026. receivedKeyPressed = true;
  1027. MenuSelectionManager.defaultManager().processKeyEvent(ev);
  1028. }
  1029. public void keyReleased(KeyEvent ev) {
  1030. if (receivedKeyPressed) {
  1031. receivedKeyPressed = false;
  1032. MenuSelectionManager.defaultManager().processKeyEvent(ev);
  1033. }
  1034. }
  1035. public void keyTyped(KeyEvent ev) {
  1036. if (receivedKeyPressed) {
  1037. MenuSelectionManager.defaultManager().processKeyEvent(ev);
  1038. }
  1039. }
  1040. }
  1041. }