1. /*
  2. * @(#)JMenuItem.java 1.79 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.util.EventListener;
  9. import java.awt.*;
  10. import java.awt.event.*;
  11. import java.awt.image.*;
  12. import java.io.Serializable;
  13. import java.io.ObjectOutputStream;
  14. import java.io.ObjectInputStream;
  15. import java.io.IOException;
  16. import javax.swing.plaf.*;
  17. import javax.swing.plaf.basic.*;
  18. import javax.swing.event.*;
  19. import javax.accessibility.*;
  20. /**
  21. * An implementation of a MenuItem. A menu item is essentially a button
  22. * sitting in a list. When the user selects the "button", the action
  23. * associated with the menu item is performed. A JMenuItem contained
  24. * in a JPopupMenu performs exactly that function.
  25. * <p>
  26. * For the keyboard keys used by this component in the standard Look and
  27. * Feel (L&F) renditions, see the
  28. * <a href="doc-files/Key-Index.html#JMenuItem">JMenuItem</a> key assignments.
  29. * <p>
  30. * <strong>Warning:</strong>
  31. * Serialized objects of this class will not be compatible with
  32. * future Swing releases. The current serialization support is appropriate
  33. * for short term storage or RMI between applications running the same
  34. * version of Swing. A future release of Swing will provide support for
  35. * long term persistence.
  36. *
  37. * @version 1.79 11/29/01
  38. * @author Georges Saab
  39. * @author David Karlton
  40. * @see JPopupMenu
  41. * @see JMenu
  42. * @see JCheckBoxMenuItem
  43. * @see JRadioButtonMenuItem
  44. */
  45. public class JMenuItem extends AbstractButton implements Accessible,MenuElement {
  46. /**
  47. * @see #getUIClassID
  48. * @see #readObject
  49. */
  50. private static final String uiClassID = "MenuItemUI";
  51. /**
  52. * Creates a menuItem with no set text or icon.
  53. */
  54. public JMenuItem() {
  55. this(null, (Icon)null);
  56. setRequestFocusEnabled(false);
  57. }
  58. /**
  59. * Creates a menuItem with an icon.
  60. *
  61. * @param icon the icon of the MenuItem.
  62. */
  63. public JMenuItem(Icon icon) {
  64. this(null, icon);
  65. setRequestFocusEnabled(false);
  66. }
  67. /**
  68. * Creates a menuItem with text.
  69. *
  70. * @param text the text of the MenuItem.
  71. */
  72. public JMenuItem(String text) {
  73. this(text, (Icon)null);
  74. }
  75. /**
  76. * Creates a menuItem with the supplied text and icon.
  77. *
  78. * @param text the text of the MenuItem.
  79. * @param icon the icon of the MenuItem.
  80. */
  81. public JMenuItem(String text, Icon icon) {
  82. setModel(new DefaultButtonModel());
  83. init(text, icon);
  84. }
  85. /**
  86. * Creates a menuItem with the specified text and
  87. * keyboard mnemonic.
  88. *
  89. * @param text the text of the MenuItem.
  90. * @param mnemonic the keyboard mnemonic for the MenuItem
  91. */
  92. public JMenuItem(String text, int mnemonic) {
  93. setModel(new DefaultButtonModel());
  94. init(text, null);
  95. setMnemonic(mnemonic);
  96. }
  97. /**
  98. * Initialize the menu item with the specified text and icon.
  99. *
  100. * @param text the text of the MenuItem.
  101. * @param icon the icon of the MenuItem.
  102. */
  103. protected void init(String text, Icon icon) {
  104. if(text != null) {
  105. setText(text);
  106. }
  107. if(icon != null) {
  108. setIcon(icon);
  109. }
  110. // Listen for Focus events
  111. addFocusListener(new MenuItemFocusListener());
  112. setBorderPainted(false);
  113. setFocusPainted(false);
  114. setHorizontalTextPosition(JButton.TRAILING);
  115. setHorizontalAlignment(JButton.LEADING);
  116. updateUI();
  117. }
  118. private static class MenuItemFocusListener implements FocusListener,
  119. Serializable {
  120. public void focusGained(FocusEvent event) {}
  121. public void focusLost(FocusEvent event) {
  122. // When focus is lost, repaint if
  123. // the focus information is painted
  124. JMenuItem mi = (JMenuItem)event.getSource();
  125. if(mi.isFocusPainted()) {
  126. mi.repaint();
  127. }
  128. }
  129. }
  130. /**
  131. * Sets the L&F object that renders this component.
  132. *
  133. * @param ui the MenuItemUI L&F object
  134. * @see UIDefaults#getUI
  135. * @beaninfo
  136. * description: The menu item's UI delegate
  137. * bound: true
  138. * expert: true
  139. * hidden: true
  140. */
  141. public void setUI(MenuItemUI ui) {
  142. super.setUI(ui);
  143. }
  144. /**
  145. * Notification from the UIFactory that the L&F has changed.
  146. * Called to replace the UI with the latest version from the
  147. * UIFactory.
  148. *
  149. * @see JComponent#updateUI
  150. */
  151. public void updateUI() {
  152. setUI((MenuItemUI)UIManager.getUI(this));
  153. }
  154. /**
  155. * Returns the name of the L&F class that renders this component.
  156. *
  157. * @return "MenuItemUI"
  158. * @see JComponent#getUIClassID
  159. * @see UIDefaults#getUI
  160. */
  161. public String getUIClassID() {
  162. return uiClassID;
  163. }
  164. /**
  165. * Identifies the menu item as "armed". If the mouse button is
  166. * released while it is over this item, the menu's action event
  167. * will fire. If the mouse button is released elsewhere, the
  168. * event will not fire and the menu item will be disarmed.
  169. *
  170. * @param b true to arm the menu item so it can be selected
  171. * @beaninfo
  172. * description: Mouse release will fire an action event
  173. * hidden: true
  174. */
  175. public void setArmed(boolean b) {
  176. ButtonModel model = (ButtonModel) getModel();
  177. boolean oldValue = model.isArmed();
  178. if ((accessibleContext != null) && (oldValue != b)) {
  179. if (b) {
  180. accessibleContext.firePropertyChange(
  181. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  182. null,
  183. AccessibleState.ARMED);
  184. } else {
  185. accessibleContext.firePropertyChange(
  186. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  187. AccessibleState.ARMED,
  188. null);
  189. }
  190. }
  191. if(model.isArmed() != b) {
  192. model.setArmed(b);
  193. }
  194. }
  195. /**
  196. * Returns whether the menu item is "armed".
  197. *
  198. * @return true if the menu item is armed, and it can be selected
  199. * @see #setArmed
  200. */
  201. public boolean isArmed() {
  202. ButtonModel model = (ButtonModel) getModel();
  203. return model.isArmed();
  204. }
  205. /**
  206. * Enable or disable the menu item.
  207. *
  208. * @param b true to enable the item
  209. * @beaninfo
  210. * description: Does the component react to user interaction
  211. * bound: true
  212. * preferred: true
  213. */
  214. public void setEnabled(boolean b) {
  215. // Make sure we aren't armed!
  216. if (b == false)
  217. setArmed(false);
  218. super.setEnabled(b);
  219. }
  220. /**
  221. * Always return true since Menus, by definition,
  222. * should always be on top of all other windows.
  223. */
  224. // package private
  225. boolean alwaysOnTop() {
  226. return true;
  227. }
  228. /* The keystroke which acts as the menu item's accelerator
  229. */
  230. private KeyStroke accelerator;
  231. /**
  232. * Set the key combination which invokes the Menu Item's
  233. * action listeners without navigating the menu hierarchy.
  234. *
  235. * @param keyStroke the KeyStroke which will serve as an accelerator
  236. * @beaninfo
  237. * description: The keystroke combination which will invoke the JMenuItem's
  238. * actionlisteners without navigating the menu hierarchy
  239. * bound: true
  240. * preferred: true
  241. */
  242. public void setAccelerator(KeyStroke keyStroke) {
  243. KeyStroke oldAccelerator = accelerator;
  244. if (oldAccelerator != null)
  245. unregisterKeyboardAction(oldAccelerator);
  246. // PENDING(ges) Make this implement Serializable
  247. if (keyStroke != null) {
  248. registerKeyboardAction(new ActionListener(){
  249. public void actionPerformed(ActionEvent e) {
  250. MenuSelectionManager.defaultManager().clearSelectedPath();
  251. doClick();
  252. }
  253. } , keyStroke, WHEN_IN_FOCUSED_WINDOW);
  254. }
  255. this.accelerator = keyStroke;
  256. firePropertyChange("accelerator", oldAccelerator, accelerator);
  257. }
  258. /**
  259. * Returns the KeyStroke which serves as an accelerator
  260. * for the menu item.
  261. * @return a KeyStroke object identifying the accelerator key
  262. */
  263. public KeyStroke getAccelerator() {
  264. return this.accelerator;
  265. }
  266. /**
  267. * Process a mouse event forwarded from the MenuSelectionManager.
  268. * @param event A MouseEvent with source being the receiving component.
  269. * @param componentPath The MenuElement path array to the receiving component.
  270. * @param manager The MenuSelectionManager for the menu hierarchy.
  271. * This method should process the MouseEvent and change the menu selection if necessary
  272. * by using MenuSelectionManager's API.
  273. * <p>
  274. * Note: you do not have to forward the event to sub-components. This is done automatically
  275. * by the MenuSelectionManager
  276. */
  277. public void processMouseEvent(MouseEvent e,MenuElement path[],MenuSelectionManager manager) {
  278. processMenuDragMouseEvent(
  279. new MenuDragMouseEvent(e.getComponent(), e.getID(),
  280. e.getWhen(),
  281. e.getModifiers(), e.getX(), e.getY(),
  282. e.getClickCount(), e.isPopupTrigger(),
  283. path, manager));
  284. }
  285. /**
  286. * Process a key event forwarded from the MenuSelectionManager.
  287. * @param event A KeyEvent with source being the receiving component.
  288. * @param componentPath The MenuElement path array to the receiving component.
  289. * @param manager The MenuSelectionManager for the menu hierarchy.
  290. * This method should process the KeyEvent and change the menu selection if necessary
  291. * by using MenuSelectionManager's API.
  292. * <p>
  293. * Note: you do not have to forward the event to sub-components. This is done automatically
  294. * by the MenuSelectionManager
  295. */
  296. public void processKeyEvent(KeyEvent e,MenuElement path[],MenuSelectionManager manager) {
  297. MenuKeyEvent mke = new MenuKeyEvent(e.getComponent(), e.getID(),
  298. e.getWhen(), e.getModifiers(),
  299. e.getKeyCode(), e.getKeyChar(),
  300. path, manager);
  301. processMenuKeyEvent(mke);
  302. if (mke.isConsumed())
  303. e.consume();
  304. }
  305. /**
  306. * Handle mouse drag in a menu.
  307. *
  308. * @param e a MenuDragMouseEvent object
  309. */
  310. public void processMenuDragMouseEvent(MenuDragMouseEvent e) {
  311. switch (e.getID()) {
  312. case MouseEvent.MOUSE_ENTERED:
  313. fireMenuDragMouseEntered(e); break;
  314. case MouseEvent.MOUSE_EXITED:
  315. fireMenuDragMouseExited(e); break;
  316. case MouseEvent.MOUSE_DRAGGED:
  317. fireMenuDragMouseDragged(e); break;
  318. case MouseEvent.MOUSE_RELEASED:
  319. fireMenuDragMouseReleased(e); break;
  320. default:
  321. break;
  322. }
  323. }
  324. /**
  325. * Handle a keystroke in a menu.
  326. *
  327. * @param e a MenuKeyEvent object
  328. */
  329. public void processMenuKeyEvent(MenuKeyEvent e) {
  330. switch (e.getID()) {
  331. case KeyEvent.KEY_PRESSED:
  332. fireMenuKeyPressed(e); break;
  333. case KeyEvent.KEY_RELEASED:
  334. fireMenuKeyReleased(e); break;
  335. case KeyEvent.KEY_TYPED:
  336. fireMenuKeyTyped(e); break;
  337. default:
  338. break;
  339. }
  340. }
  341. /*
  342. * Notify all listeners that have registered interest for
  343. * notification on this event type.
  344. * @see EventListenerList
  345. */
  346. protected void fireMenuDragMouseEntered(MenuDragMouseEvent event) {
  347. // Guaranteed to return a non-null array
  348. Object[] listeners = listenerList.getListenerList();
  349. // Process the listeners last to first, notifying
  350. // those that are interested in this event
  351. for (int i = listeners.length-2; i>=0; i-=2) {
  352. if (listeners[i]==MenuDragMouseListener.class) {
  353. // Lazily create the event:
  354. ((MenuDragMouseListener)listeners[i+1]).menuDragMouseEntered(event);
  355. }
  356. }
  357. }
  358. /*
  359. * Notify all listeners that have registered interest for
  360. * notification on this event type.
  361. * @see EventListenerList
  362. */
  363. protected void fireMenuDragMouseExited(MenuDragMouseEvent event) {
  364. // Guaranteed to return a non-null array
  365. Object[] listeners = listenerList.getListenerList();
  366. // Process the listeners last to first, notifying
  367. // those that are interested in this event
  368. for (int i = listeners.length-2; i>=0; i-=2) {
  369. if (listeners[i]==MenuDragMouseListener.class) {
  370. // Lazily create the event:
  371. ((MenuDragMouseListener)listeners[i+1]).menuDragMouseExited(event);
  372. }
  373. }
  374. }
  375. /*
  376. * Notify all listeners that have registered interest for
  377. * notification on this event type.
  378. * @see EventListenerList
  379. */
  380. protected void fireMenuDragMouseDragged(MenuDragMouseEvent event) {
  381. // Guaranteed to return a non-null array
  382. Object[] listeners = listenerList.getListenerList();
  383. // Process the listeners last to first, notifying
  384. // those that are interested in this event
  385. for (int i = listeners.length-2; i>=0; i-=2) {
  386. if (listeners[i]==MenuDragMouseListener.class) {
  387. // Lazily create the event:
  388. ((MenuDragMouseListener)listeners[i+1]).menuDragMouseDragged(event);
  389. }
  390. }
  391. }
  392. /*
  393. * Notify all listeners that have registered interest for
  394. * notification on this event type.
  395. * @see EventListenerList
  396. */
  397. protected void fireMenuDragMouseReleased(MenuDragMouseEvent event) {
  398. // Guaranteed to return a non-null array
  399. Object[] listeners = listenerList.getListenerList();
  400. // Process the listeners last to first, notifying
  401. // those that are interested in this event
  402. for (int i = listeners.length-2; i>=0; i-=2) {
  403. if (listeners[i]==MenuDragMouseListener.class) {
  404. // Lazily create the event:
  405. ((MenuDragMouseListener)listeners[i+1]).menuDragMouseReleased(event);
  406. }
  407. }
  408. }
  409. /*
  410. * Notify all listeners that have registered interest for
  411. * notification on this event type.
  412. * @see EventListenerList
  413. */
  414. protected void fireMenuKeyPressed(MenuKeyEvent event) {
  415. // Guaranteed to return a non-null array
  416. Object[] listeners = listenerList.getListenerList();
  417. // Process the listeners last to first, notifying
  418. // those that are interested in this event
  419. for (int i = listeners.length-2; i>=0; i-=2) {
  420. if (listeners[i]==MenuKeyListener.class) {
  421. // Lazily create the event:
  422. ((MenuKeyListener)listeners[i+1]).menuKeyPressed(event);
  423. }
  424. }
  425. }
  426. /*
  427. * Notify all listeners that have registered interest for
  428. * notification on this event type.
  429. * @see EventListenerList
  430. */
  431. protected void fireMenuKeyReleased(MenuKeyEvent event) {
  432. // Guaranteed to return a non-null array
  433. Object[] listeners = listenerList.getListenerList();
  434. // Process the listeners last to first, notifying
  435. // those that are interested in this event
  436. for (int i = listeners.length-2; i>=0; i-=2) {
  437. if (listeners[i]==MenuKeyListener.class) {
  438. // Lazily create the event:
  439. ((MenuKeyListener)listeners[i+1]).menuKeyReleased(event);
  440. }
  441. }
  442. }
  443. /*
  444. * Notify all listeners that have registered interest for
  445. * notification on this event type.
  446. * @see EventListenerList
  447. */
  448. protected void fireMenuKeyTyped(MenuKeyEvent event) {
  449. // Guaranteed to return a non-null array
  450. Object[] listeners = listenerList.getListenerList();
  451. // Process the listeners last to first, notifying
  452. // those that are interested in this event
  453. for (int i = listeners.length-2; i>=0; i-=2) {
  454. if (listeners[i]==MenuKeyListener.class) {
  455. // Lazily create the event:
  456. ((MenuKeyListener)listeners[i+1]).menuKeyTyped(event);
  457. }
  458. }
  459. }
  460. /**
  461. * Called by the MenuSelectionManager when the MenuElement is selected
  462. * or unselected.
  463. *
  464. * @param isIncluded true if this menu item is on the part of the menu
  465. * path that changed, false if this menu is part of the
  466. * a menu path that changed, but this particular part of
  467. * that path is still the same
  468. * @see MenuSelectionManager#setSelectedPath(MenuElement[])
  469. */
  470. public void menuSelectionChanged(boolean isIncluded) {
  471. setArmed(isIncluded);
  472. }
  473. /**
  474. * This method returns an array containing the sub-menu components for this menu component.
  475. *
  476. * @return an array of MenuElements
  477. */
  478. public MenuElement[] getSubElements() {
  479. return new MenuElement[0];
  480. }
  481. /**
  482. * This method returns the java.awt.Component used to paint this object.
  483. * The returned component will be used to convert events and detect if an event is inside
  484. * a menu component.
  485. *
  486. * @return the Component that paints this menu item
  487. */
  488. public Component getComponent() {
  489. return this;
  490. }
  491. /**
  492. * Adds a MenuDragMouseListener to the menu item
  493. */
  494. public void addMenuDragMouseListener(MenuDragMouseListener l) {
  495. listenerList.add(MenuDragMouseListener.class, l);
  496. }
  497. /**
  498. * Removes a MenuDragMouseListener from the menu item
  499. */
  500. public void removeMenuDragMouseListener(MenuDragMouseListener l) {
  501. listenerList.remove(MenuDragMouseListener.class, l);
  502. }
  503. /**
  504. * Adds a MenuKeyListener to the menu item
  505. */
  506. public void addMenuKeyListener(MenuKeyListener l) {
  507. listenerList.add(MenuKeyListener.class, l);
  508. }
  509. /**
  510. * Removes a MenuKeyListener from the menu item
  511. */
  512. public void removeMenuKeyListener(MenuKeyListener l) {
  513. listenerList.remove(MenuKeyListener.class, l);
  514. }
  515. /**
  516. * See JComponent.readObject() for information about serialization
  517. * in Swing.
  518. */
  519. private void readObject(ObjectInputStream s)
  520. throws IOException, ClassNotFoundException
  521. {
  522. s.defaultReadObject();
  523. if (getUIClassID().equals(uiClassID)) {
  524. updateUI();
  525. }
  526. }
  527. private void writeObject(ObjectOutputStream s) throws IOException {
  528. s.defaultWriteObject();
  529. if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  530. ui.installUI(this);
  531. }
  532. }
  533. /**
  534. * Returns a string representation of this JMenuItem. This method
  535. * is intended to be used only for debugging purposes, and the
  536. * content and format of the returned string may vary between
  537. * implementations. The returned string may be empty but may not
  538. * be <code>null</code>.
  539. *
  540. * @return a string representation of this JMenuItem.
  541. */
  542. protected String paramString() {
  543. return super.paramString();
  544. }
  545. /////////////////
  546. // Accessibility support
  547. ////////////////
  548. /**
  549. * Get the AccessibleContext associated with this JComponent
  550. *
  551. * @return the AccessibleContext of this JComponent
  552. */
  553. public AccessibleContext getAccessibleContext() {
  554. if (accessibleContext == null) {
  555. accessibleContext = new AccessibleJMenuItem();
  556. }
  557. return accessibleContext;
  558. }
  559. /**
  560. * The class used to obtain the accessible role for this object.
  561. * <p>
  562. * <strong>Warning:</strong>
  563. * Serialized objects of this class will not be compatible with
  564. * future Swing releases. The current serialization support is appropriate
  565. * for short term storage or RMI between applications running the same
  566. * version of Swing. A future release of Swing will provide support for
  567. * long term persistence.
  568. */
  569. protected class AccessibleJMenuItem extends AccessibleAbstractButton implements ChangeListener {
  570. AccessibleJMenuItem() {
  571. super();
  572. JMenuItem.this.addChangeListener(this);
  573. }
  574. /**
  575. * Get the role of this object.
  576. *
  577. * @return an instance of AccessibleRole describing the role of the
  578. * object
  579. */
  580. public AccessibleRole getAccessibleRole() {
  581. return AccessibleRole.MENU_ITEM;
  582. }
  583. /**
  584. * Supports the change listener interface and fires property change
  585. */
  586. public void stateChanged(ChangeEvent e) {
  587. firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  588. new Boolean(false), new Boolean(true));
  589. }
  590. } // inner class AccessibleJMenuItem
  591. }