1. /*
  2. * @(#)JMenu.java 1.126 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.awt.Component;
  9. import java.awt.Container;
  10. import java.awt.Dimension;
  11. import java.awt.Frame;
  12. import java.awt.Graphics;
  13. import java.awt.Point;
  14. import java.awt.Polygon;
  15. import java.awt.Rectangle;
  16. import java.awt.Toolkit;
  17. import java.awt.event.*;
  18. import java.beans.*;
  19. import java.util.*;
  20. import java.io.Serializable;
  21. import java.io.ObjectOutputStream;
  22. import java.io.ObjectInputStream;
  23. import java.io.IOException;
  24. import java.awt.event.KeyEvent;
  25. import javax.swing.event.*;
  26. import javax.swing.plaf.*;
  27. import javax.swing.plaf.basic.*;
  28. import javax.accessibility.*;
  29. /**
  30. * An implementation of a menu -- a popup window containing <code>JMenuItem</code>s that
  31. * is displayed when the user selects an item on the <code>JMenuBar</code>. In addition
  32. * to JMenuItems, a JMenu can also contain <code>JSeparator</code>s.
  33. * <p>
  34. * In essence, a menu is a button with an associated JPopupMenu.
  35. * When the "button" is pressed, the JPopupMenu appears. If the
  36. * "button" is on the JMenuBar, the menu is a top-level window.
  37. * If the "button" is another menu item, then the JPopupMenu is
  38. * "pull-right" menu.
  39. * <p>
  40. * For the keyboard keys used by this component in the standard Look and
  41. * Feel (L&F) renditions, see the
  42. * <a href="doc-files/Key-Index.html#JMenu">JMenu</a> key assignments.
  43. * <p>
  44. * <strong>Warning:</strong>
  45. * Serialized objects of this class will not be compatible with
  46. * future Swing releases. The current serialization support is appropriate
  47. * for short term storage or RMI between applications running the same
  48. * version of Swing. A future release of Swing will provide support for
  49. * long term persistence.
  50. *
  51. * @version 1.126 11/29/01
  52. * @author Georges Saab
  53. * @author David Karlton
  54. * @author Arnaud Weber
  55. * @see JMenuItem
  56. * @see JSeparator
  57. * @see JMenuBar
  58. * @see JPopupMenu
  59. */
  60. public class JMenu extends JMenuItem implements Accessible,MenuElement
  61. {
  62. /**
  63. * @see #getUIClassID
  64. * @see #readObject
  65. */
  66. private static final String uiClassID = "MenuUI";
  67. /*
  68. * The popup menu portion of the menu.
  69. */
  70. private JPopupMenu popupMenu;
  71. /*
  72. * The button's model listeners.
  73. */
  74. private ChangeListener menuChangeListener = null;
  75. /*
  76. * Only one MenuEvent is needed per menu instance since the
  77. * event's only state is the source property. The source of events
  78. * generated is always "this".
  79. */
  80. private MenuEvent menuEvent = null;
  81. /* Registry of listeners created for Action-JMenuItem
  82. * linkage. This is needed so that references can
  83. * be cleaned up at remove time to allow GC.
  84. */
  85. private static Hashtable listenerRegistry = null;
  86. private int delay;
  87. private boolean receivedKeyPressed=false;
  88. /**
  89. * Creates a new JMenu with no text.
  90. */
  91. public JMenu() {
  92. this("");
  93. }
  94. /**
  95. * Creates a new JMenu with the supplied string as its text
  96. *
  97. * @param s The text for the menu label
  98. */
  99. public JMenu(String s) {
  100. super(s);
  101. }
  102. /**
  103. * Creates a new JMenu with the supplied string as its text
  104. * and specified as a tear-off menu or not.
  105. *
  106. * @param s The text for the menu label
  107. * @param b can the menu be torn off (not yet implemented)
  108. */
  109. public JMenu(String s, boolean b) {
  110. this(s);
  111. }
  112. /**
  113. * Notification from the UIFactory that the L&F has changed.
  114. * Called to replace the UI with the latest version from the
  115. * UIFactory.
  116. *
  117. * @see JComponent#updateUI
  118. */
  119. public void updateUI() {
  120. setUI((MenuItemUI)UIManager.getUI(this));
  121. if ( popupMenu != null )
  122. {
  123. popupMenu.setUI((PopupMenuUI)UIManager.getUI(popupMenu));
  124. }
  125. }
  126. /**
  127. * Returns the name of the L&F class that renders this component.
  128. *
  129. * @return "MenuUI"
  130. * @see JComponent#getUIClassID
  131. * @see UIDefaults#getUI
  132. */
  133. public String getUIClassID() {
  134. return uiClassID;
  135. }
  136. // public void repaint(long tm, int x, int y, int width, int height) {
  137. // Thread.currentThread().dumpStack();
  138. // super.repaint(tm,x,y,width,height);
  139. // }
  140. /**
  141. * Set the data model for the "menu button" -- the label
  142. * that the user clicks to open or close the menu.
  143. *
  144. * @param m the ButtonModel
  145. * @see #getModel
  146. * @beaninfo
  147. * description: The menu's model
  148. * bound: true
  149. * expert: true
  150. * hidden: true
  151. */
  152. public void setModel(ButtonModel newModel) {
  153. ButtonModel oldModel = getModel();
  154. super.setModel(newModel);
  155. if (oldModel != null && menuChangeListener != null) {
  156. oldModel.removeChangeListener(menuChangeListener);
  157. menuChangeListener = null;
  158. }
  159. model = newModel;
  160. if (newModel != null) {
  161. menuChangeListener = createMenuChangeListener();
  162. newModel.addChangeListener(menuChangeListener);
  163. }
  164. }
  165. /**
  166. * Returns true if the menu is currently selected (popped up).
  167. *
  168. * @return true if the menu is open, else false
  169. */
  170. public boolean isSelected() {
  171. return getModel().isSelected();
  172. }
  173. /**
  174. * Sets the selection status of the menu.
  175. *
  176. * @param b a boolean value -- true to select the menu and
  177. * open it, false to unselect the menu and close it
  178. * @beaninfo
  179. * description: When the menu is selected, its popup child is shown.
  180. * expert: true
  181. * hidden: true
  182. */
  183. public void setSelected(boolean b) {
  184. ButtonModel model = getModel();
  185. boolean oldValue = model.isSelected();
  186. if ((accessibleContext != null) && (oldValue != b)) {
  187. if (b) {
  188. accessibleContext.firePropertyChange(
  189. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  190. null, AccessibleState.SELECTED);
  191. } else {
  192. accessibleContext.firePropertyChange(
  193. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  194. AccessibleState.SELECTED, null);
  195. }
  196. }
  197. if (b != model.isSelected()) {
  198. getModel().setSelected(b);
  199. }
  200. }
  201. /**
  202. * Returns true if the menu's popup window is visible.
  203. *
  204. * @return true if the menu is visible, else false
  205. */
  206. public boolean isPopupMenuVisible() {
  207. ensurePopupMenuCreated();
  208. return popupMenu.isVisible();
  209. }
  210. /**
  211. * Set the visibility of the Menu's popup portion. The popup
  212. * may only be made visible if the menu is itself showing on
  213. * the screen.
  214. *
  215. * @param b a boolean value -- true to make the menu visible,
  216. * false to hide it
  217. * @beaninfo
  218. * description: The popup menu's visibility
  219. * expert: true
  220. * hidden: true
  221. */
  222. public void setPopupMenuVisible(boolean b) {
  223. if (!isEnabled())
  224. return;
  225. boolean isVisible = isPopupMenuVisible();
  226. if (b != isVisible) {
  227. ensurePopupMenuCreated();
  228. // Set location of popupMenu (pulldown or pullright)
  229. // Perhaps this should be dictated by L&F
  230. if ((b==true) && isShowing()) {
  231. Point p = getPopupMenuOrigin();
  232. getPopupMenu().show(this, p.x, p.y);
  233. } else {
  234. getPopupMenu().setVisible(false);
  235. }
  236. }
  237. }
  238. /**
  239. * Compute the origin for the JMenu's popup menu.
  240. *
  241. * @returns a Point in the coordinate space of the menu instance
  242. * which should be used as the origin of the JMenu's popup menu.
  243. */
  244. private Point getPopupMenuOrigin() {
  245. int x = 0;
  246. int y = 0;
  247. JPopupMenu pm = getPopupMenu();
  248. // Figure out the sizes needed to caclulate the menu position
  249. Dimension screenSize =Toolkit.getDefaultToolkit().getScreenSize();
  250. Dimension s = getSize();
  251. Dimension pmSize = pm.getSize();
  252. // For the first time the menu is popped up,
  253. // the size has not yet been initiated
  254. if (pmSize.width==0) {
  255. pmSize = pm.getPreferredSize();
  256. }
  257. Point position = getLocationOnScreen();
  258. Container parent = getParent();
  259. if (parent instanceof JPopupMenu) {
  260. // We are a submenu (pull-right)
  261. if( SwingUtilities.isLeftToRight(this) ) {
  262. // First determine x:
  263. if (position.x+s.width + pmSize.width < screenSize.width) {
  264. x = s.width; // Prefer placement to the right
  265. } else {
  266. x = 0-pmSize.width; // Otherwise place to the left
  267. }
  268. } else {
  269. // First determine x:
  270. if (position.x < pmSize.width) {
  271. x = s.width; // Prefer placement to the right
  272. } else {
  273. x = 0-pmSize.width; // Otherwise place to the left
  274. }
  275. }
  276. // Then the y:
  277. if (position.y+pmSize.height < screenSize.height) {
  278. y = 0; // Prefer dropping down
  279. } else {
  280. y = s.height-pmSize.height; // Otherwise drop 'up'
  281. }
  282. } else {
  283. // We are a toplevel menu (pull-down)
  284. if( SwingUtilities.isLeftToRight(this) ) {
  285. // First determine the x:
  286. if (position.x+pmSize.width < screenSize.width) {
  287. x = 0; // Prefer extending to right
  288. } else {
  289. x = s.width-pmSize.width; // Otherwise extend to left
  290. }
  291. } else {
  292. // First determine the x:
  293. if (position.x+s.width < pmSize.width) {
  294. x = 0; // Prefer extending to right
  295. } else {
  296. x = s.width-pmSize.width; // Otherwise extend to left
  297. }
  298. }
  299. // Then the y:
  300. if (position.y+s.height+pmSize.height < screenSize.height) {
  301. y = s.height; // Prefer dropping down
  302. } else {
  303. y = 0-pmSize.height; // Otherwise drop 'up'
  304. }
  305. }
  306. return new Point(x,y);
  307. }
  308. /**
  309. * Returns the suggested delay before the menu's PopupMenu is popped up or down.
  310. * Each look and feel may determine its own policy for observing the delay
  311. * property. In most cases, the delay is not observed for top level menus
  312. * or while dragging.
  313. *
  314. * @return an int -- the number of milliseconds to delay
  315. */
  316. public int getDelay() {
  317. return delay;
  318. }
  319. /**
  320. * Sets the suggested delay before the menu's PopupMenu is popped up or down.
  321. * Each look and feel may determine its own policy for observing the delay
  322. * property. In most cases, the delay is not observed for top level menus
  323. * or while dragging.
  324. *
  325. * @param d the number of milliseconds to delay
  326. * @exception IllegalArgumentException if the value of
  327. * <code>d</code> is less than 0.
  328. * @beaninfo
  329. * description: The delay between menu selection and making the popup menu visible
  330. * expert: true
  331. */
  332. public void setDelay(int d) {
  333. if (d < 0)
  334. throw new IllegalArgumentException("Delay must be a positive integer");
  335. delay = d;
  336. }
  337. /**
  338. * The window-closing listener for the popup.
  339. *
  340. * @see WinListener
  341. */
  342. protected WinListener popupListener;
  343. private void ensurePopupMenuCreated() {
  344. if (popupMenu == null) {
  345. final JMenu thisMenu = this;
  346. this.popupMenu = new JPopupMenu();
  347. popupMenu.setInvoker(this);
  348. popupListener = createWinListener(popupMenu);
  349. popupMenu.addPopupMenuListener(new PopupMenuListener() {
  350. public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
  351. }
  352. public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
  353. }
  354. public void popupMenuCanceled(PopupMenuEvent e) {
  355. fireMenuCanceled();
  356. }
  357. });
  358. }
  359. }
  360. /**
  361. * Set the location of the popup component
  362. *
  363. * @param x the x coordinate of the popup's new position
  364. * @param y the y coordinate of the popup's new position
  365. */
  366. public void setMenuLocation(int x, int y) {
  367. if (popupMenu != null)
  368. popupMenu.setLocation(x, y);
  369. }
  370. /**
  371. * Appends a menuitem to the end of this menu.
  372. * Returns the menuitem added.
  373. *
  374. * @param menuItem the JMenuitem to be added
  375. * @return the JMenuItem added
  376. */
  377. public JMenuItem add(JMenuItem menuItem) {
  378. AccessibleContext ac = menuItem.getAccessibleContext();
  379. ac.setAccessibleParent(this);
  380. ensurePopupMenuCreated();
  381. return popupMenu.add(menuItem);
  382. }
  383. /**
  384. * Appends a component to the end of this menu.
  385. * Returns the component added.
  386. *
  387. * @param c the Component to add
  388. * @return the Component added
  389. */
  390. public Component add(Component c) {
  391. if (c instanceof JComponent) {
  392. AccessibleContext ac = ((JComponent) c).getAccessibleContext();
  393. if (ac != null) {
  394. ac.setAccessibleParent(this);
  395. }
  396. }
  397. ensurePopupMenuCreated();
  398. popupMenu.add(c);
  399. return c;
  400. }
  401. /**
  402. * Creates a new menuitem with the specified text and appends
  403. * it to the end of this menu.
  404. *
  405. * @param s the string for the menuitem to be added
  406. */
  407. public JMenuItem add(String s) {
  408. return add(new JMenuItem(s));
  409. }
  410. /**
  411. * Creates a new menuitem attached to the specified
  412. * Action object and appends it to the end of this menu.
  413. *
  414. * @param a the Action for the menuitem to be added
  415. * @see Action
  416. */
  417. public JMenuItem add(Action a) {
  418. JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME),
  419. (Icon)a.getValue(Action.SMALL_ICON));
  420. mi.setHorizontalTextPosition(JButton.RIGHT);
  421. mi.setVerticalTextPosition(JButton.CENTER);
  422. mi.setEnabled(a.isEnabled());
  423. mi.addActionListener(a);
  424. add(mi);
  425. registerMenuItemForAction(mi, a);
  426. return mi;
  427. }
  428. protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
  429. return new ActionChangedListener(b);
  430. }
  431. private class ActionChangedListener implements PropertyChangeListener {
  432. JMenuItem menuItem;
  433. ActionChangedListener(JMenuItem mi) {
  434. super();
  435. setTarget(mi);
  436. }
  437. public void propertyChange(PropertyChangeEvent e) {
  438. String propertyName = e.getPropertyName();
  439. if (e.getPropertyName().equals(Action.NAME)) {
  440. String text = (String) e.getNewValue();
  441. menuItem.setText(text);
  442. } else if (propertyName.equals("enabled")) {
  443. Boolean enabledState = (Boolean) e.getNewValue();
  444. menuItem.setEnabled(enabledState.booleanValue());
  445. } else if (e.getPropertyName().equals(Action.SMALL_ICON)) {
  446. Icon icon = (Icon) e.getNewValue();
  447. menuItem.setIcon(icon);
  448. menuItem.invalidate();
  449. menuItem.repaint();
  450. }
  451. }
  452. public void setTarget(JMenuItem b) {
  453. this.menuItem = b;
  454. }
  455. }
  456. /**
  457. * Append a new separator to the end of the menu.
  458. */
  459. public void addSeparator()
  460. {
  461. ensurePopupMenuCreated();
  462. popupMenu.addSeparator();
  463. }
  464. /**
  465. * Insert a new menuitem with the specified text at a
  466. * given position.
  467. *
  468. * @param s the text for the menuitem to add
  469. * @param pos an int giving the position at which to add the
  470. * new menuitem
  471. */
  472. public void insert(String s, int pos) {
  473. if (pos < 0) {
  474. throw new IllegalArgumentException("index less than zero.");
  475. }
  476. ensurePopupMenuCreated();
  477. popupMenu.insert(new JMenuItem(s), pos);
  478. }
  479. /**
  480. * Insert the specified JMenuitem at a given position.
  481. *
  482. * @param mi the JMenuitem to add
  483. * @param pos an int giving the position at which to add the
  484. * new JMenuitem
  485. */
  486. public JMenuItem insert(JMenuItem mi, int pos) {
  487. if (pos < 0) {
  488. throw new IllegalArgumentException("index less than zero.");
  489. }
  490. AccessibleContext ac = mi.getAccessibleContext();
  491. ac.setAccessibleParent(this);
  492. ensurePopupMenuCreated();
  493. popupMenu.insert(mi, pos);
  494. return mi;
  495. }
  496. /**
  497. * Insert a new menuitem attached to the specified Action
  498. * object at a given position.
  499. *
  500. * @param a the Action object for the menuitem to add
  501. * @param pos an int giving the position at which to add the
  502. * new menuitem
  503. */
  504. public JMenuItem insert(Action a, int pos) {
  505. if (pos < 0) {
  506. throw new IllegalArgumentException("index less than zero.");
  507. }
  508. ensurePopupMenuCreated();
  509. JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME),
  510. (Icon)a.getValue(Action.SMALL_ICON));
  511. mi.setHorizontalTextPosition(JButton.RIGHT);
  512. mi.setVerticalTextPosition(JButton.CENTER);
  513. mi.setEnabled(a.isEnabled());
  514. mi.addActionListener(a);
  515. popupMenu.insert(mi, pos);
  516. registerMenuItemForAction(mi, a);
  517. return mi;
  518. }
  519. private void registerMenuItemForAction(JMenuItem mi, Action a) {
  520. PropertyChangeListener actionPropertyChangeListener =
  521. createActionChangeListener(mi);
  522. if (listenerRegistry == null) {
  523. listenerRegistry = new Hashtable();
  524. }
  525. listenerRegistry.put(mi, actionPropertyChangeListener);
  526. listenerRegistry.put(actionPropertyChangeListener, a);
  527. a.addPropertyChangeListener(actionPropertyChangeListener);
  528. }
  529. private void unregisterMenuItemForAction(JMenuItem item) {
  530. if (listenerRegistry != null) {
  531. ActionChangedListener p = (ActionChangedListener)listenerRegistry.remove(item);
  532. if (p!=null) {
  533. Action a = (Action)listenerRegistry.remove(p);
  534. if (a!=null) {
  535. item.removeActionListener(a);
  536. a.removePropertyChangeListener(p);
  537. }
  538. p.setTarget(null);
  539. }
  540. }
  541. }
  542. private void clearListenerRegistry() {
  543. // Some GCs we have run across are not so good at removing
  544. // circular references, so we'll null these out ourselves:
  545. if (listenerRegistry!=null) {
  546. for (Enumeration e = listenerRegistry.keys() ; e.hasMoreElements() ;) {
  547. Object key = e.nextElement();
  548. if (key == this) { // Only snarf our own listings! 4190759
  549. JMenuItem item = (JMenuItem)key;
  550. ActionChangedListener p =
  551. (ActionChangedListener)listenerRegistry.get(item);
  552. if (p!=null) {
  553. Action a = (Action)listenerRegistry.get(p);
  554. if (a!=null) {
  555. item.removeActionListener(a);
  556. a.removePropertyChangeListener(p);
  557. }
  558. p.setTarget(null);
  559. }
  560. }
  561. }
  562. listenerRegistry.clear();
  563. }
  564. }
  565. /**
  566. * Inserts a separator at the specified position.
  567. *
  568. * @param index an int giving the position at which to
  569. * insert the menu separator
  570. * @exception IllegalArgumentException if the value of
  571. * <code>index</code> is less than 0.
  572. */
  573. public void insertSeparator(int index) {
  574. if (index < 0) {
  575. throw new IllegalArgumentException("index less than zero.");
  576. }
  577. ensurePopupMenuCreated();
  578. popupMenu.insert( new JPopupMenu.Separator(), index );
  579. }
  580. /**
  581. * Returns the JMenuItem at the specified position.
  582. * If the specified position contains a separator, this JMenu
  583. * is returned.
  584. *
  585. * @param pos an int giving the position
  586. * @exception IllegalArgumentException if the value of
  587. * <code>index</code> is less than 0.
  588. */
  589. public JMenuItem getItem(int pos) {
  590. if (pos < 0) {
  591. throw new IllegalArgumentException("index less than zero.");
  592. }
  593. Component c = getMenuComponent(pos);
  594. if (c instanceof JMenuItem) {
  595. JMenuItem mi = (JMenuItem) c;
  596. return mi;
  597. }
  598. // 4173633
  599. return null;
  600. }
  601. /**
  602. * Returns the number of items on the menu, including separators.
  603. * This method is included for AWT compatibility.
  604. *
  605. * @return an int equal to the number of items on the menu
  606. * @see #getMenuComponentCount
  607. */
  608. public int getItemCount() {
  609. return getMenuComponentCount();
  610. }
  611. /**
  612. * Returns true if the menu can be torn off.
  613. *
  614. * @return true if the menu can be torn off, else false
  615. */
  616. public boolean isTearOff() {
  617. throw new Error("boolean isTearOff() {} not yet implemented");
  618. }
  619. /**
  620. * Removes the specified menu item from this menu.
  621. *
  622. * @param item the JMenuItem to be removed from the menu
  623. */
  624. public void remove(JMenuItem item) {
  625. if (popupMenu != null)
  626. popupMenu.remove(item);
  627. unregisterMenuItemForAction(item);
  628. }
  629. /**
  630. * Removes the menu item at the specified index from this menu.
  631. *
  632. * @param index the position of the item to be removed.
  633. * @exception IllegalArgumentException if the value of
  634. * <code>index</code> is less than 0.
  635. */
  636. public void remove(int pos) {
  637. if (pos < 0) {
  638. throw new IllegalArgumentException("index less than zero.");
  639. }
  640. if (pos > getItemCount()) {
  641. throw new IllegalArgumentException("index greater than the number of items.");
  642. }
  643. Component c = getItem(pos);
  644. if (c instanceof JMenuItem)
  645. unregisterMenuItemForAction((JMenuItem)c);
  646. if (popupMenu != null)
  647. popupMenu.remove(pos);
  648. }
  649. /**
  650. * Removes the Component from this menu.
  651. *
  652. * @param c the component to be removed
  653. */
  654. public void remove(Component c) {
  655. if (popupMenu != null)
  656. popupMenu.remove(c);
  657. }
  658. /**
  659. * Remove all menu items from this menu.
  660. */
  661. public void removeAll() {
  662. if (popupMenu != null)
  663. popupMenu.removeAll();
  664. clearListenerRegistry();
  665. }
  666. /**
  667. * Returns the number of components on the menu.
  668. *
  669. * @return an int -- the number of components on the menu
  670. */
  671. public int getMenuComponentCount() {
  672. int componentCount = 0;
  673. if (popupMenu != null)
  674. componentCount = popupMenu.getComponentCount();
  675. return componentCount;
  676. }
  677. /**
  678. * Returns the component at position n
  679. *
  680. * @param n the position of the component to be returned
  681. */
  682. public Component getMenuComponent(int n) {
  683. if (popupMenu != null)
  684. return popupMenu.getComponent(n);
  685. return null;
  686. }
  687. /**
  688. * Returns an array of the menu's subcomponents
  689. *
  690. * @return an array of Components
  691. */
  692. public Component[] getMenuComponents() {
  693. if (popupMenu != null)
  694. return popupMenu.getComponents();
  695. return new Component[0];
  696. }
  697. /**
  698. * Returns true if the menu is a 'top-level menu', that is, if it is
  699. * the direct child of a menubar.
  700. *
  701. * @return true if the menu is activated from the menu bar,
  702. * false if the menu is activated from a menu item
  703. * on another menu
  704. */
  705. public boolean isTopLevelMenu() {
  706. if (getParent() instanceof JMenuBar)
  707. return true;
  708. return false;
  709. }
  710. /**
  711. * Returns true if the specified component exists in the
  712. * submenu hierarchy.
  713. *
  714. * @param c the Component to be tested
  715. * @return true if the component exists
  716. */
  717. public boolean isMenuComponent(Component c) {
  718. // Are we in the MenuItem part of the menu
  719. if (c == this)
  720. return true;
  721. // Are we in the PopupMenu?
  722. if (c instanceof JPopupMenu) {
  723. JPopupMenu comp = (JPopupMenu) c;
  724. if (comp == this.getPopupMenu())
  725. return true;
  726. }
  727. // Are we in a Component on the PopupMenu
  728. int ncomponents = this.getMenuComponentCount();
  729. Component[] component = this.getMenuComponents();
  730. for (int i = 0 ; i < ncomponents ; i++) {
  731. Component comp = component[i];
  732. // Are we in the current component?
  733. if (comp == c)
  734. return true;
  735. // Hmmm, what about Non-menu containers?
  736. // Recursive call for the Menu case
  737. if (comp instanceof JMenu) {
  738. JMenu subMenu = (JMenu) comp;
  739. if (subMenu.isMenuComponent(c))
  740. return true;
  741. }
  742. }
  743. return false;
  744. }
  745. /*
  746. * Returns a point in the coordinate space of this menu's popupmenu
  747. * which corresponds to the point p in the menu's coordinate space.
  748. *
  749. * @param p the point to be translated
  750. */
  751. private Point translateToPopupMenu(Point p) {
  752. return translateToPopupMenu(p.x, p.y);
  753. }
  754. /*
  755. * Returns a point in the coordinate space of this menu's popupmenu
  756. * which corresponds to the point (x,y) in the menu's coordinate space.
  757. * @param x the x coordinate of the point to be translated
  758. * @param y the y coordinate of the point to be translated
  759. */
  760. private Point translateToPopupMenu(int x, int y) {
  761. int newX;
  762. int newY;
  763. if (getParent() instanceof JPopupMenu) {
  764. newX = x - getSize().width;
  765. newY = y;
  766. } else {
  767. newX = x;
  768. newY = y - getSize().height;
  769. }
  770. return new Point(newX, newY);
  771. }
  772. /**
  773. * Returns the popupmenu associated with this menu
  774. */
  775. public JPopupMenu getPopupMenu() {
  776. ensurePopupMenuCreated();
  777. return popupMenu;
  778. }
  779. /**
  780. * Add a listener for menu events
  781. *
  782. * @param l the listener to be added
  783. */
  784. public void addMenuListener(MenuListener l) {
  785. listenerList.add(MenuListener.class, l);
  786. }
  787. /**
  788. * Remove a listener for menu events
  789. *
  790. * @param l the listener to be removed
  791. */
  792. public void removeMenuListener(MenuListener l) {
  793. listenerList.remove(MenuListener.class, l);
  794. }
  795. /**
  796. * Notify all listeners that have registered interest for
  797. * notification on this event type. The event instance
  798. * is lazily created using the parameters passed into
  799. * the fire method.
  800. *
  801. * @see EventListenerList
  802. */
  803. protected void fireMenuSelected() {
  804. // Guaranteed to return a non-null array
  805. Object[] listeners = listenerList.getListenerList();
  806. // Process the listeners last to first, notifying
  807. // those that are interested in this event
  808. for (int i = listeners.length-2; i>=0; i-=2) {
  809. if (listeners[i]==MenuListener.class) {
  810. if (listeners[i+1]== null) {
  811. throw new Error(getText() +" has a NULL Listener!! " + i);
  812. } else {
  813. // Lazily create the event:
  814. if (menuEvent == null)
  815. menuEvent = new MenuEvent(this);
  816. ((MenuListener)listeners[i+1]).menuSelected(menuEvent);
  817. }
  818. }
  819. }
  820. }
  821. /**
  822. * Notify all listeners that have registered interest for
  823. * notification on this event type. The event instance
  824. * is lazily created using the parameters passed into
  825. * the fire method.
  826. *
  827. * @see EventListenerList
  828. */
  829. protected void fireMenuDeselected() {
  830. // Guaranteed to return a non-null array
  831. Object[] listeners = listenerList.getListenerList();
  832. // Process the listeners last to first, notifying
  833. // those that are interested in this event
  834. for (int i = listeners.length-2; i>=0; i-=2) {
  835. if (listeners[i]==MenuListener.class) {
  836. if (listeners[i+1]== null) {
  837. throw new Error(getText() +" has a NULL Listener!! " + i);
  838. } else {
  839. // Lazily create the event:
  840. if (menuEvent == null)
  841. menuEvent = new MenuEvent(this);
  842. ((MenuListener)listeners[i+1]).menuDeselected(menuEvent);
  843. }
  844. }
  845. }
  846. }
  847. /**
  848. * Notify all listeners that have registered interest for
  849. * notification on this event type. The event instance
  850. * is lazily created using the parameters passed into
  851. * the fire method.
  852. *
  853. * @see EventListenerList
  854. */
  855. protected void fireMenuCanceled() {
  856. // Guaranteed to return a non-null array
  857. Object[] listeners = listenerList.getListenerList();
  858. // Process the listeners last to first, notifying
  859. // those that are interested in this event
  860. for (int i = listeners.length-2; i>=0; i-=2) {
  861. if (listeners[i]==MenuListener.class) {
  862. if (listeners[i+1]== null) {
  863. throw new Error(getText() +" has a NULL Listener!! "
  864. + i);
  865. } else {
  866. // Lazily create the event:
  867. if (menuEvent == null)
  868. menuEvent = new MenuEvent(this);
  869. ((MenuListener)listeners[i+1]).menuCanceled(menuEvent);
  870. }
  871. }
  872. }
  873. }
  874. class MenuChangeListener implements ChangeListener, Serializable {
  875. boolean isSelected = false;
  876. public void stateChanged(ChangeEvent e) {
  877. ButtonModel model = (ButtonModel) e.getSource();
  878. boolean modelSelected = model.isSelected();
  879. if (modelSelected != isSelected) {
  880. if (modelSelected == true) {
  881. fireMenuSelected();
  882. } else {
  883. fireMenuDeselected();
  884. }
  885. isSelected = modelSelected;
  886. }
  887. }
  888. }
  889. private ChangeListener createMenuChangeListener() {
  890. return new MenuChangeListener();
  891. }
  892. /**
  893. * Create a window-closing listener for the popup.
  894. *
  895. * @param p the JPopupMenu
  896. * @see WinListener
  897. */
  898. protected WinListener createWinListener(JPopupMenu p) {
  899. return new WinListener(p);
  900. }
  901. /**
  902. * A listener class that watches for a popup window closing.
  903. * When the popup is closing, the listener deselects the menu.
  904. * <p>
  905. * <strong>Warning:</strong>
  906. * Serialized objects of this class will not be compatible with
  907. * future Swing releases. The current serialization support is appropriate
  908. * for short term storage or RMI between applications running the same
  909. * version of Swing. A future release of Swing will provide support for
  910. * long term persistence.
  911. */
  912. protected class WinListener extends WindowAdapter implements Serializable {
  913. JPopupMenu popupMenu;
  914. /**
  915. * Create the window listener for the specified popup.
  916. */
  917. public WinListener(JPopupMenu p) {
  918. this.popupMenu = p;
  919. }
  920. /**
  921. * Deselect the menu when the popup is closed from outside.
  922. */
  923. public void windowClosing(WindowEvent e) {
  924. setSelected(false);
  925. }
  926. }
  927. /**
  928. * Messaged when the menubar selection changes to activate or
  929. * deactivate this menu.
  930. * Overrides <code>JMenuItem.menuSelectionChanged</code>.
  931. *
  932. * @param isIncluded true if this menu is active, false if
  933. * it is not
  934. */
  935. public void menuSelectionChanged(boolean isIncluded) {
  936. setSelected(isIncluded);
  937. }
  938. /**
  939. * Returns an array containing the sub-menu components for this menu component
  940. * @return an array of MenuElement objects
  941. */
  942. public MenuElement[] getSubElements() {
  943. if(popupMenu == null)
  944. return new MenuElement[0];
  945. else {
  946. MenuElement result[] = new MenuElement[1];
  947. result[0] = popupMenu;
  948. return result;
  949. }
  950. }
  951. // implements javax.swing.MenuElement
  952. /**
  953. * This method returns the java.awt.Component used to paint this MenuElement.
  954. * The returned component is used to convert events and detect if an event is inside
  955. * a menu component.
  956. */
  957. public Component getComponent() {
  958. return this;
  959. }
  960. /**
  961. * setAccelerator() is not defined for JMenu. Use setMnemonic() instead.
  962. *
  963. * @beaninfo
  964. * description: The keystroke combination which will invoke the JMenuItem's
  965. * actionlisteners without navigating the menu hierarchy
  966. * hidden: true
  967. */
  968. public void setAccelerator(KeyStroke keyStroke) {
  969. throw new Error("setAccelerator() is not defined for JMenu. Use setMnemonic() instead.");
  970. }
  971. /**
  972. *
  973. */
  974. protected void processKeyEvent(KeyEvent e) {
  975. /* fix for bug 4213634 */
  976. boolean createMenuEvent=false;
  977. switch (e.getID()) {
  978. case KeyEvent.KEY_PRESSED:
  979. if (isSelected())
  980. createMenuEvent = receivedKeyPressed = true;
  981. else receivedKeyPressed = false;
  982. break;
  983. case KeyEvent.KEY_RELEASED:
  984. if (receivedKeyPressed) {
  985. receivedKeyPressed = false;
  986. createMenuEvent = true;
  987. }
  988. break;
  989. default:
  990. createMenuEvent = receivedKeyPressed;
  991. break;
  992. }
  993. if (createMenuEvent && isSelected()) {
  994. MenuSelectionManager.defaultManager().processKeyEvent(e);
  995. }
  996. if(e.isConsumed())
  997. return;
  998. /* The "if" block below fixes bug #4108907.
  999. Without this code, opened menus that
  1000. weren't interested in TAB key events (most menus are not) would
  1001. allow such events to propagate up until a component was found
  1002. that was interested in the event. This would often result in
  1003. the focus being moved to another component as a result of the
  1004. TAB, while the menu stayed open. The behavior that is most
  1005. probably desired is that menus are modal, and thus consume
  1006. all keyboard events while they are open. This is implemented
  1007. by the inner "if" clause. But if the desired behavior on TABs
  1008. is that the menu should close and allow the focus to move,
  1009. the "else" clause takes care of that. Note that this is probably
  1010. not the right way to implement that behavior; instead, the menu
  1011. should unpost whenever it looses focus, which would also fix
  1012. another bug: 4156858.
  1013. The fact that one has to special-case TABS here in JMenu code
  1014. also offends me...
  1015. hania 23 July 1998 */
  1016. if(isSelected() && (e.getKeyCode() == KeyEvent.VK_TAB
  1017. || e.getKeyChar() == '\t')) {
  1018. if ((Boolean) UIManager.get("Menu.consumesTabs") == Boolean.TRUE) {
  1019. e.consume();
  1020. return;
  1021. } else {
  1022. MenuSelectionManager.defaultManager().clearSelectedPath();
  1023. }
  1024. }
  1025. super.processKeyEvent(e);
  1026. }
  1027. /* fix for bug 4213634, on win32, JMenu lost focus when
  1028. is not showing. On solaris, it still has focus when is not
  1029. showing
  1030. */
  1031. protected void processFocusEvent(FocusEvent e) {
  1032. switch (e.getID()) {
  1033. case FocusEvent.FOCUS_LOST:
  1034. receivedKeyPressed = false;
  1035. break;
  1036. default:
  1037. break;
  1038. }
  1039. super.processFocusEvent(e);
  1040. }
  1041. /**
  1042. * Programatically perform a "click". This overrides the method
  1043. * AbstractButton.doClick(int) in order to make the menu pop up.
  1044. */
  1045. public void doClick(int pressTime) {
  1046. MenuElement me[] = buildMenuElementArray(this);
  1047. MenuSelectionManager.defaultManager().setSelectedPath(me);
  1048. }
  1049. /*
  1050. * Build an array of menu elements - from my PopupMenu to the root
  1051. * JMenuBar
  1052. */
  1053. private MenuElement[] buildMenuElementArray(JMenu leaf) {
  1054. Vector elements = new Vector();
  1055. Component current = leaf.getPopupMenu();
  1056. JPopupMenu pop;
  1057. JMenu menu;
  1058. JMenuBar bar;
  1059. while (true) {
  1060. if (current instanceof JPopupMenu) {
  1061. pop = (JPopupMenu) current;
  1062. elements.insertElementAt(pop, 0);
  1063. current = pop.getInvoker();
  1064. } else if (current instanceof JMenu) {
  1065. menu = (JMenu) current;
  1066. elements.insertElementAt(menu, 0);
  1067. current = menu.getParent();
  1068. } else if (current instanceof JMenuBar) {
  1069. bar = (JMenuBar) current;
  1070. elements.insertElementAt(bar, 0);
  1071. MenuElement me[] = new MenuElement[elements.size()];
  1072. elements.copyInto(me);
  1073. return me;
  1074. }
  1075. }
  1076. }
  1077. /**
  1078. * See readObject() and writeObject() in JComponent for more
  1079. * information about serialization in Swing.
  1080. */
  1081. private void writeObject(ObjectOutputStream s) throws IOException {
  1082. s.defaultWriteObject();
  1083. if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  1084. ui.installUI(this);
  1085. }
  1086. }
  1087. /**
  1088. * Returns a string representation of this JMenu. This method
  1089. * is intended to be used only for debugging purposes, and the
  1090. * content and format of the returned string may vary between
  1091. * implementations. The returned string may be empty but may not
  1092. * be <code>null</code>.
  1093. *
  1094. * @return a string representation of this JMenu.
  1095. */
  1096. protected String paramString() {
  1097. return super.paramString();
  1098. }
  1099. /////////////////
  1100. // Accessibility support
  1101. ////////////////
  1102. /**
  1103. * Get the AccessibleContext associated with this JComponent
  1104. *
  1105. * @return the AccessibleContext of this JComponent
  1106. */
  1107. public AccessibleContext getAccessibleContext() {
  1108. if (accessibleContext == null) {
  1109. accessibleContext = new AccessibleJMenu();
  1110. }
  1111. return accessibleContext;
  1112. }
  1113. /**
  1114. * The class used to obtain the accessible role for this object.
  1115. * <p>
  1116. * <strong>Warning:</strong>
  1117. * Serialized objects of this class will not be compatible with
  1118. * future Swing releases. The current serialization support is appropriate
  1119. * for short term storage or RMI between applications running the same
  1120. * version of Swing. A future release of Swing will provide support for
  1121. * long term persistence.
  1122. */
  1123. protected class AccessibleJMenu extends AccessibleJMenuItem
  1124. implements AccessibleSelection {
  1125. /**
  1126. * Returns the number of accessible children in the object. If all
  1127. * of the children of this object implement Accessible, than this
  1128. * method should return the number of children of this object.
  1129. *
  1130. * @return the number of accessible children in the object.
  1131. */
  1132. public int getAccessibleChildrenCount() {
  1133. Component[] children = getMenuComponents();
  1134. int count = 0;
  1135. for (int j = 0; j < children.length; j++) {
  1136. if (children[j] instanceof Accessible) {
  1137. count++;
  1138. }
  1139. }
  1140. return count;
  1141. }
  1142. /**
  1143. * Return the nth Accessible child of the object.
  1144. *
  1145. * @param i zero-based index of child
  1146. * @return the nth Accessible child of the object
  1147. */
  1148. public Accessible getAccessibleChild(int i) {
  1149. Component[] children = getMenuComponents();
  1150. int count = 0;
  1151. for (int j = 0; j < children.length; j++) {
  1152. if (children[j] instanceof Accessible) {
  1153. if (count == i) {
  1154. if (children[j] instanceof JComponent) {
  1155. // FIXME: [[[WDW - probably should set this when
  1156. // the component is added to the menu. I tried
  1157. // to do this in most cases, but the separators
  1158. // added by addSeparator are hard to get to.]]]
  1159. AccessibleContext ac = ((Accessible) children[j]).getAccessibleContext();
  1160. ac.setAccessibleParent(JMenu.this);
  1161. }
  1162. return (Accessible) children[j];
  1163. } else {
  1164. count++;
  1165. }
  1166. }
  1167. }
  1168. return null;
  1169. }
  1170. /**
  1171. * Get the role of this object.
  1172. *
  1173. * @return an instance of AccessibleRole describing the role of the
  1174. * object
  1175. * @see AccessibleRole
  1176. */
  1177. public AccessibleRole getAccessibleRole() {
  1178. return AccessibleRole.MENU;
  1179. }
  1180. /**
  1181. * Get the AccessibleSelection associated with this object if one
  1182. * exists. Otherwise return null.
  1183. */
  1184. public AccessibleSelection getAccessibleSelection() {
  1185. return this;
  1186. }
  1187. /**
  1188. * Returns 1 if a sub-menu is currently selected in this menu.
  1189. *
  1190. * @return 1 if a menu is currently selected, else 0
  1191. */
  1192. public int getAccessibleSelectionCount() {
  1193. MenuElement me[] =
  1194. MenuSelectionManager.defaultManager().getSelectedPath();
  1195. if (me != null) {
  1196. for (int i = 0; i < me.length; i++) {
  1197. if (me[i] == JMenu.this) { // this menu is selected
  1198. if (i+1 < me.length) {
  1199. return 1;
  1200. }
  1201. }
  1202. }
  1203. }
  1204. return 0;
  1205. }
  1206. /**
  1207. * Returns the currently selected sub-menu if one is selected,
  1208. * otherwise null (there can only be one selection, and it can
  1209. * only be a sub-menu, as otherwise menu items don't remain
  1210. * selected).
  1211. */
  1212. public Accessible getAccessibleSelection(int i) {
  1213. // if i is a sub-menu & popped, return it
  1214. if (i < 0 || i >= getItemCount()) {
  1215. return null;
  1216. }
  1217. MenuElement me[] =
  1218. MenuSelectionManager.defaultManager().getSelectedPath();
  1219. if (me != null) {
  1220. for (int j = 0; j < me.length; j++) {
  1221. if (me[j] == JMenu.this) { // this menu is selected
  1222. // so find the next JMenuItem in the MenuElement
  1223. // array, and return it!
  1224. while (++j < me.length) {
  1225. if (me[j] instanceof JMenuItem) {
  1226. return (Accessible) me[j];
  1227. }
  1228. }
  1229. }
  1230. }
  1231. }
  1232. return null;
  1233. }
  1234. /**
  1235. * Returns true if the current child of this object is selected.
  1236. * (i.e. if this child is a pop-ed up sub-menu)
  1237. *
  1238. * @param i the zero-based index of the child in this Accessible
  1239. * object.
  1240. * @see AccessibleContext#getAccessibleChild
  1241. */
  1242. public boolean isAccessibleChildSelected(int i) {
  1243. // if i is a sub-menu and is pop-ed up, return true, else false
  1244. MenuElement me[] =
  1245. MenuSelectionManager.defaultManager().getSelectedPath();
  1246. if (me != null) {
  1247. JMenuItem mi = JMenu.this.getItem(i);
  1248. for (int j = 0; j < me.length; j++) {
  1249. if (me[j] == mi) {
  1250. return true;
  1251. }
  1252. }
  1253. }
  1254. return false;
  1255. }
  1256. /**
  1257. * Selects the nth menu in the menu. If that item is a sub-menu,
  1258. * it will pop up in response. If a different item is already
  1259. * popped up, this will force it to close. If this is a sub-menu
  1260. * that is already poppoed up (selected), this method has no
  1261. * effect.
  1262. *
  1263. * @param i the zero-based index of selectable items
  1264. * @see #getAccessibleStateSet
  1265. */
  1266. public void addAccessibleSelection(int i) {
  1267. if (i < 0 || i >= getItemCount()) {
  1268. return;
  1269. }
  1270. JMenuItem mi = getItem(i);
  1271. if (mi != null) {
  1272. if (mi instanceof JMenu) {
  1273. MenuElement me[] = buildMenuElementArray((JMenu) mi);
  1274. MenuSelectionManager.defaultManager().setSelectedPath(me);
  1275. } else {
  1276. mi.doClick();
  1277. MenuSelectionManager.defaultManager().setSelectedPath(null);
  1278. }
  1279. }
  1280. }
  1281. /**
  1282. * Removes the nth item from the selection. In general, menus
  1283. * can only have one item within them selected at a time
  1284. * (e.g. one sub-menu popped open).
  1285. *
  1286. * @param i the zero-based index of the selected item
  1287. */
  1288. public void removeAccessibleSelection(int i) {
  1289. if (i < 0 || i >= getItemCount()) {
  1290. return;
  1291. }
  1292. JMenuItem mi = getItem(i);
  1293. if (mi != null && mi instanceof JMenu) {
  1294. if (((JMenu) mi).isSelected()) {
  1295. MenuElement old[] =
  1296. MenuSelectionManager.defaultManager().getSelectedPath();
  1297. MenuElement me[] = new MenuElement[old.length-1];
  1298. for (int j = 0; j < old.length -1; j++) {
  1299. me[j] = old[j];
  1300. }
  1301. MenuSelectionManager.defaultManager().setSelectedPath(me);
  1302. }
  1303. }
  1304. }
  1305. /**
  1306. * Clears the selection in the object, so that nothing in the
  1307. * object is selected. This will close any open sub-menu.
  1308. */
  1309. public void clearAccessibleSelection() {
  1310. // if this menu is selected, reset selection to only go
  1311. // to this menu; else do nothing
  1312. MenuElement old[] =
  1313. MenuSelectionManager.defaultManager().getSelectedPath();
  1314. if (old != null) {
  1315. for (int j = 0; j < old.length; j++) {
  1316. if (old[j] == JMenu.this) { // menu is in the selection!
  1317. MenuElement me[] = new MenuElement[j+1];
  1318. System.arraycopy(old, 0, me, 0, j);
  1319. me[j] = JMenu.this.getPopupMenu();
  1320. MenuSelectionManager.defaultManager().setSelectedPath(me);
  1321. }
  1322. }
  1323. }
  1324. }
  1325. /**
  1326. * Normally causes every selected item in the object to be selected
  1327. * if the object supports multiple selections. This method
  1328. * makes no sense in a menu bar, and so does nothing.
  1329. */
  1330. public void selectAllAccessibleSelection() {
  1331. }
  1332. } // inner class AccessibleJMenu
  1333. }