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 java.util.ArrayList;
029import java.util.Collections;
030import java.util.List;
031import javafx.beans.property.BooleanProperty;
032import javafx.beans.property.DoubleProperty;
033import javafx.beans.property.ObjectProperty;
034import javafx.beans.property.ObjectPropertyBase;
035import javafx.beans.property.SimpleBooleanProperty;
036import javafx.beans.property.SimpleObjectProperty;
037import javafx.collections.FXCollections;
038import javafx.collections.ListChangeListener;
039import javafx.collections.ObservableList;
040import javafx.geometry.Side;
041
042import javafx.css.StyleableDoubleProperty;
043import javafx.css.CssMetaData;
044import javafx.css.PseudoClass;
045import com.sun.javafx.css.converters.SizeConverter;
046import com.sun.javafx.scene.control.skin.TabPaneSkin;
047import javafx.beans.DefaultProperty;
048import javafx.css.Styleable;
049import javafx.css.StyleableProperty;
050
051/**
052 * <p>A control that allows switching between a group of {@link Tab Tabs}.  Only one tab
053 * is visible at a time. Tabs are added to the TabPane by using the {@link #getTabs}.</p>
054 *
055 * <p>Tabs in a TabPane can be positioned at any of the four sides by specifying the
056 * {@link Side}. </p>
057 *
058 * <p>A TabPane has two modes floating or recessed.  Applying the styleclass STYLE_CLASS_FLOATING
059 * will change the TabPane mode to floating.</p>
060 *
061 * <p>The tabs width and height can be set to a specific size by
062 * setting the min and max for height and width.  TabPane default width will be
063 * determined by the largest content width in the TabPane.  This is the same for the height.
064 * If a different size is desired the width and height of the TabPane can
065 * be overridden by setting the min, pref and max size.</p>
066 *
067 * <p>When the number of tabs do not fit the TabPane a menu button will appear on the right.
068 * The menu button is used to select the tabs that are currently not visible.
069 * </p>
070 *
071 * <p>Example:</p>
072 * <pre><code>
073 * TabPane tabPane = new TabPane();
074 * Tab tab = new Tab();
075 * tab.setText("new tab");
076 * tab.setContent(new Rectangle(200,200, Color.LIGHTSTEELBLUE));
077 * tabPane.getTabs().add(tab);
078 * </code></pre>
079 *
080 * @see Tab
081 */
082@DefaultProperty("tabs")
083public class TabPane extends Control {
084    private static final double DEFAULT_TAB_MIN_WIDTH = 0;
085
086    private static final double DEFAULT_TAB_MAX_WIDTH = Double.MAX_VALUE;
087
088    private static final double DEFAULT_TAB_MIN_HEIGHT = 0;
089
090    private static final double DEFAULT_TAB_MAX_HEIGHT = Double.MAX_VALUE;
091
092    /**
093     * TabPane mode will be changed to floating allowing the TabPane
094     * to be placed alongside other control.
095     */
096    public static final String STYLE_CLASS_FLOATING = "floating";
097
098    /**
099     * Constructs a new TabPane.
100     */
101    public TabPane() {
102        getStyleClass().setAll("tab-pane");
103        setSelectionModel(new TabPaneSelectionModel(this));
104
105        tabs.addListener(new ListChangeListener<Tab>() {
106            @Override public void onChanged(Change<? extends Tab> c) {
107                while (c.next()) {
108                    for (Tab tab : c.getRemoved()) {
109                        if (tab != null && !getTabs().contains(tab)) {
110                            tab.setTabPane(null);
111                        }
112                    }
113
114                    for (Tab tab : c.getAddedSubList()) {
115                        if (tab != null) {
116                            tab.setTabPane(TabPane.this);
117                        }
118                    }
119                }
120            }
121        });
122        
123        // initialize pseudo-class state
124        Side edge = getSide();
125        pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (edge == Side.TOP));
126        pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (edge == Side.RIGHT));
127        pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (edge == Side.BOTTOM));
128        pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (edge == Side.LEFT));
129        
130    }
131
132    private ObservableList<Tab> tabs = FXCollections.observableArrayList();
133
134    /**
135     * <p>The tabs to display in this TabPane. Changing this ObservableList will
136     * immediately result in the TabPane updating to display the new contents
137     * of this ObservableList.</p>
138     *
139     * <p>If the tabs ObservableList changes, the selected tab will remain the previously
140     * selected tab, if it remains within this ObservableList. If the previously
141     * selected tab is no longer in the tabs ObservableList, the selected tab will
142     * become the first tab in the ObservableList.</p>
143     */
144    public final ObservableList<Tab> getTabs() {
145        return tabs;
146    }
147
148    private ObjectProperty<SingleSelectionModel<Tab>> selectionModel = new SimpleObjectProperty<SingleSelectionModel<Tab>>(this, "selectionModel");
149
150    /**
151     * <p>Sets the model used for tab selection.  By changing the model you can alter
152     * how the tabs are selected and which tabs are first or last.</p>
153     */
154    public final void setSelectionModel(SingleSelectionModel<Tab> value) { selectionModel.set(value); }
155
156    /**
157     * <p>Gets the model used for tab selection.</p>
158     */
159    public final SingleSelectionModel<Tab> getSelectionModel() { return selectionModel.get(); }
160
161    /**
162     * The selection model used for selecting tabs.
163     */
164    public final ObjectProperty<SingleSelectionModel<Tab>> selectionModelProperty() { return selectionModel; }
165
166    private ObjectProperty<Side> side;
167
168    /**
169     * <p>The position to place the tabs in this TabPane. Whenever this changes
170     * the TabPane will immediately update the location of the tabs to reflect
171     * this.</p>
172     *
173     */
174    public final void setSide(Side value) {
175        sideProperty().set(value);
176    }
177
178    /**
179     * The current position of the tabs in the TabPane.  The default position
180     * for the tabs is Side.Top.
181     *
182     * @return The current position of the tabs in the TabPane.
183     */
184    public final Side getSide() {
185        return side == null ? Side.TOP : side.get();
186    }
187
188    /**
189     * The position of the tabs in the TabPane.
190     */
191    public final ObjectProperty<Side> sideProperty() {
192        if (side == null) {
193            side = new ObjectPropertyBase<Side>(Side.TOP) {
194                private Side oldSide;
195                @Override protected void invalidated() {
196                    
197                    oldSide = get();
198
199                    pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (oldSide == Side.TOP || oldSide == null));
200                    pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (oldSide == Side.RIGHT));
201                    pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (oldSide == Side.BOTTOM));
202                    pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (oldSide == Side.LEFT));
203                }
204
205                @Override
206                public Object getBean() {
207                    return TabPane.this;
208                }
209
210                @Override
211                public String getName() {
212                    return "side";
213                }
214            };
215        }
216        return side;
217    }
218
219    private ObjectProperty<TabClosingPolicy> tabClosingPolicy;
220
221    /**
222     * <p>Specifies how the TabPane handles tab closing from an end-users
223     * perspective. The options are:</p>
224     *
225     * <ul>
226     *   <li> TabClosingPolicy.UNAVAILABLE: Tabs can not be closed by the user
227     *   <li> TabClosingPolicy.SELECTED_TAB: Only the currently selected tab will
228     *          have the option to be closed, with a graphic next to the tab
229     *          text being shown. The graphic will disappear when a tab is no
230     *          longer selected.
231     *   <li> TabClosingPolicy.ALL_TABS: All tabs will have the option to be
232     *          closed.
233     * </ul>
234     *
235     * <p>Refer to the {@link TabClosingPolicy} enumeration for further details.</p>
236     *
237     * The default closing policy is TabClosingPolicy.SELECTED_TAB
238     */
239    public final void setTabClosingPolicy(TabClosingPolicy value) {
240        tabClosingPolicyProperty().set(value);
241    }
242
243    /**
244     * The closing policy for the tabs.
245     *
246     * @return The closing policy for the tabs.
247     */
248    public final TabClosingPolicy getTabClosingPolicy() {
249        return tabClosingPolicy == null ? TabClosingPolicy.SELECTED_TAB : tabClosingPolicy.get();
250    }
251
252    /**
253     * The closing policy for the tabs.
254     */
255    public final ObjectProperty<TabClosingPolicy> tabClosingPolicyProperty() {
256        if (tabClosingPolicy == null) {
257            tabClosingPolicy = new SimpleObjectProperty<TabClosingPolicy>(this, "tabClosingPolicy", TabClosingPolicy.SELECTED_TAB);
258        }
259        return tabClosingPolicy;
260    }
261
262    private BooleanProperty rotateGraphic;
263
264    /**
265     * <p>Specifies whether the graphic inside a Tab is rotated or not, such
266     * that it is always upright, or rotated in the same way as the Tab text is.</p>
267     *
268     * <p>By default rotateGraphic is set to false, to represent the fact that
269     * the graphic isn't rotated, resulting in it always appearing upright. If
270     * rotateGraphic is set to {@code true}, the graphic will rotate such that it
271     * rotates with the tab text.</p>
272     *
273     */
274    public final void setRotateGraphic(boolean value) {
275        rotateGraphicProperty().set(value);
276    }
277
278    /**
279     * Returns {@code true} if the graphic inside a Tab is rotated. The
280     * default is {@code false}
281     *
282     * @return the rotatedGraphic state.
283     */
284    public final boolean isRotateGraphic() {
285        return rotateGraphic == null ? false : rotateGraphic.get();
286    }
287
288    /**
289     * The rotatedGraphic state of the tabs in the TabPane.
290     */
291    public final BooleanProperty rotateGraphicProperty() {
292        if (rotateGraphic == null) {
293            rotateGraphic = new SimpleBooleanProperty(this, "rotateGraphic", false);
294        }
295        return rotateGraphic;
296    }
297
298    private DoubleProperty tabMinWidth;
299
300    /**
301     * <p>The minimum width of the tabs in the TabPane.  This can be used to limit
302     * the length of text in tabs to prevent truncation.  Setting the min equal
303     * to the max will fix the width of the tab.  By default the min equals to the max.
304     *
305     * This value can also be set via CSS using {@code -fx-tab-min-width}
306     *
307     * </p>
308     */
309    public final void setTabMinWidth(double value) {
310        tabMinWidthProperty().setValue(value);
311    }
312
313    /**
314     * The minimum width of the tabs in the TabPane.
315     *
316     * @return The minimum width of the tabs.
317     */
318    public final double getTabMinWidth() {
319        return tabMinWidth == null ? DEFAULT_TAB_MIN_WIDTH : tabMinWidth.getValue();
320    }
321
322    /**
323     * The minimum width of the tabs in the TabPane.
324     */
325    public final DoubleProperty tabMinWidthProperty() {
326        if (tabMinWidth == null) {
327            tabMinWidth = new StyleableDoubleProperty(DEFAULT_TAB_MIN_WIDTH) {
328
329                @Override
330                public CssMetaData<TabPane,Number> getCssMetaData() {
331                    return StyleableProperties.TAB_MIN_WIDTH;
332                }
333
334                @Override
335                public Object getBean() {
336                    return TabPane.this;
337                }
338
339                @Override
340                public String getName() {
341                    return "tabMinWidth";
342                }
343            };
344        }
345        return tabMinWidth;
346    }
347
348    /**
349     * <p>Specifies the maximum width of a tab.  This can be used to limit
350     * the length of text in tabs.  If the tab text is longer than the maximum
351     * width the text will be truncated.  Setting the max equal
352     * to the min will fix the width of the tab.  By default the min equals to the max
353     *
354     * This value can also be set via CSS using {@code -fx-tab-max-width}.</p>
355     */
356    private DoubleProperty tabMaxWidth;
357    public final void setTabMaxWidth(double value) {
358        tabMaxWidthProperty().setValue(value);
359    }
360
361    /**
362     * The maximum width of the tabs in the TabPane.
363     *
364     * @return The maximum width of the tabs.
365     */
366    public final double getTabMaxWidth() {
367        return tabMaxWidth == null ? DEFAULT_TAB_MAX_WIDTH : tabMaxWidth.getValue();
368    }
369
370    /**
371     * The maximum width of the tabs in the TabPane.
372     */
373    public final DoubleProperty tabMaxWidthProperty() {
374        if (tabMaxWidth == null) {
375            tabMaxWidth = new StyleableDoubleProperty(DEFAULT_TAB_MAX_WIDTH) {
376
377                @Override
378                public CssMetaData<TabPane,Number> getCssMetaData() {
379                    return StyleableProperties.TAB_MAX_WIDTH;
380                }
381
382                @Override
383                public Object getBean() {
384                    return TabPane.this;
385                }
386
387                @Override
388                public String getName() {
389                    return "tabMaxWidth";
390                }
391            };
392        }
393        return tabMaxWidth;
394    }
395
396    private DoubleProperty tabMinHeight;
397
398    /**
399     * <p>The minimum height of the tabs in the TabPane.  This can be used to limit
400     * the height in tabs. Setting the min equal to the max will fix the height
401     * of the tab.  By default the min equals to the max.
402     *
403     * This value can also be set via CSS using {@code -fx-tab-min-height}
404     * </p>
405     */
406    public final void setTabMinHeight(double value) {
407        tabMinHeightProperty().setValue(value);
408    }
409
410    /**
411     * The minimum height of the tabs in the TabPane.
412     *
413     * @return The minimum height of the tabs.
414     */
415    public final double getTabMinHeight() {
416        return tabMinHeight == null ? DEFAULT_TAB_MIN_HEIGHT : tabMinHeight.getValue();
417    }
418
419    /**
420     * The minimum height of the tab.
421     */
422    public final DoubleProperty tabMinHeightProperty() {
423        if (tabMinHeight == null) {
424            tabMinHeight = new StyleableDoubleProperty(DEFAULT_TAB_MIN_HEIGHT) {
425
426                @Override
427                public CssMetaData<TabPane,Number> getCssMetaData() {
428                    return StyleableProperties.TAB_MIN_HEIGHT;
429                }
430
431                @Override
432                public Object getBean() {
433                    return TabPane.this;
434                }
435
436                @Override
437                public String getName() {
438                    return "tabMinHeight";
439                }
440            };
441        }
442        return tabMinHeight;
443    }
444
445    /**
446     * <p>The maximum height if the tabs in the TabPane.  This can be used to limit
447     * the height in tabs. Setting the max equal to the min will fix the height
448     * of the tab.  By default the min equals to the max.
449     *
450     * This value can also be set via CSS using -fx-tab-max-height
451     * </p>
452     */
453    private DoubleProperty tabMaxHeight;
454    public final void setTabMaxHeight(double value) {
455        tabMaxHeightProperty().setValue(value);
456    }
457
458    /**
459     * The maximum height of the tabs in the TabPane.
460     *
461     * @return The maximum height of the tabs.
462     */
463    public final double getTabMaxHeight() {
464        return tabMaxHeight == null ? DEFAULT_TAB_MAX_HEIGHT : tabMaxHeight.getValue();
465    }
466
467    /**
468     * <p>The maximum height of the tabs in the TabPane.</p>
469     */
470    public final DoubleProperty tabMaxHeightProperty() {
471        if (tabMaxHeight == null) {
472            tabMaxHeight = new StyleableDoubleProperty(DEFAULT_TAB_MAX_HEIGHT) {
473
474                @Override
475                public CssMetaData<TabPane,Number> getCssMetaData() {
476                    return StyleableProperties.TAB_MAX_HEIGHT;
477                }
478
479                @Override
480                public Object getBean() {
481                    return TabPane.this;
482                }
483
484                @Override
485                public String getName() {
486                    return "tabMaxHeight";
487                }
488            };
489        }
490        return tabMaxHeight;
491    }
492
493    /** {@inheritDoc} */
494    @Override protected Skin<?> createDefaultSkin() {
495        return new TabPaneSkin(this);
496    }
497
498    /***************************************************************************
499     *                                                                         *
500     *                         Stylesheet Handling                             *
501     *                                                                         *
502     **************************************************************************/
503
504    private static class StyleableProperties {
505        private static final CssMetaData<TabPane,Number> TAB_MIN_WIDTH =
506                new CssMetaData<TabPane,Number>("-fx-tab-min-width",
507                SizeConverter.getInstance(), DEFAULT_TAB_MIN_WIDTH) {
508
509            @Override
510            public boolean isSettable(TabPane n) {
511                return n.tabMinWidth == null || !n.tabMinWidth.isBound();
512            }
513
514            @Override
515            public StyleableProperty<Number> getStyleableProperty(TabPane n) {
516                return (StyleableProperty<Number>)n.tabMinWidthProperty();
517            }
518        };
519
520        private static final CssMetaData<TabPane,Number> TAB_MAX_WIDTH =
521                new CssMetaData<TabPane,Number>("-fx-tab-max-width",
522                SizeConverter.getInstance(), DEFAULT_TAB_MAX_WIDTH) {
523
524            @Override
525            public boolean isSettable(TabPane n) {
526                return n.tabMaxWidth == null || !n.tabMaxWidth.isBound();
527            }
528
529            @Override
530            public StyleableProperty<Number> getStyleableProperty(TabPane n) {
531                return (StyleableProperty<Number>)n.tabMaxWidthProperty();
532            }
533        };
534
535        private static final CssMetaData<TabPane,Number> TAB_MIN_HEIGHT =
536                new CssMetaData<TabPane,Number>("-fx-tab-min-height",
537                SizeConverter.getInstance(), DEFAULT_TAB_MIN_HEIGHT) {
538
539            @Override
540            public boolean isSettable(TabPane n) {
541                return n.tabMinHeight == null || !n.tabMinHeight.isBound();
542            }
543
544            @Override
545            public StyleableProperty<Number> getStyleableProperty(TabPane n) {
546                return (StyleableProperty<Number>)n.tabMinHeightProperty();
547            }
548        };
549
550        private static final CssMetaData<TabPane,Number> TAB_MAX_HEIGHT =
551                new CssMetaData<TabPane,Number>("-fx-tab-max-height",
552                SizeConverter.getInstance(), DEFAULT_TAB_MAX_HEIGHT) {
553
554            @Override
555            public boolean isSettable(TabPane n) {
556                return n.tabMaxHeight == null || !n.tabMaxHeight.isBound();
557            }
558
559            @Override
560            public StyleableProperty<Number> getStyleableProperty(TabPane n) {
561                return (StyleableProperty<Number>)n.tabMaxHeightProperty();
562            }
563        };
564
565        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
566        static {
567            final List<CssMetaData<? extends Styleable, ?>> styleables =
568                new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
569            styleables.add(TAB_MIN_WIDTH);
570            styleables.add(TAB_MAX_WIDTH);
571            styleables.add(TAB_MIN_HEIGHT);
572            styleables.add(TAB_MAX_HEIGHT);
573            STYLEABLES = Collections.unmodifiableList(styleables);
574        }
575    }
576
577    /**
578     * @return The CssMetaData associated with this class, which may include the
579     * CssMetaData of its super classes.
580     */
581    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
582        return StyleableProperties.STYLEABLES;
583    }
584
585    /**
586     * {@inheritDoc}
587     */
588    @Override
589    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
590        return getClassCssMetaData();
591    }
592
593    private static final PseudoClass TOP_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("top");
594    private static final PseudoClass BOTTOM_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("bottom");
595    private static final PseudoClass LEFT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("left");
596    private static final PseudoClass RIGHT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("right");
597
598    
599    static class TabPaneSelectionModel extends SingleSelectionModel<Tab> {
600        private final TabPane tabPane;
601
602        public TabPaneSelectionModel(final TabPane t) {
603            if (t == null) {
604                throw new NullPointerException("TabPane can not be null");
605            }
606            this.tabPane = t;
607
608            // watching for changes to the items list content
609            final ListChangeListener<Tab> itemsContentObserver = new ListChangeListener<Tab>() {
610                @Override public void onChanged(Change<? extends Tab> c) {
611                    while (c.next()) {
612                        for (Tab tab : c.getRemoved()) {
613                            if (tab != null && !tabPane.getTabs().contains(tab)) {
614                                if (getSelectedIndex() == 0 && tabPane.getTabs().size() > 0) {                                    
615                                    clearAndSelect(0);
616                                    tab.setSelected(false);
617                                }
618                                if (tab.isSelected()) {
619                                    tab.setSelected(false);
620                                    if (c.getFrom() == 0) {
621                                        if (tabPane.getTabs().size() > 1) {
622                                            clearSelection();
623                                        }
624                                    } else {
625                                        selectPrevious();
626                                    }
627                                }
628                            }
629                        }
630                        if (c.wasAdded() || c.wasRemoved()) {
631                            // The selected tab index can be out of sync with the list of tab if
632                            // we add or remove tabs before the selected tab.
633                            if (getSelectedIndex() != tabPane.getTabs().indexOf(getSelectedItem())) {
634                                clearAndSelect(tabPane.getTabs().indexOf(getSelectedItem()));
635                            }
636                        }
637                    }
638                    if (getSelectedIndex() == -1 && getSelectedItem() == null && tabPane.getTabs().size() > 0) {                        
639                        selectFirst();
640                    } else if (tabPane.getTabs().isEmpty()) {
641                        clearSelection();
642                    }
643                }                
644            };
645            if (this.tabPane.getTabs() != null) {
646                this.tabPane.getTabs().addListener(itemsContentObserver);
647            }
648        }
649
650        // API Implementation
651        @Override public void select(int index) {
652            if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) ||
653                (index == getSelectedIndex() && getModelItem(index).isSelected())) {
654                return;
655            }
656            
657            // Unselect the old tab
658            if (getSelectedIndex() >= 0 && getSelectedIndex() < tabPane.getTabs().size()) {
659                tabPane.getTabs().get(getSelectedIndex()).setSelected(false);
660            }
661
662            setSelectedIndex(index);
663
664            Tab tab = getModelItem(index);            
665            if (tab != null) {
666                setSelectedItem(tab);
667            }
668            
669            // Select the new tab
670            if (getSelectedIndex() >= 0 && getSelectedIndex() < tabPane.getTabs().size()) {
671                tabPane.getTabs().get(getSelectedIndex()).setSelected(true);
672            }
673        }
674
675        @Override public void select(Tab tab) {
676            final int itemCount = getItemCount();
677
678            for (int i = 0; i < itemCount; i++) {
679                final Tab value = getModelItem(i);
680                if (value != null && value.equals(tab)) {
681                    select(i);
682                    return;
683                }
684            }
685            if (tab != null) {
686                setSelectedItem(tab);
687            }
688        }
689
690        @Override protected Tab getModelItem(int index) {
691            final ObservableList<Tab> items = tabPane.getTabs();
692            if (items == null) return null;
693            if (index < 0 || index >= items.size()) return null;
694            return items.get(index);
695        }
696
697        @Override protected int getItemCount() {
698            final ObservableList<Tab> items = tabPane.getTabs();
699            return items == null ? 0 : items.size();
700        }
701    }
702
703    /**
704     * <p>This specifies how the TabPane handles tab closing from an end-users
705     * perspective. The options are:</p>
706     *
707     * <ul>
708     *   <li> TabClosingPolicy.UNAVAILABLE: Tabs can not be closed by the user
709     *   <li> TabClosingPolicy.SELECTED_TAB: Only the currently selected tab will
710     *          have the option to be closed, with a graphic next to the tab
711     *          text being shown. The graphic will disappear when a tab is no
712     *          longer selected.
713     *   <li> TabClosingPolicy.ALL_TABS: All tabs will have the option to be
714     *          closed.
715     * </ul>
716     */
717    public enum TabClosingPolicy {
718
719        /**
720         * Only the currently selected tab will have the option to be closed, with a
721         * graphic next to the tab text being shown. The graphic will disappear when
722         * a tab is no longer selected.
723         */
724        SELECTED_TAB,
725
726        /**
727         * All tabs will have the option to be closed.
728         */
729        ALL_TABS,
730
731        /**
732         * Tabs can not be closed by the user.
733         */
734        UNAVAILABLE
735    }
736}