Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2011, 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.collections.ObservableSet;
029import javafx.css.CssMetaData;
030import javafx.beans.property.BooleanProperty;
031import javafx.beans.property.ObjectProperty;
032import javafx.beans.property.ObjectPropertyBase;
033import javafx.beans.property.SimpleBooleanProperty;
034import javafx.beans.property.SimpleObjectProperty;
035import javafx.beans.property.SimpleStringProperty;
036import javafx.beans.property.StringProperty;
037import javafx.collections.FXCollections;
038import javafx.collections.ObservableList;
039import javafx.css.PseudoClass;
040import javafx.event.Event;
041import javafx.event.EventDispatchChain;
042import javafx.event.EventHandler;
043import javafx.event.EventTarget;
044import javafx.event.EventType;
045import javafx.scene.Node;
046
047import javafx.css.Styleable;
048import com.sun.javafx.event.EventHandlerManager;
049
050import java.lang.ref.WeakReference;
051import java.util.Collections;
052import java.util.HashMap;
053import java.util.List;
054
055import javafx.beans.DefaultProperty;
056import javafx.beans.InvalidationListener;
057import javafx.beans.Observable;
058import javafx.beans.property.BooleanPropertyBase;
059import javafx.beans.property.ReadOnlyBooleanProperty;
060import javafx.beans.property.ReadOnlyBooleanWrapper;
061import javafx.beans.property.ReadOnlyObjectProperty;
062import javafx.beans.property.ReadOnlyObjectWrapper;
063import javafx.collections.ObservableMap;
064
065/**
066 * <p>Tabs are placed within a {@link TabPane}, where each tab represents a single
067 * 'page'.</p>
068 * <p>Tabs can contain any {@link Node} such as UI controls or groups
069 * of nodes added to a layout container.</p>
070 * <p>When the user clicks
071 * on a Tab in the TabPane the Tab content becomes visible to the user.</p>
072 */
073@DefaultProperty("content")
074public class Tab implements EventTarget, Styleable {
075
076    /***************************************************************************
077     *                                                                         *
078     * Constructors                                                            *
079     *                                                                         *
080     **************************************************************************/
081
082    /**
083     * Creates a tab with no title.
084     */
085    public Tab() {
086        this(null);
087    }
088
089    /**
090     * Creates a tab with a text title.
091     *
092     * @param text The title of the tab.
093     */
094    public Tab(String text) {
095        setText(text);
096        styleClass.addAll(DEFAULT_STYLE_CLASS);
097    }
098    
099
100    /***************************************************************************
101     *                                                                         *
102     * Properties                                                              *
103     *                                                                         *
104     **************************************************************************/
105
106    private StringProperty id;
107
108    /**
109     * Sets the id of this tab. This simple string identifier is useful for
110     * finding a specific Tab within the {@code TabPane}. The default value is {@code null}.
111     */
112   public final void setId(String value) { idProperty().set(value); }
113
114    /**
115     * The id of this tab.
116     *
117     * @return The id of the tab.
118     */
119    @Override
120    public final String getId() { return id == null ? null : id.get(); }
121
122    /**
123     * The id of this tab.
124     */
125    public final StringProperty idProperty() {
126        if (id == null) {
127            id = new SimpleStringProperty(this, "id");
128        }
129        return id;
130    }
131
132    private StringProperty style;
133
134    /**
135     * A string representation of the CSS style associated with this
136     * tab. This is analogous to the "style" attribute of an
137     * HTML element. Note that, like the HTML style attribute, this
138     * variable contains style properties and values and not the
139     * selector portion of a style rule.
140     * <p>
141     * Parsing this style might not be supported on some limited
142     * platforms. It is recommended to use a standalone CSS file instead.
143     *     
144     */
145   public final void setStyle(String value) { styleProperty().set(value); }
146
147    /**
148     * The CSS style string associated to this tab.
149     *
150     * @return The CSS style string associated to this tab.
151     */
152    @Override
153    public final String getStyle() { return style == null ? null : style.get(); }
154
155    /**
156     * The CSS style string associated to this tab.
157     */
158    public final StringProperty styleProperty() {
159        if (style == null) {
160            style = new SimpleStringProperty(this, "style");
161        }
162        return style;
163    }
164    
165    private ReadOnlyBooleanWrapper selected;
166
167    final void setSelected(boolean value) {
168        selectedPropertyImpl().set(value);
169    }
170
171    /**
172     * <p>Represents whether this tab is the currently selected tab,
173     * To change the selected Tab use {@code tabPane.getSelectionModel().select()}
174     * </p>
175     */
176    public final boolean isSelected() {
177        return selected == null ? false : selected.get();
178    }
179
180    /**
181     * The currently selected tab.
182     */
183    public final ReadOnlyBooleanProperty selectedProperty() {
184        return selectedPropertyImpl().getReadOnlyProperty();
185    }
186
187    private ReadOnlyBooleanWrapper selectedPropertyImpl() {
188        if (selected == null) {
189            selected = new ReadOnlyBooleanWrapper() {
190                @Override protected void invalidated() {
191                    if (getOnSelectionChanged() != null) {
192                        Event.fireEvent(Tab.this, new Event(SELECTION_CHANGED_EVENT));
193                    }
194                }
195
196                @Override
197                public Object getBean() {
198                    return Tab.this;
199                }
200
201                @Override
202                public String getName() {
203                    return "selected";
204                }
205            };
206        }
207        return selected;
208    }
209
210    private ReadOnlyObjectWrapper<TabPane> tabPane;
211
212    final void setTabPane(TabPane value) {
213        tabPanePropertyImpl().set(value);
214    }
215
216    /**
217     * <p>A reference to the TabPane that contains this tab instance.</p>
218     */
219    public final TabPane getTabPane() {
220        return tabPane == null ? null : tabPane.get();
221    }
222
223    /**
224     * The TabPane that contains this tab.
225     */
226    public final ReadOnlyObjectProperty<TabPane> tabPaneProperty() {
227        return tabPanePropertyImpl().getReadOnlyProperty();
228    }
229
230    private ReadOnlyObjectWrapper<TabPane> tabPanePropertyImpl() {
231        if (tabPane == null) {
232            tabPane = new ReadOnlyObjectWrapper<TabPane>(this, "tabPane") {
233                private WeakReference<TabPane> oldParent;
234                                
235                @Override protected void invalidated() {
236                    if(oldParent != null && oldParent.get() != null) {
237                        oldParent.get().disabledProperty().removeListener(parentDisabledChangedListener);
238                    }
239                    updateDisabled();
240                    TabPane newParent = get();
241                    if (newParent != null) {
242                        newParent.disabledProperty().addListener(parentDisabledChangedListener);
243                    }
244                    oldParent = new WeakReference<TabPane>(newParent);
245                    super.invalidated();
246                }
247            };
248        }
249        return tabPane;
250    }
251
252    private final InvalidationListener parentDisabledChangedListener = new InvalidationListener() {
253        @Override public void invalidated(Observable valueModel) {
254            updateDisabled();
255        }
256    };
257    
258    private StringProperty text;
259
260    /**
261     * <p>Sets the text to show in the tab to allow the user to differentiate between
262     * the function of each tab. The text is always visible
263     * </p>
264     */
265    public final void setText(String value) {
266        textProperty().set(value);
267    }
268
269    /**
270     * The text shown in the tab.
271     *
272     * @return The text shown in the tab.
273     */
274    public final String getText() {
275        return text == null ? null : text.get();
276    }
277
278    /**
279     * The text shown in the tab.
280     */
281    public final StringProperty textProperty() {
282        if (text == null) {
283            text = new SimpleStringProperty(this, "text");
284        }
285        return text;
286    }
287
288    private ObjectProperty<Node> graphic;
289
290    /**
291     * <p>Sets the graphic to show in the tab to allow the user to differentiate
292     * between the function of each tab. By default the graphic does not rotate
293     * based on the TabPane.tabPosition value, but it can be set to rotate by
294     * setting TabPane.rotateGraphic to true.</p>
295     */
296    public final void setGraphic(Node value) {
297        graphicProperty().set(value);
298    }
299
300    /**
301     * The graphic shown in the tab.
302     *
303     * @return The graphic shown in the tab.
304     */
305    public final Node getGraphic() {
306        return graphic == null ? null : graphic.get();
307    }
308
309    /**
310     * The graphic in the tab.
311     * 
312     * @return The graphic in the tab.
313     */
314    public final ObjectProperty<Node> graphicProperty() {
315        if (graphic == null) {
316            graphic = new SimpleObjectProperty<Node>(this, "graphic");
317        }
318        return graphic;
319    }
320
321    private ObjectProperty<Node> content;
322
323    /**
324     * <p>The content to show within the main TabPane area. The content
325     * can be any Node such as UI controls or groups of nodes added
326     * to a layout container.</p>
327     */
328    public final void setContent(Node value) {
329        contentProperty().set(value);
330    }
331
332    /**
333     * <p>The content associated with the tab.</p>
334     *
335     * @return The content associated with the tab.
336     */
337    public final Node getContent() {
338        return content == null ? null : content.get();
339    }
340
341    /**
342     * <p>The content associated with the tab.</p>
343     */
344    public final ObjectProperty<Node> contentProperty() {
345        if (content == null) {
346            content = new SimpleObjectProperty<Node>(this, "content");
347        }
348        return content;
349    }
350
351
352    private ObjectProperty<ContextMenu> contextMenu;
353
354    /**
355     * <p>Specifies the context menu to show when the user right-clicks on the tab.
356     * </p>
357     */
358    public final void setContextMenu(ContextMenu value) {
359        contextMenuProperty().set(value);
360    }
361
362    /**
363     * The context menu associated with the tab.
364     * @return The context menu associated with the tab.
365     */
366    public final ContextMenu getContextMenu() {
367        return contextMenu == null ? null : contextMenu.get();
368    }
369
370    /**
371     * The context menu associated with the tab.
372     */
373    public final ObjectProperty<ContextMenu> contextMenuProperty() {
374        if (contextMenu == null) {
375            contextMenu = new SimpleObjectProperty<ContextMenu>(this, "contextMenu");
376        }
377        return contextMenu;
378    }
379
380    private BooleanProperty closable;
381
382    /**
383     * <p>Sets {@code true} if the tab is closable.  If this is set to {@code false},
384     * then regardless of the TabClosingPolicy, it will not be
385     * possible for the user to close this tab. Therefore, when this
386     * property is {@code false}, no 'close' button will be shown on the tab.
387     * The default is {@code true}.</p>
388     *
389     */
390    public final void setClosable(boolean value) {
391        closableProperty().set(value);
392    }
393
394    /**
395     * Returns {@code true} if this tab is closable.
396     *
397     * @return {@code true} if the tab is closable.
398     */
399    public final boolean isClosable() {
400        return closable == null ? true : closable.get();
401    }
402
403    /**
404     * The closable state for this tab.
405     */
406    public final BooleanProperty closableProperty() {
407        if (closable == null) {
408            closable = new SimpleBooleanProperty(this, "closable", true);
409        }
410        return closable;
411    }
412
413
414    /**
415     * <p>Called when the tab becomes selected or unselected.</p>
416     */
417    public static final EventType<Event> SELECTION_CHANGED_EVENT = 
418            new EventType<Event> (Event.ANY, "SELECTION_CHANGED_EVENT");
419    private ObjectProperty<EventHandler<Event>> onSelectionChanged;
420
421    /**
422     * Defines a function to be called when a selection changed has occurred on the tab.
423     */
424    public final void setOnSelectionChanged(EventHandler<Event> value) {
425        onSelectionChangedProperty().set(value);
426    }
427
428    /**
429     * The event handler that is associated with a selection on the tab.
430     *
431     * @return The event handler that is associated with a tab selection.
432     */
433    public final EventHandler<Event> getOnSelectionChanged() {
434        return onSelectionChanged == null ? null : onSelectionChanged.get();
435    }
436
437    /**
438     * The event handler that is associated with a selection on the tab.
439     */
440    public final ObjectProperty<EventHandler<Event>> onSelectionChangedProperty() {
441        if (onSelectionChanged == null) {
442            onSelectionChanged = new ObjectPropertyBase<EventHandler<Event>>() {
443                @Override protected void invalidated() {
444                    setEventHandler(SELECTION_CHANGED_EVENT, get());
445                }
446
447                @Override
448                public Object getBean() {
449                    return Tab.this;
450                }
451
452                @Override
453                public String getName() {
454                    return "onSelectionChanged";
455                }
456            };
457        }
458        return onSelectionChanged;
459    }
460
461    /**
462     * <p>Called when a user closes this tab. This is useful for freeing up memory.</p>
463     */
464    public static final EventType<Event> CLOSED_EVENT = new EventType<Event>(Event.ANY, "TAB_CLOSED");
465    private ObjectProperty<EventHandler<Event>> onClosed;
466
467    /**
468     * Defines a function to be called when the tab is closed.
469     */
470    public final void setOnClosed(EventHandler<Event> value) {
471        onClosedProperty().set(value);
472    }
473
474    /**
475     * The event handler that is associated with the tab when the tab is closed.
476     *
477     * @return The event handler that is associated with the tab when the tab is closed.
478     */
479    public final EventHandler<Event> getOnClosed() {
480        return onClosed == null ? null : onClosed.get();
481    }
482
483    /**
484     * The event handler that is associated with the tab when the tab is closed.
485     */
486    public final ObjectProperty<EventHandler<Event>> onClosedProperty() {
487        if (onClosed == null) {
488            onClosed = new ObjectPropertyBase<EventHandler<Event>>() {
489                @Override protected void invalidated() {
490                    setEventHandler(CLOSED_EVENT, get());
491                }
492
493                @Override
494                public Object getBean() {
495                    return Tab.this;
496                }
497
498                @Override
499                public String getName() {
500                    return "onClosed";
501                }
502            };
503        }
504        return onClosed;
505    }
506
507    private ObjectProperty<Tooltip> tooltip;
508
509    /**
510     * <p>Specifies the tooltip to show when the user hovers over the tab.</p>
511     */
512    public final void setTooltip(Tooltip value) { tooltipProperty().setValue(value); }
513
514    /**
515     * The tooltip associated with this tab.
516     * @return The tooltip associated with this tab.
517     */
518    public final Tooltip getTooltip() { return tooltip == null ? null : tooltip.getValue(); }
519
520    /**
521     * The tooltip associated with this tab.
522     */
523    public final ObjectProperty<Tooltip> tooltipProperty() {
524        if (tooltip == null) {
525            tooltip = new SimpleObjectProperty<Tooltip>(this, "tooltip");
526        }
527        return tooltip;
528    }
529    
530    private final ObservableList<String> styleClass = FXCollections.observableArrayList();
531
532    private BooleanProperty disable;
533    
534    /**
535     * Sets the disabled state of this tab.
536     * 
537     * @param value the state to set this tab
538     * 
539     * @defaultValue false
540     * @since 2.2
541     */
542    public final void setDisable(boolean value) { 
543        disableProperty().set(value);
544    }
545
546    /**
547     * Returns {@code true} if this tab is disable.
548     * @since 2.2
549     */
550    public final boolean isDisable() { return disable == null ? false : disable.get(); }
551
552    /**
553     * Sets the disabled state of this tab. A disable tab is no longer interactive
554     * or traversable, but the contents remain interactive.  A disable tab 
555     * can be selected using {@link TabPane#getSelectionModel()}.
556     * 
557     * @defaultValue false
558     * @since 2.2
559     */
560    public final BooleanProperty disableProperty() {
561        if (disable == null) {
562            disable = new BooleanPropertyBase(false) {
563                @Override
564                protected void invalidated() {
565                    updateDisabled();
566                }
567
568                @Override
569                public Object getBean() {
570                    return Tab.this;
571                }
572
573                @Override
574                public String getName() {
575                    return "disable";
576                }
577            };
578        }
579        return disable;
580    }
581    
582    private ReadOnlyBooleanWrapper disabled;
583
584    private final void setDisabled(boolean value) {
585        disabledPropertyImpl().set(value);
586    }
587
588    /**
589     * Returns true when the {@code Tab} {@link #disableProperty disable} is set to
590     * {@code true} or if the {@code TabPane} is disabled.
591     * @since 2.2
592     */
593    public final boolean isDisabled() {
594        return disabled == null ? false : disabled.get();
595    }
596
597    /**
598     * Indicates whether or not this {@code Tab} is disabled.  A {@code Tab}
599     * will become disabled if {@link #disableProperty disable} is set to {@code true} on either
600     * itself or if the {@code TabPane} is disabled.
601     * 
602     * @defaultValue false
603     * @since 2.2
604     */
605    public final ReadOnlyBooleanProperty disabledProperty() {
606        return disabledPropertyImpl().getReadOnlyProperty();
607    }
608
609    private ReadOnlyBooleanWrapper disabledPropertyImpl() {
610        if (disabled == null) {
611            disabled = new ReadOnlyBooleanWrapper() {
612                @Override
613                public Object getBean() {
614                    return Tab.this;
615                }
616
617                @Override
618                public String getName() {
619                    return "disabled";
620                }
621            };
622        }
623        return disabled;
624    }
625    
626    private void updateDisabled() {
627        setDisabled(isDisable() || (getTabPane() != null && getTabPane().isDisabled()));
628    }
629    
630     /**
631     * Called when there is an external request to close this {@code Tab}.
632     * The installed event handler can prevent tab closing by consuming the
633     * received event.
634     */
635    public static final EventType<Event> TAB_CLOSE_REQUEST_EVENT = new EventType<Event> (Event.ANY, "TAB_CLOSE_REQUEST_EVENT");
636   
637    /**
638     * Called when there is an external request to close this {@code Tab}.
639     * The installed event handler can prevent tab closing by consuming the
640     * received event.
641     */
642    private ObjectProperty<EventHandler<Event>> onCloseRequest;
643    public final ObjectProperty<EventHandler<Event>> onCloseRequestProperty() {
644        if (onCloseRequest == null) {
645            onCloseRequest = new ObjectPropertyBase<EventHandler<Event>>() {
646                @Override protected void invalidated() {
647                    setEventHandler(TAB_CLOSE_REQUEST_EVENT, get());
648                }
649                
650                @Override public Object getBean() {
651                    return Tab.this;
652                }
653
654                @Override public String getName() {
655                    return "onCloseRequest";
656                }
657            };
658        }
659        return onCloseRequest;
660    }
661    
662    public EventHandler<Event> getOnCloseRequest() {
663        if( onCloseRequest == null ) {
664            return null;
665        }
666        return onCloseRequest.get();
667    }
668    
669    public void setOnCloseRequest(EventHandler<Event> value) {
670        onCloseRequestProperty().set(value);
671    }
672
673    
674    // --- Properties
675    private static final Object USER_DATA_KEY = new Object();
676    
677    // A map containing a set of properties for this Tab
678    private ObservableMap<Object, Object> properties;
679
680    /**
681      * Returns an observable map of properties on this Tab for use primarily
682      * by application developers.
683      *
684      * @return an observable map of properties on this Tab for use primarily
685      * by application developers
686     * @since 2.2
687     */
688     public final ObservableMap<Object, Object> getProperties() {
689        if (properties == null) {
690            properties = FXCollections.observableMap(new HashMap<Object, Object>());
691        }
692        return properties;
693    }
694    
695    /**
696     * Tests if this Tab has properties.
697     * @return true if this tab has properties.
698     * @since 2.2
699     */
700     public boolean hasProperties() {
701        return properties != null && !properties.isEmpty();
702    }
703
704     
705    // --- UserData
706    /**
707     * Convenience method for setting a single Object property that can be
708     * retrieved at a later date. This is functionally equivalent to calling
709     * the getProperties().put(Object key, Object value) method. This can later
710     * be retrieved by calling {@link Tab#getUserData()}.
711     *
712     * @param value The value to be stored - this can later be retrieved by calling
713     *          {@link Tab#getUserData()}.
714     * @since 2.2
715     */
716    public void setUserData(Object value) {
717        getProperties().put(USER_DATA_KEY, value);
718    }
719
720    /**
721     * Returns a previously set Object property, or null if no such property
722     * has been set using the {@link Tab#setUserData(java.lang.Object)} method.
723     *
724     * @return The Object that was previously set, or null if no property
725     *          has been set or if null was set.
726     * @since 2.2
727     */
728    public Object getUserData() {
729        return getProperties().get(USER_DATA_KEY);
730    }
731    
732    /**
733     * A list of String identifiers which can be used to logically group
734     * Nodes, specifically for an external style engine. This variable is
735     * analogous to the "class" attribute on an HTML element and, as such,
736     * each element of the list is a style class to which this Node belongs.
737     *
738     * @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a>
739     */
740    @Override
741    public ObservableList<String> getStyleClass() {
742        return styleClass;
743    }
744    
745    private final EventHandlerManager eventHandlerManager =
746            new EventHandlerManager(this);
747
748    /**
749     * @treatAsPrivate implementation detail
750     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
751     */
752    @Override
753    public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {        
754        return tail.prepend(eventHandlerManager);
755    }
756
757    /**
758     * @treatAsPrivate implementation detail
759     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
760     */
761    protected <E extends Event> void setEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) {
762        eventHandlerManager.setEventHandler(eventType, eventHandler);
763    }
764
765    /***************************************************************************
766     *                                                                         *
767     * Stylesheet Handling                                                     *
768     *                                                                         *
769     **************************************************************************/
770
771    private static final String DEFAULT_STYLE_CLASS = "tab";
772    
773    /**
774     * {@inheritDoc}
775     * @return "Tab"
776     */
777   @Override
778    public String getTypeSelector() {
779        return "Tab";
780    }
781
782    /**
783     * {@inheritDoc}
784     * @return {@code getTabPane()}
785     */
786    @Override
787    public Styleable getStyleableParent() {
788        return getTabPane();
789    }
790
791    /**
792     * {@inheritDoc}
793     */
794    public final ObservableSet<PseudoClass> getPseudoClassStates() {
795        return FXCollections.emptyObservableSet();
796    }
797
798    /**
799     * {@inheritDoc}
800     */
801    @Override
802    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
803        return getClassCssMetaData();
804    }                
805
806   public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
807        return Collections.emptyList();
808    }                
809    
810}