1. /*
  2. * @(#)SynthMenuUI.java 1.8 03/01/23
  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 java.awt.*;
  9. import java.awt.event.*;
  10. import java.beans.*;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import javax.swing.plaf.*;
  14. import javax.swing.border.*;
  15. import java.util.Arrays;
  16. import java.util.ArrayList;
  17. /**
  18. * A default L&F implementation of MenuUI. This implementation
  19. * is a "combined" view/controller.
  20. *
  21. * @version 1.8, 01/23/03 (based on BasicMenuUI v 1.148)
  22. * @author Georges Saab
  23. * @author David Karlton
  24. * @author Arnaud Weber
  25. */
  26. class SynthMenuUI extends SynthMenuItemUI implements LazyActionMap.Loader
  27. {
  28. protected ChangeListener changeListener;
  29. protected PropertyChangeListener propertyChangeListener;
  30. protected MenuListener menuListener;
  31. private int lastMnemonic = 0;
  32. /** Uses as the parent of the windowInputMap when selected. */
  33. private InputMap selectedWindowInputMap;
  34. /* diagnostic aids -- should be false for production builds. */
  35. private static final boolean TRACE = false; // trace creates and disposes
  36. private static final boolean VERBOSE = false; // show reuse hits/misses
  37. private static final boolean DEBUG = false; // show bad params, misc.
  38. private static boolean crossMenuMnemonic = true;
  39. public static ComponentUI createUI(JComponent x) {
  40. return new SynthMenuUI();
  41. }
  42. protected void installDefaults() {
  43. super.installDefaults();
  44. ((JMenu)menuItem).setDelay(200);
  45. }
  46. private void fetchStyle(JMenuItem mi) {
  47. SynthContext context = getContext(mi, ENABLED);
  48. // PENDING: crossMenuMnemonic is static, but comes from the defaults,
  49. // resolve this.
  50. crossMenuMnemonic = context.getStyle().getBoolean(context,
  51. "Menu.crossMenuMnemonic", false);
  52. context.dispose();
  53. }
  54. protected String getPropertyPrefix() {
  55. return "Menu";
  56. }
  57. protected void installListeners() {
  58. super.installListeners();
  59. if (changeListener == null)
  60. changeListener = createChangeListener(menuItem);
  61. if (changeListener != null)
  62. menuItem.addChangeListener(changeListener);
  63. if (propertyChangeListener == null)
  64. propertyChangeListener = createPropertyChangeListener(menuItem);
  65. if (propertyChangeListener != null)
  66. menuItem.addPropertyChangeListener(propertyChangeListener);
  67. if (menuListener == null)
  68. menuListener = createMenuListener(menuItem);
  69. if (menuListener != null)
  70. ((JMenu)menuItem).addMenuListener(menuListener);
  71. }
  72. protected void installKeyboardActions() {
  73. super.installKeyboardActions();
  74. updateMnemonicBinding();
  75. }
  76. void updateMnemonicBinding() {
  77. int mnemonic = menuItem.getModel().getMnemonic();
  78. SynthContext context = getContext(menuItem, ENABLED);
  79. int[] shortcutKeys = (int[])context.getStyle().get(context,
  80. "Menu.shortcutKeys");
  81. context.dispose();
  82. if (mnemonic == lastMnemonic || shortcutKeys == null) {
  83. return;
  84. }
  85. if (lastMnemonic != 0 && windowInputMap != null) {
  86. for (int i=0; i<shortcutKeys.length; i++) {
  87. windowInputMap.remove(KeyStroke.getKeyStroke
  88. (lastMnemonic, shortcutKeys[i], false));
  89. }
  90. }
  91. if (mnemonic != 0) {
  92. if (windowInputMap == null) {
  93. windowInputMap = createInputMap(JComponent.
  94. WHEN_IN_FOCUSED_WINDOW);
  95. SwingUtilities.replaceUIInputMap(menuItem, JComponent.
  96. WHEN_IN_FOCUSED_WINDOW, windowInputMap);
  97. }
  98. for (int i=0; i<shortcutKeys.length; i++) {
  99. windowInputMap.put(KeyStroke.getKeyStroke(mnemonic,
  100. shortcutKeys[i], false),
  101. "selectMenu");
  102. }
  103. }
  104. lastMnemonic = mnemonic;
  105. }
  106. protected void uninstallKeyboardActions() {
  107. super.uninstallKeyboardActions();
  108. }
  109. void registerActionMap() {
  110. LazyActionMap.installLazyActionMap(menuItem, this);
  111. }
  112. public void loadActionMap(JComponent c, ActionMap map) {
  113. // Use menu items actions
  114. SynthMenuItemUI.loadActionMap(map);
  115. map.put("selectMenu", new PostAction((JMenu)menuItem, true));
  116. }
  117. protected MouseInputListener createMouseInputListener(JComponent c) {
  118. return new MouseInputHandler();
  119. }
  120. protected MenuListener createMenuListener(JComponent c) {
  121. return null;
  122. }
  123. protected ChangeListener createChangeListener(JComponent c) {
  124. return null;
  125. }
  126. protected PropertyChangeListener createPropertyChangeListener(JComponent c) {
  127. return new PropertyChangeHandler();
  128. }
  129. protected void uninstallDefaults() {
  130. menuItem.setArmed(false);
  131. menuItem.setSelected(false);
  132. menuItem.resetKeyboardActions();
  133. super.uninstallDefaults();
  134. }
  135. protected void uninstallListeners() {
  136. super.uninstallListeners();
  137. if (changeListener != null)
  138. menuItem.removeChangeListener(changeListener);
  139. if (propertyChangeListener != null)
  140. menuItem.removePropertyChangeListener(propertyChangeListener);
  141. if (menuListener != null)
  142. ((JMenu)menuItem).removeMenuListener(menuListener);
  143. changeListener = null;
  144. propertyChangeListener = null;
  145. menuListener = null;
  146. }
  147. protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) {
  148. return new MenuDragMouseHandler();
  149. }
  150. protected MenuKeyListener createMenuKeyListener(JComponent c) {
  151. return new MenuKeyHandler();
  152. }
  153. public Dimension getMaximumSize(JComponent c) {
  154. if (((JMenu)menuItem).isTopLevelMenu() == true) {
  155. Dimension d = c.getPreferredSize();
  156. return new Dimension(d.width, Short.MAX_VALUE);
  157. }
  158. return null;
  159. }
  160. protected void setupPostTimer(JMenu menu) {
  161. Timer timer = new Timer(menu.getDelay(),new PostAction(menu,false));
  162. timer.setRepeats(false);
  163. timer.start();
  164. }
  165. private static void appendPath(MenuElement[] path, MenuElement elem) {
  166. MenuElement newPath[] = new MenuElement[path.length+1];
  167. System.arraycopy(path, 0, newPath, 0, path.length);
  168. newPath[path.length] = elem;
  169. MenuSelectionManager.defaultManager().setSelectedPath(newPath);
  170. }
  171. private static class PostAction extends AbstractAction {
  172. JMenu menu;
  173. boolean force=false;
  174. PostAction(JMenu menu,boolean shouldForce) {
  175. this.menu = menu;
  176. this.force = shouldForce;
  177. }
  178. public void actionPerformed(ActionEvent e) {
  179. if (!crossMenuMnemonic) {
  180. JPopupMenu pm = getActivePopupMenu();
  181. if (pm != null && pm != menu.getParent()) {
  182. return;
  183. }
  184. }
  185. final MenuSelectionManager defaultManager = MenuSelectionManager.defaultManager();
  186. if(force) {
  187. Container cnt = menu.getParent();
  188. if(cnt != null && cnt instanceof JMenuBar) {
  189. MenuElement me[];
  190. MenuElement subElements[];
  191. subElements = menu.getPopupMenu().getSubElements();
  192. if(subElements.length > 0) {
  193. me = new MenuElement[4];
  194. me[0] = (MenuElement) cnt;
  195. me[1] = (MenuElement) menu;
  196. me[2] = (MenuElement) menu.getPopupMenu();
  197. me[3] = subElements[0];
  198. } else {
  199. me = new MenuElement[3];
  200. me[0] = (MenuElement)cnt;
  201. me[1] = menu;
  202. me[2] = (MenuElement) menu.getPopupMenu();
  203. }
  204. defaultManager.setSelectedPath(me);
  205. }
  206. } else {
  207. MenuElement path[] = defaultManager.getSelectedPath();
  208. if(path.length > 0 && path[path.length-1] == menu) {
  209. appendPath(path, menu.getPopupMenu());
  210. }
  211. }
  212. }
  213. public boolean isEnabled() {
  214. return menu.getModel().isEnabled();
  215. }
  216. }
  217. private class PropertyChangeHandler implements PropertyChangeListener {
  218. public void propertyChange(PropertyChangeEvent e) {
  219. String prop = e.getPropertyName();
  220. if(prop.equals(AbstractButton.MNEMONIC_CHANGED_PROPERTY)) {
  221. updateMnemonicBinding();
  222. }
  223. }
  224. }
  225. /**
  226. * Instantiated and used by a menu item to handle the current menu selection
  227. * from mouse events. A MouseInputHandler processes and forwards all mouse events
  228. * to a shared instance of the MenuSelectionManager.
  229. * <p>
  230. * This class is protected so that it can be subclassed by other look and
  231. * feels to implement their own mouse handling behavior. All overridden
  232. * methods should call the parent methods so that the menu selection
  233. * is correct.
  234. *
  235. * @see javax.swing.MenuSelectionManager
  236. * @since 1.4
  237. */
  238. protected class MouseInputHandler implements MouseInputListener {
  239. public void mouseClicked(MouseEvent e) {}
  240. /**
  241. * Invoked when the mouse has been clicked on the menu. This
  242. * method clears or sets the selection path of the
  243. * MenuSelectionManager.
  244. *
  245. * @param e the mouse event
  246. */
  247. public void mousePressed(MouseEvent e) {
  248. JMenu menu = (JMenu)menuItem;
  249. if (!menu.isEnabled())
  250. return;
  251. MenuSelectionManager manager =
  252. MenuSelectionManager.defaultManager();
  253. if(menu.isTopLevelMenu()) {
  254. if(menu.isSelected()) {
  255. manager.clearSelectedPath();
  256. } else {
  257. Container cnt = menu.getParent();
  258. if(cnt != null && cnt instanceof JMenuBar) {
  259. MenuElement me[] = new MenuElement[2];
  260. me[0]=(MenuElement)cnt;
  261. me[1]=menu;
  262. manager.setSelectedPath(me);
  263. }
  264. }
  265. }
  266. MenuElement selectedPath[] = manager.getSelectedPath();
  267. if (selectedPath.length > 0 &&
  268. selectedPath[selectedPath.length-1] != menu.getPopupMenu()) {
  269. if(menu.isTopLevelMenu() ||
  270. menu.getDelay() == 0) {
  271. appendPath(selectedPath, menu.getPopupMenu());
  272. } else {
  273. setupPostTimer(menu);
  274. }
  275. }
  276. }
  277. /**
  278. * Invoked when the mouse has been released on the menu. Delegates the
  279. * mouse event to the MenuSelectionManager.
  280. *
  281. * @param e the mouse event
  282. */
  283. public void mouseReleased(MouseEvent e) {
  284. JMenu menu = (JMenu)menuItem;
  285. if (!menu.isEnabled())
  286. return;
  287. MenuSelectionManager manager =
  288. MenuSelectionManager.defaultManager();
  289. manager.processMouseEvent(e);
  290. if (!e.isConsumed())
  291. manager.clearSelectedPath();
  292. }
  293. /**
  294. * Invoked when the cursor enters the menu. This method sets the selected
  295. * path for the MenuSelectionManager and handles the case
  296. * in which a menu item is used to pop up an additional menu, as in a
  297. * hierarchical menu system.
  298. *
  299. * @param e the mouse event; not used
  300. */
  301. public void mouseEntered(MouseEvent e) {
  302. JMenu menu = (JMenu)menuItem;
  303. if (!menu.isEnabled())
  304. return;
  305. MenuSelectionManager manager =
  306. MenuSelectionManager.defaultManager();
  307. MenuElement selectedPath[] = manager.getSelectedPath();
  308. if (!menu.isTopLevelMenu()) {
  309. if(!(selectedPath.length > 0 &&
  310. selectedPath[selectedPath.length-1] ==
  311. menu.getPopupMenu())) {
  312. if(menu.getDelay() == 0) {
  313. appendPath(getPath(), menu.getPopupMenu());
  314. } else {
  315. manager.setSelectedPath(getPath());
  316. setupPostTimer(menu);
  317. }
  318. }
  319. } else {
  320. if(selectedPath.length > 0 &&
  321. selectedPath[0] == menu.getParent()) {
  322. MenuElement newPath[] = new MenuElement[3];
  323. // A top level menu's parent is by definition
  324. // a JMenuBar
  325. newPath[0] = (MenuElement)menu.getParent();
  326. newPath[1] = menu;
  327. newPath[2] = menu.getPopupMenu();
  328. manager.setSelectedPath(newPath);
  329. }
  330. }
  331. }
  332. public void mouseExited(MouseEvent e) {
  333. }
  334. /**
  335. * Invoked when a mouse button is pressed on the menu and then dragged.
  336. * Delegates the mouse event to the MenuSelectionManager.
  337. *
  338. * @param e the mouse event
  339. * @see java.awt.event.MouseMotionListener#mouseDragged
  340. */
  341. public void mouseDragged(MouseEvent e) {
  342. JMenu menu = (JMenu)menuItem;
  343. if (!menu.isEnabled())
  344. return;
  345. MenuSelectionManager.defaultManager().processMouseEvent(e);
  346. }
  347. public void mouseMoved(MouseEvent e) {
  348. }
  349. }
  350. private class MenuDragMouseHandler implements MenuDragMouseListener {
  351. public void menuDragMouseEntered(MenuDragMouseEvent e) {}
  352. public void menuDragMouseDragged(MenuDragMouseEvent e) {
  353. if (menuItem.isEnabled() == false)
  354. return;
  355. MenuSelectionManager manager = e.getMenuSelectionManager();
  356. MenuElement path[] = e.getPath();
  357. Point p = e.getPoint();
  358. if(p.x >= 0 && p.x < menuItem.getWidth() &&
  359. p.y >= 0 && p.y < menuItem.getHeight()) {
  360. JMenu menu = (JMenu)menuItem;
  361. MenuElement selectedPath[] = manager.getSelectedPath();
  362. if(!(selectedPath.length > 0 &&
  363. selectedPath[selectedPath.length-1] ==
  364. menu.getPopupMenu())) {
  365. if(menu.isTopLevelMenu() ||
  366. menu.getDelay() == 0 ||
  367. e.getID() == MouseEvent.MOUSE_DRAGGED) {
  368. appendPath(path, menu.getPopupMenu());
  369. } else {
  370. manager.setSelectedPath(path);
  371. setupPostTimer(menu);
  372. }
  373. }
  374. } else if(e.getID() == MouseEvent.MOUSE_RELEASED) {
  375. Component comp = manager.componentForPoint(e.getComponent(), e.getPoint());
  376. if (comp == null)
  377. manager.clearSelectedPath();
  378. }
  379. }
  380. public void menuDragMouseExited(MenuDragMouseEvent e) {}
  381. public void menuDragMouseReleased(MenuDragMouseEvent e) {}
  382. }
  383. static JPopupMenu getActivePopupMenu() {
  384. MenuElement[] path = MenuSelectionManager.defaultManager().
  385. getSelectedPath();
  386. for (int i=path.length-1; i>=0; i--) {
  387. MenuElement elem = path[i];
  388. if (elem instanceof JPopupMenu) {
  389. return (JPopupMenu)elem;
  390. }
  391. }
  392. return null;
  393. }
  394. /**
  395. * Handles the mnemonic handling for the JMenu and JMenuItems.
  396. */
  397. private class MenuKeyHandler implements MenuKeyListener {
  398. /**
  399. * Opens the SubMenu
  400. */
  401. public void menuKeyTyped(MenuKeyEvent e) {
  402. if (DEBUG) {
  403. System.out.println("in BasicMenuUI.menuKeyTyped for " + menuItem.getText());
  404. }
  405. if (!crossMenuMnemonic) {
  406. JPopupMenu pm = getActivePopupMenu();
  407. if (pm != null && pm != menuItem.getParent()) {
  408. return;
  409. }
  410. }
  411. int key = menuItem.getMnemonic();
  412. if(key == 0)
  413. return;
  414. MenuElement path[] = e.getPath();
  415. if(lower((char)key) == lower(e.getKeyChar())) {
  416. JPopupMenu popupMenu = ((JMenu)menuItem).getPopupMenu();
  417. ArrayList newList = new ArrayList(Arrays.asList(path));
  418. newList.add(popupMenu);
  419. MenuElement sub[] = popupMenu.getSubElements();
  420. if(sub.length > 0) {
  421. newList.add(sub[0]);
  422. }
  423. MenuSelectionManager manager = e.getMenuSelectionManager();
  424. MenuElement newPath[] = new MenuElement[0];;
  425. newPath = (MenuElement[]) newList.toArray(newPath);
  426. manager.setSelectedPath(newPath);
  427. e.consume();
  428. }
  429. }
  430. /**
  431. * Handles the mnemonics for the menu items. Will also handle duplicate mnemonics.
  432. * Perhaps this should be moved into BasicPopupMenuUI. See 4670831
  433. */
  434. public void menuKeyPressed(MenuKeyEvent e) {
  435. if (DEBUG) {
  436. System.out.println("in BasicMenuUI.menuKeyPressed for " + menuItem.getText());
  437. }
  438. // Handle the case for Escape or Enter...
  439. char keyChar = e.getKeyChar();
  440. if (!Character.isLetterOrDigit(keyChar))
  441. return;
  442. MenuSelectionManager manager = e.getMenuSelectionManager();
  443. MenuElement path[] = e.getPath();
  444. MenuElement selectedPath[] = manager.getSelectedPath();
  445. for (int i = selectedPath.length - 1; i >=0; i--) {
  446. if (selectedPath[i] == menuItem) {
  447. JPopupMenu popupMenu = ((JMenu)menuItem).getPopupMenu();
  448. if(!popupMenu.isVisible()) {
  449. return; // Do not invoke items from invisible popup
  450. }
  451. MenuElement items[] = popupMenu.getSubElements();
  452. MenuElement currentItem = selectedPath[selectedPath.length - 1];
  453. int currentIndex = -1;
  454. int matches = 0;
  455. int firstMatch = -1;
  456. int indexes[] = null;
  457. for (int j = 0; j < items.length; j++) {
  458. int key = ((JMenuItem)items[j]).getMnemonic();
  459. if(lower((char)key) == lower(keyChar)) {
  460. if (matches == 0) {
  461. firstMatch = j;
  462. matches++;
  463. } else {
  464. if (indexes == null) {
  465. indexes = new int[items.length];
  466. indexes[0] = firstMatch;
  467. }
  468. indexes[matches++] = j;
  469. }
  470. }
  471. if (currentItem == items[j]) {
  472. currentIndex = matches - 1;
  473. }
  474. }
  475. if (matches == 0) {
  476. ; // no op (consume)
  477. } else if (matches == 1) {
  478. // Invoke the menu action
  479. JMenuItem item = (JMenuItem)items[firstMatch];
  480. if (!(item instanceof JMenu)) {
  481. // Let Submenus be handled by menuKeyTyped
  482. manager.clearSelectedPath();
  483. item.doClick();
  484. }
  485. } else {
  486. // Select the menu item with the matching mnemonic. If
  487. // the same mnemonic has been invoked then select the next
  488. // menu item in the cycle.
  489. MenuElement newItem = null;
  490. newItem = items[indexes[(currentIndex + 1) % matches]];
  491. MenuElement newPath[] = new MenuElement[path.length+2];
  492. System.arraycopy(path, 0, newPath, 0, path.length);
  493. newPath[path.length] = popupMenu;
  494. newPath[path.length+1] = newItem;
  495. manager.setSelectedPath(newPath);
  496. }
  497. e.consume();
  498. return;
  499. }
  500. }
  501. }
  502. public void menuKeyReleased(MenuKeyEvent e) {}
  503. private char lower(char keyChar) {
  504. return Character.toLowerCase(keyChar);
  505. }
  506. }
  507. }