blob: a8400690a59f188cf14fc94e8f99585d722d465a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.wtk;
import java.util.Iterator;
import org.apache.pivot.beans.DefaultProperty;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.ImmutableIterator;
import org.apache.pivot.util.ListenerList;
import org.apache.pivot.util.Vote;
import org.apache.pivot.wtk.content.ButtonDataRenderer;
import org.apache.pivot.wtk.skin.TabPaneSkin;
/**
* Container that provides access to a set of components via selectable tabs,
* only one of which is visible at a time.
*/
@DefaultProperty("tabs")
public class TabPane extends Container {
/**
* Tab sequence implementation.
*/
public final class TabSequence implements Sequence<Component>, Iterable<Component> {
private TabSequence() {
}
@Override
public int add(Component tab) {
int index = getLength();
insert(tab, index);
return index;
}
@Override
public void insert(Component tab, int index) {
if (tab == null) {
throw new IllegalArgumentException("tab is null.");
}
// Add the tab to the component sequence
TabPane.this.add(tab);
tabs.insert(tab, index);
// Update the selection
int previousSelectedIndex = selectedIndex;
if (selectedIndex >= index) {
selectedIndex++;
}
// Fire insert event
tabPaneListeners.tabInserted(TabPane.this, index);
// Fire selection change event, if necessary
if (selectedIndex != previousSelectedIndex && previousSelectedIndex > -1) {
tabPaneSelectionListeners.selectedIndexChanged(TabPane.this, selectedIndex);
}
}
@Override
public Component update(int index, Component tab) {
throw new UnsupportedOperationException();
}
@Override
public int remove(Component tab) {
int index = indexOf(tab);
if (index != -1) {
remove(index, 1);
}
return index;
}
@Override
public Sequence<Component> remove(int index, int count) {
Sequence<Component> removed;
Vote vote = tabPaneListeners.previewRemoveTabs(TabPane.this, index, count);
if (vote == Vote.APPROVE) {
// Remove the tabs from the tab list
removed = tabs.remove(index, count);
// Update the selection
int previousSelectedIndex = selectedIndex;
if (selectedIndex >= index) {
if (selectedIndex < index + count) {
selectedIndex = -1;
} else {
selectedIndex -= count;
}
}
// Fire remove event
tabPaneListeners.tabsRemoved(TabPane.this, index, removed);
// Remove the tabs from the component list
for (int i = 0, n = removed.getLength(); i < n; i++) {
Component tab = removed.get(i);
TabPane.this.remove(tab);
}
// Fire selection change event, if necessary
if (selectedIndex != previousSelectedIndex && previousSelectedIndex > -1) {
tabPaneSelectionListeners.selectedIndexChanged(TabPane.this, selectedIndex);
}
} else {
removed = null;
tabPaneListeners.removeTabsVetoed(TabPane.this, vote);
}
return removed;
}
@Override
public Component get(int index) {
return tabs.get(index);
}
@Override
public int indexOf(Component tab) {
return tabs.indexOf(tab);
}
@Override
public int getLength() {
return tabs.getLength();
}
@Override
public Iterator<Component> iterator() {
return new ImmutableIterator<>(tabs.iterator());
}
}
/**
* Tab pane skin interface. Tab pane skins must implement this interface to
* facilitate additional communication between the component and the skin.
*/
public interface Skin {
boolean isVisible(int index);
void setVisible(int index, boolean value);
}
private enum Attribute {
TAB_DATA, TOOLTIP_TEXT;
}
private static class TabPaneListenerList extends WTKListenerList<TabPaneListener> implements
TabPaneListener {
@Override
public void tabInserted(TabPane tabPane, int index) {
for (TabPaneListener listener : this) {
listener.tabInserted(tabPane, index);
}
}
@Override
public Vote previewRemoveTabs(TabPane tabPane, int index, int count) {
Vote vote = Vote.APPROVE;
for (TabPaneListener listener : this) {
vote = vote.tally(listener.previewRemoveTabs(tabPane, index, count));
}
return vote;
}
@Override
public void tabsRemoved(TabPane tabPane, int index, Sequence<Component> tabs) {
for (TabPaneListener listener : this) {
listener.tabsRemoved(tabPane, index, tabs);
}
}
@Override
public void removeTabsVetoed(TabPane tabPane, Vote reason) {
for (TabPaneListener listener : this) {
listener.removeTabsVetoed(tabPane, reason);
}
}
@Override
public void cornerChanged(TabPane tabPane, Component previousCorner) {
for (TabPaneListener listener : this) {
listener.cornerChanged(tabPane, previousCorner);
}
}
@Override
public void tabDataRendererChanged(TabPane tabPane,
Button.DataRenderer previousTabDataRenderer) {
for (TabPaneListener listener : this) {
listener.tabDataRendererChanged(tabPane, previousTabDataRenderer);
}
}
@Override
public void closeableChanged(TabPane tabPane) {
for (TabPaneListener listener : this) {
listener.closeableChanged(tabPane);
}
}
@Override
public void collapsibleChanged(TabPane tabPane) {
for (TabPaneListener listener : this) {
listener.collapsibleChanged(tabPane);
}
}
}
private static class TabPaneSelectionListenerList extends
WTKListenerList<TabPaneSelectionListener> implements TabPaneSelectionListener {
@Override
public Vote previewSelectedIndexChange(TabPane tabPane, int selectedIndex) {
Vote vote = Vote.APPROVE;
for (TabPaneSelectionListener listener : this) {
vote = vote.tally(listener.previewSelectedIndexChange(tabPane, selectedIndex));
}
return vote;
}
@Override
public void selectedIndexChangeVetoed(TabPane tabPane, Vote reason) {
for (TabPaneSelectionListener listener : this) {
listener.selectedIndexChangeVetoed(tabPane, reason);
}
}
@Override
public void selectedIndexChanged(TabPane tabPane, int previousSelectedIndex) {
for (TabPaneSelectionListener listener : this) {
listener.selectedIndexChanged(tabPane, previousSelectedIndex);
}
}
}
private static class TabPaneAttributeListenerList extends
WTKListenerList<TabPaneAttributeListener> implements TabPaneAttributeListener {
@Override
public void tabDataChanged(TabPane tabPane, Component component, Object previousTabData) {
for (TabPaneAttributeListener listener : this) {
listener.tabDataChanged(tabPane, component, previousTabData);
}
}
@Override
public void tooltipTextChanged(TabPane tabPane, Component component,
String previousTooltipText) {
for (TabPaneAttributeListener listener : this) {
listener.tooltipTextChanged(tabPane, component, previousTooltipText);
}
}
}
private ArrayList<Component> tabs = new ArrayList<>();
private TabSequence tabSequence = new TabSequence();
private Component corner = null;
private int selectedIndex = -1;
private Button.DataRenderer tabDataRenderer = DEFAULT_TAB_DATA_RENDERER;
private boolean closeable = false;
private boolean collapsible = false;
private TabPaneListenerList tabPaneListeners = new TabPaneListenerList();
private TabPaneSelectionListenerList tabPaneSelectionListeners = new TabPaneSelectionListenerList();
private TabPaneAttributeListenerList tabPaneAttributeListeners = new TabPaneAttributeListenerList();
private static final Button.DataRenderer DEFAULT_TAB_DATA_RENDERER = new ButtonDataRenderer();
public TabPane() {
super();
installSkin(TabPane.class);
}
public TabSequence getTabs() {
return tabSequence;
}
public Component getCorner() {
return corner;
}
public void setCorner(Component corner) {
Component previousCorner = this.corner;
if (previousCorner != corner) {
// Remove any previous corner component
this.corner = null;
if (previousCorner != null) {
remove(previousCorner);
}
// Set the new corner component
if (corner != null) {
insert(corner, 0);
}
this.corner = corner;
tabPaneListeners.cornerChanged(this, previousCorner);
}
}
public int getSelectedIndex() {
return selectedIndex;
}
public void setSelectedIndex(int selectedIndex) {
indexBoundsCheck("selectedIndex", selectedIndex, -1, tabs.getLength() - 1);
int previousSelectedIndex = this.selectedIndex;
if (previousSelectedIndex != selectedIndex) {
Vote vote = tabPaneSelectionListeners.previewSelectedIndexChange(this, selectedIndex);
if (vote == Vote.APPROVE) {
this.selectedIndex = selectedIndex;
tabPaneSelectionListeners.selectedIndexChanged(this, previousSelectedIndex);
} else {
tabPaneSelectionListeners.selectedIndexChangeVetoed(this, vote);
}
}
}
public Component getSelectedTab() {
return (selectedIndex == -1) ? null : tabs.get(selectedIndex);
}
public void setSelectedTab(Component comp) {
if (comp == null) {
setSelectedIndex(-1);
} else {
int index = tabs.indexOf(comp);
if (index < 0) {
throw new IllegalArgumentException("component is not a child of the TabPane");
}
setSelectedIndex(index);
}
}
public Button.DataRenderer getTabDataRenderer() {
return tabDataRenderer;
}
public void setTabDataRenderer(Button.DataRenderer tabDataRenderer) {
if (tabDataRenderer == null) {
throw new IllegalArgumentException();
}
Button.DataRenderer previousTabDataRenderer = this.tabDataRenderer;
if (previousTabDataRenderer != tabDataRenderer) {
this.tabDataRenderer = tabDataRenderer;
tabPaneListeners.tabDataRendererChanged(this, previousTabDataRenderer);
}
}
public boolean isCloseable() {
return closeable;
}
public void setCloseable(boolean closeable) {
if (this.closeable != closeable) {
this.closeable = closeable;
tabPaneListeners.closeableChanged(this);
}
}
/**
* @return <tt>true</tt> if the TabPane is collapsible and no tab is
* selected; <tt>false</tt>, otherwise.
* @see #isCollapsible()
* @see #getSelectedIndex()
*/
public boolean isCollapsed() {
return collapsible && (selectedIndex == -1);
}
/**
* Collapse or expand the TabPane (if it is collapsible).
*
* @param collapsed <tt>true</tt> to collapse, <tt>false</tt> to expand and
* select the first tab. Use {@link #setSelectedIndex(int)} to expand and
* select a specific Tab.
* @see #isCollapsible()
* @see #setSelectedIndex(int)
*/
public void setCollapsed(boolean collapsed) {
if (collapsible && (isCollapsed() != collapsed)) {
int index = (collapsed || tabs.getLength() == 0) ? -1 : 0;
setSelectedIndex(index);
}
}
public boolean isCollapsible() {
return collapsible;
}
public void setCollapsible(boolean collapsible) {
if (this.collapsible != collapsible) {
this.collapsible = collapsible;
tabPaneListeners.collapsibleChanged(this);
}
}
public boolean isTabVisible(int index) {
TabPane.Skin tabPaneSkin = (TabPane.Skin) getSkin();
if (tabPaneSkin instanceof TabPaneSkin) {
return ((TabPaneSkin) tabPaneSkin).isVisible(index);
}
return true;
}
public void setTabVisible(int index, boolean value) {
TabPane.Skin tabPaneSkin = (TabPane.Skin) getSkin();
if (tabPaneSkin instanceof TabPaneSkin) {
((TabPaneSkin) tabPaneSkin).setVisible(index, value);
}
}
@Override
public Sequence<Component> remove(int index, int count) {
for (int i = index, n = index + count; i < n; i++) {
Component component = get(i);
if (component == corner || tabs.indexOf(component) >= 0) {
throw new UnsupportedOperationException();
}
}
// Call the base method to remove the components
return super.remove(index, count);
}
public ListenerList<TabPaneListener> getTabPaneListeners() {
return tabPaneListeners;
}
public ListenerList<TabPaneSelectionListener> getTabPaneSelectionListeners() {
return tabPaneSelectionListeners;
}
public ListenerList<TabPaneAttributeListener> getTabPaneAttributeListeners() {
return tabPaneAttributeListeners;
}
public static Object getTabData(Component component) {
return component.getAttribute(Attribute.TAB_DATA);
}
public static void setTabData(Component component, Object tabData) {
Object previousTabData = component.setAttribute(Attribute.TAB_DATA, tabData);
if (previousTabData != tabData) {
Container parent = component.getParent();
if (parent instanceof TabPane) {
TabPane tabPane = (TabPane) parent;
tabPane.tabPaneAttributeListeners.tabDataChanged(tabPane, component,
previousTabData);
}
}
}
public static String getTooltipText(Component component) {
return (String) component.getAttribute(Attribute.TOOLTIP_TEXT);
}
public static void setTooltipText(Component component, String tooltipText) {
String previousTooltipText = (String) component.setAttribute(Attribute.TOOLTIP_TEXT,
tooltipText);
if (previousTooltipText != tooltipText) {
Container parent = component.getParent();
if (parent instanceof TabPane) {
TabPane tabPane = (TabPane) parent;
tabPane.tabPaneAttributeListeners.tooltipTextChanged(tabPane, component,
previousTooltipText);
}
}
}
}