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