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