Java tutorial
/* * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing; import java.awt.Component; import java.awt.ComponentOrientation; import java.awt.Container; import java.awt.Dimension; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.*; import java.beans.JavaBean; import java.beans.BeanProperty; import java.beans.PropertyChangeListener; import java.util.*; import java.io.Serializable; import java.io.ObjectOutputStream; import java.io.IOException; import javax.swing.event.*; import javax.swing.plaf.*; import javax.accessibility.*; /** * An implementation of a menu -- a popup window containing * <code>JMenuItem</code>s that * is displayed when the user selects an item on the <code>JMenuBar</code>. * In addition to <code>JMenuItem</code>s, a <code>JMenu</code> can * also contain <code>JSeparator</code>s. * <p> * In essence, a menu is a button with an associated <code>JPopupMenu</code>. * When the "button" is pressed, the <code>JPopupMenu</code> appears. If the * "button" is on the <code>JMenuBar</code>, the menu is a top-level window. * If the "button" is another menu item, then the <code>JPopupMenu</code> is * "pull-right" menu. * <p> * Menus can be configured, and to some degree controlled, by * <code><a href="Action.html">Action</a></code>s. Using an * <code>Action</code> with a menu has many benefits beyond directly * configuring a menu. Refer to <a href="Action.html#buttonActions"> * Swing Components Supporting <code>Action</code></a> for more * details, and you can find more information in <a * href="https://docs.oracle.com/javase/tutorial/uiswing/misc/action.html">How * to Use Actions</a>, a section in <em>The Java Tutorial</em>. * <p> * For information and examples of using menus see * <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/menu.html">How to Use Menus</a>, * a section in <em>The Java Tutorial.</em> * <p> * <strong>Warning:</strong> Swing is not thread safe. For more * information see <a * href="package-summary.html#threading">Swing's Threading * Policy</a>. * <p> * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeans™ * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @author Georges Saab * @author David Karlton * @author Arnaud Weber * @see JMenuItem * @see JSeparator * @see JMenuBar * @see JPopupMenu * @since 1.2 */ @JavaBean(description = "A popup window containing menu items displayed in a menu bar.") @SwingContainer @SuppressWarnings("serial") public class JMenu extends JMenuItem implements Accessible, MenuElement { /** * @see #getUIClassID * @see #readObject */ private static final String uiClassID = "MenuUI"; /* * The popup menu portion of the menu. */ private JPopupMenu popupMenu; /* * The button's model listeners. Default is <code>null</code>. */ private ChangeListener menuChangeListener = null; /* * Only one <code>MenuEvent</code> is needed for each menu since the * event's only state is the source property. The source of events * generated is always "this". Default is <code>null</code>. */ private MenuEvent menuEvent = null; /* * Used by the look and feel (L&F) code to handle * implementation specific menu behaviors. */ private int delay; /* * Location of the popup component. Location is <code>null</code> * if it was not customized by <code>setMenuLocation</code> */ private Point customMenuLocation = null; /* Diagnostic aids -- should be false for production builds. */ private static final boolean TRACE = false; // trace creates and disposes private static final boolean VERBOSE = false; // show reuse hits/misses private static final boolean DEBUG = false; // show bad params, misc. /** * Constructs a new <code>JMenu</code> with no text. */ public JMenu() { this(""); } /** * Constructs a new <code>JMenu</code> with the supplied string * as its text. * * @param s the text for the menu label */ public JMenu(String s) { super(s); } /** * Constructs a menu whose properties are taken from the * <code>Action</code> supplied. * @param a an <code>Action</code> * * @since 1.3 */ public JMenu(Action a) { this(); setAction(a); } /** * Constructs a new <code>JMenu</code> with the supplied string as * its text and specified as a tear-off menu or not. * * @param s the text for the menu label * @param b can the menu be torn off (not yet implemented) */ public JMenu(String s, boolean b) { this(s); } /** * Overriden to do nothing. We want JMenu to be focusable, but * <code>JMenuItem</code> doesn't want to be, thus we override this * do nothing. We don't invoke <code>setFocusable(true)</code> after * super's constructor has completed as this has the side effect that * <code>JMenu</code> will be considered traversable via the * keyboard, which we don't want. Making a Component traversable by * the keyboard after invoking <code>setFocusable(true)</code> is OK, * as <code>setFocusable</code> is new API * and is speced as such, but internally we don't want to use it like * this else we change the keyboard traversability. */ void initFocusability() { } /** * Resets the UI property with a value from the current look and feel. * * @see JComponent#updateUI */ public void updateUI() { setUI((MenuItemUI) UIManager.getUI(this)); if (popupMenu != null) { popupMenu.setUI((PopupMenuUI) UIManager.getUI(popupMenu)); } } /** * Returns the name of the L&F class that renders this component. * * @return the string "MenuUI" * @see JComponent#getUIClassID * @see UIDefaults#getUI */ @BeanProperty(bound = false) public String getUIClassID() { return uiClassID; } // public void repaint(long tm, int x, int y, int width, int height) { // Thread.currentThread().dumpStack(); // super.repaint(tm,x,y,width,height); // } /** * Sets the data model for the "menu button" -- the label * that the user clicks to open or close the menu. * * @param newModel the <code>ButtonModel</code> * @see #getModel */ public void setModel(ButtonModel newModel) { ButtonModel oldModel = getModel(); super.setModel(newModel); if (oldModel != null && menuChangeListener != null) { oldModel.removeChangeListener(menuChangeListener); menuChangeListener = null; } model = newModel; if (newModel != null) { menuChangeListener = createMenuChangeListener(); newModel.addChangeListener(menuChangeListener); } } /** * Returns true if the menu is currently selected (highlighted). * * @return true if the menu is selected, else false */ public boolean isSelected() { return getModel().isSelected(); } /** * Sets the selection status of the menu. * * @param b true to select (highlight) the menu; false to de-select * the menu */ @BeanProperty(expert = true, hidden = true, description = "When the menu is selected, its popup child is shown.") public void setSelected(boolean b) { ButtonModel model = getModel(); boolean oldValue = model.isSelected(); // TIGER - 4840653 // Removed code which fired an AccessibleState.SELECTED // PropertyChangeEvent since this resulted in two // identical events being fired since // AbstractButton.fireItemStateChanged also fires the // same event. This caused screen readers to speak the // name of the item twice. if (b != model.isSelected()) { getModel().setSelected(b); } } /** * Returns true if the menu's popup window is visible. * * @return true if the menu is visible, else false */ public boolean isPopupMenuVisible() { ensurePopupMenuCreated(); return popupMenu.isVisible(); } /** * Sets the visibility of the menu's popup. If the menu is * not enabled, this method will have no effect. * * @param b a boolean value -- true to make the menu visible, * false to hide it */ @BeanProperty(bound = false, expert = true, hidden = true, description = "The popup menu's visibility") public void setPopupMenuVisible(boolean b) { if (DEBUG) { System.out.println("in JMenu.setPopupMenuVisible " + b); // Thread.dumpStack(); } boolean isVisible = isPopupMenuVisible(); if (b != isVisible && (isEnabled() || !b)) { ensurePopupMenuCreated(); if ((b == true) && isShowing()) { // Set location of popupMenu (pulldown or pullright) Point p = getCustomMenuLocation(); if (p == null) { p = getPopupMenuOrigin(); } getPopupMenu().show(this, p.x, p.y); } else { getPopupMenu().setVisible(false); } } } /** * Computes the origin for the <code>JMenu</code>'s popup menu. * This method uses Look and Feel properties named * <code>Menu.menuPopupOffsetX</code>, * <code>Menu.menuPopupOffsetY</code>, * <code>Menu.submenuPopupOffsetX</code>, and * <code>Menu.submenuPopupOffsetY</code> * to adjust the exact location of popup. * * @return a <code>Point</code> in the coordinate space of the * menu which should be used as the origin * of the <code>JMenu</code>'s popup menu * * @since 1.3 */ protected Point getPopupMenuOrigin() { int x; int y; JPopupMenu pm = getPopupMenu(); // Figure out the sizes needed to caclulate the menu position Dimension s = getSize(); Dimension pmSize = pm.getSize(); // For the first time the menu is popped up, // the size has not yet been initiated if (pmSize.width == 0) { pmSize = pm.getPreferredSize(); } Point position = getLocationOnScreen(); Toolkit toolkit = Toolkit.getDefaultToolkit(); GraphicsConfiguration gc = getGraphicsConfiguration(); Rectangle screenBounds = new Rectangle(toolkit.getScreenSize()); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] gd = ge.getScreenDevices(); for (int i = 0; i < gd.length; i++) { if (gd[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) { GraphicsConfiguration dgc = gd[i].getDefaultConfiguration(); if (dgc.getBounds().contains(position)) { gc = dgc; break; } } } if (gc != null) { screenBounds = gc.getBounds(); // take screen insets (e.g. taskbar) into account Insets screenInsets = toolkit.getScreenInsets(gc); screenBounds.width -= Math.abs(screenInsets.left + screenInsets.right); screenBounds.height -= Math.abs(screenInsets.top + screenInsets.bottom); position.x -= Math.abs(screenInsets.left); position.y -= Math.abs(screenInsets.top); } Container parent = getParent(); if (parent instanceof JPopupMenu) { // We are a submenu (pull-right) int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX"); int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY"); if (SwingUtilities.isLeftToRight(this)) { // First determine x: x = s.width + xOffset; // Prefer placement to the right if (position.x + x + pmSize.width >= screenBounds.width + screenBounds.x && // popup doesn't fit - place it wherever there's more room screenBounds.width - s.width < 2 * (position.x - screenBounds.x)) { x = 0 - xOffset - pmSize.width; } } else { // First determine x: x = 0 - xOffset - pmSize.width; // Prefer placement to the left if (position.x + x < screenBounds.x && // popup doesn't fit - place it wherever there's more room screenBounds.width - s.width > 2 * (position.x - screenBounds.x)) { x = s.width + xOffset; } } // Then the y: y = yOffset; // Prefer dropping down if (position.y + y + pmSize.height >= screenBounds.height + screenBounds.y && // popup doesn't fit - place it wherever there's more room screenBounds.height - s.height < 2 * (position.y - screenBounds.y)) { y = s.height - yOffset - pmSize.height; } } else { // We are a toplevel menu (pull-down) int xOffset = UIManager.getInt("Menu.menuPopupOffsetX"); int yOffset = UIManager.getInt("Menu.menuPopupOffsetY"); if (SwingUtilities.isLeftToRight(this)) { // First determine the x: x = xOffset; // Extend to the right if (position.x + x + pmSize.width >= screenBounds.width + screenBounds.x && // popup doesn't fit - place it wherever there's more room screenBounds.width - s.width < 2 * (position.x - screenBounds.x)) { x = s.width - xOffset - pmSize.width; } } else { // First determine the x: x = s.width - xOffset - pmSize.width; // Extend to the left if (position.x + x < screenBounds.x && // popup doesn't fit - place it wherever there's more room screenBounds.width - s.width > 2 * (position.x - screenBounds.x)) { x = xOffset; } } // Then the y: y = s.height + yOffset; // Prefer dropping down if (position.y + y + pmSize.height >= screenBounds.height + screenBounds.y && // popup doesn't fit - place it wherever there's more room screenBounds.height - s.height < 2 * (position.y - screenBounds.y)) { y = 0 - yOffset - pmSize.height; // Otherwise drop 'up' } } return new Point(x, y); } /** * Returns the suggested delay, in milliseconds, before submenus * are popped up or down. * Each look and feel (L&F) may determine its own policy for * observing the <code>delay</code> property. * In most cases, the delay is not observed for top level menus * or while dragging. The default for <code>delay</code> is 0. * This method is a property of the look and feel code and is used * to manage the idiosyncrasies of the various UI implementations. * * * @return the <code>delay</code> property */ public int getDelay() { return delay; } /** * Sets the suggested delay before the menu's <code>PopupMenu</code> * is popped up or down. Each look and feel (L&F) may determine * it's own policy for observing the delay property. In most cases, * the delay is not observed for top level menus or while dragging. * This method is a property of the look and feel code and is used * to manage the idiosyncrasies of the various UI implementations. * * @param d the number of milliseconds to delay * @exception IllegalArgumentException if <code>d</code> * is less than 0 */ @BeanProperty(bound = false, expert = true, description = "The delay between menu selection and making the popup menu visible") public void setDelay(int d) { if (d < 0) throw new IllegalArgumentException("Delay must be a positive integer"); delay = d; } /** * The window-closing listener for the popup. * * @see WinListener */ protected WinListener popupListener; private void ensurePopupMenuCreated() { if (popupMenu == null) { final JMenu thisMenu = this; this.popupMenu = new JPopupMenu(); popupMenu.setInvoker(this); popupListener = createWinListener(popupMenu); } } /* * Return the customized location of the popup component. */ private Point getCustomMenuLocation() { return customMenuLocation; } /** * Sets the location of the popup component. * * @param x the x coordinate of the popup's new position * @param y the y coordinate of the popup's new position */ public void setMenuLocation(int x, int y) { customMenuLocation = new Point(x, y); if (popupMenu != null) popupMenu.setLocation(x, y); } /** * Appends a menu item to the end of this menu. * Returns the menu item added. * * @param menuItem the <code>JMenuitem</code> to be added * @return the <code>JMenuItem</code> added */ public JMenuItem add(JMenuItem menuItem) { ensurePopupMenuCreated(); return popupMenu.add(menuItem); } /** * Appends a component to the end of this menu. * Returns the component added. * * @param c the <code>Component</code> to add * @return the <code>Component</code> added */ public Component add(Component c) { ensurePopupMenuCreated(); popupMenu.add(c); return c; } /** * Adds the specified component to this container at the given * position. If <code>index</code> equals -1, the component will * be appended to the end. * @param c the <code>Component</code> to add * @param index the position at which to insert the component * @return the <code>Component</code> added * @see #remove * @see java.awt.Container#add(Component, int) */ public Component add(Component c, int index) { ensurePopupMenuCreated(); popupMenu.add(c, index); return c; } /** * Creates a new menu item with the specified text and appends * it to the end of this menu. * * @param s the string for the menu item to be added * @return the new {@code JMenuItem} */ public JMenuItem add(String s) { return add(new JMenuItem(s)); } /** * Creates a new menu item attached to the specified {@code Action} object * and appends it to the end of this menu. * * @param a the {@code Action} for the menu item to be added * @return the new {@code JMenuItem} * @see Action */ public JMenuItem add(Action a) { JMenuItem mi = createActionComponent(a); mi.setAction(a); add(mi); return mi; } /** * Factory method which creates the <code>JMenuItem</code> for * <code>Action</code>s added to the <code>JMenu</code>. * * @param a the <code>Action</code> for the menu item to be added * @return the new menu item * @see Action * * @since 1.3 */ protected JMenuItem createActionComponent(Action a) { JMenuItem mi = new JMenuItem() { protected PropertyChangeListener createActionPropertyChangeListener(Action a) { PropertyChangeListener pcl = createActionChangeListener(this); if (pcl == null) { pcl = super.createActionPropertyChangeListener(a); } return pcl; } }; mi.setHorizontalTextPosition(JButton.TRAILING); mi.setVerticalTextPosition(JButton.CENTER); return mi; } /** * Returns a properly configured {@code PropertyChangeListener} * which updates the control as changes to the {@code Action} occur. * * @param b a menu item for which to create a {@code PropertyChangeListener} * @return a {@code PropertyChangeListener} for {@code b} */ protected PropertyChangeListener createActionChangeListener(JMenuItem b) { return b.createActionPropertyChangeListener0(b.getAction()); } /** * Appends a new separator to the end of the menu. */ public void addSeparator() { ensurePopupMenuCreated(); popupMenu.addSeparator(); } /** * Inserts a new menu item with the specified text at a * given position. * * @param s the text for the menu item to add * @param pos an integer specifying the position at which to add the * new menu item * @exception IllegalArgumentException when the value of * <code>pos</code> < 0 */ public void insert(String s, int pos) { if (pos < 0) { throw new IllegalArgumentException("index less than zero."); } ensurePopupMenuCreated(); popupMenu.insert(new JMenuItem(s), pos); } /** * Inserts the specified <code>JMenuitem</code> at a given position. * * @param mi the <code>JMenuitem</code> to add * @param pos an integer specifying the position at which to add the * new <code>JMenuitem</code> * @return the new menu item * @exception IllegalArgumentException if the value of * <code>pos</code> < 0 */ public JMenuItem insert(JMenuItem mi, int pos) { if (pos < 0) { throw new IllegalArgumentException("index less than zero."); } ensurePopupMenuCreated(); popupMenu.insert(mi, pos); return mi; } /** * Inserts a new menu item attached to the specified <code>Action</code> * object at a given position. * * @param a the <code>Action</code> object for the menu item to add * @param pos an integer specifying the position at which to add the * new menu item * @return the new menu item * @exception IllegalArgumentException if the value of * <code>pos</code> < 0 */ public JMenuItem insert(Action a, int pos) { if (pos < 0) { throw new IllegalArgumentException("index less than zero."); } ensurePopupMenuCreated(); JMenuItem mi = new JMenuItem(a); mi.setHorizontalTextPosition(JButton.TRAILING); mi.setVerticalTextPosition(JButton.CENTER); popupMenu.insert(mi, pos); return mi; } /** * Inserts a separator at the specified position. * * @param index an integer specifying the position at which to * insert the menu separator * @exception IllegalArgumentException if the value of * <code>index</code> < 0 */ public void insertSeparator(int index) { if (index < 0) { throw new IllegalArgumentException("index less than zero."); } ensurePopupMenuCreated(); popupMenu.insert(new JPopupMenu.Separator(), index); } /** * Returns the {@code JMenuItem} at the specified position. * If the component at {@code pos} is not a menu item, * {@code null} is returned. * This method is included for AWT compatibility. * * @param pos an integer specifying the position * @return the menu item at the specified position; or <code>null</code> * if the item as the specified position is not a menu item * @exception IllegalArgumentException if the value of * {@code pos} < 0 */ public JMenuItem getItem(int pos) { if (pos < 0) { throw new IllegalArgumentException("index less than zero."); } Component c = getMenuComponent(pos); if (c instanceof JMenuItem) { JMenuItem mi = (JMenuItem) c; return mi; } // 4173633 return null; } /** * Returns the number of items on the menu, including separators. * This method is included for AWT compatibility. * * @return an integer equal to the number of items on the menu * @see #getMenuComponentCount */ @BeanProperty(bound = false) public int getItemCount() { return getMenuComponentCount(); } /** * Returns true if the menu can be torn off. This method is not * yet implemented. * * @return true if the menu can be torn off, else false * @exception Error if invoked -- this method is not yet implemented */ @BeanProperty(bound = false) public boolean isTearOff() { throw new Error("boolean isTearOff() {} not yet implemented"); } /** * Removes the specified menu item from this menu. If there is no * popup menu, this method will have no effect. * * @param item the <code>JMenuItem</code> to be removed from the menu */ public void remove(JMenuItem item) { if (popupMenu != null) popupMenu.remove(item); } /** * Removes the menu item at the specified index from this menu. * * @param pos the position of the item to be removed * @exception IllegalArgumentException if the value of * <code>pos</code> < 0, or if <code>pos</code> * is greater than the number of menu items */ public void remove(int pos) { if (pos < 0) { throw new IllegalArgumentException("index less than zero."); } if (pos > getItemCount()) { throw new IllegalArgumentException("index greater than the number of items."); } if (popupMenu != null) popupMenu.remove(pos); } /** * Removes the component <code>c</code> from this menu. * * @param c the component to be removed */ public void remove(Component c) { if (popupMenu != null) popupMenu.remove(c); } /** * Removes all menu items from this menu. */ public void removeAll() { if (popupMenu != null) popupMenu.removeAll(); } /** * Returns the number of components on the menu. * * @return an integer containing the number of components on the menu */ @BeanProperty(bound = false) public int getMenuComponentCount() { int componentCount = 0; if (popupMenu != null) componentCount = popupMenu.getComponentCount(); return componentCount; } /** * Returns the component at position <code>n</code>. * * @param n the position of the component to be returned * @return the component requested, or <code>null</code> * if there is no popup menu * */ public Component getMenuComponent(int n) { if (popupMenu != null) return popupMenu.getComponent(n); return null; } /** * Returns an array of <code>Component</code>s of the menu's * subcomponents. Note that this returns all <code>Component</code>s * in the popup menu, including separators. * * @return an array of <code>Component</code>s or an empty array * if there is no popup menu */ @BeanProperty(bound = false) public Component[] getMenuComponents() { if (popupMenu != null) return popupMenu.getComponents(); return new Component[0]; } /** * Returns true if the menu is a 'top-level menu', that is, if it is * the direct child of a menubar. * * @return true if the menu is activated from the menu bar; * false if the menu is activated from a menu item * on another menu */ @BeanProperty(bound = false) public boolean isTopLevelMenu() { return getParent() instanceof JMenuBar; } /** * Returns true if the specified component exists in the * submenu hierarchy. * * @param c the <code>Component</code> to be tested * @return true if the <code>Component</code> exists, false otherwise */ public boolean isMenuComponent(Component c) { // Are we in the MenuItem part of the menu if (c == this) return true; // Are we in the PopupMenu? if (c instanceof JPopupMenu) { JPopupMenu comp = (JPopupMenu) c; if (comp == this.getPopupMenu()) return true; } // Are we in a Component on the PopupMenu int ncomponents = this.getMenuComponentCount(); Component[] component = this.getMenuComponents(); for (int i = 0; i < ncomponents; i++) { Component comp = component[i]; // Are we in the current component? if (comp == c) return true; // Hmmm, what about Non-menu containers? // Recursive call for the Menu case if (comp instanceof JMenu) { JMenu subMenu = (JMenu) comp; if (subMenu.isMenuComponent(c)) return true; } } return false; } /* * Returns a point in the coordinate space of this menu's popupmenu * which corresponds to the point <code>p</code> in the menu's * coordinate space. * * @param p the point to be translated * @return the point in the coordinate space of this menu's popupmenu */ private Point translateToPopupMenu(Point p) { return translateToPopupMenu(p.x, p.y); } /* * Returns a point in the coordinate space of this menu's popupmenu * which corresponds to the point (x,y) in the menu's coordinate space. * * @param x the x coordinate of the point to be translated * @param y the y coordinate of the point to be translated * @return the point in the coordinate space of this menu's popupmenu */ private Point translateToPopupMenu(int x, int y) { int newX; int newY; if (getParent() instanceof JPopupMenu) { newX = x - getSize().width; newY = y; } else { newX = x; newY = y - getSize().height; } return new Point(newX, newY); } /** * Returns the popupmenu associated with this menu. If there is * no popupmenu, it will create one. * * @return the {@code JPopupMenu} associated with this menu */ @BeanProperty(bound = false) public JPopupMenu getPopupMenu() { ensurePopupMenuCreated(); return popupMenu; } /** * Adds a listener for menu events. * * @param l the listener to be added */ public void addMenuListener(MenuListener l) { listenerList.add(MenuListener.class, l); } /** * Removes a listener for menu events. * * @param l the listener to be removed */ public void removeMenuListener(MenuListener l) { listenerList.remove(MenuListener.class, l); } /** * Returns an array of all the <code>MenuListener</code>s added * to this JMenu with addMenuListener(). * * @return all of the <code>MenuListener</code>s added or an empty * array if no listeners have been added * @since 1.4 */ @BeanProperty(bound = false) public MenuListener[] getMenuListeners() { return listenerList.getListeners(MenuListener.class); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is created lazily. * * @exception Error if there is a <code>null</code> listener * @see EventListenerList */ protected void fireMenuSelected() { if (DEBUG) { System.out.println("In JMenu.fireMenuSelected"); } // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == MenuListener.class) { if (listeners[i + 1] == null) { throw new Error(getText() + " has a NULL Listener!! " + i); } else { // Lazily create the event: if (menuEvent == null) menuEvent = new MenuEvent(this); ((MenuListener) listeners[i + 1]).menuSelected(menuEvent); } } } } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is created lazily. * * @exception Error if there is a <code>null</code> listener * @see EventListenerList */ protected void fireMenuDeselected() { if (DEBUG) { System.out.println("In JMenu.fireMenuDeselected"); } // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == MenuListener.class) { if (listeners[i + 1] == null) { throw new Error(getText() + " has a NULL Listener!! " + i); } else { // Lazily create the event: if (menuEvent == null) menuEvent = new MenuEvent(this); ((MenuListener) listeners[i + 1]).menuDeselected(menuEvent); } } } } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is created lazily. * * @exception Error if there is a <code>null</code> listener * @see EventListenerList */ protected void fireMenuCanceled() { if (DEBUG) { System.out.println("In JMenu.fireMenuCanceled"); } // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == MenuListener.class) { if (listeners[i + 1] == null) { throw new Error(getText() + " has a NULL Listener!! " + i); } else { // Lazily create the event: if (menuEvent == null) menuEvent = new MenuEvent(this); ((MenuListener) listeners[i + 1]).menuCanceled(menuEvent); } } } } // Overriden to do nothing, JMenu doesn't support an accelerator void configureAcceleratorFromAction(Action a) { } @SuppressWarnings("serial") class MenuChangeListener implements ChangeListener, Serializable { boolean isSelected = false; public void stateChanged(ChangeEvent e) { ButtonModel model = (ButtonModel) e.getSource(); boolean modelSelected = model.isSelected(); if (modelSelected != isSelected) { if (modelSelected == true) { fireMenuSelected(); } else { fireMenuDeselected(); } isSelected = modelSelected; } } } private ChangeListener createMenuChangeListener() { return new MenuChangeListener(); } /** * Creates a window-closing listener for the popup. * * @param p the <code>JPopupMenu</code> * @return the new window-closing listener * * @see WinListener */ protected WinListener createWinListener(JPopupMenu p) { return new WinListener(p); } /** * A listener class that watches for a popup window closing. * When the popup is closing, the listener deselects the menu. * <p> * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeans™ * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. */ @SuppressWarnings("serial") protected class WinListener extends WindowAdapter implements Serializable { JPopupMenu popupMenu; /** * Create the window listener for the specified popup. * * @param p the popup menu for which to create a listener * @since 1.4 */ public WinListener(JPopupMenu p) { this.popupMenu = p; } /** * Deselect the menu when the popup is closed from outside. */ public void windowClosing(WindowEvent e) { setSelected(false); } } /** * Messaged when the menubar selection changes to activate or * deactivate this menu. * Overrides <code>JMenuItem.menuSelectionChanged</code>. * * @param isIncluded true if this menu is active, false if * it is not */ public void menuSelectionChanged(boolean isIncluded) { if (DEBUG) { System.out.println("In JMenu.menuSelectionChanged to " + isIncluded); } setSelected(isIncluded); } /** * Returns an array of <code>MenuElement</code>s containing the submenu * for this menu component. If popup menu is <code>null</code> returns * an empty array. This method is required to conform to the * <code>MenuElement</code> interface. Note that since * <code>JSeparator</code>s do not conform to the <code>MenuElement</code> * interface, this array will only contain <code>JMenuItem</code>s. * * @return an array of <code>MenuElement</code> objects */ @BeanProperty(bound = false) public MenuElement[] getSubElements() { if (popupMenu == null) return new MenuElement[0]; else { MenuElement[] result = new MenuElement[1]; result[0] = popupMenu; return result; } } // implements javax.swing.MenuElement /** * Returns the <code>java.awt.Component</code> used to * paint this <code>MenuElement</code>. * The returned component is used to convert events and detect if * an event is inside a menu component. */ public Component getComponent() { return this; } /** * Sets the <code>ComponentOrientation</code> property of this menu * and all components contained within it. This includes all * components returned by {@link #getMenuComponents getMenuComponents}. * * @param o the new component orientation of this menu and * the components contained within it. * @exception NullPointerException if <code>orientation</code> is null. * @see java.awt.Component#setComponentOrientation * @see java.awt.Component#getComponentOrientation * @since 1.4 */ public void applyComponentOrientation(ComponentOrientation o) { super.applyComponentOrientation(o); if (popupMenu != null) { int ncomponents = getMenuComponentCount(); for (int i = 0; i < ncomponents; ++i) { getMenuComponent(i).applyComponentOrientation(o); } popupMenu.setComponentOrientation(o); } } public void setComponentOrientation(ComponentOrientation o) { super.setComponentOrientation(o); if (popupMenu != null) { popupMenu.setComponentOrientation(o); } } /** * <code>setAccelerator</code> is not defined for <code>JMenu</code>. * Use <code>setMnemonic</code> instead. * @param keyStroke the keystroke combination which will invoke * the <code>JMenuItem</code>'s actionlisteners * without navigating the menu hierarchy * @exception Error if invoked -- this method is not defined for JMenu. * Use <code>setMnemonic</code> instead */ public void setAccelerator(KeyStroke keyStroke) { throw new Error("setAccelerator() is not defined for JMenu. Use setMnemonic() instead."); } /** * Processes key stroke events such as mnemonics and accelerators. * * @param evt the key event to be processed */ protected void processKeyEvent(KeyEvent evt) { MenuSelectionManager.defaultManager().processKeyEvent(evt); if (evt.isConsumed()) return; super.processKeyEvent(evt); } /** * Programmatically performs a "click". This overrides the method * <code>AbstractButton.doClick</code> in order to make the menu pop up. * @param pressTime indicates the number of milliseconds the * button was pressed for */ public void doClick(int pressTime) { MenuElement[] me = buildMenuElementArray(this); MenuSelectionManager.defaultManager().setSelectedPath(me); } /* * Build an array of menu elements - from <code>PopupMenu</code> to * the root <code>JMenuBar</code>. * @param leaf the leaf node from which to start building up the array * @return the array of menu items */ private MenuElement[] buildMenuElementArray(JMenu leaf) { Vector<MenuElement> elements = new Vector<>(); Component current = leaf.getPopupMenu(); JPopupMenu pop; JMenu menu; JMenuBar bar; while (true) { if (current instanceof JPopupMenu) { pop = (JPopupMenu) current; elements.insertElementAt(pop, 0); current = pop.getInvoker(); } else if (current instanceof JMenu) { menu = (JMenu) current; elements.insertElementAt(menu, 0); current = menu.getParent(); } else if (current instanceof JMenuBar) { bar = (JMenuBar) current; elements.insertElementAt(bar, 0); break; } else { break; } } MenuElement[] me = new MenuElement[elements.size()]; elements.copyInto(me); return me; } /** * See <code>readObject</code> and <code>writeObject</code> in * <code>JComponent</code> for more * information about serialization in Swing. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); if (getUIClassID().equals(uiClassID)) { byte count = JComponent.getWriteObjCounter(this); JComponent.setWriteObjCounter(this, --count); if (count == 0 && ui != null) { ui.installUI(this); } } } /** * Returns a string representation of this <code>JMenu</code>. This * method is intended to be used only for debugging purposes, and the * content and format of the returned string may vary between * implementations. The returned string may be empty but may not * be <code>null</code>. * * @return a string representation of this JMenu. */ protected String paramString() { return super.paramString(); } ///////////////// // Accessibility support //////////////// /** * Gets the AccessibleContext associated with this JMenu. * For JMenus, the AccessibleContext takes the form of an * AccessibleJMenu. * A new AccessibleJMenu instance is created if necessary. * * @return an AccessibleJMenu that serves as the * AccessibleContext of this JMenu */ @BeanProperty(bound = false) public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleJMenu(); } return accessibleContext; } /** * This class implements accessibility support for the * <code>JMenu</code> class. It provides an implementation of the * Java Accessibility API appropriate to menu user-interface elements. * <p> * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeans™ * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. */ @SuppressWarnings("serial") protected class AccessibleJMenu extends AccessibleJMenuItem implements AccessibleSelection { /** * Returns the number of accessible children in the object. If all * of the children of this object implement Accessible, than this * method should return the number of children of this object. * * @return the number of accessible children in the object. */ public int getAccessibleChildrenCount() { Component[] children = getMenuComponents(); int count = 0; for (Component child : children) { if (child instanceof Accessible) { count++; } } return count; } /** * Returns the nth Accessible child of the object. * * @param i zero-based index of child * @return the nth Accessible child of the object */ public Accessible getAccessibleChild(int i) { Component[] children = getMenuComponents(); int count = 0; for (Component child : children) { if (child instanceof Accessible) { if (count == i) { if (child instanceof JComponent) { // FIXME: [[[WDW - probably should set this when // the component is added to the menu. I tried // to do this in most cases, but the separators // added by addSeparator are hard to get to.]]] AccessibleContext ac = child.getAccessibleContext(); ac.setAccessibleParent(JMenu.this); } return (Accessible) child; } else { count++; } } } return null; } /** * Get the role of this object. * * @return an instance of AccessibleRole describing the role of the * object * @see AccessibleRole */ public AccessibleRole getAccessibleRole() { return AccessibleRole.MENU; } /** * Get the AccessibleSelection associated with this object. In the * implementation of the Java Accessibility API for this class, * return this object, which is responsible for implementing the * AccessibleSelection interface on behalf of itself. * * @return this object */ public AccessibleSelection getAccessibleSelection() { return this; } /** * Returns 1 if a sub-menu is currently selected in this menu. * * @return 1 if a menu is currently selected, else 0 */ public int getAccessibleSelectionCount() { MenuElement[] me = MenuSelectionManager.defaultManager().getSelectedPath(); if (me != null) { for (int i = 0; i < me.length; i++) { if (me[i] == JMenu.this) { // this menu is selected if (i + 1 < me.length) { return 1; } } } } return 0; } /** * Returns the currently selected sub-menu if one is selected, * otherwise null (there can only be one selection, and it can * only be a sub-menu, as otherwise menu items don't remain * selected). */ public Accessible getAccessibleSelection(int i) { // if i is a sub-menu & popped, return it if (i < 0 || i >= getItemCount()) { return null; } MenuElement[] me = MenuSelectionManager.defaultManager().getSelectedPath(); if (me != null) { for (int j = 0; j < me.length; j++) { if (me[j] == JMenu.this) { // this menu is selected // so find the next JMenuItem in the MenuElement // array, and return it! while (++j < me.length) { if (me[j] instanceof JMenuItem) { return (Accessible) me[j]; } } } } } return null; } /** * Returns true if the current child of this object is selected * (that is, if this child is a popped-up submenu). * * @param i the zero-based index of the child in this Accessible * object. * @see AccessibleContext#getAccessibleChild */ public boolean isAccessibleChildSelected(int i) { // if i is a sub-menu and is pop-ed up, return true, else false MenuElement[] me = MenuSelectionManager.defaultManager().getSelectedPath(); if (me != null) { JMenuItem mi = JMenu.this.getItem(i); for (int j = 0; j < me.length; j++) { if (me[j] == mi) { return true; } } } return false; } /** * Selects the <code>i</code>th menu in the menu. * If that item is a submenu, * it will pop up in response. If a different item is already * popped up, this will force it to close. If this is a sub-menu * that is already popped up (selected), this method has no * effect. * * @param i the index of the item to be selected * @see #getAccessibleStateSet */ public void addAccessibleSelection(int i) { if (i < 0 || i >= getItemCount()) { return; } JMenuItem mi = getItem(i); if (mi != null) { if (mi instanceof JMenu) { MenuElement[] me = buildMenuElementArray((JMenu) mi); MenuSelectionManager.defaultManager().setSelectedPath(me); } else { MenuSelectionManager.defaultManager().setSelectedPath(null); } } } /** * Removes the nth item from the selection. In general, menus * can only have one item within them selected at a time * (e.g. one sub-menu popped open). * * @param i the zero-based index of the selected item */ public void removeAccessibleSelection(int i) { if (i < 0 || i >= getItemCount()) { return; } JMenuItem mi = getItem(i); if (mi != null && mi instanceof JMenu) { if (mi.isSelected()) { MenuElement[] old = MenuSelectionManager.defaultManager().getSelectedPath(); MenuElement[] me = new MenuElement[old.length - 2]; for (int j = 0; j < old.length - 2; j++) { me[j] = old[j]; } MenuSelectionManager.defaultManager().setSelectedPath(me); } } } /** * Clears the selection in the object, so that nothing in the * object is selected. This will close any open sub-menu. */ public void clearAccessibleSelection() { // if this menu is selected, reset selection to only go // to this menu; else do nothing MenuElement[] old = MenuSelectionManager.defaultManager().getSelectedPath(); if (old != null) { for (int j = 0; j < old.length; j++) { if (old[j] == JMenu.this) { // menu is in the selection! MenuElement[] me = new MenuElement[j + 1]; System.arraycopy(old, 0, me, 0, j); me[j] = JMenu.this.getPopupMenu(); MenuSelectionManager.defaultManager().setSelectedPath(me); } } } } /** * Normally causes every selected item in the object to be selected * if the object supports multiple selections. This method * makes no sense in a menu bar, and so does nothing. */ public void selectAllAccessibleSelection() { } } // inner class AccessibleJMenu }