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.beans.property.ObjectProperty;
029import javafx.beans.property.ObjectPropertyBase;
030import javafx.collections.ListChangeListener.Change;
031import javafx.collections.ObservableList;
032import javafx.event.ActionEvent;
033import javafx.event.Event;
034import javafx.event.EventHandler;
035import javafx.geometry.HPos;
036import javafx.geometry.Point2D;
037import javafx.geometry.Side;
038import javafx.geometry.VPos;
039import javafx.scene.Node;
040import javafx.scene.Scene;
041import javafx.stage.Window;
042
043import com.sun.javafx.Utils;
044import com.sun.javafx.collections.TrackableObservableList;
045import com.sun.javafx.scene.control.skin.ContextMenuSkin;
046import javafx.beans.property.BooleanProperty;
047import javafx.beans.property.SimpleBooleanProperty;
048
049/**
050 * <p>
051 * A popup control containing an ObservableList of menu items. The {@link #items}
052 * ObservableList allows for any {@link MenuItem} type to be inserted,
053 * including its subclasses {@link Menu}, {@link MenuItem}, {@link RadioMenuItem}, {@link CheckMenuItem} and
054 * {@link CustomMenuItem}. If an arbitrary Node needs to be
055 * inserted into a menu, a CustomMenuItem can be used. One exception to this general rule is that
056 * {@link SeparatorMenuItem} could be used for inserting a separator.
057 * <p>
058 * A common use case for this class is creating and showing context menus to
059 * users. To create a context menu using ContextMenu you can do the
060 * following:
061<pre><code>
062final ContextMenu contextMenu = new ContextMenu();
063contextMenu.setOnShowing(new EventHandler&lt;WindowEvent&gt;() {
064    public void handle(WindowEvent e) {
065        System.out.println("showing");
066    }
067});
068contextMenu.setOnShown(new EventHandler&lt;WindowEvent&gt;() {
069    public void handle(WindowEvent e) {
070        System.out.println("shown");
071    }
072});
073
074MenuItem item1 = new MenuItem("About");
075item1.setOnAction(new EventHandler&lt;ActionEvent&gt;() {
076    public void handle(ActionEvent e) {
077        System.out.println("About");
078    }
079});
080MenuItem item2 = new MenuItem("Preferences");
081item2.setOnAction(new EventHandler&lt;ActionEvent&gt;() {
082    public void handle(ActionEvent e) {
083        System.out.println("Preferences");
084    }
085});
086contextMenu.getItems().addAll(item1, item2);
087
088final TextField textField = new TextField("Type Something");
089textField.setContextMenu(contextMenu);
090</code></pre>
091 *
092 * <p>{@link Control#setContextMenu(javafx.scene.control.ContextMenu) } convenience
093 * method can be used to set a context menu on on any control. The example above results in the
094 * context menu being displayed on the right {@link javafx.geometry.Side Side}
095 * of the TextField. Alternatively, an event handler can also be set on the control
096 * to invoke the context menu as shown below.
097 * <pre><code>
098textField.setOnAction(new EventHandler&lt;ActionEvent&gt;() {
099    public void handle(ActionEvent e) {
100        contextMenu.show(textField, Side.BOTTOM, 0, 0);
101    }
102});
103 
104Group root = (Group) scene.getRoot();
105root.getChildren().add(textField);
106</code></pre>
107 *
108 * <p>In this example, the context menu is shown when the user clicks on the
109 * {@link javafx.scene.control.Button Button} (of course, you should use the
110 * {@link MenuButton} control to do this rather than doing the above).</p>
111 *
112 * <p>Note that the show function used in the code sample
113 * above will result in the ContextMenu appearing directly beneath the
114 * TextField. You can vary the {@link javafx.geometry.Side Side}  to get the results you expect.</p>
115 *
116 * @see MenuItem
117 * @see Menu
118 */
119public class ContextMenu extends PopupControl {
120
121    /***************************************************************************
122     *                                                                         *
123     * Constructors                                                            *
124     *                                                                         *
125     **************************************************************************/
126
127    /**
128     * Create a new ContextMenu
129     */
130    public ContextMenu() {
131        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
132        setAutoHide(true);
133    }
134
135    /**
136     * Create a new ContextMenu initialized with the given items
137     */
138    public ContextMenu(MenuItem... items) {
139        this();
140        this.items.addAll(items);
141    }
142
143    /***************************************************************************
144     *                                                                         *
145     * Properties                                                              *
146     *                                                                         *
147     **************************************************************************/
148
149    /**
150     * Callback function to be informed when an item contained within this
151     * {@code ContextMenu} has been activated. The current implementation informs
152     * all parent menus as well, so that it is not necessary to listen to all
153     * sub menus for events.
154     */
155    private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
156        @Override protected void invalidated() {
157            setEventHandler(ActionEvent.ACTION, get());
158       }
159
160        @Override
161        public Object getBean() {
162            return ContextMenu.this;
163        }
164
165        @Override
166        public String getName() {
167            return "onAction";
168        }
169    };
170    public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
171    public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
172    public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
173
174    private final ObservableList<MenuItem> items = new TrackableObservableList<MenuItem>() {
175        @Override protected void onChanged(Change<MenuItem> c) {
176            while (c.next()) {
177                for (MenuItem item : c.getRemoved()) {
178                    item.setParentPopup(null);
179                }
180                for (MenuItem item : c.getAddedSubList()) {
181                    item.setParentPopup(ContextMenu.this);
182                }
183            }
184        }
185    };
186    /**
187     * The menu items on the context menu. If this ObservableList is modified at
188     * runtime, the ContextMenu will update as expected.
189     * @see MenuItem
190     */
191    public final ObservableList<MenuItem> getItems() { return items; }
192
193     /**
194     * @treatAsPrivate implementation detail
195     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
196     */
197    @Deprecated
198    private final BooleanProperty impl_showRelativeToWindow = new SimpleBooleanProperty(false);
199     /**
200      * @treatAsPrivate implementation detail
201      */
202    public final boolean isImpl_showRelativeToWindow() { return impl_showRelativeToWindow.get(); }
203     /**
204      * @treatAsPrivate implementation detail
205      */
206    public final void setImpl_showRelativeToWindow(boolean value) { impl_showRelativeToWindow.set(value); }
207     /**
208      * @treatAsPrivate implementation detail
209      */
210    public final BooleanProperty impl_showRelativeToWindowProperty() { return impl_showRelativeToWindow; }
211    
212    /***************************************************************************
213     *                                                                         *
214     * Methods                                                                 *
215     *                                                                         *
216     **************************************************************************/
217
218    /**
219     * Shows the {@code ContextMenu} relative to the given anchor node, on the side
220     * specified by the {@code hpos} and {@code vpos} parameters, and offset
221     * by the given {@code dx} and {@code dy} values for the x-axis and y-axis, respectively.
222     * If there is not enough room, the menu is moved to the opposite side and
223     * the offset is not applied.
224     * <p>
225     * To clarify the purpose of the {@code hpos} and {@code vpos} parameters,
226     * consider that they are relative to the anchor node. As such, a {@code hpos}
227     * and {@code vpos} of {@code CENTER} would mean that the ContextMenu appears
228     * on top of the anchor, with the (0,0) position of the {@code ContextMenu}
229     * positioned at (0,0) of the anchor. A {@code hpos} of right would then shift
230     * the {@code ContextMenu} such that its top-left (0,0) position would be attached
231     * to the top-right position of the anchor.
232     * <p>
233     * This function is useful for finely tuning the position of a menu,
234     * relative to the parent node to ensure close alignment.
235     */
236    // TODO provide more detail
237     public void show(Node anchor, Side side, double dx, double dy) {
238        if (anchor == null) return;
239        Event.fireEvent(this, new Event(Menu.ON_SHOWING));
240        if (getItems().size() == 0) return;
241
242        getScene().setNodeOrientation(anchor.getEffectiveNodeOrientation());
243
244        // FIXME because Side is not yet in javafx.geometry, we have to convert
245        // to the old HPos/VPos API here, as Utils can not refer to Side in the
246        // charting API.
247        HPos hpos = side == Side.LEFT ? HPos.LEFT : side == Side.RIGHT ? HPos.RIGHT : HPos.CENTER;
248        VPos vpos = side == Side.TOP ? VPos.TOP : side == Side.BOTTOM ? VPos.BOTTOM : VPos.CENTER;
249
250        // translate from anchor/hpos/vpos/dx/dy into screenX/screenY
251        Point2D point = Utils.pointRelativeTo(anchor,
252                prefWidth(-1), prefHeight(-1),
253                hpos, vpos, dx, dy, true);
254        doShow(anchor, point.getX(), point.getY());
255    }
256
257     /**
258     * Shows the {@code ContextMenu} at the specified screen coordinates. If there
259     * is not enough room at the specified location to show the {@code ContextMenu}
260     * given its size requirements, the necessary adjustments are made to bring
261     * the {@code ContextMenu} back back on screen. This also means that the
262     * {@code ContextMenu} will not span multiple monitors.
263     */
264    public void show(Node anchor, double screenX, double screenY) {
265        if (anchor == null) return;
266        Event.fireEvent(this, new Event(Menu.ON_SHOWING));
267        if (getItems().size() == 0) return;
268
269        getScene().setNodeOrientation(anchor.getEffectiveNodeOrientation());
270        doShow(anchor, screenX, screenY);
271    }
272
273    private void doShow(Node anchor, double screenX, double screenY) {
274        if(isImpl_showRelativeToWindow()) {
275            final Scene scene = (anchor == null) ? null : anchor.getScene();
276            final Window win = (scene == null) ? null : scene.getWindow();
277            if (win == null) return;
278            super.show(win, screenX, screenY);
279        } else {
280            super.show(anchor, screenX, screenY);
281        }
282        Event.fireEvent(this, new Event(Menu.ON_SHOWN));
283    }
284
285    /**
286     * Hides this {@code ContextMenu} and any visible submenus, assuming that when this function
287     * is called that the {@code ContextMenu} was showing.
288     * <p>
289     * If this {@code ContextMenu} is not showing, then nothing happens.
290     */
291    @Override public void hide() {
292        if (!isShowing()) return;
293        Event.fireEvent(this, new Event(Menu.ON_HIDING));
294        super.hide();
295        Event.fireEvent(this, new Event(Menu.ON_HIDDEN));
296    }
297
298    /** {@inheritDoc} */
299    @Override protected Skin<?> createDefaultSkin() {
300        return new ContextMenuSkin(this);
301    }
302
303    /***************************************************************************
304     *                                                                         *
305     *                         Stylesheet Handling                             *
306     *                                                                         *
307     ***************************************************************************/
308
309    private static final String DEFAULT_STYLE_CLASS = "context-menu";
310}