1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import javafx.css.PseudoClass;
  29 import javafx.beans.property.ObjectProperty;
  30 import javafx.beans.property.ObjectPropertyBase;
  31 import javafx.collections.FXCollections;
  32 import javafx.collections.ObservableList;
  33 import javafx.event.ActionEvent;
  34 import javafx.event.Event;
  35 import javafx.event.EventType;
  36 import javafx.geometry.Side;
  37 import javafx.scene.AccessibleAction;
  38 import javafx.scene.AccessibleRole;
  39 import javafx.scene.Node;
  40 import com.sun.javafx.scene.control.skin.MenuButtonSkin;
  41 import javafx.beans.property.ReadOnlyBooleanProperty;
  42 import javafx.beans.property.ReadOnlyBooleanWrapper;
  43 
  44 /**
  45  * MenuButton is a button which, when clicked or pressed, will show a
  46  * {@link ContextMenu}. A MenuButton shares a very similar API to the {@link Menu}
  47  * control, insofar that you set the items that should be shown in the
  48  * {@link #items} ObservableList, and there is a {@link #text} property to specify the
  49  * label shown within the MenuButton.
  50  * <p>
  51  * As mentioned, like the Menu API itself, you'll find an {@link #items} ObservableList
  52  * within which you can provide anything that extends from {@link MenuItem}.
  53  * There are several useful subclasses of {@link MenuItem} including
  54  * {@link RadioMenuItem}, {@link CheckMenuItem}, {@link Menu},
  55  * {@link SeparatorMenuItem} and {@link CustomMenuItem}.
  56  * <p>
  57  * A MenuButton can be set to show its menu on any side of the button. This is
  58  * specified using the {@link #popupSideProperty() popupSide} property. By default
  59  * the menu appears below the button. However, regardless of the popupSide specified,
  60  * if there is not enough room, the {@link ContextMenu} will be
  61  * smartly repositioned, most probably to be on the opposite side of the 
  62  * MenuButton.
  63  *
  64  * <p>Example:</p>
  65  * <pre>
  66  * MenuButton m = new MenuButton("Eats");
  67  * m.getItems().addAll(new MenuItem("Burger"), new MenuItem("Hot Dog"));
  68  * </pre>
  69  * 
  70  * <p>
  71  * MnemonicParsing is enabled by default for MenuButton.
  72  * </p>
  73  *
  74  * @see MenuItem
  75  * @see Menu
  76  * @see SplitMenuButton
  77  * @since JavaFX 2.0
  78  */
  79 public class MenuButton extends ButtonBase {
  80 
  81 
  82     /***************************************************************************
  83      *                                                                         *
  84      * Static properties and methods                                           *
  85      *                                                                         *
  86      **************************************************************************/
  87 
  88     /**
  89      * Called prior to the MenuButton showing its popup after the user
  90      * has clicked or otherwise interacted with the MenuButton.
  91      * @since JavaFX 8u60
  92      */
  93     public static final EventType<Event> ON_SHOWING =
  94             new EventType<Event>(Event.ANY, "MENU_BUTTON_ON_SHOWING");
  95 
  96     /**
  97      * Called after the MenuButton has shown its popup.
  98      * @since JavaFX 8u60
  99      */
 100     public static final EventType<Event> ON_SHOWN =
 101             new EventType<Event>(Event.ANY, "MENU_BUTTON_ON_SHOWN");
 102 
 103     /**
 104      * Called when the MenuButton popup <b>will</b> be hidden.
 105      * @since JavaFX 8u60
 106      */
 107     public static final EventType<Event> ON_HIDING =
 108             new EventType<Event>(Event.ANY, "MENU_BUTTON_ON_HIDING");
 109 
 110     /**
 111      * Called when the MenuButton popup has been hidden.
 112      * @since JavaFX 8u60
 113      */
 114     public static final EventType<Event> ON_HIDDEN =
 115             new EventType<Event>(Event.ANY, "MENU_BUTTON_ON_HIDDEN");
 116 
 117 
 118     /***************************************************************************
 119      *                                                                         *
 120      * Constructors                                                            *
 121      *                                                                         *
 122      **************************************************************************/
 123 
 124     /**
 125      * Creates a new empty menu button. Use {@link #setText(String)},
 126      * {@link #setGraphic(Node)} and {@link #getItems()} to set the content.
 127      */
 128     public MenuButton() {
 129         this(null, null);
 130     }
 131 
 132     /**
 133      * Creates a new empty menu button with the given text to display on the
 134      * button. Use {@link #setGraphic(Node)} and {@link #getItems()} to set the
 135      * content.
 136      * 
 137      * @param text the text to display on the menu button
 138      */
 139     public MenuButton(String text) {
 140         this(text, null);
 141     }
 142 
 143     /**
 144      * Creates a new empty menu button with the given text and graphic to
 145      * display on the button. Use {@link #getItems()} to set the content.
 146      * 
 147      * @param text the text to display on the menu button
 148      * @param graphic the graphic to display on the menu button
 149      */
 150     public MenuButton(String text, Node graphic) {
 151         this(text, graphic, (MenuItem[])null);
 152     }
 153 
 154     /**
 155      * Creates a new menu button with the given text and graphic to
 156      * display on the button, and inserts the given items
 157      * into the {@link #getItems() items} list.
 158      *
 159      * @param text the text to display on the menu button
 160      * @param graphic the graphic to display on the menu button
 161      * @param items The items to display in the popup menu.
 162      * @since JavaFX 8u40
 163      */
 164     public MenuButton(String text, Node graphic, MenuItem... items) {
 165         if (text != null) {
 166             setText(text);
 167         }
 168         if (graphic != null) {
 169             setGraphic(graphic);
 170         }
 171         if (items != null) {
 172             getItems().addAll(items);
 173         }
 174 
 175         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
 176         setAccessibleRole(AccessibleRole.MENU_BUTTON);
 177         setMnemonicParsing(true);     // enable mnemonic auto-parsing by default
 178         // the default value for popupSide = Side.BOTTOM therefor
 179         // PSEUDO_CLASS_OPENVERTICALLY should be set from the start.
 180         pseudoClassStateChanged(PSEUDO_CLASS_OPENVERTICALLY, true);
 181     }
 182 
 183     /***************************************************************************
 184      *                                                                         *
 185      * Properties                                                              *
 186      *                                                                         *
 187      **************************************************************************/
 188     private final ObservableList<MenuItem> items = FXCollections.<MenuItem>observableArrayList();
 189 
 190     /**
 191      * The items to show within this buttons menu. If this ObservableList is modified
 192      * at runtime, the Menu will update as expected.
 193      * <p>
 194      * Commonly used controls include including {@code MenuItem}, 
 195      * {@code CheckMenuItem}, {@code RadioMenuItem},
 196      * and of course {@code Menu}, which if added to a menu, will become a sub
 197      * menu. {@link SeparatorMenuItem} is another commonly used Node in the Menu's items
 198      * ObservableList.
 199      */
 200     public final ObservableList<MenuItem> getItems() {
 201         return items;
 202     }
 203 
 204     // --- Showing
 205     /**
 206      * Indicates whether the {@link ContextMenu} is currently visible.
 207      */
 208     private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper(this, "showing", false) {
 209         @Override protected void invalidated() {
 210             pseudoClassStateChanged(PSEUDO_CLASS_SHOWING, get());
 211             super.invalidated();
 212         }
 213     };
 214     private void setShowing(boolean value) {
 215         // these events will not fire if the showing property is bound
 216         Event.fireEvent(this, value ? new Event(ComboBoxBase.ON_SHOWING) :
 217                 new Event(ComboBoxBase.ON_HIDING));
 218         showing.set(value);
 219         Event.fireEvent(this, value ? new Event(ComboBoxBase.ON_SHOWN) :
 220                 new Event(ComboBoxBase.ON_HIDDEN));
 221     }
 222     public final boolean isShowing() { return showing.get(); }
 223     public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); }
 224     
 225     
 226 
 227     /**
 228      * Indicates on which side the {@link ContextMenu} should open in
 229      * relation to the MenuButton. Menu items are generally laid
 230      * out vertically in either case.
 231      * For example, if the menu button were in a vertical toolbar on the left
 232      * edge of the application, you might change {@link #popupSide} to {@code Side.RIGHT} so that
 233      * the popup will appear to the right of the MenuButton.
 234      *
 235      * @defaultValue {@code Side.BOTTOM}
 236      */
 237     // TODO expose via CSS
 238     private ObjectProperty<Side> popupSide;
 239 
 240     public final void setPopupSide(Side value) {
 241         popupSideProperty().set(value);
 242     }
 243 
 244     public final Side getPopupSide() {
 245         return popupSide == null ? Side.BOTTOM : popupSide.get();
 246     }
 247 
 248     public final ObjectProperty<Side> popupSideProperty() {
 249         if (popupSide == null) {
 250             popupSide = new ObjectPropertyBase<Side>(Side.BOTTOM) {
 251                 @Override protected void invalidated() {
 252                     final Side side = get();
 253                     final boolean active = (side == Side.TOP) || (side == Side.BOTTOM);
 254                     pseudoClassStateChanged(PSEUDO_CLASS_OPENVERTICALLY, active);
 255                 }
 256 
 257                 @Override
 258                 public Object getBean() {
 259                     return MenuButton.this;
 260                 }
 261 
 262                 @Override
 263                 public String getName() {
 264                     return "popupSide";
 265                 }
 266             };
 267         }
 268         return popupSide;
 269     }
 270 
 271     /***************************************************************************
 272      *                                                                         *
 273      * Control methods                                                         *
 274      *                                                                         *
 275      **************************************************************************/
 276 
 277     /**
 278      * Shows the {@link ContextMenu}, assuming this MenuButton is not disabled.
 279      * 
 280      * @see #isDisabled()
 281      * @see #isShowing()
 282      */
 283     public void show() {
 284         // TODO: isBound check is probably unnecessary here
 285         if (!isDisabled() && !showing.isBound()) {
 286             setShowing(true);
 287         }
 288     }
 289 
 290     /**
 291      * Hides the {@link ContextMenu}.
 292      * 
 293      * @see #isShowing()
 294      */
 295     public void hide() {
 296         // TODO: isBound check is probably unnecessary here
 297         if (!showing.isBound()) {
 298             setShowing(false);
 299         }
 300     }
 301 
 302     /**
 303      * This has no impact.
 304      */
 305     @Override
 306     public void fire() {
 307         if (!isDisabled()) {
 308             fireEvent(new ActionEvent());
 309         }
 310     }
 311 
 312     /** {@inheritDoc} */
 313     @Override protected Skin<?> createDefaultSkin() {
 314         return new MenuButtonSkin(this);
 315     }
 316 
 317     /***************************************************************************
 318      *                                                                         *
 319      * Stylesheet Handling                                                     *
 320      *                                                                         *
 321      **************************************************************************/
 322 
 323     private static final String DEFAULT_STYLE_CLASS = "menu-button";
 324     private static final PseudoClass PSEUDO_CLASS_OPENVERTICALLY = 
 325             PseudoClass.getPseudoClass("openvertically");
 326     private static final PseudoClass PSEUDO_CLASS_SHOWING = 
 327             PseudoClass.getPseudoClass("showing");
 328 
 329     /***************************************************************************
 330      *                                                                         *
 331      * Accessibility handling                                                  *
 332      *                                                                         *
 333      **************************************************************************/
 334 
 335     @Override
 336     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 337         switch (action) {
 338             case FIRE:
 339                 if (isShowing()) {
 340                     hide();
 341                 } else {
 342                     show();
 343                 }
 344                 break;
 345             default: super.executeAccessibleAction(action);
 346         }
 347     }
 348 }