blob: 220f1e6f7f2515f4d37fa041ba8023feb76f9732 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.widgets;
import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
/**
* Instances of this class implement the notebook user interface
* metaphor. It allows the user to select a notebook page from
* set of pages.
* <p>
* The item children that may be added to instances of this class
* must be of type <code>TabItem</code>.
* <code>Control</code> children are created and then set into a
* tab item using <code>TabItem#setControl</code>.
* </p><p>
* Note that although this class is a subclass of <code>Composite</code>,
* it does not make sense to set a layout on it.
* </p><p>
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>TOP, BOTTOM</dd>
* <dt><b>Events:</b></dt>
* <dd>Selection</dd>
* </dl>
* <p>
* Note: Only one of the styles TOP and BOTTOM may be specified.
* </p><p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
*/
public class TabFolder extends Composite {
TabItem items[] = new TabItem [0];
int selectedIndex = -1;
int xClient, yClient;
int imageHeight = -1; // all images have the height of the first image ever set
int topTabIndex = 0; // index of the first visible tab. Used for tab scrolling
boolean scrollButtonDown = false; // true=one of the scroll buttons is being pushed
boolean inDispose = false;
String toolTipText;
// internal constants
static final int SCROLL_BUTTON_SIZE = 20; // width/height of the scroll button used for scrolling tab items
static final int CLIENT_MARGIN_WIDTH = 2; // distance between widget border and client rect
static final int SELECTED_TAB_TOP_EXPANSION = 2; // amount we expand the selected tab on top
static final int SELECTED_TAB_HORIZONTAL_EXPANSION = 2; // amount we expand so it overlays to left and right
/**
* Constructs a new instance of this class given its parent
* and a style value describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
* </p>
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param style the style of control to construct
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*
* @see SWT
* @see Widget#checkSubclass
* @see Widget#getStyle
*/
public TabFolder(Composite parent, int style) {
super(parent, checkStyle (style));
Listener listener = new Listener() {
public void handleEvent(Event event) {handleEvents(event);}
};
addListener (SWT.Dispose, listener);
addListener (SWT.MouseDown, listener);
addListener (SWT.MouseUp, listener);
addListener (SWT.MouseHover, listener);
addListener (SWT.Paint, listener);
addListener (SWT.Resize, listener);
addListener (SWT.Traverse, listener);
addListener (SWT.KeyDown, listener);
addListener (SWT.FocusIn, listener);
addListener (SWT.FocusOut, listener);
}
/**
* Adds the listener to the collection of listeners who will
* be notified when the receiver's selection changes, by sending
* it one of the messages defined in the <code>SelectionListener</code>
* interface.
* <p>
* When <code>widgetSelected</code> is called, the item field of the event object is valid.
* <code>widgetDefaultSelected</code> is not called.
* </p>
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
*/
public void addSelectionListener(SelectionListener listener) {
checkWidget();
if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener(listener);
addListener(SWT.Selection,typedListener);
addListener(SWT.DefaultSelection,typedListener);
}
static int checkStyle (int style) {
style = checkBits (style, SWT.TOP, SWT.BOTTOM, 0, 0, 0, 0);
/*
* Even though it is legal to create this widget
* with scroll bars, they serve no useful purpose
* because they do not automatically scroll the
* widget's client area. The fix is to clear
* the SWT style.
*/
return style & ~(SWT.H_SCROLL | SWT.V_SCROLL);
}
protected void checkSubclass () {
if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
}
public Point computeSize (int wHint, int hHint, boolean changed) {
checkWidget();
int width = CLIENT_MARGIN_WIDTH * 2 + TabItem.SHADOW_WIDTH * 2;
int height = 0;
if (items.length > 0) {
TabItem lastItem = items[items.length-1];
width = Math.max (width, lastItem.x + lastItem.width);
}
Point size;
Layout layout = getLayout();
if (layout != null) {
size = layout.computeSize (this, wHint, hHint, changed);
} else {
size = minimumSize (wHint, hHint, changed);
}
if (size.x == 0) size.x = DEFAULT_WIDTH;
if (size.y == 0) size.y = DEFAULT_HEIGHT;
if (wHint != SWT.DEFAULT) size.x = wHint;
if (hHint != SWT.DEFAULT) size.y = hHint;
width = Math.max (width, size.x);
height = Math.max (height, size.y);
Rectangle trim = computeTrim (0, 0, width, height);
return new Point (trim.width, trim.height);
}
public Rectangle computeTrim (int x, int y, int width, int height) {
checkWidget();
int border = getBorderWidth ();
int trimX = x - border - CLIENT_MARGIN_WIDTH - TabItem.SHADOW_WIDTH;
int trimY = y - border - CLIENT_MARGIN_WIDTH - TabItem.SHADOW_WIDTH;
int tabHeight = 0;
if (items.length > 0) {
TabItem item = items [0];
tabHeight = item.y + item.height; // only use height of the first item because all items should be the same height
}
int trimWidth = width + border * 2 + CLIENT_MARGIN_WIDTH * 2 + TabItem.SHADOW_WIDTH * 2;
int trimHeight = height + tabHeight + border * 2 + CLIENT_MARGIN_WIDTH * 2 + TabItem.SHADOW_WIDTH * 2;
return new Rectangle (trimX, trimY - tabHeight, trimWidth, trimHeight);
}
/**
* Create the specified item at 'index'.
*/
void createChild (TabItem item, int index) {
boolean isTabScrolling = isTabScrolling();
if (!(0 <= index && index <= getItemCount ())) error (SWT.ERROR_INVALID_RANGE);
item.parent = this;
// grow by one and rearrange the array.
TabItem[] newItems = new TabItem [items.length + 1];
System.arraycopy(items, 0, newItems, 0, index);
newItems[index] = item;
System.arraycopy(items, index, newItems, index + 1, items.length - index);
items = newItems;
if (selectedIndex >= index) selectedIndex ++;
layoutItems();
redrawTabs();
// redraw scroll buttons if they just became visible
// fixes 1G5X1QL
if (isTabScrolling() != isTabScrolling && isTabScrolling == false) {
redrawScrollButtons();
}
if (getItemCount() == 1) {
// select the first added item and send a selection event.
// fixes 1GAP79N
setSelection(0, true);
}
}
/**
* Destroy the specified item.
*/
void destroyChild (TabItem item) {
int index = indexOf(item);
if (index == -1) return; // should trigger an error?
if (items.length == 1) {
items = new TabItem [0];
selectedIndex = -1;
topTabIndex = 0;
if (!inDispose){
Control control = item.control;
if (control != null && !control.isDisposed()) {
control.setVisible(false);
}
redraw();
}
} else {
// shrink by one and rearrange the array.
TabItem[] newItems = new TabItem [items.length - 1];
System.arraycopy(items, 0, newItems, 0, index);
System.arraycopy(items, index + 1, newItems, index, items.length - index - 1);
items = newItems;
// move the selection if this item is selected
if (selectedIndex == index) {
if (!inDispose) {
Control control = item.control;
if (control != null && !control.isDisposed()) {
control.setVisible(false);
}
selectedIndex = -1;
setSelection(Math.max(0, index - 1), true);
}
} else if (selectedIndex > index) {
selectedIndex--;
}
if (topTabIndex == items.length) {
--topTabIndex;
}
}
// Make sure that the first tab is visible if scroll buttons are no longer drawn.
// Fixes 1FXW5DV
if (topTabIndex > 0 && !isTabScrolling()) {
topTabIndex = 0;
}
if (!inDispose) {
layoutItems();
redrawTabs();
}
}
/**
* Dispose the items of the receiver
*/
void doDispose() {
inDispose = true;
// items array is resized during TabItem.dispose
// it is length 0 if the last item is removed
while (items.length > 0) {
if (items[items.length-1] != null) {
items[items.length-1].dispose();
}
}
}
/**
* Draw an arrow like that used in Button with SWT.ARROW style.
* @param gc - GC to draw on
* @param xPos - x position the underlying button is drawn at
* @param yPos - y position the underlying button is drawn at
* @param size - size of the underlying button
* @param left - true=arrow is facing left. false=arrow is facing right
*/
void drawArrow(GC gc, int xPos, int yPos, int size, boolean left) {
int arrowWidth = size / 4;
int arrow[] = new int[6];
if (!left) arrowWidth *= -1;
// start polygon lines with vertical line which is always the same
arrow[0] = xPos + (size + arrowWidth) / 2;
arrow[1] = yPos + size / 4;
arrow[2] = arrow[0];
arrow[3] = arrow[1] + size / 2;
arrow[4] = arrow[0] - arrowWidth;
arrow[5] = yPos + size / 2;
gc.setBackground(getForeground());
gc.fillPolygon(arrow);
gc.setBackground(getBackground());
}
/**
* Draw a border around the receiver.
*/
void drawBorder(Event event) {
GC gc = event.gc;
Rectangle clientArea = getClientArea();
int wClient = clientArea.width;
int hClient = clientArea.height;
int x, y, x1, y1;
final Color HighlightShadow = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
final Color LightShadow = display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
// Draw the left line
gc.setForeground(HighlightShadow);
gc.drawLine((x = xClient - CLIENT_MARGIN_WIDTH),
yClient + hClient + CLIENT_MARGIN_WIDTH,
x,
(y = yClient - CLIENT_MARGIN_WIDTH) + 1);
// Second, darker, line right of the previous line.
// Necessary to workaround color constant differences on Windows/Motif
gc.setForeground(LightShadow);
gc.drawLine(x + 1, yClient + hClient + CLIENT_MARGIN_WIDTH, x + 1, y + 1);
gc.setForeground(HighlightShadow);
// Draw the upper line in two chunks so we don't overwrite the selected tab
if (selectedIndex == -1) {
gc.setForeground(LightShadow);
gc.drawLine(x + 1, y + 1, xClient + wClient + CLIENT_MARGIN_WIDTH, y + 1);
} else {
TabItem item = items[selectedIndex];
gc.setForeground(LightShadow);
if (selectedIndex > 0) {
gc.drawLine(x + 1, y + 1, item.x - 1 + CLIENT_MARGIN_WIDTH, y + 1);
}
gc.drawLine(item.x + item.width, y + 1, xClient + wClient + CLIENT_MARGIN_WIDTH, y + 1);
}
// Draw the right and bottom black lines
gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND));
gc.drawLine((x = xClient - CLIENT_MARGIN_WIDTH),
(y = yClient + hClient + CLIENT_MARGIN_WIDTH),
(x1 = xClient + wClient + CLIENT_MARGIN_WIDTH),
y);
gc.drawLine(x1, y, x1, (y1 = yClient - CLIENT_MARGIN_WIDTH + 1));
x1--;
x++;
y--;
y1++;
// There is a dark gray line above the bottom back line
gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
gc.drawLine(x, y, x1, y);
// On the right there is a dark gray line, left of the black one
gc.drawLine(x1, y-1, x1, y1);
// restore the foreground color.
gc.setForeground(getForeground());
}
/**
* Draw a plain push button
* @param gc - GC to draw on
* @param xPos - x position the button is drawn at
* @param yPos - y position the button is drawn at
* @param size - size of the button
*/
void drawPlainButton(GC gc, int xPos, int yPos, int size) {
Color rightBottomColor = getForeground();
Color leftTopColor = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
Color rightBottomInnerColor = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
Color leftTopInnerColor = display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
int upper = yPos;
int left = xPos;
int lower = yPos + size - 1;
int right = xPos + size - 1;
if (scrollButtonDown) { // draw the button in the pressed down state?
rightBottomColor = display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
leftTopColor = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
rightBottomInnerColor = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
leftTopInnerColor = getForeground();
}
gc.fillRectangle(left, upper, right - left, lower - upper);
// draw right, bottom line in foreground color
gc.setForeground(rightBottomColor);
gc.drawLine(right, upper, right, lower);
gc.drawLine(left, lower, right, lower);
// draw left, top line in normal shadow (default light gray)
gc.setForeground(leftTopColor);
gc.drawLine(left, upper, left, lower - 1);
gc.drawLine(left, upper, right - 1, upper);
upper++;
left++;
lower--;
right--;
// draw right, bottom line in dark shadow (default dark gray)
gc.setForeground(rightBottomInnerColor);
gc.drawLine(right, upper, right, lower);
gc.drawLine(left, lower, right, lower);
// draw left, top line in high light shadow (default off white)
gc.setForeground(leftTopInnerColor);
gc.drawLine(left, upper, left, lower - 1);
gc.drawLine(left, upper, right - 1, upper);
gc.setForeground(getForeground());
}
/**
* Draw the buttons used to scroll tab items
*/
void drawScrollButtons(Event event) {
Rectangle buttonArea = getScrollButtonArea();
int buttonSize = buttonArea.width / 2;
drawPlainButton(event.gc, buttonArea.x, buttonArea.y, buttonSize);
drawPlainButton(event.gc, buttonArea.x + buttonSize, buttonArea.y, buttonSize);
if (scrollButtonDown) {
drawArrow(event.gc, buttonArea.x, buttonArea.y, buttonSize, true);
drawArrow(event.gc, buttonArea.x + buttonSize + 1, buttonArea.y, buttonSize + 1, false);
}
else {
drawArrow(event.gc, buttonArea.x - 1, buttonArea.y - 1, buttonSize, true);
drawArrow(event.gc, buttonArea.x + buttonSize, buttonArea.y - 1, buttonSize, false);
}
}
/**
* Make sure that the first tab is visible if scroll buttons are no
* longer drawn.
*/
void ensureRightFreeSpaceUsed() {
if (topTabIndex > 0 && !isTabScrolling()) {
topTabIndex = 0;
layoutItems();
redrawTabs();
}
}
/**
* If the tab at 'tabIndex' is not visible or partially covered by the tab
* scroll buttons and there is enough space to completely show the tab,
* the tab is scrolled to the left to make it fully visible.
*/
void ensureVisible(int tabIndex) {
if (tabIndex < 0 || tabIndex >= items.length) return;
if (!isTabScrolling()) return;
if (tabIndex < topTabIndex) {
topTabIndex = tabIndex;
layoutItems();
redrawTabs();
return;
}
int rightEdge = getScrollButtonArea().x;
TabItem tabItem = items[tabIndex];
while (tabItem.x + tabItem.width > rightEdge && tabIndex != topTabIndex) {
topTabIndex++;
layoutItems();
redrawTabs();
}
}
void focus (Event e) {
if (selectedIndex == -1) return;
TabItem tab = items[selectedIndex];
redraw(tab.x, tab.y, tab.width, tab.height);
}
public Rectangle getClientArea() {
checkWidget();
Rectangle clientArea = super.getClientArea();
if (yClient == 0) { // position not calculated yet
layoutItems(); // calculate tab folder bounds as soon as there is tab data to use.
}
clientArea.x = xClient;
clientArea.y = yClient;
clientArea.width -= xClient + CLIENT_MARGIN_WIDTH + 1;
clientArea.height -= yClient + CLIENT_MARGIN_WIDTH + 1;
return clientArea;
}
/**
* Return the height of item images. All images are scaled to
* the height of the first image.
*/
int getImageHeight() {
return imageHeight;
}
/**
* Returns the item at the given, zero-relative index in the
* receiver. Throws an exception if the index is out of range.
*
* @param index the index of the item to return
* @return the item at the given index
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public TabItem getItem (int index) {
checkWidget();
if (!(0 <= index && index < getItemCount())) error(SWT.ERROR_INVALID_RANGE);
return items [index];
}
/**
* Returns the number of items contained in the receiver.
*
* @return the number of items
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getItemCount(){
checkWidget();
return items.length;
}
/**
* Returns an array of <code>TabItem</code>s which are the items
* in the receiver.
* <p>
* Note: This is not the actual structure used by the receiver
* to maintain its list of items, so modifying the array will
* not affect the receiver.
* </p>
*
* @return the items in the receiver
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public TabItem [] getItems() {
checkWidget();
TabItem[] tabItems = new TabItem [items.length];
System.arraycopy(items, 0, tabItems, 0, items.length);
return tabItems;
}
/**
* Returns the area where the two scroll buttons are drawn.
*/
Rectangle getScrollButtonArea() {
return new Rectangle(
super.getClientArea().width - SCROLL_BUTTON_SIZE * 2, SELECTED_TAB_TOP_EXPANSION,
SCROLL_BUTTON_SIZE * 2, SCROLL_BUTTON_SIZE);
}
/**
* Returns an array of <code>TabItem</code>s that are currently
* selected in the receiver. An empty array indicates that no
* items are selected.
* <p>
* Note: This is not the actual structure used by the receiver
* to maintain its selection, so modifying the array will
* not affect the receiver.
* </p>
* @return an array representing the selection
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public TabItem [] getSelection() {
checkWidget();
if (selectedIndex == -1) return new TabItem [0];
return new TabItem [] {items[selectedIndex]};
}
/**
* Returns the zero-relative index of the item which is currently
* selected in the receiver, or -1 if no item is selected.
*
* @return the index of the selected item
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int getSelectionIndex() {
checkWidget();
return selectedIndex;
}
public String getToolTipText () {
checkWidget();
return toolTipText;
}
/**
* Handle the events that I have hooked on the canvas.
*/
void handleEvents (Event event){
switch (event.type) {
case SWT.Dispose:
doDispose();
break;
case SWT.Paint:
paint(event);
break;
case SWT.Resize:
resize();
break;
case SWT.MouseDown:
mouseDown(event);
break;
case SWT.MouseUp:
mouseUp(event);
break;
case SWT.MouseHover:
mouseHover(event);
break;
case SWT.Traverse:
traversal(event);
break;
case SWT.FocusIn:
case SWT.FocusOut:
focus(event);
break;
case SWT.KeyDown:
//do nothing - this callback exists so that widget is included in tab order
break;
default:
break;
}
}
/**
* Searches the receiver's list starting at the first item
* (index 0) until an item is found that is equal to the
* argument, and returns the index of that item. If no item
* is found, returns -1.
*
* @param item the search item
* @return the index of the item
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the string is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public int indexOf(TabItem item) {
checkWidget();
if (item == null) {
error(SWT.ERROR_NULL_ARGUMENT);
}
for (int i = 0; i < items.length; i++) {
if (items[i] == item) return i;
}
return -1;
}
/**
* Answer true when the left scroll button was clicked with mouse button 1.
*/
boolean isLeftButtonHit(Event event) {
Rectangle buttonArea = getScrollButtonArea();
buttonArea.width /= 2;
return isTabScrolling() && event.button == 1 && buttonArea.contains(event.x, event.y);
}
/**
* Answer true when the right scroll button was clicked with mouse button 1.
*/
boolean isRightButtonHit(Event event) {
Rectangle buttonArea = getScrollButtonArea();
int buttonSize = buttonArea.width / 2;
buttonArea.x += buttonSize;
buttonArea.width = buttonSize;
return isTabScrolling() && event.button == 1 && buttonArea.contains(event.x, event.y);
}
/**
* Answer true if not all tabs can be visible in the receive
* thus requiring the scroll buttons to be visible.
*/
boolean isTabScrolling() {
boolean isVisible = false;
if (items.length > 0) {
TabItem tabItem = items[items.length-1];
int tabStopX = tabItem.x + tabItem.width;
tabItem = items[0];
if (tabStopX - tabItem.x > super.getClientArea().width) {
isVisible = true; // not all tabs fit in the client area
}
}
return isVisible;
}
/**
* 'item' has changed. Store the image size if this is the
* first item with an image.
*/
void itemChanged(TabItem item) {
Image itemImage = item.getImage();
boolean isTabScrolling = isTabScrolling();
if (imageHeight == -1 && itemImage != null) {
imageHeight = itemImage.getBounds().height;
}
layoutItems();
redrawTabs();
// redraw scroll buttons if they just became visible
// fixes 1G5X1QL
if (isTabScrolling() != isTabScrolling && isTabScrolling == false) {
redrawScrollButtons();
}
}
/**
* Layout the items and store the client area size.
*/
void layoutItems() {
int x = SELECTED_TAB_HORIZONTAL_EXPANSION;
int y = SELECTED_TAB_TOP_EXPANSION;
int tabHeight = 0;
GC gc = new GC(this);
for (int i=topTabIndex - 1; i>=0; i--) { // if the first visible tab is not the first tab
TabItem tab = items[i];
tab.width = tab.preferredWidth(gc);
tab.height = tab.preferredHeight(gc);
x -= tab.width; // layout tab items from right to left thus making them invisible
tab.x = x;
tab.y = y;
if (tab.height > tabHeight) tabHeight = tab.height;
}
x = SELECTED_TAB_HORIZONTAL_EXPANSION;
for (int i=topTabIndex; i<items.length; i++) { // continue laying out remaining, visible items left to right
TabItem tab = items[i];
tab.x = x;
tab.y = y;
tab.width = tab.preferredWidth(gc);
tab.height = tab.preferredHeight(gc);
x = x + tab.width;
if (tab.height > tabHeight) tabHeight = tab.height;
}
gc.dispose();
xClient = CLIENT_MARGIN_WIDTH;
yClient = CLIENT_MARGIN_WIDTH + tabHeight;
TabItem selection[] = getSelection();
if (selection.length > 0)
selection[0].expand(SELECTED_TAB_HORIZONTAL_EXPANSION, SELECTED_TAB_TOP_EXPANSION, SELECTED_TAB_HORIZONTAL_EXPANSION, 0);
}
Point minimumSize (int wHint, int hHint, boolean flushCache) {
Control [] children = _getChildren ();
int width = 0, height = 0;
for (int i=0; i<children.length; i++) {
Control child = children [i];
int index = 0;
while (index < items.length) {
if (items [index].control == child) break;
index++;
}
if (index == items.length) {
Rectangle rect = child.getBounds ();
width = Math.max (width, rect.x + rect.width);
height = Math.max (height, rect.y + rect.height);
} else {
Point size = child.computeSize (wHint, hHint, flushCache);
width = Math.max (width, size.x);
height = Math.max (height, size.y);
}
}
return new Point (width, height);
}
/**
* A mouse button was pressed down.
* If one of the tab scroll buttons was hit, scroll in the appropriate
* direction.
* If a tab was hit select the tab.
*/
void mouseDown(Event event) {
if (isLeftButtonHit(event)) {
scrollButtonDown = true;
redrawHitButton(event);
scrollLeft();
}
else
if (isRightButtonHit(event)) {
scrollButtonDown = true;
redrawHitButton(event);
scrollRight();
}
else {
for (int i=0; i<items.length; i++) {
if (items[i].getBounds().contains(new Point(event.x, event.y))) {
forceFocus();
setSelection(i, true);
return;
}
}
}
}
void mouseHover(Event event) {
String current = super.getToolTipText();
if (toolTipText == null) {
Point point = new Point(event.x, event.y);
for (int i=0; i<items.length; i++) {
if (items[i].getBounds().contains(point)) {
String string = items[i].getToolTipText();
if (string != null && !string.equals(current)) {
super.setToolTipText(string);
}
return;
}
}
if (current != null) super.setToolTipText(null);
return;
}
if (!toolTipText.equals(current)) {
super.setToolTipText(toolTipText);
}
}
/**
* A mouse button was released.
*/
void mouseUp(Event event) {
if (scrollButtonDown && event.button == 1) {
scrollButtonDown = false;
redrawHitButton(event);
}
}
/**
* Paint the receiver.
*/
void paint(Event event) {
// Draw the unselected tabs first.
for (int i=0; i<getItemCount(); i++) {
if (i != selectedIndex && event.getBounds().intersects(items[i].getBounds())) {
items[i].paint(event.gc, false);
}
}
drawBorder(event);
// Selected tab comes last since selected tabs overlay adjacent tabs
// and the border
if (selectedIndex != -1) {
items[selectedIndex].paint(event.gc, true);
}
if (isTabScrolling()) drawScrollButtons(event);
}
/**
* Redraw the area of the receiver specified by x, y, width, height.
* Don't redraw the scroll buttons to avoid flashing.
*/
void redraw (int x, int y, int width, int height) {
Rectangle buttonArea = getScrollButtonArea();
boolean fixScrollButtons = false;
if (isTabScrolling()) {
if (x > buttonArea.x) {
x = buttonArea.x;
fixScrollButtons = true;
}
if (x + width > buttonArea.x) {
width = buttonArea.x - x;
fixScrollButtons = true;
}
}
redraw(x, y, width, height, false);
if (fixScrollButtons) {
redraw(buttonArea.x, 0, buttonArea.width, buttonArea.y, false); // redraw space above scroll buttons
if (buttonArea.height < getClientArea().y) {
int redrawY = buttonArea.y + buttonArea.height;
redraw(
buttonArea.x, redrawY,
buttonArea.width, getClientArea().y - redrawY, false); // redraw space below scroll buttons
}
}
}
/**
* Redraw the scroll button that was pressed down
*/
void redrawHitButton(Event event) {
Rectangle scrollButtonArea = getScrollButtonArea();
int scrollButtonWidth = scrollButtonArea.width / 2;
if (isLeftButtonHit(event)) {
redraw(
scrollButtonArea.x, scrollButtonArea.y,
scrollButtonWidth, scrollButtonArea.height, false);
}
else
if (isRightButtonHit(event)) {
redraw(
scrollButtonArea.x + scrollButtonWidth, scrollButtonArea.y,
scrollButtonWidth, scrollButtonArea.height, false);
}
}
/**
* Redraw both scroll buttons
*/
void redrawScrollButtons() {
Rectangle scrollButtonArea = getScrollButtonArea();
redraw(
scrollButtonArea.x, scrollButtonArea.y,
scrollButtonArea.width, scrollButtonArea.height, false);
}
/**
* Redraw the tabs at the specified indexes.
*/
void redrawSelectionChange(int oldSelection, int newSelection) {
if (oldSelection != -1) {
TabItem tab = items[oldSelection];
// since the tab used to be selected, we need to clear its old expanded size
redraw(tab.x - SELECTED_TAB_HORIZONTAL_EXPANSION,
tab.y - SELECTED_TAB_TOP_EXPANSION,
tab.width + 2 * SELECTED_TAB_HORIZONTAL_EXPANSION,
tab.height + SELECTED_TAB_TOP_EXPANSION);
}
if (newSelection != -1) {
TabItem tab = items[newSelection];
// this tab is already at the expanded size
redraw(tab.x, tab.y, tab.width, tab.height);
}
// make sure the tab is repainted before the new page is made visible.
// The latter could take a long time and delay the screen update.
update();
}
/**
* Redraw the whole tab area
*/
void redrawTabs() {
redraw(0, 0, super.getClientArea().width, getClientArea().y);
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the receiver's selection changes.
*
* @param listener the listener which should no longer be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see SelectionListener
* @see #addSelectionListener
*/
public void removeSelectionListener(SelectionListener listener) {
checkWidget();
if (listener == null) {
error(SWT.ERROR_NULL_ARGUMENT);
}
removeListener(SWT.Selection, listener);
removeListener(SWT.DefaultSelection, listener);
}
/**
* The widget was resized. Adjust the size of the currently selected page.
*/
void resize() {
if (selectedIndex != -1) {
Control control = items[selectedIndex].getControl();
if (control != null && !control.isDisposed()) {
control.setBounds(getClientArea());
}
}
ensureRightFreeSpaceUsed();
}
/**
* Scroll the tab items to the left.
*/
void scrollLeft() {
if (topTabIndex > 0) {
--topTabIndex;
layoutItems();
redrawTabs();
}
}
/**
* Scroll the tab items to the right.
*/
void scrollRight() {
if (items.length > 0 && topTabIndex < items.length - 1) {
TabItem lastTabItem = items[items.length-1];
int tabStopX = lastTabItem.x + lastTabItem.width;
if (tabStopX > super.getClientArea().width - SCROLL_BUTTON_SIZE * 2) {
topTabIndex++;
layoutItems();
redrawTabs();
}
}
}
public void setFont(Font font) {
checkWidget();
if (font != null && font.equals(getFont())) return;
super.setFont(font);
layoutItems();
redrawTabs();
}
/**
* Selects the item at the given zero-relative index in the receiver.
* If the item at the index was already selected, it remains selected.
* The current selection is first cleared, then the new items are
* selected. Indices that are out of range are ignored.
*
* @param index the index of the item to select
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setSelection(int index) {
checkWidget();
if (!(0 <= index && index < items.length)) return;
setSelection(index, false);
}
/**
* Sets the receiver's selection to be the given array of items.
* The current selected is first cleared, then the new items are
* selected.
*
* @param items the array of items
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setSelection(TabItem selectedItems[]) {
checkWidget();
if (selectedItems == null) error(SWT.ERROR_NULL_ARGUMENT);
int index = -1;
if (selectedItems.length > 0) {
index = indexOf(selectedItems[0]);
}
setSelection(index, false);
}
/**
* Set the selection to the tab at the specified index.
*/
void setSelection(int index, boolean notify) {
int oldIndex = selectedIndex;
if (selectedIndex == index || index >= getItemCount()) return;
if (selectedIndex != -1) {
Control control = items[selectedIndex].control;
if (control != null && !control.isDisposed()) {
control.setVisible(false);
}
}
if (index < 0) {
index = -1; // make sure the index is always -1 if it's negative
}
selectedIndex = index;
layoutItems();
ensureVisible(index);
redrawSelectionChange(oldIndex, index);
if (index >= 0) {
Control control = items[index].control;
if (control != null && !control.isDisposed()) {
control.setBounds(getClientArea());
control.setVisible(true);
}
}
if (notify) {
if (selectedIndex != oldIndex && selectedIndex != -1) {
Event event = new Event();
event.item = getSelection()[0];
notifyListeners(SWT.Selection, event);
}
}
}
public void setToolTipText (String string) {
checkWidget();
super.setToolTipText (string);
toolTipText = string;
}
void traversal(Event event) {
switch (event.detail) {
case SWT.TRAVERSE_ESCAPE:
case SWT.TRAVERSE_RETURN:
case SWT.TRAVERSE_TAB_NEXT:
case SWT.TRAVERSE_TAB_PREVIOUS:
event.doit = true;
break;
case SWT.TRAVERSE_MNEMONIC:
event.doit = mnemonicTraversal(event);
if (event.doit) event.detail = SWT.TRAVERSE_NONE;
break;
case SWT.TRAVERSE_PAGE_NEXT:
case SWT.TRAVERSE_PAGE_PREVIOUS:
event.doit = pageTraversal(event);
if (event.doit) event.detail = SWT.TRAVERSE_NONE;
break;
case SWT.TRAVERSE_ARROW_NEXT:
if (selectedIndex < items.length - 1) {
setSelection(selectedIndex + 1, true);
}
event.doit = true;
event.detail = SWT.TRAVERSE_NONE;
break;
case SWT.TRAVERSE_ARROW_PREVIOUS:
if (selectedIndex > 0) {
setSelection(selectedIndex - 1, true);
}
event.doit = true;
event.detail = SWT.TRAVERSE_NONE;
break;
}
}
boolean pageTraversal(Event event) {
int count = getItemCount ();
if (count == 0) return false;
int index = getSelectionIndex ();
if (index == -1) {
index = 0;
} else {
int offset = (event.detail == SWT.TRAVERSE_PAGE_NEXT) ? 1 : -1;
index = (index + offset + count) % count;
}
setSelection (index, true);
return true;
}
boolean mnemonicTraversal (Event event) {
char key = event.character;
for (int i = 0; i < items.length; i++) {
if (items[i] != null) {
char mnemonic = getMnemonic (items[i].getText ());
if (mnemonic != '\0') {
if (Character.toUpperCase (key) == Character.toUpperCase (mnemonic)) {
setSelection(i, true);
return true;
}
}
}
}
return false;
}
char getMnemonic (String string) {
int index = 0;
int length = string.length ();
do {
while ((index < length) && (string.charAt (index) != '&')) index++;
if (++index >= length) return '\0';
if (string.charAt (index) != '&') return string.charAt (index);
index++;
} while (index < length);
return '\0';
}
}