/*
* Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.dtv.lwuit;
import com.sun.dtv.lwuit.events.SelectionListener;
import com.sun.dtv.lwuit.geom.Dimension;
import com.sun.dtv.lwuit.geom.Rectangle;
import com.sun.dtv.lwuit.layouts.BorderLayout;
import com.sun.dtv.lwuit.list.DefaultListModel;
import com.sun.dtv.lwuit.list.ListCellRenderer;
import com.sun.dtv.lwuit.plaf.Style;
import com.sun.dtv.lwuit.plaf.UIManager;
import java.util.Hashtable;
/**
* A component that lets the user switch between a group of components by
* clicking on a tab with a given title and/or icon.
*
* <p>
* Tabs/components are added to a <code>TabbedPane</code> object by using the
* <code>addTab</code> and <code>insertTab</code> methods.
* A tab is represented by an index corresponding
* to the position it was added in, where the first tab has an index equal to 0
* and the last tab has an index equal to the tab count minus 1.
* <p>
* The <code>TabbedPane</code> uses a <code>SingleSelectionModel</code>
* to represent the set of tab indices and the currently selected index.
* If the tab count is greater than 0, then there will always be a selected
* index, which by default will be initialized to the first tab.
* If the tab count is 0, then the selected index will be -1.
* <p>
*
* @author Tamir Shabat
*
*/
public class TabbedPane extends Container {
private Container contentPane = new Container(new BorderLayout());
private List tabsList = new List();
private Hashtable tabsTable = new Hashtable();
/**
* Where the tabs are placed.
*/
private int tabPlacement = -1;
/**
* The TabbedPane surrounded border width (contentPane and tabs border)
*/
private int tPBorder = 1;
/**
* Creates an empty <code>TabbedPane</code> with a default
* tab placement of <code>Component.TOP</code>.
*/
public TabbedPane() {
this(TOP);
}
/**
* Creates an empty <code>TabbedPane</code> with the specified tab placement
* of either: <code>Component.TOP</code>, <code>Component.BOTTOM</code>,
* <code>Component.LEFT</code>, or <code>Component.RIGHT</code>.
*
* @param tabPlacement the placement for the tabs relative to the content
*/
public TabbedPane(int tabPlacement) {
super(new BorderLayout());
contentPane.getStyle().setBgPainter(new Painter() {
public void paint(Graphics g, Rectangle rect) {
UIManager.getInstance().getLookAndFeel().
drawTabbedPaneContentPane(TabbedPane.this, g, rect,
tabsList.getPreferredSize(), tabsList.size(),
tabsList.getSelectedIndex(), tabsList.getElementSize(true),
tabsList.getScrollX(), tabsList.getScrollY());
}
});
super.addComponent(BorderLayout.CENTER, contentPane);
setTabPlacement(tabPlacement);
tabsList.getStyle().setPadding(0, 0, 0, 0);
tabsList.getStyle().setMargin(0, 0, 0, 0);
tabsList.getStyle().setBorder(null);
tabsList.setListCellRenderer(new TabsRenderer());
tabsList.setItemGap(0);
tabsList.setIsScrollVisible(false);
tabsList.setSmoothScrolling(false);
tabsList.setBorderGap(0);
tabsList.addSelectionListener(new SelectionListener() {
public void selectionChanged(int oldSelected, int newSelected) {
Component c = (Component) tabsList.getModel().getItemAt(newSelected);
contentPane.removeAll();
contentPane.addComponent("Center", (Component) tabsTable.get(c));
revalidate();
}
});
}
/**
* @inheritDoc
*/
public void setFocusable(boolean b) {
if(tabsList != null) {
tabsList.setFocusable(b);
}
super.setFocusable(b);
}
/**
* This method adds a listener to the tabs.
*
* @param listener a selection listener to gets the selection
* events
*/
public void addTabsListener(SelectionListener listener){
tabsList.addSelectionListener(listener);
}
/**
* @inheritDoc
*/
public void requestFocus() {
tabsList.requestFocus();
}
/**
* @inheritDoc
*/
protected Dimension calcPreferredSize() {
int maxContentW = 0;
int maxContentH = 0;
int maxW = 0;
int maxH = 0;
for (int i = 0; i < tabsList.size(); i++) {
Component tabsComp = (Component) tabsList.getModel().getItemAt(i);
Component contentComp = (Component) tabsTable.get(tabsComp);
if (contentComp.getPreferredSize().getWidth() > maxContentW) {
maxContentW = contentComp.getPreferredSize().getWidth();
}
if (contentComp.getPreferredSize().getHeight() > maxContentH) {
maxContentH = contentComp.getPreferredSize().getHeight();
}
}
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
maxW = maxContentW;
maxH = tabsList.getPreferredSize().getHeight() + maxContentH;
} else {
maxW = tabsList.getPreferredSize().getWidth() + maxContentW;
maxH = maxContentH;
}
return new Dimension(maxW, maxH);
}
/**
* Sets the tab placement for this tabbedpane.
* Possible values are:<ul>
* <li><code>Component.TOP</code>
* <li><code>Component.BOTTOM</code>
* <li><code>Component.LEFT</code>
* <li><code>Component.RIGHT</code>
* </ul>
* The default value, if not set, is <code>Component.TOP</code>.
*
* @param tabPlacement the placement for the tabs relative to the content
*/
public void setTabPlacement(int tabPlacement) {
if (tabPlacement != TOP && tabPlacement != LEFT &&
tabPlacement != BOTTOM && tabPlacement != RIGHT) {
throw new IllegalArgumentException("illegal tab placement: must be TOP, BOTTOM, LEFT, or RIGHT");
}
if (this.tabPlacement == tabPlacement) {
return;
}
this.tabPlacement = tabPlacement;
removeComponent(tabsList);
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
tabsList.setOrientation(List.HORIZONTAL);
if (tabPlacement == TOP) {
super.addComponent(BorderLayout.NORTH, tabsList);
} else if (tabPlacement == BOTTOM) {
super.addComponent(BorderLayout.SOUTH, tabsList);
}
} else {// LEFT Or RIGHT
tabsList.setOrientation(List.VERTICAL);
if (tabPlacement == LEFT) {
super.addComponent(BorderLayout.WEST, tabsList);
} else {// RIGHT
super.addComponent(BorderLayout.EAST, tabsList);
}
}
tabsList.setShouldCalcPreferredSize(true);
contentPane.setShouldCalcPreferredSize(true);
revalidate();
}
/**
* Adds a <code>component</code>
* represented by a <code>title</code> and/or <code>icon</code>,
* either of which can be <code>null</code>.
* Cover method for <code>insertTab</code>.
*
* @param title the title to be displayed in this tab
* @param icon the icon to be displayed in this tab
* @param component the component to be displayed when this tab is clicked
*
* @see #insertTab
* @see #removeTabAt
*/
public void addTab(String title, Image icon, Component component) {
insertTab(title, icon, component, tabsList.size());
}
/**
* Adds a <code>component</code>
* represented by a <code>title</code> and no <code>icon</code>.
* Cover method for <code>insertTab</code>.
*
* @param title the title to be displayed in this tab
* @param component the component to be displayed when this tab is clicked
*
* @see #insertTab
* @see #removeTabAt
*/
public void addTab(String title, Component component) {
insertTab(title, null, component, tabsList.size());
}
/**
* Inserts a <code>component</code>, at <code>index</code>,
* represented by a <code>title</code> and/or <code>icon</code>,
* either of which may be <code>null</code>.
* Uses java.util.Vector internally, see <code>insertElementAt</code>
* for details of insertion conventions.
*
* @param title the title to be displayed in this tab
* @param icon the icon to be displayed in this tab
* @param component The component to be displayed when this tab is clicked.
* @param index the position to insert this new tab
*
* @see #addTab
* @see #removeTabAt
*/
public void insertTab(String title, Image icon, Component component,
int index) {
checkIndex(index);
if (component == null) {
return;
}
Button b = new Button(title != null ? title : "", icon);
((DefaultListModel) tabsList.getModel()).addItemAtIndex(b, index);
tabsTable.put(b, component);
if (tabsList.size() == 1) {
contentPane.addComponent("Center", component);
}
}
/**
* Updates the information about the tab details
*
* @param title the title to be displayed in this tab
* @param icon the icon to be displayed in this tab
* @param index the position to insert this new tab
*/
public void setTabTitle(String title, Image icon, int index) {
checkIndex(index);
Button b = (Button)tabsList.getModel().getItemAt(index);
b.setText(title);
b.setIcon(icon);
((DefaultListModel) tabsList.getModel()).setItem(index, b);
}
/**
* Removes the tab at <code>index</code>.
* After the component associated with <code>index</code> is removed,
* its visibility is reset to true to ensure it will be visible
* if added to other containers.
* @param index the index of the tab to be removed
* @exception IndexOutOfBoundsException if index is out of range
* (index < 0 || index >= tab count)
*
* @see #addTab
* @see #insertTab
*/
public void removeTabAt(int index) {
checkIndex(index);
Component removedComp = (Component) tabsTable.get(((Component) tabsList.getModel().getItemAt(index)));
((DefaultListModel) tabsList.getModel()).removeItem(index);
tabsTable.remove(removedComp);
}
/**
* Returns tthe tab at <code>index</code>.
*
* @param index the index of the tab to be removed
* @exception IndexOutOfBoundsException if index is out of range
* (index < 0 || index >= tab count)
*
* @see #addTab
* @see #insertTab
*/
public Component getTabComponentAt(int index) {
checkIndex(index);
return (Component) tabsTable.get(((Component) tabsList.getModel().getItemAt(index)));
}
private void checkIndex(int index) {
if (index < 0 || index > tabsList.size()) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}
/**
* Returns the index of the tab for the specified component.
* Returns -1 if there is no tab for this component.
*
* @param component the component for the tab
* @return the first tab which matches this component, or -1
* if there is no tab for this component
*/
public int indexOfComponent(Component component) {
for (int i = 0; i < getTabCount(); i++) {
Component c = (Component) tabsList.getModel().getItemAt(i);
Component content = (Component) tabsTable.get(c);
if(component.equals(content)){
return i;
}
}
return -1;
}
/**
* Returns the number of tabs in this <code>tabbedpane</code>.
*
* @return an integer specifying the number of tabbed pages
*/
public int getTabCount() {
return tabsList.size();
}
/**
* Returns the currently selected index for this tabbedpane.
* Returns -1 if there is no currently selected tab.
*
* @return the index of the selected tab
*/
public int getSelectedIndex() {
return tabsList.getSelectedIndex();
}
/**
* @inheritDoc
*/
protected String getUIID() {
return "TabbedPane";
}
/**
* The prototype is optionally used in calculating the size of an individual
* tab and is recommended for performance reasons. You should invoke it with a String
* representing the width/height which will be used to calculate
* the size required for each element in the list.\
* <p>This operation is not essential and if it isn't invoked the size of the first
* few tabs is used to determin the overall size of a tab.
* <p>This allows the size calculations to work across look and feels and allows
* developers to predetermin size for the tabs.
* <p>e.g. For tabs which you would like to always be 5 characters wide
* you can use a prototype "XXXXX" which would use the preferred size of the XXXXX
* String to determin the size of the tabs..
*
* @param title a string to determine the size.
*/
public void setTabTitlePrototype(String title) {
tabsList.setRenderingPrototype(title);
}
/**
* @inheritDoc
*/
public String toString() {
String className = getClass().getName();
className = className.substring(className.lastIndexOf('.') + 1);
return className + "[x=" + getX() + " y=" + getY() + " width=" +
getWidth() + " height=" + getHeight() + ", tab placement = " +
tabPlacement + ", tab count = " + getTabCount() +
", selected index = " + getSelectedIndex() + "]";
}
/**
* @inheritDoc
*/
public void paint(Graphics g) {
super.paint(g);
UIManager.getInstance().getLookAndFeel().drawTabbedPane(g, this);
}
/**
* @inheritDoc
*/
public void setStyle(Style style) {
tabsList.setStyle(style);
contentPane.setStyle(style);
}
/**
* Returns the placement of the tabs for this tabbedpane.
*
* @see #setTabPlacement
*/
public int getTabPlacement() {
return tabPlacement;
}
/**
* The TabbedPane surrounded border width
*
* @return The TabbedPane surrounded border width
*/
public int getTabbedPaneBorderWidth() {
return tPBorder;
}
/**
* Setting the TabbedPane surrounded border width
*
* @param tabbedPaneBorderWidth TabbedPane surrounded border width
*/
public void setTabbedPaneBorderWidth(int tabbedPaneBorderWidth) {
this.tPBorder = tabbedPaneBorderWidth;
}
/**
* @inheritDoc
*/
public void setPadding(int top, int bottom, int left, int right) {
if (contentPane != null) {
contentPane.getStyle().setPadding(top, bottom, left, right);
}
}
/**
* @inheritDoc
*/
public void setPadding(int orientation, int gap) {
if (contentPane != null) {
contentPane.getStyle().setPadding(orientation, gap);
}
}
/**
* Sets the selected index for this tabbedpane. The index must be a valid
* tab index.
* @param index the index to be selected
* @throws IndexOutOfBoundsException if index is out of range
* (index < 0 || index >= tab count)
*/
public void setSelectedIndex(int index) {
if (index < 0 || index >= tabsList.size()) {
throw new IndexOutOfBoundsException("Index: "+index+", Tab count: "+tabsList.size());
}
tabsList.setSelectedIndex(index);
}
class TabsRenderer implements ListCellRenderer {
public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {
// prototype value can cause this
if(value == null || (!(value instanceof Button))) {
value = new Button("" + value);
}
return UIManager.getInstance().getLookAndFeel().getTabbedPaneCell(
TabbedPane.this, ((Button) value).getText(),
((Button) value).getIcon(), isSelected,
list.hasFocus(), list.getStyle(), TabbedPane.this.getStyle(),
list.getScrollX(), list.getScrollY(),
list.getPreferredSize(), contentPane.getBounds().getSize());
}
public Component getListFocusComponent(List list) {
return null;
}
}
}