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