1 /* 2 * Copyright (c) 1995, 2016, 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 26 package java.awt; 27 28 import java.awt.event.KeyEvent; 29 import java.awt.peer.MenuPeer; 30 import java.io.IOException; 31 import java.io.ObjectInputStream; 32 import java.util.Enumeration; 33 import java.util.Vector; 34 35 import javax.accessibility.Accessible; 36 import javax.accessibility.AccessibleContext; 37 import javax.accessibility.AccessibleRole; 38 39 import sun.awt.AWTAccessor; 40 41 /** 42 * A {@code Menu} object is a pull-down menu component 43 * that is deployed from a menu bar. 44 * <p> 45 * A menu can optionally be a <i>tear-off</i> menu. A tear-off menu 46 * can be opened and dragged away from its parent menu bar or menu. 47 * It remains on the screen after the mouse button has been released. 48 * The mechanism for tearing off a menu is platform dependent, since 49 * the look and feel of the tear-off menu is determined by its peer. 50 * On platforms that do not support tear-off menus, the tear-off 51 * property is ignored. 52 * <p> 53 * Each item in a menu must belong to the {@code MenuItem} 54 * class. It can be an instance of {@code MenuItem}, a submenu 55 * (an instance of {@code Menu}), or a check box (an instance of 56 * {@code CheckboxMenuItem}). 57 * 58 * @author Sami Shaio 59 * @see java.awt.MenuItem 60 * @see java.awt.CheckboxMenuItem 61 * @since 1.0 62 */ 63 public class Menu extends MenuItem implements MenuContainer, Accessible { 64 65 static { 66 /* ensure that the necessary native libraries are loaded */ 67 Toolkit.loadLibraries(); 68 if (!GraphicsEnvironment.isHeadless()) { 69 initIDs(); 70 } 71 72 AWTAccessor.setMenuAccessor( 73 new AWTAccessor.MenuAccessor() { 74 public Vector<MenuItem> getItems(Menu menu) { 75 return menu.items; 76 } 77 }); 78 } 79 80 /** 81 * A vector of the items that will be part of the Menu. 82 * 83 * @serial 84 * @see #countItems() 85 */ 86 private final Vector<MenuItem> items = new Vector<>(); 87 88 /** 89 * This field indicates whether the menu has the 90 * tear of property or not. It will be set to 91 * {@code true} if the menu has the tear off 92 * property and it will be set to {@code false} 93 * if it does not. 94 * A torn off menu can be deleted by a user when 95 * it is no longer needed. 96 * 97 * @serial 98 * @see #isTearOff() 99 */ 100 private final boolean tearOff; 101 102 /** 103 * This field will be set to {@code true} 104 * if the Menu in question is actually a help 105 * menu. Otherwise it will be set to 106 * {@code false}. 107 * 108 * @serial 109 */ 110 volatile boolean isHelpMenu; 111 112 private static final String base = "menu"; 113 private static int nameCounter = 0; 114 115 /* 116 * JDK 1.1 serialVersionUID 117 */ 118 private static final long serialVersionUID = -8809584163345499784L; 119 120 /** 121 * Constructs a new menu with an empty label. This menu is not 122 * a tear-off menu. 123 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 124 * returns true. 125 * @see java.awt.GraphicsEnvironment#isHeadless 126 * @since 1.1 127 */ 128 public Menu() throws HeadlessException { 129 this("", false); 130 } 131 132 /** 133 * Constructs a new menu with the specified label. This menu is not 134 * a tear-off menu. 135 * @param label the menu's label in the menu bar, or in 136 * another menu of which this menu is a submenu. 137 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 138 * returns true. 139 * @see java.awt.GraphicsEnvironment#isHeadless 140 */ 141 public Menu(String label) throws HeadlessException { 142 this(label, false); 143 } 144 145 /** 146 * Constructs a new menu with the specified label, 147 * indicating whether the menu can be torn off. 148 * <p> 149 * Tear-off functionality may not be supported by all 150 * implementations of AWT. If a particular implementation doesn't 151 * support tear-off menus, this value is silently ignored. 152 * @param label the menu's label in the menu bar, or in 153 * another menu of which this menu is a submenu. 154 * @param tearOff if {@code true}, the menu 155 * is a tear-off menu. 156 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 157 * returns true. 158 * @see java.awt.GraphicsEnvironment#isHeadless 159 */ 160 public Menu(String label, boolean tearOff) throws HeadlessException { 161 super(label); 162 this.tearOff = tearOff; 163 } 164 165 /** 166 * Construct a name for this MenuComponent. Called by getName() when 167 * the name is null. 168 */ 169 String constructComponentName() { 170 synchronized (Menu.class) { 171 return base + nameCounter++; 172 } 173 } 174 175 /** 176 * Creates the menu's peer. The peer allows us to modify the 177 * appearance of the menu without changing its functionality. 178 */ 179 public void addNotify() { 180 synchronized (getTreeLock()) { 181 if (peer == null) 182 peer = getComponentFactory().createMenu(this); 183 int nitems = getItemCount(); 184 for (int i = 0 ; i < nitems ; i++) { 185 MenuItem mi = getItem(i); 186 mi.parent = this; 187 mi.addNotify(); 188 } 189 } 190 } 191 192 /** 193 * Removes the menu's peer. The peer allows us to modify the appearance 194 * of the menu without changing its functionality. 195 */ 196 public void removeNotify() { 197 synchronized (getTreeLock()) { 198 int nitems = getItemCount(); 199 for (int i = 0 ; i < nitems ; i++) { 200 getItem(i).removeNotify(); 201 } 202 super.removeNotify(); 203 } 204 } 205 206 /** 207 * Indicates whether this menu is a tear-off menu. 208 * <p> 209 * Tear-off functionality may not be supported by all 210 * implementations of AWT. If a particular implementation doesn't 211 * support tear-off menus, this value is silently ignored. 212 * @return {@code true} if this is a tear-off menu; 213 * {@code false} otherwise. 214 */ 215 public boolean isTearOff() { 216 return tearOff; 217 } 218 219 /** 220 * Get the number of items in this menu. 221 * @return the number of items in this menu 222 * @since 1.1 223 */ 224 public int getItemCount() { 225 return countItems(); 226 } 227 228 /** 229 * Returns the number of items in this menu. 230 * 231 * @return the number of items in this menu 232 * @deprecated As of JDK version 1.1, 233 * replaced by {@code getItemCount()}. 234 */ 235 @Deprecated 236 public int countItems() { 237 return countItemsImpl(); 238 } 239 240 /* 241 * This is called by the native code, so client code can't 242 * be called on the toolkit thread. 243 */ 244 final int countItemsImpl() { 245 return items.size(); 246 } 247 248 /** 249 * Gets the item located at the specified index of this menu. 250 * @param index the position of the item to be returned. 251 * @return the item located at the specified index. 252 */ 253 public MenuItem getItem(int index) { 254 return getItemImpl(index); 255 } 256 257 /* 258 * This is called by the native code, so client code can't 259 * be called on the toolkit thread. 260 */ 261 final MenuItem getItemImpl(int index) { 262 return items.elementAt(index); 263 } 264 265 /** 266 * Adds the specified menu item to this menu. If the 267 * menu item has been part of another menu, removes it 268 * from that menu. 269 * 270 * @param mi the menu item to be added 271 * @return the menu item added 272 * @see java.awt.Menu#insert(java.lang.String, int) 273 * @see java.awt.Menu#insert(java.awt.MenuItem, int) 274 */ 275 public MenuItem add(MenuItem mi) { 276 synchronized (getTreeLock()) { 277 if (mi.parent != null) { 278 mi.parent.remove(mi); 279 } 280 items.addElement(mi); 281 mi.parent = this; 282 MenuPeer peer = (MenuPeer)this.peer; 283 if (peer != null) { 284 mi.addNotify(); 285 peer.addItem(mi); 286 } 287 return mi; 288 } 289 } 290 291 /** 292 * Adds an item with the specified label to this menu. 293 * 294 * @param label the text on the item 295 * @see java.awt.Menu#insert(java.lang.String, int) 296 * @see java.awt.Menu#insert(java.awt.MenuItem, int) 297 */ 298 public void add(String label) { 299 add(new MenuItem(label)); 300 } 301 302 /** 303 * Inserts a menu item into this menu 304 * at the specified position. 305 * 306 * @param menuitem the menu item to be inserted. 307 * @param index the position at which the menu 308 * item should be inserted. 309 * @see java.awt.Menu#add(java.lang.String) 310 * @see java.awt.Menu#add(java.awt.MenuItem) 311 * @exception IllegalArgumentException if the value of 312 * {@code index} is less than zero 313 * @since 1.1 314 */ 315 316 public void insert(MenuItem menuitem, int index) { 317 synchronized (getTreeLock()) { 318 if (index < 0) { 319 throw new IllegalArgumentException("index less than zero."); 320 } 321 322 int nitems = getItemCount(); 323 Vector<MenuItem> tempItems = new Vector<>(); 324 325 /* Remove the item at index, nitems-index times 326 storing them in a temporary vector in the 327 order they appear on the menu. 328 */ 329 for (int i = index ; i < nitems; i++) { 330 tempItems.addElement(getItem(index)); 331 remove(index); 332 } 333 334 add(menuitem); 335 336 /* Add the removed items back to the menu, they are 337 already in the correct order in the temp vector. 338 */ 339 for (int i = 0; i < tempItems.size() ; i++) { 340 add(tempItems.elementAt(i)); 341 } 342 } 343 } 344 345 /** 346 * Inserts a menu item with the specified label into this menu 347 * at the specified position. This is a convenience method for 348 * {@code insert(menuItem, index)}. 349 * 350 * @param label the text on the item 351 * @param index the position at which the menu item 352 * should be inserted 353 * @see java.awt.Menu#add(java.lang.String) 354 * @see java.awt.Menu#add(java.awt.MenuItem) 355 * @exception IllegalArgumentException if the value of 356 * {@code index} is less than zero 357 * @since 1.1 358 */ 359 360 public void insert(String label, int index) { 361 insert(new MenuItem(label), index); 362 } 363 364 /** 365 * Adds a separator line, or a hypen, to the menu at the current position. 366 * @see java.awt.Menu#insertSeparator(int) 367 */ 368 public void addSeparator() { 369 add("-"); 370 } 371 372 /** 373 * Inserts a separator at the specified position. 374 * @param index the position at which the 375 * menu separator should be inserted. 376 * @exception IllegalArgumentException if the value of 377 * {@code index} is less than 0. 378 * @see java.awt.Menu#addSeparator 379 * @since 1.1 380 */ 381 382 public void insertSeparator(int index) { 383 synchronized (getTreeLock()) { 384 if (index < 0) { 385 throw new IllegalArgumentException("index less than zero."); 386 } 387 388 int nitems = getItemCount(); 389 Vector<MenuItem> tempItems = new Vector<>(); 390 391 /* Remove the item at index, nitems-index times 392 storing them in a temporary vector in the 393 order they appear on the menu. 394 */ 395 for (int i = index ; i < nitems; i++) { 396 tempItems.addElement(getItem(index)); 397 remove(index); 398 } 399 400 addSeparator(); 401 402 /* Add the removed items back to the menu, they are 403 already in the correct order in the temp vector. 404 */ 405 for (int i = 0; i < tempItems.size() ; i++) { 406 add(tempItems.elementAt(i)); 407 } 408 } 409 } 410 411 /** 412 * Removes the menu item at the specified index from this menu. 413 * @param index the position of the item to be removed. 414 */ 415 public void remove(int index) { 416 synchronized (getTreeLock()) { 417 MenuItem mi = getItem(index); 418 items.removeElementAt(index); 419 MenuPeer peer = (MenuPeer)this.peer; 420 if (peer != null) { 421 peer.delItem(index); 422 mi.removeNotify(); 423 } 424 mi.parent = null; 425 } 426 } 427 428 /** 429 * Removes the specified menu item from this menu. 430 * @param item the item to be removed from the menu. 431 * If {@code item} is {@code null} 432 * or is not in this menu, this method does 433 * nothing. 434 */ 435 public void remove(MenuComponent item) { 436 synchronized (getTreeLock()) { 437 int index = items.indexOf(item); 438 if (index >= 0) { 439 remove(index); 440 } 441 } 442 } 443 444 /** 445 * Removes all items from this menu. 446 * @since 1.1 447 */ 448 public void removeAll() { 449 synchronized (getTreeLock()) { 450 int nitems = getItemCount(); 451 for (int i = nitems-1 ; i >= 0 ; i--) { 452 remove(i); 453 } 454 } 455 } 456 457 /* 458 * Post an ActionEvent to the target of the MenuPeer 459 * associated with the specified keyboard event (on 460 * keydown). Returns true if there is an associated 461 * keyboard event. 462 */ 463 boolean handleShortcut(KeyEvent e) { 464 int nitems = getItemCount(); 465 for (int i = 0 ; i < nitems ; i++) { 466 MenuItem mi = getItem(i); 467 if (mi.handleShortcut(e)) { 468 return true; 469 } 470 } 471 return false; 472 } 473 474 MenuItem getShortcutMenuItem(MenuShortcut s) { 475 int nitems = getItemCount(); 476 for (int i = 0 ; i < nitems ; i++) { 477 MenuItem mi = getItem(i).getShortcutMenuItem(s); 478 if (mi != null) { 479 return mi; 480 } 481 } 482 return null; 483 } 484 485 synchronized Enumeration<MenuShortcut> shortcuts() { 486 Vector<MenuShortcut> shortcuts = new Vector<>(); 487 int nitems = getItemCount(); 488 for (int i = 0 ; i < nitems ; i++) { 489 MenuItem mi = getItem(i); 490 if (mi instanceof Menu) { 491 Enumeration<MenuShortcut> e = ((Menu)mi).shortcuts(); 492 while (e.hasMoreElements()) { 493 shortcuts.addElement(e.nextElement()); 494 } 495 } else { 496 MenuShortcut ms = mi.getShortcut(); 497 if (ms != null) { 498 shortcuts.addElement(ms); 499 } 500 } 501 } 502 return shortcuts.elements(); 503 } 504 505 void deleteShortcut(MenuShortcut s) { 506 int nitems = getItemCount(); 507 for (int i = 0 ; i < nitems ; i++) { 508 getItem(i).deleteShortcut(s); 509 } 510 } 511 512 513 /* Serialization support. A MenuContainer is responsible for 514 * restoring the parent fields of its children. 515 */ 516 517 /** 518 * The menu serialized Data Version. 519 * 520 * @serial 521 */ 522 private int menuSerializedDataVersion = 1; 523 524 /** 525 * Writes default serializable fields to stream. 526 * 527 * @param s the {@code ObjectOutputStream} to write 528 * @see AWTEventMulticaster#save(ObjectOutputStream, String, EventListener) 529 * @see #readObject(ObjectInputStream) 530 */ 531 private void writeObject(java.io.ObjectOutputStream s) 532 throws java.io.IOException 533 { 534 s.defaultWriteObject(); 535 } 536 537 /** 538 * Reads the {@code ObjectInputStream}. 539 * Unrecognized keys or values will be ignored. 540 * 541 * @param s the {@code ObjectInputStream} to read 542 * @exception HeadlessException if 543 * {@code GraphicsEnvironment.isHeadless} returns 544 * {@code true} 545 * @see java.awt.GraphicsEnvironment#isHeadless 546 * @see #writeObject(ObjectOutputStream) 547 */ 548 private void readObject(ObjectInputStream s) 549 throws IOException, ClassNotFoundException, HeadlessException 550 { 551 // HeadlessException will be thrown from MenuComponent's readObject 552 s.defaultReadObject(); 553 for(int i = 0; i < items.size(); i++) { 554 MenuItem item = items.elementAt(i); 555 item.parent = this; 556 } 557 } 558 559 /** 560 * Returns a string representing the state of this {@code Menu}. 561 * This method is intended to be used only for debugging purposes, and the 562 * content and format of the returned string may vary between 563 * implementations. The returned string may be empty but may not be 564 * {@code null}. 565 * 566 * @return the parameter string of this menu 567 */ 568 public String paramString() { 569 String str = ",tearOff=" + tearOff+",isHelpMenu=" + isHelpMenu; 570 return super.paramString() + str; 571 } 572 573 /** 574 * Initialize JNI field and method IDs 575 */ 576 private static native void initIDs(); 577 578 579 ///////////////// 580 // Accessibility support 581 //////////////// 582 583 /** 584 * Gets the AccessibleContext associated with this Menu. 585 * For menus, the AccessibleContext takes the form of an 586 * AccessibleAWTMenu. 587 * A new AccessibleAWTMenu instance is created if necessary. 588 * 589 * @return an AccessibleAWTMenu that serves as the 590 * AccessibleContext of this Menu 591 * @since 1.3 592 */ 593 public AccessibleContext getAccessibleContext() { 594 if (accessibleContext == null) { 595 accessibleContext = new AccessibleAWTMenu(); 596 } 597 return accessibleContext; 598 } 599 600 /** 601 * Defined in MenuComponent. Overridden here. 602 */ 603 int getAccessibleChildIndex(MenuComponent child) { 604 return items.indexOf(child); 605 } 606 607 /** 608 * Inner class of Menu used to provide default support for 609 * accessibility. This class is not meant to be used directly by 610 * application developers, but is instead meant only to be 611 * subclassed by menu component developers. 612 * <p> 613 * This class implements accessibility support for the 614 * {@code Menu} class. It provides an implementation of the 615 * Java Accessibility API appropriate to menu user-interface elements. 616 * @since 1.3 617 */ 618 protected class AccessibleAWTMenu extends AccessibleAWTMenuItem 619 { 620 /* 621 * JDK 1.3 serialVersionUID 622 */ 623 private static final long serialVersionUID = 5228160894980069094L; 624 625 /** 626 * Get the role of this object. 627 * 628 * @return an instance of AccessibleRole describing the role of the 629 * object 630 */ 631 public AccessibleRole getAccessibleRole() { 632 return AccessibleRole.MENU; 633 } 634 635 } // class AccessibleAWTMenu 636 637 }