javafx.scene.control.Menu Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2023, 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 javafx.scene.control;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;
import com.sun.javafx.collections.TrackableObservableList;
import com.sun.javafx.scene.control.Logging;
import javafx.beans.DefaultProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
/**
*
* A popup menu of actionable items which is displayed to the user only upon request.
* When a menu is visible, in most use cases, the user can select one menu item
* before the menu goes back to its hidden state. This means the menu is a good
* place to put important functionality that does not necessarily need to be
* visible at all times to the user.
*
* Menus are typically placed in a {@link MenuBar}, or as a submenu of another Menu.
* If the intention is to offer a context menu when the user right-clicks in a
* certain area of their user interface, then this is the wrong control to use.
* This is because when Menu is added to the scenegraph, it has a visual
* representation that will result in it appearing on screen. Instead,
* {@link ContextMenu} should be used in this circumstance.
*
* Creating a Menu and inserting it into a MenuBar is easy, as shown below:
*
Menu menu1 = new Menu("File");
* MenuBar menuBar = new MenuBar(menu1);
*
*
* A Menu is a subclass of {@link MenuItem} which means that it can be inserted
* into a Menu's {@link #getItems() items} ObservableList, resulting in a submenu being created:
*
MenuItem menu12 = new MenuItem("Open");
* menu1.getItems().add(menu12);
*
*
*
*
* The items ObservableList allows for any {@link MenuItem} type to be inserted,
* including its subclasses {@link Menu}, {@link MenuItem}, {@link RadioMenuItem}, {@link CheckMenuItem},
* {@link CustomMenuItem} and {@link SeparatorMenuItem}. In order to insert an arbitrary {@link Node} to
* a Menu, a CustomMenuItem can be used. One exception to this general rule is that
* {@link SeparatorMenuItem} could be used for inserting a separator.
*
* @see MenuBar
* @see MenuItem
* @since JavaFX 2.0
*/
@DefaultProperty("items")
public class Menu extends MenuItem {
/**
*
Called when the contextMenu for this menu will be shown. However if the
* contextMenu is empty then this will not be called.
*
*/
public static final EventType ON_SHOWING =
new EventType<>(Event.ANY, "MENU_ON_SHOWING");
/**
* Called when the contextMenu for this menu shows. However if the
* contextMenu is empty then this will not be called.
*
*/
public static final EventType ON_SHOWN =
new EventType<>(Event.ANY, "MENU_ON_SHOWN");
/**
* Called when the contextMenu for this menu will be hidden. However if the
* contextMenu is empty then this will not be called.
*
*/
public static final EventType ON_HIDING =
new EventType<>(Event.ANY, "MENU_ON_HIDING");
/**
* Called when the contextMenu for this menu is hidden. However if the
* contextMenu is empty then this will not be called.
*
*/
public static final EventType ON_HIDDEN =
new EventType<>(Event.ANY, "MENU_ON_HIDDEN");
/* *************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Constructs a Menu with an empty string for its display text.
* @since JavaFX 2.2
*/
public Menu() {
this("");
}
/**
* Constructs a Menu and sets the display text with the specified text.
*
* @param text the text to display on the menu button
*/
public Menu(String text) {
this(text,null);
}
/**
* Constructs a Menu and sets the display text with the specified text
* and sets the graphic {@link Node} to the given node.
*
* @param text the text to display on the menu button
* @param graphic the graphic to display on the menu button
*/
public Menu(String text, Node graphic) {
this(text, graphic, (MenuItem[])null);
}
/**
* Constructs a Menu and sets the display text with the specified text,
* the graphic {@link Node} to the given node, and inserts the given items
* into the {@link #getItems() items} list.
*
* @param text the text to display on the menu button
* @param graphic the graphic to display on the menu button
* @param items The items to display in the popup menu.
* @since JavaFX 8u40
*/
public Menu(String text, Node graphic, MenuItem... items) {
super(text,graphic);
getStyleClass().add(DEFAULT_STYLE_CLASS);
if (items != null) {
getItems().addAll(items);
}
parentPopupProperty().addListener(observable -> {
for (int i = 0; i < getItems().size(); i++) {
MenuItem item = getItems().get(i);
item.setParentPopup(getParentPopup());
}
});
}
/* *************************************************************************
* *
* Properties *
* *
**************************************************************************/
/**
* Indicates whether the {@link ContextMenu} is currently visible.
*
* @defaultValue false
*/
private ReadOnlyBooleanWrapper showing;
private void setShowing(boolean value) {
if (getItems().size() == 0 || (value && isShowing())) return;
// these events will not fire if the showing property is bound
if (value) {
if (getOnMenuValidation() != null) {
Event.fireEvent(this, new Event(MENU_VALIDATION_EVENT));
for(MenuItem m : getItems()) {
if (!(m instanceof Menu) && m.getOnMenuValidation() != null) {
Event.fireEvent(m, new Event(MenuItem.MENU_VALIDATION_EVENT));
}
}
}
Event.fireEvent(this, new Event(Menu.ON_SHOWING));
} else {
Event.fireEvent(this, new Event(Menu.ON_HIDING));
}
showingPropertyImpl().set(value);
Event.fireEvent(this, (value) ? new Event(Menu.ON_SHOWN) :
new Event(Menu.ON_HIDDEN));
}
public final boolean isShowing() {
return showing == null ? false : showing.get();
}
public final ReadOnlyBooleanProperty showingProperty() {
return showingPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper showingPropertyImpl() {
if (showing == null) {
showing = new ReadOnlyBooleanWrapper() {
@Override protected void invalidated() {
// force validation
get();
// update the styleclass
if (isShowing()) {
getStyleClass().add(STYLE_CLASS_SHOWING);
} else {
getStyleClass().remove(STYLE_CLASS_SHOWING);
}
}
@Override
public Object getBean() {
return Menu.this;
}
@Override
public String getName() {
return "showing";
}
};
}
return showing;
}
// --- On Showing
/**
* Called just prior to the {@code ContextMenu} being shown, even if the menu has
* no items to show. Note however that this won't be called if the menu does
* not have a valid anchor node.
* @return the on showing property
*/
public final ObjectProperty> onShowingProperty() { return onShowing; }
public final void setOnShowing(EventHandler value) { onShowingProperty().set(value); }
public final EventHandler getOnShowing() { return onShowingProperty().get(); }
private ObjectProperty> onShowing = new ObjectPropertyBase<>() {
@Override protected void invalidated() {
eventHandlerManager.setEventHandler(ON_SHOWING, get());
}
@Override
public Object getBean() {
return Menu.this;
}
@Override
public String getName() {
return "onShowing";
}
};
// -- On Shown
/**
* Called just after the {@link ContextMenu} is shown.
* @return the on shown property
*/
public final ObjectProperty> onShownProperty() { return onShown; }
public final void setOnShown(EventHandler value) { onShownProperty().set(value); }
public final EventHandler getOnShown() { return onShownProperty().get(); }
private ObjectProperty> onShown = new ObjectPropertyBase<>() {
@Override protected void invalidated() {
eventHandlerManager.setEventHandler(ON_SHOWN, get());
}
@Override
public Object getBean() {
return Menu.this;
}
@Override
public String getName() {
return "onShown";
}
};
// --- On Hiding
/**
* Called just prior to the {@link ContextMenu} being hidden.
* @return the on hiding property
*/
public final ObjectProperty> onHidingProperty() { return onHiding; }
public final void setOnHiding(EventHandler value) { onHidingProperty().set(value); }
public final EventHandler getOnHiding() { return onHidingProperty().get(); }
private ObjectProperty> onHiding = new ObjectPropertyBase<>() {
@Override protected void invalidated() {
eventHandlerManager.setEventHandler(ON_HIDING, get());
}
@Override
public Object getBean() {
return Menu.this;
}
@Override
public String getName() {
return "onHiding";
}
};
// --- On Hidden
/**
* Called just after the {@link ContextMenu} has been hidden.
* @return the on hidden property
*/
public final ObjectProperty> onHiddenProperty() { return onHidden; }
public final void setOnHidden(EventHandler value) { onHiddenProperty().set(value); }
public final EventHandler getOnHidden() { return onHiddenProperty().get(); }
private ObjectProperty> onHidden = new ObjectPropertyBase<>() {
@Override protected void invalidated() {
eventHandlerManager.setEventHandler(ON_HIDDEN, get());
}
@Override
public Object getBean() {
return Menu.this;
}
@Override
public String getName() {
return "onHidden";
}
};
/* *************************************************************************
* *
* Instance variables *
* *
**************************************************************************/
private final ObservableList