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