Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.control;
027
028import javafx.css.PseudoClass;
029import javafx.beans.property.ObjectProperty;
030import javafx.beans.property.ObjectPropertyBase;
031import javafx.collections.FXCollections;
032import javafx.collections.ObservableList;
033import javafx.event.ActionEvent;
034import javafx.geometry.Side;
035import javafx.scene.Node;
036import com.sun.javafx.scene.control.skin.MenuButtonSkin;
037import javafx.beans.property.ReadOnlyBooleanProperty;
038import javafx.beans.property.ReadOnlyBooleanWrapper;
039
040/**
041 * MenuButton is a button which, when clicked or pressed, will show a
042 * {@link ContextMenu}. A MenuButton shares a very similar API to the {@link Menu}
043 * control, insofar that you set the items that should be shown in the
044 * {@link #items} ObservableList, and there is a {@link #text} property to specify the
045 * label shown within the MenuButton.
046 * <p>
047 * As mentioned, like the Menu API itself, you'll find an {@link #items} ObservableList
048 * within which you can provide any type of Node, although it is recommended to
049 * only insert controls that extend from {@link MenuItem}. There are several useful subclasses
050 * of {@link MenuItem} including {@link RadioMenuItem}, {@link CheckMenuItem}, 
051 * {@link Menu}, {@link Separator} and {@link CustomMenuItem}.
052 * <p>
053 * A MenuButton can be set to show its menu on any side of the button. This is
054 * specified using the {@link #popupSideProperty() popupSide} property. By default
055 * the menu appears below the button. However, regardless of the popupSide specified,
056 * if there is not enough room, the {@link ContextMenu} will be
057 * smartly repositioned, most probably to be on the opposite side of the 
058 * MenuButton.
059 *
060 * <p>Example:</p>
061 * <pre>
062 * MenuButton m = new MenuButton("Eats");
063 * m.getItems().addAll(new MenuItem("Burger"), new MenuItem("Hot Dog"));
064 * </pre>
065 * 
066 * <p>
067 * MnemonicParsing is enabled by default for MenuButton.
068 * </p>
069 *
070 * @see MenuItem
071 * @see Menu
072 * @see SplitMenuButton
073 */
074public class MenuButton extends ButtonBase {
075
076    /***************************************************************************
077     *                                                                         *
078     * Constructors                                                            *
079     *                                                                         *
080     **************************************************************************/
081
082    /**
083     * Creates a new empty menu button. Use {@link #setText(String)},
084     * {@link #setGraphic(Node)} and {@link #getItems()} to set the content.
085     */
086    public MenuButton() {
087        this(null, null);
088    }
089
090    /**
091     * Creates a new empty menu button with the given text to display on the
092     * menu. Use {@link #setGraphic(Node)} and {@link #getItems()} to set the
093     * content.
094     * 
095     * @param text the text to display on the menu button
096     */
097    public MenuButton(String text) {
098        this(text, null);
099    }
100
101    /**
102     * Creates a new empty menu button with the given text and graphic to
103     * display on the menu. Use {@link #getItems()} to set the content.
104     * 
105     * @param text the text to display on the menu button
106     * @param graphic the graphic to display on the menu button
107     */
108    public MenuButton(String text, Node graphic) {
109        if (text != null) {
110            setText(text);
111        }
112        if (graphic != null) {
113            setGraphic(graphic);
114        }
115        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
116        setMnemonicParsing(true);     // enable mnemonic auto-parsing by default
117        // the default value for popupSide = Side.BOTTOM therefor
118        // PSEUDO_CLASS_OPENVERTICALLY should be set from the start.
119        pseudoClassStateChanged(PSEUDO_CLASS_OPENVERTICALLY, true);
120    }
121
122    /***************************************************************************
123     *                                                                         *
124     * Properties                                                              *
125     *                                                                         *
126     **************************************************************************/
127    private final ObservableList<MenuItem> items = FXCollections.<MenuItem>observableArrayList();
128
129    /**
130     * The items to show within this buttons menu. If this ObservableList is modified
131     * at runtime, the Menu will update as expected.
132     * <p>
133     * Commonly used controls include including {@code MenuItem}, 
134     * {@code CheckMenuItem}, {@code RadioMenuItem},
135     * and of course {@code Menu}, which if added to a menu, will become a sub
136     * menu. {@link SeparatorMenuItem} is another commonly used Node in the Menu's items
137     * ObservableList.
138     */
139    public final ObservableList<MenuItem> getItems() {
140        return items;
141    }
142
143    // --- Showing
144    private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper(this, "showing", false) {
145        @Override protected void invalidated() {
146            pseudoClassStateChanged(PSEUDO_CLASS_SHOWING, get());
147            super.invalidated();
148        }
149    };
150    private void setShowing(boolean value) { showing.set(value); }
151    /**
152     * Indicates whether the {@link ContextMenu} is currently visible.
153     */
154    public final boolean isShowing() { return showing.get(); }    
155    public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); }
156    
157    
158
159    /**
160     * Indicates on which side the {@link ContextMenu} should open in
161     * relation to the MenuButton. Menu items are generally laid
162     * out vertically in either case.
163     * For example, if the menu button were in a vertical toolbar on the left
164     * edge of the application, you might change {@link #popupSide} to {@code Side.RIGHT} so that
165     * the popup will appear to the right of the MenuButton.
166     *
167     * @defaultValue {@code Side.BOTTOM}
168     */
169    // TODO expose via CSS
170    private ObjectProperty<Side> popupSide;
171
172    public final void setPopupSide(Side value) {
173        popupSideProperty().set(value);
174    }
175
176    public final Side getPopupSide() {
177        return popupSide == null ? Side.BOTTOM : popupSide.get();
178    }
179
180    public final ObjectProperty<Side> popupSideProperty() {
181        if (popupSide == null) {
182            popupSide = new ObjectPropertyBase<Side>(Side.BOTTOM) {
183                @Override protected void invalidated() {
184                    final Side side = get();
185                    final boolean active = (side == Side.TOP) || (side == Side.BOTTOM);
186                    pseudoClassStateChanged(PSEUDO_CLASS_OPENVERTICALLY, active);
187                }
188
189                @Override
190                public Object getBean() {
191                    return MenuButton.this;
192                }
193
194                @Override
195                public String getName() {
196                    return "popupSide";
197                }
198            };
199        }
200        return popupSide;
201    }
202
203    /***************************************************************************
204     *                                                                         *
205     * Control methods                                                         *
206     *                                                                         *
207     **************************************************************************/
208
209    /**
210     * Shows the {@link ContextMenu}, assuming this MenuButton is not disabled.
211     * 
212     * @see #isDisabled()
213     * @see #isShowing()
214     */
215    public void show() {
216        // TODO: isBound check is probably unnecessary here
217        if (!isDisabled() && !showing.isBound()) {
218            setShowing(true);
219        }
220    }
221
222    /**
223     * Hides the {@link ContextMenu}.
224     * 
225     * @see #isShowing()
226     */
227    public void hide() {
228        // TODO: isBound check is probably unnecessary here
229        if (!showing.isBound()) {
230            setShowing(false);
231        }
232    }
233
234    /**
235     * This has no impact.
236     */
237    @Override
238    public void fire() {
239        fireEvent(new ActionEvent());
240    }
241
242    /** {@inheritDoc} */
243    @Override protected Skin<?> createDefaultSkin() {
244        return new MenuButtonSkin(this);
245    }
246
247    /***************************************************************************
248     *                                                                         *
249     * Stylesheet Handling                                                     *
250     *                                                                         *
251     **************************************************************************/
252
253    private static final String DEFAULT_STYLE_CLASS = "menu-button";
254    private static final PseudoClass PSEUDO_CLASS_OPENVERTICALLY = 
255            PseudoClass.getPseudoClass("openvertically");
256    private static final PseudoClass PSEUDO_CLASS_SHOWING = 
257            PseudoClass.getPseudoClass("showing");
258
259}