/* * Copyright (c) 2010, 2017, 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.css.PseudoClass; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.geometry.Side; import javafx.scene.AccessibleAction; import javafx.scene.AccessibleRole; import javafx.scene.Node; import javafx.scene.control.skin.MenuButtonSkin; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; /** * MenuButton is a button which, when clicked or pressed, will show a * {@link ContextMenu}. A MenuButton shares a very similar API to the {@link Menu} * control, insofar that you set the items that should be shown in the * {@link #getItems() items} ObservableList, and there is a {@link #textProperty() text} property to specify the * label shown within the MenuButton. *

* As mentioned, like the Menu API itself, you'll find an {@link #getItems() items} ObservableList * within which you can provide anything that extends from {@link MenuItem}. * There are several useful subclasses of {@link MenuItem} including * {@link RadioMenuItem}, {@link CheckMenuItem}, {@link Menu}, * {@link SeparatorMenuItem} and {@link CustomMenuItem}. *

* A MenuButton can be set to show its menu on any side of the button. This is * specified using the {@link #popupSideProperty() popupSide} property. By default * the menu appears below the button. However, regardless of the popupSide specified, * if there is not enough room, the {@link ContextMenu} will be * smartly repositioned, most probably to be on the opposite side of the * MenuButton. * *

Example:

*
 * MenuButton m = new MenuButton("Eats");
 * m.getItems().addAll(new MenuItem("Burger"), new MenuItem("Hot Dog"));
 * 
* *

* MnemonicParsing is enabled by default for MenuButton. *

* * @see MenuItem * @see Menu * @see SplitMenuButton * @since JavaFX 2.0 */ public class MenuButton extends ButtonBase { /*************************************************************************** * * * Static properties and methods * * * **************************************************************************/ /** * Called prior to the MenuButton showing its popup after the user * has clicked or otherwise interacted with the MenuButton. * @since JavaFX 8u60 */ public static final EventType ON_SHOWING = new EventType(Event.ANY, "MENU_BUTTON_ON_SHOWING"); /** * Called after the MenuButton has shown its popup. * @since JavaFX 8u60 */ public static final EventType ON_SHOWN = new EventType(Event.ANY, "MENU_BUTTON_ON_SHOWN"); /** * Called when the MenuButton popup will be hidden. * @since JavaFX 8u60 */ public static final EventType ON_HIDING = new EventType(Event.ANY, "MENU_BUTTON_ON_HIDING"); /** * Called when the MenuButton popup has been hidden. * @since JavaFX 8u60 */ public static final EventType ON_HIDDEN = new EventType(Event.ANY, "MENU_BUTTON_ON_HIDDEN"); /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Creates a new empty menu button. Use {@link #setText(String)}, * {@link #setGraphic(Node)} and {@link #getItems()} to set the content. */ public MenuButton() { this(null, null); } /** * Creates a new empty menu button with the given text to display on the * button. Use {@link #setGraphic(Node)} and {@link #getItems()} to set the * content. * * @param text the text to display on the menu button */ public MenuButton(String text) { this(text, null); } /** * Creates a new empty menu button with the given text and graphic to * display on the button. Use {@link #getItems()} to set the content. * * @param text the text to display on the menu button * @param graphic the graphic to display on the menu button */ public MenuButton(String text, Node graphic) { this(text, graphic, (MenuItem[])null); } /** * Creates a new menu button with the given text and graphic to * display on the button, 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 MenuButton(String text, Node graphic, MenuItem... items) { if (text != null) { setText(text); } if (graphic != null) { setGraphic(graphic); } if (items != null) { getItems().addAll(items); } getStyleClass().setAll(DEFAULT_STYLE_CLASS); setAccessibleRole(AccessibleRole.MENU_BUTTON); setMnemonicParsing(true); // enable mnemonic auto-parsing by default // the default value for popupSide = Side.BOTTOM therefor // PSEUDO_CLASS_OPENVERTICALLY should be set from the start. pseudoClassStateChanged(PSEUDO_CLASS_OPENVERTICALLY, true); } /*************************************************************************** * * * Properties * * * **************************************************************************/ private final ObservableList items = FXCollections.observableArrayList(); /** * The items to show within this buttons menu. If this ObservableList is modified * at runtime, the Menu will update as expected. *

* Commonly used controls include including {@code MenuItem}, * {@code CheckMenuItem}, {@code RadioMenuItem}, * and of course {@code Menu}, which if added to a menu, will become a sub * menu. {@link SeparatorMenuItem} is another commonly used Node in the Menu's items * ObservableList. * @return the list of menu items within this buttons menu */ public final ObservableList getItems() { return items; } // --- Showing /** * Indicates whether the {@link ContextMenu} is currently visible. */ private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper(this, "showing", false) { @Override protected void invalidated() { pseudoClassStateChanged(PSEUDO_CLASS_SHOWING, get()); super.invalidated(); } }; private void setShowing(boolean value) { // these events will not fire if the showing property is bound Event.fireEvent(this, value ? new Event(ON_SHOWING) : new Event(ON_HIDING)); showing.set(value); Event.fireEvent(this, value ? new Event(ON_SHOWN) : new Event(ON_HIDDEN)); } public final boolean isShowing() { return showing.get(); } public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); } /** * Indicates on which side the {@link ContextMenu} should open in * relation to the MenuButton. Menu items are generally laid * out vertically in either case. * For example, if the menu button were in a vertical toolbar on the left * edge of the application, you might change {@link #popupSideProperty() popupSide} * to {@code Side.RIGHT} so that the popup will appear to the right of the MenuButton. * * @defaultValue {@code Side.BOTTOM} */ // TODO expose via CSS private ObjectProperty popupSide; public final void setPopupSide(Side value) { popupSideProperty().set(value); } public final Side getPopupSide() { return popupSide == null ? Side.BOTTOM : popupSide.get(); } public final ObjectProperty popupSideProperty() { if (popupSide == null) { popupSide = new ObjectPropertyBase(Side.BOTTOM) { @Override protected void invalidated() { final Side side = get(); final boolean active = (side == Side.TOP) || (side == Side.BOTTOM); pseudoClassStateChanged(PSEUDO_CLASS_OPENVERTICALLY, active); } @Override public Object getBean() { return MenuButton.this; } @Override public String getName() { return "popupSide"; } }; } return popupSide; } /** * Called just prior to the {@code ContextMenu} being shown. * @return the on showing property * @since 10 */ 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() { setEventHandler(ON_SHOWING, get()); } @Override public Object getBean() { return MenuButton.this; } @Override public String getName() { return "onShowing"; } }; /** * Called just after the {@code ContextMenu} is shown. * @return the on shown property * @since 10 */ 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() { setEventHandler(ON_SHOWN, get()); } @Override public Object getBean() { return MenuButton.this; } @Override public String getName() { return "onShown"; } }; /** * Called just prior to the {@code ContextMenu} being hidden. * @return the on hiding property * @since 10 */ 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() { setEventHandler(ON_HIDING, get()); } @Override public Object getBean() { return MenuButton.this; } @Override public String getName() { return "onHiding"; } }; /** * Called just after the {@code ContextMenu} has been hidden. * @return the on hidden property * @since 10 */ 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() { setEventHandler(ON_HIDDEN, get()); } @Override public Object getBean() { return MenuButton.this; } @Override public String getName() { return "onHidden"; } }; /*************************************************************************** * * * Control methods * * * **************************************************************************/ /** * Shows the {@link ContextMenu}, assuming this MenuButton is not disabled. * * @see #isDisabled() * @see #isShowing() */ public void show() { // TODO: isBound check is probably unnecessary here if (!isDisabled() && !showing.isBound()) { setShowing(true); } } /** * Hides the {@link ContextMenu}. * * @see #isShowing() */ public void hide() { // TODO: isBound check is probably unnecessary here if (!showing.isBound()) { setShowing(false); } } /** * This has no impact. */ @Override public void fire() { if (!isDisabled()) { fireEvent(new ActionEvent()); } } /** {@inheritDoc} */ @Override protected Skin createDefaultSkin() { return new MenuButtonSkin(this); } /*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/ private static final String DEFAULT_STYLE_CLASS = "menu-button"; private static final PseudoClass PSEUDO_CLASS_OPENVERTICALLY = PseudoClass.getPseudoClass("openvertically"); private static final PseudoClass PSEUDO_CLASS_SHOWING = PseudoClass.getPseudoClass("showing"); /*************************************************************************** * * * Accessibility handling * * * **************************************************************************/ /** {@inheritDoc} */ @Override public void executeAccessibleAction(AccessibleAction action, Object... parameters) { switch (action) { case FIRE: if (isShowing()) { hide(); } else { show(); } break; default: super.executeAccessibleAction(action); } } }