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 }