/*******************************************************************************
 * 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.*;
import org.eclipse.swt.internal.*;

/**
 * Instances of this class provide a selectable user interface object
 * that displays a hierarchy of items and issues notification when an
 * item in the hierarchy is selected.
 * <p>
 * The item children that may be added to instances of this class
 * must be of type <code>TreeItem</code>.
 * </p><p>
 * Style <code>VIRTUAL</code> is used to create a <code>Tree</code> whose
 * <code>TreeItem</code>s are to be populated by the client on an on-demand basis
 * instead of up-front.  This can provide significant performance improvements for
 * trees that are very large or for which <code>TreeItem</code> population is
 * expensive (for example, retrieving values from an external source).
 * </p><p>
 * Here is an example of using a <code>Tree</code> with style <code>VIRTUAL</code>:
 * <code><pre>
 *  final Tree tree = new Tree(parent, SWT.VIRTUAL | SWT.BORDER);
 *  tree.setItemCount(20);
 *  tree.addListener(SWT.SetData, new Listener() {
 *      public void handleEvent(Event event) {
 *          TreeItem item = (TreeItem)event.item;
 *          TreeItem parentItem = item.getParentItem();
 *          String text = null;
 *          if (parentItem == null) {
 *              text = "node " + tree.indexOf(item);
 *          } else {
 *              text = parentItem.getText() + " - " + parentItem.indexOf(item);
 *          }
 *          item.setText(text);
 *          System.out.println(text);
 *          item.setItemCount(10);
 *      }
 *  });
 * </pre></code>
 * </p><p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not normally make sense to add <code>Control</code> children to
 * it, or set a layout on it, unless implementing something like a cell
 * editor.
 * </p><p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>SINGLE, MULTI, CHECK, FULL_SELECTION, VIRTUAL, NO_SCROLL</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection, DefaultSelection, Collapse, Expand, SetData, MeasureItem, EraseItem, PaintItem</dd>
 * </dl>
 * </p><p>
 * Note: Only one of the styles SINGLE and MULTI 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/#tree">Tree, TreeItem, TreeColumn 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 Tree extends Composite {
	Canvas header;
	TreeColumn[] columns = new TreeColumn [0];
	TreeColumn[] orderedColumns;
	TreeItem[] items = NO_ITEMS;
	TreeItem[] availableItems = NO_ITEMS;
	TreeItem[] selectedItems = NO_ITEMS;
	TreeItem focusItem, anchorItem, insertMarkItem;
	TreeItem lastClickedItem;
	Event lastSelectionEvent;
	int availableItemsCount = 0;
	boolean insertMarkPrecedes = false;
	boolean linesVisible, ignoreKey, ignoreDispose, customHeightSet;
	int topIndex = 0, horizontalOffset = 0;
	int fontHeight = 0, imageHeight = 0, itemHeight = 0;
	int headerImageHeight = 0, orderedCol0imageWidth = 0;
	TreeColumn resizeColumn;
	int resizeColumnX = -1;
	int drawCount = 0;
	boolean inExpand = false;	/* for item creation within Expand callback */
	TreeColumn sortColumn;
	int sortDirection = SWT.NONE;

	/* column header tooltip */
	Listener toolTipListener;
	Shell toolTipShell;
	Label toolTipLabel;

	Rectangle arrowBounds, expanderBounds, checkboxBounds, clientArea;

	static final TreeItem[] NO_ITEMS = new TreeItem [0];

	static final int MARGIN_IMAGE = 3;
	static final int MARGIN_CELL = 1;
	static final int SIZE_HORIZONTALSCROLL = 5;
	static final int TOLLERANCE_COLUMNRESIZE = 2;
	static final int WIDTH_HEADER_SHADOW = 2;
	static final int WIDTH_CELL_HIGHLIGHT = 1;
	static final int [] toolTipEvents = new int[] {SWT.MouseExit, SWT.MouseHover, SWT.MouseMove, SWT.MouseDown};
	static final String ELLIPSIS = "...";						//$NON-NLS-1$
	static final String ID_EXPANDED = "EXPANDED";				//$NON-NLS-1$
	static final String ID_COLLAPSED = "COLLAPSED";			//$NON-NLS-1$
	static final String ID_UNCHECKED = "UNCHECKED";			//$NON-NLS-1$
	static final String ID_GRAYUNCHECKED = "GRAYUNCHECKED";	//$NON-NLS-1$
	static final String ID_CHECKMARK = "CHECKMARK";			//$NON-NLS-1$
	static final String ID_CONNECTOR_COLOR = "CONNECTOR_COLOR";	//$NON-NLS-1$
	static final String ID_ARROWUP = "ARROWUP";				//$NON-NLS-1$
	static final String ID_ARROWDOWN = "ARROWDOWN";			//$NON-NLS-1$

//	TEMPORARY CODE
boolean hasFocus;

public boolean isFocusControl() {
	return hasFocus;
}

/**
 * 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#SINGLE
 * @see SWT#MULTI
 * @see SWT#CHECK
 * @see SWT#FULL_SELECTION
 * @see SWT#VIRTUAL
 * @see SWT#NO_SCROLL
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public Tree (Composite parent, int style) {
	super (parent, checkStyle (style));
	setForeground (null);	/* set foreground and background to chosen default colors */
	setBackground (null);
	GC gc = new GC (this);
	fontHeight = gc.getFontMetrics ().getHeight ();
	gc.dispose ();
	itemHeight = fontHeight + (2 * getCellPadding ());
	initImages (display);
	expanderBounds = getExpandedImage ().getBounds ();
	checkboxBounds = getUncheckedImage ().getBounds ();
	arrowBounds = getArrowDownImage ().getBounds ();
	clientArea = getClientArea ();
	
	Listener listener = new Listener () {
		public void handleEvent (Event event) {
			handleEvents (event);
		}
	};
	addListener (SWT.Paint, listener);
	addListener (SWT.MouseDown, listener);
	addListener (SWT.MouseUp, listener);
	addListener (SWT.MouseDoubleClick, listener);
	addListener (SWT.Dispose, listener);	
	addListener (SWT.Resize, listener);
	addListener (SWT.KeyDown, listener);
	addListener (SWT.FocusOut, listener);
	addListener (SWT.FocusIn, listener);
	addListener (SWT.Traverse, listener);

	header = new Canvas (this, SWT.NO_REDRAW_RESIZE | SWT.NO_FOCUS);
	header.setVisible (false);
	header.setBounds (0, 0, 0, fontHeight + 2 * getHeaderPadding ());
	header.addListener (SWT.Paint, listener);
	header.addListener (SWT.MouseDown, listener);
	header.addListener (SWT.MouseUp, listener);
	header.addListener (SWT.MouseHover, listener);
	header.addListener (SWT.MouseDoubleClick, listener);
	header.addListener (SWT.MouseMove, listener);
	header.addListener (SWT.MouseExit, listener);

	toolTipListener = new Listener () {
		public void handleEvent (Event event) {
			switch (event.type) {
				case SWT.MouseHover:
				case SWT.MouseMove:
					if (headerUpdateToolTip (event.x)) break;
					// FALL THROUGH
				case SWT.MouseExit:
				case SWT.MouseDown:
					headerHideToolTip ();
					break;
			}
		}
	};

	ScrollBar hBar = getHorizontalBar ();
	if (hBar != null) {
		hBar.setValues (0, 0, 1, 1, 1, 1);
		hBar.setVisible (false);
		hBar.addListener (SWT.Selection, listener);
	}
	ScrollBar vBar = getVerticalBar ();
	if (vBar != null) {
		vBar.setValues (0, 0, 1, 1, 1, 1);
		vBar.setVisible (false);
		vBar.addListener (SWT.Selection, 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.
 * If the receiver has the <code>SWT.CHECK</code> style and the check selection changes,
 * the event object detail field contains the value <code>SWT.CHECK</code>.
 * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked.
 * The item field of the event object is valid for default selection, but the detail field is not used.
 * </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);
}
/**
 * Adds the listener to the collection of listeners who will
 * be notified when an item in the receiver is expanded or collapsed
 * by sending it one of the messages defined in the <code>TreeListener</code>
 * interface.
 *
 * @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 TreeListener
 * @see #removeTreeListener
 */
public void addTreeListener (TreeListener listener) {
	checkWidget ();
	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
	TypedListener typedListener = new TypedListener (listener);	
	addListener (SWT.Expand, typedListener);
	addListener (SWT.Collapse, typedListener);
}
boolean checkData (TreeItem item, boolean redraw) {
	if (item.cached) return true;
	if ((style & SWT.VIRTUAL) != 0) {
		item.cached = true;
		Event event = new Event ();
		TreeItem parentItem = item.getParentItem ();
		event.item = item;
		event.index = parentItem == null ? indexOf (item) : parentItem.indexOf (item);
		sendEvent (SWT.SetData, event);
		if (isDisposed () || item.isDisposed ()) return false;
		if (redraw) redrawItem (item.availableIndex, false);
	}
	return true;
}
static int checkStyle (int style) {
	/*
	* Feature in Windows.  Even when WS_HSCROLL or
	* WS_VSCROLL is not specified, Windows creates
	* trees and tables with scroll bars.  The fix
	* is to set H_SCROLL and V_SCROLL.
	* 
	* NOTE: This code appears on all platforms so that
	* applications have consistent scroll bar behavior.
	*/
	if ((style & SWT.NO_SCROLL) == 0) {
		style |= SWT.H_SCROLL | SWT.V_SCROLL;
	}
	style |= SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED;
	//TEMPORARY CODE
	style |= SWT.FULL_SELECTION;
	return checkBits (style, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0);
}
protected void checkSubclass () {
	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
}
/**
 * Clears the item at the given zero-relative index in the receiver.
 * The text, icon and other attributes of the item are set to the default
 * value.  If the tree was created with the <code>SWT.VIRTUAL</code> style,
 * these attributes are requested again as needed.
 *
 * @param index the index of the item to clear
 * @param all <code>true</code> if all child items of the indexed item should be
 * cleared recursively, and <code>false</code> otherwise
 *
 * @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>
 * 
 * @see SWT#VIRTUAL
 * @see SWT#SetData
 * 
 * @since 3.2
 */
public void clear (int index, boolean recursive) {
	checkWidget ();
	if (!(0 <= index && index < items.length)) error (SWT.ERROR_INVALID_RANGE);
	TreeItem item = items [index];
	
	/* if there are no columns then the horizontal scrollbar may need adjusting */
	TreeItem[] availableDescendents = null;
	int oldRightX = 0;
	if (columns.length == 0) {
		if (recursive) {
			availableDescendents = item.computeAvailableDescendents ();
			for (int i = 0; i < availableDescendents.length; i++) {
				Rectangle bounds = availableDescendents [i].getBounds (false);
				oldRightX = Math.max (oldRightX, bounds.x + bounds.width);
			}
		} else {
			Rectangle bounds = item.getBounds (false);
			oldRightX = bounds.x + bounds.width;
		}
	}
	
	/* clear the item(s) */
	item.clear ();
	if (recursive) {
		item.clearAll (true, false);
	}

	/* adjust the horizontal scrollbar if needed */
	if (columns.length == 0) {
		int newRightX = 0;
		if (recursive) {
			for (int i = 0; i < availableDescendents.length; i++) {
				Rectangle bounds = availableDescendents [i].getBounds (false);
				newRightX = Math.max (newRightX, bounds.x + bounds.width);
			}
		} else {
			Rectangle bounds = item.getBounds (false);
			newRightX = bounds.x + bounds.width;
		}
		updateHorizontalBar (newRightX, newRightX - oldRightX);
	}
	
	/* redraw the item(s) */
	if (recursive && item.expanded) {
		int descendentCount = availableDescendents == null ?
			item.computeAvailableDescendentCount () :
			availableDescendents.length;
		redrawItems (item.availableIndex, item.availableIndex + descendentCount - 1, false);
	} else {
		redrawItem (item.availableIndex, false);
	}
}
/**
 * Clears all the items in the receiver. The text, icon and other
 * attributes of the items are set to their default values. If the
 * tree was created with the <code>SWT.VIRTUAL</code> style, these
 * attributes are requested again as needed.
 * 
 * @param all <code>true</code> if all child items should be cleared
 * recursively, and <code>false</code> otherwise
 *
 * @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 SWT#VIRTUAL
 * @see SWT#SetData
 * 
 * @since 3.2
 */
public void clearAll (boolean recursive) {
	checkWidget ();
	if (items.length == 0) return;
	
	/* if there are no columns then the horizontal scrollbar may need adjusting */
	int oldRightX = 0;
	if (columns.length == 0 && !recursive) {
		for (int i = 0; i < items.length; i++) {
			Rectangle bounds = items [i].getBounds (false);
			oldRightX = Math.max (oldRightX, bounds.x + bounds.width);
		}
	}

	/* clear the item(s) */
	for (int i = 0; i < items.length; i++) {
		items [i].clear ();
		if (recursive) items [i].clearAll (true, false);
	}

	/* adjust the horizontal scrollbar if needed */
	if (columns.length == 0) {
		if (recursive) {
			updateHorizontalBar ();		/* recompute from scratch */
		} else {
			/*
			 * All cleared root items will have the same x and width values now,
			 * so just measure the first one as a sample.
			 */
			Rectangle bounds = items [0].getBounds (false);
			int newRightX = bounds.x + bounds.width;
			updateHorizontalBar (newRightX, newRightX - oldRightX);
		}
	}
	
	/* redraw the item(s) */
	if (recursive) {
		redrawItems (0, availableItemsCount - 1, false);
	} else {
		for (int i = 0; i < items.length; i++) {
			redrawItem (items [i].availableIndex, false);
		}
	}
}
/*
 * Returns the ORDERED index of the column that the specified x falls within,
 * or -1 if the x lies to the right of the last column.
 */
int computeColumnIntersect (int x, int startColumn) {
	TreeColumn[] orderedColumns = getOrderedColumns ();
	if (orderedColumns.length - 1 < startColumn) return -1;
	int rightX = orderedColumns [startColumn].getX ();
	for (int i = startColumn; i < orderedColumns.length; i++) {
		rightX += orderedColumns [i].width;
		if (x < rightX) return i;
	}
	return -1;
}
public Point computeSize (int wHint, int hHint, boolean changed) {
	checkWidget ();
	int width = 0, height = 0;
	if (wHint != SWT.DEFAULT) {
		width = wHint;
	} else {
		if (columns.length == 0) {
			for (int i = 0; i < availableItemsCount; i++) {
				Rectangle itemBounds = availableItems [i].getBounds (false);
				width = Math.max (width, itemBounds.x + itemBounds.width);
			}
		} else {
			TreeColumn[] orderedColumns = getOrderedColumns ();
			TreeColumn lastColumn = orderedColumns [orderedColumns.length - 1];
			width = lastColumn.getX () + lastColumn.width;
		}
	}
	if (hHint != SWT.DEFAULT) {
		height = hHint;
	} else {
		height = getHeaderHeight () + availableItemsCount * itemHeight;
	}
	Rectangle result = computeTrim (0, 0, width, height);
	return new Point (result.width, result.height);
}
void createItem (TreeColumn column, int index) {
	TreeColumn[] newColumns = new TreeColumn [columns.length + 1];
	System.arraycopy (columns, 0, newColumns, 0, index);
	newColumns [index] = column;
	System.arraycopy (columns, index, newColumns, index + 1, columns.length - index);
	columns = newColumns;
	
	if (orderedColumns != null) {
		int insertIndex = 0;
		if (index > 0) {
			insertIndex = columns [index - 1].getOrderIndex () + 1;
		}
		TreeColumn[] newOrderedColumns = new TreeColumn [orderedColumns.length + 1];
		System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, insertIndex);
		newOrderedColumns [insertIndex] = column;
		System.arraycopy (
			orderedColumns,
			insertIndex,
			newOrderedColumns,
			insertIndex + 1,
			orderedColumns.length - insertIndex);
		orderedColumns = newOrderedColumns;
	}

	if (columns.length == 1) {
		column.itemImageWidth = orderedCol0imageWidth;
	} else {
		if (column.getOrderIndex () == 0) orderedCol0imageWidth = 0;
	}

	/* allow all items to update their internal structures accordingly */
	for (int i = 0; i < items.length; i++) {
		items [i].addColumn (column);
	}

	/* existing items become hidden when going from 0 to 1 column (0 width) */
	if (columns.length == 1 && availableItemsCount > 0) {
		redrawFromItemDownwards (topIndex);
	} else {
		/* checkboxes become hidden when creating a column with index == ordered index == 0 (0 width) */
		if (availableItemsCount > 0 && (style & SWT.CHECK) != 0 && index == 0 && column.getOrderIndex () == 0) {
			redrawFromItemDownwards (topIndex);
		}
	}
}
void createItem (TreeItem item, int index) {
	TreeItem[] newItems = new TreeItem [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;

	/* determine the item's availability index */
	int startIndex;
	if (index == items.length - 1) {
		startIndex = availableItemsCount;		/* last item */
	} else {
		startIndex = items [index + 1].availableIndex;
	}

	if (availableItemsCount == availableItems.length) {
		int grow = drawCount <= 0 ? 4 : Math.max (4, availableItems.length * 3 / 2);
		TreeItem[] newAvailableItems = new TreeItem [availableItems.length + grow];
		System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItems.length);
		availableItems = newAvailableItems;
	}
	if (startIndex != availableItemsCount) {
		/* new item is not at end of list, so shift other items right to create space for it */
		System.arraycopy (
			availableItems,
			startIndex,
			availableItems,
			startIndex + 1,
			availableItemsCount - startIndex);
	}
	availableItems [startIndex] = item;
	availableItemsCount++;

	/* update the availableIndex for items bumped down by this new item */
	for (int i = startIndex; i < availableItemsCount; i++) {
		availableItems [i].availableIndex = i;
	}

	/* update scrollbars */
	updateVerticalBar ();
	Rectangle bounds = item.getBounds (false);
	int rightX = bounds.x + bounds.width;
	updateHorizontalBar (rightX, rightX);
	/* 
	 * If new item is above viewport then adjust topIndex and the vertical
	 * scrollbar so that the current viewport items will not change.
	 */
	if (item.availableIndex < topIndex) {
		topIndex++;
		ScrollBar vBar = getVerticalBar ();
		if (vBar != null) vBar.setSelection (topIndex);
		return;
	}
	/*
	 * If this is the first item and the receiver has focus then its boundary
	 * focus ring must be removed. 
	 */
	if (availableItemsCount == 1 && isFocusControl ()) {
		focusItem = item;
		redraw ();
		return;
	}
	int redrawIndex = index;
	if (redrawIndex > 0 && item.isLastChild ()) redrawIndex--;
	redrawFromItemDownwards (items [redrawIndex].availableIndex);
}
/**
 * Deselects an item in the receiver.  If the item was already
 * deselected, it remains deselected.
 *
 * @param item the item to be deselected
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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 void deselect (TreeItem item) {
	checkWidget ();
	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	deselectItem (item);
	redrawItem (item.availableIndex, true);
}
/**
 * Deselects all selected 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 void deselectAll () {
	checkWidget ();
	TreeItem[] oldSelection = selectedItems;
	selectedItems = NO_ITEMS;
	for (int i = 0; i < oldSelection.length; i++) {
		redrawItem (oldSelection [i].availableIndex, true);
	}
}
void deselectItem (TreeItem item) {
	int index = getSelectionIndex (item);
	if (index == -1) return;
	TreeItem[] newSelectedItems = new TreeItem [selectedItems.length - 1];
	System.arraycopy (selectedItems, 0, newSelectedItems, 0, index);
	System.arraycopy (
		selectedItems,
		index + 1,
		newSelectedItems,
		index,
		newSelectedItems.length - index);
	selectedItems = newSelectedItems;
}
void destroyItem (TreeColumn column) {
	headerHideToolTip ();
	int index = column.getIndex ();
	int orderedIndex = column.getOrderIndex ();

	TreeColumn[] newColumns = new TreeColumn [columns.length - 1];
	System.arraycopy (columns, 0, newColumns, 0, index);
	System.arraycopy (columns, index + 1, newColumns, index, newColumns.length - index);
	columns = newColumns;

	if (orderedColumns != null) {
		if (columns.length < 2) {
			orderedColumns = null;
		} else {
			int removeIndex = column.getOrderIndex ();
			TreeColumn[] newOrderedColumns = new TreeColumn [orderedColumns.length - 1];
			System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, removeIndex);
			System.arraycopy (
				orderedColumns,
				removeIndex + 1,
				newOrderedColumns,
				removeIndex,
				newOrderedColumns.length - removeIndex);
			orderedColumns = newOrderedColumns;
		}
	}

	if (orderedIndex == 0 && columns.length > 0) {
		orderedCol0imageWidth = columns [getColumnOrder ()[0]].itemImageWidth;
	}

	/* allow all items to update their internal structures accordingly */
	for (int i = 0; i < items.length; i++) {
		items [i].removeColumn (column, index, orderedIndex);
	}

	/* update horizontal scrollbar */
	int lastColumnIndex = columns.length - 1;
	if (lastColumnIndex < 0) {		/* no more columns */
		updateHorizontalBar ();
	} else {
		int newWidth = 0;
		for (int i = 0; i < columns.length; i++) {
			newWidth += columns [i].width;
		}
		ScrollBar hBar = getHorizontalBar (); 
		if (hBar != null) {
			hBar.setMaximum (newWidth);
			hBar.setVisible (clientArea.width < newWidth);
			int selection = hBar.getSelection ();
			if (selection != horizontalOffset) {
				horizontalOffset = selection;
				redraw ();
				if (header.isVisible () && drawCount <= 0) header.redraw ();
			}
		}
	}
	TreeColumn[] orderedColumns = getOrderedColumns ();
	for (int i = orderedIndex; i < orderedColumns.length; i++) {
		if (!orderedColumns [i].isDisposed ()) {
			orderedColumns [i].sendEvent (SWT.Move);
		}
	}

	if (sortColumn == column) {
		sortColumn = null;
	}
}
/*
 * Allows the Tree to update internal structures it has that may contain the
 * item being destroyed.  The argument is not necessarily a root-level item.
 */
void destroyItem (TreeItem item) {
	if (item == focusItem) reassignFocus ();

	/* availableItems array */
	int availableIndex = item.availableIndex; 
	if (availableIndex != -1) {
		Rectangle bounds = item.getBounds (false);
		int rightX = bounds.x + bounds.width;

		if (availableIndex != availableItemsCount - 1) {
			/* item is not at end of available items list, so must shift items left to reclaim its slot */
			System.arraycopy (
				availableItems,
				availableIndex + 1,
				availableItems,
				availableIndex,
				availableItemsCount - availableIndex - 1);
			availableItems [availableItemsCount - 1] = null;
		} else {
			availableItems [availableIndex] = null;	/* last item, so no array copy needed */
		}
		availableItemsCount--;

		if (drawCount <= 0 && availableItems.length - availableItemsCount == 4) {
			/* shrink the available items array */
			TreeItem[] newAvailableItems = new TreeItem [availableItemsCount];
			System.arraycopy (availableItems, 0, newAvailableItems, 0, newAvailableItems.length);
			availableItems = newAvailableItems;
		}

		/* update the availableIndex on affected items */
		for (int i = availableIndex; i < availableItemsCount; i++) {
			availableItems [i].availableIndex = i;
		}
		item.availableIndex = -1;
		int oldTopIndex = topIndex;
		updateVerticalBar ();
		updateHorizontalBar (0, -rightX);
		/* 
		 * If destroyed item is above viewport then adjust topIndex and the vertical
		 * scrollbar so that the current viewport items will not change. 
		 */
		if (availableIndex < topIndex) {
			topIndex = oldTopIndex - 1;
			ScrollBar vBar = getVerticalBar ();
			if (vBar != null) vBar.setSelection (topIndex);
		}
	}
	/* selectedItems array */
	if (item.isSelected ()) {
		int selectionIndex = getSelectionIndex (item);
		TreeItem[] newSelectedItems = new TreeItem [selectedItems.length - 1];
		System.arraycopy (selectedItems, 0, newSelectedItems, 0, selectionIndex);
		System.arraycopy (
			selectedItems,
			selectionIndex + 1,
			newSelectedItems,
			selectionIndex,
			newSelectedItems.length - selectionIndex);
		selectedItems = newSelectedItems;
	}
	/* root-level items array */
	if (item.depth == 0) {
		int index = item.getIndex ();
		TreeItem[] newItems = new TreeItem [items.length - 1];
		System.arraycopy (items, 0, newItems, 0, index);
		System.arraycopy (items, index + 1, newItems, index, newItems.length - index);
		items = newItems;
	}
	if (item == anchorItem) anchorItem = null;
	if (item == insertMarkItem) insertMarkItem = null;
	if (item == lastClickedItem) lastClickedItem = null;
	/*
	 * If this was the last item and the receiver has focus then its boundary
	 * focus ring must be redrawn.
	 */
	if (availableItemsCount == 0 && isFocusControl ()) {
		redraw ();
		return;
	}
}
Image getArrowDownImage () {
	return (Image) display.getData (ID_ARROWDOWN);
}
Image getArrowUpImage () {
	return (Image) display.getData (ID_ARROWUP);
}
int getCellPadding () {
	return MARGIN_CELL + WIDTH_CELL_HIGHLIGHT; 
}
Image getCheckmarkImage () {
	return (Image) display.getData (ID_CHECKMARK);
}
public Control[] getChildren () {
	checkWidget ();
	Control[] controls = _getChildren ();
	if (header == null) return controls;
	Control[] result = new Control [controls.length - 1];
	/* remove the Header from the returned set of children */
	int index = 0;
	for (int i = 0; i < controls.length; i++) {
		 if (controls [i] != header) {
		 	result [index++] = controls [i];
		 }
	}
	return result;
}
Image getCollapsedImage () {
	return (Image) display.getData (ID_COLLAPSED);
}
/**
 * Returns the column at the given, zero-relative index in the
 * receiver. Throws an exception if the index is out of range.
 * Columns are returned in the order that they were created.
 * If no <code>TreeColumn</code>s were created by the programmer,
 * this method will throw <code>ERROR_INVALID_RANGE</code> despite
 * the fact that a single column of data may be visible in the tree.
 * This occurs when the programmer uses the tree like a list, adding
 * items but never creating a column.
 *
 * @param index the index of the column to return
 * @return the column 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>
 * 
 * @see Tree#getColumnOrder()
 * @see Tree#setColumnOrder(int[])
 * @see TreeColumn#getMoveable()
 * @see TreeColumn#setMoveable(boolean)
 * @see SWT#Move
 * 
 * @since 3.1
 */
public TreeColumn getColumn (int index) {
	checkWidget ();
	if (!(0 <= index && index < columns.length)) error (SWT.ERROR_INVALID_RANGE);
	return columns [index];
}
/**
 * Returns the number of columns contained in the receiver.
 * If no <code>TreeColumn</code>s were created by the programmer,
 * this value is zero, despite the fact that visually, one column
 * of items may be visible. This occurs when the programmer uses
 * the tree like a list, adding items but never creating a column.
 *
 * @return the number of columns
 *
 * @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.1
 */
public int getColumnCount () {
	checkWidget ();
	return columns.length;
}
/**
 * Returns an array of zero-relative integers that map
 * the creation order of the receiver's items to the
 * order in which they are currently being displayed.
 * <p>
 * Specifically, the indices of the returned array represent
 * the current visual order of the items, and the contents
 * of the array represent the creation order of the items.
 * </p><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 current visual order of the receiver's 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>
 * 
 * @see Tree#setColumnOrder(int[])
 * @see TreeColumn#getMoveable()
 * @see TreeColumn#setMoveable(boolean)
 * @see SWT#Move
 * 
 * @since 3.2
 */
public int[] getColumnOrder () {
	checkWidget ();
	int[] result = new int [columns.length];
	if (orderedColumns != null) {
		for (int i = 0; i < result.length; i++) {
			result [i] = orderedColumns [i].getIndex ();
		}
	} else {
		for (int i = 0; i < columns.length; i++) {
			result [i] = i;
		}
	}
	return result;
}
/**
 * Returns an array of <code>TreeColumn</code>s which are the
 * columns in the receiver. Columns are returned in the order
 * that they were created.  If no <code>TreeColumn</code>s were
 * created by the programmer, the array is empty, despite the fact
 * that visually, one column of items may be visible. This occurs
 * when the programmer uses the tree like a list, adding items but
 * never creating a column.
 * <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>
 * 
 * @see Tree#getColumnOrder()
 * @see Tree#setColumnOrder(int[])
 * @see TreeColumn#getMoveable()
 * @see TreeColumn#setMoveable(boolean)
 * @see SWT#Move
 * 
 * @since 3.1
 */
public TreeColumn[] getColumns () {
	checkWidget ();
	TreeColumn[] result = new TreeColumn [columns.length];
	System.arraycopy (columns, 0, result, 0, columns.length);
	return result;
}
Color getConnectorColor () {
	return (Color) display.getData (ID_CONNECTOR_COLOR);
}
Image getExpandedImage () {
	return (Image) display.getData (ID_EXPANDED);
}
Image getGrayUncheckedImage () {
	return (Image) display.getData (ID_GRAYUNCHECKED);
}
/**
 * Returns the width in pixels of a grid line.
 *
 * @return the width of a grid line in pixels
 * 
 * @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.1
 */
public int getGridLineWidth () {
	checkWidget ();
	return 1;
}
/**
 * Returns the height of the receiver's header 
 *
 * @return the height of the header or zero if the header is not visible
 *
 * @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.1 
 */
public int getHeaderHeight () {
	checkWidget ();
	if (!header.getVisible ()) return 0;
	return header.getSize ().y;
}
int getHeaderPadding () {
	return MARGIN_CELL + WIDTH_HEADER_SHADOW; 
}
/**
 * Returns <code>true</code> if the receiver's header is visible,
 * and <code>false</code> otherwise.
 * <p>
 * If one of the receiver's ancestors is not visible or some
 * other condition makes the receiver not visible, this method
 * may still indicate that it is considered visible even though
 * it may not actually be showing.
 * </p>
 *
 * @return the receiver's header's visibility state
 *
 * @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.1
 */
public boolean getHeaderVisible () {
	checkWidget ();
	return header.getVisible ();
}
/**
 * Returns the 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.
 * <p>
 * The item that is returned represents an item that could be selected by the user.
 * For example, if selection only occurs in items in the first column, then null is 
 * returned if the point is outside of the item. 
 * Note that the SWT.FULL_SELECTION style hint, which specifies the selection policy,
 * determines the extent of the selection.
 * </p>
 *
 * @param point the point used to locate the item
 * @return the item at the given point, or null if the point is not in a selectable 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>
 */
public TreeItem getItem (Point point) {
	checkWidget ();
	if (point == null) error (SWT.ERROR_NULL_ARGUMENT);
	int index = (point.y - getHeaderHeight ()) / itemHeight + topIndex;
	if (!(0 <= index && index < availableItemsCount)) return null;		/* below the last item */
	TreeItem result = availableItems [index];
	if (!result.getHitBounds ().contains (point)) return null;	/* considers the x value */
	return result;
}
/**
 * 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>
 * 
 * @since 3.1
 */
public TreeItem getItem (int index) {
	checkWidget ();
	if (!(0 <= index && index < items.length)) error (SWT.ERROR_INVALID_RANGE);
	return items [index];
}
/**
 * Returns the number of items contained in the receiver
 * that are direct item children of the receiver.  The
 * number that is returned is the number of roots in the
 * tree.
 *
 * @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 the height of the area which would be used to
 * display <em>one</em> of the items in the tree.
 *
 * @return the height of one 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 getItemHeight () {
	checkWidget ();
	return itemHeight;
}
/**
 * Returns a (possibly empty) array of items contained in the
 * receiver that are direct item children of the receiver.  These
 * are the roots of the tree.
 * <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
 *
 * @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 TreeItem[] getItems () {
	checkWidget ();
	TreeItem[] result = new TreeItem [items.length];
	System.arraycopy (items, 0, result, 0, items.length);
	return result;	
}
/*
 * Returns the current y-coordinate that the specified item should have. 
 */
int getItemY (TreeItem item) {
	int index = item.availableIndex;
	if (index == -1) return -1;
	return (index - topIndex) * itemHeight + getHeaderHeight ();
}
/**
 * Returns <code>true</code> if the receiver's lines are visible,
 * and <code>false</code> otherwise.
 * <p>
 * If one of the receiver's ancestors is not visible or some
 * other condition makes the receiver not visible, this method
 * may still indicate that it is considered visible even though
 * it may not actually be showing.
 * </p>
 *
 * @return the visibility state of the lines
 *
 * @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.1
 */
public boolean getLinesVisible () {
	checkWidget ();
	return linesVisible;
}
TreeColumn[] getOrderedColumns () {
	if (orderedColumns != null) return orderedColumns;
	return columns;
}
/**
 * Returns the receiver's parent item, which must be a
 * <code>TreeItem</code> or null when the receiver is a
 * root.
 *
 * @return the receiver's parent 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 TreeItem getParentItem () {
	checkWidget ();
	return null;
}
/**
 * Returns an array of <code>TreeItem</code>s that are currently
 * selected in the receiver. The order of the items is unspecified.
 * 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 TreeItem[] getSelection () {
	checkWidget ();
	int count = selectedItems.length;
	TreeItem[] result = new TreeItem [count];
	if (count > 0) {
		if (count == 1) {
			System.arraycopy (selectedItems, 0, result, 0, count);
		} else {
			getSelection (result, items, 0);
		}
	}
	return result;
}
int getSelection (TreeItem[] result, TreeItem[] items, int index) {
	for (int i = 0; i < items.length; i++) {
		TreeItem item = items [i];
		if (item.isSelected ()) result [index++] = item;
		if (index == result.length) break;
		index = getSelection (result, items [i].items, index);
		if (index == result.length) break;
	}
	return index;
}
/**
 * Returns the number of selected items contained in the receiver.
 *
 * @return the number of selected 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 getSelectionCount () {
	checkWidget ();
	return selectedItems.length;
}
/*
 * Returns the index of the argument in the receiver's array of currently-
 * selected items, or -1 if the item is not currently selected.
 */
int getSelectionIndex (TreeItem item) {
	for (int i = 0; i < selectedItems.length; i++) {
		if (selectedItems [i] == item) return i;
	}
	return -1;
}
/**
 * Returns the column which shows the sort indicator for
 * the receiver. The value may be null if no column shows
 * the sort indicator.
 *
 * @return the sort indicator 
 *
 * @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 #setSortColumn(TreeColumn)
 * 
 * @since 3.2
 */
public TreeColumn getSortColumn () {
	checkWidget ();
	return sortColumn;
}
/**
 * Returns the direction of the sort indicator for the receiver. 
 * The value will be one of <code>UP</code>, <code>DOWN</code> 
 * or <code>NONE</code>.
 *
 * @return the sort direction
 *
 * @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 #setSortDirection(int)
 * 
 * @since 3.2
 */
public int getSortDirection () {
	checkWidget ();
	return sortDirection;
}
/**
 * Returns the item which is currently at the top of the receiver.
 * This item can change when items are expanded, collapsed, scrolled
 * or new items are added or removed.
 *
 * @return the item at the top of 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>
 * 
 * @since 2.1
 */
public TreeItem getTopItem () {
	checkWidget ();
	if (availableItemsCount == 0) return null;
	return availableItems [topIndex];
}
Image getUncheckedImage () {
	return (Image) display.getData (ID_UNCHECKED);
}
void handleEvents (Event event) {
	switch (event.type) {
		case SWT.Paint:
			if (event.widget == header) {
				headerOnPaint (event);
			} else {
				onPaint (event);
			}
			break;
		case SWT.MouseDown:
			if (event.widget == header) {
				headerOnMouseDown (event);
			} else {
				onMouseDown (event);
			}
			break;
		case SWT.MouseUp:
			if (event.widget == header) {
				headerOnMouseUp (event);
			} else {
				onMouseUp (event);
			}
			break;
		case SWT.MouseHover:
			headerOnMouseHover (event); break;
		case SWT.MouseMove:
			headerOnMouseMove (event); break;
		case SWT.MouseDoubleClick:
			if (event.widget == header) {
				headerOnMouseDoubleClick (event);
			} else {
				onMouseDoubleClick (event);
			}
			break;
		case SWT.MouseExit:
			headerOnMouseExit (); break;
		case SWT.Dispose:
			onDispose (event); break;		
		case SWT.KeyDown:
			onKeyDown (event); break;
		case SWT.Resize:
			onResize (event); break;
		case SWT.Selection:
			if (event.widget == getHorizontalBar ()) {
				onScrollHorizontal (event);
			}
			if (event.widget == getVerticalBar ()) {
				onScrollVertical (event);
			}
			break;
		case SWT.FocusOut:
			onFocusOut (); break;
		case SWT.FocusIn:
			onFocusIn (); break;	
		case SWT.Traverse:
			switch (event.detail) {
				case SWT.TRAVERSE_ESCAPE:
				case SWT.TRAVERSE_RETURN:
				case SWT.TRAVERSE_TAB_NEXT:
				case SWT.TRAVERSE_TAB_PREVIOUS:
				case SWT.TRAVERSE_PAGE_NEXT:
				case SWT.TRAVERSE_PAGE_PREVIOUS:
					event.doit = true;
					break;
			}
			break;			
	}
}
String headerGetToolTip (int x) {
	if (resizeColumn != null) return null;
	int orderedIndex = computeColumnIntersect (x, 0);
	if (orderedIndex == -1) return null;
	TreeColumn[] orderedColumns = getOrderedColumns ();
	TreeColumn column = orderedColumns [orderedIndex];
	if (column.toolTipText == null) return null;

	/* no tooltip should appear if the hover is at a column resize opportunity */
	int columnX = column.getX ();
	if (orderedIndex > 0 && orderedColumns [orderedIndex - 1].resizable) {
		/* left column bound is resizable */
		if (x - columnX <= TOLLERANCE_COLUMNRESIZE) return null;
	}
	if (column.resizable) {
		/* right column bound is resizable */
		int columnRightX = columnX + column.width;
		if (columnRightX - x <= TOLLERANCE_COLUMNRESIZE) return null;
	}
	return column.toolTipText;
}
void headerHideToolTip() {
	if (toolTipShell == null) return;
	for (int i = 0; i < toolTipEvents.length; i++) {
		header.removeListener (toolTipEvents [i], toolTipListener);
	}
	toolTipShell.dispose ();
	toolTipShell = null;
	toolTipLabel = null;
}
void headerOnMouseDoubleClick (Event event) {
	if (!isFocusControl ()) setFocus ();
	if (columns.length == 0) return;
	TreeColumn[] orderedColumns = getOrderedColumns ();
	int x = -horizontalOffset;
	for (int i = 0; i < orderedColumns.length; i++) {
		TreeColumn column = orderedColumns [i];
		x += column.width;
		if (event.x < x) {
			/* found the clicked column */
			TreeColumn packColumn = null;
			if (x - event.x <= TOLLERANCE_COLUMNRESIZE) {
				/* clicked on column bound for this column */
				packColumn = column;
			} else {
				if (i > 0 && event.x - column.getX () <= TOLLERANCE_COLUMNRESIZE) {
					/* clicked on column bound that applies to previous column */
					packColumn = orderedColumns [i - 1];
				}
			}
			if (packColumn != null) {
				packColumn.pack ();
				resizeColumn = null;
				if (Math.abs (packColumn.getX () + packColumn.width - event.x) > TOLLERANCE_COLUMNRESIZE) {
					/* column separator has relocated away from pointer location */
					setCursor (null);
				}
				return;
			}
			/* did not click on column separator, so just fire column event */
			Event newEvent = new Event ();
			newEvent.widget = column;
			column.postEvent (SWT.DefaultSelection, newEvent);
			return;
		}
	}
}
void headerOnMouseDown (Event event) {
	if (event.button != 1) return;
	TreeColumn[] orderedColumns = getOrderedColumns ();
	int x = -horizontalOffset;
	for (int i = 0; i < orderedColumns.length; i++) {
		TreeColumn column = orderedColumns [i]; 
		x += column.width;
		/* if close to a resizable column separator line then begin column resize */
		if (column.resizable && Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) {
			resizeColumn = column;
			resizeColumnX = x;
			return;
		}
		/*
		 * If within column but not near separator line then start column drag
		 * if column is moveable, or just fire column Selection otherwise.
		 */
		if (event.x < x) {
			if (column.moveable) {
				/* open tracker on the dragged column's header cell */
				int columnX = column.getX ();
				int pointerOffset = event.x - columnX;
				headerHideToolTip ();
				Tracker tracker = new Tracker (this, SWT.NONE);
				tracker.setRectangles (new Rectangle[] {
					new Rectangle (columnX, 0, column.width, getHeaderHeight ())
				});
				if (!tracker.open ()) return;	/* cancelled */
				/* determine which column was dragged onto */
				Rectangle result = tracker.getRectangles () [0];
				int pointerX = result.x + pointerOffset;
				if (pointerX < 0) return;	/* dragged too far left */
				x = -horizontalOffset;
				for (int destIndex = 0; destIndex < orderedColumns.length; destIndex++) {
					TreeColumn destColumn = orderedColumns [destIndex];
					x += destColumn.width;
					if (pointerX < x) {
						int oldIndex = column.getOrderIndex ();
						if (destIndex == oldIndex) {	/* dragged onto self */
							Event newEvent = new Event ();
							newEvent.widget = column;
							column.postEvent (SWT.Selection, newEvent);
							return;
						}
						int leftmostIndex = Math.min (destIndex, oldIndex);
						int[] oldOrder = getColumnOrder ();
						int[] newOrder = new int [oldOrder.length];
						System.arraycopy (oldOrder, 0, newOrder, 0, leftmostIndex);
						if (leftmostIndex == oldIndex) {
							/* column moving to the right */
							System.arraycopy (oldOrder, oldIndex + 1, newOrder, oldIndex, destIndex - oldIndex);
						} else {
							/* column moving to the left */
							System.arraycopy (oldOrder, destIndex, newOrder, destIndex + 1, oldIndex - destIndex);
						}
						newOrder [destIndex] = oldOrder [oldIndex];
						int rightmostIndex = Math.max (destIndex, oldIndex);
						System.arraycopy (
							oldOrder,
							rightmostIndex + 1,
							newOrder,
							rightmostIndex + 1,
							newOrder.length - rightmostIndex - 1);
						setColumnOrder (newOrder);
						return;
					}
				}
				return;		/* dragged too far right */
			}
			/* column is not moveable */
			Event newEvent = new Event ();
			newEvent.widget = column;
			column.postEvent (SWT.Selection, newEvent);
			return;
		}
	}
}
void headerOnMouseExit () {
	if (resizeColumn != null) return;
	setCursor (null);	/* ensure that a column resize cursor does not escape */
}
void headerOnMouseHover (Event event) {
	headerShowToolTip (event.x);
}
void headerOnMouseMove (Event event) {
	if (resizeColumn == null) {
		/* not currently resizing a column */
		for (int i = 0; i < columns.length; i++) {
			TreeColumn column = columns [i]; 
			int x = column.getX () + column.width;
			if (Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) {
				if (column.resizable) {
					setCursor (display.getSystemCursor (SWT.CURSOR_SIZEWE));
				} else {
					setCursor (null);
				}
				return;
			}
		}
		setCursor (null);
		return;
	}
	
	/* currently resizing a column */
	
	/* don't allow the resize x to move left of the column's x position */
	if (event.x <= resizeColumn.getX ()) return;

	/* redraw the resizing line at its new location */
	GC gc = new GC (this);
	gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK));
	int lineHeight = clientArea.height;
	redraw (resizeColumnX - 1, 0, 1, lineHeight, false);
	resizeColumnX = event.x;
	gc.drawLine (resizeColumnX - 1, 0, resizeColumnX - 1, lineHeight);
	gc.dispose ();
}
void headerOnMouseUp (Event event) {
	if (resizeColumn == null) return;	/* not resizing a column */

	/* remove the resize line */
	GC gc = new GC (this);
	redraw (resizeColumnX - 1, 0, 1, clientArea.height, false);
	gc.dispose ();

	int newWidth = resizeColumnX - resizeColumn.getX ();
	if (newWidth != resizeColumn.width) {
		setCursor (null);
		updateColumnWidth (resizeColumn, newWidth);
	}
	resizeColumnX = -1;
	resizeColumn = null;
}
void headerOnPaint (Event event) {
	TreeColumn[] orderedColumns = getOrderedColumns ();
	int numColumns = orderedColumns.length;
	GC gc = event.gc;
	Rectangle clipping = gc.getClipping ();
	int startColumn = -1, endColumn = -1;
	if (numColumns > 0) {
		startColumn = computeColumnIntersect (clipping.x, 0);
		if (startColumn != -1) {	/* the clip x is within a column's bounds */
			endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn);
			if (endColumn == -1) endColumn = numColumns - 1;
		}
	} else {
		startColumn = endColumn = 0;
	}

	/* paint the column header shadow that spans the full header width */
	Point headerSize = header.getSize ();
	headerPaintHShadows (gc, 0, 0, headerSize.x, headerSize.y);

	/* if all damage is to the right of the last column then finished */
	if (startColumn == -1) return;
	
	/* paint each of the column headers */
	if (numColumns == 0) return;	/* no headers to paint */
	for (int i = startColumn; i <= endColumn; i++) {
		headerPaintVShadows (gc, orderedColumns [i].getX (), 0, orderedColumns [i].width, headerSize.y);
		orderedColumns [i].paint (gc);
	}
}
void headerPaintHShadows (GC gc, int x, int y, int width, int height) {
	gc.setClipping (x, y, width, height);
	int endX = x + width;
	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
	gc.drawLine (x, y, endX, y);					/* highlight shadow */
	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW));
	gc.drawLine (x, height - 2, endX, height - 2);	/* lowlight shadow */
	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW));
	gc.drawLine (x, height - 1, endX, height - 1);	/* outer shadow */
}
void headerPaintVShadows (GC gc, int x, int y, int width, int height) {
	gc.setClipping (x, y, width, height);
	int endX = x + width;
	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
	gc.drawLine (x, y, x, y + height - 1);					/* highlight shadow */
	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW));
	gc.drawLine (endX - 2, y + 1, endX - 2, height - 2);	/* light inner shadow */
	gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW));
	gc.drawLine (endX - 1, y, endX - 1, height - 1);		/* dark outer shadow */
}
void headerShowToolTip (int x) {
	String tooltip = headerGetToolTip (x);
	if (tooltip == null || tooltip.length () == 0) return;

	if (toolTipShell == null) {
		toolTipShell = new Shell (getShell (), SWT.ON_TOP | SWT.TOOL);
		toolTipLabel = new Label (toolTipShell, SWT.CENTER);
		Display display = toolTipShell.getDisplay ();
		toolTipLabel.setForeground (display.getSystemColor (SWT.COLOR_INFO_FOREGROUND));
		toolTipLabel.setBackground (display.getSystemColor (SWT.COLOR_INFO_BACKGROUND));
		for (int i = 0; i < toolTipEvents.length; i++) {
			header.addListener (toolTipEvents [i], toolTipListener);
		}
	}
	if (headerUpdateToolTip (x)) {
		toolTipShell.setVisible (true);
	} else {
		headerHideToolTip ();
	}
}
boolean headerUpdateToolTip (int x) {
	String tooltip = headerGetToolTip (x);
	if (tooltip == null || tooltip.length () == 0) return false;
	if (tooltip.equals (toolTipLabel.getText ())) return true;

	toolTipLabel.setText (tooltip);
	TreeColumn column = getOrderedColumns () [computeColumnIntersect (x, 0)];
	toolTipShell.setData (new Integer (column.getIndex ()));
	Point labelSize = toolTipLabel.computeSize (SWT.DEFAULT, SWT.DEFAULT, true);
	labelSize.x += 2; labelSize.y += 2;
	toolTipLabel.setSize (labelSize);
	toolTipShell.pack ();
	/*
	 * On some platforms, there is a minimum size for a shell  
	 * which may be greater than the label size.
	 * To avoid having the background of the tip shell showing
	 * around the label, force the label to fill the entire client area.
	 */
	Rectangle area = toolTipShell.getClientArea ();
	toolTipLabel.setSize (area.width, area.height);

	/* Position the tooltip and ensure it's not located off the screen */
	Point cursorLocation = getDisplay ().getCursorLocation ();
	int cursorHeight = 21;	/* assuming cursor is 21x21 */ 
	Point size = toolTipShell.getSize ();
	Rectangle rect = getMonitor ().getBounds ();
	Point pt = new Point (cursorLocation.x, cursorLocation.y + cursorHeight + 2);
	pt.x = Math.max (pt.x, rect.x);
	if (pt.x + size.x > rect.x + rect.width) pt.x = rect.x + rect.width - size.x;
	if (pt.y + size.y > rect.y + rect.height) pt.y = cursorLocation.y - 2 - size.y;
	toolTipShell.setLocation (pt);
	return true;
}
/**
 * Searches the receiver's list starting at the first column
 * (index 0) until a column is found that is equal to the 
 * argument, and returns the index of that column. If no column
 * is found, returns -1.
 *
 * @param column the search column
 * @return the index of the column
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the column 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.1
 */
public int indexOf (TreeColumn column) {
	checkWidget ();
	if (column == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (column.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	return column.getIndex ();
}
/**
 * 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>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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.1
 */
public int indexOf (TreeItem item) {
	checkWidget ();
	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	if (item.parentItem != null || item.parent != this) return -1;
	return item.getIndex ();
}
static void initImages (final Display display) {
	PaletteData fourBit = new PaletteData (new RGB[] {
		new RGB (0, 0, 0), new RGB (128, 0, 0), new RGB (0, 128, 0), new RGB (128, 128, 0),
		new RGB (0, 0, 128), new RGB (128, 0, 128), new RGB (0, 128, 128), new RGB (128, 128, 128),
		new RGB (192, 192, 192), new RGB (255, 0, 0), new RGB (0, 255, 0), new RGB (255, 255, 0),
		new RGB (0, 0, 255), new RGB (255, 0, 255), new RGB (0, 255, 255), new RGB (255, 255, 255)});

	if (display.getData (ID_EXPANDED) == null) {
		ImageData expanded = new ImageData (
			9, 9, 4, 										/* width, height, depth */
			fourBit, 4,
			new byte[] {
				119, 119, 119, 119, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				127, 0, 0, 15, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				119, 119, 119, 119, 112, 0, 0, 0});
		expanded.transparentPixel = 15;		/* use white for transparency */
		display.setData (ID_EXPANDED, new Image (display, expanded));
	}

	if (display.getData (ID_COLLAPSED) == null) {
		ImageData collapsed = new ImageData (
			9, 9, 4, 										/* width, height, depth */
			fourBit, 4,
			new byte[] {
				119, 119, 119, 119, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				127, -1, 15, -1, 112, 0, 0, 0,
				127, -1, 15, -1, 112, 0, 0, 0,
				127, 0, 0, 15, 112, 0, 0, 0,
				127, -1, 15, -1, 112, 0, 0, 0,
				127, -1, 15, -1, 112, 0, 0, 0,
				127, -1, -1, -1, 112, 0, 0, 0,
				119, 119, 119, 119, 112, 0, 0, 0});
		collapsed.transparentPixel = 15;	/* use white for transparency */
		display.setData (ID_COLLAPSED, new Image (display, collapsed));
	}

	PaletteData arrowPalette = new PaletteData (new RGB[] {
		new RGB (0, 0, 0), new RGB (255, 255, 255)});
	if (display.getData (ID_ARROWDOWN) == null) {
		ImageData arrowDown = new ImageData (
			7, 4, 1,
			arrowPalette, 1,
			new byte[] {0x00, (byte)0x83, (byte)0xC7, (byte)0xEF});
		arrowDown.transparentPixel = 0x1;	/* use white for transparency */
		display.setData (ID_ARROWDOWN, new Image (display, arrowDown));
	}
	if (display.getData (ID_ARROWUP) == null) {
		ImageData arrowUp = new ImageData (
			7, 4, 1,
			arrowPalette, 1,
			new byte[] {(byte)0xEF, (byte)0xC7, (byte)0x83, 0x00});
		arrowUp.transparentPixel = 0x1;		/* use white for transparency */
		display.setData (ID_ARROWUP, new Image (display, arrowUp));
	}

	PaletteData checkMarkPalette = new PaletteData (
		new RGB[] {new RGB (0, 0, 0), new RGB (252, 3, 251)});
	byte[] checkbox = new byte[] {0, 0, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 0, 0};
	ImageData checkmark = new ImageData (7, 7, 1, checkMarkPalette, 1, new byte[] {-4, -8, 112, 34, 6, -114, -34});
	checkmark.transparentPixel = 1;
	if (display.getData (ID_CHECKMARK) == null) {
		display.setData (ID_CHECKMARK, new Image (display, checkmark));
	}
	
	if (display.getData (ID_UNCHECKED) == null) {
		PaletteData uncheckedPalette = new PaletteData (	
			new RGB[] {new RGB (128, 128, 128), new RGB (255, 255, 255)});
		ImageData unchecked = new ImageData (11, 11, 1, uncheckedPalette, 2, checkbox);
		display.setData (ID_UNCHECKED, new Image (display, unchecked));
	}
	
	if (display.getData (ID_GRAYUNCHECKED) == null) {
		PaletteData grayUncheckedPalette = new PaletteData (	
			new RGB[] {new RGB (128, 128, 128), new RGB (192, 192, 192)});
		ImageData grayUnchecked = new ImageData (11, 11, 1, grayUncheckedPalette, 2, checkbox);
		display.setData (ID_GRAYUNCHECKED, new Image (display, grayUnchecked));
	}
	
	if (display.getData (ID_CONNECTOR_COLOR) == null) {
		display.setData (ID_CONNECTOR_COLOR, new Color (display, 170, 170, 170));
	}

	display.disposeExec (new Runnable () {
		public void run() {
			Image expanded = (Image) display.getData (ID_EXPANDED);
			if (expanded != null) expanded.dispose ();
			Image collapsed = (Image) display.getData (ID_COLLAPSED);
			if (collapsed != null) collapsed.dispose ();
			Color connectorColor = (Color) display.getData (ID_CONNECTOR_COLOR);
			if (connectorColor != null) connectorColor.dispose ();
			Image unchecked = (Image) display.getData (ID_UNCHECKED);
			if (unchecked != null) unchecked.dispose ();
			Image grayUnchecked = (Image) display.getData (ID_GRAYUNCHECKED);
			if (grayUnchecked != null) grayUnchecked.dispose ();
			Image checkmark = (Image) display.getData (ID_CHECKMARK);
			if (checkmark != null) checkmark.dispose ();
			Image arrowDown = (Image) display.getData (ID_ARROWDOWN);
			if (arrowDown != null) arrowDown.dispose ();
			Image arrowUp = (Image) display.getData (ID_ARROWUP);
			if (arrowUp != null) arrowUp.dispose ();

			display.setData (ID_EXPANDED, null);
			display.setData (ID_COLLAPSED, null);
			display.setData (ID_CONNECTOR_COLOR, null);
			display.setData (ID_UNCHECKED, null);
			display.setData (ID_GRAYUNCHECKED, null);
			display.setData (ID_CHECKMARK, null);
			display.setData (ID_ARROWDOWN, null);
			display.setData (ID_ARROWUP, null);
		}
	});
}
/*
 * Important: Assumes that item just became available (ie.- was either created
 * or the parent item was expanded) and the parent is available.
 */
void makeAvailable (TreeItem item) {
	int parentItemCount = item.parentItem.items.length; 
	int index = 0;
	if (parentItemCount == 1) {		/* this is the only child of parentItem */
		index = item.parentItem.availableIndex + 1;
	} else {
		/* determine this item's index in its parent */
		int itemIndex = 0;
		TreeItem[] items = item.parentItem.items;
		for (int i = 0; i < items.length; i++) {
			if (items [i] == item) {
				itemIndex = i;
				break;
			}
		}
		if (itemIndex != parentItemCount - 1) {	/* this is not the last child */
			index = items [itemIndex + 1].availableIndex;
		} else {	/* this is the last child */
			TreeItem previousItem = items [itemIndex - 1];
			index = previousItem.availableIndex + previousItem.computeAvailableDescendentCount ();
		}
	}

	if (availableItemsCount == availableItems.length) {
		int grow = drawCount <= 0 ? 4 : Math.max (4, availableItems.length * 3 / 2);
		TreeItem[] newAvailableItems = new TreeItem [availableItems.length + grow];
		System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItems.length);
		availableItems = newAvailableItems;
	}
	if (index != availableItemsCount) {
		/* new item is not at end of list, so shift other items right to create space for it */
		System.arraycopy (availableItems, index, availableItems, index + 1, availableItemsCount - index);
	}
	availableItems [index] = item;
	availableItemsCount++;

	/* update availableIndex as needed */
	for (int i = index; i < availableItemsCount; i++) {
		availableItems [i].availableIndex = i;
	}
}

/*
 * Important: Assumes that item is available and its descendents have just become
 * available (ie.- they were either created or the item was expanded).
 */
void makeDescendentsAvailable (TreeItem item, TreeItem[] descendents) {
	int itemAvailableIndex = item.availableIndex;
	TreeItem[] newAvailableItems = new TreeItem [availableItemsCount + descendents.length - 1];
	
	System.arraycopy (availableItems, 0, newAvailableItems, 0, itemAvailableIndex);
	System.arraycopy (descendents, 0, newAvailableItems, itemAvailableIndex, descendents.length);
	int startIndex = itemAvailableIndex + 1;
	System.arraycopy (
		availableItems,
		startIndex,
		newAvailableItems,
		itemAvailableIndex + descendents.length,
		availableItemsCount - startIndex);
	availableItems = newAvailableItems;
	availableItemsCount = availableItems.length;
	
	/* update availableIndex as needed */
	for (int i = itemAvailableIndex; i < availableItemsCount; i++) {
		availableItems [i].availableIndex = i;
	}
}

/*
 * Important: Assumes that item is available and its descendents have just become
 * unavailable (ie.- they were either disposed or the item was collapsed).
 */
void makeDescendentsUnavailable (TreeItem item, TreeItem[] descendents) {
	int descendentsLength = descendents.length;
	TreeItem[] newAvailableItems = new TreeItem [availableItemsCount - descendentsLength + 1];
	
	System.arraycopy (availableItems, 0, newAvailableItems, 0, item.availableIndex + 1);
	int startIndex = item.availableIndex + descendentsLength;
	System.arraycopy (
		availableItems,
		startIndex,
		newAvailableItems,
		item.availableIndex + 1,
		availableItemsCount - startIndex);
	availableItems = newAvailableItems;
	availableItemsCount = availableItems.length;
	
	/* update availableIndexes */
	for (int i = 1; i < descendents.length; i++) {
		/* skip the first descendent since this is the item being collapsed */
		descendents [i].availableIndex = -1;
	}
	for (int i = item.availableIndex; i < availableItemsCount; i++) {
		availableItems [i].availableIndex = i;
	}
	
	/* remove the selection from all descendents */
	for (int i = selectedItems.length - 1; i >= 0; i--) {
		if (selectedItems [i] != item && selectedItems [i].hasAncestor (item)) {
			removeSelectedItem (i);
		}
	}
	
	/* if the anchorItem is being hidden then clear it */
	if (anchorItem != null && anchorItem != item && anchorItem.hasAncestor (item)) {
		anchorItem = null;
	}
}
void onArrowDown (int stateMask) {
	if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) {
		/* Down Arrow with no modifiers */
		int newFocusIndex = focusItem.availableIndex + 1;
		if (newFocusIndex == availableItemsCount) return; 	/* at bottom */
		selectItem (availableItems [newFocusIndex], false);
		setFocusItem (availableItems [newFocusIndex], true);
		redrawItem (newFocusIndex, true);
		showItem (availableItems [newFocusIndex]);
		Event newEvent = new Event ();
		newEvent.item = availableItems [newFocusIndex];
		postEvent (SWT.Selection, newEvent);
		return;
	}
	if ((style & SWT.SINGLE) != 0) {
		if ((stateMask & SWT.CTRL) != 0) {
			/* CTRL+Down Arrow, CTRL+Shift+Down Arrow */
			int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
			if (availableItemsCount <= topIndex + visibleItemCount) return;	/* at bottom */
			update ();
			topIndex++;
			ScrollBar vBar = getVerticalBar ();
			if (vBar != null) vBar.setSelection (topIndex);
			GC gc = new GC (this);
			gc.copyArea (
				0, 0,
				clientArea.width, clientArea.height,
				0, -itemHeight);
			gc.dispose ();
			return;
		}
		/* Shift+Down Arrow */
		int newFocusIndex = focusItem.availableIndex + 1;
		if (newFocusIndex == availableItemsCount) return; 	/* at bottom */
		selectItem (availableItems [newFocusIndex], false);
		setFocusItem (availableItems [newFocusIndex], true);
		redrawItem (newFocusIndex, true);
		showItem (availableItems [newFocusIndex]);
		Event newEvent = new Event ();
		newEvent.item = availableItems [newFocusIndex];
		postEvent (SWT.Selection, newEvent);
		return;
	}
	/* SWT.MULTI */
	if ((stateMask & SWT.CTRL) != 0) {
		if ((stateMask & SWT.SHIFT) != 0) {
			/* CTRL+Shift+Down Arrow */
			int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
			if (availableItemsCount <= topIndex + visibleItemCount) return;	/* at bottom */
			update ();
			topIndex++;
			ScrollBar vBar = getVerticalBar ();
			if (vBar != null) vBar.setSelection (topIndex);
			GC gc = new GC (this);
			gc.copyArea (
				0, 0,
				clientArea.width, clientArea.height,
				0, -itemHeight);
			gc.dispose ();
			return;
		}
		/* CTRL+Down Arrow */
		int focusIndex = focusItem.availableIndex; 
		if (focusIndex == availableItemsCount - 1) return;	/* at bottom */
		TreeItem newFocusItem = availableItems [focusIndex + 1];
		setFocusItem (newFocusItem, true);
		redrawItem (newFocusItem.availableIndex, true);
		showItem (newFocusItem);
		return;
	}
	/* Shift+Down Arrow */
	int newFocusIndex = focusItem.availableIndex + 1;
	if (newFocusIndex == availableItemsCount) return; 	/* at bottom */
	if (anchorItem == null) anchorItem = focusItem;
	if (focusItem.availableIndex < anchorItem.availableIndex) {
		deselectItem (focusItem);
		redrawItem (focusItem.availableIndex, true);
	}
	selectItem (availableItems [newFocusIndex], true);
	setFocusItem (availableItems [newFocusIndex], true);
	redrawItem (newFocusIndex, true);
	showItem (availableItems [newFocusIndex]);
	Event newEvent = new Event ();
	newEvent.item = availableItems [newFocusIndex];
	postEvent (SWT.Selection, newEvent);
}
void onArrowLeft (int stateMask) {
	if ((stateMask & SWT.CTRL) != 0) {
		/* CTRL+Left Arrow, CTRL+Shift+Left Arrow */
		if (horizontalOffset == 0) return;
		int newSelection = Math.max (0, horizontalOffset - SIZE_HORIZONTALSCROLL);
		update ();
		GC gc = new GC (this);
		gc.copyArea (
			0, 0,
			clientArea.width, clientArea.height,
			horizontalOffset - newSelection, 0);
		gc.dispose ();
		if (header.getVisible ()) {
			header.update ();
			Rectangle headerClientArea = header.getClientArea ();
			gc = new GC (header);
			gc.copyArea (
				0, 0,
				headerClientArea.width, headerClientArea.height,
				horizontalOffset - newSelection, 0);
			gc.dispose();
		}
		horizontalOffset = newSelection;
		ScrollBar hBar = getHorizontalBar (); 
		if (hBar != null) hBar.setSelection (horizontalOffset);
		return;
	}
	/* Left Arrow with no modifiers, Shift+Left Arrow */
	if (focusItem.expanded) {
		focusItem.setExpanded (false);
		Event newEvent = new Event ();
		newEvent.item = focusItem;
		sendEvent (SWT.Collapse, newEvent);
		return;
	}
	TreeItem parentItem = focusItem.parentItem;
	if (parentItem == null) return;
	
	selectItem (parentItem, false);
	setFocusItem (parentItem, true);
	redrawItem (parentItem.availableIndex, true);
	showItem (parentItem);
	Event newEvent = new Event ();
	newEvent.item = parentItem;
	postEvent (SWT.Selection, newEvent);
}
void onArrowRight (int stateMask) {
	if ((stateMask & SWT.CTRL) != 0) {
		/* CTRL+Right Arrow, CTRL+Shift+Right Arrow */
		ScrollBar hBar = getHorizontalBar ();
		if (hBar != null) {
			int maximum = hBar.getMaximum ();
			int clientWidth = clientArea.width;
			if ((horizontalOffset + clientWidth) == maximum) return;
			if (maximum <= clientWidth) return;
			int newSelection = Math.min (horizontalOffset + SIZE_HORIZONTALSCROLL, maximum - clientWidth);
			update ();
			GC gc = new GC (this);
			gc.copyArea (
				0, 0,
				clientArea.width, clientArea.height,
				horizontalOffset - newSelection, 0);
			gc.dispose ();
			if (header.getVisible ()) {
				Rectangle headerClientArea = header.getClientArea ();
				header.update ();
				gc = new GC (header);
				gc.copyArea (
					0, 0,
					headerClientArea.width, headerClientArea.height,
					horizontalOffset - newSelection, 0);
				gc.dispose();
			}
			horizontalOffset = newSelection;
			hBar.setSelection (horizontalOffset);
		}
		return;
	}
	/* Right Arrow with no modifiers, Shift+Right Arrow */
	TreeItem[] children = focusItem.items;
	if (children.length == 0) return;
	if (!focusItem.expanded) {
		focusItem.setExpanded (true);
		Event newEvent = new Event ();
		newEvent.item = focusItem;
		inExpand = true;
		sendEvent (SWT.Expand, newEvent);
		inExpand = false;
		if (isDisposed ()) return;
		if (focusItem.items.length == 0) {
			focusItem.expanded = false;
		}
		return;
	}
	selectItem (children [0], false);
	setFocusItem (children [0], true);
	redrawItem (children [0].availableIndex, true);
	showItem (children [0]);
	Event newEvent = new Event ();
	newEvent.item = children [0];
	postEvent (SWT.Selection, newEvent);
}
void onArrowUp (int stateMask) {
	if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) {
		/* Up Arrow with no modifiers */
		int newFocusIndex = focusItem.availableIndex - 1;
		if (newFocusIndex < 0) return; 		/* at top */
		TreeItem item = availableItems [newFocusIndex];
		selectItem (item, false);
		setFocusItem (item, true);
		redrawItem (newFocusIndex, true);
		showItem (item);
		Event newEvent = new Event ();
		newEvent.item = item;
		postEvent (SWT.Selection, newEvent);
		return;
	}
	if ((style & SWT.SINGLE) != 0) {
		if ((stateMask & SWT.CTRL) != 0) {
			/* CTRL+Up Arrow, CTRL+Shift+Up Arrow */
			if (topIndex == 0) return;	/* at top */
			update ();
			topIndex--;
			ScrollBar vBar = getVerticalBar ();
			if (vBar != null) vBar.setSelection (topIndex);
			GC gc = new GC (this);
			gc.copyArea (
				0, 0,
				clientArea.width, clientArea.height,
				0, itemHeight);
			gc.dispose ();
			return;
		}
		/* Shift+Up Arrow */
		int newFocusIndex = focusItem.availableIndex - 1;
		if (newFocusIndex < 0) return; 	/* at top */
		TreeItem item = availableItems [newFocusIndex];
		selectItem (item, false);
		setFocusItem (item, true);
		redrawItem (newFocusIndex, true);
		showItem (item);
		Event newEvent = new Event ();
		newEvent.item = item;
		postEvent (SWT.Selection, newEvent);
		return;
	}
	/* SWT.MULTI */
	if ((stateMask & SWT.CTRL) != 0) {
		if ((stateMask & SWT.SHIFT) != 0) {
			/* CTRL+Shift+Up Arrow */
			if (topIndex == 0) return;	/* at top */
			update ();
			topIndex--;
			ScrollBar vBar = getVerticalBar ();
			if (vBar != null) vBar.setSelection (topIndex);
			GC gc = new GC (this);
			gc.copyArea (
				0, 0,
				clientArea.width, clientArea.height,
				0, itemHeight);
			gc.dispose ();
			return;
		}
		/* CTRL+Up Arrow */
		int focusIndex = focusItem.availableIndex; 
		if (focusIndex == 0) return;	/* at top */
		TreeItem newFocusItem = availableItems [focusIndex - 1];
		setFocusItem (newFocusItem, true);
		showItem (newFocusItem);
		redrawItem (newFocusItem.availableIndex, true);
		return;
	}
	/* Shift+Up Arrow */
	int newFocusIndex = focusItem.availableIndex - 1;
	if (newFocusIndex < 0) return; 		/* at top */
	if (anchorItem == null) anchorItem = focusItem;
	if (anchorItem.availableIndex < focusItem.availableIndex) {
		deselectItem (focusItem);
		redrawItem (focusItem.availableIndex, true);
	}
	TreeItem item = availableItems [newFocusIndex];
	selectItem (item, true);
	setFocusItem (item, true);
	redrawItem (newFocusIndex, true);
	showItem (item);
	Event newEvent = new Event ();
	newEvent.item = item;
	postEvent (SWT.Selection, newEvent);
}
void onCR () {
	if (focusItem == null) return;
	Event event = new Event ();
	event.item = focusItem;
	postEvent (SWT.DefaultSelection, event);
}
void onDispose (Event event) {
	if (isDisposed ()) return;
	if (ignoreDispose) return;
	ignoreDispose = true;
	notifyListeners(SWT.Dispose, event);
	event.type = SWT.None;
	for (int i = 0; i < items.length; i++) {
		items [i].dispose (false);
	}
	for (int i = 0; i < columns.length; i++) {
		columns [i].dispose (false);
	}
	if (toolTipShell != null) {
		toolTipShell.dispose ();
		toolTipShell = null;
		toolTipLabel = null;
	}
	toolTipListener = null;
	topIndex = availableItemsCount = horizontalOffset = 0;
	availableItems = items = selectedItems = null;
	columns = orderedColumns = null;
	focusItem = anchorItem = insertMarkItem = lastClickedItem = null;
	lastSelectionEvent = null;
	header = null;
	resizeColumn = sortColumn = null;
	expanderBounds = null;
}
void onEnd (int stateMask) {
	int lastAvailableIndex = availableItemsCount - 1;
	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
		/* End with no modifiers */
		if (focusItem.availableIndex == lastAvailableIndex) return; 	/* at bottom */
		TreeItem item = availableItems [lastAvailableIndex]; 
		selectItem (item, false);
		setFocusItem (item, true);
		redrawItem (lastAvailableIndex, true);
		showItem (item);
		Event newEvent = new Event ();
		newEvent.item = item;
		postEvent (SWT.Selection, newEvent);
		return;
	}
	if ((style & SWT.SINGLE) != 0) {
		if ((stateMask & SWT.CTRL) != 0) {
			/* CTRL+End, CTRL+Shift+End */
			int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
			setTopItem (availableItems [availableItemsCount - visibleItemCount]);
			return;
		}
		/* Shift+End */
		if (focusItem.availableIndex == lastAvailableIndex) return; /* at bottom */
		TreeItem item = availableItems [lastAvailableIndex]; 
		selectItem (item, false);
		setFocusItem (item, true);
		redrawItem (lastAvailableIndex, true);
		showItem (item);
		Event newEvent = new Event ();
		newEvent.item = item;
		postEvent (SWT.Selection, newEvent);
		return;
	}
	/* SWT.MULTI */
	if ((stateMask & SWT.CTRL) != 0) {
		if ((stateMask & SWT.SHIFT) != 0) {
			/* CTRL+Shift+End */
			showItem (availableItems [lastAvailableIndex]);
			return;
		}
		/* CTRL+End */
		if (focusItem.availableIndex == lastAvailableIndex) return; /* at bottom */
		TreeItem item = availableItems [lastAvailableIndex];
		setFocusItem (item, true);
		showItem (item);
		redrawItem (item.availableIndex, true);
		return;
	}
	/* Shift+End */
	if (anchorItem == null) anchorItem = focusItem;
	TreeItem selectedItem = availableItems [lastAvailableIndex];
	if (selectedItem == focusItem && selectedItem.isSelected ()) return;
	int anchorIndex = anchorItem.availableIndex;
	int selectIndex = selectedItem.availableIndex;
	TreeItem[] newSelection = new TreeItem [selectIndex - anchorIndex + 1];
	int writeIndex = 0;
	for (int i = anchorIndex; i <= selectIndex; i++) {
		newSelection [writeIndex++] = availableItems [i];
	}
	setSelection (newSelection, false);
	setFocusItem (selectedItem, true);
	redrawItems (anchorIndex, selectIndex, true);
	showItem (selectedItem);
	Event newEvent = new Event ();
	newEvent.item = selectedItem;
	postEvent (SWT.Selection, newEvent);
}
void onFocusIn () {
	hasFocus = true;

	if (items.length == 0) {
		redraw ();
		return;
	}
	if (focusItem != null) {
		redrawItem (focusItem.availableIndex, true);
		return;
	}
	/* an initial focus item must be selected */
	TreeItem initialFocus = null;
	if (selectedItems.length > 0) {
		for (int i = 0; i < selectedItems.length && initialFocus == null; i++) {
			if (selectedItems [i].isAvailable ()) {
				initialFocus = selectedItems [i];
			}
		}
		if (initialFocus == null) {
			/* none of the selected items are available */
			initialFocus = availableItems [topIndex];
		}
	} else {
		initialFocus = availableItems [topIndex];
	}
	setFocusItem (initialFocus, false);
	redrawItem (initialFocus.availableIndex, true);
	return;
}
void onFocusOut () {
	hasFocus = false;

	if (items.length == 0) {
		redraw ();
		return;
	}	

	if (focusItem != null) {
		redrawItem (focusItem.availableIndex, true);
	}
}
void onHome (int stateMask) {
	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
		/* Home with no modifiers */
		if (focusItem.availableIndex == 0) return; 		/* at top */
		TreeItem item = availableItems [0];
		selectItem (item, false);
		setFocusItem (item, true);
		redrawItem (0, true);
		showItem (item);
		Event newEvent = new Event ();
		newEvent.item = item;
		postEvent (SWT.Selection, newEvent);
		return;
	}
	if ((style & SWT.SINGLE) != 0) {
		if ((stateMask & SWT.CTRL) != 0) {
			/* CTRL+Home, CTRL+Shift+Home */
			setTopItem (availableItems [0]);
			return;
		}
		/* Shift+Home */
		if (focusItem.availableIndex == 0) return; 		/* at top */
		TreeItem item = availableItems [0];
		selectItem (item, false);
		setFocusItem (item, true);
		redrawItem (0, true);
		showItem (item);
		Event newEvent = new Event ();
		newEvent.item = item;
		postEvent (SWT.Selection, newEvent);
		return;
	}
	/* SWT.MULTI */
	if ((stateMask & SWT.CTRL) != 0) {
		if ((stateMask & SWT.SHIFT) != 0) {
			/* CTRL+Shift+Home */
			setTopItem (availableItems [0]);
			return;
		}
		/* CTRL+Home */
		if (focusItem.availableIndex == 0) return; /* at top */
		TreeItem item = availableItems [0];
		setFocusItem (item, true);
		showItem (item);
		redrawItem (item.availableIndex, true);
		return;
	}
	/* Shift+Home */
	if (anchorItem == null) anchorItem = focusItem;
	TreeItem selectedItem = availableItems [0];
	if (selectedItem == focusItem && selectedItem.isSelected ()) return;
	int anchorIndex = anchorItem.availableIndex;
	int selectIndex = selectedItem.availableIndex;
	TreeItem[] newSelection = new TreeItem [anchorIndex + 1];
	int writeIndex = 0;
	for (int i = anchorIndex; i >= 0; i--) {
		newSelection [writeIndex++] = availableItems [i];
	}
	setSelection (newSelection, false);
	setFocusItem (selectedItem, true);
	redrawItems (anchorIndex, selectIndex, true);
	showItem (selectedItem);
	Event newEvent = new Event ();
	newEvent.item = selectedItem;
	postEvent (SWT.Selection, newEvent);
}
void onKeyDown (Event event) {
	if (ignoreKey) {
		ignoreKey = false;
		return;
	}
	ignoreKey = true;
	notifyListeners (event.type, event);
	event.type = SWT.None;
	if (!event.doit) return;
	if (focusItem == null) return;
	if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) {
		anchorItem = null;
	}
	switch (event.keyCode) {
		case SWT.ARROW_UP:
			onArrowUp (event.stateMask);
			return;
		case SWT.ARROW_DOWN:
			onArrowDown (event.stateMask);
			return;
		case SWT.ARROW_LEFT:
			onArrowLeft (event.stateMask);
			return;
		case SWT.ARROW_RIGHT:
			onArrowRight (event.stateMask);
			return;
		case SWT.PAGE_UP:
			onPageUp (event.stateMask);
			return;
		case SWT.PAGE_DOWN:
			onPageDown (event.stateMask);
			return;
		case SWT.HOME:
			onHome (event.stateMask);
			return;
		case SWT.END:
			onEnd (event.stateMask);
			return;
	}
	if (event.character == ' ') {
		onSpace ();
		return;
	}
	if (event.character == SWT.CR) {
		onCR ();
		return;
	}
	if ((event.stateMask & SWT.CTRL) != 0) return;

	int initialIndex = focusItem.availableIndex;
	char character = Character.toLowerCase (event.character);
	/* check available items from current focus item to bottom */
	for (int i = initialIndex + 1; i < availableItemsCount; i++) {
		TreeItem item = availableItems [i];
		String text = item.getText (0, false);
		if (text.length() > 0) {
			if (Character.toLowerCase (text.charAt (0)) == character) {
				selectItem (item, false);
				setFocusItem (item, true);
				redrawItem (i, true);
				showItem (item);
				Event newEvent = new Event ();
				newEvent.item = item;
				postEvent (SWT.Selection, newEvent);
				return;
			}
		}
	}
	/* check available items from top to current focus item */
	for (int i = 0; i < initialIndex; i++) {
		TreeItem item = availableItems [i];
		String text = item.getText (0, false);
		if (text.length() > 0) {
			if (Character.toLowerCase (text.charAt (0)) == character) {
				selectItem (item, false);
				setFocusItem (item, true);
				redrawItem (i, true);
				showItem (item);
				Event newEvent = new Event ();
				newEvent.item = item;
				postEvent (SWT.Selection, newEvent);
				return;
			}
		}
	}
}
void onMouseDoubleClick (Event event) {
	if (!isFocusControl ()) setFocus ();
	int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex;
	if  (!(0 <= index && index < availableItemsCount)) return;	/* not on an available item */
	TreeItem selectedItem = availableItems [index];
	
	/* 
	 * If the two clicks of the double click did not occur over the same item then do not
	 * consider this to be a default selection.
	 */
	if (selectedItem != lastClickedItem) return;

	/* if click was in expander box then don't fire event */
	if (selectedItem.items.length > 0 && selectedItem.getExpanderBounds ().contains (event.x, event.y)) {
		return;
	}
	
	if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return;	/* considers x */
	
	Event newEvent = new Event ();
	newEvent.item = selectedItem;
	postEvent (SWT.DefaultSelection, newEvent);
}
void onMouseDown (Event event) {
	if (!isFocusControl ()) forceFocus ();
	int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex;
	if (!(0 <= index && index < availableItemsCount)) return;	/* not on an available item */
	TreeItem selectedItem = availableItems [index];
	
	/* if click was in expander box */
	if (selectedItem.items.length > 0 && selectedItem.getExpanderBounds ().contains (event.x, event.y)) {
		if (event.button != 1) return;
		boolean expand = !selectedItem.expanded;
		selectedItem.setExpanded (expand);
		Event newEvent = new Event ();
		newEvent.item = selectedItem;
		if (expand) {
			inExpand = true;
			sendEvent (SWT.Expand, newEvent);
			inExpand = false;
			if (isDisposed ()) return;
			if (selectedItem.items.length == 0) {
				selectedItem.expanded = false;
			}
		} else {
			sendEvent (SWT.Collapse, newEvent);
		}
		return;
	}
	/* if click was in checkbox */
	if ((style & SWT.CHECK) != 0 && selectedItem.getCheckboxBounds ().contains (event.x, event.y)) {
		if (event.button != 1) return;
		selectedItem.setChecked (!selectedItem.checked);
		Event newEvent = new Event ();
		newEvent.item = selectedItem;
		newEvent.detail = SWT.CHECK;
		postEvent (SWT.Selection, newEvent);
		return;
	}
	
	if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return;
	
	if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) anchorItem = null;

	boolean sendSelection = true;
	/* Detect when this is the second click of a DefaultSelection and don't fire Selection */
	if (lastSelectionEvent != null && lastSelectionEvent.item == selectedItem) {
		if (event.time - lastSelectionEvent.time <= display.getDoubleClickTime ()) {
			sendSelection = false;
		} else {
			lastSelectionEvent = event;
			event.item = selectedItem;
		}
	} else {
		lastSelectionEvent = event;
		event.item = selectedItem;
	}

	if ((style & SWT.SINGLE) != 0) {
		if (!selectedItem.isSelected ()) {
			if (event.button == 1) {
				selectItem (selectedItem, false);
				setFocusItem (selectedItem, true);
				redrawItem (selectedItem.availableIndex, true);
				if (sendSelection) {
					Event newEvent = new Event ();
					newEvent.item = selectedItem;
					postEvent (SWT.Selection, newEvent);
				}
				return;
			}
			if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
				selectItem (selectedItem, false);
				setFocusItem (selectedItem, true);
				redrawItem (selectedItem.availableIndex, true);
				if (sendSelection) {
					Event newEvent = new Event ();
					newEvent.item = selectedItem;
					postEvent (SWT.Selection, newEvent);
				}
				return;
			}
		}
		/* item is selected */
		if (event.button == 1) {
			/* fire a selection event, though the selection did not change */
			if (sendSelection) {
				Event newEvent = new Event ();
				newEvent.item = selectedItem;
				postEvent (SWT.Selection, newEvent);
			}
			return;
		}
	}
	/* SWT.MULTI */
	if (!selectedItem.isSelected ()) {
		if (event.button == 1) {
			if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == SWT.SHIFT) {
				if (anchorItem == null) anchorItem = focusItem;
				int anchorIndex = anchorItem.availableIndex;
				int selectIndex = selectedItem.availableIndex;
				TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1];
				int step = anchorIndex < selectIndex ? 1 : -1;
				int writeIndex = 0;
				for (int i = anchorIndex; i != selectIndex; i += step) {
					newSelection [writeIndex++] = availableItems [i];
				}
				newSelection [writeIndex] = availableItems [selectIndex];
				setSelection (newSelection, false);
				setFocusItem (selectedItem, true);
				redrawItems (
					Math.min (anchorIndex, selectIndex),
					Math.max (anchorIndex, selectIndex),
					true);
				if (sendSelection) {
					Event newEvent = new Event ();
					newEvent.item = selectedItem;
					postEvent (SWT.Selection, newEvent);
				}
				return;
			}
			selectItem (selectedItem, (event.stateMask & SWT.CTRL) != 0);
			setFocusItem (selectedItem, true);
			redrawItem (selectedItem.availableIndex, true);
			if (sendSelection) {
				Event newEvent = new Event ();
				newEvent.item = selectedItem;
				postEvent (SWT.Selection, newEvent);
			}
			return;
		}
		/* button 3 */
		if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
			selectItem (selectedItem, false);
			setFocusItem (selectedItem, true);
			redrawItem (selectedItem.availableIndex, true);
			if (sendSelection) {
				Event newEvent = new Event ();
				newEvent.item = selectedItem;
				postEvent (SWT.Selection, newEvent);
			}
			return;
		}
	}
	/* item is selected */
	if (event.button != 1) return;
	if ((event.stateMask & SWT.CTRL) != 0) {
		removeSelectedItem (getSelectionIndex (selectedItem));
		setFocusItem (selectedItem, true);
		redrawItem (selectedItem.availableIndex, true);
		if (sendSelection) {
			Event newEvent = new Event ();
			newEvent.item = selectedItem;
			postEvent (SWT.Selection, newEvent);
		}
		return;
	}
	if ((event.stateMask & SWT.SHIFT) != 0) {
		if (anchorItem == null) anchorItem = focusItem;
		int anchorIndex = anchorItem.availableIndex;
		int selectIndex = selectedItem.availableIndex;
		TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1];
		int step = anchorIndex < selectIndex ? 1 : -1;
		int writeIndex = 0;
		for (int i = anchorIndex; i != selectIndex; i += step) {
			newSelection [writeIndex++] = availableItems [i];
		}
		newSelection [writeIndex] = availableItems [selectIndex];
		setSelection (newSelection, false);
		setFocusItem (selectedItem, true);
		redrawItems (
			Math.min (anchorIndex, selectIndex),
			Math.max (anchorIndex, selectIndex),
			true);
		if (sendSelection) {
			Event newEvent = new Event ();
			newEvent.item = selectedItem;
			postEvent (SWT.Selection, newEvent);
		}
		return;
	}
	selectItem (selectedItem, false);
	setFocusItem (selectedItem, true);
	redrawItem (selectedItem.availableIndex, true);
	if (sendSelection) {
		Event newEvent = new Event ();
		newEvent.item = selectedItem;
		postEvent (SWT.Selection, newEvent);
	}
}
void onMouseUp (Event event) {
	int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex;
	if (!(0 <= index && index < availableItemsCount)) return;	/* not on an available item */
	lastClickedItem = availableItems [index];
}
void onPageDown (int stateMask) {
	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
		/* PageDown with no modifiers */
		int newFocusIndex = focusItem.availableIndex + visibleItemCount - 1;
		newFocusIndex = Math.min (newFocusIndex, availableItemsCount - 1);
		if (newFocusIndex == focusItem.availableIndex) return;
		TreeItem item = availableItems [newFocusIndex];
		selectItem (item, false);
		setFocusItem (item, true);
		showItem (item);
		redrawItem (item.availableIndex, true);
		return;
	}
	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) {
		/* CTRL+Shift+PageDown */
		int newTopIndex = topIndex + visibleItemCount;
		newTopIndex = Math.min (newTopIndex, availableItemsCount - visibleItemCount);
		if (newTopIndex == topIndex) return;
		setTopItem (availableItems [newTopIndex]);
		return;
	}
	if ((style & SWT.SINGLE) != 0) {
		if ((stateMask & SWT.SHIFT) != 0) {
			/* Shift+PageDown */
			int newFocusIndex = focusItem.availableIndex + visibleItemCount - 1;
			newFocusIndex = Math.min (newFocusIndex, availableItemsCount - 1);
			if (newFocusIndex == focusItem.availableIndex) return;
			TreeItem item = availableItems [newFocusIndex];
			selectItem (item, false);
			setFocusItem (item, true);
			showItem (item);
			redrawItem (item.availableIndex, true);
			return;
		}
		/* CTRL+PageDown */
		int newTopIndex = topIndex + visibleItemCount;
		newTopIndex = Math.min (newTopIndex, availableItemsCount - visibleItemCount);
		if (newTopIndex == topIndex) return;
		setTopItem (availableItems [newTopIndex]);
		return;
	}
	/* SWT.MULTI */
	if ((stateMask & SWT.CTRL) != 0) {
		/* CTRL+PageDown */
		int bottomIndex = Math.min (topIndex + visibleItemCount - 1, availableItemsCount - 1);
		if (focusItem.availableIndex != bottomIndex) {
			/* move focus to bottom item in viewport */
			setFocusItem (availableItems [bottomIndex], true);
			redrawItem (bottomIndex, true);
		} else {
			/* at bottom of viewport, so set focus to bottom item one page down */
			int newFocusIndex = Math.min (availableItemsCount - 1, bottomIndex + visibleItemCount);
			if (newFocusIndex == focusItem.availableIndex) return;
			setFocusItem (availableItems [newFocusIndex], true);
			showItem (availableItems [newFocusIndex]);
			redrawItem (newFocusIndex, true);
		}
		return;
	}
	/* Shift+PageDown */
	if (anchorItem == null) anchorItem = focusItem;
	int anchorIndex = anchorItem.availableIndex;
	int bottomIndex = Math.min (topIndex + visibleItemCount - 1, availableItemsCount - 1);
	int selectIndex;
	if (focusItem.availableIndex != bottomIndex) {
		/* select from focus to bottom item in viewport */
		selectIndex = bottomIndex;
	} else {
		/* already at bottom of viewport, so select to bottom of one page down */
		selectIndex = Math.min (availableItemsCount - 1, bottomIndex + visibleItemCount);
		if (selectIndex == focusItem.availableIndex && focusItem.isSelected ()) return;
	}
	TreeItem selectedItem = availableItems [selectIndex];
	TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1];
	int step = anchorIndex < selectIndex ? 1 : -1;
	int writeIndex = 0;
	for (int i = anchorIndex; i != selectIndex; i += step) {
		newSelection [writeIndex++] = availableItems [i];
	}
	newSelection [writeIndex] = availableItems [selectIndex];
	setSelection (newSelection, false);
	setFocusItem (selectedItem, true);
	showItem (selectedItem);
	Event newEvent = new Event ();
	newEvent.item = selectedItem;
	postEvent (SWT.Selection, newEvent);
}
void onPageUp (int stateMask) {
	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) {
		/* PageUp with no modifiers */
		int newFocusIndex = Math.max (0, focusItem.availableIndex - visibleItemCount + 1);
		if (newFocusIndex == focusItem.availableIndex) return;
		TreeItem item = availableItems [newFocusIndex];
		selectItem (item, false);
		setFocusItem (item, true);
		showItem (item);
		redrawItem (item.availableIndex, true);
		return;
	}
	if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) {
		/* CTRL+Shift+PageUp */
		int newTopIndex = Math.max (0, topIndex - visibleItemCount);
		if (newTopIndex == topIndex) return;
		setTopItem (availableItems [newTopIndex]);
		return;
	}
	if ((style & SWT.SINGLE) != 0) {
		if ((stateMask & SWT.SHIFT) != 0) {
			/* Shift+PageUp */
			int newFocusIndex = Math.max (0, focusItem.availableIndex - visibleItemCount + 1);
			if (newFocusIndex == focusItem.availableIndex) return;
			TreeItem item = availableItems [newFocusIndex];
			selectItem (item, false);
			setFocusItem (item, true);
			showItem (item);
			redrawItem (item.availableIndex, true);
			return;
		}
		/* CTRL+PageUp */
		int newTopIndex = Math.max (0, topIndex - visibleItemCount);
		if (newTopIndex == topIndex) return;
		setTopItem (availableItems [newTopIndex]);
		return;
	}
	/* SWT.MULTI */
	if ((stateMask & SWT.CTRL) != 0) {
		/* CTRL+PageUp */
		if (focusItem.availableIndex != topIndex) {
			/* move focus to top item in viewport */
			setFocusItem (availableItems [topIndex], true);
			redrawItem (topIndex, true);
		} else {
			/* at top of viewport, so set focus to top item one page up */
			int newFocusIndex = Math.max (0, focusItem.availableIndex - visibleItemCount);
			if (newFocusIndex == focusItem.availableIndex) return;
			setFocusItem (availableItems [newFocusIndex], true);
			showItem (availableItems [newFocusIndex]);
			redrawItem (newFocusIndex, true);
		}
		return;
	}
	/* Shift+PageUp */
	if (anchorItem == null) anchorItem = focusItem;
	int anchorIndex = anchorItem.availableIndex;
	int selectIndex;
	if (focusItem.availableIndex != topIndex) {
		/* select from focus to top item in viewport */
		selectIndex = topIndex;
	} else {
		/* already at top of viewport, so select to top of one page up */
		selectIndex = Math.max (0, topIndex - visibleItemCount);
		if (selectIndex == focusItem.availableIndex && focusItem.isSelected ()) return;
	}
	TreeItem selectedItem = availableItems [selectIndex];
	TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1];
	int step = anchorIndex < selectIndex ? 1 : -1;
	int writeIndex = 0;
	for (int i = anchorIndex; i != selectIndex; i += step) {
		newSelection [writeIndex++] = availableItems [i];
	}
	newSelection [writeIndex] = availableItems [selectIndex];
	setSelection (newSelection, false);
	setFocusItem (selectedItem, true);
	showItem (selectedItem);
	Event newEvent = new Event ();
	newEvent.item = selectedItem;
	postEvent (SWT.Selection, newEvent);
}
void onPaint (Event event) {
	TreeColumn[] orderedColumns = getOrderedColumns ();
	GC gc = event.gc;
	Rectangle clipping = gc.getClipping ();
	int headerHeight = getHeaderHeight ();
	int numColumns = orderedColumns.length;
	int startColumn = -1, endColumn = -1;
	if (numColumns > 0) {
		startColumn = computeColumnIntersect (clipping.x, 0);
		if (startColumn != -1) {	/* the clip x is within a column's bounds */
			endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn);
			if (endColumn == -1) endColumn = numColumns - 1;
		}
	} else {
		startColumn = endColumn = 0;
	}

	/* Determine the items to be painted */
	int startIndex = (clipping.y - headerHeight) / itemHeight + topIndex;
	int endIndex = -1;
	if (startIndex < availableItemsCount) {
		endIndex = startIndex + Compatibility.ceil (clipping.height, itemHeight);
	}
	startIndex = Math.max (0, startIndex);
	endIndex = Math.min (endIndex, availableItemsCount - 1);

	/* fill background not handled by items */
	gc.setBackground (getBackground ());
	gc.setClipping (clipping);
	int bottomY = endIndex >= 0 ? getItemY (availableItems [endIndex]) + itemHeight : 0;
	int fillHeight = Math.max (0, clientArea.height - bottomY);
	if (fillHeight > 0) {	/* space below bottom item */
		drawBackground (gc, 0, bottomY, clientArea.width, fillHeight);
	}
	if (columns.length > 0) {
		TreeColumn column = orderedColumns [orderedColumns.length - 1];	/* last column */
		int rightX = column.getX () + column.width;
		if (rightX < clientArea.width) {
			drawBackground (gc, rightX, 0, clientArea.width - rightX, clientArea.height - fillHeight);
		}
	}

	/* paint the items */
	boolean noFocusDraw = false;
	int[] lineDash = gc.getLineDash ();
	int lineWidth = gc.getLineWidth ();
	for (int i = startIndex; i <= Math.min (endIndex, availableItemsCount - 1); i++) {
		TreeItem item = availableItems [i];
		if (!item.isDisposed ()) {	/* ensure that item was not disposed in a callback */
			if (startColumn == -1) {
				/* indicates that region to paint is to the right of the last column */
				noFocusDraw = item.paint (gc, null, true) || noFocusDraw;
			} else {
				if (numColumns == 0) {
					noFocusDraw = item.paint (gc, null, false) || noFocusDraw;
				} else {
					for (int j = startColumn; j <= Math.min (endColumn, columns.length - 1); j++) {
						if (!item.isDisposed ()) {	/* ensure that item was not disposed in a callback */
							noFocusDraw = item.paint (gc, orderedColumns [j], false) || noFocusDraw;
						}
						if (isDisposed () || gc.isDisposed ()) return;	/* ensure that receiver was not disposed in a callback */
					}
				}
			}
		}
		if (isDisposed () || gc.isDisposed ()) return;	/* ensure that receiver was not disposed in a callback */
	}

	/* repaint grid lines */
	gc.setClipping(clipping);
	gc.setLineWidth (lineWidth);
	if (linesVisible) {
		gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_LIGHT_SHADOW));
		gc.setLineDash (lineDash);
		if (numColumns > 0 && startColumn != -1) {
			/* vertical column lines */
			for (int i = startColumn; i <= endColumn; i++) {
				int x = orderedColumns [i].getX () + orderedColumns [i].width - 1;
				gc.drawLine (x, clipping.y, x, clipping.y + clipping.height);
			}
		}
		/* horizontal item lines */
		bottomY = clipping.y + clipping.height;
		int rightX = clipping.x + clipping.width;
		int y = (clipping.y - headerHeight) / itemHeight * itemHeight + headerHeight;
		while (y <= bottomY) {
			gc.drawLine (clipping.x, y, rightX, y);
			y += itemHeight;
		}
	}

	/* draw focus rectangle */
	if (!noFocusDraw && isFocusControl ()) {
		if (focusItem != null) {
			Rectangle focusBounds = focusItem.getFocusBounds ();
			if (focusBounds.width > 0) {
				gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK));
				gc.setClipping (focusBounds);
				if (focusItem.isSelected ()) {
					gc.setLineDash (new int[] {2, 2});
				} else {
					gc.setLineDash (new int[] {1, 1});
				}
				gc.drawFocus (focusBounds.x, focusBounds.y, focusBounds.width, focusBounds.height);
			}
		} else {
			/* no items, so draw focus border around Tree */
			int y = headerHeight + 1;
			int width = Math.max (0, clientArea.width - 2);
			int height = Math.max (0, clientArea.height - headerHeight - 2);
			gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK));
			gc.setClipping (1, y, width, height);
			gc.setLineDash (new int[] {1, 1});
			gc.drawFocus (1, y, width, height);
		}
	}

	/* draw insert mark */
	if (insertMarkItem != null) {
		Rectangle focusBounds = insertMarkItem.getFocusBounds ();
		gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK));
		gc.setClipping (focusBounds);
		gc.setLineDash (lineDash);
		if (insertMarkPrecedes) {
			gc.drawLine (focusBounds.x, focusBounds.y, focusBounds.x + focusBounds.width, focusBounds.y);
		} else {
			int y = focusBounds.y + focusBounds.height - 1;
			gc.drawLine (focusBounds.x, y, focusBounds.x + focusBounds.width, y);
		}
	}
}
void onResize (Event event) {
	clientArea = getClientArea ();
	/* vertical scrollbar */
	ScrollBar vBar = getVerticalBar ();
	if (vBar != null) {
		int clientHeight = (clientArea.height - getHeaderHeight ()) / itemHeight;
		int thumb = Math.min (clientHeight, availableItemsCount);
		vBar.setThumb (thumb);
		vBar.setPageIncrement (thumb);
		int index = vBar.getSelection ();
		if (index != topIndex) {
			topIndex = index;
			redraw ();
		}
		boolean visible = clientHeight < availableItemsCount;
		if (visible != vBar.getVisible ()) {
			vBar.setVisible (visible);
			clientArea = getClientArea ();
		}
	}
	
	/* horizontal scrollbar */ 
	ScrollBar hBar = getHorizontalBar (); 
	if (hBar != null) {
		int hBarMaximum = hBar.getMaximum ();
		int thumb = Math.min (clientArea.width, hBarMaximum);
		hBar.setThumb (thumb);
		hBar.setPageIncrement (thumb);
		horizontalOffset = hBar.getSelection ();
		boolean visible = clientArea.width < hBarMaximum;
		if (visible != hBar.getVisible ()) {
			hBar.setVisible (visible);
			clientArea = getClientArea ();
		}
	}
	
	/* header */
	int headerHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding ();
	header.setSize (clientArea.width, headerHeight);
	
	/* if this is the focus control but there are no items then the boundary focus ring must be repainted */
	if (availableItemsCount == 0 && isFocusControl ()) redraw ();
}
void onScrollHorizontal (Event event) {
	ScrollBar hBar = getHorizontalBar (); 
	if (hBar == null) return;
	int newSelection = hBar.getSelection ();
	update ();
	if (availableItemsCount > 0) {
		GC gc = new GC (this);
		gc.copyArea (
			0, 0,
			clientArea.width, clientArea.height,
			horizontalOffset - newSelection, 0);
		gc.dispose ();
	} else {
		redraw ();	/* ensure that static focus rectangle updates properly */
	}
	if (drawCount <= 0 && header.isVisible ()) {
		header.update ();
		Rectangle headerClientArea = header.getClientArea ();
		GC gc = new GC (header);
		gc.copyArea (
			0, 0,
			headerClientArea.width, headerClientArea.height,
			horizontalOffset - newSelection, 0);
		gc.dispose ();
	}
	horizontalOffset = newSelection;
}
void onScrollVertical (Event event) {
	ScrollBar vBar = getVerticalBar ();
	if (vBar != null) {
		int newSelection = vBar.getSelection ();
		update ();
		GC gc = new GC (this);
		gc.copyArea (
			0, 0,
			clientArea.width, clientArea.height,
			0, (topIndex - newSelection) * itemHeight);
		gc.dispose ();
		topIndex = newSelection;
	}
}
void onSpace () {
	if (focusItem == null) return;
	if (!focusItem.isSelected ()) {
		selectItem (focusItem, (style & SWT.MULTI) != 0);
		redrawItem (focusItem.availableIndex, true);
	}
	if ((style & SWT.CHECK) != 0) {
		focusItem.setChecked (!focusItem.checked);
	}
	showItem (focusItem);
	Event event = new Event ();
	event.item = focusItem;
	postEvent (SWT.Selection, event);
	if ((style & SWT.CHECK) == 0) return;

	/* SWT.CHECK */
	event = new Event ();
	event.item = focusItem;
	event.detail = SWT.CHECK;
	postEvent (SWT.Selection, event);
}
/*
 * The current focus item is about to become unavailable, so reassign focus.
 */
void reassignFocus () {
	if (focusItem == null) return;
	
	/* reassign to current focus' parent item if it has one */
	if (focusItem.parentItem != null) {
		TreeItem item = focusItem.parentItem;
		setFocusItem (item, false);
		showItem (item);
		return;
	}
	
	/* 
	 * reassign to the previous root-level item if there is one, or the next
	 * root-level item otherwise
	 */
	int index = focusItem.getIndex ();
	if (index != 0) {
		index--;
	} else {
		index++;
	}
	if (index < items.length) {
		TreeItem item = items [index];
		setFocusItem (item, false);
		showItem (item);
	} else {
		setFocusItem (null, false);		/* no items left */
	}
}
public void redraw () {
	checkWidget ();
	if (drawCount <= 0) super.redraw ();
}
public void redraw (int x, int y, int width, int height, boolean all) {
	checkWidget ();
	if (drawCount <= 0) super.redraw (x, y, width, height, all);
}
/* 
 * Redraws from the specified index down to the last available item inclusive.  Note
 * that the redraw bounds do not extend beyond the current last item, so clients
 * that reduce the number of available items should use #redrawItems(int,int) instead
 * to ensure that redrawing extends down to the previous bottom item boundary.
 */
void redrawFromItemDownwards (int index) {
	redrawItems (index, availableItemsCount - 1, false);
}
/*
 * Redraws the tree item at the specified index.  It is valid for this index to reside
 * beyond the last available item.
 */
void redrawItem (int itemIndex, boolean focusBoundsOnly) {
	if (itemIndex == -1) return;
	if (itemIndex < availableItemsCount && !availableItems [itemIndex].isInViewport ()) return;
	redrawItems (itemIndex, itemIndex, focusBoundsOnly);
}
/*
 * Redraws the tree between the start and end item indices inclusive.  It is valid
 * for the end index value to extend beyond the last available item.
 */
void redrawItems (int startIndex, int endIndex, boolean focusBoundsOnly) {
	if (drawCount > 0) return;

	int startY = (startIndex - topIndex) * itemHeight + getHeaderHeight ();
	int height = (endIndex - startIndex + 1) * itemHeight;
	if (focusBoundsOnly) {
		boolean custom = hooks (SWT.EraseItem) || hooks (SWT.PaintItem);
		if (!custom && columns.length > 0) {
			TreeColumn lastColumn;
			if ((style & SWT.FULL_SELECTION) != 0) {
				TreeColumn[] orderedColumns = getOrderedColumns ();
				lastColumn = orderedColumns [orderedColumns.length - 1];
			} else {
				lastColumn = columns [0];
			}
			int rightX = lastColumn.getX () + lastColumn.getWidth ();
			if (rightX <= 0) return;	/* focus column(s) not visible */
		}
		endIndex = Math.min (endIndex, availableItemsCount - 1);
		for (int i = startIndex; i <= endIndex; i++) {
			TreeItem item = availableItems [i];
			if (item.isInViewport ()) {
				/* if custom painting is being done then repaint the full item */
				if (custom) {
					redraw (0, getItemY (item), clientArea.width, itemHeight, false);
				} else {
					/* repaint the item's focus bounds */
					Rectangle bounds = item.getFocusBounds ();
					redraw (bounds.x, startY, bounds.width, height, false);
				}
			}
		}
	} else {
		redraw (0, startY, clientArea.width, height, false);
	}
}
/**
 * Removes all of the items from 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 void removeAll () {
	checkWidget ();
	if (items.length == 0) return;
	setRedraw (false);

	setFocusItem (null, false);
	for (int i = 0; i < items.length; i++) {
		items [i].dispose (false);
	}
	items = availableItems = selectedItems = NO_ITEMS;
	availableItemsCount = topIndex = 0;
	anchorItem = lastClickedItem = insertMarkItem = null;
	lastSelectionEvent = null;
	inExpand = false;
	ScrollBar vBar = getVerticalBar ();
	if (vBar != null) {
		vBar.setMaximum (1);
		vBar.setVisible (false);
	}
	if (columns.length == 0) {
		horizontalOffset = 0;
		ScrollBar hBar = getHorizontalBar ();
		if (hBar != null) {
			hBar.setMaximum (1);
			hBar.setVisible (false);
		}
	}

	setRedraw (true);
}
void removeSelectedItem (int index) {
	TreeItem[] newSelectedItems = new TreeItem [selectedItems.length - 1];
	System.arraycopy (selectedItems, 0, newSelectedItems, 0, index);
	System.arraycopy (selectedItems, index + 1, newSelectedItems, index, newSelectedItems.length - index);
	selectedItems = newSelectedItems;
}
/**
 * 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);	
}
/**
 * Removes the listener from the collection of listeners who will
 * be notified when items in the receiver are expanded or collapsed.
 *
 * @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 TreeListener
 * @see #addTreeListener
 */
public void removeTreeListener (TreeListener listener) {
	checkWidget ();
	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
	removeListener (SWT.Expand, listener);
	removeListener (SWT.Collapse, listener);
}
/**
 * Selects an item in the receiver.  If the item was already
 * selected, it remains selected.
 *
 * @param item the item to be selected
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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 void select (TreeItem item) {
	checkWidget ();
	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	selectItem (item, (style & SWT.MULTI) != 0);
	redrawItem (item.availableIndex, true);
}
/**
 * Selects all of the items in the receiver.
 * <p>
 * If the receiver is single-select, do nothing.
 * </p>
 *
 * @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 selectAll () {
	checkWidget ();
	if ((style & SWT.SINGLE) != 0) return;
	selectedItems = new TreeItem [availableItemsCount];
	System.arraycopy (availableItems, 0, selectedItems, 0, availableItemsCount);
	redraw ();
}
void selectItem (TreeItem item, boolean addToSelection) {
	TreeItem[] oldSelectedItems = selectedItems;
	if (!addToSelection || (style & SWT.SINGLE) != 0) {
		selectedItems = new TreeItem[] {item};
		for (int i = 0; i < oldSelectedItems.length; i++) {
			if (oldSelectedItems [i] != item) {
				redrawItem (oldSelectedItems [i].availableIndex, true);
			}
		}
	} else {
		if (item.isSelected ()) return;
		selectedItems = new TreeItem [selectedItems.length + 1];
		System.arraycopy (oldSelectedItems, 0, selectedItems, 0, oldSelectedItems.length);
		selectedItems [selectedItems.length - 1] = item;
	}
}
public void setBackground (Color color) {
	checkWidget ();
	if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_BACKGROUND); 
	super.setBackground (color);
}
/**
 * Sets the order that the items in the receiver should 
 * be displayed in to the given argument which is described
 * in terms of the zero-relative ordering of when the items
 * were added.
 *
 * @param order the new order to display the 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>
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item order is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the item order is not the same length as the number of items</li>
 * </ul>
 * 
 * @see Tree#getColumnOrder()
 * @see TreeColumn#getMoveable()
 * @see TreeColumn#setMoveable(boolean)
 * @see SWT#Move
 * 
 * @since 3.2
 */
public void setColumnOrder (int [] order) {
	checkWidget ();
	if (order == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (columns.length == 0) {
		if (order.length != 0) error (SWT.ERROR_INVALID_ARGUMENT);
		return;
	}
	if (order.length != columns.length) error (SWT.ERROR_INVALID_ARGUMENT);
	boolean reorder = false;
	boolean [] seen = new boolean [columns.length];
	int[] oldOrder = getColumnOrder ();
	for (int i = 0; i < order.length; i++) {
		int index = order [i];
		if (index < 0 || index >= columns.length) error (SWT.ERROR_INVALID_RANGE);
		if (seen [index]) error (SWT.ERROR_INVALID_ARGUMENT);
		seen [index] = true;
		if (index != oldOrder [i]) reorder = true;
	}
	if (!reorder) return;

	headerHideToolTip ();
	int[] oldX = new int [columns.length];
	for (int i = 0; i < columns.length; i++) {
		oldX [i] = columns [i].getX ();
	}
	orderedColumns = new TreeColumn [order.length];
	for (int i = 0; i < order.length; i++) {
		orderedColumns [i] = columns [order [i]];
	}
	/*
	 * If the first ordered column has changed then the old and new ordered column 0's
	 * have to recompute their display texts since they will now have just gained/lost
	 * space as a result of the hierarchy decorations that appear in ordered column 0.
	 */
	if (oldOrder [0] != order [0]) {
		orderedCol0imageWidth = columns [order [0]].itemImageWidth;
		GC gc = new GC (this);
		for (int i = 0; i < items.length; i++) {
			items [i].updateColumnWidth (columns [oldOrder [0]], gc);
			items [i].updateColumnWidth (columns [order [0]], gc);
		}
		gc.dispose();
	}
	for (int i = 0; i < orderedColumns.length; i++) {
		TreeColumn column = orderedColumns [i];
		if (!column.isDisposed () && column.getX () != oldX [column.getIndex ()]) {
			column.sendEvent (SWT.Move);
		}
	}

	redraw ();
	if (drawCount <= 0 && header.isVisible ()) header.redraw ();
}
void setFocusItem (TreeItem item, boolean redrawOldFocus) {
	if (item == focusItem) return;
	TreeItem oldFocusItem = focusItem;
	focusItem = item;
	if (redrawOldFocus && oldFocusItem != null) {
		redrawItem (oldFocusItem.availableIndex, true);
	}
}
public void setFont (Font value) {
	checkWidget ();
	Font oldFont = getFont ();
	super.setFont (value);
	Font font = getFont ();
	if (font.equals (oldFont)) return;
		
	GC gc = new GC (this);
	
	/* recompute the receiver's cached font height and item height values */
	fontHeight = gc.getFontMetrics ().getHeight ();
	setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ());
	Point headerSize = header.getSize ();
	int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding ();
	if (headerSize.y != newHeaderHeight) {
		header.setSize (headerSize.x, newHeaderHeight);
	}
	header.setFont (font);

	/* 
	 * Notify all columns and items of the font change so that elements that
	 * use the receiver's font can recompute their cached string widths.
	 */
	for (int i = 0; i < columns.length; i++) {
		columns [i].updateFont (gc);
	}
	for (int i = 0; i < items.length; i++) {
		items [i].updateFont (gc);
	}
	
	gc.dispose ();
	
	if (drawCount <= 0 && header.isVisible ()) header.redraw ();
	
	/* update scrollbars */
	if (columns.length == 0) updateHorizontalBar ();
	ScrollBar vBar = getVerticalBar ();
	if (vBar != null) {
		int thumb = (clientArea.height - getHeaderHeight ()) / itemHeight;
		if (vBar.getThumb () != thumb) {
			vBar.setThumb (thumb);
			vBar.setPageIncrement (thumb);
		}
		topIndex = vBar.getSelection ();
		vBar.setVisible (thumb < vBar.getMaximum ());
	}
	redraw ();
}
public void setForeground (Color color) {
	checkWidget ();
	if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_FOREGROUND); 
	super.setForeground (color);
}
void setHeaderImageHeight (int value) {
	headerImageHeight = value;
	Point headerSize = header.getSize ();
	int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding ();
	if (headerSize.y != newHeaderHeight) {
		header.setSize (headerSize.x, newHeaderHeight);
	}
}
/**
 * Marks the receiver's header as visible if the argument is <code>true</code>,
 * and marks it invisible otherwise. 
 * <p>
 * If one of the receiver's ancestors is not visible or some
 * other condition makes the receiver not visible, marking
 * it visible may not actually cause it to be displayed.
 * </p>
 *
 * @param show the new visibility state
 *
 * @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.1
 */
public void setHeaderVisible (boolean value) {
	checkWidget ();
	if (header.getVisible () == value) return;		/* no change */
	headerHideToolTip ();
	header.setVisible (value);
	updateVerticalBar ();
	redraw ();
}
void setImageHeight (int value) {
	imageHeight = value;
	setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ());
}
/**
 * Display a mark indicating the point at which an item will be inserted.
 * The drop insert item has a visual hint to show where a dragged item 
 * will be inserted when dropped on the tree.
 * 
 * @param item the insert item.  Null will clear the insertion mark.
 * @param before true places the insert mark above 'item'. false places 
 *	the insert mark below 'item'.
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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 setInsertMark (TreeItem item, boolean before) {
	checkWidget ();
	if (item != null && item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	if (item != null && item.parent != this) return;
	if (item == insertMarkItem && before == insertMarkPrecedes) return;	/* no change */
	
	TreeItem oldInsertItem = insertMarkItem;
	insertMarkItem = item;
	insertMarkPrecedes = before;
	if (oldInsertItem != null && oldInsertItem.availableIndex != -1) {
		redrawItem (oldInsertItem.availableIndex, true);
	}
	if (item != null && item != oldInsertItem && item.availableIndex != -1) {
		redrawItem (item.availableIndex, true);
	}
}
/**
 * Sets the number of root-level items contained in the receiver.
 *
 * @param count 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>
 *
 * @since 3.2
 */
public void setItemCount (int count) {
	checkWidget ();
	count = Math.max (0, count);
	if (count == items.length) return;
	int oldCount = availableItemsCount;
	int redrawStart, redrawEnd;

	/* if the new item count is less than the current count then remove all excess items from the end */
	if (count < items.length) {
		redrawStart = count > 0 ? items [count - 1].availableIndex : 0;
		redrawEnd = availableItemsCount - 1;
		availableItemsCount = items [count].availableIndex;
		for (int i = count; i < items.length; i++) {
			items [i].dispose (false);
		}
		if (count == 0) {
			items = Tree.NO_ITEMS;
		} else {
			TreeItem[] newItems = new TreeItem [count];
			System.arraycopy (items, 0, newItems, 0, count);
			items = newItems;
		}

		int newSelectedCount = 0;
		for (int i = 0; i < selectedItems.length; i++) {
			if (!selectedItems [i].isDisposed ()) newSelectedCount++;
		}
		if (newSelectedCount != selectedItems.length) {
			/* one or more selected items have been disposed */
			TreeItem[] newSelectedItems = new TreeItem [newSelectedCount];
			int pos = 0;
			for (int i = 0; i < selectedItems.length; i++) {
				TreeItem item = selectedItems [i];
				if (!item.isDisposed ()) {
					newSelectedItems [pos++] = item;
				}
			}
			selectedItems = newSelectedItems;
		}

		if (insertMarkItem != null && insertMarkItem.isDisposed ()) insertMarkItem = null;
		if (lastClickedItem != null && lastClickedItem.isDisposed ()) lastClickedItem = null;
		if (anchorItem != null && anchorItem.isDisposed ()) anchorItem = null;
		if (focusItem != null && focusItem.isDisposed ()) {
			TreeItem newFocusItem = count > 0 ? items [count - 1] : null; 
			setFocusItem (newFocusItem, false);
		}
		if (columns.length == 0) updateHorizontalBar ();
	} else {
		int grow = count - items.length;
		redrawStart = items.length == 0 ? 0 : items [items.length - 1].availableIndex;
		redrawEnd = availableItemsCount + grow - 1;
		TreeItem[] newItems = new TreeItem [count];
		System.arraycopy (items, 0, newItems, 0, items.length);
		items = newItems;
		if (availableItems.length < availableItemsCount + grow) {
			TreeItem[] newAvailableItems = new TreeItem [availableItemsCount + grow];
			System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItemsCount);
			availableItems = newAvailableItems;
		}
		for (int i = items.length - grow; i < count; i++) {
			TreeItem newItem = new TreeItem (this, SWT.NONE, i, false);
			items [i] = newItem;
			items [i].availableIndex = availableItemsCount;
			availableItems [availableItemsCount++] = newItem;
		}
		if (oldCount == 0) focusItem = availableItems [0];
	}

	updateVerticalBar ();
	/*
	 * If this is the focus control and the available item count is going from 0->!0 or !0->0
	 * then the receiver must be redrawn to ensure that its boundary focus ring is updated.
	 */
	if ((oldCount == 0 || availableItemsCount == 0) && isFocusControl ()) {
		redraw ();
		return;
	}
	redrawItems (redrawStart, redrawEnd, false);
}
boolean setItemHeight (int value) {
	boolean update = !customHeightSet || itemHeight < value; 
	if (update) itemHeight = value;
	return update;
}
/**
 * Marks the receiver's lines as visible if the argument is <code>true</code>,
 * and marks it invisible otherwise. 
 * <p>
 * If one of the receiver's ancestors is not visible or some
 * other condition makes the receiver not visible, marking
 * it visible may not actually cause it to be displayed.
 * </p>
 *
 * @param show the new visibility state
 *
 * @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.1
 */
public void setLinesVisible (boolean value) {
	checkWidget ();
	if (linesVisible == value) return;		/* no change */
	linesVisible = value;
	redraw ();
}
public void setRedraw (boolean value) {
	checkWidget();
	if (value) {
		if (--drawCount == 0) {
			if (availableItems.length - availableItemsCount > 3) {
				TreeItem[] newAvailableItems = new TreeItem [availableItemsCount];
				System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItemsCount);
				availableItems = newAvailableItems;
			}
			updateVerticalBar ();
			updateHorizontalBar ();
		}
	} else {
		drawCount++;
	}
	super.setRedraw (value);
	header.setRedraw (value);
}
/**
 * Sets the receiver's selection to the given item.
 * The current selection is cleared before the new item is selected.
 * <p>
 * If the item is not in the receiver, then it is ignored.
 * </p>
 *
 * @param item the item to select
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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 (TreeItem item) {
	checkWidget ();
	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
	setSelection (new TreeItem [] {item}, true);
}
/**
 * Sets the receiver's selection to be the given array of items.
 * The current selection is cleared before the new items are selected.
 * <p>
 * Items that are not in the receiver are ignored.
 * If the receiver is single-select and multiple items are specified,
 * then all items are ignored.
 * </p>
 *
 * @param items the array of items
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the array of items is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if one of the items has been disposed</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 Tree#deselectAll()
 */
public void setSelection (TreeItem[] items) {
	checkWidget ();
	if (items == null) error (SWT.ERROR_NULL_ARGUMENT);
	setSelection (items, true);
}

void setSelection (TreeItem[] items, boolean updateViewport) {
	if (items.length == 0 || ((style & SWT.SINGLE) != 0 && items.length > 1)) {
		deselectAll ();
		return;
	}
	TreeItem[] oldSelection = selectedItems;
	
	/* remove null and duplicate items */
	int index = 0;
	selectedItems = new TreeItem [items.length];	/* initially assume all valid items */
	for (int i = 0; i < items.length; i++) {
		TreeItem item = items [i];
		if (item != null && item.parent == this && !item.isSelected ()) {
			selectedItems [index++] = item;
		}
	}
	if (index != items.length) {
		/* an invalid item was provided so resize the array accordingly */
		TreeItem[] temp = new TreeItem [index];
		System.arraycopy (selectedItems, 0, temp, 0, index);
		selectedItems = temp;
	}
	if (selectedItems.length == 0) {	/* no valid items */
		deselectAll ();
		return;
	}

	for (int i = 0; i < oldSelection.length; i++) {
		if (!oldSelection [i].isSelected ()) {
			int availableIndex = oldSelection [i].availableIndex;
			if (availableIndex != -1) {
				redrawItem (availableIndex, true);
			}
		}
	}
	if (updateViewport) {
		showItem (selectedItems [0]);
		setFocusItem (selectedItems [0], true);
	}
	for (int i = 0; i < selectedItems.length; i++) {
		int availableIndex = selectedItems [i].availableIndex;
		if (availableIndex != -1) {
			redrawItem (availableIndex, true);
		}
	}
}
/**
 * Sets the column used by the sort indicator for the receiver. A null
 * value will clear the sort indicator.  The current sort column is cleared 
 * before the new column is set.
 *
 * @param column the column used by the sort indicator or <code>null</code>
 * 
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the column is disposed</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 setSortColumn (TreeColumn column) {
	checkWidget ();
	if (column != null && column.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	if (column == sortColumn) return;
	if (sortColumn != null && !sortColumn.isDisposed ()) {
		sortColumn.setSortDirection (SWT.NONE);
	}
	sortColumn = column;
	if (sortColumn != null) {
		sortColumn.setSortDirection (sortDirection);
	}
}
/**
 * Sets the direction of the sort indicator for the receiver. The value 
 * can be one of <code>UP</code>, <code>DOWN</code> or <code>NONE</code>.
 *
 * @param direction the direction of the sort indicator 
 *
 * @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 setSortDirection (int direction) {
	checkWidget ();
	if (direction != SWT.UP && direction != SWT.DOWN && direction != SWT.NONE) return;
	sortDirection = direction;
	if (sortColumn == null || sortColumn.isDisposed ()) return;
	sortColumn.setSortDirection (sortDirection);
}
/**
 * Sets the item which is currently at the top of the receiver.
 * This item can change when items are expanded, collapsed, scrolled
 * or new items are added or removed.
 *
 * @param item the item to be shown
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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 Tree#getTopItem()
 * 
 * @since 2.1
 */
public void setTopItem (TreeItem item) {
	checkWidget ();
	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	if (item.parent != this) return;

	/* item must be available */
	if (!item.isAvailable ()) item.parentItem.expandAncestors ();

	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
	if (availableItemsCount < visibleItemCount) return;
	int index = Math.min (item.availableIndex, availableItemsCount - visibleItemCount);
	if (topIndex == index) return;

	update ();
	int change = topIndex - index;
	topIndex = index;
	ScrollBar vBar = getVerticalBar ();
	if (vBar != null) vBar.setSelection (topIndex);
	if (drawCount <= 0) {
		GC gc = new GC (this);
		gc.copyArea (0, 0, clientArea.width, clientArea.height, 0, change * itemHeight);
		gc.dispose ();
	}
}
/**
 * Shows the column.  If the column is already showing in the receiver,
 * this method simply returns.  Otherwise, the columns are scrolled until
 * the column is visible.
 *
 * @param column the column to be shown
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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.1
 */
public void showColumn (TreeColumn column) {
	checkWidget ();
	if (column == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (column.isDisposed ()) error(SWT.ERROR_INVALID_ARGUMENT);
	if (column.parent != this) return;

	int x = column.getX ();
	int rightX = x + column.width;
	if (0 <= x && rightX <= clientArea.width) return;	 /* column is fully visible */

	headerHideToolTip ();
	int absX = 0;	/* the X of the column irrespective of the horizontal scroll */
	TreeColumn[] orderedColumns = getOrderedColumns ();
	for (int i = 0; i < column.getOrderIndex (); i++) {
		absX += orderedColumns [i].width;
	}
	if (x < clientArea.x) { 	/* column is to left of viewport */
		horizontalOffset = absX;
	} else {
		horizontalOffset = absX + column.width - clientArea.width;
	}
	ScrollBar hBar = getHorizontalBar (); 
	if (hBar != null) hBar.setSelection (horizontalOffset);
	redraw ();
	if (drawCount <= 0 && header.isVisible ()) header.redraw ();
}
/**
 * Shows the item.  If the item is already showing in the receiver,
 * this method simply returns.  Otherwise, the items are scrolled
 * and expanded until the item is visible.
 *
 * @param item the item to be shown
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</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 Tree#showSelection()
 */
public void showItem (TreeItem item) {
	checkWidget ();
	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
	if (item.parent != this) return;
	
	/* item must be available */
	if (!item.isAvailable ()) item.parentItem.expandAncestors ();
	
	int index = item.availableIndex;
	int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight;
	/* nothing to do if item is already in viewport */
	if (topIndex <= index && index < topIndex + visibleItemCount) return;
	
	if (index <= topIndex) {
		/* item is above current viewport, so show on top */
		setTopItem (item);
	} else {
		/* item is below current viewport, so show on bottom */
		visibleItemCount = Math.max (visibleItemCount, 1);	/* item to show should be top item */
		setTopItem (availableItems [Math.min (index - visibleItemCount + 1, availableItemsCount - 1)]);
	}
}
/**
 * Shows the selection.  If the selection is already showing in the receiver,
 * this method simply returns.  Otherwise, the items are scrolled until
 * the selection is visible.
 *
 * @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 Tree#showItem(TreeItem)
 */
public void showSelection () {
	checkWidget ();
	if (selectedItems.length == 0) return;
	showItem (selectedItems [0]);
}
void updateColumnWidth (TreeColumn column, int width) {
	headerHideToolTip ();
	int oldWidth = column.width;
	int columnX = column.getX ();
	int x = columnX + oldWidth - 1;	/* -1 ensures that grid line is included */

	update ();
	GC gc = new GC (this);
	gc.copyArea (x, 0, clientArea.width - x, clientArea.height, columnX + width - 1, 0);	/* dest x -1 offsets x's -1 above */
	if (width > oldWidth) {
		/* column width grew */
		int change = width - oldWidth + 1;	/* +1 offsets x's -1 above */
		/* -1/+1 below ensure that right bound of selection redraws correctly in column */
		redraw (x - 1, 0, change + 1, clientArea.height, false);
	} else {
		int change = oldWidth - width + 1;	/* +1 offsets x's -1 above */
		redraw (clientArea.width - change, 0, change, clientArea.height, false);
	}
	/* the focus box must be repainted because its stipple may become shifted as a result of its new width */
	if (focusItem != null) redrawItem (focusItem.availableIndex, true);

	GC headerGC = new GC (header);
	if (drawCount <= 0 && header.getVisible ()) {
		Rectangle headerBounds = header.getClientArea ();
		header.update ();
		x -= 1;	/* -1 ensures that full header column separator is included */
		headerGC.copyArea (x, 0, headerBounds.width - x, headerBounds.height, columnX + width - 2, 0);	/* dest x -2 offsets x's -1s above */
		if (width > oldWidth) {
			/* column width grew */
			int change = width - oldWidth + 2;	/* +2 offsets x's -1s above */
			header.redraw (x, 0, change, headerBounds.height, false);
		} else {
			int change = oldWidth - width + 2;	/* +2 offsets x's -1s above */
			header.redraw (headerBounds.width - change, 0, change, headerBounds.height, false);
		}
	}

	column.width = width;

	/*
	 * Notify column and all items of column width change so that display labels
	 * can be recomputed if needed.
	 */
	column.updateWidth (headerGC);
	headerGC.dispose ();
	for (int i = 0; i < items.length; i++) {
		items [i].updateColumnWidth (column, gc);
	}
	gc.dispose ();

	int maximum = 0;
	for (int i = 0; i < columns.length; i++) {
		maximum += columns [i].width;
	}
	ScrollBar hBar = getHorizontalBar ();
	if (hBar != null) {
		hBar.setMaximum (Math.max (1, maximum));	/* setting a value of 0 here is ignored */
		if (hBar.getThumb () != clientArea.width) {
			hBar.setThumb (clientArea.width);
			hBar.setPageIncrement (clientArea.width);
		}
		int oldHorizontalOffset = horizontalOffset;	/* hBar.setVisible() can modify horizontalOffset */
		hBar.setVisible (clientArea.width < maximum);
		int selection = hBar.getSelection ();
		if (selection != oldHorizontalOffset) {
			horizontalOffset = selection;
			redraw ();
			if (drawCount <= 0 && header.getVisible ()) header.redraw ();
		}
	}

	column.sendEvent (SWT.Resize);
	TreeColumn[] orderedColumns = getOrderedColumns ();
	for (int i = column.getOrderIndex () + 1; i < orderedColumns.length; i++) {
		if (!orderedColumns [i].isDisposed ()) {
			orderedColumns [i].sendEvent (SWT.Move);
		}
	}

	if (availableItemsCount == 0) redraw ();	/* ensure that static focus rectangle updates properly */
}
/*
 * This is a naive implementation that computes the value from scratch.
 */
void updateHorizontalBar () {
	if (drawCount > 0) return;
	ScrollBar hBar = getHorizontalBar ();
	if (hBar == null) return;

	int maxX = 0;
	if (columns.length > 0) {
		for (int i = 0; i < columns.length; i++) {
			maxX += columns [i].width;
		}
	} else {
		for (int i = 0; i < availableItemsCount; i++) {
			Rectangle itemBounds = availableItems [i].getCellBounds (0);
			maxX = Math.max (maxX, itemBounds.x + itemBounds.width + horizontalOffset);
		}
	}
	
	int clientWidth = clientArea.width;
	if (maxX != hBar.getMaximum ()) {
		hBar.setMaximum (Math.max (1, maxX));	/* setting a value of 0 here is ignored */
	}
	int thumb = Math.min (clientWidth, maxX);
	if (thumb != hBar.getThumb ()) {
		hBar.setThumb (thumb);
		hBar.setPageIncrement (thumb);
	}
	hBar.setVisible (clientWidth < maxX);
	
	/* reclaim any space now left on the right */
	if (maxX < horizontalOffset + thumb) {
		horizontalOffset = maxX - thumb;
		hBar.setSelection (horizontalOffset);
		redraw ();
	} else {
		int selection = hBar.getSelection ();
		if (selection != horizontalOffset) {
			horizontalOffset = selection;
			redraw ();
		}
	}
}
/*
 * Update the horizontal bar, if needed, in response to an item change (eg.- created,
 * disposed, expanded, etc.).  newRightX is the new rightmost X value of the item,
 * and rightXchange is the change that led to the item's rightmost X value becoming
 * newRightX (so oldRightX + rightXchange = newRightX)
 */
void updateHorizontalBar (int newRightX, int rightXchange) {
	if (drawCount > 0) return;
	ScrollBar hBar = getHorizontalBar ();
	if (hBar == null) return;

	newRightX += horizontalOffset;
	int barMaximum = hBar.getMaximum ();
	if (newRightX > barMaximum) {	/* item has extended beyond previous maximum */
		hBar.setMaximum (newRightX);
		int clientAreaWidth = clientArea.width;
		int thumb = Math.min (newRightX, clientAreaWidth);
		if (hBar.getThumb () != thumb) {
			hBar.setThumb (thumb);
			hBar.setPageIncrement (thumb);
		}
		hBar.setVisible (clientAreaWidth <= newRightX);
		return;
	}

	int previousRightX = newRightX - rightXchange;
	if (previousRightX != barMaximum) {
		/* this was not the rightmost item, so just check for client width change */
		int clientAreaWidth = clientArea.width;
		int thumb = Math.min (barMaximum, clientAreaWidth);
		if (hBar.getThumb () != thumb) {
			hBar.setThumb (thumb);
			hBar.setPageIncrement (thumb);
		}
		hBar.setVisible (clientAreaWidth <= barMaximum);
		return;
	}
	updateHorizontalBar ();		/* must search for the new rightmost item */
}
void updateVerticalBar () {
	if (drawCount > 0) return;
	ScrollBar vBar = getVerticalBar ();
	if (vBar == null) return;

	int pageSize = (clientArea.height - getHeaderHeight ()) / itemHeight;
	int maximum = Math.max (1, availableItemsCount);
	if (maximum != vBar.getMaximum ()) {
		vBar.setMaximum (maximum);
	}
	int thumb = Math.min (pageSize, maximum);
	if (thumb != vBar.getThumb ()) {
		vBar.setThumb (thumb);
		vBar.setPageIncrement (thumb);
	}
	vBar.setVisible (pageSize < maximum);

	/* reclaim any space now left on the bottom */
	if (maximum < topIndex + thumb) {
		topIndex = maximum - thumb;
		vBar.setSelection (topIndex);
		redraw ();
	} else {
		int selection = vBar.getSelection ();
		if (selection != topIndex) {
			topIndex = selection;
			redraw ();
		}
	}
}
}
