1. /*
  2. * @(#)BasicMenuUI.java 1.158 04/02/26
  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 sun.swing.DefaultLookup;
  9. import sun.swing.UIAction;
  10. import java.awt.*;
  11. import java.awt.event.*;
  12. import java.beans.*;
  13. import javax.swing.*;
  14. import javax.swing.event.*;
  15. import javax.swing.plaf.*;
  16. import javax.swing.border.*;
  17. import java.util.Arrays;
  18. import java.util.ArrayList;
  19. /**
  20. * A default L&F implementation of MenuUI. This implementation
  21. * is a "combined" view/controller.
  22. *
  23. * @version 1.158 02/26/04
  24. * @author Georges Saab
  25. * @author David Karlton
  26. * @author Arnaud Weber
  27. */
  28. public class BasicMenuUI extends BasicMenuItemUI
  29. {
  30. protected ChangeListener changeListener;
  31. protected PropertyChangeListener propertyChangeListener;
  32. protected MenuListener menuListener;
  33. private int lastMnemonic = 0;
  34. /** Uses as the parent of the windowInputMap when selected. */
  35. private InputMap selectedWindowInputMap;
  36. /* diagnostic aids -- should be false for production builds. */
  37. private static final boolean TRACE = false; // trace creates and disposes
  38. private static final boolean VERBOSE = false; // show reuse hits/misses
  39. private static final boolean DEBUG = false; // show bad params, misc.
  40. private static boolean crossMenuMnemonic = true;
  41. public static ComponentUI createUI(JComponent x) {
  42. return new BasicMenuUI();
  43. }
  44. static void loadActionMap(LazyActionMap map) {
  45. BasicMenuItemUI.loadActionMap(map);
  46. map.put(new Actions(Actions.SELECT, null, true));
  47. }
  48. protected void installDefaults() {
  49. super.installDefaults();
  50. updateDefaultBackgroundColor();
  51. ((JMenu)menuItem).setDelay(200);
  52. crossMenuMnemonic = UIManager.getBoolean("Menu.crossMenuMnemonic");
  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 installLazyActionMap() {
  77. LazyActionMap.installLazyActionMap(menuItem, BasicMenuUI.class,
  78. getPropertyPrefix() + ".actionMap");
  79. }
  80. void updateMnemonicBinding() {
  81. int mnemonic = menuItem.getModel().getMnemonic();
  82. int[] shortcutKeys = (int[])DefaultLookup.get(menuItem, this,
  83. "Menu.shortcutKeys");
  84. if (shortcutKeys == null) {
  85. shortcutKeys = new int[] {KeyEvent.ALT_MASK};
  86. }
  87. if (mnemonic == lastMnemonic) {
  88. return;
  89. }
  90. InputMap windowInputMap = SwingUtilities.getUIInputMap(
  91. menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
  92. if (lastMnemonic != 0 && windowInputMap != null) {
  93. for (int i=0; i<shortcutKeys.length; i++) {
  94. windowInputMap.remove(KeyStroke.getKeyStroke
  95. (lastMnemonic, shortcutKeys[i], false));
  96. }
  97. }
  98. if (mnemonic != 0) {
  99. if (windowInputMap == null) {
  100. windowInputMap = createInputMap(JComponent.
  101. WHEN_IN_FOCUSED_WINDOW);
  102. SwingUtilities.replaceUIInputMap(menuItem, JComponent.
  103. WHEN_IN_FOCUSED_WINDOW, windowInputMap);
  104. }
  105. for (int i=0; i<shortcutKeys.length; i++) {
  106. windowInputMap.put(KeyStroke.getKeyStroke(mnemonic,
  107. shortcutKeys[i], false),
  108. "selectMenu");
  109. }
  110. }
  111. lastMnemonic = mnemonic;
  112. }
  113. protected void uninstallKeyboardActions() {
  114. super.uninstallKeyboardActions();
  115. lastMnemonic = 0;
  116. }
  117. protected MouseInputListener createMouseInputListener(JComponent c) {
  118. return getHandler();
  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 getHandler();
  128. }
  129. BasicMenuItemUI.Handler getHandler() {
  130. if (handler == null) {
  131. handler = new Handler();
  132. }
  133. return handler;
  134. }
  135. protected void uninstallDefaults() {
  136. menuItem.setArmed(false);
  137. menuItem.setSelected(false);
  138. menuItem.resetKeyboardActions();
  139. super.uninstallDefaults();
  140. }
  141. protected void uninstallListeners() {
  142. super.uninstallListeners();
  143. if (changeListener != null)
  144. menuItem.removeChangeListener(changeListener);
  145. if (propertyChangeListener != null)
  146. menuItem.removePropertyChangeListener(propertyChangeListener);
  147. if (menuListener != null)
  148. ((JMenu)menuItem).removeMenuListener(menuListener);
  149. changeListener = null;
  150. propertyChangeListener = null;
  151. menuListener = null;
  152. handler = null;
  153. }
  154. protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) {
  155. return getHandler();
  156. }
  157. protected MenuKeyListener createMenuKeyListener(JComponent c) {
  158. return (MenuKeyListener)getHandler();
  159. }
  160. public Dimension getMaximumSize(JComponent c) {
  161. if (((JMenu)menuItem).isTopLevelMenu() == true) {
  162. Dimension d = c.getPreferredSize();
  163. return new Dimension(d.width, Short.MAX_VALUE);
  164. }
  165. return null;
  166. }
  167. protected void setupPostTimer(JMenu menu) {
  168. Timer timer = new Timer(menu.getDelay(), new Actions(
  169. Actions.SELECT, menu,false));
  170. timer.setRepeats(false);
  171. timer.start();
  172. }
  173. private static void appendPath(MenuElement[] path, MenuElement elem) {
  174. MenuElement newPath[] = new MenuElement[path.length+1];
  175. System.arraycopy(path, 0, newPath, 0, path.length);
  176. newPath[path.length] = elem;
  177. MenuSelectionManager.defaultManager().setSelectedPath(newPath);
  178. }
  179. private static class Actions extends UIAction {
  180. private static final String SELECT = "selectMenu";
  181. // NOTE: This will be null if the action is registered in the
  182. // ActionMap. For the timer use it will be non-null.
  183. private JMenu menu;
  184. private boolean force=false;
  185. Actions(String key, JMenu menu, boolean shouldForce) {
  186. super(key);
  187. this.menu = menu;
  188. this.force = shouldForce;
  189. }
  190. private JMenu getMenu(ActionEvent e) {
  191. if (e.getSource() instanceof JMenu) {
  192. return (JMenu)e.getSource();
  193. }
  194. return menu;
  195. }
  196. public void actionPerformed(ActionEvent e) {
  197. JMenu menu = getMenu(e);
  198. if (!crossMenuMnemonic) {
  199. JPopupMenu pm = BasicPopupMenuUI.getLastPopup();
  200. if (pm != null && pm != menu.getParent()) {
  201. return;
  202. }
  203. }
  204. final MenuSelectionManager defaultManager = MenuSelectionManager.defaultManager();
  205. if(force) {
  206. Container cnt = menu.getParent();
  207. if(cnt != null && cnt instanceof JMenuBar) {
  208. MenuElement me[];
  209. MenuElement subElements[];
  210. subElements = menu.getPopupMenu().getSubElements();
  211. if(subElements.length > 0) {
  212. me = new MenuElement[4];
  213. me[0] = (MenuElement) cnt;
  214. me[1] = (MenuElement) menu;
  215. me[2] = (MenuElement) menu.getPopupMenu();
  216. me[3] = subElements[0];
  217. } else {
  218. me = new MenuElement[3];
  219. me[0] = (MenuElement)cnt;
  220. me[1] = menu;
  221. me[2] = (MenuElement) menu.getPopupMenu();
  222. }
  223. defaultManager.setSelectedPath(me);
  224. }
  225. } else {
  226. MenuElement path[] = defaultManager.getSelectedPath();
  227. if(path.length > 0 && path[path.length-1] == menu) {
  228. appendPath(path, menu.getPopupMenu());
  229. }
  230. }
  231. }
  232. public boolean isEnabled(Object c) {
  233. if (c instanceof JMenu) {
  234. return ((JMenu)c).isEnabled();
  235. }
  236. return true;
  237. }
  238. }
  239. /*
  240. * Set the background color depending on whether this is a toplevel menu
  241. * in a menubar or a submenu of another menu.
  242. */
  243. private void updateDefaultBackgroundColor() {
  244. if (!UIManager.getBoolean("Menu.useMenuBarBackgroundForTopLevel")) {
  245. return;
  246. }
  247. JMenu menu = (JMenu)menuItem;
  248. if (menu.getBackground() instanceof UIResource) {
  249. if (menu.isTopLevelMenu()) {
  250. menu.setBackground(UIManager.getColor("MenuBar.background"));
  251. } else {
  252. menu.setBackground(UIManager.getColor(getPropertyPrefix() + ".background"));
  253. }
  254. }
  255. }
  256. /**
  257. * Instantiated and used by a menu item to handle the current menu selection
  258. * from mouse events. A MouseInputHandler processes and forwards all mouse events
  259. * to a shared instance of the MenuSelectionManager.
  260. * <p>
  261. * This class is protected so that it can be subclassed by other look and
  262. * feels to implement their own mouse handling behavior. All overridden
  263. * methods should call the parent methods so that the menu selection
  264. * is correct.
  265. *
  266. * @see javax.swing.MenuSelectionManager
  267. * @since 1.4
  268. */
  269. protected class MouseInputHandler implements MouseInputListener {
  270. // NOTE: This class exists only for backward compatability. All
  271. // its functionality has been moved into Handler. If you need to add
  272. // new functionality add it to the Handler, but make sure this
  273. // class calls into the Handler.
  274. public void mouseClicked(MouseEvent e) {
  275. getHandler().mouseClicked(e);
  276. }
  277. /**
  278. * Invoked when the mouse has been clicked on the menu. This
  279. * method clears or sets the selection path of the
  280. * MenuSelectionManager.
  281. *
  282. * @param e the mouse event
  283. */
  284. public void mousePressed(MouseEvent e) {
  285. getHandler().mousePressed(e);
  286. }
  287. /**
  288. * Invoked when the mouse has been released on the menu. Delegates the
  289. * mouse event to the MenuSelectionManager.
  290. *
  291. * @param e the mouse event
  292. */
  293. public void mouseReleased(MouseEvent e) {
  294. getHandler().mouseReleased(e);
  295. }
  296. /**
  297. * Invoked when the cursor enters the menu. This method sets the selected
  298. * path for the MenuSelectionManager and handles the case
  299. * in which a menu item is used to pop up an additional menu, as in a
  300. * hierarchical menu system.
  301. *
  302. * @param e the mouse event; not used
  303. */
  304. public void mouseEntered(MouseEvent e) {
  305. getHandler().mouseEntered(e);
  306. }
  307. public void mouseExited(MouseEvent e) {
  308. getHandler().mouseExited(e);
  309. }
  310. /**
  311. * Invoked when a mouse button is pressed on the menu and then dragged.
  312. * Delegates the mouse event to the MenuSelectionManager.
  313. *
  314. * @param e the mouse event
  315. * @see java.awt.event.MouseMotionListener#mouseDragged
  316. */
  317. public void mouseDragged(MouseEvent e) {
  318. getHandler().mouseDragged(e);
  319. }
  320. public void mouseMoved(MouseEvent e) {
  321. getHandler().mouseMoved(e);
  322. }
  323. }
  324. /**
  325. * As of Java 2 platform 1.4, this previously undocumented class
  326. * is now obsolete. KeyBindings are now managed by the popup menu.
  327. */
  328. public class ChangeHandler implements ChangeListener {
  329. public JMenu menu;
  330. public BasicMenuUI ui;
  331. public boolean isSelected = false;
  332. public Component wasFocused;
  333. public ChangeHandler(JMenu m, BasicMenuUI ui) {
  334. menu = m;
  335. this.ui = ui;
  336. }
  337. public void stateChanged(ChangeEvent e) { }
  338. }
  339. private class Handler extends BasicMenuItemUI.Handler implements
  340. MenuKeyListener {
  341. //
  342. // PropertyChangeListener
  343. //
  344. public void propertyChange(PropertyChangeEvent e) {
  345. if (e.getPropertyName() == AbstractButton.
  346. MNEMONIC_CHANGED_PROPERTY) {
  347. updateMnemonicBinding();
  348. }
  349. else {
  350. if (e.getPropertyName().equals("ancestor")) {
  351. updateDefaultBackgroundColor();
  352. }
  353. super.propertyChange(e);
  354. }
  355. }
  356. //
  357. // MouseInputListener
  358. //
  359. public void mouseClicked(MouseEvent e) {
  360. }
  361. /**
  362. * Invoked when the mouse has been clicked on the menu. This
  363. * method clears or sets the selection path of the
  364. * MenuSelectionManager.
  365. *
  366. * @param e the mouse event
  367. */
  368. public void mousePressed(MouseEvent e) {
  369. JMenu menu = (JMenu)menuItem;
  370. if (!menu.isEnabled())
  371. return;
  372. MenuSelectionManager manager =
  373. MenuSelectionManager.defaultManager();
  374. if(menu.isTopLevelMenu()) {
  375. if(menu.isSelected()) {
  376. manager.clearSelectedPath();
  377. } else {
  378. Container cnt = menu.getParent();
  379. if(cnt != null && cnt instanceof JMenuBar) {
  380. MenuElement me[] = new MenuElement[2];
  381. me[0]=(MenuElement)cnt;
  382. me[1]=menu;
  383. manager.setSelectedPath(me);
  384. }
  385. }
  386. }
  387. MenuElement selectedPath[] = manager.getSelectedPath();
  388. if (selectedPath.length > 0 &&
  389. selectedPath[selectedPath.length-1] != menu.getPopupMenu()) {
  390. if(menu.isTopLevelMenu() ||
  391. menu.getDelay() == 0) {
  392. appendPath(selectedPath, menu.getPopupMenu());
  393. } else {
  394. setupPostTimer(menu);
  395. }
  396. }
  397. }
  398. /**
  399. * Invoked when the mouse has been released on the menu. Delegates the
  400. * mouse event to the MenuSelectionManager.
  401. *
  402. * @param e the mouse event
  403. */
  404. public void mouseReleased(MouseEvent e) {
  405. JMenu menu = (JMenu)menuItem;
  406. if (!menu.isEnabled())
  407. return;
  408. MenuSelectionManager manager =
  409. MenuSelectionManager.defaultManager();
  410. manager.processMouseEvent(e);
  411. if (!e.isConsumed())
  412. manager.clearSelectedPath();
  413. }
  414. /**
  415. * Invoked when the cursor enters the menu. This method sets the selected
  416. * path for the MenuSelectionManager and handles the case
  417. * in which a menu item is used to pop up an additional menu, as in a
  418. * hierarchical menu system.
  419. *
  420. * @param e the mouse event; not used
  421. */
  422. public void mouseEntered(MouseEvent e) {
  423. JMenu menu = (JMenu)menuItem;
  424. if (!menu.isEnabled())
  425. return;
  426. MenuSelectionManager manager =
  427. MenuSelectionManager.defaultManager();
  428. MenuElement selectedPath[] = manager.getSelectedPath();
  429. if (!menu.isTopLevelMenu()) {
  430. if(!(selectedPath.length > 0 &&
  431. selectedPath[selectedPath.length-1] ==
  432. menu.getPopupMenu())) {
  433. if(menu.getDelay() == 0) {
  434. appendPath(getPath(), menu.getPopupMenu());
  435. } else {
  436. manager.setSelectedPath(getPath());
  437. setupPostTimer(menu);
  438. }
  439. }
  440. } else {
  441. if(selectedPath.length > 0 &&
  442. selectedPath[0] == menu.getParent()) {
  443. MenuElement newPath[] = new MenuElement[3];
  444. // A top level menu's parent is by definition
  445. // a JMenuBar
  446. newPath[0] = (MenuElement)menu.getParent();
  447. newPath[1] = menu;
  448. newPath[2] = menu.getPopupMenu();
  449. manager.setSelectedPath(newPath);
  450. }
  451. }
  452. }
  453. public void mouseExited(MouseEvent e) {
  454. }
  455. /**
  456. * Invoked when a mouse button is pressed on the menu and then dragged.
  457. * Delegates the mouse event to the MenuSelectionManager.
  458. *
  459. * @param e the mouse event
  460. * @see java.awt.event.MouseMotionListener#mouseDragged
  461. */
  462. public void mouseDragged(MouseEvent e) {
  463. JMenu menu = (JMenu)menuItem;
  464. if (!menu.isEnabled())
  465. return;
  466. MenuSelectionManager.defaultManager().processMouseEvent(e);
  467. }
  468. public void mouseMoved(MouseEvent e) {
  469. }
  470. //
  471. // MenuDragHandler
  472. //
  473. public void menuDragMouseEntered(MenuDragMouseEvent e) {}
  474. public void menuDragMouseDragged(MenuDragMouseEvent e) {
  475. if (menuItem.isEnabled() == false)
  476. return;
  477. MenuSelectionManager manager = e.getMenuSelectionManager();
  478. MenuElement path[] = e.getPath();
  479. Point p = e.getPoint();
  480. if(p.x >= 0 && p.x < menuItem.getWidth() &&
  481. p.y >= 0 && p.y < menuItem.getHeight()) {
  482. JMenu menu = (JMenu)menuItem;
  483. MenuElement selectedPath[] = manager.getSelectedPath();
  484. if(!(selectedPath.length > 0 &&
  485. selectedPath[selectedPath.length-1] ==
  486. menu.getPopupMenu())) {
  487. if(menu.isTopLevelMenu() ||
  488. menu.getDelay() == 0 ||
  489. e.getID() == MouseEvent.MOUSE_DRAGGED) {
  490. appendPath(path, menu.getPopupMenu());
  491. } else {
  492. manager.setSelectedPath(path);
  493. setupPostTimer(menu);
  494. }
  495. }
  496. } else if(e.getID() == MouseEvent.MOUSE_RELEASED) {
  497. Component comp = manager.componentForPoint(e.getComponent(), e.getPoint());
  498. if (comp == null)
  499. manager.clearSelectedPath();
  500. }
  501. }
  502. public void menuDragMouseExited(MenuDragMouseEvent e) {}
  503. public void menuDragMouseReleased(MenuDragMouseEvent e) {}
  504. //
  505. // MenuKeyListener
  506. //
  507. /**
  508. * Open the Menu
  509. */
  510. public void menuKeyTyped(MenuKeyEvent e) {
  511. if (!crossMenuMnemonic && BasicPopupMenuUI.getLastPopup() != null) {
  512. // when crossMenuMnemonic is not set, we don't open a toplevel
  513. // menu if another toplevel menu is already open
  514. return;
  515. }
  516. char key = Character.toLowerCase((char)menuItem.getMnemonic());
  517. MenuElement path[] = e.getPath();
  518. if (key == Character.toLowerCase(e.getKeyChar())) {
  519. JPopupMenu popupMenu = ((JMenu)menuItem).getPopupMenu();
  520. ArrayList newList = new ArrayList(Arrays.asList(path));
  521. newList.add(popupMenu);
  522. MenuElement subs[] = popupMenu.getSubElements();
  523. MenuElement sub =
  524. BasicPopupMenuUI.findEnabledChild(subs, -1, true);
  525. if(sub != null) {
  526. newList.add(sub);
  527. }
  528. MenuSelectionManager manager = e.getMenuSelectionManager();
  529. MenuElement newPath[] = new MenuElement[0];;
  530. newPath = (MenuElement[]) newList.toArray(newPath);
  531. manager.setSelectedPath(newPath);
  532. e.consume();
  533. }
  534. }
  535. public void menuKeyPressed(MenuKeyEvent e) {}
  536. public void menuKeyReleased(MenuKeyEvent e) {}
  537. }
  538. }