modules/controls/src/main/java/javafx/scene/control/skin/TabPaneSkin.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


   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 com.sun.javafx.scene.control.skin;
  27 


  28 import com.sun.javafx.util.Utils;
  29 import javafx.animation.Animation;
  30 import javafx.animation.Interpolator;
  31 import javafx.animation.KeyFrame;
  32 import javafx.animation.KeyValue;
  33 import javafx.animation.Timeline;
  34 import javafx.beans.InvalidationListener;
  35 import javafx.beans.Observable;
  36 import javafx.beans.WeakInvalidationListener;
  37 import javafx.beans.property.DoubleProperty;
  38 import javafx.beans.property.ObjectProperty;
  39 import javafx.beans.property.SimpleDoubleProperty;
  40 import javafx.beans.value.WritableValue;
  41 import javafx.collections.FXCollections;
  42 import javafx.collections.ListChangeListener;
  43 import javafx.collections.ObservableList;
  44 import javafx.collections.WeakListChangeListener;
  45 import javafx.css.CssMetaData;
  46 import javafx.css.PseudoClass;
  47 import javafx.css.Styleable;
  48 import javafx.css.StyleableObjectProperty;
  49 import javafx.css.StyleableProperty;
  50 import javafx.event.ActionEvent;
  51 import javafx.event.EventHandler;
  52 import javafx.geometry.HPos;
  53 import javafx.geometry.Pos;
  54 import javafx.geometry.Side;
  55 import javafx.geometry.VPos;
  56 import javafx.scene.AccessibleAction;
  57 import javafx.scene.AccessibleAttribute;
  58 import javafx.scene.AccessibleRole;
  59 import javafx.scene.Node;
  60 import javafx.scene.control.ContextMenu;

  61 import javafx.scene.control.Label;
  62 import javafx.scene.control.MenuItem;
  63 import javafx.scene.control.RadioMenuItem;
  64 import javafx.scene.control.SkinBase;
  65 import javafx.scene.control.Tab;
  66 import javafx.scene.control.TabPane;
  67 import javafx.scene.control.TabPane.TabClosingPolicy;
  68 import javafx.scene.control.ToggleGroup;
  69 import javafx.scene.control.Tooltip;
  70 import javafx.scene.effect.DropShadow;
  71 import javafx.scene.image.ImageView;
  72 import javafx.scene.input.ContextMenuEvent;
  73 import javafx.scene.input.MouseButton;
  74 import javafx.scene.input.MouseEvent;
  75 import javafx.scene.input.ScrollEvent;
  76 import javafx.scene.input.SwipeEvent;
  77 import javafx.scene.layout.Pane;
  78 import javafx.scene.layout.Region;
  79 import javafx.scene.layout.StackPane;
  80 import javafx.scene.shape.Rectangle;
  81 import javafx.scene.transform.Rotate;
  82 import javafx.util.Duration;
  83 
  84 import java.util.ArrayList;
  85 import java.util.Collections;
  86 import java.util.Iterator;
  87 import java.util.List;
  88 
  89 import com.sun.javafx.css.converters.EnumConverter;
  90 import com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler;
  91 import com.sun.javafx.scene.control.behavior.TabPaneBehavior;
  92 import com.sun.javafx.scene.traversal.Direction;
  93 import com.sun.javafx.scene.traversal.TraversalEngine;
  94 
  95 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
  96 
  97 public class TabPaneSkin extends BehaviorSkinBase<TabPane, TabPaneBehavior> {
  98     private static enum TabAnimation {













  99         NONE,
 100         GROW
 101         // In future we could add FADE, ...
 102     }
 103 
 104     private enum TabAnimationState {
 105         SHOWING, HIDING, NONE;
 106     }
 107     
 108     private ObjectProperty<TabAnimation> openTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
 109         @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
 110             return StyleableProperties.OPEN_TAB_ANIMATION;
 111         }
 112         
 113         @Override public Object getBean() {
 114             return TabPaneSkin.this;
 115         }
 116 
 117         @Override public String getName() {
 118             return "openTabAnimation";
 119         }
 120     };

 121     
 122     private ObjectProperty<TabAnimation> closeTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
 123         @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
 124             return StyleableProperties.CLOSE_TAB_ANIMATION;
 125         }
 126 
 127         @Override public Object getBean() {
 128             return TabPaneSkin.this;
 129         }
 130 
 131         @Override public String getName() {
 132             return "closeTabAnimation";
 133         }
 134     };
 135     
 136     private static int getRotation(Side pos) {
 137         switch (pos) {
 138             case TOP:
 139                 return 0;
 140             case BOTTOM:
 141                 return 180;
 142             case LEFT:
 143                 return -90;
 144             case RIGHT:
 145                 return 90;
 146             default:
 147                 return 0;
 148         }
 149     }
 150 
 151     /**
 152      * VERY HACKY - this lets us 'duplicate' Label and ImageView nodes to be used in a
 153      * Tab and the tabs menu at the same time.
 154      */
 155     private static Node clone(Node n) {
 156         if (n == null) {
 157             return null;
 158         }
 159         if (n instanceof ImageView) {
 160             ImageView iv = (ImageView) n;
 161             ImageView imageview = new ImageView();
 162             imageview.setImage(iv.getImage());
 163             return imageview;
 164         }
 165         if (n instanceof Label) {            
 166             Label l = (Label)n;
 167             Label label = new Label(l.getText(), l.getGraphic());
 168             return label;
 169         }
 170         return null;
 171     }
 172     private static final double ANIMATION_SPEED = 150;
 173     private static final int SPACER = 10;
 174 
 175     private TabHeaderArea tabHeaderArea;
 176     private ObservableList<TabContentRegion> tabContentRegions;
 177     private Rectangle clipRect;
 178     private Rectangle tabHeaderAreaClipRect;
 179     private Tab selectedTab;
 180     private boolean isSelectingTab;






 181 
 182     public TabPaneSkin(TabPane tabPane) {
 183         super(tabPane, new TabPaneBehavior(tabPane));













 184 
 185         clipRect = new Rectangle(tabPane.getWidth(), tabPane.getHeight());




 186         getSkinnable().setClip(clipRect);
 187 
 188         tabContentRegions = FXCollections.<TabContentRegion>observableArrayList();
 189 
 190         for (Tab tab : getSkinnable().getTabs()) {
 191             addTabContent(tab);
 192         }
 193 
 194         tabHeaderAreaClipRect = new Rectangle();
 195         tabHeaderArea = new TabHeaderArea();
 196         tabHeaderArea.setClip(tabHeaderAreaClipRect);
 197         getChildren().add(tabHeaderArea);
 198         if (getSkinnable().getTabs().size() == 0) {
 199             tabHeaderArea.setVisible(false);
 200         }
 201 
 202         initializeTabListener();
 203 
 204         registerChangeListener(tabPane.getSelectionModel().selectedItemProperty(), "SELECTED_TAB");
 205         registerChangeListener(tabPane.sideProperty(), "SIDE");
 206         registerChangeListener(tabPane.widthProperty(), "WIDTH");
 207         registerChangeListener(tabPane.heightProperty(), "HEIGHT");




 208 
 209         selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
 210         // Could not find the selected tab try and get the selected tab using the selected index
 211         if (selectedTab == null && getSkinnable().getSelectionModel().getSelectedIndex() != -1) {
 212             getSkinnable().getSelectionModel().select(getSkinnable().getSelectionModel().getSelectedIndex());
 213             selectedTab = getSkinnable().getSelectionModel().getSelectedItem();        
 214         } 
 215         if (selectedTab == null) {
 216             // getSelectedItem and getSelectedIndex failed select the first.
 217             getSkinnable().getSelectionModel().selectFirst();
 218         } 
 219         selectedTab = getSkinnable().getSelectionModel().getSelectedItem();        
 220         isSelectingTab = false;
 221 
 222         initializeSwipeHandlers();
 223     }
 224 
 225     public StackPane getSelectedTabContentRegion() {
 226         for (TabContentRegion contentRegion : tabContentRegions) {
 227             if (contentRegion.getTab().equals(selectedTab)) {
 228                 return contentRegion;































































































































































































































 229             }




 230         }
 231         return null;
 232     }
 233 
 234     @Override protected void handleControlPropertyChanged(String property) {
 235         super.handleControlPropertyChanged(property);
 236         if ("SELECTED_TAB".equals(property)) {            
 237             isSelectingTab = true;
 238             selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
 239             getSkinnable().requestLayout();
 240         } else if ("SIDE".equals(property)) {
 241             updateTabPosition();
 242         } else if ("WIDTH".equals(property)) {
 243             clipRect.setWidth(getSkinnable().getWidth());
 244         } else if ("HEIGHT".equals(property)) {
 245             clipRect.setHeight(getSkinnable().getHeight());
 246         }
 247     }
 248     private void removeTabs(List<? extends Tab> removedList) {
 249         for (final Tab tab : removedList) {
 250             stopCurrentAnimation(tab);
 251             // Animate the tab removal
 252             final TabHeaderSkin tabRegion = tabHeaderArea.getTabHeaderSkin(tab);
 253             if (tabRegion != null) {
 254                 tabRegion.isClosing = true;
 255 
 256                 tabRegion.removeListeners(tab);
 257                 removeTabContent(tab);
 258 
 259                 // remove the menu item from the popup menu
 260                 ContextMenu popupMenu = tabHeaderArea.controlButtons.popup;
 261                 TabMenuItem tabItem = null;
 262                 if (popupMenu != null) {
 263                     for (MenuItem item : popupMenu.getItems()) {
 264                         tabItem = (TabMenuItem) item;
 265                         if (tab == tabItem.getTab()) {
 266                             break;
 267                         }


 386                     openTabAnimation.set(prevOpenAnimation);
 387                     closeTabAnimation.set(prevCloseAnimation);
 388                     getSkinnable().getSelectionModel().select(selTab);
 389                 }
 390 
 391                 if (c.wasRemoved()) {
 392                     tabsToRemove.addAll(c.getRemoved());
 393                 }
 394 
 395                 if (c.wasAdded()) {
 396                     tabsToAdd.addAll(c.getAddedSubList());
 397                     insertPos = c.getFrom();
 398                 }
 399             }
 400 
 401             // now only remove the tabs that are not in the tabsToAdd list
 402             tabsToRemove.removeAll(tabsToAdd);
 403             removeTabs(tabsToRemove);
 404 
 405             // and add in any new tabs (that we don't already have showing)
 406             if (! tabsToAdd.isEmpty()) {
 407                 for (TabContentRegion tabContentRegion : tabContentRegions) {
 408                     Tab tab = tabContentRegion.getTab();
 409                     TabHeaderSkin tabHeader = tabHeaderArea.getTabHeaderSkin(tab);
 410                     if (!tabHeader.isClosing && tabsToAdd.contains(tabContentRegion.getTab())) {
 411                         tabsToAdd.remove(tabContentRegion.getTab());
 412                     }
 413                 }
 414 
 415                 addTabs(tabsToAdd, insertPos == -1 ? tabContentRegions.size() : insertPos);
 416             }
 417 
 418             // Fix for RT-34692
 419             getSkinnable().requestLayout();
 420         });
 421     }
 422 
 423     private void addTabContent(Tab tab) {
 424         TabContentRegion tabContentRegion = new TabContentRegion(tab);
 425         tabContentRegion.setClip(new Rectangle());
 426         tabContentRegions.add(tabContentRegion);


 446     }
 447 
 448     private Timeline createTimeline(final TabHeaderSkin tabRegion, final Duration duration, final double endValue, final EventHandler<ActionEvent> func) {
 449         Timeline timeline = new Timeline();
 450         timeline.setCycleCount(1);
 451 
 452         KeyValue keyValue = new KeyValue(tabRegion.animationTransition, endValue, Interpolator.LINEAR);
 453         timeline.getKeyFrames().clear();
 454         timeline.getKeyFrames().add(new KeyFrame(duration, keyValue));
 455 
 456         timeline.setOnFinished(func);
 457         return timeline;
 458     }
 459 
 460     private boolean isHorizontal() {
 461         Side tabPosition = getSkinnable().getSide();
 462         return Side.TOP.equals(tabPosition) || Side.BOTTOM.equals(tabPosition);
 463     }
 464 
 465     private void initializeSwipeHandlers() {
 466         if (IS_TOUCH_SUPPORTED) {
 467             getSkinnable().addEventHandler(SwipeEvent.SWIPE_LEFT, t -> {
 468                 getBehavior().selectNextTab();
 469             });
 470 
 471             getSkinnable().addEventHandler(SwipeEvent.SWIPE_RIGHT, t -> {
 472                 getBehavior().selectPreviousTab();
 473             });
 474         }    
 475     }
 476     
 477     //TODO need to cache this.
 478     private boolean isFloatingStyleClass() {
 479         return getSkinnable().getStyleClass().contains(TabPane.STYLE_CLASS_FLOATING);
 480     }
 481 
 482     private double maxw = 0.0d;
 483     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 484         // The TabPane can only be as wide as it widest content width.
 485         for (TabContentRegion contentRegion: tabContentRegions) {
 486              maxw = Math.max(maxw, snapSize(contentRegion.prefWidth(-1)));
 487         }
 488 
 489         final boolean isHorizontal = isHorizontal();
 490         final double tabHeaderAreaSize = snapSize(isHorizontal ?
 491                 tabHeaderArea.prefWidth(-1) : tabHeaderArea.prefHeight(-1));
 492 
 493         double prefWidth = isHorizontal ?
 494             Math.max(maxw, tabHeaderAreaSize) : maxw + tabHeaderAreaSize;
 495         return snapSize(prefWidth) + rightInset + leftInset;
 496     }
 497 
 498     private double maxh = 0.0d;
 499     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 500         // The TabPane can only be as high as it highest content height.
 501         for (TabContentRegion contentRegion: tabContentRegions) {
 502              maxh = Math.max(maxh, snapSize(contentRegion.prefHeight(-1)));
 503         }
 504 
 505         final boolean isHorizontal = isHorizontal();
 506         final double tabHeaderAreaSize = snapSize(isHorizontal ?
 507                 tabHeaderArea.prefHeight(-1) : tabHeaderArea.prefWidth(-1));
 508 
 509         double prefHeight = isHorizontal ?
 510              maxh + snapSize(tabHeaderAreaSize) : Math.max(maxh, tabHeaderAreaSize);
 511         return snapSize(prefHeight) + topInset + bottomInset;
 512     }
 513 
 514     @Override public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
 515         Side tabPosition = getSkinnable().getSide();
 516         if (tabPosition == Side.TOP) {
 517             return tabHeaderArea.getBaselineOffset() + topInset;
 518         }
 519         return 0;
 520     }
 521 
 522     @Override protected void layoutChildren(final double x, final double y,
 523             final double w, final double h) {
 524         TabPane tabPane = getSkinnable();
 525         Side tabPosition = tabPane.getSide();
 526 
 527         double headerHeight = snapSize(tabHeaderArea.prefHeight(-1));
 528         double tabsStartX = tabPosition.equals(Side.RIGHT)? x + w - headerHeight : x;
 529         double tabsStartY = tabPosition.equals(Side.BOTTOM)? y + h - headerHeight : y;
 530 
 531         if (tabPosition == Side.TOP) {
 532             tabHeaderArea.resize(w, headerHeight);
 533             tabHeaderArea.relocate(tabsStartX, tabsStartY);
 534             tabHeaderArea.getTransforms().clear();
 535             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.TOP)));
 536         } else if (tabPosition == Side.BOTTOM) {
 537             tabHeaderArea.resize(w, headerHeight);
 538             tabHeaderArea.relocate(w, tabsStartY - headerHeight);
 539             tabHeaderArea.getTransforms().clear();
 540             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.BOTTOM), 0, headerHeight));
 541         } else if (tabPosition == Side.LEFT) {
 542             tabHeaderArea.resize(h, headerHeight);
 543             tabHeaderArea.relocate(tabsStartX + headerHeight, h - headerHeight);
 544             tabHeaderArea.getTransforms().clear();
 545             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.LEFT), 0, headerHeight));
 546         } else if (tabPosition == Side.RIGHT) {
 547             tabHeaderArea.resize(h, headerHeight);
 548             tabHeaderArea.relocate(tabsStartX, y - headerHeight);
 549             tabHeaderArea.getTransforms().clear();
 550             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.RIGHT), 0, headerHeight));
 551         }
 552 
 553         tabHeaderAreaClipRect.setX(0);
 554         tabHeaderAreaClipRect.setY(0);
 555         if (isHorizontal()) {
 556             tabHeaderAreaClipRect.setWidth(w);
 557         } else {
 558             tabHeaderAreaClipRect.setWidth(h);
 559         }
 560         tabHeaderAreaClipRect.setHeight(headerHeight);
 561 
 562         // ==================================
 563         // position the tab content for the selected tab only
 564         // ==================================
 565         // if the tabs are on the left, the content needs to be indented
 566         double contentStartX = 0;
 567         double contentStartY = 0;
 568 
 569         if (tabPosition == Side.TOP) {
 570             contentStartX = x;
 571             contentStartY = y + headerHeight;
 572             if (isFloatingStyleClass()) {
 573                 // This is to hide the top border content
 574                 contentStartY -= 1;
 575             }
 576         } else if (tabPosition == Side.BOTTOM) {
 577             contentStartX = x;
 578             contentStartY = y;
 579             if (isFloatingStyleClass()) {
 580                 // This is to hide the bottom border content
 581                 contentStartY = 1;
 582             }
 583         } else if (tabPosition == Side.LEFT) {
 584             contentStartX = x + headerHeight;
 585             contentStartY = y;
 586             if (isFloatingStyleClass()) {
 587                 // This is to hide the left border content
 588                 contentStartX -= 1;
 589             }
 590         } else if (tabPosition == Side.RIGHT) {
 591             contentStartX = x;
 592             contentStartY = y;
 593             if (isFloatingStyleClass()) {
 594                 // This is to hide the right border content
 595                 contentStartX = 1;
 596             }
 597         }
 598 
 599         double contentWidth = w - (isHorizontal() ? 0 : headerHeight);
 600         double contentHeight = h - (isHorizontal() ? headerHeight: 0);
 601         
 602         for (int i = 0, max = tabContentRegions.size(); i < max; i++) {
 603             TabContentRegion tabContent = tabContentRegions.get(i);
 604             
 605             tabContent.setAlignment(Pos.TOP_LEFT);
 606             if (tabContent.getClip() != null) {
 607                 ((Rectangle)tabContent.getClip()).setWidth(contentWidth);
 608                 ((Rectangle)tabContent.getClip()).setHeight(contentHeight);
 609             }
 610             
 611             // we need to size all tabs, even if they aren't visible. For example,
 612             // see RT-29167
 613             tabContent.resize(contentWidth, contentHeight);
 614             tabContent.relocate(contentStartX, contentStartY);
 615         }
 616     }
 617     





 618     
 619    /**
 620     * Super-lazy instantiation pattern from Bill Pugh.
 621     * @treatAsPrivate implementation detail
 622     */
 623    private static class StyleableProperties {
 624         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 625         
 626         private final static CssMetaData<TabPane,TabAnimation> OPEN_TAB_ANIMATION = 
 627                 new CssMetaData<TabPane, TabPaneSkin.TabAnimation>("-fx-open-tab-animation", 
 628                     new EnumConverter<TabAnimation>(TabAnimation.class), TabAnimation.GROW) {
 629 
 630             @Override public boolean isSettable(TabPane node) {
 631                 return true;
 632             }
 633 
 634             @Override public StyleableProperty<TabAnimation> getStyleableProperty(TabPane node) {
 635                 TabPaneSkin skin = (TabPaneSkin) node.getSkin();
 636                 return (StyleableProperty<TabAnimation>)(WritableValue<TabAnimation>)skin.openTabAnimation;
 637             }


 646             }
 647 
 648             @Override public StyleableProperty<TabAnimation> getStyleableProperty(TabPane node) {
 649                 TabPaneSkin skin = (TabPaneSkin) node.getSkin();
 650                 return (StyleableProperty<TabAnimation>)(WritableValue<TabAnimation>)skin.closeTabAnimation;
 651             }
 652         };
 653         
 654         static {
 655 
 656            final List<CssMetaData<? extends Styleable, ?>> styleables = 
 657                new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
 658            styleables.add(OPEN_TAB_ANIMATION);
 659            styleables.add(CLOSE_TAB_ANIMATION);
 660            STYLEABLES = Collections.unmodifiableList(styleables);
 661 
 662         }
 663     }
 664     
 665     /**
 666      * @return The CssMetaData associated with this class, which may include the
 667      * CssMetaData of its super classes.
 668      */
 669     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 670         return StyleableProperties.STYLEABLES;
 671     }
 672 
 673     /**
 674      * {@inheritDoc}
 675      */
 676     @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 677         return getClassCssMetaData();
 678     }
 679 








 680     /**************************************************************************
 681      *
 682      * TabHeaderArea: Area responsible for painting all tabs
 683      *
 684      **************************************************************************/
 685     class TabHeaderArea extends StackPane {
 686         private Rectangle headerClip;
 687         private StackPane headersRegion;
 688         private StackPane headerBackground;
 689         private TabControlButtons controlButtons;
 690 
 691         private boolean measureClosingTabs = false;
 692 
 693         private double scrollOffset;
 694 
 695         public TabHeaderArea() {
 696             getStyleClass().setAll("tab-header-area");
 697             setManaged(false);
 698             final TabPane tabPane = getSkinnable();
 699 


1078                 startX = snapSize(getWidth()) - headersPrefWidth - leftInset;
1079                 startY = tabBackgroundHeight - headersPrefHeight - topInset;
1080                 controlStartX = rightInset;
1081                 controlStartY = snapSize(getHeight()) - btnHeight - topInset;
1082             } else if (tabPosition.equals(Side.LEFT)) {
1083                 startX = snapSize(getWidth()) - headersPrefWidth - topInset;
1084                 startY = tabBackgroundHeight - headersPrefHeight - rightInset;
1085                 controlStartX = leftInset;
1086                 controlStartY = snapSize(getHeight()) - btnHeight - rightInset;
1087             }
1088             if (headerBackground.isVisible()) {
1089                 positionInArea(headerBackground, 0, 0,
1090                         snapSize(getWidth()), snapSize(getHeight()), /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
1091             }
1092             positionInArea(headersRegion, startX, startY, w, h, /*baseline ignored*/0, HPos.LEFT, VPos.CENTER);
1093             positionInArea(controlButtons, controlStartX, controlStartY, btnWidth, btnHeight,
1094                         /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
1095         }
1096     } /* End TabHeaderArea */
1097 
1098     static int CLOSE_BTN_SIZE = 16;

1099 
1100     /**************************************************************************
1101      *
1102      * TabHeaderSkin: skin for each tab
1103      *
1104      **************************************************************************/
1105 
1106     class TabHeaderSkin extends StackPane {
1107         private final Tab tab;
1108         public Tab getTab() {
1109             return tab;
1110         }
1111         private Label label;
1112         private StackPane closeBtn;
1113         private StackPane inner;
1114         private Tooltip oldTooltip;
1115         private Tooltip tooltip;
1116         private Rectangle clip;
1117 
1118         private boolean isClosing = false;
1119 
1120         private MultiplePropertyChangeListenerHandler listener =
1121                 new MultiplePropertyChangeListenerHandler(param -> {
1122                     handlePropertyChanged(param);
1123                     return null;
1124                 });
1125         
1126         private final ListChangeListener<String> styleClassListener = new ListChangeListener<String>() {
1127             @Override
1128             public void onChanged(Change<? extends String> c) {
1129                 getStyleClass().setAll(tab.getStyleClass());
1130             }
1131         };
1132         
1133         private final WeakListChangeListener<String> weakStyleClassListener =
1134                 new WeakListChangeListener<>(styleClassListener);
1135 
1136         public TabHeaderSkin(final Tab tab) {
1137             getStyleClass().setAll(tab.getStyleClass());
1138             setId(tab.getId());
1139             setStyle(tab.getStyle());
1140             setAccessibleRole(AccessibleRole.TAB_ITEM);
1141 
1142             this.tab = tab;
1143             clip = new Rectangle();
1144             setClip(clip);
1145 
1146             label = new Label(tab.getText(), tab.getGraphic());
1147             label.getStyleClass().setAll("tab-label");
1148 
1149             closeBtn = new StackPane() {
1150                 @Override protected double computePrefWidth(double h) {
1151                     return CLOSE_BTN_SIZE;
1152                 }
1153                 @Override protected double computePrefHeight(double w) {
1154                     return CLOSE_BTN_SIZE;
1155                 }
1156                 @Override
1157                 public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
1158                     switch (action) {
1159                         case FIRE: {
1160                             Tab tab = getTab();
1161                             TabPaneBehavior behavior = getBehavior();
1162                             if (behavior.canCloseTab(tab)) {
1163                                 behavior.closeTab(tab);
1164                                 setOnMousePressed(null);
1165                             }
1166                         }
1167                         default: super.executeAccessibleAction(action, parameters);
1168                     }
1169                 }
1170             };
1171             closeBtn.setAccessibleRole(AccessibleRole.BUTTON);
1172             closeBtn.setAccessibleText(getString("Accessibility.title.TabPane.CloseButton"));
1173             closeBtn.getStyleClass().setAll("tab-close-button");
1174             closeBtn.setOnMousePressed(new EventHandler<MouseEvent>() {
1175                 @Override public void handle(MouseEvent me) {

1176                     Tab tab = getTab();
1177                     TabPaneBehavior behavior = getBehavior();
1178                     if (behavior.canCloseTab(tab)) {
1179                          behavior.closeTab(tab);
1180                          setOnMousePressed(null);    
1181                     }
1182                 }
1183             });
1184             
1185             updateGraphicRotation();
1186 
1187             final Region focusIndicator = new Region();
1188             focusIndicator.setMouseTransparent(true);
1189             focusIndicator.getStyleClass().add("focus-indicator");
1190             
1191             inner = new StackPane() {
1192                 @Override protected void layoutChildren() {
1193                     final TabPane skinnable = getSkinnable();
1194                     
1195                     final double paddingTop = snappedTopInset();
1196                     final double paddingRight = snappedRightInset();
1197                     final double paddingBottom = snappedBottomInset();


1269                     final int hPadding = Utils.isMac() ? 2 : 1;
1270                     focusIndicator.resizeRelocate(
1271                             paddingLeft - hPadding,
1272                             paddingTop + vPadding,
1273                             w + 2 * hPadding,
1274                             h - 2 * vPadding);
1275                 }
1276             };
1277             inner.getStyleClass().add("tab-container");
1278             inner.setRotate(getSkinnable().getSide().equals(Side.BOTTOM) ? 180.0F : 0.0F);
1279             inner.getChildren().addAll(label, closeBtn, focusIndicator);
1280 
1281             getChildren().addAll(inner);
1282 
1283             tooltip = tab.getTooltip();
1284             if (tooltip != null) {
1285                 Tooltip.install(this, tooltip);
1286                 oldTooltip = tooltip;
1287             }
1288 
1289             listener.registerChangeListener(tab.closableProperty(), "CLOSABLE");
1290             listener.registerChangeListener(tab.selectedProperty(), "SELECTED");
1291             listener.registerChangeListener(tab.textProperty(), "TEXT");
1292             listener.registerChangeListener(tab.graphicProperty(), "GRAPHIC");
1293             listener.registerChangeListener(tab.contextMenuProperty(), "CONTEXT_MENU");
1294             listener.registerChangeListener(tab.tooltipProperty(), "TOOLTIP");
1295             listener.registerChangeListener(tab.disableProperty(), "DISABLE");
1296             listener.registerChangeListener(tab.styleProperty(), "STYLE");
1297             
1298             tab.getStyleClass().addListener(weakStyleClassListener);
1299 
1300             listener.registerChangeListener(getSkinnable().tabClosingPolicyProperty(), "TAB_CLOSING_POLICY");
1301             listener.registerChangeListener(getSkinnable().sideProperty(), "SIDE");
1302             listener.registerChangeListener(getSkinnable().rotateGraphicProperty(), "ROTATE_GRAPHIC");
1303             listener.registerChangeListener(getSkinnable().tabMinWidthProperty(), "TAB_MIN_WIDTH");
1304             listener.registerChangeListener(getSkinnable().tabMaxWidthProperty(), "TAB_MAX_WIDTH");
1305             listener.registerChangeListener(getSkinnable().tabMinHeightProperty(), "TAB_MIN_HEIGHT");
1306             listener.registerChangeListener(getSkinnable().tabMaxHeightProperty(), "TAB_MAX_HEIGHT");
1307             
1308             getProperties().put(Tab.class, tab);
1309             getProperties().put(ContextMenu.class, tab.getContextMenu());
1310 
1311             setOnContextMenuRequested((ContextMenuEvent me) -> {
1312                if (getTab().getContextMenu() != null) {
1313                     getTab().getContextMenu().show(inner, me.getScreenX(), me.getScreenY());
1314                     me.consume();
1315                 }
1316             });
1317             setOnMousePressed(new EventHandler<MouseEvent>() {
1318                 @Override public void handle(MouseEvent me) {
1319                     if (getTab().isDisable()) {
1320                         return;
1321                     }
1322                     if (me.getButton().equals(MouseButton.MIDDLE)) {
1323                         if (showCloseButton()) {
1324                             Tab tab = getTab();
1325                             TabPaneBehavior behavior = getBehavior();
1326                             if (behavior.canCloseTab(tab)) {
1327                                 removeListeners(tab);
1328                                 behavior.closeTab(tab);    
1329                             }
1330                         }
1331                     } else if (me.getButton().equals(MouseButton.PRIMARY)) {
1332                         getBehavior().selectTab(getTab());
1333                     }
1334                 }
1335             });    
1336 
1337             // initialize pseudo-class state
1338             pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
1339             pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
1340             final Side side = getSkinnable().getSide();
1341             pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
1342             pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
1343             pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
1344             pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
1345         }
1346         
1347         private void handlePropertyChanged(final String p) {
1348             // --- Tab properties
1349             if ("CLOSABLE".equals(p)) {
1350                 inner.requestLayout();
1351                 requestLayout();
1352             } else if ("SELECTED".equals(p)) {

1353                 pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
1354                 // Need to request a layout pass for inner because if the width
1355                 // and height didn't not change the label or close button may have
1356                 // changed.
1357                 inner.requestLayout();
1358                 requestLayout();
1359             } else if ("TEXT".equals(p)) {
1360                 label.setText(getTab().getText());
1361             } else if ("GRAPHIC".equals(p)) {
1362                 label.setGraphic(getTab().getGraphic());
1363             } else if ("CONTEXT_MENU".equals(p)) {
1364                 // todo
1365             } else if ("TOOLTIP".equals(p)) {
1366                 // uninstall the old tooltip
1367                 if (oldTooltip != null) {
1368                     Tooltip.uninstall(this, oldTooltip);
1369                 }
1370                 tooltip = tab.getTooltip();
1371                 if (tooltip != null) {
1372                     // install new tooltip and save as old tooltip.
1373                     Tooltip.install(this, tooltip);
1374                     oldTooltip = tooltip; 
1375                 }
1376             } else if ("DISABLE".equals(p)) {

1377                 pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
1378                 inner.requestLayout();
1379                 requestLayout();
1380             } else if ("STYLE".equals(p)) {
1381                 setStyle(tab.getStyle());
1382             }
1383             
1384             // --- Skinnable properties
1385             else if ("TAB_CLOSING_POLICY".equals(p)) {

1386                 inner.requestLayout();
1387                 requestLayout(); 
1388             } else if ("SIDE".equals(p)) {

1389                 final Side side = getSkinnable().getSide();
1390                 pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
1391                 pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
1392                 pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
1393                 pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
1394                 inner.setRotate(side == Side.BOTTOM ? 180.0F : 0.0F);
1395                 if (getSkinnable().isRotateGraphic()) {
1396                     updateGraphicRotation();
1397                 }
1398             } else if ("ROTATE_GRAPHIC".equals(p)) {
1399                 updateGraphicRotation();
1400             } else if ("TAB_MIN_WIDTH".equals(p)) {
1401                 requestLayout();
1402                 getSkinnable().requestLayout();
1403             } else if ("TAB_MAX_WIDTH".equals(p)) {

1404                 requestLayout();
1405                 getSkinnable().requestLayout();
1406             } else if ("TAB_MIN_HEIGHT".equals(p)) {

1407                 requestLayout();
1408                 getSkinnable().requestLayout();
1409             } else if ("TAB_MAX_HEIGHT".equals(p)) {

1410                 requestLayout();
1411                 getSkinnable().requestLayout();



























1412             } 










1413         }
1414 
1415         private void updateGraphicRotation() {
1416             if (label.getGraphic() != null) {
1417                 label.getGraphic().setRotate(getSkinnable().isRotateGraphic() ? 0.0F :
1418                     (getSkinnable().getSide().equals(Side.RIGHT) ? -90.0F :
1419                         (getSkinnable().getSide().equals(Side.LEFT) ? 90.0F : 0.0F)));
1420             }
1421         }
1422 
1423         private boolean showCloseButton() {
1424             return tab.isClosable() &&
1425                     (getSkinnable().getTabClosingPolicy().equals(TabClosingPolicy.ALL_TABS) ||
1426                     getSkinnable().getTabClosingPolicy().equals(TabClosingPolicy.SELECTED_TAB) && tab.isSelected());
1427         }
1428 
1429         private final DoubleProperty animationTransition = new SimpleDoubleProperty(this, "animationTransition", 1.0) {
1430             @Override protected void invalidated() {
1431                 requestLayout();
1432             }




   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.skin;
  27 
  28 import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler;
  29 import com.sun.javafx.scene.control.Properties;
  30 import com.sun.javafx.util.Utils;
  31 import javafx.animation.Animation;
  32 import javafx.animation.Interpolator;
  33 import javafx.animation.KeyFrame;
  34 import javafx.animation.KeyValue;
  35 import javafx.animation.Timeline;
  36 import javafx.beans.InvalidationListener;
  37 import javafx.beans.Observable;
  38 import javafx.beans.WeakInvalidationListener;
  39 import javafx.beans.property.DoubleProperty;
  40 import javafx.beans.property.ObjectProperty;
  41 import javafx.beans.property.SimpleDoubleProperty;
  42 import javafx.beans.value.WritableValue;
  43 import javafx.collections.FXCollections;
  44 import javafx.collections.ListChangeListener;
  45 import javafx.collections.ObservableList;
  46 import javafx.collections.WeakListChangeListener;
  47 import javafx.css.CssMetaData;
  48 import javafx.css.PseudoClass;
  49 import javafx.css.Styleable;
  50 import javafx.css.StyleableObjectProperty;
  51 import javafx.css.StyleableProperty;
  52 import javafx.event.ActionEvent;
  53 import javafx.event.EventHandler;
  54 import javafx.geometry.HPos;
  55 import javafx.geometry.Pos;
  56 import javafx.geometry.Side;
  57 import javafx.geometry.VPos;
  58 import javafx.scene.AccessibleAction;
  59 import javafx.scene.AccessibleAttribute;
  60 import javafx.scene.AccessibleRole;
  61 import javafx.scene.Node;
  62 import javafx.scene.control.ContextMenu;
  63 import javafx.scene.control.Control;
  64 import javafx.scene.control.Label;
  65 import javafx.scene.control.MenuItem;
  66 import javafx.scene.control.RadioMenuItem;
  67 import javafx.scene.control.SkinBase;
  68 import javafx.scene.control.Tab;
  69 import javafx.scene.control.TabPane;
  70 import javafx.scene.control.TabPane.TabClosingPolicy;
  71 import javafx.scene.control.ToggleGroup;
  72 import javafx.scene.control.Tooltip;
  73 import javafx.scene.effect.DropShadow;
  74 import javafx.scene.image.ImageView;
  75 import javafx.scene.input.ContextMenuEvent;
  76 import javafx.scene.input.MouseButton;
  77 import javafx.scene.input.MouseEvent;
  78 import javafx.scene.input.ScrollEvent;
  79 import javafx.scene.input.SwipeEvent;
  80 import javafx.scene.layout.Pane;
  81 import javafx.scene.layout.Region;
  82 import javafx.scene.layout.StackPane;
  83 import javafx.scene.shape.Rectangle;
  84 import javafx.scene.transform.Rotate;
  85 import javafx.util.Duration;
  86 
  87 import java.util.ArrayList;
  88 import java.util.Collections;
  89 import java.util.Iterator;
  90 import java.util.List;
  91 
  92 import javafx.css.converter.EnumConverter;

  93 import com.sun.javafx.scene.control.behavior.TabPaneBehavior;
  94 import com.sun.javafx.scene.traversal.Direction;
  95 import com.sun.javafx.scene.traversal.TraversalEngine;
  96 
  97 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
  98 
  99 /**
 100  * Default skin implementation for the {@link TabPane} control.
 101  *
 102  * @see TabPane
 103  * @since 9
 104  */
 105 public class TabPaneSkin extends SkinBase<TabPane> {
 106 
 107     /***************************************************************************
 108      *                                                                         *
 109      * Enums                                                                   *
 110      *                                                                         *
 111      **************************************************************************/
 112 
 113     private enum TabAnimation {
 114         NONE,
 115         GROW
 116         // In future we could add FADE, ...
 117     }
 118 
 119     private enum TabAnimationState {
 120         SHOWING, HIDING, NONE;
 121     }
 122 




 123 



 124 
 125     /***************************************************************************
 126      *                                                                         *
 127      * Static fields                                                           *
 128      *                                                                         *
 129      **************************************************************************/
 130 
 131     static int CLOSE_BTN_SIZE = 16;



 132 



 133 




 134 
 135     /***************************************************************************
 136      *                                                                         *
 137      * Private fields                                                          *
 138      *                                                                         *
 139      **************************************************************************/









 140 





















 141     private static final double ANIMATION_SPEED = 150;
 142     private static final int SPACER = 10;
 143 
 144     private TabHeaderArea tabHeaderArea;
 145     private ObservableList<TabContentRegion> tabContentRegions;
 146     private Rectangle clipRect;
 147     private Rectangle tabHeaderAreaClipRect;
 148     private Tab selectedTab;
 149     private boolean isSelectingTab;
 150     private double maxw = 0.0d;
 151     private double maxh = 0.0d;
 152 
 153     private final TabPaneBehavior behavior;
 154 
 155 
 156 
 157     /***************************************************************************
 158      *                                                                         *
 159      * Constructors                                                            *
 160      *                                                                         *
 161      **************************************************************************/
 162 
 163     /**
 164      * Creates a new TabPaneSkin instance, installing the necessary child
 165      * nodes into the Control {@link Control#getChildren() children} list, as
 166      * well as the necessary input mappings for handling key, mouse, etc events.
 167      *
 168      * @param control The control that this skin should be installed onto.
 169      */
 170     public TabPaneSkin(TabPane control) {
 171         super(control);
 172 
 173         // install default input map for the TabPane control
 174         this.behavior = new TabPaneBehavior(control);
 175 //        control.setInputMap(behavior.getInputMap());
 176 
 177         clipRect = new Rectangle(control.getWidth(), control.getHeight());
 178         getSkinnable().setClip(clipRect);
 179 
 180         tabContentRegions = FXCollections.<TabContentRegion>observableArrayList();
 181 
 182         for (Tab tab : getSkinnable().getTabs()) {
 183             addTabContent(tab);
 184         }
 185 
 186         tabHeaderAreaClipRect = new Rectangle();
 187         tabHeaderArea = new TabHeaderArea();
 188         tabHeaderArea.setClip(tabHeaderAreaClipRect);
 189         getChildren().add(tabHeaderArea);
 190         if (getSkinnable().getTabs().size() == 0) {
 191             tabHeaderArea.setVisible(false);
 192         }
 193 
 194         initializeTabListener();
 195 
 196         registerChangeListener(control.getSelectionModel().selectedItemProperty(), e -> {
 197             isSelectingTab = true;
 198             selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
 199             getSkinnable().requestLayout();
 200         });
 201         registerChangeListener(control.sideProperty(), e -> updateTabPosition());
 202         registerChangeListener(control.widthProperty(), e -> clipRect.setWidth(getSkinnable().getWidth()));
 203         registerChangeListener(control.heightProperty(), e -> clipRect.setHeight(getSkinnable().getHeight()));
 204 
 205         selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
 206         // Could not find the selected tab try and get the selected tab using the selected index
 207         if (selectedTab == null && getSkinnable().getSelectionModel().getSelectedIndex() != -1) {
 208             getSkinnable().getSelectionModel().select(getSkinnable().getSelectionModel().getSelectedIndex());
 209             selectedTab = getSkinnable().getSelectionModel().getSelectedItem();        
 210         } 
 211         if (selectedTab == null) {
 212             // getSelectedItem and getSelectedIndex failed select the first.
 213             getSkinnable().getSelectionModel().selectFirst();
 214         } 
 215         selectedTab = getSkinnable().getSelectionModel().getSelectedItem();        
 216         isSelectingTab = false;
 217 
 218         initializeSwipeHandlers();
 219     }
 220 
 221 
 222 
 223     /***************************************************************************
 224      *                                                                         *
 225      * Properties                                                              *
 226      *                                                                         *
 227      **************************************************************************/
 228 
 229     private ObjectProperty<TabAnimation> openTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
 230         @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
 231             return StyleableProperties.OPEN_TAB_ANIMATION;
 232         }
 233 
 234         @Override public Object getBean() {
 235             return TabPaneSkin.this;
 236         }
 237 
 238         @Override public String getName() {
 239             return "openTabAnimation";
 240         }
 241     };
 242 
 243     private ObjectProperty<TabAnimation> closeTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
 244         @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
 245             return StyleableProperties.CLOSE_TAB_ANIMATION;
 246         }
 247 
 248         @Override public Object getBean() {
 249             return TabPaneSkin.this;
 250         }
 251 
 252         @Override public String getName() {
 253             return "closeTabAnimation";
 254         }
 255     };
 256 
 257 
 258 
 259     /***************************************************************************
 260      *                                                                         *
 261      * Public API                                                              *
 262      *                                                                         *
 263      **************************************************************************/
 264 
 265     /** {@inheritDoc} */
 266     @Override public void dispose() {
 267         super.dispose();
 268 
 269         if (behavior != null) {
 270             behavior.dispose();
 271         }
 272     }
 273 
 274     /** {@inheritDoc} */
 275     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 276         // The TabPane can only be as wide as it widest content width.
 277         for (TabContentRegion contentRegion: tabContentRegions) {
 278             maxw = Math.max(maxw, snapSize(contentRegion.prefWidth(-1)));
 279         }
 280 
 281         final boolean isHorizontal = isHorizontal();
 282         final double tabHeaderAreaSize = snapSize(isHorizontal ?
 283                 tabHeaderArea.prefWidth(-1) : tabHeaderArea.prefHeight(-1));
 284 
 285         double prefWidth = isHorizontal ?
 286                 Math.max(maxw, tabHeaderAreaSize) : maxw + tabHeaderAreaSize;
 287         return snapSize(prefWidth) + rightInset + leftInset;
 288     }
 289 
 290     /** {@inheritDoc} */
 291     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 292         // The TabPane can only be as high as it highest content height.
 293         for (TabContentRegion contentRegion: tabContentRegions) {
 294             maxh = Math.max(maxh, snapSize(contentRegion.prefHeight(-1)));
 295         }
 296 
 297         final boolean isHorizontal = isHorizontal();
 298         final double tabHeaderAreaSize = snapSize(isHorizontal ?
 299                 tabHeaderArea.prefHeight(-1) : tabHeaderArea.prefWidth(-1));
 300 
 301         double prefHeight = isHorizontal ?
 302                 maxh + snapSize(tabHeaderAreaSize) : Math.max(maxh, tabHeaderAreaSize);
 303         return snapSize(prefHeight) + topInset + bottomInset;
 304     }
 305 
 306     /** {@inheritDoc} */
 307     @Override public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
 308         Side tabPosition = getSkinnable().getSide();
 309         if (tabPosition == Side.TOP) {
 310             return tabHeaderArea.getBaselineOffset() + topInset;
 311         }
 312         return 0;
 313     }
 314 
 315     /** {@inheritDoc} */
 316     @Override protected void layoutChildren(final double x, final double y,
 317                                             final double w, final double h) {
 318         TabPane tabPane = getSkinnable();
 319         Side tabPosition = tabPane.getSide();
 320 
 321         double headerHeight = snapSize(tabHeaderArea.prefHeight(-1));
 322         double tabsStartX = tabPosition.equals(Side.RIGHT)? x + w - headerHeight : x;
 323         double tabsStartY = tabPosition.equals(Side.BOTTOM)? y + h - headerHeight : y;
 324 
 325         if (tabPosition == Side.TOP) {
 326             tabHeaderArea.resize(w, headerHeight);
 327             tabHeaderArea.relocate(tabsStartX, tabsStartY);
 328             tabHeaderArea.getTransforms().clear();
 329             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.TOP)));
 330         } else if (tabPosition == Side.BOTTOM) {
 331             tabHeaderArea.resize(w, headerHeight);
 332             tabHeaderArea.relocate(w, tabsStartY - headerHeight);
 333             tabHeaderArea.getTransforms().clear();
 334             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.BOTTOM), 0, headerHeight));
 335         } else if (tabPosition == Side.LEFT) {
 336             tabHeaderArea.resize(h, headerHeight);
 337             tabHeaderArea.relocate(tabsStartX + headerHeight, h - headerHeight);
 338             tabHeaderArea.getTransforms().clear();
 339             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.LEFT), 0, headerHeight));
 340         } else if (tabPosition == Side.RIGHT) {
 341             tabHeaderArea.resize(h, headerHeight);
 342             tabHeaderArea.relocate(tabsStartX, y - headerHeight);
 343             tabHeaderArea.getTransforms().clear();
 344             tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.RIGHT), 0, headerHeight));
 345         }
 346 
 347         tabHeaderAreaClipRect.setX(0);
 348         tabHeaderAreaClipRect.setY(0);
 349         if (isHorizontal()) {
 350             tabHeaderAreaClipRect.setWidth(w);
 351         } else {
 352             tabHeaderAreaClipRect.setWidth(h);
 353         }
 354         tabHeaderAreaClipRect.setHeight(headerHeight);
 355 
 356         // ==================================
 357         // position the tab content for the selected tab only
 358         // ==================================
 359         // if the tabs are on the left, the content needs to be indented
 360         double contentStartX = 0;
 361         double contentStartY = 0;
 362 
 363         if (tabPosition == Side.TOP) {
 364             contentStartX = x;
 365             contentStartY = y + headerHeight;
 366             if (isFloatingStyleClass()) {
 367                 // This is to hide the top border content
 368                 contentStartY -= 1;
 369             }
 370         } else if (tabPosition == Side.BOTTOM) {
 371             contentStartX = x;
 372             contentStartY = y;
 373             if (isFloatingStyleClass()) {
 374                 // This is to hide the bottom border content
 375                 contentStartY = 1;
 376             }
 377         } else if (tabPosition == Side.LEFT) {
 378             contentStartX = x + headerHeight;
 379             contentStartY = y;
 380             if (isFloatingStyleClass()) {
 381                 // This is to hide the left border content
 382                 contentStartX -= 1;
 383             }
 384         } else if (tabPosition == Side.RIGHT) {
 385             contentStartX = x;
 386             contentStartY = y;
 387             if (isFloatingStyleClass()) {
 388                 // This is to hide the right border content
 389                 contentStartX = 1;
 390             }
 391         }
 392 
 393         double contentWidth = w - (isHorizontal() ? 0 : headerHeight);
 394         double contentHeight = h - (isHorizontal() ? headerHeight: 0);
 395 
 396         for (int i = 0, max = tabContentRegions.size(); i < max; i++) {
 397             TabContentRegion tabContent = tabContentRegions.get(i);
 398 
 399             tabContent.setAlignment(Pos.TOP_LEFT);
 400             if (tabContent.getClip() != null) {
 401                 ((Rectangle)tabContent.getClip()).setWidth(contentWidth);
 402                 ((Rectangle)tabContent.getClip()).setHeight(contentHeight);
 403             }
 404 
 405             // we need to size all tabs, even if they aren't visible. For example,
 406             // see RT-29167
 407             tabContent.resize(contentWidth, contentHeight);
 408             tabContent.relocate(contentStartX, contentStartY);
 409         }
 410     }
 411 
 412 
 413 
 414     /***************************************************************************
 415      *                                                                         *
 416      * Private implementation                                                  *
 417      *                                                                         *
 418      **************************************************************************/
 419 
 420     private static int getRotation(Side pos) {
 421         switch (pos) {
 422             case TOP:
 423                 return 0;
 424             case BOTTOM:
 425                 return 180;
 426             case LEFT:
 427                 return -90;
 428             case RIGHT:
 429                 return 90;
 430             default:
 431                 return 0;
 432         }
 433     }
 434 
 435     /**
 436      * VERY HACKY - this lets us 'duplicate' Label and ImageView nodes to be used in a
 437      * Tab and the tabs menu at the same time.
 438      */
 439     private static Node clone(Node n) {
 440         if (n == null) {
 441             return null;
 442         }
 443         if (n instanceof ImageView) {
 444             ImageView iv = (ImageView) n;
 445             ImageView imageview = new ImageView();
 446             imageview.setImage(iv.getImage());
 447             return imageview;
 448         }
 449         if (n instanceof Label) {
 450             Label l = (Label)n;
 451             Label label = new Label(l.getText(), l.getGraphic());
 452             return label;
 453         }
 454         return null;
 455     }
 456 














 457     private void removeTabs(List<? extends Tab> removedList) {
 458         for (final Tab tab : removedList) {
 459             stopCurrentAnimation(tab);
 460             // Animate the tab removal
 461             final TabHeaderSkin tabRegion = tabHeaderArea.getTabHeaderSkin(tab);
 462             if (tabRegion != null) {
 463                 tabRegion.isClosing = true;
 464 
 465                 tabRegion.removeListeners(tab);
 466                 removeTabContent(tab);
 467 
 468                 // remove the menu item from the popup menu
 469                 ContextMenu popupMenu = tabHeaderArea.controlButtons.popup;
 470                 TabMenuItem tabItem = null;
 471                 if (popupMenu != null) {
 472                     for (MenuItem item : popupMenu.getItems()) {
 473                         tabItem = (TabMenuItem) item;
 474                         if (tab == tabItem.getTab()) {
 475                             break;
 476                         }


 595                     openTabAnimation.set(prevOpenAnimation);
 596                     closeTabAnimation.set(prevCloseAnimation);
 597                     getSkinnable().getSelectionModel().select(selTab);
 598                 }
 599 
 600                 if (c.wasRemoved()) {
 601                     tabsToRemove.addAll(c.getRemoved());
 602                 }
 603 
 604                 if (c.wasAdded()) {
 605                     tabsToAdd.addAll(c.getAddedSubList());
 606                     insertPos = c.getFrom();
 607                 }
 608             }
 609 
 610             // now only remove the tabs that are not in the tabsToAdd list
 611             tabsToRemove.removeAll(tabsToAdd);
 612             removeTabs(tabsToRemove);
 613 
 614             // and add in any new tabs (that we don't already have showing)
 615             if (!tabsToAdd.isEmpty()) {
 616                 for (TabContentRegion tabContentRegion : tabContentRegions) {
 617                     Tab tab = tabContentRegion.getTab();
 618                     TabHeaderSkin tabHeader = tabHeaderArea.getTabHeaderSkin(tab);
 619                     if (!tabHeader.isClosing && tabsToAdd.contains(tabContentRegion.getTab())) {
 620                         tabsToAdd.remove(tabContentRegion.getTab());
 621                     }
 622                 }
 623 
 624                 addTabs(tabsToAdd, insertPos == -1 ? tabContentRegions.size() : insertPos);
 625             }
 626 
 627             // Fix for RT-34692
 628             getSkinnable().requestLayout();
 629         });
 630     }
 631 
 632     private void addTabContent(Tab tab) {
 633         TabContentRegion tabContentRegion = new TabContentRegion(tab);
 634         tabContentRegion.setClip(new Rectangle());
 635         tabContentRegions.add(tabContentRegion);


 655     }
 656 
 657     private Timeline createTimeline(final TabHeaderSkin tabRegion, final Duration duration, final double endValue, final EventHandler<ActionEvent> func) {
 658         Timeline timeline = new Timeline();
 659         timeline.setCycleCount(1);
 660 
 661         KeyValue keyValue = new KeyValue(tabRegion.animationTransition, endValue, Interpolator.LINEAR);
 662         timeline.getKeyFrames().clear();
 663         timeline.getKeyFrames().add(new KeyFrame(duration, keyValue));
 664 
 665         timeline.setOnFinished(func);
 666         return timeline;
 667     }
 668 
 669     private boolean isHorizontal() {
 670         Side tabPosition = getSkinnable().getSide();
 671         return Side.TOP.equals(tabPosition) || Side.BOTTOM.equals(tabPosition);
 672     }
 673 
 674     private void initializeSwipeHandlers() {
 675         if (Properties.IS_TOUCH_SUPPORTED) {
 676             getSkinnable().addEventHandler(SwipeEvent.SWIPE_LEFT, t -> {
 677                 behavior.selectNextTab();
 678             });
 679 
 680             getSkinnable().addEventHandler(SwipeEvent.SWIPE_RIGHT, t -> {
 681                 behavior.selectPreviousTab();
 682             });
 683         }    
 684     }
 685     
 686     //TODO need to cache this.
 687     private boolean isFloatingStyleClass() {
 688         return getSkinnable().getStyleClass().contains(TabPane.STYLE_CLASS_FLOATING);
 689     }
 690 
































































































































 691 






 692 
 693     /***************************************************************************
 694      *                                                                         *
 695      * CSS                                                                     *
 696      *                                                                         *
 697      **************************************************************************/
 698 
 699    /**
 700     * Super-lazy instantiation pattern from Bill Pugh.
 701     * @treatAsPrivate implementation detail
 702     */
 703    private static class StyleableProperties {
 704         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 705         
 706         private final static CssMetaData<TabPane,TabAnimation> OPEN_TAB_ANIMATION = 
 707                 new CssMetaData<TabPane, TabPaneSkin.TabAnimation>("-fx-open-tab-animation", 
 708                     new EnumConverter<TabAnimation>(TabAnimation.class), TabAnimation.GROW) {
 709 
 710             @Override public boolean isSettable(TabPane node) {
 711                 return true;
 712             }
 713 
 714             @Override public StyleableProperty<TabAnimation> getStyleableProperty(TabPane node) {
 715                 TabPaneSkin skin = (TabPaneSkin) node.getSkin();
 716                 return (StyleableProperty<TabAnimation>)(WritableValue<TabAnimation>)skin.openTabAnimation;
 717             }


 726             }
 727 
 728             @Override public StyleableProperty<TabAnimation> getStyleableProperty(TabPane node) {
 729                 TabPaneSkin skin = (TabPaneSkin) node.getSkin();
 730                 return (StyleableProperty<TabAnimation>)(WritableValue<TabAnimation>)skin.closeTabAnimation;
 731             }
 732         };
 733         
 734         static {
 735 
 736            final List<CssMetaData<? extends Styleable, ?>> styleables = 
 737                new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
 738            styleables.add(OPEN_TAB_ANIMATION);
 739            styleables.add(CLOSE_TAB_ANIMATION);
 740            STYLEABLES = Collections.unmodifiableList(styleables);
 741 
 742         }
 743     }
 744 
 745     /**
 746      * Returns the CssMetaData associated with this class, which may include the
 747      * CssMetaData of its super classes.
 748      */
 749     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 750         return StyleableProperties.STYLEABLES;
 751     }
 752 
 753     /**
 754      * {@inheritDoc}
 755      */
 756     @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 757         return getClassCssMetaData();
 758     }
 759 
 760 
 761 
 762     /***************************************************************************
 763      *                                                                         *
 764      * Support classes                                                         *
 765      *                                                                         *
 766      **************************************************************************/
 767 
 768     /**************************************************************************
 769      *
 770      * TabHeaderArea: Area responsible for painting all tabs
 771      *
 772      **************************************************************************/
 773     class TabHeaderArea extends StackPane {
 774         private Rectangle headerClip;
 775         private StackPane headersRegion;
 776         private StackPane headerBackground;
 777         private TabControlButtons controlButtons;
 778 
 779         private boolean measureClosingTabs = false;
 780 
 781         private double scrollOffset;
 782 
 783         public TabHeaderArea() {
 784             getStyleClass().setAll("tab-header-area");
 785             setManaged(false);
 786             final TabPane tabPane = getSkinnable();
 787 


1166                 startX = snapSize(getWidth()) - headersPrefWidth - leftInset;
1167                 startY = tabBackgroundHeight - headersPrefHeight - topInset;
1168                 controlStartX = rightInset;
1169                 controlStartY = snapSize(getHeight()) - btnHeight - topInset;
1170             } else if (tabPosition.equals(Side.LEFT)) {
1171                 startX = snapSize(getWidth()) - headersPrefWidth - topInset;
1172                 startY = tabBackgroundHeight - headersPrefHeight - rightInset;
1173                 controlStartX = leftInset;
1174                 controlStartY = snapSize(getHeight()) - btnHeight - rightInset;
1175             }
1176             if (headerBackground.isVisible()) {
1177                 positionInArea(headerBackground, 0, 0,
1178                         snapSize(getWidth()), snapSize(getHeight()), /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
1179             }
1180             positionInArea(headersRegion, startX, startY, w, h, /*baseline ignored*/0, HPos.LEFT, VPos.CENTER);
1181             positionInArea(controlButtons, controlStartX, controlStartY, btnWidth, btnHeight,
1182                         /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
1183         }
1184     } /* End TabHeaderArea */
1185 
1186 
1187 
1188 
1189     /**************************************************************************
1190      *
1191      * TabHeaderSkin: skin for each tab
1192      *
1193      **************************************************************************/
1194 
1195     class TabHeaderSkin extends StackPane {
1196         private final Tab tab;
1197         public Tab getTab() {
1198             return tab;
1199         }
1200         private Label label;
1201         private StackPane closeBtn;
1202         private StackPane inner;
1203         private Tooltip oldTooltip;
1204         private Tooltip tooltip;
1205         private Rectangle clip;
1206 
1207         private boolean isClosing = false;
1208 
1209         private LambdaMultiplePropertyChangeListenerHandler listener = new LambdaMultiplePropertyChangeListenerHandler();




1210         
1211         private final ListChangeListener<String> styleClassListener = new ListChangeListener<String>() {
1212             @Override
1213             public void onChanged(Change<? extends String> c) {
1214                 getStyleClass().setAll(tab.getStyleClass());
1215             }
1216         };
1217         
1218         private final WeakListChangeListener<String> weakStyleClassListener =
1219                 new WeakListChangeListener<>(styleClassListener);
1220 
1221         public TabHeaderSkin(final Tab tab) {
1222             getStyleClass().setAll(tab.getStyleClass());
1223             setId(tab.getId());
1224             setStyle(tab.getStyle());
1225             setAccessibleRole(AccessibleRole.TAB_ITEM);
1226 
1227             this.tab = tab;
1228             clip = new Rectangle();
1229             setClip(clip);
1230 
1231             label = new Label(tab.getText(), tab.getGraphic());
1232             label.getStyleClass().setAll("tab-label");
1233 
1234             closeBtn = new StackPane() {
1235                 @Override protected double computePrefWidth(double h) {
1236                     return CLOSE_BTN_SIZE;
1237                 }
1238                 @Override protected double computePrefHeight(double w) {
1239                     return CLOSE_BTN_SIZE;
1240                 }
1241                 @Override
1242                 public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
1243                     switch (action) {
1244                         case FIRE: {
1245                             Tab tab = getTab();

1246                             if (behavior.canCloseTab(tab)) {
1247                                 behavior.closeTab(tab);
1248                                 setOnMousePressed(null);
1249                             }
1250                         }
1251                         default: super.executeAccessibleAction(action, parameters);
1252                     }
1253                 }
1254             };
1255             closeBtn.setAccessibleRole(AccessibleRole.BUTTON);
1256             closeBtn.setAccessibleText(getString("Accessibility.title.TabPane.CloseButton"));
1257             closeBtn.getStyleClass().setAll("tab-close-button");
1258             closeBtn.setOnMousePressed(new EventHandler<MouseEvent>() {
1259                 @Override
1260                 public void handle(MouseEvent me) {
1261                     Tab tab = getTab();

1262                     if (behavior.canCloseTab(tab)) {
1263                         behavior.closeTab(tab);
1264                         setOnMousePressed(null);
1265                     }
1266                 }
1267             });
1268             
1269             updateGraphicRotation();
1270 
1271             final Region focusIndicator = new Region();
1272             focusIndicator.setMouseTransparent(true);
1273             focusIndicator.getStyleClass().add("focus-indicator");
1274             
1275             inner = new StackPane() {
1276                 @Override protected void layoutChildren() {
1277                     final TabPane skinnable = getSkinnable();
1278                     
1279                     final double paddingTop = snappedTopInset();
1280                     final double paddingRight = snappedRightInset();
1281                     final double paddingBottom = snappedBottomInset();


1353                     final int hPadding = Utils.isMac() ? 2 : 1;
1354                     focusIndicator.resizeRelocate(
1355                             paddingLeft - hPadding,
1356                             paddingTop + vPadding,
1357                             w + 2 * hPadding,
1358                             h - 2 * vPadding);
1359                 }
1360             };
1361             inner.getStyleClass().add("tab-container");
1362             inner.setRotate(getSkinnable().getSide().equals(Side.BOTTOM) ? 180.0F : 0.0F);
1363             inner.getChildren().addAll(label, closeBtn, focusIndicator);
1364 
1365             getChildren().addAll(inner);
1366 
1367             tooltip = tab.getTooltip();
1368             if (tooltip != null) {
1369                 Tooltip.install(this, tooltip);
1370                 oldTooltip = tooltip;
1371             }
1372 
1373             listener.registerChangeListener(tab.closableProperty(), e -> {




























































1374                 inner.requestLayout();
1375                 requestLayout();
1376             });
1377             listener.registerChangeListener(tab.selectedProperty(), e -> {
1378                 pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
1379                 // Need to request a layout pass for inner because if the width
1380                 // and height didn't not change the label or close button may have
1381                 // changed.
1382                 inner.requestLayout();
1383                 requestLayout();
1384             });
1385             listener.registerChangeListener(tab.textProperty(),e -> label.setText(getTab().getText()));
1386             listener.registerChangeListener(tab.graphicProperty(), e -> label.setGraphic(getTab().getGraphic()));
1387             listener.registerChangeListener(tab.tooltipProperty(), e -> {



1388                 // uninstall the old tooltip
1389                 if (oldTooltip != null) {
1390                     Tooltip.uninstall(this, oldTooltip);
1391                 }
1392                 tooltip = tab.getTooltip();
1393                 if (tooltip != null) {
1394                     // install new tooltip and save as old tooltip.
1395                     Tooltip.install(this, tooltip);
1396                     oldTooltip = tooltip;
1397                 }
1398             });
1399             listener.registerChangeListener(tab.disableProperty(), e -> {
1400                 pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
1401                 inner.requestLayout();
1402                 requestLayout();
1403             });
1404             listener.registerChangeListener(tab.styleProperty(), e -> setStyle(tab.getStyle()));

1405             
1406             tab.getStyleClass().addListener(weakStyleClassListener);
1407 
1408             listener.registerChangeListener(getSkinnable().tabClosingPolicyProperty(),e -> {
1409                 inner.requestLayout();
1410                 requestLayout();
1411             });
1412             listener.registerChangeListener(getSkinnable().sideProperty(),e -> {
1413                 final Side side = getSkinnable().getSide();
1414                 pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
1415                 pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
1416                 pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
1417                 pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
1418                 inner.setRotate(side == Side.BOTTOM ? 180.0F : 0.0F);
1419                 if (getSkinnable().isRotateGraphic()) {
1420                     updateGraphicRotation();
1421                 }
1422             });
1423             listener.registerChangeListener(getSkinnable().rotateGraphicProperty(), e -> updateGraphicRotation());
1424             listener.registerChangeListener(getSkinnable().tabMinWidthProperty(), e -> {
1425                 requestLayout();
1426                 getSkinnable().requestLayout();
1427             });
1428             listener.registerChangeListener(getSkinnable().tabMaxWidthProperty(), e -> {
1429                 requestLayout();
1430                 getSkinnable().requestLayout();
1431             });
1432             listener.registerChangeListener(getSkinnable().tabMinHeightProperty(), e -> {
1433                 requestLayout();
1434                 getSkinnable().requestLayout();
1435             });
1436             listener.registerChangeListener(getSkinnable().tabMaxHeightProperty(), e -> {
1437                 requestLayout();
1438                 getSkinnable().requestLayout();
1439             });
1440 
1441             getProperties().put(Tab.class, tab);
1442             getProperties().put(ContextMenu.class, tab.getContextMenu());
1443 
1444             setOnContextMenuRequested((ContextMenuEvent me) -> {
1445                if (getTab().getContextMenu() != null) {
1446                     getTab().getContextMenu().show(inner, me.getScreenX(), me.getScreenY());
1447                     me.consume();
1448                 }
1449             });
1450             setOnMousePressed(new EventHandler<MouseEvent>() {
1451                 @Override public void handle(MouseEvent me) {
1452                     if (getTab().isDisable()) {
1453                         return;
1454                     }
1455                     if (me.getButton().equals(MouseButton.MIDDLE)) {
1456                         if (showCloseButton()) {
1457                             Tab tab = getTab();
1458                             if (behavior.canCloseTab(tab)) {
1459                                 removeListeners(tab);
1460                                 behavior.closeTab(tab);    
1461                             }
1462                         }
1463                     } else if (me.getButton().equals(MouseButton.PRIMARY)) {
1464                         behavior.selectTab(getTab());
1465                     }
1466                 }
1467             });    
1468 
1469             // initialize pseudo-class state
1470             pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
1471             pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
1472             final Side side = getSkinnable().getSide();
1473             pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
1474             pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
1475             pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
1476             pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
1477         }
1478 
1479         private void updateGraphicRotation() {
1480             if (label.getGraphic() != null) {
1481                 label.getGraphic().setRotate(getSkinnable().isRotateGraphic() ? 0.0F :
1482                     (getSkinnable().getSide().equals(Side.RIGHT) ? -90.0F :
1483                         (getSkinnable().getSide().equals(Side.LEFT) ? 90.0F : 0.0F)));
1484             }
1485         }
1486 
1487         private boolean showCloseButton() {
1488             return tab.isClosable() &&
1489                     (getSkinnable().getTabClosingPolicy().equals(TabClosingPolicy.ALL_TABS) ||
1490                     getSkinnable().getTabClosingPolicy().equals(TabClosingPolicy.SELECTED_TAB) && tab.isSelected());
1491         }
1492 
1493         private final DoubleProperty animationTransition = new SimpleDoubleProperty(this, "animationTransition", 1.0) {
1494             @Override protected void invalidated() {
1495                 requestLayout();
1496             }