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<WindowEvent>() { 064 public void handle(WindowEvent e) { 065 System.out.println("showing"); 066 } 067}); 068contextMenu.setOnShown(new EventHandler<WindowEvent>() { 069 public void handle(WindowEvent e) { 070 System.out.println("shown"); 071 } 072}); 073 074MenuItem item1 = new MenuItem("About"); 075item1.setOnAction(new EventHandler<ActionEvent>() { 076 public void handle(ActionEvent e) { 077 System.out.println("About"); 078 } 079}); 080MenuItem item2 = new MenuItem("Preferences"); 081item2.setOnAction(new EventHandler<ActionEvent>() { 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<ActionEvent>() { 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}