/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-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>
 *
 * @see <a href="http://www.eclipse.org/swt/snippets/#tabfolder">TabFolder, TabItem snippets</a>
 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 */
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 user changes the receiver's selection, 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 when the user changes the receiver's selection
 *
 * @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) {
	Point size = super.computeSize (wHint, hHint, changed);
	if (items.length > 0) {
		TabItem lastItem = items[items.length-1];
		int border = getBorderWidth ();
		int width = lastItem.x + lastItem.width + border * 2 + CLIENT_MARGIN_WIDTH * 2 + TabItem.SHADOW_WIDTH * 2;
		size.x = Math.max (width, size.x);
	}
	return size;
}
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(Event event) {
	if (inDispose) return;
	inDispose = true;
	notifyListeners(SWT.Dispose, event);
	event.type = SWT.None;
	// 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 = Math.max (0, clientArea.width - (xClient + CLIENT_MARGIN_WIDTH + 1));
	clientArea.height = Math.max (0, 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 tab item at the given point in the receiver
 * or null if no such item exists. The point is in the
 * coordinate system of the receiver.
 *
 * @param point the point used to locate the item
 * @return the tab item at the given point, or null if the point is not in a tab item
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the point 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>
 * 
 * @since 3.4
 */
public TabItem getItem (Point point) {
	checkWidget();
	if (point == null) error (SWT.ERROR_NULL_ARGUMENT);
	int count = items.length;
	for (int index = 0; index < count; index++) {
		TabItem item = items[index];
		Rectangle bounds = item.getBounds();
		if (bounds.contains(point)) return item;
	}
	return null;
}
/**
 * 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;
}
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';
}
/** 
 * 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(event);
			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:
			// this callback is always needed so that widget is included in tab order
			keyDown(event);
			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 item 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();
	}	
}
/** 
 * A key was pressed.  If one of the tab-selection keys that is not a traversal
 * was pressed then change tabs accordingly.
 */
void keyDown(Event event) {
	int count = items.length;
	if (count <= 1) return;
	switch (event.keyCode) {
		case SWT.ARROW_RIGHT:
			if (selectedIndex < items.length - 1) {
				setSelection(selectedIndex + 1, true);
			}
			break;
		case SWT.ARROW_LEFT:
			if (selectedIndex > 0) {
				setSelection(selectedIndex - 1, true);
			}
			break;
		case SWT.HOME:
			if (selectedIndex > 0) {
				setSelection(0, true);
			}
			break;
		case SWT.END:
			if (selectedIndex < count - 1) {
				setSelection(count - 1, true);
			}
			break;
	}
}
/**
 * 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);
}
boolean mnemonicHit (char key) {
	for (int i = 0; i < items.length; i++) {
		char mnemonic = getMnemonic (items[i].getText ());
		if (mnemonic != '\0') {
			if (Character.toUpperCase (key) == Character.toUpperCase (mnemonic)) {
				if (forceFocus ()) {
					if (i != selectedIndex) setSelection(i, true);
					return true;
				}
			}
		}
	}
	return false;
}
/** 
 * 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);
}
void removeControl (Control control) {
	super.removeControl (control);
	for (int i=0; i<items.length; i++) {
		TabItem item = items [i];
		if (item.control == control) item.setControl (null);
	}
}
/**
 * Removes the listener from the collection of listeners who will
 * be notified when the user changes the receiver's selection.
 *
 * @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();
		}
	}	
}
boolean setBounds (int x, int y, int width, int height, boolean move, boolean resize) {
	boolean changed = super.setBounds (x, y, width, height, move, resize);
	if (changed && resize) resize ();
	return changed;
}
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 the given item.
 * The current selected is first cleared, then the new item is
 * selected.
 *
 * @param item the item to select
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item 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>
 * 
 * @since 3.2
 */
public void setSelection(TabItem item) {
	checkWidget();
	if (item == null) error(SWT.ERROR_NULL_ARGUMENT);
	setSelection(new TabItem[]{item});
}
/**
 * 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 IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the items array 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 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) {
	if (selectedIndex == index) return;
	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:
		case SWT.TRAVERSE_MNEMONIC:
		case SWT.TRAVERSE_PAGE_NEXT:
		case SWT.TRAVERSE_PAGE_PREVIOUS:
			event.doit = true;
	}
}
boolean traverseItem (boolean next) {
	return false;
}
boolean traversePage (boolean next) {
	int count = items.length;
	if (count == 0) return false;
	int index = selectedIndex;
	if (index == -1) {
		index = 0;
	} else {
		int offset = next ? 1 : -1;
		index = (index + offset + count) % count;
	}
	setSelection (index, true);
	return true;
}
}
