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.Event; 033import javafx.event.EventHandler; 034import javafx.event.EventType; 035import javafx.scene.Node; 036 037import com.sun.javafx.collections.TrackableObservableList; 038import com.sun.javafx.scene.control.Logging; 039import javafx.beans.DefaultProperty; 040import javafx.beans.property.ReadOnlyBooleanProperty; 041import javafx.beans.property.ReadOnlyBooleanWrapper; 042import javafx.event.EventDispatchChain; 043 044/** 045 * <p> 046 * A popup menu of actionable items which is displayed to the user only upon request. 047 * When a menu is visible, in most use cases, the user can select one menu item 048 * before the menu goes back to its hidden state. This means the menu is a good 049 * place to put important functionality that does not necessarily need to be 050 * visible at all times to the user. 051 * <p> 052 * Menus are typically placed in a {@link MenuBar}, or as a submenu of another Menu. 053 * If the intention is to offer a context menu when the user right-clicks in a 054 * certain area of their user interface, then this is the wrong control to use. 055 * This is because when Menu is added to the scenegraph, it has a visual 056 * representation that will result in it appearing on screen. Instead, 057 * {@link ContextMenu} should be used in this circumstance. 058 * <p> 059 * Creating a Menu and inserting it into a MenuBar is easy, as shown below: 060 * <pre><code> 061 * final Menu menu1 = new Menu("File"); 062 * MenuBar menuBar = new MenuBar(); 063 * menuBar.getMenus().add(menu1); 064 * </code></pre> 065 * <p> 066 * A Menu is a subclass of {@link MenuItem} which means that it can be inserted 067 * into a Menu's {@link #items} ObservableList, resulting in a submenu being created: 068 * <pre><code> 069 * MenuItem menu12 = new MenuItem("Open"); 070 * menu1.getItems().add(menu12); 071 * </code></pre> 072 * <p> 073 * The items ObservableList allows for any {@link MenuItem} type to be inserted, 074 * including its subclasses {@link Menu}, {@link MenuItem}, {@link RadioMenuItem}, {@link CheckMenuItem}, 075 * {@link CustomMenuItem} and {@link SeparatorMenuItem}. In order to insert an arbitrary {@link Node} to 076 * a Menu, a CustomMenuItem can be used. One exception to this general rule is that 077 * {@link SeparatorMenuItem} could be used for inserting a separator. 078 * 079 * @see MenuBar 080 * @see MenuItem 081 */ 082@DefaultProperty("items") 083public class Menu extends MenuItem { 084 085 /** 086 * <p>Called when the contentMenu for this menu <b>will</b> be shown. However if the 087 * contextMenu is empty then this will not be called. 088 * </p> 089 */ 090 public static final EventType<Event> ON_SHOWING = 091 new EventType<Event>(Event.ANY, "MENU_ON_SHOWING"); 092 093 /** 094 * <p>Called when the contentMenu for this menu shows. However if the 095 * contextMenu is empty then this will not be called. 096 * </p> 097 */ 098 public static final EventType<Event> ON_SHOWN = 099 new EventType<Event>(Event.ANY, "MENU_ON_SHOWN"); 100 101 /** 102 * <p>Called when the contentMenu for this menu <b>will</b> be hidden. However if the 103 * contextMenu is empty then this will not be called. 104 * </p> 105 */ 106 public static final EventType<Event> ON_HIDING = 107 new EventType<Event>(Event.ANY, "MENU_ON_HIDING"); 108 109 /** 110 * <p>Called when the contentMenu for this menu is hidden. However if the 111 * contextMenu is empty then this will not be called. 112 * </p> 113 */ 114 public static final EventType<Event> ON_HIDDEN = 115 new EventType<Event>(Event.ANY, "MENU_ON_HIDDEN"); 116 117 /*************************************************************************** 118 * * 119 * Constructors * 120 * * 121 **************************************************************************/ 122 123 /** 124 * Constructs a Menu with an empty string for its display text. 125 * @since 2.2 126 */ 127 public Menu() { 128 this(""); 129 } 130 131 /** 132 * Constructs a Menu and sets the display text with the specified text. 133 */ 134 public Menu(String text) { 135 this(text,null); 136 } 137 138 /** 139 * Constructs a Menu and sets the display text with the specified text 140 * and sets the graphic {@link Node} to the given node. 141 */ 142 public Menu(String text, Node graphic) { 143 super(text,graphic); 144 getStyleClass().add(DEFAULT_STYLE_CLASS); 145 } 146 147 148 149 /*************************************************************************** 150 * * 151 * Properties * 152 * * 153 **************************************************************************/ 154 155 /** 156 * Indicates whether the {@link ContextMenu} is currently visible. 157 * 158 * @defaultValue false 159 */ 160 private ReadOnlyBooleanWrapper showing; 161 162 private void setShowing(boolean value) { 163 if (getItems().size() == 0 || (value && isShowing())) return; 164 165 // these events will not fire if the showing property is bound 166 if (value) { 167 if (getOnMenuValidation() != null) { 168 Event.fireEvent(this, new Event(MENU_VALIDATION_EVENT)); 169 for(MenuItem m : getItems()) { 170 if (!(m instanceof Menu) && m.getOnMenuValidation() != null) { 171 Event.fireEvent(m, new Event(MenuItem.MENU_VALIDATION_EVENT)); 172 } 173 } 174 } 175 Event.fireEvent(this, new Event(Menu.ON_SHOWING)); 176 } else { 177 Event.fireEvent(this, new Event(Menu.ON_HIDING)); 178 } 179 showingPropertyImpl().set(value); 180 Event.fireEvent(this, (value) ? new Event(Menu.ON_SHOWN) : 181 new Event(Menu.ON_HIDDEN)); 182 } 183 184 public final boolean isShowing() { 185 return showing == null ? false : showing.get(); 186 } 187 188 public final ReadOnlyBooleanProperty showingProperty() { 189 return showingPropertyImpl().getReadOnlyProperty(); 190 } 191 192 private ReadOnlyBooleanWrapper showingPropertyImpl() { 193 if (showing == null) { 194 showing = new ReadOnlyBooleanWrapper() { 195 @Override protected void invalidated() { 196 // force validation 197 get(); 198 199 // update the styleclass 200 if (isShowing()) { 201 getStyleClass().add(STYLE_CLASS_SHOWING); 202 } else { 203 getStyleClass().remove(STYLE_CLASS_SHOWING); 204 } 205 } 206 207 @Override 208 public Object getBean() { 209 return Menu.this; 210 } 211 212 @Override 213 public String getName() { 214 return "showing"; 215 } 216 }; 217 } 218 return showing; 219 } 220 221 222 // --- On Showing 223 public final ObjectProperty<EventHandler<Event>> onShowingProperty() { return onShowing; } 224 /** 225 * Called just prior to the {@code ContextMenu} being shown, even if the menu has 226 * no items to show. Note however that this won't be called if the menu does 227 * not have a valid anchor node. 228 */ 229 public final void setOnShowing(EventHandler<Event> value) { onShowingProperty().set(value); } 230 public final EventHandler<Event> getOnShowing() { return onShowingProperty().get(); } 231 private ObjectProperty<EventHandler<Event>> onShowing = new ObjectPropertyBase<EventHandler<Event>>() { 232 @Override protected void invalidated() { 233 eventHandlerManager.setEventHandler(ON_SHOWING, get()); 234 } 235 236 @Override 237 public Object getBean() { 238 return Menu.this; 239 } 240 241 @Override 242 public String getName() { 243 return "onShowing"; 244 } 245 }; 246 247 248 // -- On Shown 249 public final ObjectProperty<EventHandler<Event>> onShownProperty() { return onShown; } 250 /** 251 * Called just after the {@link ContextMenu} is shown. 252 */ 253 public final void setOnShown(EventHandler<Event> value) { onShownProperty().set(value); } 254 public final EventHandler<Event> getOnShown() { return onShownProperty().get(); } 255 private ObjectProperty<EventHandler<Event>> onShown = new ObjectPropertyBase<EventHandler<Event>>() { 256 @Override protected void invalidated() { 257 eventHandlerManager.setEventHandler(ON_SHOWN, get()); 258 } 259 260 @Override 261 public Object getBean() { 262 return Menu.this; 263 } 264 265 @Override 266 public String getName() { 267 return "onShown"; 268 } 269 }; 270 271 272 // --- On Hiding 273 public final ObjectProperty<EventHandler<Event>> onHidingProperty() { return onHiding; } 274 /** 275 * Called just prior to the {@link ContextMenu} being hidden. 276 */ 277 public final void setOnHiding(EventHandler<Event> value) { onHidingProperty().set(value); } 278 public final EventHandler<Event> getOnHiding() { return onHidingProperty().get(); } 279 private ObjectProperty<EventHandler<Event>> onHiding = new ObjectPropertyBase<EventHandler<Event>>() { 280 @Override protected void invalidated() { 281 eventHandlerManager.setEventHandler(ON_HIDING, get()); 282 } 283 284 @Override 285 public Object getBean() { 286 return Menu.this; 287 } 288 289 @Override 290 public String getName() { 291 return "onHiding"; 292 } 293 }; 294 295 296 // --- On Hidden 297 public final ObjectProperty<EventHandler<Event>> onHiddenProperty() { return onHidden; } 298 /** 299 * Called just after the {@link ContextMenu} has been hidden. 300 */ 301 public final void setOnHidden(EventHandler<Event> value) { onHiddenProperty().set(value); } 302 public final EventHandler<Event> getOnHidden() { return onHiddenProperty().get(); } 303 private ObjectProperty<EventHandler<Event>> onHidden = new ObjectPropertyBase<EventHandler<Event>>() { 304 @Override protected void invalidated() { 305 eventHandlerManager.setEventHandler(ON_HIDDEN, get()); 306 } 307 308 @Override 309 public Object getBean() { 310 return Menu.this; 311 } 312 313 @Override 314 public String getName() { 315 return "onHidden"; 316 } 317 }; 318 319 320 321 /*************************************************************************** 322 * * 323 * Instance variables * 324 * * 325 **************************************************************************/ 326 327 private final ObservableList<MenuItem> items = new TrackableObservableList<MenuItem>() { 328 @Override protected void onChanged(Change<MenuItem> c) { 329 while (c.next()) { 330 // remove the parent menu from all menu items that have been removed 331 for (MenuItem item : c.getRemoved()) { 332 item.setParentMenu(null); 333 } 334 335 // set the parent menu to be this menu for all added menu items 336 for (MenuItem item : c.getAddedSubList()) { 337 if (item.getParentMenu() != null) { 338 Logging.getControlsLogger().warning("Adding MenuItem " + 339 item.getText() + " that has already been added to " 340 + item.getParentMenu().getText()); 341 item.getParentMenu().getItems().remove(item); 342 } 343 344 item.setParentMenu(Menu.this); 345 } 346 } 347 if (getItems().size() == 0 && isShowing()) { 348 showingPropertyImpl().set(false); 349 } 350 } 351 }; 352 353 354 355 /*************************************************************************** 356 * * 357 * Public API * 358 * * 359 **************************************************************************/ 360 361 /** 362 * The items to show within this menu. If this ObservableList is modified at 363 * runtime, the Menu will update as expected. 364 */ 365 public final ObservableList<MenuItem> getItems() { 366 return items; 367 } 368 369 /** 370 * If the Menu is not disabled and the {@link ContextMenu} is not already showing, 371 * then this will cause the {@link ContextMenu} to be shown. 372 */ 373 public void show() { 374 if (isDisable()) return; 375 setShowing(true); 376 } 377 378 /** 379 * Hides the {@link ContextMenu} if it was previously showing, and any showing 380 * submenus. If this menu is not showing, then invoking this function 381 * has no effect. 382 */ 383 public void hide() { 384 if (!isShowing()) return; 385 // hide all sub menus 386 for (MenuItem i : getItems()) { 387 if (i instanceof Menu) { 388 final Menu m = (Menu) i; 389 m.hide(); 390 } 391 } 392 setShowing(false); 393 } 394 395 @Override public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { 396 eventHandlerManager.addEventHandler(eventType, eventHandler); 397 } 398 399 @Override public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { 400 eventHandlerManager.removeEventHandler(eventType, eventHandler); 401 } 402 403 /** {@inheritDoc} */ 404 @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { 405 return tail.prepend(eventHandlerManager); 406 } 407 408 /*************************************************************************** 409 * * 410 * Stylesheet Handling * 411 * * 412 **************************************************************************/ 413 414 private static final String DEFAULT_STYLE_CLASS = "menu"; 415 private static final String STYLE_CLASS_SHOWING = "showing"; 416}