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 com.sun.javafx.beans.IDProperty;
029import javafx.collections.ObservableSet;
030import javafx.css.PseudoClass;
031import javafx.css.Styleable;
032import javafx.css.CssMetaData;
033import javafx.beans.property.BooleanProperty;
034import javafx.beans.property.ObjectProperty;
035import javafx.beans.property.ObjectPropertyBase;
036import javafx.beans.property.SimpleBooleanProperty;
037import javafx.beans.property.SimpleObjectProperty;
038import javafx.beans.property.SimpleStringProperty;
039import javafx.beans.property.StringProperty;
040import javafx.collections.FXCollections;
041import javafx.collections.ObservableList;
042import javafx.event.ActionEvent;
043import javafx.event.Event;
044import javafx.event.EventDispatchChain;
045import javafx.event.EventHandler;
046import javafx.event.EventTarget;
047import javafx.event.EventType;
048import javafx.scene.Node;
049import javafx.scene.input.KeyCombination;
050
051import com.sun.javafx.event.EventHandlerManager;
052import com.sun.javafx.scene.control.skin.ContextMenuContent;
053import com.sun.javafx.scene.control.skin.ContextMenuSkin;
054import java.util.Collections;
055import java.util.HashMap;
056import java.util.List;
057
058import javafx.beans.property.ReadOnlyObjectProperty;
059import javafx.beans.property.ReadOnlyObjectWrapper;
060import javafx.collections.ObservableMap;
061import javafx.scene.Parent;
062
063/**
064 * <p>
065 * MenuItem is intended to be used in conjunction with {@link Menu} to provide
066 * options to users. MenuItem serves as the base class for the bulk of JavaFX menus
067 * API.
068 * It has a display {@link #getText() text} property, as well as an optional {@link #getGraphic() graphic} node
069 * that can be set on it. 
070 * The {@link #getAccelerator() accelerator} property enables accessing the
071 * associated action in one keystroke. Also, as with the {@link Button} control,
072 * by using the {@link #setOnAction} method, you can have an instance of MenuItem
073 * perform any action you wish.
074 * <p>
075 * <b>Note:</b> Whilst any size of graphic can be inserted into a MenuItem, the most
076 * commonly used size in most applications is 16x16 pixels. This is
077 * the recommended graphic dimension to use if you're using the default style provided by
078 * JavaFX.
079 * <p>
080 * To create a MenuItem is simple:
081<pre><code>
082MenuItem menuItem = new MenuItem("Open");
083menuItem.setOnAction(new EventHandler&lt;ActionEvent&gt;() {
084    &#064;Override public void handle(ActionEvent e) {
085        System.out.println("Opening Database Connection...");
086    }
087});
088menuItem.setGraphic(new ImageView(new Image("flower.png")));
089</code></pre>
090 * <p>
091 * Refer to the {@link Menu} page to learn how to insert MenuItem into a menu
092 * instance. Briefly however, you can insert the MenuItem from the previous
093 * example into a Menu as such:
094<pre><code>
095final Menu menu = new Menu("File");
096menu.getItems().add(menuItem);
097</code></pre>
098 *
099 * @see Menu
100 */
101@IDProperty("id")
102public class MenuItem implements EventTarget, Styleable {
103
104    /***************************************************************************
105     *                                                                         *
106     * Constructors                                                            *
107     *                                                                         *
108     **************************************************************************/
109
110    /**
111     * Constructs a MenuItem with no display text.
112     */
113    public MenuItem() {
114        this(null,null);
115    }
116
117    /**
118     * Constructs a MenuItem and sets the display text with the specified text
119     * @see #setText
120     */
121    public MenuItem(String text) {
122        this(text,null);
123    }
124
125    /**
126     * Constructor s MenuItem and sets the display text with the specified text
127     * and sets the graphic {@link Node} to the given node.
128     * @see #setText
129     * @see #setGraphic
130     */
131    public MenuItem(String text, Node graphic) {
132        setText(text);
133        setGraphic(graphic);
134        styleClass.add(DEFAULT_STYLE_CLASS);
135    }
136    
137
138    
139    /***************************************************************************
140     *                                                                         *
141     * Instance Variables                                                      *
142     *                                                                         *
143     **************************************************************************/    
144    
145    private final ObservableList<String> styleClass = FXCollections.observableArrayList();
146    
147    final EventHandlerManager eventHandlerManager =
148            new EventHandlerManager(this);
149
150    private Object userData;
151    private ObservableMap<Object, Object> properties;
152
153    /***************************************************************************
154     *                                                                         *
155     * Properties                                                              *
156     *                                                                         *
157     **************************************************************************/
158    
159    /**
160     * The id of this MenuItem. This simple string identifier is useful for finding
161     * a specific MenuItem within the scene graph. 
162     */
163    private StringProperty id;
164    public final void setId(String value) { idProperty().set(value); }
165    @Override public final String getId() { return id == null ? null : id.get(); }
166    public final StringProperty idProperty() {
167        if (id == null) {
168            id = new SimpleStringProperty(this, "id");
169        }
170        return id;
171    }
172    
173    /**
174     * A string representation of the CSS style associated with this specific MenuItem. 
175     * This is analogous to the "style" attribute of an HTML element. Note that, 
176     * like the HTML style attribute, this variable contains style properties and 
177     * values and not the selector portion of a style rule.
178     */
179    private StringProperty style;
180    public final void setStyle(String value) { styleProperty().set(value); }
181    @Override public final String getStyle() { return style == null ? null : style.get(); }
182    public final StringProperty styleProperty() {
183        if (style == null) {
184            style = new SimpleStringProperty(this, "style");
185        }
186        return style;
187    }
188    
189    // --- Parent Menu (useful for submenus)
190    /**
191     * This is the {@link Menu} in which this {@code MenuItem} exists. It is
192     * possible for an instance of this class to not have a {@code parentMenu} -
193     * this means that this instance is either:
194     * <ul>
195     * <li>Not yet associated with its {@code parentMenu}.
196     * <li>A 'root' {@link Menu} (i.e. it is a context menu, attached directly to a
197     * {@link MenuBar}, {@link MenuButton}, or any of the other controls that use
198     * {@link Menu} internally.
199     * </ul>
200     */
201    private ReadOnlyObjectWrapper<Menu> parentMenu;
202
203    protected final void setParentMenu(Menu value) {
204        parentMenuPropertyImpl().set(value);
205    }
206
207    public final Menu getParentMenu() {
208        return parentMenu == null ? null : parentMenu.get();
209    }
210
211    public final ReadOnlyObjectProperty<Menu> parentMenuProperty() {
212        return parentMenuPropertyImpl().getReadOnlyProperty();
213    }
214
215    private ReadOnlyObjectWrapper<Menu> parentMenuPropertyImpl() {
216        if (parentMenu == null) {
217            parentMenu = new ReadOnlyObjectWrapper<Menu>(this, "parentMenu");
218        }
219        return parentMenu;
220    }
221
222
223    // --- Parent Popup
224     /**
225     * This is the {@link ContextMenu} in which this {@code MenuItem} exists.
226     */
227    private ReadOnlyObjectWrapper<ContextMenu> parentPopup;
228
229    protected final void setParentPopup(ContextMenu value) {
230        parentPopupPropertyImpl().set(value);
231    }
232
233    public final ContextMenu getParentPopup() {
234        return parentPopup == null ? null : parentPopup.get();
235    }
236
237    public final ReadOnlyObjectProperty<ContextMenu> parentPopupProperty() {
238        return parentPopupPropertyImpl().getReadOnlyProperty();
239    }
240
241    private ReadOnlyObjectWrapper<ContextMenu> parentPopupPropertyImpl() {
242        if (parentPopup == null) {
243            parentPopup = new ReadOnlyObjectWrapper<ContextMenu>(this, "parentPopup");
244        }
245        return parentPopup;
246    }
247
248
249    // --- Text
250    /**
251     * The text to display in the {@code MenuItem}.
252     */
253    private StringProperty text;
254
255    public final void setText(String value) {
256        textProperty().set(value);
257    }
258
259    public final String getText() {
260        return text == null ? null : text.get();
261    }
262
263    public final StringProperty textProperty() {
264        if (text == null) {
265            text = new SimpleStringProperty(this, "text");
266        }
267        return text;
268    }
269
270
271    // --- Graphic
272    /**
273     * An optional graphic for the {@code MenuItem}. This will normally be
274     * an {@link javafx.scene.image.ImageView} node, but there is no requirement for this to be
275     * the case.
276     */
277    private ObjectProperty<Node> graphic;
278    
279    public final void setGraphic(Node value) {
280        graphicProperty().set(value);
281    }
282
283    public final Node getGraphic() {
284        return graphic == null ? null : graphic.get();
285    }
286
287    public final ObjectProperty<Node> graphicProperty() {
288        if (graphic == null) {
289            graphic = new SimpleObjectProperty<Node>(this, "graphic");
290        }
291        return graphic;
292    }
293    
294
295    // --- OnAction
296    /**
297     * The action, which is invoked whenever the MenuItem is fired. This
298     * may be due to the user clicking on the button with the mouse, or by
299     * a touch event, or by a key press, or if the developer programatically
300     * invokes the {@link #fire()} method.
301     */
302    private ObjectProperty<EventHandler<ActionEvent>> onAction;
303
304    public final void setOnAction(EventHandler<ActionEvent> value) {
305        onActionProperty().set( value);
306    }
307
308    public final EventHandler<ActionEvent> getOnAction() {
309        return onAction == null ? null : onAction.get();
310    }
311
312    public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
313        if (onAction == null) {
314            onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
315                @Override protected void invalidated() {
316                    eventHandlerManager.setEventHandler(ActionEvent.ACTION, get());
317                }
318
319                @Override
320                public Object getBean() {
321                    return MenuItem.this;
322                }
323
324                @Override
325                public String getName() {
326                    return "onAction";
327                }
328            };
329        }
330        return onAction;
331    }
332    
333    /**
334     * <p>Called when a accelerator for the Menuitem is invoked</p>
335     */
336    public static final EventType<Event> MENU_VALIDATION_EVENT = new EventType<Event>
337            (Event.ANY, "MENU_VALIDATION_EVENT");
338    
339    /**
340     * The event handler that is associated with invocation of an accelerator for a MenuItem. This 
341     * can happen when a key sequence for an accelerator is pressed. The event handler is also  
342     * invoked when onShowing event handler is called. 
343     * @since 2.2
344     */
345    private ObjectProperty<EventHandler<Event>> onMenuValidation;
346    
347    public final void setOnMenuValidation(EventHandler<Event> value) {
348        onMenuValidationProperty().set( value);
349    }
350
351    public final EventHandler<Event> getOnMenuValidation() {
352        return onMenuValidation == null ? null : onMenuValidation.get();
353    }
354
355    public final ObjectProperty<EventHandler<Event>> onMenuValidationProperty() {
356        if (onMenuValidation == null) {
357            onMenuValidation = new ObjectPropertyBase<EventHandler<Event>>() {
358                @Override protected void invalidated() {
359                    eventHandlerManager.setEventHandler(MENU_VALIDATION_EVENT, get());
360                }
361                @Override public Object getBean() {
362                    return MenuItem.this;
363                }
364                @Override public String getName() {
365                    return "onMenuValidation";
366                }
367            };
368        }
369        return onMenuValidation;
370    }
371    
372    // --- Disable
373    /** 
374     * Sets the individual disabled state of this MenuItem.
375     * Setting disable to true will cause this MenuItem to become disabled.
376     */
377    private BooleanProperty disable;
378    public final void setDisable(boolean value) { disableProperty().set(value); }
379    public final boolean isDisable() { return disable == null ? false : disable.get(); }
380    public final BooleanProperty disableProperty() {
381        if (disable == null) {
382            disable = new SimpleBooleanProperty(this, "disable");
383        }
384        return disable;
385    }
386
387
388    // --- Visible
389    /**
390     * Specifies whether this MenuItem should be rendered as part of the scene graph. 
391     */
392    private BooleanProperty visible;
393    public final void setVisible(boolean value) { visibleProperty().set(value); }
394    public final boolean isVisible() { return visible == null ? true : visible.get(); }
395    public final BooleanProperty visibleProperty() {
396        if (visible == null) {
397            visible = new SimpleBooleanProperty(this, "visible", true);
398        }
399        return visible;
400    }
401
402    /**
403     * The accelerator property enables accessing the associated action in one keystroke.
404     * It is a convenience offered to perform quickly a given action. 
405     */
406    private ObjectProperty<KeyCombination> accelerator;
407    public final void setAccelerator(KeyCombination value) {
408        acceleratorProperty().set(value);
409    }
410    public final KeyCombination getAccelerator() {
411        return accelerator == null ? null : accelerator.get();
412    }
413    public final ObjectProperty<KeyCombination> acceleratorProperty() {
414        if (accelerator == null) {
415            accelerator = new SimpleObjectProperty<KeyCombination>(this, "accelerator");
416        }
417        return accelerator;
418    }
419
420    /**
421     * MnemonicParsing property to enable/disable text parsing.
422     * If this is set to true, then the MenuItem text will be
423     * parsed to see if it contains the mnemonic parsing character '_'.
424     * When a mnemonic is detected the key combination will
425     * be determined based on the succeeding character, and the mnemonic
426     * added.
427     * 
428     * <p>
429     * The default value for MenuItem is true.
430     * </p>
431     */
432    private BooleanProperty mnemonicParsing;
433    public final void setMnemonicParsing(boolean value) {
434        mnemonicParsingProperty().set(value);
435    }
436    public final boolean isMnemonicParsing() {
437        return mnemonicParsing == null ? true : mnemonicParsing.get();
438    }
439    public final BooleanProperty mnemonicParsingProperty() {
440        if (mnemonicParsing == null) {
441            mnemonicParsing = new SimpleBooleanProperty(this, "mnemonicParsing", true);
442        }
443        return mnemonicParsing;
444    }
445
446    /***************************************************************************
447     *                                                                         *
448     * Public API                                                              *
449     *                                                                         *
450     **************************************************************************/
451    
452    @Override public ObservableList<String> getStyleClass() {
453        return styleClass;
454    }
455    
456    /**
457     * Fires a new ActionEvent.
458     */
459    public void fire() {
460        Event.fireEvent(this, new ActionEvent(this, this));
461    }
462
463    /**
464     * Registers an event handler to this MenuItem. The handler is called when the
465     * menu item receives an {@code Event} of the specified type during the bubbling
466     * phase of event delivery.
467     *
468     * @param <E> the specific event class of the handler
469     * @param eventType the type of the events to receive by the handler
470     * @param eventHandler the handler to register
471     * @throws NullPointerException if the event type or handler is null
472     */
473    public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
474        eventHandlerManager.addEventHandler(eventType, eventHandler);
475    }
476
477    /**
478     * Unregisters a previously registered event handler from this MenuItem. One
479     * handler might have been registered for different event types, so the
480     * caller needs to specify the particular event type from which to
481     * unregister the handler.
482     *
483     * @param <E> the specific event class of the handler
484     * @param eventType the event type from which to unregister
485     * @param eventHandler the handler to unregister
486     * @throws NullPointerException if the event type or handler is null
487     */
488    public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
489        eventHandlerManager.removeEventHandler(eventType, eventHandler);
490    }
491
492    /** {@inheritDoc} */
493    @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
494        // FIXME review that these are configure properly
495        if (getParentPopup() != null) {
496            getParentPopup().buildEventDispatchChain(tail);
497        }
498
499        if (getParentMenu() != null) {
500            getParentMenu().buildEventDispatchChain(tail);
501        }
502
503        return tail.prepend(eventHandlerManager);
504    }
505
506    /**
507     * Returns a previously set Object property, or null if no such property
508     * has been set using the {@link MenuItem#setUserData(java.lang.Object)} method.
509     *
510     * @return The Object that was previously set, or null if no property
511     *          has been set or if null was set.
512     */
513    public Object getUserData() {
514        return userData;
515    }
516 
517    /**
518     * Convenience method for setting a single Object property that can be
519     * retrieved at a later date. This is functionally equivalent to calling
520     * the getProperties().put(Object key, Object value) method. This can later
521     * be retrieved by calling {@link Node#getUserData()}.
522     *
523     * @param value The value to be stored - this can later be retrieved by calling
524     *          {@link Node#getUserData()}.
525     */
526    public void setUserData(Object value) {
527        this.userData = value;
528    }
529
530    /**
531     * Returns an observable map of properties on this menu item for use primarily
532     * by application developers.
533     *
534     * @return an observable map of properties on this menu item for use primarily
535     * by application developers
536     */
537    public ObservableMap<Object, Object> getProperties() {
538        if (properties == null) {
539            properties = FXCollections.observableMap(new HashMap<Object, Object>());
540        }
541        return properties;
542    }
543    
544    /***************************************************************************
545     *                                                                         *
546     * Stylesheet Handling                                                     *
547     *                                                                         *
548     **************************************************************************/
549
550    private static final String DEFAULT_STYLE_CLASS = "menu-item";
551
552    /**
553     * {@inheritDoc}
554     * @return "MenuItem"
555     */
556    @Override
557    public String getTypeSelector() {
558        return "MenuItem";
559    }
560
561    /**
562     * {@inheritDoc}
563     * @return {@code getParentMenu()}, or {@code getParentPopup()} 
564     * if {@code parentMenu} is null
565     */
566    @Override
567    public Styleable getStyleableParent() {
568        
569        if(getParentMenu() == null) {
570            return getParentPopup();
571        } else {
572            return getParentMenu();
573        }
574    }
575
576    /**
577     * {@inheritDoc}
578     */
579    public final ObservableSet<PseudoClass> getPseudoClassStates() {
580        return FXCollections.emptyObservableSet();
581    }
582
583    @Override
584    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
585        return Collections.emptyList();
586    }                
587
588    public Node impl_styleableGetNode() {
589        // Fix for RT-20582. We dive into the visual representation
590        // of this MenuItem so that we may return it to the caller.
591        ContextMenu parentPopup = MenuItem.this.getParentPopup();
592        if (parentPopup == null || ! (parentPopup.getSkin() instanceof ContextMenuSkin)) return null;
593
594        ContextMenuSkin skin = (ContextMenuSkin) parentPopup.getSkin();
595        if (! (skin.getNode() instanceof ContextMenuContent)) return null;
596
597        ContextMenuContent content = (ContextMenuContent) skin.getNode();
598        Parent nodes = content.getItemsContainer();
599
600        MenuItem desiredMenuItem = MenuItem.this;
601        List<Node> childrenNodes = nodes.getChildrenUnmodifiable();
602        for (int i = 0; i < childrenNodes.size(); i++) {
603            if (! (childrenNodes.get(i) instanceof ContextMenuContent.MenuItemContainer)) continue;
604
605            ContextMenuContent.MenuItemContainer MenuRow = 
606                    (ContextMenuContent.MenuItemContainer) childrenNodes.get(i);
607
608            if (desiredMenuItem.equals(MenuRow.getItem())) {
609                return MenuRow;
610            }
611        }
612
613        return null;
614    }
615}