1 /*
   2  * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import java.util.Set;
  32 
  33 import com.sun.javafx.collections.UnmodifiableListSet;
  34 import com.sun.javafx.scene.control.TabObservableList;
  35 import javafx.beans.property.BooleanProperty;
  36 import javafx.beans.property.DoubleProperty;
  37 import javafx.beans.property.ObjectProperty;
  38 import javafx.beans.property.ObjectPropertyBase;
  39 import javafx.beans.property.SimpleBooleanProperty;
  40 import javafx.beans.property.SimpleObjectProperty;
  41 import javafx.beans.value.WritableValue;
  42 import javafx.collections.ListChangeListener;
  43 import javafx.collections.ObservableList;
  44 import javafx.geometry.Side;
  45 import javafx.scene.AccessibleAttribute;
  46 import javafx.scene.AccessibleRole;
  47 import javafx.css.StyleableDoubleProperty;
  48 import javafx.css.CssMetaData;
  49 import javafx.css.PseudoClass;
  50 
  51 import javafx.css.converter.SizeConverter;
  52 import javafx.scene.control.skin.TabPaneSkin;
  53 
  54 import javafx.beans.DefaultProperty;
  55 import javafx.css.Styleable;
  56 import javafx.css.StyleableProperty;
  57 import javafx.scene.Node;
  58 
  59 /**
  60  * <p>A control that allows switching between a group of {@link Tab Tabs}.  Only one tab
  61  * is visible at a time. Tabs are added to the TabPane by using the {@link #getTabs}.</p>
  62  *
  63  * <p>Tabs in a TabPane can be positioned at any of the four sides by specifying the
  64  * {@link Side}. </p>
  65  *
  66  * <p>A TabPane has two modes floating or recessed.  Applying the styleclass STYLE_CLASS_FLOATING
  67  * will change the TabPane mode to floating.</p>
  68  *
  69  * <p>The tabs width and height can be set to a specific size by
  70  * setting the min and max for height and width.  TabPane default width will be
  71  * determined by the largest content width in the TabPane.  This is the same for the height.
  72  * If a different size is desired the width and height of the TabPane can
  73  * be overridden by setting the min, pref and max size.</p>
  74  *
  75  * <p>When the number of tabs do not fit the TabPane a menu button will appear on the right.
  76  * The menu button is used to select the tabs that are currently not visible.
  77  * </p>
  78  *
  79  * <p>Example:</p>
  80  * <pre><code>
  81  * TabPane tabPane = new TabPane();
  82  * Tab tab = new Tab();
  83  * tab.setText("new tab");
  84  * tab.setContent(new Rectangle(200,200, Color.LIGHTSTEELBLUE));
  85  * tabPane.getTabs().add(tab);
  86  * </code></pre>
  87  *
  88  * @see Tab
  89  * @since JavaFX 2.0
  90  */
  91 @DefaultProperty("tabs")
  92 public class TabPane extends Control {
  93     private static final double DEFAULT_TAB_MIN_WIDTH = 0;
  94 
  95     private static final double DEFAULT_TAB_MAX_WIDTH = Double.MAX_VALUE;
  96 
  97     private static final double DEFAULT_TAB_MIN_HEIGHT = 0;
  98 
  99     private static final double DEFAULT_TAB_MAX_HEIGHT = Double.MAX_VALUE;
 100 
 101     /**
 102      * TabPane mode will be changed to floating allowing the TabPane
 103      * to be placed alongside other control.
 104      */
 105     public static final String STYLE_CLASS_FLOATING = "floating";
 106 
 107     /**
 108      * Constructs a new TabPane.
 109      */
 110     public TabPane() {
 111         this((Tab[])null);
 112     }
 113 
 114     /**
 115      * Constructs a new TabPane with the given tabs set to show.
 116      *
 117      * @param tabs The {@link Tab tabs} to display inside the TabPane.
 118      * @since JavaFX 8u40
 119      */
 120     public TabPane(Tab... tabs) {
 121         getStyleClass().setAll("tab-pane");
 122         setAccessibleRole(AccessibleRole.TAB_PANE);
 123         setSelectionModel(new TabPaneSelectionModel(this));
 124 
 125         this.tabs.addListener((ListChangeListener<Tab>) c -> {
 126             while (c.next()) {
 127                 for (Tab tab : c.getRemoved()) {
 128                     if (tab != null && !getTabs().contains(tab)) {
 129                         tab.setTabPane(null);
 130                     }
 131                 }
 132 
 133                 for (Tab tab : c.getAddedSubList()) {
 134                     if (tab != null) {
 135                         tab.setTabPane(TabPane.this);
 136                     }
 137                 }
 138             }
 139         });
 140 
 141         if (tabs != null) {
 142             getTabs().addAll(tabs);
 143         }
 144 
 145         // initialize pseudo-class state
 146         Side edge = getSide();
 147         pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (edge == Side.TOP));
 148         pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (edge == Side.RIGHT));
 149         pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (edge == Side.BOTTOM));
 150         pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (edge == Side.LEFT));
 151 
 152     }
 153 
 154     private ObservableList<Tab> tabs = new TabObservableList<>(new ArrayList<>());
 155 
 156     /**
 157      * <p>The tabs to display in this TabPane. Changing this ObservableList will
 158      * immediately result in the TabPane updating to display the new contents
 159      * of this ObservableList.</p>
 160      *
 161      * <p>If the tabs ObservableList changes, the selected tab will remain the previously
 162      * selected tab, if it remains within this ObservableList. If the previously
 163      * selected tab is no longer in the tabs ObservableList, the selected tab will
 164      * become the first tab in the ObservableList.</p>
 165      * @return the list of tabs
 166      */
 167     public final ObservableList<Tab> getTabs() {
 168         return tabs;
 169     }
 170 
 171     private ObjectProperty<SingleSelectionModel<Tab>> selectionModel = new SimpleObjectProperty<SingleSelectionModel<Tab>>(this, "selectionModel");
 172 
 173     /**
 174      * <p>Sets the model used for tab selection.  By changing the model you can alter
 175      * how the tabs are selected and which tabs are first or last.</p>
 176      * @param value the selection model
 177      */
 178     public final void setSelectionModel(SingleSelectionModel<Tab> value) { selectionModel.set(value); }
 179 
 180     /**
 181      * <p>Gets the model used for tab selection.</p>
 182      * @return the model used for tab selection
 183      */
 184     public final SingleSelectionModel<Tab> getSelectionModel() { return selectionModel.get(); }
 185 
 186     /**
 187      * The selection model used for selecting tabs.
 188      * @return selection model property
 189      */
 190     public final ObjectProperty<SingleSelectionModel<Tab>> selectionModelProperty() { return selectionModel; }
 191 
 192     private ObjectProperty<Side> side;
 193 
 194     /**
 195      * <p>The position to place the tabs in this TabPane. Whenever this changes
 196      * the TabPane will immediately update the location of the tabs to reflect
 197      * this.</p>
 198      *
 199      * @param value the side
 200      */
 201     public final void setSide(Side value) {
 202         sideProperty().set(value);
 203     }
 204 
 205     /**
 206      * The current position of the tabs in the TabPane.  The default position
 207      * for the tabs is Side.Top.
 208      *
 209      * @return The current position of the tabs in the TabPane.
 210      */
 211     public final Side getSide() {
 212         return side == null ? Side.TOP : side.get();
 213     }
 214 
 215     /**
 216      * The position of the tabs in the TabPane.
 217      * @return the side property
 218      */
 219     public final ObjectProperty<Side> sideProperty() {
 220         if (side == null) {
 221             side = new ObjectPropertyBase<Side>(Side.TOP) {
 222                 private Side oldSide;
 223                 @Override protected void invalidated() {
 224 
 225                     oldSide = get();
 226 
 227                     pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (oldSide == Side.TOP || oldSide == null));
 228                     pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (oldSide == Side.RIGHT));
 229                     pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (oldSide == Side.BOTTOM));
 230                     pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (oldSide == Side.LEFT));
 231                 }
 232 
 233                 @Override
 234                 public Object getBean() {
 235                     return TabPane.this;
 236                 }
 237 
 238                 @Override
 239                 public String getName() {
 240                     return "side";
 241                 }
 242             };
 243         }
 244         return side;
 245     }
 246 
 247     private ObjectProperty<TabClosingPolicy> tabClosingPolicy;
 248 
 249     /**
 250      * <p>Specifies how the TabPane handles tab closing from an end-users
 251      * perspective. The options are:</p>
 252      *
 253      * <ul>
 254      *   <li> TabClosingPolicy.UNAVAILABLE: Tabs can not be closed by the user
 255      *   <li> TabClosingPolicy.SELECTED_TAB: Only the currently selected tab will
 256      *          have the option to be closed, with a graphic next to the tab
 257      *          text being shown. The graphic will disappear when a tab is no
 258      *          longer selected.
 259      *   <li> TabClosingPolicy.ALL_TABS: All tabs will have the option to be
 260      *          closed.
 261      * </ul>
 262      *
 263      * <p>Refer to the {@link TabClosingPolicy} enumeration for further details.</p>
 264      *
 265      * The default closing policy is TabClosingPolicy.SELECTED_TAB
 266      * @param value the closing policy
 267      */
 268     public final void setTabClosingPolicy(TabClosingPolicy value) {
 269         tabClosingPolicyProperty().set(value);
 270     }
 271 
 272     /**
 273      * The closing policy for the tabs.
 274      *
 275      * @return The closing policy for the tabs.
 276      */
 277     public final TabClosingPolicy getTabClosingPolicy() {
 278         return tabClosingPolicy == null ? TabClosingPolicy.SELECTED_TAB : tabClosingPolicy.get();
 279     }
 280 
 281     /**
 282      * The closing policy for the tabs.
 283      * @return the closing policy property
 284      */
 285     public final ObjectProperty<TabClosingPolicy> tabClosingPolicyProperty() {
 286         if (tabClosingPolicy == null) {
 287             tabClosingPolicy = new SimpleObjectProperty<TabClosingPolicy>(this, "tabClosingPolicy", TabClosingPolicy.SELECTED_TAB);
 288         }
 289         return tabClosingPolicy;
 290     }
 291 
 292     private BooleanProperty rotateGraphic;
 293 
 294     /**
 295      * <p>Specifies whether the graphic inside a Tab is rotated or not, such
 296      * that it is always upright, or rotated in the same way as the Tab text is.</p>
 297      *
 298      * <p>By default rotateGraphic is set to false, to represent the fact that
 299      * the graphic isn't rotated, resulting in it always appearing upright. If
 300      * rotateGraphic is set to {@code true}, the graphic will rotate such that it
 301      * rotates with the tab text.</p>
 302      *
 303      * @param value a flag indicating whether to rotate the graphic
 304      */
 305     public final void setRotateGraphic(boolean value) {
 306         rotateGraphicProperty().set(value);
 307     }
 308 
 309     /**
 310      * Returns {@code true} if the graphic inside a Tab is rotated. The
 311      * default is {@code false}
 312      *
 313      * @return the rotatedGraphic state.
 314      */
 315     public final boolean isRotateGraphic() {
 316         return rotateGraphic == null ? false : rotateGraphic.get();
 317     }
 318 
 319     /**
 320      * The rotateGraphic state of the tabs in the TabPane.
 321      * @return the rotateGraphic property
 322      */
 323     public final BooleanProperty rotateGraphicProperty() {
 324         if (rotateGraphic == null) {
 325             rotateGraphic = new SimpleBooleanProperty(this, "rotateGraphic", false);
 326         }
 327         return rotateGraphic;
 328     }
 329 
 330     private DoubleProperty tabMinWidth;
 331 
 332     /**
 333      * <p>The minimum width of the tabs in the TabPane.  This can be used to limit
 334      * the length of text in tabs to prevent truncation.  Setting the min equal
 335      * to the max will fix the width of the tab.  By default the min equals to the max.
 336      *
 337      * This value can also be set via CSS using {@code -fx-tab-min-width}
 338      *
 339      * </p>
 340      * @param value the minimum width of the tabs
 341      */
 342     public final void setTabMinWidth(double value) {
 343         tabMinWidthProperty().setValue(value);
 344     }
 345 
 346     /**
 347      * The minimum width of the tabs in the TabPane.
 348      *
 349      * @return The minimum width of the tabs
 350      */
 351     public final double getTabMinWidth() {
 352         return tabMinWidth == null ? DEFAULT_TAB_MIN_WIDTH : tabMinWidth.getValue();
 353     }
 354 
 355     /**
 356      * The minimum width of the tabs in the TabPane.
 357      * @return the minimum width property
 358      */
 359     public final DoubleProperty tabMinWidthProperty() {
 360         if (tabMinWidth == null) {
 361             tabMinWidth = new StyleableDoubleProperty(DEFAULT_TAB_MIN_WIDTH) {
 362 
 363                 @Override
 364                 public CssMetaData<TabPane,Number> getCssMetaData() {
 365                     return StyleableProperties.TAB_MIN_WIDTH;
 366                 }
 367 
 368                 @Override
 369                 public Object getBean() {
 370                     return TabPane.this;
 371                 }
 372 
 373                 @Override
 374                 public String getName() {
 375                     return "tabMinWidth";
 376                 }
 377             };
 378         }
 379         return tabMinWidth;
 380     }
 381 
 382     /**
 383      * <p>Specifies the maximum width of a tab.  This can be used to limit
 384      * the length of text in tabs.  If the tab text is longer than the maximum
 385      * width the text will be truncated.  Setting the max equal
 386      * to the min will fix the width of the tab.  By default the min equals to the max
 387      *
 388      * This value can also be set via CSS using {@code -fx-tab-max-width}.</p>
 389      */
 390     private DoubleProperty tabMaxWidth;
 391     public final void setTabMaxWidth(double value) {
 392         tabMaxWidthProperty().setValue(value);
 393     }
 394 
 395     /**
 396      * The maximum width of the tabs in the TabPane.
 397      *
 398      * @return The maximum width of the tabs
 399      */
 400     public final double getTabMaxWidth() {
 401         return tabMaxWidth == null ? DEFAULT_TAB_MAX_WIDTH : tabMaxWidth.getValue();
 402     }
 403 
 404     /**
 405      * The maximum width of the tabs in the TabPane.
 406      * @return the maximum width property
 407      */
 408     public final DoubleProperty tabMaxWidthProperty() {
 409         if (tabMaxWidth == null) {
 410             tabMaxWidth = new StyleableDoubleProperty(DEFAULT_TAB_MAX_WIDTH) {
 411 
 412                 @Override
 413                 public CssMetaData<TabPane,Number> getCssMetaData() {
 414                     return StyleableProperties.TAB_MAX_WIDTH;
 415                 }
 416 
 417                 @Override
 418                 public Object getBean() {
 419                     return TabPane.this;
 420                 }
 421 
 422                 @Override
 423                 public String getName() {
 424                     return "tabMaxWidth";
 425                 }
 426             };
 427         }
 428         return tabMaxWidth;
 429     }
 430 
 431     private DoubleProperty tabMinHeight;
 432 
 433     /**
 434      * <p>The minimum height of the tabs in the TabPane.  This can be used to limit
 435      * the height in tabs. Setting the min equal to the max will fix the height
 436      * of the tab.  By default the min equals to the max.
 437      *
 438      * This value can also be set via CSS using {@code -fx-tab-min-height}
 439      * </p>
 440      * @param value the minimum height of the tabs
 441      */
 442     public final void setTabMinHeight(double value) {
 443         tabMinHeightProperty().setValue(value);
 444     }
 445 
 446     /**
 447      * The minimum height of the tabs in the TabPane.
 448      *
 449      * @return the minimum height of the tabs
 450      */
 451     public final double getTabMinHeight() {
 452         return tabMinHeight == null ? DEFAULT_TAB_MIN_HEIGHT : tabMinHeight.getValue();
 453     }
 454 
 455     /**
 456      * The minimum height of the tab.
 457      * @return the minimum height property
 458      */
 459     public final DoubleProperty tabMinHeightProperty() {
 460         if (tabMinHeight == null) {
 461             tabMinHeight = new StyleableDoubleProperty(DEFAULT_TAB_MIN_HEIGHT) {
 462 
 463                 @Override
 464                 public CssMetaData<TabPane,Number> getCssMetaData() {
 465                     return StyleableProperties.TAB_MIN_HEIGHT;
 466                 }
 467 
 468                 @Override
 469                 public Object getBean() {
 470                     return TabPane.this;
 471                 }
 472 
 473                 @Override
 474                 public String getName() {
 475                     return "tabMinHeight";
 476                 }
 477             };
 478         }
 479         return tabMinHeight;
 480     }
 481 
 482     /**
 483      * <p>The maximum height if the tabs in the TabPane.  This can be used to limit
 484      * the height in tabs. Setting the max equal to the min will fix the height
 485      * of the tab.  By default the min equals to the max.
 486      *
 487      * This value can also be set via CSS using -fx-tab-max-height
 488      * </p>
 489      */
 490     private DoubleProperty tabMaxHeight;
 491     public final void setTabMaxHeight(double value) {
 492         tabMaxHeightProperty().setValue(value);
 493     }
 494 
 495     /**
 496      * The maximum height of the tabs in the TabPane.
 497      *
 498      * @return The maximum height of the tabs
 499      */
 500     public final double getTabMaxHeight() {
 501         return tabMaxHeight == null ? DEFAULT_TAB_MAX_HEIGHT : tabMaxHeight.getValue();
 502     }
 503 
 504     /**
 505      * <p>The maximum height of the tabs in the TabPane.</p>
 506      * @return the maximum height of the tabs
 507      */
 508     public final DoubleProperty tabMaxHeightProperty() {
 509         if (tabMaxHeight == null) {
 510             tabMaxHeight = new StyleableDoubleProperty(DEFAULT_TAB_MAX_HEIGHT) {
 511 
 512                 @Override
 513                 public CssMetaData<TabPane,Number> getCssMetaData() {
 514                     return StyleableProperties.TAB_MAX_HEIGHT;
 515                 }
 516 
 517                 @Override
 518                 public Object getBean() {
 519                     return TabPane.this;
 520                 }
 521 
 522                 @Override
 523                 public String getName() {
 524                     return "tabMaxHeight";
 525                 }
 526             };
 527         }
 528         return tabMaxHeight;
 529     }
 530 
 531     /** {@inheritDoc} */
 532     @Override protected Skin<?> createDefaultSkin() {
 533         return new TabPaneSkin(this);
 534     }
 535 
 536     /** {@inheritDoc} */
 537     @Override public Node lookup(String selector) {
 538         Node n = super.lookup(selector);
 539         if (n == null) {
 540             for(Tab tab : tabs) {
 541                 n = tab.lookup(selector);
 542                 if (n != null) break;
 543             }
 544         }
 545         return n;
 546     }
 547 
 548     /** {@inheritDoc} */
 549     public Set<Node> lookupAll(String selector) {
 550 
 551         if (selector == null) return null;
 552 
 553         final List<Node> results = new ArrayList<>();
 554 
 555         results.addAll(super.lookupAll(selector));
 556         for(Tab tab : tabs) {
 557             results.addAll(tab.lookupAll(selector));
 558         }
 559 
 560         return new UnmodifiableListSet<Node>(results);
 561     }
 562 
 563 
 564     /***************************************************************************
 565      *                                                                         *
 566      *                         Stylesheet Handling                             *
 567      *                                                                         *
 568      **************************************************************************/
 569 
 570     private static class StyleableProperties {
 571         private static final CssMetaData<TabPane,Number> TAB_MIN_WIDTH =
 572                 new CssMetaData<TabPane,Number>("-fx-tab-min-width",
 573                 SizeConverter.getInstance(), DEFAULT_TAB_MIN_WIDTH) {
 574 
 575             @Override
 576             public boolean isSettable(TabPane n) {
 577                 return n.tabMinWidth == null || !n.tabMinWidth.isBound();
 578             }
 579 
 580             @Override
 581             public StyleableProperty<Number> getStyleableProperty(TabPane n) {
 582                 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMinWidthProperty();
 583             }
 584         };
 585 
 586         private static final CssMetaData<TabPane,Number> TAB_MAX_WIDTH =
 587                 new CssMetaData<TabPane,Number>("-fx-tab-max-width",
 588                 SizeConverter.getInstance(), DEFAULT_TAB_MAX_WIDTH) {
 589 
 590             @Override
 591             public boolean isSettable(TabPane n) {
 592                 return n.tabMaxWidth == null || !n.tabMaxWidth.isBound();
 593             }
 594 
 595             @Override
 596             public StyleableProperty<Number> getStyleableProperty(TabPane n) {
 597                 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMaxWidthProperty();
 598             }
 599         };
 600 
 601         private static final CssMetaData<TabPane,Number> TAB_MIN_HEIGHT =
 602                 new CssMetaData<TabPane,Number>("-fx-tab-min-height",
 603                 SizeConverter.getInstance(), DEFAULT_TAB_MIN_HEIGHT) {
 604 
 605             @Override
 606             public boolean isSettable(TabPane n) {
 607                 return n.tabMinHeight == null || !n.tabMinHeight.isBound();
 608             }
 609 
 610             @Override
 611             public StyleableProperty<Number> getStyleableProperty(TabPane n) {
 612                 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMinHeightProperty();
 613             }
 614         };
 615 
 616         private static final CssMetaData<TabPane,Number> TAB_MAX_HEIGHT =
 617                 new CssMetaData<TabPane,Number>("-fx-tab-max-height",
 618                 SizeConverter.getInstance(), DEFAULT_TAB_MAX_HEIGHT) {
 619 
 620             @Override
 621             public boolean isSettable(TabPane n) {
 622                 return n.tabMaxHeight == null || !n.tabMaxHeight.isBound();
 623             }
 624 
 625             @Override
 626             public StyleableProperty<Number> getStyleableProperty(TabPane n) {
 627                 return (StyleableProperty<Number>)(WritableValue<Number>)n.tabMaxHeightProperty();
 628             }
 629         };
 630 
 631         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 632         static {
 633             final List<CssMetaData<? extends Styleable, ?>> styleables =
 634                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
 635             styleables.add(TAB_MIN_WIDTH);
 636             styleables.add(TAB_MAX_WIDTH);
 637             styleables.add(TAB_MIN_HEIGHT);
 638             styleables.add(TAB_MAX_HEIGHT);
 639             STYLEABLES = Collections.unmodifiableList(styleables);
 640         }
 641     }
 642 
 643     /**
 644      * @return The CssMetaData associated with this class, which may include the
 645      * CssMetaData of its superclasses.
 646      * @since JavaFX 8.0
 647      */
 648     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 649         return StyleableProperties.STYLEABLES;
 650     }
 651 
 652     /**
 653      * {@inheritDoc}
 654      * @since JavaFX 8.0
 655      */
 656     @Override
 657     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
 658         return getClassCssMetaData();
 659     }
 660 
 661     private static final PseudoClass TOP_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("top");
 662     private static final PseudoClass BOTTOM_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("bottom");
 663     private static final PseudoClass LEFT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("left");
 664     private static final PseudoClass RIGHT_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("right");
 665 
 666     /***************************************************************************
 667      *                                                                         *
 668      * Support classes                                                         *
 669      *                                                                         *
 670      **************************************************************************/
 671 
 672     static class TabPaneSelectionModel extends SingleSelectionModel<Tab> {
 673         private final TabPane tabPane;
 674 
 675         public TabPaneSelectionModel(final TabPane t) {
 676             if (t == null) {
 677                 throw new NullPointerException("TabPane can not be null");
 678             }
 679             this.tabPane = t;
 680 
 681             // watching for changes to the items list content
 682             final ListChangeListener<Tab> itemsContentObserver = c -> {
 683                 while (c.next()) {
 684                     for (Tab tab : c.getRemoved()) {
 685                         if (tab != null && !tabPane.getTabs().contains(tab)) {
 686                             if (tab.isSelected()) {
 687                                 tab.setSelected(false);
 688                                 final int tabIndex = c.getFrom();
 689 
 690                                 // we always try to select the nearest, non-disabled
 691                                 // tab from the position of the closed tab.
 692                                 findNearestAvailableTab(tabIndex, true);
 693                             }
 694                         }
 695                     }
 696                     if (c.wasAdded() || c.wasRemoved()) {
 697                         // The selected tab index can be out of sync with the list of tab if
 698                         // we add or remove tabs before the selected tab.
 699                         if (getSelectedIndex() != tabPane.getTabs().indexOf(getSelectedItem())) {
 700                             clearAndSelect(tabPane.getTabs().indexOf(getSelectedItem()));
 701                         }
 702                     }
 703                 }
 704                 if (getSelectedIndex() == -1 && getSelectedItem() == null && tabPane.getTabs().size() > 0) {
 705                     // we go looking for the first non-disabled tab, as opposed to
 706                     // just selecting the first tab (fix for RT-36908)
 707                     findNearestAvailableTab(0, true);
 708                 } else if (tabPane.getTabs().isEmpty()) {
 709                     clearSelection();
 710                 }
 711             };
 712             if (this.tabPane.getTabs() != null) {
 713                 this.tabPane.getTabs().addListener(itemsContentObserver);
 714             }
 715         }
 716 
 717         // API Implementation
 718         @Override public void select(int index) {
 719             if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) ||
 720                 (index == getSelectedIndex() && getModelItem(index).isSelected())) {
 721                 return;
 722             }
 723 
 724             // Unselect the old tab
 725             if (getSelectedIndex() >= 0 && getSelectedIndex() < tabPane.getTabs().size()) {
 726                 tabPane.getTabs().get(getSelectedIndex()).setSelected(false);
 727             }
 728 
 729             setSelectedIndex(index);
 730 
 731             Tab tab = getModelItem(index);
 732             if (tab != null) {
 733                 setSelectedItem(tab);
 734             }
 735 
 736             // Select the new tab
 737             if (getSelectedIndex() >= 0 && getSelectedIndex() < tabPane.getTabs().size()) {
 738                 tabPane.getTabs().get(getSelectedIndex()).setSelected(true);
 739             }
 740 
 741             /* Does this get all the change events */
 742             tabPane.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
 743         }
 744 
 745         @Override public void select(Tab tab) {
 746             final int itemCount = getItemCount();
 747 
 748             for (int i = 0; i < itemCount; i++) {
 749                 final Tab value = getModelItem(i);
 750                 if (value != null && value.equals(tab)) {
 751                     select(i);
 752                     return;
 753                 }
 754             }
 755             if (tab != null) {
 756                 setSelectedItem(tab);
 757             }
 758         }
 759 
 760         @Override protected Tab getModelItem(int index) {
 761             final ObservableList<Tab> items = tabPane.getTabs();
 762             if (items == null) return null;
 763             if (index < 0 || index >= items.size()) return null;
 764             return items.get(index);
 765         }
 766 
 767         @Override protected int getItemCount() {
 768             final ObservableList<Tab> items = tabPane.getTabs();
 769             return items == null ? 0 : items.size();
 770         }
 771 
 772         private Tab findNearestAvailableTab(int tabIndex, boolean doSelect) {
 773             // we always try to select the nearest, non-disabled
 774             // tab from the position of the closed tab.
 775             final int tabCount = getItemCount();
 776             int i = 1;
 777             Tab bestTab = null;
 778             while (true) {
 779                 // look leftwards
 780                 int downPos = tabIndex - i;
 781                 if (downPos >= 0) {
 782                     Tab _tab = getModelItem(downPos);
 783                     if (_tab != null && ! _tab.isDisable()) {
 784                         bestTab = _tab;
 785                         break;
 786                     }
 787                 }
 788 
 789                 // look rightwards. We subtract one as we need
 790                 // to take into account that a tab has been removed
 791                 // and if we don't do this we'll miss the tab
 792                 // to the right of the tab (as it has moved into
 793                 // the removed tabs position).
 794                 int upPos = tabIndex + i - 1;
 795                 if (upPos < tabCount) {
 796                     Tab _tab = getModelItem(upPos);
 797                     if (_tab != null && ! _tab.isDisable()) {
 798                         bestTab = _tab;
 799                         break;
 800                     }
 801                 }
 802 
 803                 if (downPos < 0 && upPos >= tabCount) {
 804                     break;
 805                 }
 806                 i++;
 807             }
 808 
 809             if (doSelect && bestTab != null) {
 810                 select(bestTab);
 811             }
 812 
 813             return bestTab;
 814         }
 815     }
 816 
 817     /**
 818      * <p>This specifies how the TabPane handles tab closing from an end-users
 819      * perspective. The options are:</p>
 820      *
 821      * <ul>
 822      *   <li> TabClosingPolicy.UNAVAILABLE: Tabs can not be closed by the user
 823      *   <li> TabClosingPolicy.SELECTED_TAB: Only the currently selected tab will
 824      *          have the option to be closed, with a graphic next to the tab
 825      *          text being shown. The graphic will disappear when a tab is no
 826      *          longer selected.
 827      *   <li> TabClosingPolicy.ALL_TABS: All tabs will have the option to be
 828      *          closed.
 829      * </ul>
 830      * @since JavaFX 2.0
 831      */
 832     public enum TabClosingPolicy {
 833 
 834         /**
 835          * Only the currently selected tab will have the option to be closed, with a
 836          * graphic next to the tab text being shown. The graphic will disappear when
 837          * a tab is no longer selected.
 838          */
 839         SELECTED_TAB,
 840 
 841         /**
 842          * All tabs will have the option to be closed.
 843          */
 844         ALL_TABS,
 845 
 846         /**
 847          * Tabs can not be closed by the user.
 848          */
 849         UNAVAILABLE
 850     }
 851 
 852 
 853     // TabDragPolicy //
 854     private ObjectProperty<TabDragPolicy> tabDragPolicy;
 855 
 856     /**
 857      * The drag policy for the tabs. The policy can be changed dynamically.
 858      *
 859      * @defaultValue TabDragPolicy.FIXED
 860      * @return The tab drag policy property
 861      * @since 10
 862      */
 863     public final ObjectProperty<TabDragPolicy> tabDragPolicyProperty() {
 864         if (tabDragPolicy == null) {
 865             tabDragPolicy = new SimpleObjectProperty<TabDragPolicy>(this, "tabDragPolicy", TabDragPolicy.FIXED);
 866         }
 867         return tabDragPolicy;
 868     }
 869     public final void setTabDragPolicy(TabDragPolicy value) {
 870         tabDragPolicyProperty().set(value);
 871     }
 872     public final TabDragPolicy getTabDragPolicy() {
 873         return tabDragPolicyProperty().get();
 874     }
 875 
 876     /**
 877      * This enum specifies drag policies for tabs in a TabPane.
 878      *
 879      * @since 10
 880      */
 881     public enum TabDragPolicy {
 882         /**
 883          * The tabs remain fixed in their positions and cannot be dragged.
 884          */
 885         FIXED,
 886 
 887         /**
 888          * The tabs can be dragged to reorder them within the same TabPane.
 889          * Users can perform the simple mouse press-drag-release gesture on a
 890          * tab header to drag it to a new position. A tab can not be detached
 891          * from its parent TabPane.
 892          * <p>After a tab is reordered, the {@link #getTabs() tabs} list is
 893          * permuted to reflect the updated order.
 894          * A {@link javafx.collections.ListChangeListener.Change permutation
 895          * change} event is fired to indicate which tabs were reordered. This
 896          * reordering is done after the mouse button is released. While a tab
 897          * is being dragged, the list of tabs is unchanged.</p>
 898          */
 899         REORDER
 900     }
 901 }