/* * Copyright (c) 1997, 2015, 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.*; import java.awt.event.*; import java.beans.JavaBean; import java.beans.BeanProperty; import java.io.Serializable; import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.io.IOException; import javax.swing.plaf.*; import javax.swing.event.*; import javax.accessibility.*; /** * An implementation of an item in a menu. A menu item is essentially a button * sitting in a list. When the user selects the "button", the action * associated with the menu item is performed. A JMenuItem * contained in a JPopupMenu performs exactly that function. *

* Menu items can be configured, and to some degree controlled, by * Actions. Using an * Action with a menu item has many benefits beyond directly * configuring a menu item. Refer to * Swing Components Supporting Action for more * details, and you can find more information in How * to Use Actions, a section in The Java Tutorial. *

* For further documentation and for examples, see * How to Use Menus * in The Java Tutorial. *

* Warning: Swing is not thread safe. For more * information see Swing's Threading * Policy. *

* Warning: * 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 java.beans package. * Please see {@link java.beans.XMLEncoder}. * * @author Georges Saab * @author David Karlton * @see JPopupMenu * @see JMenu * @see JCheckBoxMenuItem * @see JRadioButtonMenuItem * @since 1.2 */ @JavaBean(defaultProperty = "UIClassID", description = "An item which can be selected in a menu.") @SwingContainer(false) @SuppressWarnings("serial") public class JMenuItem extends AbstractButton implements Accessible,MenuElement { /** * @see #getUIClassID * @see #readObject */ private static final String uiClassID = "MenuItemUI"; /* 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. private boolean isMouseDragged = false; /** * Creates a JMenuItem with no set text or icon. */ public JMenuItem() { this(null, (Icon)null); } /** * Creates a JMenuItem with the specified icon. * * @param icon the icon of the JMenuItem */ public JMenuItem(Icon icon) { this(null, icon); } /** * Creates a JMenuItem with the specified text. * * @param text the text of the JMenuItem */ public JMenuItem(String text) { this(text, (Icon)null); } /** * Creates a menu item whose properties are taken from the * specified Action. * * @param a the action of the JMenuItem * @since 1.3 */ public JMenuItem(Action a) { this(); setAction(a); } /** * Creates a JMenuItem with the specified text and icon. * * @param text the text of the JMenuItem * @param icon the icon of the JMenuItem */ public JMenuItem(String text, Icon icon) { setModel(new DefaultButtonModel()); init(text, icon); initFocusability(); } /** * Creates a JMenuItem with the specified text and * keyboard mnemonic. * * @param text the text of the JMenuItem * @param mnemonic the keyboard mnemonic for the JMenuItem */ public JMenuItem(String text, int mnemonic) { setModel(new DefaultButtonModel()); init(text, null); setMnemonic(mnemonic); initFocusability(); } /** * {@inheritDoc} */ public void setModel(ButtonModel newModel) { super.setModel(newModel); if(newModel instanceof DefaultButtonModel) { ((DefaultButtonModel)newModel).setMenuItem(true); } } /** * Inititalizes the focusability of the JMenuItem. * JMenuItem's are focusable, but subclasses may * want to be, this provides them the opportunity to override this * and invoke something else, or nothing at all. Refer to * {@link javax.swing.JMenu#initFocusability} for the motivation of * this. */ void initFocusability() { setFocusable(false); } /** * Initializes the menu item with the specified text and icon. * * @param text the text of the JMenuItem * @param icon the icon of the JMenuItem */ protected void init(String text, Icon icon) { if(text != null) { setText(text); } if(icon != null) { setIcon(icon); } // Listen for Focus events addFocusListener(new MenuItemFocusListener()); setUIProperty("borderPainted", Boolean.FALSE); setFocusPainted(false); setHorizontalTextPosition(JButton.TRAILING); setHorizontalAlignment(JButton.LEADING); updateUI(); } private static class MenuItemFocusListener implements FocusListener, Serializable { public void focusGained(FocusEvent event) {} public void focusLost(FocusEvent event) { // When focus is lost, repaint if // the focus information is painted JMenuItem mi = (JMenuItem)event.getSource(); if(mi.isFocusPainted()) { mi.repaint(); } } } /** * Sets the look and feel object that renders this component. * * @param ui the JMenuItemUI L&F object * @see UIDefaults#getUI */ @BeanProperty(hidden = true, visualUpdate = true, description = "The UI object that implements the LookAndFeel.") public void setUI(MenuItemUI ui) { super.setUI(ui); } /** * Resets the UI property with a value from the current look and feel. * * @see JComponent#updateUI */ public void updateUI() { setUI((MenuItemUI)UIManager.getUI(this)); } /** * Returns the suffix used to construct the name of the L&F class used to * render this component. * * @return the string "MenuItemUI" * @see JComponent#getUIClassID * @see UIDefaults#getUI */ @BeanProperty(bound = false) public String getUIClassID() { return uiClassID; } /** * Identifies the menu item as "armed". If the mouse button is * released while it is over this item, the menu's action event * will fire. If the mouse button is released elsewhere, the * event will not fire and the menu item will be disarmed. * * @param b true to arm the menu item so it can be selected */ @BeanProperty(bound = false, hidden = true, description = "Mouse release will fire an action event") public void setArmed(boolean b) { ButtonModel model = getModel(); boolean oldValue = model.isArmed(); if(model.isArmed() != b) { model.setArmed(b); } } /** * Returns whether the menu item is "armed". * * @return true if the menu item is armed, and it can be selected * @see #setArmed */ public boolean isArmed() { ButtonModel model = getModel(); return model.isArmed(); } /** * Enables or disables the menu item. * * @param b true to enable the item */ @BeanProperty(preferred = true, description = "The enabled state of the component.") public void setEnabled(boolean b) { // Make sure we aren't armed! if (!b && !UIManager.getBoolean("MenuItem.disabledAreNavigable")) { setArmed(false); } super.setEnabled(b); } /** * Returns true since Menus, by definition, * should always be on top of all other windows. If the menu is * in an internal frame false is returned due to the rollover effect * for windows laf where the menu is not always on top. */ // package private boolean alwaysOnTop() { // Fix for bug #4482165 if (SwingUtilities.getAncestorOfClass(JInternalFrame.class, this) != null) { return false; } return true; } /* The keystroke which acts as the menu item's accelerator */ private KeyStroke accelerator; /** * Sets the key combination which invokes the menu item's * action listeners without navigating the menu hierarchy. It is the * UI's responsibility to install the correct action. Note that * when the keyboard accelerator is typed, it will work whether or * not the menu is currently displayed. * * @param keyStroke the KeyStroke which will * serve as an accelerator */ @BeanProperty(preferred = true, description = "The keystroke combination which will invoke the JMenuItem's actionlisteners without navigating the menu hierarchy") public void setAccelerator(KeyStroke keyStroke) { KeyStroke oldAccelerator = accelerator; this.accelerator = keyStroke; repaint(); revalidate(); firePropertyChange("accelerator", oldAccelerator, accelerator); } /** * Returns the KeyStroke which serves as an accelerator * for the menu item. * @return a KeyStroke object identifying the * accelerator key */ public KeyStroke getAccelerator() { return this.accelerator; } /** * {@inheritDoc} * * @since 1.3 */ protected void configurePropertiesFromAction(Action a) { super.configurePropertiesFromAction(a); configureAcceleratorFromAction(a); } void setIconFromAction(Action a) { Icon icon = null; if (a != null) { icon = (Icon)a.getValue(Action.SMALL_ICON); } setIcon(icon); } void largeIconChanged(Action a) { } void smallIconChanged(Action a) { setIconFromAction(a); } void configureAcceleratorFromAction(Action a) { KeyStroke ks = (a==null) ? null : (KeyStroke)a.getValue(Action.ACCELERATOR_KEY); setAccelerator(ks); } /** * {@inheritDoc} * @since 1.6 */ protected void actionPropertyChanged(Action action, String propertyName) { if (propertyName == Action.ACCELERATOR_KEY) { configureAcceleratorFromAction(action); } else { super.actionPropertyChanged(action, propertyName); } } /** * Processes a mouse event forwarded from the * MenuSelectionManager and changes the menu * selection, if necessary, by using the * MenuSelectionManager's API. *

* Note: you do not have to forward the event to sub-components. * This is done automatically by the MenuSelectionManager. * * @param e a MouseEvent * @param path the MenuElement path array * @param manager the MenuSelectionManager */ public void processMouseEvent(MouseEvent e,MenuElement path[],MenuSelectionManager manager) { processMenuDragMouseEvent( new MenuDragMouseEvent(e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), e.getClickCount(), e.isPopupTrigger(), path, manager)); } /** * Processes a key event forwarded from the * MenuSelectionManager and changes the menu selection, * if necessary, by using MenuSelectionManager's API. *

* Note: you do not have to forward the event to sub-components. * This is done automatically by the MenuSelectionManager. * * @param e a KeyEvent * @param path the MenuElement path array * @param manager the MenuSelectionManager */ public void processKeyEvent(KeyEvent e,MenuElement path[],MenuSelectionManager manager) { if (DEBUG) { System.out.println("in JMenuItem.processKeyEvent/3 for " + getText() + " " + KeyStroke.getKeyStrokeForEvent(e)); } MenuKeyEvent mke = new MenuKeyEvent(e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(), e.getKeyCode(), e.getKeyChar(), path, manager); processMenuKeyEvent(mke); if (mke.isConsumed()) { e.consume(); } } /** * Handles mouse drag in a menu. * * @param e a MenuDragMouseEvent object */ public void processMenuDragMouseEvent(MenuDragMouseEvent e) { switch (e.getID()) { case MouseEvent.MOUSE_ENTERED: isMouseDragged = false; fireMenuDragMouseEntered(e); break; case MouseEvent.MOUSE_EXITED: isMouseDragged = false; fireMenuDragMouseExited(e); break; case MouseEvent.MOUSE_DRAGGED: isMouseDragged = true; fireMenuDragMouseDragged(e); break; case MouseEvent.MOUSE_RELEASED: if(isMouseDragged) fireMenuDragMouseReleased(e); break; default: break; } } /** * Handles a keystroke in a menu. * * @param e a MenuKeyEvent object */ public void processMenuKeyEvent(MenuKeyEvent e) { if (DEBUG) { System.out.println("in JMenuItem.processMenuKeyEvent for " + getText()+ " " + KeyStroke.getKeyStrokeForEvent(e)); } switch (e.getID()) { case KeyEvent.KEY_PRESSED: fireMenuKeyPressed(e); break; case KeyEvent.KEY_RELEASED: fireMenuKeyReleased(e); break; case KeyEvent.KEY_TYPED: fireMenuKeyTyped(e); break; default: break; } } /** * Notifies all listeners that have registered interest for * notification on this event type. * * @param event a MenuMouseDragEvent * @see EventListenerList */ protected void fireMenuDragMouseEntered(MenuDragMouseEvent event) { // 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]==MenuDragMouseListener.class) { // Lazily create the event: ((MenuDragMouseListener)listeners[i+1]).menuDragMouseEntered(event); } } } /** * Notifies all listeners that have registered interest for * notification on this event type. * * @param event a MenuDragMouseEvent * @see EventListenerList */ protected void fireMenuDragMouseExited(MenuDragMouseEvent event) { // 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]==MenuDragMouseListener.class) { // Lazily create the event: ((MenuDragMouseListener)listeners[i+1]).menuDragMouseExited(event); } } } /** * Notifies all listeners that have registered interest for * notification on this event type. * * @param event a MenuDragMouseEvent * @see EventListenerList */ protected void fireMenuDragMouseDragged(MenuDragMouseEvent event) { // 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]==MenuDragMouseListener.class) { // Lazily create the event: ((MenuDragMouseListener)listeners[i+1]).menuDragMouseDragged(event); } } } /** * Notifies all listeners that have registered interest for * notification on this event type. * * @param event a MenuDragMouseEvent * @see EventListenerList */ protected void fireMenuDragMouseReleased(MenuDragMouseEvent event) { // 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]==MenuDragMouseListener.class) { // Lazily create the event: ((MenuDragMouseListener)listeners[i+1]).menuDragMouseReleased(event); } } } /** * Notifies all listeners that have registered interest for * notification on this event type. * * @param event a MenuKeyEvent * @see EventListenerList */ protected void fireMenuKeyPressed(MenuKeyEvent event) { if (DEBUG) { System.out.println("in JMenuItem.fireMenuKeyPressed for " + getText()+ " " + KeyStroke.getKeyStrokeForEvent(event)); } // 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]==MenuKeyListener.class) { // Lazily create the event: ((MenuKeyListener)listeners[i+1]).menuKeyPressed(event); } } } /** * Notifies all listeners that have registered interest for * notification on this event type. * * @param event a MenuKeyEvent * @see EventListenerList */ protected void fireMenuKeyReleased(MenuKeyEvent event) { if (DEBUG) { System.out.println("in JMenuItem.fireMenuKeyReleased for " + getText()+ " " + KeyStroke.getKeyStrokeForEvent(event)); } // 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]==MenuKeyListener.class) { // Lazily create the event: ((MenuKeyListener)listeners[i+1]).menuKeyReleased(event); } } } /** * Notifies all listeners that have registered interest for * notification on this event type. * * @param event a MenuKeyEvent * @see EventListenerList */ protected void fireMenuKeyTyped(MenuKeyEvent event) { if (DEBUG) { System.out.println("in JMenuItem.fireMenuKeyTyped for " + getText()+ " " + KeyStroke.getKeyStrokeForEvent(event)); } // 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]==MenuKeyListener.class) { // Lazily create the event: ((MenuKeyListener)listeners[i+1]).menuKeyTyped(event); } } } /** * Called by the MenuSelectionManager when the * MenuElement is selected or unselected. * * @param isIncluded true if this menu item is on the part of the menu * path that changed, false if this menu is part of the * a menu path that changed, but this particular part of * that path is still the same * @see MenuSelectionManager#setSelectedPath(MenuElement[]) */ public void menuSelectionChanged(boolean isIncluded) { setArmed(isIncluded); } /** * This method returns an array containing the sub-menu * components for this menu component. * * @return an array of MenuElements */ @BeanProperty(bound = false) public MenuElement[] getSubElements() { return new MenuElement[0]; } /** * Returns the java.awt.Component used to paint * this object. The returned component will be used to convert * events and detect if an event is inside a menu component. * * @return the Component that paints this menu item */ public Component getComponent() { return this; } /** * Adds a MenuDragMouseListener to the menu item. * * @param l the MenuDragMouseListener to be added */ public void addMenuDragMouseListener(MenuDragMouseListener l) { listenerList.add(MenuDragMouseListener.class, l); } /** * Removes a MenuDragMouseListener from the menu item. * * @param l the MenuDragMouseListener to be removed */ public void removeMenuDragMouseListener(MenuDragMouseListener l) { listenerList.remove(MenuDragMouseListener.class, l); } /** * Returns an array of all the MenuDragMouseListeners added * to this JMenuItem with addMenuDragMouseListener(). * * @return all of the MenuDragMouseListeners added or an empty * array if no listeners have been added * @since 1.4 */ @BeanProperty(bound = false) public MenuDragMouseListener[] getMenuDragMouseListeners() { return listenerList.getListeners(MenuDragMouseListener.class); } /** * Adds a MenuKeyListener to the menu item. * * @param l the MenuKeyListener to be added */ public void addMenuKeyListener(MenuKeyListener l) { listenerList.add(MenuKeyListener.class, l); } /** * Removes a MenuKeyListener from the menu item. * * @param l the MenuKeyListener to be removed */ public void removeMenuKeyListener(MenuKeyListener l) { listenerList.remove(MenuKeyListener.class, l); } /** * Returns an array of all the MenuKeyListeners added * to this JMenuItem with addMenuKeyListener(). * * @return all of the MenuKeyListeners added or an empty * array if no listeners have been added * @since 1.4 */ @BeanProperty(bound = false) public MenuKeyListener[] getMenuKeyListeners() { return listenerList.getListeners(MenuKeyListener.class); } /** * See JComponent.readObject() for information about serialization * in Swing. */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); if (getUIClassID().equals(uiClassID)) { updateUI(); } } 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 JMenuItem. * 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 null. * * @return a string representation of this JMenuItem */ protected String paramString() { return super.paramString(); } ///////////////// // Accessibility support //////////////// /** * Returns the AccessibleContext associated with this * JMenuItem. For JMenuItems, * the AccessibleContext takes the form of an * AccessibleJMenuItem. * A new AccessibleJMenuItme instance is created if necessary. * * @return an AccessibleJMenuItem that serves as the * AccessibleContext of this JMenuItem */ @BeanProperty(bound = false) public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleJMenuItem(); } return accessibleContext; } /** * This class implements accessibility support for the * JMenuItem class. It provides an implementation of the * Java Accessibility API appropriate to menu item user-interface * elements. *

* Warning: * 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 java.beans package. * Please see {@link java.beans.XMLEncoder}. */ @SuppressWarnings("serial") protected class AccessibleJMenuItem extends AccessibleAbstractButton implements ChangeListener { private boolean isArmed = false; private boolean hasFocus = false; private boolean isPressed = false; private boolean isSelected = false; AccessibleJMenuItem() { super(); JMenuItem.this.addChangeListener(this); } /** * Get the role of this object. * * @return an instance of AccessibleRole describing the role of the * object */ public AccessibleRole getAccessibleRole() { return AccessibleRole.MENU_ITEM; } private void fireAccessibilityFocusedEvent(JMenuItem toCheck) { MenuElement [] path = MenuSelectionManager.defaultManager().getSelectedPath(); if (path.length > 0) { Object menuItem = path[path.length - 1]; if (toCheck == menuItem) { firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, null, AccessibleState.FOCUSED); } } } /** * Supports the change listener interface and fires property changes. */ public void stateChanged(ChangeEvent e) { firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, Boolean.valueOf(false), Boolean.valueOf(true)); if (JMenuItem.this.getModel().isArmed()) { if (!isArmed) { isArmed = true; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, null, AccessibleState.ARMED); // Fix for 4848220 moved here to avoid major memory leak // Here we will fire the event in case of JMenuItem // See bug 4910323 for details [zav] fireAccessibilityFocusedEvent(JMenuItem.this); } } else { if (isArmed) { isArmed = false; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.ARMED, null); } } if (JMenuItem.this.isFocusOwner()) { if (!hasFocus) { hasFocus = true; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, null, AccessibleState.FOCUSED); } } else { if (hasFocus) { hasFocus = false; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.FOCUSED, null); } } if (JMenuItem.this.getModel().isPressed()) { if (!isPressed) { isPressed = true; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, null, AccessibleState.PRESSED); } } else { if (isPressed) { isPressed = false; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.PRESSED, null); } } if (JMenuItem.this.getModel().isSelected()) { if (!isSelected) { isSelected = true; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, null, AccessibleState.CHECKED); // Fix for 4848220 moved here to avoid major memory leak // Here we will fire the event in case of JMenu // See bug 4910323 for details [zav] fireAccessibilityFocusedEvent(JMenuItem.this); } } else { if (isSelected) { isSelected = false; firePropertyChange( AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.CHECKED, null); } } } } // inner class AccessibleJMenuItem }