javafx.scene.control.Tab Maven / Gradle / Ivy
/*
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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;
import com.sun.javafx.beans.IDProperty;
import com.sun.javafx.scene.control.ControlAcceleratorSupport;
import javafx.collections.ObservableSet;
import javafx.css.CssMetaData;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.css.Styleable;
import com.sun.javafx.event.EventHandlerManager;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import javafx.beans.DefaultProperty;
import javafx.beans.InvalidationListener;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.ObservableMap;
/**
* Tabs are placed within a {@link TabPane}, where each tab represents a single
* 'page'.
* Tabs can contain any {@link Node} such as UI controls or groups
* of nodes added to a layout container.
* When the user clicks
* on a Tab in the TabPane the Tab content becomes visible to the user.
* @since JavaFX 2.0
*/
@DefaultProperty("content")
@IDProperty("id")
public class Tab implements EventTarget, Styleable {
/* *************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates a tab with no title.
*/
public Tab() {
this(null);
}
/**
* Creates a tab with a text title.
*
* @param text The title of the tab.
*/
public Tab(String text) {
this(text, null);
}
/**
* Creates a tab with a text title and the specified content node.
*
* @param text The title of the tab.
* @param content The content of the tab.
* @since JavaFX 8u40
*/
public Tab(String text, Node content) {
setText(text);
setContent(content);
styleClass.addAll(DEFAULT_STYLE_CLASS);
}
/* *************************************************************************
* *
* Properties *
* *
**************************************************************************/
private StringProperty id;
/**
* Sets the id of this tab. This simple string identifier is useful for
* finding a specific Tab within the {@code TabPane}. The default value is {@code null}.
* @param value the id of this tab
*/
public final void setId(String value) { idProperty().set(value); }
/**
* The id of this tab.
*
* @return The id of the tab.
*/
@Override
public final String getId() { return id == null ? null : id.get(); }
/**
* The id of this tab.
* @return the id property of this tab
*/
public final StringProperty idProperty() {
if (id == null) {
id = new SimpleStringProperty(this, "id");
}
return id;
}
private StringProperty style;
/**
* A string representation of the CSS style associated with this
* tab. This is analogous to the "style" attribute of an
* HTML element. Note that, like the HTML style attribute, this
* variable contains style properties and values and not the
* selector portion of a style rule.
*
* Parsing this style might not be supported on some limited
* platforms. It is recommended to use a standalone CSS file instead.
*
* @param value the style string
*/
public final void setStyle(String value) { styleProperty().set(value); }
/**
* The CSS style string associated to this tab.
*
* @return The CSS style string associated to this tab.
*/
@Override
public final String getStyle() { return style == null ? null : style.get(); }
/**
* The CSS style string associated to this tab.
* @return the CSS style string property associated to this tab
*/
public final StringProperty styleProperty() {
if (style == null) {
style = new SimpleStringProperty(this, "style");
}
return style;
}
private ReadOnlyBooleanWrapper selected;
final void setSelected(boolean value) {
selectedPropertyImpl().set(value);
}
/**
*
Represents whether this tab is the currently selected tab,
* To change the selected Tab use {@code tabPane.getSelectionModel().select()}
*
* @return true if selected
*/
public final boolean isSelected() {
return selected == null ? false : selected.get();
}
/**
* The currently selected tab.
* @return the selected tab
*/
public final ReadOnlyBooleanProperty selectedProperty() {
return selectedPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper selectedPropertyImpl() {
if (selected == null) {
selected = new ReadOnlyBooleanWrapper() {
@Override protected void invalidated() {
if (getOnSelectionChanged() != null) {
Event.fireEvent(Tab.this, new Event(SELECTION_CHANGED_EVENT));
}
}
@Override
public Object getBean() {
return Tab.this;
}
@Override
public String getName() {
return "selected";
}
};
}
return selected;
}
private ReadOnlyObjectWrapper tabPane;
final void setTabPane(TabPane value) {
tabPanePropertyImpl().set(value);
}
/**
* A reference to the TabPane that contains this tab instance.
* @return the TabPane
*/
public final TabPane getTabPane() {
return tabPane == null ? null : tabPane.get();
}
/**
* The TabPane that contains this tab.
* @return the TabPane property
*/
public final ReadOnlyObjectProperty tabPaneProperty() {
return tabPanePropertyImpl().getReadOnlyProperty();
}
private ReadOnlyObjectWrapper tabPanePropertyImpl() {
if (tabPane == null) {
tabPane = new ReadOnlyObjectWrapper<>(this, "tabPane") {
private WeakReference oldParent;
@Override protected void invalidated() {
if(oldParent != null && oldParent.get() != null) {
oldParent.get().disabledProperty().removeListener(parentDisabledChangedListener);
}
updateDisabled();
TabPane newParent = get();
if (newParent != null) {
newParent.disabledProperty().addListener(parentDisabledChangedListener);
}
oldParent = new WeakReference<>(newParent);
super.invalidated();
}
};
}
return tabPane;
}
private final InvalidationListener parentDisabledChangedListener = valueModel -> {
updateDisabled();
};
private StringProperty text;
/**
* Sets the text to show in the tab to allow the user to differentiate between
* the function of each tab. The text is always visible
*
* @param value the text string
*/
public final void setText(String value) {
textProperty().set(value);
}
/**
* The text shown in the tab.
*
* @return The text shown in the tab.
*/
public final String getText() {
return text == null ? null : text.get();
}
/**
* The text shown in the tab.
* @return the text property
*/
public final StringProperty textProperty() {
if (text == null) {
text = new SimpleStringProperty(this, "text");
}
return text;
}
private ObjectProperty graphic;
/**
* Sets the graphic to show in the tab to allow the user to differentiate
* between the function of each tab. By default the graphic does not rotate
* based on the TabPane.tabPosition value, but it can be set to rotate by
* setting TabPane.rotateGraphic to true.
* @param value the graphic node
*/
public final void setGraphic(Node value) {
graphicProperty().set(value);
}
/**
* The graphic shown in the tab.
*
* @return The graphic shown in the tab.
*/
public final Node getGraphic() {
return graphic == null ? null : graphic.get();
}
/**
* The graphic in the tab.
*
* @return The graphic in the tab.
*/
public final ObjectProperty graphicProperty() {
if (graphic == null) {
graphic = new SimpleObjectProperty<>(this, "graphic");
}
return graphic;
}
private ObjectProperty content;
/**
* The content to show within the main TabPane area. The content
* can be any Node such as UI controls or groups of nodes added
* to a layout container.
* @param value the content node
*/
public final void setContent(Node value) {
contentProperty().set(value);
}
/**
* The content associated with the tab.
*
* @return The content associated with the tab.
*/
public final Node getContent() {
return content == null ? null : content.get();
}
/**
* The content associated with the tab.
* @return the content property
*/
public final ObjectProperty contentProperty() {
if (content == null) {
content = new SimpleObjectProperty<>(this, "content") {
@Override protected void invalidated() {
updateDisabled();
}
};
}
return content;
}
private ObjectProperty contextMenu;
/**
* Specifies the context menu to show when the user right-clicks on the tab.
*
* @param value the context menu
*/
public final void setContextMenu(ContextMenu value) {
contextMenuProperty().set(value);
}
/**
* The context menu associated with the tab.
* @return The context menu associated with the tab.
*/
public final ContextMenu getContextMenu() {
return contextMenu == null ? null : contextMenu.get();
}
/**
* The context menu associated with the tab.
* @return the context menu property
*/
public final ObjectProperty contextMenuProperty() {
if (contextMenu == null) {
contextMenu = new SimpleObjectProperty<>(this, "contextMenu") {
private WeakReference contextMenuRef;
@Override protected void invalidated() {
ContextMenu oldMenu = contextMenuRef == null ? null : contextMenuRef.get();
if (oldMenu != null) {
ControlAcceleratorSupport.removeAcceleratorsFromScene(oldMenu.getItems(), Tab.this);
}
ContextMenu ctx = get();
contextMenuRef = new WeakReference<>(ctx);
if (ctx != null) {
// if a context menu is set, we need to install any accelerators
// belonging to its menu items ASAP into the scene that this
// Control is in (if the control is not in a Scene, we will need
// to wait until it is and then do it).
ControlAcceleratorSupport.addAcceleratorsIntoScene(ctx.getItems(), Tab.this);
}
}
};
}
return contextMenu;
}
private BooleanProperty closable;
/**
* Sets {@code true} if the tab is closable. If this is set to {@code false},
* then regardless of the TabClosingPolicy, it will not be
* possible for the user to close this tab. Therefore, when this
* property is {@code false}, no 'close' button will be shown on the tab.
* The default is {@code true}.
*
* @param value the closable value
*/
public final void setClosable(boolean value) {
closableProperty().set(value);
}
/**
* Returns {@code true} if this tab is closable.
*
* @return {@code true} if the tab is closable.
*/
public final boolean isClosable() {
return closable == null ? true : closable.get();
}
/**
* The closable state for this tab.
* @return the closable property
*/
public final BooleanProperty closableProperty() {
if (closable == null) {
closable = new SimpleBooleanProperty(this, "closable", true);
}
return closable;
}
/**
* Called when the tab becomes selected or unselected.
*/
public static final EventType SELECTION_CHANGED_EVENT =
new EventType<> (Event.ANY, "SELECTION_CHANGED_EVENT");
private ObjectProperty> onSelectionChanged;
/**
* Defines a function to be called when a selection changed has occurred on the tab.
* @param value the on selection changed event handler
*/
public final void setOnSelectionChanged(EventHandler value) {
onSelectionChangedProperty().set(value);
}
/**
* The event handler that is associated with a selection on the tab.
*
* @return The event handler that is associated with a tab selection.
*/
public final EventHandler getOnSelectionChanged() {
return onSelectionChanged == null ? null : onSelectionChanged.get();
}
/**
* The event handler that is associated with a selection on the tab.
* @return the on selection changed event handler property
*/
public final ObjectProperty> onSelectionChangedProperty() {
if (onSelectionChanged == null) {
onSelectionChanged = new ObjectPropertyBase<>() {
@Override protected void invalidated() {
setEventHandler(SELECTION_CHANGED_EVENT, get());
}
@Override
public Object getBean() {
return Tab.this;
}
@Override
public String getName() {
return "onSelectionChanged";
}
};
}
return onSelectionChanged;
}
/**
* Called when a user closes this tab. This is useful for freeing up memory.
*/
public static final EventType CLOSED_EVENT = new EventType<>(Event.ANY, "TAB_CLOSED");
private ObjectProperty> onClosed;
/**
* Defines a function to be called when the tab is closed.
* @param value the on closed event handler
*/
public final void setOnClosed(EventHandler value) {
onClosedProperty().set(value);
}
/**
* The event handler that is associated with the tab when the tab is closed.
*
* @return The event handler that is associated with the tab when the tab is closed.
*/
public final EventHandler getOnClosed() {
return onClosed == null ? null : onClosed.get();
}
/**
* The event handler that is associated with the tab when the tab is closed.
* @return the on closed event handler property
*/
public final ObjectProperty> onClosedProperty() {
if (onClosed == null) {
onClosed = new ObjectPropertyBase<>() {
@Override protected void invalidated() {
setEventHandler(CLOSED_EVENT, get());
}
@Override
public Object getBean() {
return Tab.this;
}
@Override
public String getName() {
return "onClosed";
}
};
}
return onClosed;
}
private ObjectProperty tooltip;
/**
* Specifies the tooltip to show when the user hovers over the tab.
* @param value the tool tip value
*/
public final void setTooltip(Tooltip value) { tooltipProperty().setValue(value); }
/**
* The tooltip associated with this tab.
* @return The tooltip associated with this tab.
*/
public final Tooltip getTooltip() { return tooltip == null ? null : tooltip.getValue(); }
/**
* The tooltip associated with this tab.
* @return the tool tip property
*/
public final ObjectProperty tooltipProperty() {
if (tooltip == null) {
tooltip = new SimpleObjectProperty<>(this, "tooltip");
}
return tooltip;
}
private final ObservableList styleClass = FXCollections.observableArrayList();
private BooleanProperty disable;
/**
* Sets the disabled state of this tab.
*
* @param value the state to set this tab
*
* @defaultValue false
* @since JavaFX 2.2
*/
public final void setDisable(boolean value) {
disableProperty().set(value);
}
/**
* Returns {@code true} if this tab is disable.
* @return true if this tab is disable
* @since JavaFX 2.2
*/
public final boolean isDisable() { return disable == null ? false : disable.get(); }
/**
* Sets the disabled state of this tab. A disable tab is no longer interactive
* or traversable, but the contents remain interactive. A disable tab
* can be selected using {@link TabPane#getSelectionModel()}.
*
* @return the disable property
* @defaultValue false
* @since JavaFX 2.2
*/
public final BooleanProperty disableProperty() {
if (disable == null) {
disable = new BooleanPropertyBase(false) {
@Override
protected void invalidated() {
updateDisabled();
}
@Override
public Object getBean() {
return Tab.this;
}
@Override
public String getName() {
return "disable";
}
};
}
return disable;
}
private ReadOnlyBooleanWrapper disabled;
private final void setDisabled(boolean value) {
disabledPropertyImpl().set(value);
}
/**
* Returns true when the {@code Tab} {@link #disableProperty disable} is set to
* {@code true} or if the {@code TabPane} is disabled.
* @return true if the TabPane is disabled
* @since JavaFX 2.2
*/
public final boolean isDisabled() {
return disabled == null ? false : disabled.get();
}
/**
* Indicates whether or not this {@code Tab} is disabled. A {@code Tab}
* will become disabled if {@link #disableProperty disable} is set to {@code true} on either
* itself or if the {@code TabPane} is disabled.
*
* @return the disabled property
* @defaultValue false
* @since JavaFX 2.2
*/
public final ReadOnlyBooleanProperty disabledProperty() {
return disabledPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper disabledPropertyImpl() {
if (disabled == null) {
disabled = new ReadOnlyBooleanWrapper() {
@Override
public Object getBean() {
return Tab.this;
}
@Override
public String getName() {
return "disabled";
}
};
}
return disabled;
}
private void updateDisabled() {
boolean disabled = isDisable() || (getTabPane() != null && getTabPane().isDisabled());
setDisabled(disabled);
// Fix for RT-24658 - content should be disabled if the tab is disabled
Node content = getContent();
if (content != null) {
content.setDisable(disabled);
}
}
/**
* Called when there is an external request to close this {@code Tab}.
* The installed event handler can prevent tab closing by consuming the
* received event.
* @since JavaFX 8.0
*/
public static final EventType TAB_CLOSE_REQUEST_EVENT = new EventType<> (Event.ANY, "TAB_CLOSE_REQUEST_EVENT");
/**
* Called when there is an external request to close this {@code Tab}.
* The installed event handler can prevent tab closing by consuming the
* received event.
* @since JavaFX 8.0
*/
private ObjectProperty> onCloseRequest;
public final ObjectProperty> onCloseRequestProperty() {
if (onCloseRequest == null) {
onCloseRequest = new ObjectPropertyBase<>() {
@Override protected void invalidated() {
setEventHandler(TAB_CLOSE_REQUEST_EVENT, get());
}
@Override public Object getBean() {
return Tab.this;
}
@Override public String getName() {
return "onCloseRequest";
}
};
}
return onCloseRequest;
}
public EventHandler getOnCloseRequest() {
if( onCloseRequest == null ) {
return null;
}
return onCloseRequest.get();
}
public void setOnCloseRequest(EventHandler value) {
onCloseRequestProperty().set(value);
}
// --- Properties
private static final Object USER_DATA_KEY = new Object();
// A map containing a set of properties for this Tab
private ObservableMap