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
*** 21,32 ****
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
! package com.sun.javafx.scene.control.skin;
import com.sun.javafx.util.Utils;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
--- 21,34 ----
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
! package javafx.scene.control.skin;
+ import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler;
+ import com.sun.javafx.scene.control.Properties;
import com.sun.javafx.util.Utils;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
*** 56,65 ****
--- 58,68 ----
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
+ import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tab;
*** 84,190 ****
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
! import com.sun.javafx.css.converters.EnumConverter;
! import com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler;
import com.sun.javafx.scene.control.behavior.TabPaneBehavior;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.scene.traversal.TraversalEngine;
import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
! public class TabPaneSkin extends BehaviorSkinBase<TabPane, TabPaneBehavior> {
! private static enum TabAnimation {
NONE,
GROW
// In future we could add FADE, ...
}
private enum TabAnimationState {
SHOWING, HIDING, NONE;
}
- private ObjectProperty<TabAnimation> openTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
- @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
- return StyleableProperties.OPEN_TAB_ANIMATION;
- }
- @Override public Object getBean() {
- return TabPaneSkin.this;
- }
! @Override public String getName() {
! return "openTabAnimation";
! }
! };
! private ObjectProperty<TabAnimation> closeTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
! @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
! return StyleableProperties.CLOSE_TAB_ANIMATION;
! }
- @Override public Object getBean() {
- return TabPaneSkin.this;
- }
- @Override public String getName() {
- return "closeTabAnimation";
- }
- };
! private static int getRotation(Side pos) {
! switch (pos) {
! case TOP:
! return 0;
! case BOTTOM:
! return 180;
! case LEFT:
! return -90;
! case RIGHT:
! return 90;
! default:
! return 0;
! }
! }
- /**
- * VERY HACKY - this lets us 'duplicate' Label and ImageView nodes to be used in a
- * Tab and the tabs menu at the same time.
- */
- private static Node clone(Node n) {
- if (n == null) {
- return null;
- }
- if (n instanceof ImageView) {
- ImageView iv = (ImageView) n;
- ImageView imageview = new ImageView();
- imageview.setImage(iv.getImage());
- return imageview;
- }
- if (n instanceof Label) {
- Label l = (Label)n;
- Label label = new Label(l.getText(), l.getGraphic());
- return label;
- }
- return null;
- }
private static final double ANIMATION_SPEED = 150;
private static final int SPACER = 10;
private TabHeaderArea tabHeaderArea;
private ObservableList<TabContentRegion> tabContentRegions;
private Rectangle clipRect;
private Rectangle tabHeaderAreaClipRect;
private Tab selectedTab;
private boolean isSelectingTab;
! public TabPaneSkin(TabPane tabPane) {
! super(tabPane, new TabPaneBehavior(tabPane));
! clipRect = new Rectangle(tabPane.getWidth(), tabPane.getHeight());
getSkinnable().setClip(clipRect);
tabContentRegions = FXCollections.<TabContentRegion>observableArrayList();
for (Tab tab : getSkinnable().getTabs()) {
--- 87,182 ----
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
! import javafx.css.converter.EnumConverter;
import com.sun.javafx.scene.control.behavior.TabPaneBehavior;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.scene.traversal.TraversalEngine;
import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
! /**
! * Default skin implementation for the {@link TabPane} control.
! *
! * @see TabPane
! * @since 9
! */
! public class TabPaneSkin extends SkinBase<TabPane> {
!
! /***************************************************************************
! * *
! * Enums *
! * *
! **************************************************************************/
!
! private enum TabAnimation {
NONE,
GROW
// In future we could add FADE, ...
}
private enum TabAnimationState {
SHOWING, HIDING, NONE;
}
! /***************************************************************************
! * *
! * Static fields *
! * *
! **************************************************************************/
! static int CLOSE_BTN_SIZE = 16;
! /***************************************************************************
! * *
! * Private fields *
! * *
! **************************************************************************/
private static final double ANIMATION_SPEED = 150;
private static final int SPACER = 10;
private TabHeaderArea tabHeaderArea;
private ObservableList<TabContentRegion> tabContentRegions;
private Rectangle clipRect;
private Rectangle tabHeaderAreaClipRect;
private Tab selectedTab;
private boolean isSelectingTab;
+ private double maxw = 0.0d;
+ private double maxh = 0.0d;
+
+ private final TabPaneBehavior behavior;
+
+
! /***************************************************************************
! * *
! * Constructors *
! * *
! **************************************************************************/
!
! /**
! * Creates a new TabPaneSkin instance, installing the necessary child
! * nodes into the Control {@link Control#getChildren() children} list, as
! * well as the necessary input mappings for handling key, mouse, etc events.
! *
! * @param control The control that this skin should be installed onto.
! */
! public TabPaneSkin(TabPane control) {
! super(control);
! // install default input map for the TabPane control
! this.behavior = new TabPaneBehavior(control);
! // control.setInputMap(behavior.getInputMap());
!
! clipRect = new Rectangle(control.getWidth(), control.getHeight());
getSkinnable().setClip(clipRect);
tabContentRegions = FXCollections.<TabContentRegion>observableArrayList();
for (Tab tab : getSkinnable().getTabs()) {
*** 199,212 ****
tabHeaderArea.setVisible(false);
}
initializeTabListener();
! registerChangeListener(tabPane.getSelectionModel().selectedItemProperty(), "SELECTED_TAB");
! registerChangeListener(tabPane.sideProperty(), "SIDE");
! registerChangeListener(tabPane.widthProperty(), "WIDTH");
! registerChangeListener(tabPane.heightProperty(), "HEIGHT");
selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
// Could not find the selected tab try and get the selected tab using the selected index
if (selectedTab == null && getSkinnable().getSelectionModel().getSelectedIndex() != -1) {
getSkinnable().getSelectionModel().select(getSkinnable().getSelectionModel().getSelectedIndex());
--- 191,208 ----
tabHeaderArea.setVisible(false);
}
initializeTabListener();
! registerChangeListener(control.getSelectionModel().selectedItemProperty(), e -> {
! isSelectingTab = true;
! selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
! getSkinnable().requestLayout();
! });
! registerChangeListener(control.sideProperty(), e -> updateTabPosition());
! registerChangeListener(control.widthProperty(), e -> clipRect.setWidth(getSkinnable().getWidth()));
! registerChangeListener(control.heightProperty(), e -> clipRect.setHeight(getSkinnable().getHeight()));
selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
// Could not find the selected tab try and get the selected tab using the selected index
if (selectedTab == null && getSkinnable().getSelectionModel().getSelectedIndex() != -1) {
getSkinnable().getSelectionModel().select(getSkinnable().getSelectionModel().getSelectedIndex());
*** 219,252 ****
selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
isSelectingTab = false;
initializeSwipeHandlers();
}
!
! public StackPane getSelectedTabContentRegion() {
! for (TabContentRegion contentRegion : tabContentRegions) {
! if (contentRegion.getTab().equals(selectedTab)) {
! return contentRegion;
}
}
return null;
}
- @Override protected void handleControlPropertyChanged(String property) {
- super.handleControlPropertyChanged(property);
- if ("SELECTED_TAB".equals(property)) {
- isSelectingTab = true;
- selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
- getSkinnable().requestLayout();
- } else if ("SIDE".equals(property)) {
- updateTabPosition();
- } else if ("WIDTH".equals(property)) {
- clipRect.setWidth(getSkinnable().getWidth());
- } else if ("HEIGHT".equals(property)) {
- clipRect.setHeight(getSkinnable().getHeight());
- }
- }
private void removeTabs(List<? extends Tab> removedList) {
for (final Tab tab : removedList) {
stopCurrentAnimation(tab);
// Animate the tab removal
final TabHeaderSkin tabRegion = tabHeaderArea.getTabHeaderSkin(tab);
--- 215,461 ----
selectedTab = getSkinnable().getSelectionModel().getSelectedItem();
isSelectingTab = false;
initializeSwipeHandlers();
}
!
!
!
! /***************************************************************************
! * *
! * Properties *
! * *
! **************************************************************************/
!
! private ObjectProperty<TabAnimation> openTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
! @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
! return StyleableProperties.OPEN_TAB_ANIMATION;
! }
!
! @Override public Object getBean() {
! return TabPaneSkin.this;
! }
!
! @Override public String getName() {
! return "openTabAnimation";
! }
! };
!
! private ObjectProperty<TabAnimation> closeTabAnimation = new StyleableObjectProperty<TabAnimation>(TabAnimation.GROW) {
! @Override public CssMetaData<TabPane,TabAnimation> getCssMetaData() {
! return StyleableProperties.CLOSE_TAB_ANIMATION;
! }
!
! @Override public Object getBean() {
! return TabPaneSkin.this;
! }
!
! @Override public String getName() {
! return "closeTabAnimation";
! }
! };
!
!
!
! /***************************************************************************
! * *
! * Public API *
! * *
! **************************************************************************/
!
! /** {@inheritDoc} */
! @Override public void dispose() {
! super.dispose();
!
! if (behavior != null) {
! behavior.dispose();
! }
! }
!
! /** {@inheritDoc} */
! @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
! // The TabPane can only be as wide as it widest content width.
! for (TabContentRegion contentRegion: tabContentRegions) {
! maxw = Math.max(maxw, snapSize(contentRegion.prefWidth(-1)));
! }
!
! final boolean isHorizontal = isHorizontal();
! final double tabHeaderAreaSize = snapSize(isHorizontal ?
! tabHeaderArea.prefWidth(-1) : tabHeaderArea.prefHeight(-1));
!
! double prefWidth = isHorizontal ?
! Math.max(maxw, tabHeaderAreaSize) : maxw + tabHeaderAreaSize;
! return snapSize(prefWidth) + rightInset + leftInset;
! }
!
! /** {@inheritDoc} */
! @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
! // The TabPane can only be as high as it highest content height.
! for (TabContentRegion contentRegion: tabContentRegions) {
! maxh = Math.max(maxh, snapSize(contentRegion.prefHeight(-1)));
! }
!
! final boolean isHorizontal = isHorizontal();
! final double tabHeaderAreaSize = snapSize(isHorizontal ?
! tabHeaderArea.prefHeight(-1) : tabHeaderArea.prefWidth(-1));
!
! double prefHeight = isHorizontal ?
! maxh + snapSize(tabHeaderAreaSize) : Math.max(maxh, tabHeaderAreaSize);
! return snapSize(prefHeight) + topInset + bottomInset;
! }
!
! /** {@inheritDoc} */
! @Override public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
! Side tabPosition = getSkinnable().getSide();
! if (tabPosition == Side.TOP) {
! return tabHeaderArea.getBaselineOffset() + topInset;
! }
! return 0;
! }
!
! /** {@inheritDoc} */
! @Override protected void layoutChildren(final double x, final double y,
! final double w, final double h) {
! TabPane tabPane = getSkinnable();
! Side tabPosition = tabPane.getSide();
!
! double headerHeight = snapSize(tabHeaderArea.prefHeight(-1));
! double tabsStartX = tabPosition.equals(Side.RIGHT)? x + w - headerHeight : x;
! double tabsStartY = tabPosition.equals(Side.BOTTOM)? y + h - headerHeight : y;
!
! if (tabPosition == Side.TOP) {
! tabHeaderArea.resize(w, headerHeight);
! tabHeaderArea.relocate(tabsStartX, tabsStartY);
! tabHeaderArea.getTransforms().clear();
! tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.TOP)));
! } else if (tabPosition == Side.BOTTOM) {
! tabHeaderArea.resize(w, headerHeight);
! tabHeaderArea.relocate(w, tabsStartY - headerHeight);
! tabHeaderArea.getTransforms().clear();
! tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.BOTTOM), 0, headerHeight));
! } else if (tabPosition == Side.LEFT) {
! tabHeaderArea.resize(h, headerHeight);
! tabHeaderArea.relocate(tabsStartX + headerHeight, h - headerHeight);
! tabHeaderArea.getTransforms().clear();
! tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.LEFT), 0, headerHeight));
! } else if (tabPosition == Side.RIGHT) {
! tabHeaderArea.resize(h, headerHeight);
! tabHeaderArea.relocate(tabsStartX, y - headerHeight);
! tabHeaderArea.getTransforms().clear();
! tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.RIGHT), 0, headerHeight));
! }
!
! tabHeaderAreaClipRect.setX(0);
! tabHeaderAreaClipRect.setY(0);
! if (isHorizontal()) {
! tabHeaderAreaClipRect.setWidth(w);
! } else {
! tabHeaderAreaClipRect.setWidth(h);
! }
! tabHeaderAreaClipRect.setHeight(headerHeight);
!
! // ==================================
! // position the tab content for the selected tab only
! // ==================================
! // if the tabs are on the left, the content needs to be indented
! double contentStartX = 0;
! double contentStartY = 0;
!
! if (tabPosition == Side.TOP) {
! contentStartX = x;
! contentStartY = y + headerHeight;
! if (isFloatingStyleClass()) {
! // This is to hide the top border content
! contentStartY -= 1;
! }
! } else if (tabPosition == Side.BOTTOM) {
! contentStartX = x;
! contentStartY = y;
! if (isFloatingStyleClass()) {
! // This is to hide the bottom border content
! contentStartY = 1;
! }
! } else if (tabPosition == Side.LEFT) {
! contentStartX = x + headerHeight;
! contentStartY = y;
! if (isFloatingStyleClass()) {
! // This is to hide the left border content
! contentStartX -= 1;
! }
! } else if (tabPosition == Side.RIGHT) {
! contentStartX = x;
! contentStartY = y;
! if (isFloatingStyleClass()) {
! // This is to hide the right border content
! contentStartX = 1;
! }
! }
!
! double contentWidth = w - (isHorizontal() ? 0 : headerHeight);
! double contentHeight = h - (isHorizontal() ? headerHeight: 0);
!
! for (int i = 0, max = tabContentRegions.size(); i < max; i++) {
! TabContentRegion tabContent = tabContentRegions.get(i);
!
! tabContent.setAlignment(Pos.TOP_LEFT);
! if (tabContent.getClip() != null) {
! ((Rectangle)tabContent.getClip()).setWidth(contentWidth);
! ((Rectangle)tabContent.getClip()).setHeight(contentHeight);
! }
!
! // we need to size all tabs, even if they aren't visible. For example,
! // see RT-29167
! tabContent.resize(contentWidth, contentHeight);
! tabContent.relocate(contentStartX, contentStartY);
! }
! }
!
!
!
! /***************************************************************************
! * *
! * Private implementation *
! * *
! **************************************************************************/
!
! private static int getRotation(Side pos) {
! switch (pos) {
! case TOP:
! return 0;
! case BOTTOM:
! return 180;
! case LEFT:
! return -90;
! case RIGHT:
! return 90;
! default:
! return 0;
! }
! }
!
! /**
! * VERY HACKY - this lets us 'duplicate' Label and ImageView nodes to be used in a
! * Tab and the tabs menu at the same time.
! */
! private static Node clone(Node n) {
! if (n == null) {
! return null;
! }
! if (n instanceof ImageView) {
! ImageView iv = (ImageView) n;
! ImageView imageview = new ImageView();
! imageview.setImage(iv.getImage());
! return imageview;
}
+ if (n instanceof Label) {
+ Label l = (Label)n;
+ Label label = new Label(l.getText(), l.getGraphic());
+ return label;
}
return null;
}
private void removeTabs(List<? extends Tab> removedList) {
for (final Tab tab : removedList) {
stopCurrentAnimation(tab);
// Animate the tab removal
final TabHeaderSkin tabRegion = tabHeaderArea.getTabHeaderSkin(tab);
*** 401,411 ****
// now only remove the tabs that are not in the tabsToAdd list
tabsToRemove.removeAll(tabsToAdd);
removeTabs(tabsToRemove);
// and add in any new tabs (that we don't already have showing)
! if (! tabsToAdd.isEmpty()) {
for (TabContentRegion tabContentRegion : tabContentRegions) {
Tab tab = tabContentRegion.getTab();
TabHeaderSkin tabHeader = tabHeaderArea.getTabHeaderSkin(tab);
if (!tabHeader.isClosing && tabsToAdd.contains(tabContentRegion.getTab())) {
tabsToAdd.remove(tabContentRegion.getTab());
--- 610,620 ----
// now only remove the tabs that are not in the tabsToAdd list
tabsToRemove.removeAll(tabsToAdd);
removeTabs(tabsToRemove);
// and add in any new tabs (that we don't already have showing)
! if (!tabsToAdd.isEmpty()) {
for (TabContentRegion tabContentRegion : tabContentRegions) {
Tab tab = tabContentRegion.getTab();
TabHeaderSkin tabHeader = tabHeaderArea.getTabHeaderSkin(tab);
if (!tabHeader.isClosing && tabsToAdd.contains(tabContentRegion.getTab())) {
tabsToAdd.remove(tabContentRegion.getTab());
*** 461,622 ****
Side tabPosition = getSkinnable().getSide();
return Side.TOP.equals(tabPosition) || Side.BOTTOM.equals(tabPosition);
}
private void initializeSwipeHandlers() {
! if (IS_TOUCH_SUPPORTED) {
getSkinnable().addEventHandler(SwipeEvent.SWIPE_LEFT, t -> {
! getBehavior().selectNextTab();
});
getSkinnable().addEventHandler(SwipeEvent.SWIPE_RIGHT, t -> {
! getBehavior().selectPreviousTab();
});
}
}
//TODO need to cache this.
private boolean isFloatingStyleClass() {
return getSkinnable().getStyleClass().contains(TabPane.STYLE_CLASS_FLOATING);
}
- private double maxw = 0.0d;
- @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
- // The TabPane can only be as wide as it widest content width.
- for (TabContentRegion contentRegion: tabContentRegions) {
- maxw = Math.max(maxw, snapSize(contentRegion.prefWidth(-1)));
- }
-
- final boolean isHorizontal = isHorizontal();
- final double tabHeaderAreaSize = snapSize(isHorizontal ?
- tabHeaderArea.prefWidth(-1) : tabHeaderArea.prefHeight(-1));
-
- double prefWidth = isHorizontal ?
- Math.max(maxw, tabHeaderAreaSize) : maxw + tabHeaderAreaSize;
- return snapSize(prefWidth) + rightInset + leftInset;
- }
-
- private double maxh = 0.0d;
- @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
- // The TabPane can only be as high as it highest content height.
- for (TabContentRegion contentRegion: tabContentRegions) {
- maxh = Math.max(maxh, snapSize(contentRegion.prefHeight(-1)));
- }
-
- final boolean isHorizontal = isHorizontal();
- final double tabHeaderAreaSize = snapSize(isHorizontal ?
- tabHeaderArea.prefHeight(-1) : tabHeaderArea.prefWidth(-1));
-
- double prefHeight = isHorizontal ?
- maxh + snapSize(tabHeaderAreaSize) : Math.max(maxh, tabHeaderAreaSize);
- return snapSize(prefHeight) + topInset + bottomInset;
- }
-
- @Override public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
- Side tabPosition = getSkinnable().getSide();
- if (tabPosition == Side.TOP) {
- return tabHeaderArea.getBaselineOffset() + topInset;
- }
- return 0;
- }
-
- @Override protected void layoutChildren(final double x, final double y,
- final double w, final double h) {
- TabPane tabPane = getSkinnable();
- Side tabPosition = tabPane.getSide();
-
- double headerHeight = snapSize(tabHeaderArea.prefHeight(-1));
- double tabsStartX = tabPosition.equals(Side.RIGHT)? x + w - headerHeight : x;
- double tabsStartY = tabPosition.equals(Side.BOTTOM)? y + h - headerHeight : y;
-
- if (tabPosition == Side.TOP) {
- tabHeaderArea.resize(w, headerHeight);
- tabHeaderArea.relocate(tabsStartX, tabsStartY);
- tabHeaderArea.getTransforms().clear();
- tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.TOP)));
- } else if (tabPosition == Side.BOTTOM) {
- tabHeaderArea.resize(w, headerHeight);
- tabHeaderArea.relocate(w, tabsStartY - headerHeight);
- tabHeaderArea.getTransforms().clear();
- tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.BOTTOM), 0, headerHeight));
- } else if (tabPosition == Side.LEFT) {
- tabHeaderArea.resize(h, headerHeight);
- tabHeaderArea.relocate(tabsStartX + headerHeight, h - headerHeight);
- tabHeaderArea.getTransforms().clear();
- tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.LEFT), 0, headerHeight));
- } else if (tabPosition == Side.RIGHT) {
- tabHeaderArea.resize(h, headerHeight);
- tabHeaderArea.relocate(tabsStartX, y - headerHeight);
- tabHeaderArea.getTransforms().clear();
- tabHeaderArea.getTransforms().add(new Rotate(getRotation(Side.RIGHT), 0, headerHeight));
- }
-
- tabHeaderAreaClipRect.setX(0);
- tabHeaderAreaClipRect.setY(0);
- if (isHorizontal()) {
- tabHeaderAreaClipRect.setWidth(w);
- } else {
- tabHeaderAreaClipRect.setWidth(h);
- }
- tabHeaderAreaClipRect.setHeight(headerHeight);
-
- // ==================================
- // position the tab content for the selected tab only
- // ==================================
- // if the tabs are on the left, the content needs to be indented
- double contentStartX = 0;
- double contentStartY = 0;
-
- if (tabPosition == Side.TOP) {
- contentStartX = x;
- contentStartY = y + headerHeight;
- if (isFloatingStyleClass()) {
- // This is to hide the top border content
- contentStartY -= 1;
- }
- } else if (tabPosition == Side.BOTTOM) {
- contentStartX = x;
- contentStartY = y;
- if (isFloatingStyleClass()) {
- // This is to hide the bottom border content
- contentStartY = 1;
- }
- } else if (tabPosition == Side.LEFT) {
- contentStartX = x + headerHeight;
- contentStartY = y;
- if (isFloatingStyleClass()) {
- // This is to hide the left border content
- contentStartX -= 1;
- }
- } else if (tabPosition == Side.RIGHT) {
- contentStartX = x;
- contentStartY = y;
- if (isFloatingStyleClass()) {
- // This is to hide the right border content
- contentStartX = 1;
- }
- }
-
- double contentWidth = w - (isHorizontal() ? 0 : headerHeight);
- double contentHeight = h - (isHorizontal() ? headerHeight: 0);
-
- for (int i = 0, max = tabContentRegions.size(); i < max; i++) {
- TabContentRegion tabContent = tabContentRegions.get(i);
-
- tabContent.setAlignment(Pos.TOP_LEFT);
- if (tabContent.getClip() != null) {
- ((Rectangle)tabContent.getClip()).setWidth(contentWidth);
- ((Rectangle)tabContent.getClip()).setHeight(contentHeight);
- }
- // we need to size all tabs, even if they aren't visible. For example,
- // see RT-29167
- tabContent.resize(contentWidth, contentHeight);
- tabContent.relocate(contentStartX, contentStartY);
- }
- }
/**
* Super-lazy instantiation pattern from Bill Pugh.
* @treatAsPrivate implementation detail
*/
--- 670,702 ----
Side tabPosition = getSkinnable().getSide();
return Side.TOP.equals(tabPosition) || Side.BOTTOM.equals(tabPosition);
}
private void initializeSwipeHandlers() {
! if (Properties.IS_TOUCH_SUPPORTED) {
getSkinnable().addEventHandler(SwipeEvent.SWIPE_LEFT, t -> {
! behavior.selectNextTab();
});
getSkinnable().addEventHandler(SwipeEvent.SWIPE_RIGHT, t -> {
! behavior.selectPreviousTab();
});
}
}
//TODO need to cache this.
private boolean isFloatingStyleClass() {
return getSkinnable().getStyleClass().contains(TabPane.STYLE_CLASS_FLOATING);
}
+ /***************************************************************************
+ * *
+ * CSS *
+ * *
+ **************************************************************************/
/**
* Super-lazy instantiation pattern from Bill Pugh.
* @treatAsPrivate implementation detail
*/
*** 661,671 ****
}
}
/**
! * @return The CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
--- 741,751 ----
}
}
/**
! * Returns the CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
*** 675,684 ****
--- 755,772 ----
*/
@Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return getClassCssMetaData();
}
+
+
+ /***************************************************************************
+ * *
+ * Support classes *
+ * *
+ **************************************************************************/
+
/**************************************************************************
*
* TabHeaderArea: Area responsible for painting all tabs
*
**************************************************************************/
*** 1093,1103 ****
positionInArea(controlButtons, controlStartX, controlStartY, btnWidth, btnHeight,
/*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
}
} /* End TabHeaderArea */
! static int CLOSE_BTN_SIZE = 16;
/**************************************************************************
*
* TabHeaderSkin: skin for each tab
*
--- 1181,1192 ----
positionInArea(controlButtons, controlStartX, controlStartY, btnWidth, btnHeight,
/*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
}
} /* End TabHeaderArea */
!
!
/**************************************************************************
*
* TabHeaderSkin: skin for each tab
*
*** 1115,1129 ****
private Tooltip tooltip;
private Rectangle clip;
private boolean isClosing = false;
! private MultiplePropertyChangeListenerHandler listener =
! new MultiplePropertyChangeListenerHandler(param -> {
! handlePropertyChanged(param);
! return null;
! });
private final ListChangeListener<String> styleClassListener = new ListChangeListener<String>() {
@Override
public void onChanged(Change<? extends String> c) {
getStyleClass().setAll(tab.getStyleClass());
--- 1204,1214 ----
private Tooltip tooltip;
private Rectangle clip;
private boolean isClosing = false;
! private LambdaMultiplePropertyChangeListenerHandler listener = new LambdaMultiplePropertyChangeListenerHandler();
private final ListChangeListener<String> styleClassListener = new ListChangeListener<String>() {
@Override
public void onChanged(Change<? extends String> c) {
getStyleClass().setAll(tab.getStyleClass());
*** 1156,1166 ****
@Override
public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
switch (action) {
case FIRE: {
Tab tab = getTab();
- TabPaneBehavior behavior = getBehavior();
if (behavior.canCloseTab(tab)) {
behavior.closeTab(tab);
setOnMousePressed(null);
}
}
--- 1241,1250 ----
*** 1170,1182 ****
};
closeBtn.setAccessibleRole(AccessibleRole.BUTTON);
closeBtn.setAccessibleText(getString("Accessibility.title.TabPane.CloseButton"));
closeBtn.getStyleClass().setAll("tab-close-button");
closeBtn.setOnMousePressed(new EventHandler<MouseEvent>() {
! @Override public void handle(MouseEvent me) {
Tab tab = getTab();
- TabPaneBehavior behavior = getBehavior();
if (behavior.canCloseTab(tab)) {
behavior.closeTab(tab);
setOnMousePressed(null);
}
}
--- 1254,1266 ----
};
closeBtn.setAccessibleRole(AccessibleRole.BUTTON);
closeBtn.setAccessibleText(getString("Accessibility.title.TabPane.CloseButton"));
closeBtn.getStyleClass().setAll("tab-close-button");
closeBtn.setOnMousePressed(new EventHandler<MouseEvent>() {
! @Override
! public void handle(MouseEvent me) {
Tab tab = getTab();
if (behavior.canCloseTab(tab)) {
behavior.closeTab(tab);
setOnMousePressed(null);
}
}
*** 1284,1417 ****
if (tooltip != null) {
Tooltip.install(this, tooltip);
oldTooltip = tooltip;
}
! listener.registerChangeListener(tab.closableProperty(), "CLOSABLE");
! listener.registerChangeListener(tab.selectedProperty(), "SELECTED");
! listener.registerChangeListener(tab.textProperty(), "TEXT");
! listener.registerChangeListener(tab.graphicProperty(), "GRAPHIC");
! listener.registerChangeListener(tab.contextMenuProperty(), "CONTEXT_MENU");
! listener.registerChangeListener(tab.tooltipProperty(), "TOOLTIP");
! listener.registerChangeListener(tab.disableProperty(), "DISABLE");
! listener.registerChangeListener(tab.styleProperty(), "STYLE");
!
! tab.getStyleClass().addListener(weakStyleClassListener);
!
! listener.registerChangeListener(getSkinnable().tabClosingPolicyProperty(), "TAB_CLOSING_POLICY");
! listener.registerChangeListener(getSkinnable().sideProperty(), "SIDE");
! listener.registerChangeListener(getSkinnable().rotateGraphicProperty(), "ROTATE_GRAPHIC");
! listener.registerChangeListener(getSkinnable().tabMinWidthProperty(), "TAB_MIN_WIDTH");
! listener.registerChangeListener(getSkinnable().tabMaxWidthProperty(), "TAB_MAX_WIDTH");
! listener.registerChangeListener(getSkinnable().tabMinHeightProperty(), "TAB_MIN_HEIGHT");
! listener.registerChangeListener(getSkinnable().tabMaxHeightProperty(), "TAB_MAX_HEIGHT");
!
! getProperties().put(Tab.class, tab);
! getProperties().put(ContextMenu.class, tab.getContextMenu());
!
! setOnContextMenuRequested((ContextMenuEvent me) -> {
! if (getTab().getContextMenu() != null) {
! getTab().getContextMenu().show(inner, me.getScreenX(), me.getScreenY());
! me.consume();
! }
! });
! setOnMousePressed(new EventHandler<MouseEvent>() {
! @Override public void handle(MouseEvent me) {
! if (getTab().isDisable()) {
! return;
! }
! if (me.getButton().equals(MouseButton.MIDDLE)) {
! if (showCloseButton()) {
! Tab tab = getTab();
! TabPaneBehavior behavior = getBehavior();
! if (behavior.canCloseTab(tab)) {
! removeListeners(tab);
! behavior.closeTab(tab);
! }
! }
! } else if (me.getButton().equals(MouseButton.PRIMARY)) {
! getBehavior().selectTab(getTab());
! }
! }
! });
!
! // initialize pseudo-class state
! pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
! pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
! final Side side = getSkinnable().getSide();
! pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
! pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
! pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
! pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
! }
!
! private void handlePropertyChanged(final String p) {
! // --- Tab properties
! if ("CLOSABLE".equals(p)) {
inner.requestLayout();
requestLayout();
! } else if ("SELECTED".equals(p)) {
pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
// Need to request a layout pass for inner because if the width
// and height didn't not change the label or close button may have
// changed.
inner.requestLayout();
requestLayout();
! } else if ("TEXT".equals(p)) {
! label.setText(getTab().getText());
! } else if ("GRAPHIC".equals(p)) {
! label.setGraphic(getTab().getGraphic());
! } else if ("CONTEXT_MENU".equals(p)) {
! // todo
! } else if ("TOOLTIP".equals(p)) {
// uninstall the old tooltip
if (oldTooltip != null) {
Tooltip.uninstall(this, oldTooltip);
}
tooltip = tab.getTooltip();
if (tooltip != null) {
// install new tooltip and save as old tooltip.
Tooltip.install(this, tooltip);
oldTooltip = tooltip;
}
! } else if ("DISABLE".equals(p)) {
pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
inner.requestLayout();
requestLayout();
! } else if ("STYLE".equals(p)) {
! setStyle(tab.getStyle());
! }
! // --- Skinnable properties
! else if ("TAB_CLOSING_POLICY".equals(p)) {
inner.requestLayout();
requestLayout();
! } else if ("SIDE".equals(p)) {
final Side side = getSkinnable().getSide();
pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
inner.setRotate(side == Side.BOTTOM ? 180.0F : 0.0F);
if (getSkinnable().isRotateGraphic()) {
updateGraphicRotation();
}
! } else if ("ROTATE_GRAPHIC".equals(p)) {
! updateGraphicRotation();
! } else if ("TAB_MIN_WIDTH".equals(p)) {
requestLayout();
getSkinnable().requestLayout();
! } else if ("TAB_MAX_WIDTH".equals(p)) {
requestLayout();
getSkinnable().requestLayout();
! } else if ("TAB_MIN_HEIGHT".equals(p)) {
requestLayout();
getSkinnable().requestLayout();
! } else if ("TAB_MAX_HEIGHT".equals(p)) {
requestLayout();
getSkinnable().requestLayout();
}
}
private void updateGraphicRotation() {
if (label.getGraphic() != null) {
label.getGraphic().setRotate(getSkinnable().isRotateGraphic() ? 0.0F :
--- 1368,1481 ----
if (tooltip != null) {
Tooltip.install(this, tooltip);
oldTooltip = tooltip;
}
! listener.registerChangeListener(tab.closableProperty(), e -> {
inner.requestLayout();
requestLayout();
! });
! listener.registerChangeListener(tab.selectedProperty(), e -> {
pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
// Need to request a layout pass for inner because if the width
// and height didn't not change the label or close button may have
// changed.
inner.requestLayout();
requestLayout();
! });
! listener.registerChangeListener(tab.textProperty(),e -> label.setText(getTab().getText()));
! listener.registerChangeListener(tab.graphicProperty(), e -> label.setGraphic(getTab().getGraphic()));
! listener.registerChangeListener(tab.tooltipProperty(), e -> {
// uninstall the old tooltip
if (oldTooltip != null) {
Tooltip.uninstall(this, oldTooltip);
}
tooltip = tab.getTooltip();
if (tooltip != null) {
// install new tooltip and save as old tooltip.
Tooltip.install(this, tooltip);
oldTooltip = tooltip;
}
! });
! listener.registerChangeListener(tab.disableProperty(), e -> {
pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
inner.requestLayout();
requestLayout();
! });
! listener.registerChangeListener(tab.styleProperty(), e -> setStyle(tab.getStyle()));
! tab.getStyleClass().addListener(weakStyleClassListener);
!
! listener.registerChangeListener(getSkinnable().tabClosingPolicyProperty(),e -> {
inner.requestLayout();
requestLayout();
! });
! listener.registerChangeListener(getSkinnable().sideProperty(),e -> {
final Side side = getSkinnable().getSide();
pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
inner.setRotate(side == Side.BOTTOM ? 180.0F : 0.0F);
if (getSkinnable().isRotateGraphic()) {
updateGraphicRotation();
}
! });
! listener.registerChangeListener(getSkinnable().rotateGraphicProperty(), e -> updateGraphicRotation());
! listener.registerChangeListener(getSkinnable().tabMinWidthProperty(), e -> {
requestLayout();
getSkinnable().requestLayout();
! });
! listener.registerChangeListener(getSkinnable().tabMaxWidthProperty(), e -> {
requestLayout();
getSkinnable().requestLayout();
! });
! listener.registerChangeListener(getSkinnable().tabMinHeightProperty(), e -> {
requestLayout();
getSkinnable().requestLayout();
! });
! listener.registerChangeListener(getSkinnable().tabMaxHeightProperty(), e -> {
requestLayout();
getSkinnable().requestLayout();
+ });
+
+ getProperties().put(Tab.class, tab);
+ getProperties().put(ContextMenu.class, tab.getContextMenu());
+
+ setOnContextMenuRequested((ContextMenuEvent me) -> {
+ if (getTab().getContextMenu() != null) {
+ getTab().getContextMenu().show(inner, me.getScreenX(), me.getScreenY());
+ me.consume();
+ }
+ });
+ setOnMousePressed(new EventHandler<MouseEvent>() {
+ @Override public void handle(MouseEvent me) {
+ if (getTab().isDisable()) {
+ return;
+ }
+ if (me.getButton().equals(MouseButton.MIDDLE)) {
+ if (showCloseButton()) {
+ Tab tab = getTab();
+ if (behavior.canCloseTab(tab)) {
+ removeListeners(tab);
+ behavior.closeTab(tab);
+ }
+ }
+ } else if (me.getButton().equals(MouseButton.PRIMARY)) {
+ behavior.selectTab(getTab());
+ }
}
+ });
+
+ // initialize pseudo-class state
+ pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, tab.isSelected());
+ pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, tab.isDisable());
+ final Side side = getSkinnable().getSide();
+ pseudoClassStateChanged(TOP_PSEUDOCLASS_STATE, (side == Side.TOP));
+ pseudoClassStateChanged(RIGHT_PSEUDOCLASS_STATE, (side == Side.RIGHT));
+ pseudoClassStateChanged(BOTTOM_PSEUDOCLASS_STATE, (side == Side.BOTTOM));
+ pseudoClassStateChanged(LEFT_PSEUDOCLASS_STATE, (side == Side.LEFT));
}
private void updateGraphicRotation() {
if (label.getGraphic() != null) {
label.getGraphic().setRotate(getSkinnable().isRotateGraphic() ? 0.0F :