/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.control;
import com.sun.javafx.beans.IDProperty;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.css.CssMetaData;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.input.KeyCombination;
import com.sun.javafx.event.EventHandlerManager;
import com.sun.javafx.scene.control.skin.ContextMenuContent;
import com.sun.javafx.scene.control.skin.ContextMenuSkin;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.ObservableMap;
import javafx.scene.Parent;
/**
*
* MenuItem is intended to be used in conjunction with {@link Menu} to provide
* options to users. MenuItem serves as the base class for the bulk of JavaFX menus
* API.
* It has a display {@link #getText() text} property, as well as an optional {@link #getGraphic() graphic} node
* that can be set on it.
* The {@link #getAccelerator() accelerator} property enables accessing the
* associated action in one keystroke. Also, as with the {@link Button} control,
* by using the {@link #setOnAction} method, you can have an instance of MenuItem
* perform any action you wish.
*
* Note: Whilst any size of graphic can be inserted into a MenuItem, the most
* commonly used size in most applications is 16x16 pixels. This is
* the recommended graphic dimension to use if you're using the default style provided by
* JavaFX.
*
* To create a MenuItem is simple:
MenuItem menuItem = new MenuItem("Open");
menuItem.setOnAction(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent e) {
System.out.println("Opening Database Connection...");
}
});
menuItem.setGraphic(new ImageView(new Image("flower.png")));
*
* Refer to the {@link Menu} page to learn how to insert MenuItem into a menu
* instance. Briefly however, you can insert the MenuItem from the previous
* example into a Menu as such:
final Menu menu = new Menu("File");
menu.getItems().add(menuItem);
*
* @see Menu
* @since JavaFX 2.0
*/
@IDProperty("id")
public class MenuItem implements EventTarget, Styleable {
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Constructs a MenuItem with no display text.
*/
public MenuItem() {
this(null,null);
}
/**
* Constructs a MenuItem and sets the display text with the specified text
* @see #setText
*/
public MenuItem(String text) {
this(text,null);
}
/**
* Constructor s MenuItem and sets the display text with the specified text
* and sets the graphic {@link Node} to the given node.
* @see #setText
* @see #setGraphic
*/
public MenuItem(String text, Node graphic) {
setText(text);
setGraphic(graphic);
styleClass.add(DEFAULT_STYLE_CLASS);
}
/***************************************************************************
* *
* Instance Variables *
* *
**************************************************************************/
private final ObservableList styleClass = FXCollections.observableArrayList();
final EventHandlerManager eventHandlerManager =
new EventHandlerManager(this);
private Object userData;
private ObservableMap properties;
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
/**
* The id of this MenuItem. This simple string identifier is useful for finding
* a specific MenuItem within the scene graph.
*/
private StringProperty id;
public final void setId(String value) { idProperty().set(value); }
@Override public final String getId() { return id == null ? null : id.get(); }
public final StringProperty idProperty() {
if (id == null) {
id = new SimpleStringProperty(this, "id");
}
return id;
}
/**
* A string representation of the CSS style associated with this specific MenuItem.
* This is analogous to the "style" attribute of an HTML element. Note that,
* like the HTML style attribute, this variable contains style properties and
* values and not the selector portion of a style rule.
*/
private StringProperty style;
public final void setStyle(String value) { styleProperty().set(value); }
@Override public final String getStyle() { return style == null ? null : style.get(); }
public final StringProperty styleProperty() {
if (style == null) {
style = new SimpleStringProperty(this, "style");
}
return style;
}
// --- Parent Menu (useful for submenus)
/**
* This is the {@link Menu} in which this {@code MenuItem} exists. It is
* possible for an instance of this class to not have a {@code parentMenu} -
* this means that this instance is either:
*
* Not yet associated with its {@code parentMenu}.
* A 'root' {@link Menu} (i.e. it is a context menu, attached directly to a
* {@link MenuBar}, {@link MenuButton}, or any of the other controls that use
* {@link Menu} internally.
*
*/
private ReadOnlyObjectWrapper parentMenu;
protected final void setParentMenu(Menu value) {
parentMenuPropertyImpl().set(value);
}
public final Menu getParentMenu() {
return parentMenu == null ? null : parentMenu.get();
}
public final ReadOnlyObjectProperty parentMenuProperty() {
return parentMenuPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyObjectWrapper parentMenuPropertyImpl() {
if (parentMenu == null) {
parentMenu = new ReadOnlyObjectWrapper(this, "parentMenu");
}
return parentMenu;
}
// --- Parent Popup
/**
* This is the {@link ContextMenu} in which this {@code MenuItem} exists.
*/
private ReadOnlyObjectWrapper parentPopup;
protected final void setParentPopup(ContextMenu value) {
parentPopupPropertyImpl().set(value);
}
public final ContextMenu getParentPopup() {
return parentPopup == null ? null : parentPopup.get();
}
public final ReadOnlyObjectProperty parentPopupProperty() {
return parentPopupPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyObjectWrapper parentPopupPropertyImpl() {
if (parentPopup == null) {
parentPopup = new ReadOnlyObjectWrapper(this, "parentPopup");
}
return parentPopup;
}
// --- Text
/**
* The text to display in the {@code MenuItem}.
*/
private StringProperty text;
public final void setText(String value) {
textProperty().set(value);
}
public final String getText() {
return text == null ? null : text.get();
}
public final StringProperty textProperty() {
if (text == null) {
text = new SimpleStringProperty(this, "text");
}
return text;
}
// --- Graphic
/**
* An optional graphic for the {@code MenuItem}. This will normally be
* an {@link javafx.scene.image.ImageView} node, but there is no requirement for this to be
* the case.
*/
private ObjectProperty graphic;
public final void setGraphic(Node value) {
graphicProperty().set(value);
}
public final Node getGraphic() {
return graphic == null ? null : graphic.get();
}
public final ObjectProperty graphicProperty() {
if (graphic == null) {
graphic = new SimpleObjectProperty(this, "graphic");
}
return graphic;
}
// --- OnAction
/**
* The action, which is invoked whenever the MenuItem is fired. This
* may be due to the user clicking on the button with the mouse, or by
* a touch event, or by a key press, or if the developer programatically
* invokes the {@link #fire()} method.
*/
private ObjectProperty> onAction;
public final void setOnAction(EventHandler value) {
onActionProperty().set( value);
}
public final EventHandler getOnAction() {
return onAction == null ? null : onAction.get();
}
public final ObjectProperty> onActionProperty() {
if (onAction == null) {
onAction = new ObjectPropertyBase>() {
@Override protected void invalidated() {
eventHandlerManager.setEventHandler(ActionEvent.ACTION, get());
}
@Override
public Object getBean() {
return MenuItem.this;
}
@Override
public String getName() {
return "onAction";
}
};
}
return onAction;
}
/**
* Called when a accelerator for the Menuitem is invoked
* @since JavaFX 2.2
*/
public static final EventType MENU_VALIDATION_EVENT = new EventType
(Event.ANY, "MENU_VALIDATION_EVENT");
/**
* The event handler that is associated with invocation of an accelerator for a MenuItem. This
* can happen when a key sequence for an accelerator is pressed. The event handler is also
* invoked when onShowing event handler is called.
* @since JavaFX 2.2
*/
private ObjectProperty> onMenuValidation;
public final void setOnMenuValidation(EventHandler value) {
onMenuValidationProperty().set( value);
}
public final EventHandler getOnMenuValidation() {
return onMenuValidation == null ? null : onMenuValidation.get();
}
public final ObjectProperty> onMenuValidationProperty() {
if (onMenuValidation == null) {
onMenuValidation = new ObjectPropertyBase>() {
@Override protected void invalidated() {
eventHandlerManager.setEventHandler(MENU_VALIDATION_EVENT, get());
}
@Override public Object getBean() {
return MenuItem.this;
}
@Override public String getName() {
return "onMenuValidation";
}
};
}
return onMenuValidation;
}
// --- Disable
/**
* Sets the individual disabled state of this MenuItem.
* Setting disable to true will cause this MenuItem to become disabled.
*/
private BooleanProperty disable;
public final void setDisable(boolean value) { disableProperty().set(value); }
public final boolean isDisable() { return disable == null ? false : disable.get(); }
public final BooleanProperty disableProperty() {
if (disable == null) {
disable = new SimpleBooleanProperty(this, "disable");
}
return disable;
}
// --- Visible
/**
* Specifies whether this MenuItem should be rendered as part of the scene graph.
*/
private BooleanProperty visible;
public final void setVisible(boolean value) { visibleProperty().set(value); }
public final boolean isVisible() { return visible == null ? true : visible.get(); }
public final BooleanProperty visibleProperty() {
if (visible == null) {
visible = new SimpleBooleanProperty(this, "visible", true);
}
return visible;
}
/**
* The accelerator property enables accessing the associated action in one keystroke.
* It is a convenience offered to perform quickly a given action.
*/
private ObjectProperty accelerator;
public final void setAccelerator(KeyCombination value) {
acceleratorProperty().set(value);
}
public final KeyCombination getAccelerator() {
return accelerator == null ? null : accelerator.get();
}
public final ObjectProperty acceleratorProperty() {
if (accelerator == null) {
accelerator = new SimpleObjectProperty(this, "accelerator");
}
return accelerator;
}
/**
* MnemonicParsing property to enable/disable text parsing.
* If this is set to true, then the MenuItem text will be
* parsed to see if it contains the mnemonic parsing character '_'.
* When a mnemonic is detected the key combination will
* be determined based on the succeeding character, and the mnemonic
* added.
*
*
* The default value for MenuItem is true.
*
*/
private BooleanProperty mnemonicParsing;
public final void setMnemonicParsing(boolean value) {
mnemonicParsingProperty().set(value);
}
public final boolean isMnemonicParsing() {
return mnemonicParsing == null ? true : mnemonicParsing.get();
}
public final BooleanProperty mnemonicParsingProperty() {
if (mnemonicParsing == null) {
mnemonicParsing = new SimpleBooleanProperty(this, "mnemonicParsing", true);
}
return mnemonicParsing;
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
@Override public ObservableList getStyleClass() {
return styleClass;
}
/**
* Fires a new ActionEvent.
*/
public void fire() {
Event.fireEvent(this, new ActionEvent(this, this));
}
/**
* Registers an event handler to this MenuItem. The handler is called when the
* menu item receives an {@code Event} of the specified type during the bubbling
* phase of event delivery.
*
* @param the specific event class of the handler
* @param eventType the type of the events to receive by the handler
* @param eventHandler the handler to register
* @throws NullPointerException if the event type or handler is null
*/
public void addEventHandler(EventType eventType, EventHandler eventHandler) {
eventHandlerManager.addEventHandler(eventType, eventHandler);
}
/**
* Unregisters a previously registered event handler from this MenuItem. One
* handler might have been registered for different event types, so the
* caller needs to specify the particular event type from which to
* unregister the handler.
*
* @param the specific event class of the handler
* @param eventType the event type from which to unregister
* @param eventHandler the handler to unregister
* @throws NullPointerException if the event type or handler is null
*/
public void removeEventHandler(EventType eventType, EventHandler eventHandler) {
eventHandlerManager.removeEventHandler(eventType, eventHandler);
}
/** {@inheritDoc} */
@Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
// FIXME review that these are configure properly
if (getParentPopup() != null) {
getParentPopup().buildEventDispatchChain(tail);
}
if (getParentMenu() != null) {
getParentMenu().buildEventDispatchChain(tail);
}
return tail.prepend(eventHandlerManager);
}
/**
* Returns a previously set Object property, or null if no such property
* has been set using the {@link MenuItem#setUserData(java.lang.Object)} method.
*
* @return The Object that was previously set, or null if no property
* has been set or if null was set.
*/
public Object getUserData() {
return userData;
}
/**
* Convenience method for setting a single Object property that can be
* retrieved at a later date. This is functionally equivalent to calling
* the getProperties().put(Object key, Object value) method. This can later
* be retrieved by calling {@link Node#getUserData()}.
*
* @param value The value to be stored - this can later be retrieved by calling
* {@link Node#getUserData()}.
*/
public void setUserData(Object value) {
this.userData = value;
}
/**
* Returns an observable map of properties on this menu item for use primarily
* by application developers.
*
* @return an observable map of properties on this menu item for use primarily
* by application developers
*/
public ObservableMap getProperties() {
if (properties == null) {
properties = FXCollections.observableMap(new HashMap());
}
return properties;
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
private static final String DEFAULT_STYLE_CLASS = "menu-item";
/**
* {@inheritDoc}
* @return "MenuItem"
* @since JavaFX 8.0
*/
@Override
public String getTypeSelector() {
return "MenuItem";
}
/**
* {@inheritDoc}
* @return {@code getParentMenu()}, or {@code getParentPopup()}
* if {@code parentMenu} is null
* @since JavaFX 8.0
*/
@Override
public Styleable getStyleableParent() {
if(getParentMenu() == null) {
return getParentPopup();
} else {
return getParentMenu();
}
}
/**
* {@inheritDoc}
* @since JavaFX 8.0
*/
public final ObservableSet getPseudoClassStates() {
return FXCollections.emptyObservableSet();
}
@Override
public List> getCssMetaData() {
return Collections.emptyList();
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
public Node impl_styleableGetNode() {
// Fix for RT-20582. We dive into the visual representation
// of this MenuItem so that we may return it to the caller.
ContextMenu parentPopup = MenuItem.this.getParentPopup();
if (parentPopup == null || ! (parentPopup.getSkin() instanceof ContextMenuSkin)) return null;
ContextMenuSkin skin = (ContextMenuSkin) parentPopup.getSkin();
if (! (skin.getNode() instanceof ContextMenuContent)) return null;
ContextMenuContent content = (ContextMenuContent) skin.getNode();
Parent nodes = content.getItemsContainer();
MenuItem desiredMenuItem = MenuItem.this;
List childrenNodes = nodes.getChildrenUnmodifiable();
for (int i = 0; i < childrenNodes.size(); i++) {
if (! (childrenNodes.get(i) instanceof ContextMenuContent.MenuItemContainer)) continue;
ContextMenuContent.MenuItemContainer MenuRow =
(ContextMenuContent.MenuItemContainer) childrenNodes.get(i);
if (desiredMenuItem.equals(MenuRow.getItem())) {
return MenuRow;
}
}
return null;
}
@Override public String toString() {
StringBuilder sbuf = new StringBuilder(getClass().getSimpleName());
boolean hasId = id != null && !"".equals(getId());
boolean hasStyleClass = !getStyleClass().isEmpty();
if (!hasId) {
sbuf.append('@');
sbuf.append(Integer.toHexString(hashCode()));
} else {
sbuf.append("[id=");
sbuf.append(getId());
if (!hasStyleClass) sbuf.append("]");
}
if (hasStyleClass) {
if (!hasId) sbuf.append('[');
else sbuf.append(", ");
sbuf.append("styleClass=");
sbuf.append(getStyleClass());
sbuf.append("]");
}
return sbuf.toString();
}
}