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 :