1. /*
  2. * @(#)JMenuBar.java 1.94 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 javax.swing;
  8. import java.awt.Component;
  9. import java.awt.Dimension;
  10. import java.awt.Graphics;
  11. import java.awt.Insets;
  12. import java.awt.Point;
  13. import java.awt.Rectangle;
  14. import java.awt.event.*;
  15. import java.util.Vector;
  16. import java.util.Enumeration;
  17. import java.io.Serializable;
  18. import java.io.ObjectOutputStream;
  19. import java.io.ObjectInputStream;
  20. import java.io.IOException;
  21. import javax.swing.event.*;
  22. import javax.swing.border.Border;
  23. import javax.swing.plaf.*;
  24. import javax.accessibility.*;
  25. /**
  26. * An implementation of a menu bar. You add <code>JMenu</code> objects to the
  27. * menu bar to construct a menu. When the user selects a <code>JMenu</code>
  28. * object, its associated <code>JPopupMenu</code> is displayed, allowing the
  29. * user to select one of the <code>JMenuItems</code> on it.
  30. * <p>
  31. * For information and examples of using menu bars see
  32. * <a
  33. href="http://java.sun.com/docs/books/tutorial/uiswing/components/menu.html">How to Use Menus</a>,
  34. * a section in <em>The Java Tutorial.</em>
  35. * For the keyboard keys used by this component in the standard Look and
  36. * Feel (L&F) renditions, see the
  37. * <a href="doc-files/Key-Index.html#JMenuBar"><code>JMenuBar</code> key assignments</a>.
  38. * <p>
  39. * <strong>Warning:</strong>
  40. * Serialized objects of this class will not be compatible with
  41. * future Swing releases. The current serialization support is
  42. * appropriate for short term storage or RMI between applications running
  43. * the same version of Swing. As of 1.4, support for long term storage
  44. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  45. * has been added to the <code>java.beans</code> package.
  46. * Please see {@link java.beans.XMLEncoder}.
  47. *
  48. * @beaninfo
  49. * attribute: isContainer true
  50. * description: A container for holding and displaying menus.
  51. *
  52. * @version 1.94 01/23/03
  53. * @author Georges Saab
  54. * @author David Karlton
  55. * @author Arnaud Weber
  56. * @see JMenu
  57. * @see JPopupMenu
  58. * @see JMenuItem
  59. */
  60. public class JMenuBar extends JComponent implements Accessible,MenuElement
  61. {
  62. /**
  63. * @see #getUIClassID
  64. * @see #readObject
  65. */
  66. private static final String uiClassID = "MenuBarUI";
  67. /*
  68. * Model for the selected subcontrol.
  69. */
  70. private transient SingleSelectionModel selectionModel;
  71. private boolean paintBorder = true;
  72. private Insets margin = null;
  73. /* diagnostic aids -- should be false for production builds. */
  74. private static final boolean TRACE = false; // trace creates and disposes
  75. private static final boolean VERBOSE = false; // show reuse hits/misses
  76. private static final boolean DEBUG = false; // show bad params, misc.
  77. /**
  78. * Creates a new menu bar.
  79. */
  80. public JMenuBar() {
  81. super();
  82. setFocusTraversalKeysEnabled(false);
  83. setSelectionModel(new DefaultSingleSelectionModel());
  84. updateUI();
  85. }
  86. /**
  87. * Returns the menubar's current UI.
  88. * @see #setUI
  89. */
  90. public MenuBarUI getUI() {
  91. return (MenuBarUI)ui;
  92. }
  93. /**
  94. * Sets the L&F object that renders this component.
  95. *
  96. * @param ui the new MenuBarUI L&F object
  97. * @see UIDefaults#getUI
  98. * @beaninfo
  99. * bound: true
  100. * hidden: true
  101. * attribute: visualUpdate true
  102. * description: The UI object that implements the Component's LookAndFeel.
  103. */
  104. public void setUI(MenuBarUI ui) {
  105. super.setUI(ui);
  106. }
  107. /**
  108. * Resets the UI property with a value from the current look and feel.
  109. *
  110. * @see JComponent#updateUI
  111. */
  112. public void updateUI() {
  113. setUI((MenuBarUI)UIManager.getUI(this));
  114. }
  115. /**
  116. * Returns the name of the L&F class that renders this component.
  117. *
  118. * @return the string "MenuBarUI"
  119. * @see JComponent#getUIClassID
  120. * @see UIDefaults#getUI
  121. */
  122. public String getUIClassID() {
  123. return uiClassID;
  124. }
  125. /**
  126. * Returns the model object that handles single selections.
  127. *
  128. * @return the <code>SingleSelectionModel</code> property
  129. * @see SingleSelectionModel
  130. */
  131. public SingleSelectionModel getSelectionModel() {
  132. return selectionModel;
  133. }
  134. /**
  135. * Sets the model object to handle single selections.
  136. *
  137. * @param model the <code>SingleSelectionModel</code> to use
  138. * @see SingleSelectionModel
  139. * @beaninfo
  140. * bound: true
  141. * description: The selection model, recording which child is selected.
  142. */
  143. public void setSelectionModel(SingleSelectionModel model) {
  144. SingleSelectionModel oldValue = selectionModel;
  145. this.selectionModel = model;
  146. firePropertyChange("selectionModel", oldValue, selectionModel);
  147. }
  148. /**
  149. * Appends the specified menu to the end of the menu bar.
  150. *
  151. * @param c the <code>JMenu</code> component to add
  152. * @return the menu component
  153. */
  154. public JMenu add(JMenu c) {
  155. super.add(c);
  156. return c;
  157. }
  158. /**
  159. * Returns the menu at the specified position in the menu bar.
  160. *
  161. * @param index an integer giving the position in the menu bar, where
  162. * 0 is the first position
  163. * @return the <code>JMenu</code> at that position, or <code>null</code> if
  164. * if there is no <code>JMenu</code> at that position (ie. if
  165. * it is a <code>JMenuItem</code>)
  166. */
  167. public JMenu getMenu(int index) {
  168. Component c = getComponentAtIndex(index);
  169. if (c instanceof JMenu)
  170. return (JMenu) c;
  171. return null;
  172. }
  173. /**
  174. * Returns the number of items in the menu bar.
  175. *
  176. * @return the number of items in the menu bar
  177. */
  178. public int getMenuCount() {
  179. return getComponentCount();
  180. }
  181. /**
  182. * Sets the help menu that appears when the user selects the
  183. * "help" option in the menu bar. This method is not yet implemented
  184. * and will throw an exception.
  185. *
  186. * @param menu the JMenu that delivers help to the user
  187. */
  188. public void setHelpMenu(JMenu menu) {
  189. throw new Error("setHelpMenu() not yet implemented.");
  190. }
  191. /**
  192. * Gets the help menu for the menu bar. This method is not yet
  193. * implemented and will throw an exception.
  194. *
  195. * @return the <code>JMenu</code> that delivers help to the user
  196. */
  197. public JMenu getHelpMenu() {
  198. throw new Error("getHelpMenu() not yet implemented.");
  199. }
  200. /**
  201. * Returns the component at the specified index.
  202. *
  203. * @param i an integer specifying the position, where 0 is first
  204. * @return the <code>Component</code> at the position,
  205. * or <code>null</code> for an invalid index
  206. * @deprecated replaced by <code>getComponent(int i)</code>
  207. */
  208. public Component getComponentAtIndex(int i) {
  209. return getComponent(i);
  210. }
  211. /**
  212. * Returns the index of the specified component.
  213. *
  214. * @param c the <code>Component</code> to find
  215. * @return an integer giving the component's position, where 0 is first;
  216. * or -1 if it can't be found
  217. */
  218. public int getComponentIndex(Component c) {
  219. int ncomponents = this.getComponentCount();
  220. Component[] component = this.getComponents();
  221. for (int i = 0 ; i < ncomponents ; i++) {
  222. Component comp = component[i];
  223. if (comp == c)
  224. return i;
  225. }
  226. return -1;
  227. }
  228. /**
  229. * Sets the currently selected component, producing a
  230. * a change to the selection model.
  231. *
  232. * @param sel the <code>Component</code> to select
  233. */
  234. public void setSelected(Component sel) {
  235. SingleSelectionModel model = getSelectionModel();
  236. int index = getComponentIndex(sel);
  237. model.setSelectedIndex(index);
  238. }
  239. /**
  240. * Returns true if the menu bar currently has a component selected.
  241. *
  242. * @return true if a selection has been made, else false
  243. */
  244. public boolean isSelected() {
  245. return selectionModel.isSelected();
  246. }
  247. /**
  248. * Returns true if the menu bars border should be painted.
  249. *
  250. * @return true if the border should be painted, else false
  251. */
  252. public boolean isBorderPainted() {
  253. return paintBorder;
  254. }
  255. /**
  256. * Sets whether the border should be painted.
  257. *
  258. * @param b if true and border property is not <code>null</code>,
  259. * the border is painted.
  260. * @see #isBorderPainted
  261. * @beaninfo
  262. * bound: true
  263. * attribute: visualUpdate true
  264. * description: Whether the border should be painted.
  265. */
  266. public void setBorderPainted(boolean b) {
  267. boolean oldValue = paintBorder;
  268. paintBorder = b;
  269. firePropertyChange("borderPainted", oldValue, paintBorder);
  270. if (b != oldValue) {
  271. revalidate();
  272. repaint();
  273. }
  274. }
  275. /**
  276. * Paints the menubar's border if <code>BorderPainted</code>
  277. * property is true.
  278. *
  279. * @param g the <code>Graphics</code> context to use for painting
  280. * @see JComponent#paint
  281. * @see JComponent#setBorder
  282. */
  283. protected void paintBorder(Graphics g) {
  284. if (isBorderPainted()) {
  285. super.paintBorder(g);
  286. }
  287. }
  288. /**
  289. * Sets the margin between the menubar's border and
  290. * its menus. Setting to <code>null</code> will cause the menubar to
  291. * use the default margins.
  292. *
  293. * @param m an Insets object containing the margin values
  294. * @see Insets
  295. * @beaninfo
  296. * bound: true
  297. * attribute: visualUpdate true
  298. * description: The space between the menubar's border and its contents
  299. */
  300. public void setMargin(Insets m) {
  301. Insets old = margin;
  302. this.margin = m;
  303. firePropertyChange("margin", old, m);
  304. if (old == null || !m.equals(old)) {
  305. revalidate();
  306. repaint();
  307. }
  308. }
  309. /**
  310. * Returns the margin between the menubar's border and
  311. * its menus. If there is no previous margin, it will create
  312. * a default margin with zero size.
  313. *
  314. * @return an <code>Insets</code> object containing the margin values
  315. * @see Insets
  316. */
  317. public Insets getMargin() {
  318. if(margin == null) {
  319. return new Insets(0,0,0,0);
  320. } else {
  321. return margin;
  322. }
  323. }
  324. /**
  325. * Implemented to be a <code>MenuElement</code> -- does nothing.
  326. *
  327. * @see #getSubElements
  328. */
  329. public void processMouseEvent(MouseEvent event,MenuElement path[],MenuSelectionManager manager) {
  330. }
  331. /**
  332. * Implemented to be a <code>MenuElement</code> -- does nothing.
  333. *
  334. * @see #getSubElements
  335. */
  336. public void processKeyEvent(KeyEvent e,MenuElement path[],MenuSelectionManager manager) {
  337. }
  338. /**
  339. * Implemented to be a <code>MenuElemen<code>t -- does nothing.
  340. *
  341. * @see #getSubElements
  342. */
  343. public void menuSelectionChanged(boolean isIncluded) {
  344. }
  345. /**
  346. * Implemented to be a <code>MenuElement</code> -- returns the
  347. * menus in this menu bar.
  348. * This is the reason for implementing the <code>MenuElement</code>
  349. * interface -- so that the menu bar can be treated the same as
  350. * other menu elements.
  351. * @return an array of menu items in the menu bar.
  352. */
  353. public MenuElement[] getSubElements() {
  354. MenuElement result[];
  355. Vector tmp = new Vector();
  356. int c = getComponentCount();
  357. int i;
  358. Component m;
  359. for(i=0 ; i < c ; i++) {
  360. m = getComponent(i);
  361. if(m instanceof MenuElement)
  362. tmp.addElement(m);
  363. }
  364. result = new MenuElement[tmp.size()];
  365. for(i=0,c=tmp.size() ; i < c ; i++)
  366. result[i] = (MenuElement) tmp.elementAt(i);
  367. return result;
  368. }
  369. /**
  370. * Implemented to be a <code>MenuElement</code>. Returns this object.
  371. *
  372. * @return the current <code>Component</code> (this)
  373. * @see #getSubElements
  374. */
  375. public Component getComponent() {
  376. return this;
  377. }
  378. /**
  379. * Returns a string representation of this <code>JMenuBar</code>.
  380. * This method
  381. * is intended to be used only for debugging purposes, and the
  382. * content and format of the returned string may vary between
  383. * implementations. The returned string may be empty but may not
  384. * be <code>null</code>.
  385. *
  386. * @return a string representation of this <code>JMenuBar</code>
  387. */
  388. protected String paramString() {
  389. String paintBorderString = (paintBorder ?
  390. "true" : "false");
  391. String marginString = (margin != null ?
  392. margin.toString() : "");
  393. return super.paramString() +
  394. ",margin=" + marginString +
  395. ",paintBorder=" + paintBorderString;
  396. }
  397. /////////////////
  398. // Accessibility support
  399. ////////////////
  400. /**
  401. * Gets the AccessibleContext associated with this JMenuBar.
  402. * For JMenuBars, the AccessibleContext takes the form of an
  403. * AccessibleJMenuBar.
  404. * A new AccessibleJMenuBar instance is created if necessary.
  405. *
  406. * @return an AccessibleJMenuBar that serves as the
  407. * AccessibleContext of this JMenuBar
  408. */
  409. public AccessibleContext getAccessibleContext() {
  410. if (accessibleContext == null) {
  411. accessibleContext = new AccessibleJMenuBar();
  412. }
  413. return accessibleContext;
  414. }
  415. /**
  416. * This class implements accessibility support for the
  417. * <code>JMenuBar</code> class. It provides an implementation of the
  418. * Java Accessibility API appropriate to menu bar user-interface
  419. * elements.
  420. * <p>
  421. * <strong>Warning:</strong>
  422. * Serialized objects of this class will not be compatible with
  423. * future Swing releases. The current serialization support is
  424. * appropriate for short term storage or RMI between applications running
  425. * the same version of Swing. As of 1.4, support for long term storage
  426. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  427. * has been added to the <code>java.beans</code> package.
  428. * Please see {@link java.beans.XMLEncoder}.
  429. */
  430. protected class AccessibleJMenuBar extends AccessibleJComponent
  431. implements AccessibleSelection {
  432. /**
  433. * Get the accessible state set of this object.
  434. *
  435. * @return an instance of AccessibleState containing the current state
  436. * of the object
  437. */
  438. public AccessibleStateSet getAccessibleStateSet() {
  439. AccessibleStateSet states = super.getAccessibleStateSet();
  440. return states;
  441. }
  442. /**
  443. * Get the role of this object.
  444. *
  445. * @return an instance of AccessibleRole describing the role of the
  446. * object
  447. */
  448. public AccessibleRole getAccessibleRole() {
  449. return AccessibleRole.MENU_BAR;
  450. }
  451. /**
  452. * Get the AccessibleSelection associated with this object. In the
  453. * implementation of the Java Accessibility API for this class,
  454. * return this object, which is responsible for implementing the
  455. * AccessibleSelection interface on behalf of itself.
  456. *
  457. * @return this object
  458. */
  459. public AccessibleSelection getAccessibleSelection() {
  460. return this;
  461. }
  462. /**
  463. * Returns 1 if a menu is currently selected in this menu bar.
  464. *
  465. * @return 1 if a menu is currently selected, else 0
  466. */
  467. public int getAccessibleSelectionCount() {
  468. if (isSelected()) {
  469. return 1;
  470. } else {
  471. return 0;
  472. }
  473. }
  474. /**
  475. * Returns the currently selected menu if one is selected,
  476. * otherwise null.
  477. */
  478. public Accessible getAccessibleSelection(int i) {
  479. if (isSelected()) {
  480. if (i != 0) { // single selection model for JMenuBar
  481. return null;
  482. }
  483. int j = getSelectionModel().getSelectedIndex();
  484. if (getComponentAtIndex(j) instanceof Accessible) {
  485. return (Accessible) getComponentAtIndex(j);
  486. }
  487. }
  488. return null;
  489. }
  490. /**
  491. * Returns true if the current child of this object is selected.
  492. *
  493. * @param i the zero-based index of the child in this Accessible
  494. * object.
  495. * @see AccessibleContext#getAccessibleChild
  496. */
  497. public boolean isAccessibleChildSelected(int i) {
  498. return (i == getSelectionModel().getSelectedIndex());
  499. }
  500. /**
  501. * Selects the nth menu in the menu bar, forcing it to
  502. * pop up. If another menu is popped up, this will force
  503. * it to close. If the nth menu is already selected, this
  504. * method has no effect.
  505. *
  506. * @param i the zero-based index of selectable items
  507. * @see #getAccessibleStateSet
  508. */
  509. public void addAccessibleSelection(int i) {
  510. // first close up any open menu
  511. int j = getSelectionModel().getSelectedIndex();
  512. if (i == j) {
  513. return;
  514. }
  515. if (j >= 0 && j < getMenuCount()) {
  516. JMenu menu = getMenu(j);
  517. if (menu != null) {
  518. MenuSelectionManager.defaultManager().setSelectedPath(null);
  519. // menu.setPopupMenuVisible(false);
  520. }
  521. }
  522. // now popup the new menu
  523. getSelectionModel().setSelectedIndex(i);
  524. JMenu menu = getMenu(i);
  525. if (menu != null) {
  526. MenuElement me[] = new MenuElement[3];
  527. me[0] = JMenuBar.this;
  528. me[1] = menu;
  529. me[2] = menu.getPopupMenu();
  530. MenuSelectionManager.defaultManager().setSelectedPath(me);
  531. // menu.setPopupMenuVisible(true);
  532. }
  533. }
  534. /**
  535. * Removes the nth selected item in the object from the object's
  536. * selection. If the nth item isn't currently selected, this
  537. * method has no effect. Otherwise, it closes the popup menu.
  538. *
  539. * @param i the zero-based index of selectable items
  540. */
  541. public void removeAccessibleSelection(int i) {
  542. if (i >= 0 && i < getMenuCount()) {
  543. JMenu menu = getMenu(i);
  544. if (menu != null) {
  545. MenuSelectionManager.defaultManager().setSelectedPath(null);
  546. // menu.setPopupMenuVisible(false);
  547. }
  548. getSelectionModel().setSelectedIndex(-1);
  549. }
  550. }
  551. /**
  552. * Clears the selection in the object, so that nothing in the
  553. * object is selected. This will close any open menu.
  554. */
  555. public void clearAccessibleSelection() {
  556. int i = getSelectionModel().getSelectedIndex();
  557. if (i >= 0 && i < getMenuCount()) {
  558. JMenu menu = getMenu(i);
  559. if (menu != null) {
  560. MenuSelectionManager.defaultManager().setSelectedPath(null);
  561. // menu.setPopupMenuVisible(false);
  562. }
  563. }
  564. getSelectionModel().setSelectedIndex(-1);
  565. }
  566. /**
  567. * Normally causes every selected item in the object to be selected
  568. * if the object supports multiple selections. This method
  569. * makes no sense in a menu bar, and so does nothing.
  570. */
  571. public void selectAllAccessibleSelection() {
  572. }
  573. } // internal class AccessibleJMenuBar
  574. /**
  575. * Subclassed to check all the child menus.
  576. */
  577. protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
  578. int condition, boolean pressed) {
  579. // See if we have a local binding.
  580. boolean retValue = super.processKeyBinding(ks, e, condition, pressed);
  581. if (!retValue) {
  582. MenuElement[] subElements = getSubElements();
  583. for (int i=0; i<subElements.length; i++) {
  584. if (processBindingForKeyStrokeRecursive(
  585. subElements[i], ks, e, condition, pressed)) {
  586. return true;
  587. }
  588. }
  589. }
  590. return retValue;
  591. }
  592. static boolean processBindingForKeyStrokeRecursive(MenuElement elem,
  593. KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
  594. if (elem == null) {
  595. return false;
  596. }
  597. Component c = elem.getComponent();
  598. if (c != null && c instanceof JComponent &&
  599. ((JComponent)c).processKeyBinding(ks, e, condition, pressed)) {
  600. return true;
  601. }
  602. MenuElement[] subElements = elem.getSubElements();
  603. for(int i=0; i<subElements.length; i++) {
  604. if (processBindingForKeyStrokeRecursive(subElements[i], ks, e,
  605. condition, pressed)) {
  606. return true;
  607. // We don't, pass along to children JMenu's
  608. }
  609. }
  610. return false;
  611. }
  612. /**
  613. * Overrides <code>JComponent.addNotify</code> to register this
  614. * menu bar with the current keyboard manager.
  615. */
  616. public void addNotify() {
  617. super.addNotify();
  618. KeyboardManager.getCurrentManager().registerMenuBar(this);
  619. }
  620. /**
  621. * Overrides <code>JComponent.removeNotify</code> to unregister this
  622. * menu bar with the current keyboard manager.
  623. */
  624. public void removeNotify() {
  625. super.removeNotify();
  626. KeyboardManager.getCurrentManager().unregisterMenuBar(this);
  627. }
  628. private void writeObject(ObjectOutputStream s) throws IOException {
  629. s.defaultWriteObject();
  630. if (getUIClassID().equals(uiClassID)) {
  631. byte count = JComponent.getWriteObjCounter(this);
  632. JComponent.setWriteObjCounter(this, --count);
  633. if (count == 0 && ui != null) {
  634. ui.installUI(this);
  635. }
  636. }
  637. Object[] kvData = new Object[4];
  638. int n = 0;
  639. if (selectionModel instanceof Serializable) {
  640. kvData[n++] = "selectionModel";
  641. kvData[n++] = selectionModel;
  642. }
  643. s.writeObject(kvData);
  644. }
  645. /**
  646. * See JComponent.readObject() for information about serialization
  647. * in Swing.
  648. */
  649. private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException
  650. {
  651. s.defaultReadObject();
  652. Object[] kvData = (Object[])(s.readObject());
  653. for(int i = 0; i < kvData.length; i += 2) {
  654. if (kvData[i] == null) {
  655. break;
  656. }
  657. else if (kvData[i].equals("selectionModel")) {
  658. selectionModel = (SingleSelectionModel)kvData[i + 1];
  659. }
  660. }
  661. }
  662. }