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
26package java.awt;
27
28import java.awt.event.KeyEvent;
29import java.awt.peer.MenuPeer;
30import java.io.IOException;
31import java.io.ObjectInputStream;
32import java.io.ObjectOutputStream;
33import java.util.Enumeration;
34import java.util.EventListener;
35import java.util.Vector;
36
37import javax.accessibility.Accessible;
38import javax.accessibility.AccessibleContext;
39import javax.accessibility.AccessibleRole;
40
41import 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 */
65public 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}
640