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

/**
 * Instances of this class represent a selectable user interface object
 * that represents a hierarchy of tree items in a tree widget.
 * 
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>(none)</dd>
 * <dt><b>Events:</b></dt>
 * <dd>(none)</dd>
 * </dl>
 * <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/">Sample code and further information</a>
 */
public class TreeItem extends Item {
	Tree parent;
	TreeItem parentItem;
	TreeItem[] items = Tree.NO_ITEMS;
	int availableIndex = -1;	/* index in parent's flat list of available (though not necessarily within viewport) items */
	int depth = 0;				/* cached for performance, does not change after instantiation */
	boolean checked, grayed, expanded, cached;

	String[] texts;
	int[] textWidths = new int [1];	/* cached string measurements */
	int customWidth = -1;				/* width specified by Measure callback */
	int fontHeight;						/* cached item font height */
	int[] fontHeights;
	Image[] images;
	Color foreground, background;
	String[] displayTexts;
	Color[] cellForegrounds, cellBackgrounds;
	Font font;
	Font[] cellFonts;
	
	static final int INDENT_HIERARCHY = 6;	/* the margin between an item's expander and its checkbox or content */
	static final int MARGIN_TEXT = 3;			/* the left and right margins within the text's space */

/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Tree</code> or a <code>TreeItem</code>)
 * and a style value describing its behavior and appearance.
 * The item is added to the end of the items maintained by its parent.
 * <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 tree control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public TreeItem (Tree parent, int style) {
	this (parent, style, checkNull (parent).items.length);
}
/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Tree</code> or a <code>TreeItem</code>),
 * a style value describing its behavior and appearance, and the index
 * at which to place it in the items maintained by its parent.
 * <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 tree control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 * @param index the zero-relative index to store the receiver in its parent
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the parent (inclusive)</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public TreeItem (Tree parent, int style, int index) {
	this (parent, style, index, true);
}
/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Tree</code> or a <code>TreeItem</code>)
 * and a style value describing its behavior and appearance.
 * The item is added to the end of the items maintained by its parent.
 * <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 parentItem a tree control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public TreeItem (TreeItem parentItem, int style) {
	this (parentItem, style, checkNull (parentItem).items.length);
}
/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Tree</code> or a <code>TreeItem</code>),
 * a style value describing its behavior and appearance, and the index
 * at which to place it in the items maintained by its parent.
 * <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 parentItem a tree control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 * @param index the zero-relative index to store the receiver in its parent
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the parent (inclusive)</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public TreeItem (TreeItem parentItem, int style, int index) {
	this (parentItem, style, index, true);
}
TreeItem (TreeItem parentItem, int style, int index, boolean notifyParent) {
	super (parentItem, style);
	this.parentItem = parentItem;
	parent = parentItem.parent;
	depth = parentItem.depth + 1;
	int validItemIndex = parentItem.items.length;
	if (!(0 <= index && index <= validItemIndex)) error (SWT.ERROR_INVALID_RANGE);
	int columnCount = parent.columns.length;
	if (columnCount > 0) {
		displayTexts = new String [columnCount];
		if (columnCount > 1) {
			texts = new String [columnCount];
			textWidths = new int [columnCount];
			images = new Image [columnCount];
		}
	}
	if (notifyParent) parentItem.addItem (this, index);
}
TreeItem (Tree parent, int style, int index, boolean notifyParent) {
	super (parent, style);
	int validItemIndex = parent.items.length;
	if (!(0 <= index && index <= validItemIndex)) error (SWT.ERROR_INVALID_RANGE);
	this.parent = parent;
	int columnCount = parent.columns.length;
	if (columnCount > 0) {
		displayTexts = new String [columnCount];
		if (columnCount > 1) {
			texts = new String [columnCount];
			textWidths = new int [columnCount];
			images = new Image [columnCount];
		}
	}
	if (notifyParent) parent.createItem (this, index);
}
/*
 * Updates internal structures in the receiver and its child items to handle the creation of a new column.
 */
void addColumn (TreeColumn column) {
	int index = column.getIndex ();
	int columnCount = parent.columns.length;

	if (columnCount > 1) {
		if (columnCount == 2) {
			texts = new String [2];
		} else {
			String[] newTexts = new String [columnCount];
			System.arraycopy (texts, 0, newTexts, 0, index);
			System.arraycopy (texts, index, newTexts, index + 1, columnCount - index - 1);
			texts = newTexts;
		}
		if (index == 0) {
			texts [1] = text;
			text = "";	//$NON-NLS-1$
		}

		if (columnCount == 2) {
			images = new Image [2];
		} else {
			Image[] newImages = new Image [columnCount];
			System.arraycopy (images, 0, newImages, 0, index);
			System.arraycopy (images, index, newImages, index + 1, columnCount - index - 1);
			images = newImages;
		}
		if (index == 0) {
			images [1] = image;
			image = null;
		}
		
		int[] newTextWidths = new int [columnCount];
		System.arraycopy (textWidths, 0, newTextWidths, 0, index);
		System.arraycopy (textWidths, index, newTextWidths, index + 1, columnCount - index - 1);
		textWidths = newTextWidths;
	} else {
		customWidth = -1;		/* columnCount == 1 */
	}

	/*
	 * The length of displayTexts always matches the parent's column count, unless this
	 * count is zero, in which case displayTexts is null.  
	 */
	String[] newDisplayTexts = new String [columnCount];
	if (columnCount > 1) {
		System.arraycopy (displayTexts, 0, newDisplayTexts, 0, index);
		System.arraycopy (displayTexts, index, newDisplayTexts, index + 1, columnCount - index - 1);
	}
	displayTexts = newDisplayTexts;

	if (cellBackgrounds != null) {
		Color[] newCellBackgrounds = new Color [columnCount];
		System.arraycopy (cellBackgrounds, 0, newCellBackgrounds, 0, index);
		System.arraycopy (cellBackgrounds, index, newCellBackgrounds, index + 1, columnCount - index - 1);
		cellBackgrounds = newCellBackgrounds;
	}
	if (cellForegrounds != null) {
		Color[] newCellForegrounds = new Color [columnCount];
		System.arraycopy (cellForegrounds, 0, newCellForegrounds, 0, index);
		System.arraycopy (cellForegrounds, index, newCellForegrounds, index + 1, columnCount - index - 1);
		cellForegrounds = newCellForegrounds;
	}
	if (cellFonts != null) {
		Font[] newCellFonts = new Font [columnCount];
		System.arraycopy (cellFonts, 0, newCellFonts, 0, index);
		System.arraycopy (cellFonts, index, newCellFonts, index + 1, columnCount - index - 1);
		cellFonts = newCellFonts;

		int[] newFontHeights = new int [columnCount];
		System.arraycopy (fontHeights, 0, newFontHeights, 0, index);
		System.arraycopy (fontHeights, index, newFontHeights, index + 1, columnCount - index - 1);
		fontHeights = newFontHeights;
	}

	int orderedIndex = column.getOrderIndex ();
	if (orderedIndex == 0 && columnCount > 1) {
		/*
		 * The new second ordered column now has more space available to it than it did while
		 * it was the first ordered column since it no longer has to show hierarchy decorations,
		 * so recompute its displayText.
		 */
		TreeColumn[] orderedColumns = parent.getOrderedColumns ();
		int secondColumnIndex = orderedColumns [1].getIndex ();
		GC gc = new GC (parent);
		gc.setFont (getFont (secondColumnIndex, false));
		computeDisplayText (secondColumnIndex, gc);
		gc.dispose ();
	}
	
	/* notify all child items as well */
	for (int i = 0; i < items.length; i++) {
		items[i].addColumn (column);
	}
}
/*
 * Adds a child item to the receiver.
 */
void addItem (TreeItem item, int index) {
	TreeItem[] newChildren = new TreeItem [items.length + 1];
	System.arraycopy (items, 0, newChildren, 0, index);
	newChildren [index] = item;
	System.arraycopy (items, index, newChildren, index + 1, items.length - index);
	items = newChildren;

	if (!item.isAvailable ()) {
		/* receiver will now need an expander box if this is its first child */
		if (isInViewport () && items.length == 1) {
			Rectangle bounds = getExpanderBounds ();
			parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false);
		}
		return;
	}
	
	/* item should be available immediately so update parent */
	parent.makeAvailable (item);
	
	/* update scrollbars */
	Rectangle bounds = item.getBounds (false);
	int rightX = bounds.x + bounds.width;
	parent.updateHorizontalBar (rightX, rightX);
	parent.updateVerticalBar ();
	/* 
	 * 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 < parent.topIndex) {
		parent.topIndex++;
		ScrollBar vBar = parent.getVerticalBar ();
		if (vBar != null) vBar.setSelection (parent.topIndex);
		return;
	}
	
	parent.redrawFromItemDownwards (availableIndex);
}
static Tree checkNull (Tree tree) {
	if (tree == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
	return tree;
}
static TreeItem checkNull (TreeItem item) {
	if (item == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
	return item;
}
protected void checkSubclass () {
	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
}
void clear () {
	checked = grayed = false;
	texts = null;
	textWidths = new int [1];
	fontHeight = 0;
	fontHeights = null;
	images = null;
	foreground = background = null;
	displayTexts = null;
	cellForegrounds = cellBackgrounds = null;
	font = null;
	cellFonts = null;
	cached = false;
	text = "";
	image = null;

	int columnCount = parent.columns.length;
	if (columnCount > 0) {
		displayTexts = new String [columnCount];
		if (columnCount > 1) {
			texts = new String [columnCount];
			textWidths = new int [columnCount];
			images = new Image [columnCount];
		}
	}
}
/**
 * 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 (item.availableIndex != -1 && parent.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);
	}
	if (item.availableIndex == -1) return;	/* no visual update needed */

	/* adjust the horizontal scrollbar if needed */
	if (parent.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;
		}
		parent.updateHorizontalBar (newRightX, newRightX - oldRightX);
	}

	/* redraw the item(s) */
	if (recursive) {
		int descendentCount = availableDescendents == null ?
			item.computeAvailableDescendentCount () :
			availableDescendents.length;
		parent.redrawItems (item.availableIndex, item.availableIndex + descendentCount - 1, false);
	} else {
		parent.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) {
	clearAll (recursive, true);
}
void clearAll (boolean recursive, boolean doVisualUpdate) {
	checkWidget ();
	if (items.length == 0) return;

	/* if there are no columns then the horizontal scrollbar may need adjusting */
	TreeItem[] availableDescendents = null;
	int oldRightX = 0;
	if (doVisualUpdate && availableIndex != -1 && expanded && parent.columns.length == 0) {
		if (recursive) {
			availableDescendents = computeAvailableDescendents ();
			/*
			 * i starts at 1 here because item 0 in availableDescendents
			 * will be the receiver, but this item is not being cleared.
			 */
			for (int i = 1; i < availableDescendents.length; i++) {
				Rectangle bounds = availableDescendents [i].getBounds (false);
				oldRightX = Math.max (oldRightX, bounds.x + bounds.width);
			}
		} else {
			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);
	}

	if (!doVisualUpdate || availableIndex == -1 || !expanded) return;	/* no visual update needed */

	/* adjust the horizontal scrollbar if needed */
	if (parent.columns.length == 0) {
		int newRightX = 0;
		if (recursive) {
			/*
			 * i starts at 1 here because item 0 in availableDescendents
			 * is the receiver, but this item was not cleared.
			 */
			for (int i = 1; i < availableDescendents.length; i++) {
				Rectangle bounds = availableDescendents [i].getBounds (false);
				newRightX = Math.max (newRightX, bounds.x + bounds.width);
			}
		} else {
			/*
			 * All cleared direct child 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);
			newRightX = bounds.x + bounds.width;
		}
		parent.updateHorizontalBar (newRightX, newRightX - oldRightX);
	}

	/* redraw the item(s) */
	if (recursive) {
		int startIndex = items [0].availableIndex;
		TreeItem lastChild = items [items.length - 1]; 
		int endIndex = lastChild.availableIndex + lastChild.computeAvailableDescendentCount () - 1;
		parent.redrawItems (startIndex, endIndex, false);
	} else {
		for (int i = 0; i < items.length; i++) {
			parent.redrawItem (items [i].availableIndex, false);
		}
	}
}
/*
 * Returns a collection of all tree items descending from the receiver, including
 * the receiver.  The order of the items in this collection are receiver, child0tree,
 * child1tree, ..., childNtree. 
 */
TreeItem[] computeAllDescendents () {
	int childCount = items.length;
	TreeItem[][] childResults = new TreeItem [childCount][];
	int count = 1;	/* receiver */
	for (int i = 0; i < childCount; i++) {
		childResults [i] = items [i].computeAllDescendents ();
		count += childResults [i].length;
	}
	TreeItem[] result = new TreeItem [count];
	int index = 0;
	result [index++] = this;
	for (int i = 0; i < childCount; i++) {
		System.arraycopy (childResults [i], 0, result, index, childResults [i].length);
		index += childResults [i].length;
	}
	return result;
}
/*
 * Returns the number of tree items descending from the receiver, including the
 * receiver, that are currently available.  It is assumed that the receiver is
 * currently available. 
 */
int computeAvailableDescendentCount () {
	int result = 1;		/* receiver */
	if (!expanded) return result;
	for (int i = 0; i < items.length; i++) {
		result += items [i].computeAvailableDescendentCount ();
	}
	return result;
}
/*
 * Returns a collection of the tree items descending from the receiver, including
 * the receiver, that are currently available.  It is assumed that the receiver is
 * currently available.  The order of the items in this collection are receiver,
 * child0tree, child1tree, ..., childNtree. 
 */
TreeItem[] computeAvailableDescendents () {
	if (!expanded) return new TreeItem[] {this};
	int childCount = items.length;
	TreeItem[][] childResults = new TreeItem [childCount][];
	int count = 1;	/* receiver */
	for (int i = 0; i < childCount; i++) {
		childResults [i] = items [i].computeAvailableDescendents ();
		count += childResults [i].length;
	}
	TreeItem[] result = new TreeItem [count];
	int index = 0;
	result [index++] = this;
	for (int i = 0; i < childCount; i++) {
		System.arraycopy (childResults [i], 0, result, index, childResults [i].length);
		index += childResults [i].length;
	}
	return result;
}
void computeDisplayText (int columnIndex, GC gc) {
	if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return;	/* nothing to do */

	int columnCount = parent.columns.length;
	if (columnCount == 0) {
		String text = getText (0, false);
		textWidths [columnIndex] = gc.stringExtent (text).x;
		return;
	}

	int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex ();
	TreeColumn column = parent.columns [columnIndex];
	int availableWidth;
	if (orderedIndex == 0) {
		/* ordered column 0 is always LEFT and must consider hierarchy decorations */
		availableWidth = column.getX () + column.width - getTextX (columnIndex) - 2 * MARGIN_TEXT;
	} else {
		/* ordered columns > 0 may not be LEFT so cannot use getTextX (int) */
		availableWidth = column.width - 2 * parent.getCellPadding () - 2 * MARGIN_TEXT;
		if (images [columnIndex] != null) {
			availableWidth -= images [columnIndex].getBounds ().width;
			availableWidth -= Tree.MARGIN_IMAGE;
		}
	}
	String text = getText (columnIndex, false);
	int textWidth = gc.stringExtent (text).x;
	if (textWidth <= availableWidth) {
		displayTexts [columnIndex] = text;
		textWidths [columnIndex] = textWidth;
		return;
	}

	/* Ellipsis will be needed, so subtract their width from the available text width */
	int ellipsisWidth = gc.stringExtent (Tree.ELLIPSIS).x;
	availableWidth -= ellipsisWidth;
	if (availableWidth <= 0) {
		displayTexts [columnIndex] = Tree.ELLIPSIS;
		textWidths [columnIndex] = ellipsisWidth;
		return;
	}
	
	/* Make initial guess. */
	int index = Math.min (availableWidth / gc.getFontMetrics ().getAverageCharWidth (), text.length ());
	textWidth = gc.stringExtent (text.substring (0, index)).x;

	/* Initial guess is correct. */
	if (availableWidth == textWidth) {
		displayTexts [columnIndex] = text.substring (0, index) + Tree.ELLIPSIS;
		textWidths [columnIndex] = textWidth + ellipsisWidth;
		return;
	}

	/* Initial guess is too high, so reduce until fit is found. */
	if (availableWidth < textWidth) {
		do {
			index--;
			if (index < 0) {
				displayTexts [columnIndex] = Tree.ELLIPSIS;
				textWidths [columnIndex] = ellipsisWidth;
				return;
			}
			text = text.substring (0, index);
			textWidth = gc.stringExtent (text).x;
		} while (availableWidth < textWidth);
		displayTexts [columnIndex] = text + Tree.ELLIPSIS;
		textWidths [columnIndex] = textWidth + ellipsisWidth;
		return;
	}
	
	/* Initial guess is too low, so increase until overrun is found. */
	int previousWidth = 0;
	while (textWidth < availableWidth) {
		index++;
		previousWidth = textWidth;
		textWidth = gc.stringExtent (text.substring (0, index)).x;
	}
	displayTexts [columnIndex] = text.substring (0, index - 1) + Tree.ELLIPSIS;
	textWidths [columnIndex] = previousWidth + ellipsisWidth;
}
void computeDisplayTexts (GC gc) {
	if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return;	/* nothing to do */

	int columnCount = parent.columns.length;
	if (columnCount == 0) return;
	
	for (int i = 0; i < columnCount; i++) {
		gc.setFont (getFont (i, false));
		computeDisplayText (i, gc);
	}
}
/*
 * Computes the cached text widths.
 */
void computeTextWidths (GC gc) {
	if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return;	/* nothing to do */

	int validColumnCount = Math.max (1, parent.columns.length);
	textWidths = new int [validColumnCount];
	for (int i = 0; i < textWidths.length; i++) {
		String value = getDisplayText (i);
		if (value != null) {
			gc.setFont (getFont (i, false));
			textWidths [i] = gc.stringExtent (value).x;
		}
	}
}
public void dispose () {
	if (isDisposed ()) return;
	int startIndex = -1, endIndex = -1;
	Tree parent = this.parent;
	int index = getIndex ();
	
	/* determine the indices, if any, that will need to be visually updated */
	if (isAvailable ()) {
		if (isLastChild () && index > 0) {
			/* vertical connector lines no longer needed for this item */
			if (parentItem != null) {
				startIndex = parentItem.items [index - 1].availableIndex;
			} else {
				startIndex = parent.items [index - 1].availableIndex;
			}
		} else {
			startIndex = availableIndex;
		}
		endIndex = parent.availableItemsCount - 1;
	}

	/* for performance do this upfront for whole descendent chain */
	TreeItem focusItem = parent.focusItem; 
	if (focusItem != null && focusItem.hasAncestor (this)) {
		parent.setFocusItem (this, false);
		parent.reassignFocus ();
		focusItem = parent.focusItem;
		if (focusItem != null) {
			parent.redrawItem (focusItem.availableIndex, true);
		}
	}
	if (parentItem != null) parentItem.removeItem (this, index);
	dispose (true);
	if (startIndex != -1) {
		parent.redrawItems (startIndex, endIndex, false);
	}
}
void dispose (boolean notifyParent) {
	if (isDisposed ()) return;
	for (int i = 0; i < items.length; i++) {
		items [i].dispose (notifyParent);
	}
	if (notifyParent) parent.destroyItem (this);
	super.dispose ();	/* super is intentional here */
	background = foreground = null;
	cellBackgrounds = cellForegrounds = null;
	font = null;
	cellFonts = null;
	images = null;
	texts = displayTexts = null;
	textWidths = fontHeights = null;
	parent = null;
	parentItem = null;
	items = null;
}
/*
 * Ensure that all ancestors of the receiver are expanded
 */
void expandAncestors () {
	if (parentItem != null) parentItem.expandAncestors ();
	setExpanded (true);
	Event newEvent = new Event ();
	newEvent.item = this;
	parent.inExpand = true;
	parent.sendEvent (SWT.Expand, newEvent);
	parent.inExpand = false;
	if (isDisposed ()) return;
	if (items.length == 0) {
		expanded = false;
	}
}
/**
 * Returns the receiver's background color.
 *
 * @return the background color
 * 
 * @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.0
 * 
 */
public Color getBackground () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	if (background != null) return background;
	return parent.getBackground ();
}
/**
 * Returns the background color at the given column index in the receiver.
 *
 * @param index the column index
 * @return the background color
 *
 * @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 Color getBackground (int columnIndex) {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getBackground ();
	if (cellBackgrounds == null || cellBackgrounds [columnIndex] == null) return getBackground ();
	return cellBackgrounds [columnIndex];
}
/**
 * Returns a rectangle describing the receiver's size and location
 * relative to its parent.
 *
 * @return the receiver's bounding rectangle
 *
 * @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 Rectangle getBounds () {
	checkWidget ();
	return getBounds (true);
}
Rectangle getBounds (boolean checkData) {
	if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	if (!isAvailable ()) return new Rectangle (0, 0, 0, 0);
	TreeColumn[] orderedColumns = parent.getOrderedColumns ();
	int orderedCol0Index = orderedColumns.length == 0 ? 0 : orderedColumns [0].getIndex ();
	int x = getTextX (orderedCol0Index);
	int width = textWidths [orderedCol0Index] + 2 * MARGIN_TEXT;
	if (orderedColumns.length > 0) {
		TreeColumn column = orderedColumns [0];
		int right = column.getX () + column.width;
		if (x + width > right) {
			width = Math.max (0, right - x);
		}
	}
	return new Rectangle (x, parent.getItemY (this), width, parent.itemHeight);
}
/**
 * Returns a rectangle describing the receiver's size and location
 * relative to its parent at a column in the tree.
 *
 * @param index the index that specifies the column
 * @return the receiver's bounding column rectangle
 *
 * @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 Rectangle getBounds (int columnIndex) {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	if (!isAvailable ()) return new Rectangle (0, 0, 0, 0);
	TreeColumn[] columns = parent.columns;
	int columnCount = columns.length;
	int validColumnCount = Math.max (1, columnCount);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) {
		return new Rectangle (0, 0, 0, 0);
	}
	/*
	 * If there are no columns then this is the bounds of the receiver's content.
	 */
	if (columnCount == 0) {
		return new Rectangle (
			getContentX (0),
			parent.getItemY (this),
			getContentWidth (0),
			parent.itemHeight - 1);
	}

	TreeColumn column = columns [columnIndex];
	if (column.getOrderIndex () == 0) {
		/* 
		 * For ordered column 0 this is bounds from the beginning of the content to the
		 * end of the column.
		 */
		int x = getContentX (columnIndex);
		int offset = x - column.getX ();
		int width = Math.max (0, column.width - offset - 1);		/* max is for columns with small widths */
		return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1);
	}
	/*
	 * For ordered columns > 0 this is the bounds of the tree cell.
	 */
	return new Rectangle (column.getX (), parent.getItemY (this) + 1, column.width, parent.itemHeight - 1);
}
/*
 * Returns the full bounds of a cell in a tree, regardless of its content.
 */
Rectangle getCellBounds (int columnIndex) {
	int y = parent.getItemY (this);
	if (parent.columns.length == 0) {
		int width;
		if (customWidth != -1) {
			width = getContentX (0) + customWidth + parent.horizontalOffset;
		} else {
			int textPaintWidth = textWidths [0] + 2 * MARGIN_TEXT;
			width = getTextX (0) + textPaintWidth + parent.horizontalOffset;
		}
		return new Rectangle (-parent.horizontalOffset, y, width, parent.itemHeight);
	}
	TreeColumn column = parent.columns [columnIndex];
	return new Rectangle (column.getX (), y, column.width, parent.itemHeight);
}
/*
 * Returns the bounds of the receiver's checkbox, or null if the parent's style does not
 * include SWT.CHECK.
 */
Rectangle getCheckboxBounds () {
	if ((parent.getStyle () & SWT.CHECK) == 0) return null;
	int itemHeight = parent.itemHeight;
	Rectangle result = parent.checkboxBounds;
	Point[] hLinePoints = getHconnectorEndpoints ();
	result.x = hLinePoints [1].x;
	result.y = parent.getItemY (this) + (itemHeight - result.height) / 2;
	return result;
}
/**
 * Returns <code>true</code> if the receiver is checked,
 * and false otherwise.  When the parent does not have
 * the <code>CHECK style, return false.
 * <p>
 *
 * @return the checked 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>
 */
public boolean getChecked () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	return checked;
}
int getContentWidth (int columnIndex) {
	int width = textWidths [columnIndex] + 2 * MARGIN_TEXT;
	int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex ();
	if (orderedIndex == 0) {
		width += parent.orderedCol0imageWidth;
		if (parent.orderedCol0imageWidth > 0) width += Tree.MARGIN_IMAGE;
	} else {
		Image image = getImage (columnIndex, false);
		if (image != null) {
			width += image.getBounds ().width + Tree.MARGIN_IMAGE;
		}
	}
	return width;
}
/*
 * Returns the x value where the receiver's content (ie.- its image or text) begins
 * for the specified column.  For ordered columns > 0 this is dependent upon column
 * alignment, and for ordered column 0 this is dependent upon the receiver's depth in
 * the tree item hierarchy and the presence/absence of a checkbox.
 */
int getContentX (int columnIndex) {
	int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex ();
	if (orderedIndex > 0) {
		TreeColumn column = parent.columns [columnIndex];
		int contentX = column.getX () + parent.getCellPadding ();
		if ((column.style & SWT.LEFT) != 0) return contentX;

		/* column is not left-aligned */
		int contentWidth = getContentWidth (columnIndex);
		if ((column.style & SWT.RIGHT) != 0) {
			int padding = parent.getCellPadding ();
			contentX = Math.max (contentX, column.getX () + column.width - padding - contentWidth);	
		} else {	/* SWT.CENTER */
			contentX = Math.max (contentX, column.getX () + (column.width - contentWidth) / 2);
		}
		return contentX;
	}

	/* ordered column 0 (always left-aligned) */
	if ((parent.style & SWT.CHECK) != 0) {
		Rectangle checkBounds = getCheckboxBounds ();
		return checkBounds.x + checkBounds.width + Tree.MARGIN_IMAGE;
	}

	int contentX = parent.getCellPadding () - parent.horizontalOffset;
	if (parentItem != null) {
		int expanderWidth = parent.expanderBounds.width + INDENT_HIERARCHY;
		contentX += expanderWidth * depth;
	}
	contentX += parent.expanderBounds.width;
	return contentX + Tree.MARGIN_IMAGE + INDENT_HIERARCHY;
}
String getDisplayText (int columnIndex) {
	if (parent.columns.length == 0) return getText (0, false);
	String result = displayTexts [columnIndex];
	return result != null ? result : "";	//$NON-NLS-1$
}
/**
 * Returns <code>true</code> if the receiver is expanded,
 * and false otherwise.
 * <p>
 *
 * @return the expanded 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>
 */
public boolean getExpanded () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	return expanded;
}
/*
 * Returns the bounds of the receiver's expander box, regardless of whether the
 * receiver currently has children or not.
 */ 
Rectangle getExpanderBounds () {
	int itemHeight = parent.itemHeight;
	int x = parent.getCellPadding () - parent.horizontalOffset;
	int y = parent.getItemY (this);
	if (parentItem != null) {
		int expanderWidth = parent.expanderBounds.width + INDENT_HIERARCHY;
		x += expanderWidth * depth;
	}
	return new Rectangle (
		x, y + (itemHeight - parent.expanderBounds.height) / 2,
		parent.expanderBounds.width, parent.expanderBounds.height);
}
/*
 * Returns the bounds that should be used for drawing a focus rectangle on the receiver
 */
Rectangle getFocusBounds () {
	TreeColumn[] columns = parent.columns;
	int orderedCol0index = columns.length == 0 ? 0 : parent.getOrderedColumns ()[0].getIndex ();
	int x = getTextX (orderedCol0index);

	int width;
	if (columns.length > 0) {
		/* ensure that the focus x does not start beyond the right bound of ordered column 0 */
		int rightX = columns [orderedCol0index].getX () + columns [orderedCol0index].width;
		x = Math.min (x, rightX - 1);
		
		TreeColumn column;
		if ((parent.style & SWT.FULL_SELECTION) != 0) {
			int[] columnOrder = parent.getColumnOrder ();
			column = columns [columnOrder [columnOrder.length - 1]];	/* last ordered column */
		} else {
			column = columns [orderedCol0index];
		}
		width = column.getX () + column.width - x - 1;
	} else {	/* no columns */
		if (customWidth != -1) {
			width = customWidth;
		} else {
			width = textWidths [0] + 2 * MARGIN_TEXT;
		}
	}

	return new Rectangle (
		x,
		parent.getItemY (this) + (parent.linesVisible ? 1 : 0),
		width,
		parent.itemHeight - (parent.linesVisible ? 1 : 0));
}
/**
 * Returns the font that the receiver will use to paint textual information for this item.
 *
 * @return the receiver's font
 *
 * @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.0
 */
public Font getFont () {
	checkWidget ();
	return getFont (true);
}
Font getFont (boolean checkData) {
	if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	if (font != null) return font;
	return parent.getFont ();
}
/**
 * Returns the font that the receiver will use to paint textual information
 * for the specified cell in this item.
 *
 * @param index the column index
 * @return the receiver's font
 *
 * @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 Font getFont (int columnIndex) {
	checkWidget ();
	return getFont (columnIndex, true);
}
Font getFont (int columnIndex, boolean checkData) {
	if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getFont (checkData);
	if (cellFonts == null || cellFonts [columnIndex] == null) return getFont (checkData);
	return cellFonts [columnIndex];
}
int getFontHeight () {
	if (fontHeight != 0) return fontHeight;
	return parent.fontHeight;
}
int getFontHeight (int columnIndex) {
	if (fontHeights == null || fontHeights [columnIndex] == 0) return getFontHeight ();
	return fontHeights [columnIndex];
}
/**
 * Returns the foreground color that the receiver will use to draw.
 *
 * @return the receiver's foreground color
 *
 * @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.0
 * 
 */
public Color getForeground () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	if (foreground != null) return foreground;
	return parent.getForeground ();
}
/**
 * 
 * Returns the foreground color at the given column index in the receiver.
 *
 * @param index the column index
 * @return the foreground color
 *
 * @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 Color getForeground (int columnIndex) {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getForeground ();
	if (cellForegrounds == null || cellForegrounds [columnIndex] == null) return getForeground ();
	return cellForegrounds [columnIndex];
}
/**
 * Returns <code>true</code> if the receiver is grayed,
 * and false otherwise. When the parent does not have
 * the <code>CHECK style, return false.
 * <p>
 *
 * @return the grayed state of the checkbox
 *
 * @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 boolean getGrayed () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	return grayed;
}
/*
 * Answers the start and end points of the horizontal connector line that is
 * drawn between an item's expander box and its checkbox or content.
 */
Point[] getHconnectorEndpoints () {
	Rectangle expanderBounds = getExpanderBounds ();
	int x, width;
	if (items.length == 0) {	/* no child items, so no expander box */
		x = expanderBounds.x + Compatibility.ceil (expanderBounds.width, 2);
		width = Compatibility.floor (expanderBounds.width, 2) + INDENT_HIERARCHY;
	} else {					/* has child items */
		x = expanderBounds.x + expanderBounds.width;
		width = INDENT_HIERARCHY;
	}
	int y = expanderBounds.y + expanderBounds.height / 2;
	return new Point[] {
		new Point (x, y),
		new Point (x + width, y)
	};
}
/*
 * Returns the bounds representing the clickable region that should select the receiver.
 */
Rectangle getHitBounds () {
	int[] columnOrder = parent.getColumnOrder ();
	int orderedCol0index = columnOrder.length == 0 ? 0 : parent.columns [columnOrder [0]].getIndex ();
	int contentX = getContentX (orderedCol0index);
	int width = 0;
	TreeColumn[] columns = parent.columns;
	if (columns.length == 0) {
		width = getContentWidth (0); 
	} else {
		/* 
		 * If there are columns then this spans from the beginning of the receiver's column 0
		 * image or text to the end of either column 0 or the last column (FULL_SELECTION).
		 */
		TreeColumn column;
		if ((parent.style & SWT.FULL_SELECTION) != 0) {
			column = columns [columnOrder [columnOrder.length - 1]];	/* last column */
		} else {
			column = columns [orderedCol0index];
		}
		width = column.getX () + column.width - contentX;
	}
	return new Rectangle (contentX, parent.getItemY (this), width, parent.itemHeight);
}
public Image getImage () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	return super.getImage ();
}
/**
 * Returns the image stored at the given column index in the receiver,
 * or null if the image has not been set or if the column does not exist.
 *
 * @param index the column index
 * @return the image stored at the given column index 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>
 * 
 * @since 3.1
 */
public Image getImage (int columnIndex) {
	checkWidget ();
	return getImage (columnIndex, true);
}
Image getImage (int columnIndex, boolean checkData) {
	if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return null;
	if (columnIndex == 0) return super.getImage ();	/* super is intentional here */
	return images [columnIndex];
}
/**
 * Returns a rectangle describing the size and location
 * relative to its parent of an image at a column in the
 * tree.
 *
 * @param index the index that specifies the column
 * @return the receiver's bounding image rectangle
 *
 * @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 Rectangle getImageBounds (int columnIndex) {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return new Rectangle (0,0,0,0);

	int padding = parent.getCellPadding ();
	int startX = getContentX (columnIndex);
	int itemHeight = parent.itemHeight;
	int imageSpaceY = itemHeight - 2 * padding;
	int y = parent.getItemY (this);
	int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex ();
	Image image = getImage (columnIndex, false); 
	int drawWidth = 0;
	if (orderedIndex == 0) {
		/* for ordered column 0 all images have the same width */
		drawWidth = parent.orderedCol0imageWidth;
	} else {
		if (image != null) drawWidth = image.getBounds ().width;
	}
	return new Rectangle (startX, y + padding, drawWidth, imageSpaceY);
}
int getIndex () {
	TreeItem[] items;
	if (parentItem != null) {
		items = parentItem.items;
	} else {
		items = parent.items;
	}
	for (int i = 0; i < items.length; i++) {
		if (items [i] == this) return i;
	}
	return -1;
}
/**
 * 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 (index < 0) error (SWT.ERROR_INVALID_RANGE);
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	if (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.
 *
 * @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 ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	return items.length;
}
String getNameText () {
	if ((parent.style & SWT.VIRTUAL) != 0) {
		if (!cached) return "*virtual*"; //$NON-NLS-1$
	}
	return super.getNameText ();
}
/**
 * Returns a (possibly empty) array of <code>TreeItem</code>s which
 * are the direct item children of the receiver.
 * <p>
 * Note: This is not the actual structure used by the receiver
 * to maintain its list of items, so modifying the array will
 * not affect the receiver. 
 * </p>
 *
 * @return the 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>
 */
public TreeItem [] getItems () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	TreeItem[] result = new TreeItem [items.length];
	System.arraycopy (items, 0, result, 0, items.length);
	return result;
}
/**
 * Returns the receiver's parent, which must be a <code>Tree</code>.
 *
 * @return the receiver's parent
 *
 * @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 Tree getParent () {
	checkWidget ();
	return parent;
}
/**
 * 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 parentItem;
}
/*
 * Returns the receiver's ideal width for the specified columnIndex.
 */
int getPreferredWidth (int columnIndex) {
	int width = 0;
	GC gc = new GC (parent);
	gc.setFont (getFont (columnIndex, false));
	width += gc.stringExtent (getText (columnIndex, false)).x + 2 * MARGIN_TEXT;
	int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex ();
	if (orderedIndex == 0) {
		if (parent.orderedCol0imageWidth > 0) {
			width += parent.orderedCol0imageWidth;
			width += Tree.MARGIN_IMAGE;
		}
	} else {
		Image image = getImage (columnIndex, false);
		if (image != null) {
			width += image.getBounds ().width;
			width += Tree.MARGIN_IMAGE;
		}
	}

	if (parent.hooks (SWT.MeasureItem)) {
		Event event = new Event ();
		event.item = this;
		event.gc = gc;
		event.index = columnIndex;
		event.x = getContentX (columnIndex);
		event.y = parent.getItemY (this);
		event.width = width;
		event.height = parent.itemHeight;
		parent.sendEvent (SWT.MeasureItem, event);
		if (parent.itemHeight != event.height) {
			parent.customHeightSet = true;
			boolean update = parent.setItemHeight (event.height + 2 * parent.getCellPadding ());
			if (update) parent.redraw ();
		}
		width = event.width;
	}
	gc.dispose ();

	if (orderedIndex == 0) {
		return getContentX (columnIndex) + parent.horizontalOffset + width + parent.getCellPadding ();	/* right side cell pad */
	}

	return width + 2 * parent.getCellPadding ();
}
public String getText () {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	return super.getText ();
}
/**
 * Returns the text stored at the given column index in the receiver,
 * or empty string if the text has not been set.
 *
 * @param index the column index
 * @return the text stored at the given column index 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>
 * 
 * @since 3.1
 */
public String getText (int columnIndex) {
	checkWidget ();
	return getText (columnIndex, true);
}
String getText (int columnIndex, boolean checkData) {
	if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return "";	//$NON-NLS-1$
	if (columnIndex == 0) return super.getText ();	/* super is intentional here */
	if (texts [columnIndex] == null) return "";	//$NON-NLS-1$
	return texts [columnIndex];
}
/**
 * Returns a rectangle describing the size and location
 * relative to its parent of the text at a column in the
 * tree.
 *
 * @param index the index that specifies the column
 * @return the receiver's bounding text rectangle
 *
 * @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.3
 */
public Rectangle getTextBounds (int columnIndex) {
	checkWidget ();
	if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED);
	if (!isAvailable ()) return new Rectangle (0, 0, 0, 0);
	TreeColumn[] columns = parent.columns;
	int columnCount = columns.length;
	int validColumnCount = Math.max (1, columnCount);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) {
		return new Rectangle (0, 0, 0, 0);
	}
	/*
	 * If there are no columns then this is the bounds of the receiver's content.
	 */
	if (columnCount == 0) {
		int x = getTextX (0) + MARGIN_TEXT;
		int width = Math.max (0, getContentX(0) + getContentWidth (0) - x);
		return new Rectangle (
			x,
			parent.getItemY (this),
			width,
			parent.itemHeight - 1);
	}

	TreeColumn column = columns [columnIndex];
	if (column.getOrderIndex () == 0) {
		/* 
		 * For ordered column 0 this is bounds from the beginning of the content to the
		 * end of the column.
		 */
		int x = getTextX (columnIndex) + MARGIN_TEXT;
		int offset = x - column.getX ();
		int width = Math.max (0, column.width - offset - 1);		/* max is for columns with small widths */
		return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1);
	}
	/*
	 * For ordered columns > 0 this is the bounds of the tree cell.
	 */
	int x = getTextX (columnIndex) + MARGIN_TEXT;
	int offset = x - column.getX ();
	int width = Math.max (0, column.width - offset - MARGIN_TEXT);
	return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1);
}
/*
 * Returns the x value where the receiver's text begins.
 */
int getTextX (int columnIndex) {
	int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex ();
	int textX = getContentX (columnIndex);
	if (orderedIndex == 0) {
		textX += parent.orderedCol0imageWidth;
		if (parent.orderedCol0imageWidth > 0) textX += Tree.MARGIN_IMAGE;
	} else {
		Image image = getImage (columnIndex, false);
		if (image != null) {
			textX += image.getBounds ().width + Tree.MARGIN_IMAGE;	
		}
	}
	return textX;
}
/*
 * Returns true if the receiver descends from (or is identical to) the item.
 */
boolean hasAncestor (TreeItem item) {
	if (this == item) return true;
	if (parentItem == null) return false;
	return parentItem.hasAncestor (item);
}
/**
 * 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 != this) return -1;
	return item.getIndex ();
}
/*
 * Returns true if the receiver is currently available (though not necessary in the viewport).
 */
boolean isAvailable () {
	if (parentItem == null) return true; 	/* root items are always available */
	if (!parentItem.expanded) return false;
	return parentItem.isAvailable ();
}
/*
 * Answers a boolean indicating whether the receiver's y is within the current
 * viewport of the parent.
 */
boolean isInViewport () {
	if (availableIndex == -1) return false;
	int topIndex = parent.topIndex;
	if (availableIndex < topIndex) return false;
	int visibleCount = parent.clientArea.height / parent.itemHeight;
	return availableIndex <= topIndex + visibleCount;
}
/*
 * Returns true if the receiver is the last child of its parent item, or of its parent
 * if the receiver is a root item, and false otherwise.
 */
boolean isLastChild () {
	if (parentItem != null) {
		return getIndex () == parentItem.items.length - 1;
	}
	return getIndex () == parent.items.length - 1;
}
boolean isSelected () {
	return parent.getSelectionIndex (this) != -1;
}
/*
 * The backgroundOnly argument indicates whether the item should only
 * worry about painting its background color and selection.
 *
 * Returns a boolean indicating whether to abort drawing focus on the item.
 * If the receiver is not the current focus item then this value is irrelevant.
 */
boolean paint (GC gc, TreeColumn column, boolean backgroundOnly) {
	if (!parent.checkData (this, true)) return false;
	int columnIndex = 0, orderedIndex = 0, x = 0;
	if (column != null) {
		columnIndex = column.getIndex ();
		orderedIndex = column.getOrderIndex ();
		x = column.getX ();
	}

	/* 
	 * Capture GC attributes that will need to be restored later in the paint
	 * process to ensure that the item paints as intended without being affected
	 * by GC changes made in MeasureItem/EraseItem/PaintItem callbacks.
	 */
	int oldAlpha = gc.getAlpha ();
	boolean oldAdvanced = gc.getAdvanced ();
	int oldAntialias = gc.getAntialias ();
	Pattern oldBackgroundPattern = gc.getBackgroundPattern ();
	Pattern oldForegroundPattern = gc.getForegroundPattern ();
	int oldInterpolation = gc.getInterpolation ();
	int[] oldLineDash = gc.getLineDash ();
	int oldLineWidth = gc.getLineWidth ();
	int oldTextAntialias = gc.getTextAntialias ();

	if (parent.hooks (SWT.MeasureItem)) {
		int contentWidth = getContentWidth (columnIndex);
		int contentX = getContentX (columnIndex);
		gc.setFont (getFont (columnIndex, false));
		Event event = new Event ();
		event.item = this;
		event.gc = gc;
		event.index = columnIndex;
		event.x = contentX;
		event.y = parent.getItemY (this);
		event.width = contentWidth;
		event.height = parent.itemHeight;
		parent.sendEvent (SWT.MeasureItem, event);
		event.gc = null;
		if (gc.isDisposed ()) return false;
		gc.setAlpha (oldAlpha);
		gc.setAntialias (oldAntialias);
		gc.setBackgroundPattern (oldBackgroundPattern);
		gc.setForegroundPattern (oldForegroundPattern);
		gc.setInterpolation (oldInterpolation);
		gc.setLineDash (oldLineDash);
		gc.setLineWidth (oldLineWidth);
		gc.setTextAntialias (oldTextAntialias);
		gc.setAdvanced (oldAdvanced);
		if (isDisposed ()) return false;
		if (parent.itemHeight != event.height) {
			parent.customHeightSet = true;
			boolean update = parent.setItemHeight (event.height + 2 * parent.getCellPadding ());
			if (update) parent.redraw ();
		}
		if (parent.columns.length == 0) {
			int change = event.width - (customWidth != -1 ? customWidth : contentWidth);
			if (event.width != contentWidth || customWidth != -1) customWidth = event.width;
			if (change != 0) {	/* scrollbar may be affected since no columns */
				parent.updateHorizontalBar (contentX + event.width, change);
				// TODO what if clip is too small now?
			}
		}
	}

	/* if this cell is completely to the right of the client area then there's no need to paint it */
	Rectangle clientArea = parent.clientArea;
	if (clientArea.x + clientArea.width < x) return false;

	Rectangle cellBounds = getCellBounds (columnIndex);
	if (parent.linesVisible) {
		cellBounds.y++;
		cellBounds.height--;
	}
	int cellRightX = 0;
	if (column != null) {
		cellRightX = column.getX () + column.width;
	} else {
		cellRightX = cellBounds.x + cellBounds.width;
	}

	/* restrict the clipping region to the cell */
	gc.setClipping (x, cellBounds.y, clientArea.width - x, cellBounds.height);
	
	int y = parent.getItemY (this);
	int itemHeight = parent.itemHeight;

	/* draw the parent background color/image of this cell */
	if (column == null) {
		parent.drawBackground (gc, 0, y, clientArea.width, itemHeight);
	} else {
		int fillWidth = cellBounds.width;
		if (parent.linesVisible) fillWidth--;
		parent.drawBackground (gc, cellBounds.x, cellBounds.y, fillWidth, cellBounds.height);
	}

	boolean isSelected = isSelected ();
	boolean isFocusItem = parent.focusItem == this && parent.isFocusControl ();
	boolean drawBackground = true;
	boolean drawForeground = true;
	boolean drawSelection = isSelected;
	boolean drawFocus = isFocusItem;
	if (parent.hooks (SWT.EraseItem)) {
		drawBackground = background != null || (cellBackgrounds != null && cellBackgrounds [columnIndex] != null);
		gc.setFont (getFont (columnIndex, false));
		if (isSelected && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) {
			gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT));
			gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION));
		} else {
			gc.setForeground (getForeground (columnIndex));
			gc.setBackground (getBackground (columnIndex));
		}
		Event event = new Event ();
		event.item = this;
		event.gc = gc;
		event.index = columnIndex;
		event.doit = true;
		event.detail = SWT.FOREGROUND;
		if (drawBackground) event.detail |= SWT.BACKGROUND;
		if (isSelected) event.detail |= SWT.SELECTED;
		if (isFocusItem) event.detail |= SWT.FOCUSED;
		event.x = cellBounds.x;
		event.y = cellBounds.y;
		event.width = cellBounds.width;
		event.height = cellBounds.height;
		gc.setClipping (cellBounds);
		parent.sendEvent (SWT.EraseItem, event);
		event.gc = null;
		if (gc.isDisposed ()) return false;
		gc.setAlpha (oldAlpha);
		gc.setAntialias (oldAntialias);
		gc.setBackgroundPattern (oldBackgroundPattern);
		gc.setClipping (cellBounds);
		gc.setForegroundPattern (oldForegroundPattern);
		gc.setInterpolation (oldInterpolation);
		gc.setLineDash (oldLineDash);
		gc.setLineWidth (oldLineWidth);
		gc.setTextAntialias (oldTextAntialias);
		gc.setAdvanced (oldAdvanced);
		if (isDisposed ()) return false;
		if (!event.doit) {
			drawBackground = drawForeground = drawSelection = drawFocus = false;
		} else {
			drawBackground = drawBackground && (event.detail & SWT.BACKGROUND) != 0;
			drawForeground = (event.detail & SWT.FOREGROUND) != 0;
			drawSelection = isSelected && (event.detail & SWT.SELECTED) != 0;
			drawFocus = isFocusItem && (event.detail & SWT.FOCUSED) != 0;
		}
	}

	/* draw the cell's set background if appropriate */
	if (drawBackground) {
		gc.setBackground (getBackground (columnIndex));
		if (columnIndex == 0 && (column == null || column.getOrderIndex () == 0)) {
			Rectangle focusBounds = getFocusBounds ();
			int fillWidth = 0;
			if (column == null) {
				fillWidth = focusBounds.width;
			} else {
				fillWidth = column.width - focusBounds.x;
				if (parent.linesVisible) fillWidth--;
			}
			gc.fillRectangle (focusBounds.x, focusBounds.y, fillWidth, focusBounds.height);
		} else {
			int fillWidth = cellBounds.width;
			gc.fillRectangle (cellBounds.x, cellBounds.y, fillWidth, cellBounds.height);
		}
	}

	/* draw the selection bar if the receiver is selected */
	if (drawSelection && isSelected && (orderedIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) {
		gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION));
		if (orderedIndex == 0) {
			Rectangle focusBounds = getFocusBounds ();
			int fillWidth = focusBounds.width;
			if (parent.columns.length < 2 || (parent.style & SWT.FULL_SELECTION) == 0) {
				fillWidth -= 2;	/* space for right bound of focus rect */
			}
			if (fillWidth > 0) {
				gc.fillRectangle (focusBounds.x + 1, focusBounds.y + 1, fillWidth, focusBounds.height - 2);
			}
		} else {
			int fillWidth = column.width;
			int[] columnOrder = parent.getColumnOrder ();
			if (columnIndex == columnOrder [columnOrder.length - 1]) {
				fillWidth -= 2;		/* space for right bound of focus rect */
			}
			if (fillWidth > 0) {
				gc.fillRectangle (
					column.getX (),
					cellBounds.y + 1,
					fillWidth,
					cellBounds.height - 2);
			}
		}
	}
		
	if (backgroundOnly) return false;

	/* Draw column 0 decorations */
	if (orderedIndex == 0) {
		gc.setClipping (cellBounds);

		/* Draw hierarchy connector lines */
		Rectangle expanderBounds = getExpanderBounds ();
		Color oldForeground = gc.getForeground ();
		gc.setForeground (parent.getConnectorColor ());

		/* Draw vertical line above expander */
		int lineX = expanderBounds.x + expanderBounds.width / 2;
		int y2 = expanderBounds.y;
		if (items.length == 0) {
			y2 += expanderBounds.height / 2;
		}
		/* Do not draw this line iff this is the very first item in the tree */ 
		if (parentItem != null || getIndex () != 0) {
			gc.drawLine (lineX, y, lineX, y2);
		}

		/* Draw vertical line below expander if the receiver has lower siblings */
		if (!isLastChild ()) {
			if (items.length != 0) y2 += expanderBounds.height;
			gc.drawLine (lineX, y2, lineX, y + itemHeight);
		}

		/* Draw horizontal line to right of expander */
		Point[] endpoints = getHconnectorEndpoints ();
		gc.drawLine (endpoints [0].x, endpoints [0].y, endpoints [1].x - Tree.MARGIN_IMAGE, endpoints [1].y);
		
		/* 
		 * Draw hierarchy lines that are needed by other items that are shown below
		 * this item but whose parents are shown above (ie.- lines to the left of
		 * this item's connector line).
		 */
		TreeItem item = parentItem;
		while (item != null) {
			if (!item.isLastChild ()) {
				Rectangle itemExpanderBounds = item.getExpanderBounds ();
				lineX = itemExpanderBounds.x + itemExpanderBounds.width / 2;
				gc.drawLine (lineX, y, lineX, y + itemHeight);
			}
			item = item.parentItem;
		}

		gc.setForeground (oldForeground);

		/* Draw expand/collapse image if receiver has children */
		if (items.length > 0) {
			Image image = expanded ? parent.getExpandedImage () : parent.getCollapsedImage ();
			gc.drawImage (image, expanderBounds.x, expanderBounds.y);
		}
		
		/* Draw checkbox if parent Tree has style SWT.CHECK */
		if ((parent.style & SWT.CHECK) != 0) {
			Image baseImage = grayed ? parent.getGrayUncheckedImage () : parent.getUncheckedImage ();
			Rectangle checkboxBounds = getCheckboxBounds ();
			gc.drawImage (baseImage, checkboxBounds.x, checkboxBounds.y);
			/* Draw checkmark if item is checked */
			if (checked) {
				Image checkmarkImage = parent.getCheckmarkImage ();
				Rectangle checkmarkBounds = checkmarkImage.getBounds ();
				int xInset = (checkboxBounds.width - checkmarkBounds.width) / 2;
				int yInset = (checkboxBounds.height - checkmarkBounds.height) / 2;
				gc.drawImage (checkmarkImage, checkboxBounds.x + xInset, checkboxBounds.y + yInset);
			}
		}
	}

	if (drawForeground) {
		Image image = getImage (columnIndex, false);
		String text = getDisplayText (columnIndex);
		Rectangle imageArea = getImageBounds (columnIndex);
		int startX = imageArea.x;
		
		/* while painting the cell's content restrict the clipping region */
		int padding = parent.getCellPadding ();
		gc.setClipping (
			startX,
			cellBounds.y + padding - (parent.linesVisible ? 1 : 0),
			cellRightX - startX - padding,
			cellBounds.height - 2 * (padding - (parent.linesVisible ? 1 : 0)));
	
		/* draw the image */
		if (image != null) {
			Rectangle imageBounds = image.getBounds ();
			gc.drawImage (
				image,
				0, 0,									/* source x, y */
				imageBounds.width, imageBounds.height,	/* source width, height */
				imageArea.x, imageArea.y,				/* dest x, y */
				imageArea.width, imageArea.height);		/* dest width, height */
		}
		
		/* draw the text */
		if (text.length () > 0) {
			gc.setFont (getFont (columnIndex, false));
			int fontHeight = getFontHeight (columnIndex);
			if (drawSelection && (orderedIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) {
				gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT));
			} else {
				if (!isSelected || drawSelection) {
					gc.setForeground (getForeground (columnIndex));
				}
			}
			x = getTextX (columnIndex) + MARGIN_TEXT;
			gc.drawString (text, x, y + (itemHeight - fontHeight) / 2, true);
		}
	}
	
	if (parent.hooks (SWT.PaintItem)) {
		int contentWidth = getContentWidth (columnIndex);
		int contentX = getContentX (columnIndex);
		gc.setFont (getFont (columnIndex, false));
		if (isSelected && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) {
			gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT));
			gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION));
		} else {
			gc.setForeground (getForeground (columnIndex));
			gc.setBackground (getBackground (columnIndex));
		}
		Event event = new Event ();
		event.item = this;
		event.gc = gc;
		event.index = columnIndex;
		if (isSelected) event.detail |= SWT.SELECTED;
		if (drawFocus) event.detail |= SWT.FOCUSED;
		event.x = contentX;
		event.y = cellBounds.y;
		event.width = contentWidth;
		event.height = cellBounds.height;
		gc.setClipping (cellBounds);
		parent.sendEvent (SWT.PaintItem, event);
		event.gc = null;
		if (gc.isDisposed ()) return false;
		gc.setAlpha (oldAlpha);
		gc.setAntialias (oldAntialias);
		gc.setBackgroundPattern (oldBackgroundPattern);
		gc.setClipping (cellBounds);
		gc.setForegroundPattern (oldForegroundPattern);
		gc.setInterpolation (oldInterpolation);
		gc.setLineDash (oldLineDash);
		gc.setLineWidth (oldLineWidth);
		gc.setTextAntialias (oldTextAntialias);
		gc.setAdvanced (oldAdvanced);
		drawFocus = isFocusItem && (event.detail & SWT.FOCUSED) != 0;
	}

	return isFocusItem && !drawFocus;
}
/*
 * Redraw part of the receiver.  If either EraseItem or PaintItem is hooked then
 * only full cells should be damaged, so adjust accordingly.  If neither of these
 * events are hooked then the exact bounds given for damaging can be used.
 */
void redraw (int x, int y, int width, int height, int columnIndex) {
	if (!parent.hooks (SWT.EraseItem) && !parent.hooks (SWT.PaintItem)) {
		parent.redraw (x, y, width, height, false);
		return;
	}
	Rectangle cellBounds = getCellBounds (columnIndex);
	parent.redraw (cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height, false);
}
void redrawItem () {
	if (!isAvailable ()) return;
	parent.redraw (0, parent.getItemY (this), parent.clientArea.width, parent.itemHeight, false);
}
/*
 * Updates internal structures in the receiver and its child items to handle the removal of a column.
 */
void removeColumn (TreeColumn column, int index, int orderedIndex) {
	int columnCount = parent.columns.length;

	if (columnCount == 0) {
		/* reverts to normal tree when last column disposed */
		cellBackgrounds = cellForegrounds = null;
		displayTexts = null;
		cellFonts = null;
		fontHeights = null;
		GC gc = new GC (parent);
		computeTextWidths (gc);
		gc.dispose ();
		/* notify all child items as well */
		for (int i = 0; i < items.length; i++) {
			items [i].removeColumn (column, index, orderedIndex);
		}
		return;
	}

	String[] newTexts = new String [columnCount];
	System.arraycopy (texts, 0, newTexts, 0, index);
	System.arraycopy (texts, index + 1, newTexts, index, columnCount - index);
	texts = newTexts;
	
	Image[] newImages = new Image [columnCount];
	System.arraycopy (images, 0, newImages, 0, index);
	System.arraycopy (images, index + 1, newImages, index, columnCount - index);
	images = newImages;

	int[] newTextWidths = new int [columnCount];
	System.arraycopy (textWidths, 0, newTextWidths, 0, index);
	System.arraycopy (textWidths, index + 1, newTextWidths, index, columnCount - index);
	textWidths = newTextWidths;

	String[] newDisplayTexts = new String [columnCount];
	System.arraycopy (displayTexts, 0, newDisplayTexts, 0, index);
	System.arraycopy (displayTexts, index + 1, newDisplayTexts, index, columnCount - index);
	displayTexts = newDisplayTexts;

	if (cellBackgrounds != null) {
		Color[] newCellBackgrounds = new Color [columnCount];
		System.arraycopy (cellBackgrounds, 0, newCellBackgrounds, 0, index);
		System.arraycopy (cellBackgrounds, index + 1, newCellBackgrounds, index, columnCount - index);
		cellBackgrounds = newCellBackgrounds;
	}
	if (cellForegrounds != null) {
		Color[] newCellForegrounds = new Color [columnCount];
		System.arraycopy (cellForegrounds, 0, newCellForegrounds, 0, index);
		System.arraycopy (cellForegrounds, index + 1, newCellForegrounds, index, columnCount - index);
		cellForegrounds = newCellForegrounds;
	}
	if (cellFonts != null) {
		Font[] newCellFonts = new Font [columnCount];
		System.arraycopy (cellFonts, 0, newCellFonts, 0, index);
		System.arraycopy (cellFonts, index + 1, newCellFonts, index, columnCount - index);
		cellFonts = newCellFonts;

		int[] newFontHeights = new int [columnCount];
		System.arraycopy (fontHeights, 0, newFontHeights, 0, index);
		System.arraycopy (fontHeights, index + 1, newFontHeights, index, columnCount - index);
		fontHeights = newFontHeights;
	}

	if (index == 0) {
		text = texts [0] != null ? texts [0] : "";	//$NON-NLS-1$
		texts [0] = null;
		image = images [0];
		images [0] = null;
	}

	if (orderedIndex == 0) {
		/* 
		 * The new first ordered column will not have as much width available to it as it did when
		 * it was the second ordered column since it now has to show hierarchy decorations as well,
		 * so recompute its displayText. 
		 */
		int firstColumnIndex = parent.getOrderedColumns () [0].getIndex ();
		GC gc = new GC (parent);
		gc.setFont (getFont (firstColumnIndex, false));
		computeDisplayText (firstColumnIndex, gc);
		gc.dispose ();
	}
	if (columnCount < 2) {
		texts = null;
		images = null;
	}

	/* notify all child items as well */
	for (int i = 0; i < items.length; i++) {
		items [i].removeColumn (column, index, orderedIndex);
	}
}
/**
 * Removes all of the items from the receiver.
 * <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>
 *
 * @since 3.1
 */
public void removeAll () {
	checkWidget ();
	if (items.length == 0) return;

	int lastAvailableIndex = parent.availableItemsCount - 1;
	/* for performance do this upfront for whole descendent chain */
	TreeItem focusItem = parent.focusItem; 
	if (focusItem != null && focusItem.hasAncestor (this)) {
		parent.setFocusItem (this, false);
	}
	while (items.length > 0) {
		items [0].dispose (true);
		removeItem (items [0], 0);
	}
	items = Tree.NO_ITEMS;
	expanded = false;
	if (isAvailable ()) {
		parent.redrawItems (availableIndex, lastAvailableIndex, false);
	}
}
/*
 * Removes a child item from the receiver.
 */
void removeItem (TreeItem item, int index) {
	if (isDisposed ()) return;
	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 (items.length == 0) {
		items = Tree.NO_ITEMS;
		/* condition below handles creation of item within Expand callback */
		if (!parent.inExpand) {
			expanded = false;
			if (isInViewport ()) {
				Rectangle bounds = getExpanderBounds ();	/* expander box no longer needed */
				parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false);
			}
		}
		return;
	}
}
/**
 * Sets the receiver's background color to the color specified
 * by the argument, or to the default system color for the item
 * if the argument is null.
 *
 * @param color the new color (or null)
 * 
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the argument 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 2.0
 * 
 */
public void setBackground (Color color) {
	checkWidget ();
	if (color != null && color.isDisposed ()) {
		SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	}
	Color oldColor = background;
	if (oldColor == color) return;
	background = color;
	if (oldColor != null && oldColor.equals (color)) return;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	redrawItem ();
}
/**
 * Sets the background color at the given column index in the receiver 
 * to the color specified by the argument, or to the default system color for the item
 * if the argument is null.
 *
 * @param index the column index
 * @param color the new color (or null)
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the argument 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 setBackground (int columnIndex, Color color) {
	checkWidget ();
	if (color != null && color.isDisposed ()) {
		SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	}
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return;
	if (cellBackgrounds == null) {
		if (color == null) return;
		cellBackgrounds = new Color [validColumnCount];
	}
	Color oldColor = cellBackgrounds [columnIndex];
	if (oldColor == color) return;
	cellBackgrounds [columnIndex] = color;
	if (oldColor != null && oldColor.equals (color)) return;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	if (isInViewport ()) {
		Rectangle bounds = getCellBounds (columnIndex);
		parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false);
	}
}
/**
 * Sets the checked state of the receiver.
 * <p>
 *
 * @param checked the new checked 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>
 */
public void setChecked (boolean value) {
	checkWidget ();
	if ((parent.getStyle () & SWT.CHECK) == 0) return;
	if (checked == value) return;
	checked = value;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	if (isInViewport ()) {
		if (parent.hooks (SWT.EraseItem) || parent.hooks (SWT.PaintItem)) {
			redrawItem ();
		} else {
			Rectangle bounds = getCheckboxBounds ();
			parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false);
		}
	}
}
/**
 * Sets the expanded state of the receiver.
 * <p>
 *
 * @param expanded the new expanded 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>
 */
public void setExpanded (boolean value) {
	checkWidget ();
	if (expanded == value) return;
	if (items.length == 0) return;
	if (parent.inExpand) return;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	if (value) {
		expanded = value;
		if (availableIndex == -1) return;

		TreeItem[] availableDescendents = computeAvailableDescendents ();
		int descendentsCount = availableDescendents.length;
		if (availableIndex != parent.availableItemsCount - 1) {
			/* the receiver is not the last available item */
			Rectangle clientArea = parent.clientArea;
			int y = parent.getItemY (this) + parent.itemHeight;
			if (0 < y && y < clientArea.height) {
				if (parent.drawCount <= 0) {
					parent.update ();
					GC gc = new GC (parent);
					gc.copyArea (
						0, y,
						clientArea.width, clientArea.height - y,
						0, y + ((descendentsCount - 1) * parent.itemHeight));				
					gc.dispose ();
				}
			}
		}

		parent.makeDescendentsAvailable (this, availableDescendents);

		/* update scrollbars */
		int rightX = 0;
		for (int i = 1; i < availableDescendents.length; i++) {
			Rectangle bounds = availableDescendents [i].getBounds (false);
			rightX = Math.max (rightX, bounds.x + bounds.width);
		}
		parent.updateHorizontalBar (rightX, rightX);
		parent.updateVerticalBar ();
		/* 
		 * If new item is above viewport then adjust topIndex and the vertical scrollbar
		 * so that the current viewport items will not change. 
		 */
		if (availableIndex < parent.topIndex) {
			parent.topIndex += descendentsCount - 1;
			ScrollBar vBar = parent.getVerticalBar ();
			if (vBar != null) vBar.setSelection (parent.topIndex);
			return;
		}

		int redrawStart = availableIndex + 1;
		int redrawEnd = redrawStart + descendentsCount - 2;
		parent.redrawItems (redrawStart, redrawEnd, false);
	} else {
		TreeItem[] descendents = computeAvailableDescendents ();
		expanded = value;
		if (availableIndex == -1) return;
		Rectangle clientArea = parent.clientArea;

		int y = parent.getItemY (this) + parent.itemHeight;
		int startY = y + (descendents.length - 1) * parent.itemHeight;
		if (y < clientArea.height && 0 < startY) {	/* determine whether any visual update is actually needed */
			if (parent.drawCount <= 0) {
				parent.update ();
				GC gc = new GC (parent);
				gc.copyArea (0, startY, clientArea.width, clientArea.height - startY, 0, y);
				gc.dispose ();
				int redrawY = y + Math.max (0, clientArea.height - startY);
				parent.redraw (0, redrawY, clientArea.width, clientArea.height - redrawY, false);
			}
		}

		parent.makeDescendentsUnavailable (this, descendents);

		/* 
		 * If all collapsed items are above the viewport then adjust topIndex and
		 * the vertical scrollbar so that the current viewport items will not change.
		 */
		int bottomIndex = availableIndex + descendents.length - 1;
		if (bottomIndex < parent.topIndex) {
			parent.topIndex = parent.topIndex - descendents.length + 1;
			ScrollBar vBar = parent.getVerticalBar ();
			if (vBar != null) vBar.setSelection (parent.topIndex);
		}
		
		parent.updateHorizontalBar ();
		parent.updateVerticalBar ();

		/* move focus (and selection if SWT.SINGLE) to item if a descendent had focus */
		TreeItem focusItem = parent.focusItem;
		if (focusItem != null && focusItem != this && focusItem.hasAncestor (this)) {
			parent.setFocusItem (this, false);
			if ((parent.style & SWT.SINGLE) != 0) {
				parent.selectItem (this, false);
			}
			/* Fire an event since the selection is being changed automatically */
			Event newEvent = new Event ();
			newEvent.item = this;
			parent.sendEvent (SWT.Selection, newEvent);
			if (isDisposed ()) return;
			parent.showItem (this);
			parent.redrawItem (availableIndex, true);
		}
	}
	/* redraw the receiver's expander box */
	if (isInViewport ()) {
		Rectangle bounds = getExpanderBounds ();
		parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false);
	}
}
/**
 * Sets the font that the receiver will use to paint textual information
 * for this item to the font specified by the argument, or to the default font
 * for that kind of control if the argument is null.
 *
 * @param font the new font (or null)
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the argument 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.0
 */
public void setFont (Font font) {
	checkWidget ();
	if (font != null && font.isDisposed ()) {
		SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	}
	Font oldFont = this.font;
	if (oldFont == font) return;
	this.font = font;
	if (oldFont != null && oldFont.equals (font)) return;
	
	Rectangle bounds = getBounds (false);
	int oldRightX = bounds.x + bounds.width;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;

	/* recompute cached values for string measurements */
	GC gc = new GC (parent);
	gc.setFont (getFont (false));
	fontHeight = gc.getFontMetrics ().getHeight ();
	computeDisplayTexts (gc);
	computeTextWidths (gc);
	gc.dispose ();
	
	/* horizontal bar could be affected if tree has no columns */
	if (parent.columns.length == 0) {
		bounds = getBounds (false);
		int newRightX = bounds.x + bounds.width;
		parent.updateHorizontalBar (newRightX, newRightX - oldRightX);
	}
	redrawItem ();
}
/**
 * Sets the font that the receiver will use to paint textual information
 * for the specified cell in this item to the font specified by the 
 * argument, or to the default font for that kind of control if the 
 * argument is null.
 *
 * @param index the column index
 * @param font the new font (or null)
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the argument 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 setFont (int columnIndex, Font font) {
	checkWidget ();
	if (font != null && font.isDisposed ()) {
		SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	}

	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return;
	if (cellFonts == null) {
		if (font == null) return;
		cellFonts = new Font [validColumnCount];
	}
	Font oldFont = cellFonts [columnIndex];
	if (oldFont == font) return;
	cellFonts [columnIndex] = font;
	if (oldFont != null && oldFont.equals (font)) return;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;

	/* recompute cached values for string measurements */
	GC gc = new GC (parent);
	gc.setFont (getFont (columnIndex, false));
	if (fontHeights == null) fontHeights = new int [validColumnCount];
	fontHeights [columnIndex] = gc.getFontMetrics ().getHeight ();
	computeDisplayText (columnIndex, gc);
	gc.dispose ();

	if (isInViewport ()) {
		Rectangle bounds = getCellBounds (columnIndex);
		parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false);
	}
}
/**
 * Sets the receiver's foreground color to the color specified
 * by the argument, or to the default system color for the item
 * if the argument is null.
 *
 * @param color the new color (or null)
 *
 * @since 2.0
 * 
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the argument 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 2.0
 * 
 */
public void setForeground (Color color) {
	checkWidget ();
	if (color != null && color.isDisposed ()) {
		SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	}
	Color oldColor = foreground;
	if (oldColor == color) return;
	foreground = color;
	if (oldColor != null && oldColor.equals (color)) return;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	redrawItem ();
}
/**
 * Sets the foreground color at the given column index in the receiver 
 * to the color specified by the argument, or to the default system color for the item
 * if the argument is null.
 *
 * @param index the column index
 * @param color the new color (or null)
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the argument 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 setForeground (int columnIndex, Color value) {
	checkWidget ();
	if (value != null && value.isDisposed ()) {
		SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	}
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return;
	if (cellForegrounds == null) {
		if (value == null) return;
		cellForegrounds = new Color [validColumnCount];
	}
	Color oldColor = cellForegrounds [columnIndex];
	if (oldColor == value) return;
	cellForegrounds [columnIndex] = value;
	if (oldColor != null && oldColor.equals (value)) return;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	if (isInViewport ()) {
		redraw (
			getTextX (columnIndex),
			parent.getItemY (this),
			textWidths [columnIndex] + 2 * MARGIN_TEXT,
			parent.itemHeight,
			columnIndex);
	}
}
/**
 * Sets the grayed state of the checkbox for this item.  This state change 
 * only applies if the Tree was created with the SWT.CHECK style.
 *
 * @param grayed the new grayed state of the checkbox
 *
 * @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 setGrayed (boolean value) {
	checkWidget ();
	if ((parent.getStyle () & SWT.CHECK) == 0) return;
	if (grayed == value) return;
	grayed = value;
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	if (isInViewport ()) {
		Rectangle bounds = getCheckboxBounds ();
		parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false);
	}
}
public void setImage (Image value) {
	checkWidget ();
	setImage (0, value);
}
/**
 * Sets the image for multiple columns in the tree. 
 * 
 * @param images the array of new images
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the array of images is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if one of the images 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 setImage (Image[] value) {
	checkWidget ();
	if (value == null) error (SWT.ERROR_NULL_ARGUMENT);
	
	// TODO make a smarter implementation of this
	for (int i = 0; i < value.length; i++) {
		if (value [i] != null) setImage (i, value [i]);
	}
}
/**
 * Sets the receiver's image at a column.
 *
 * @param index the column index
 * @param image the new image
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the image 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 setImage (int columnIndex, Image value) {
	checkWidget ();
	if (value != null && value.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);

	TreeColumn[] columns = parent.columns;
	int validColumnCount = Math.max (1, columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return;
	Image image = getImage (columnIndex, false);
	if (value == image) return;
	if (value != null && value.equals (image)) return;
	if (columnIndex == 0) {
		super.setImage (value);
	} else {
		images [columnIndex] = value;
	}
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;

	/* 
	 * An image width change may affect the space available for the item text, so
	 * recompute the displayText if there are columns.
	 */
	if (columns.length > 0) {
		GC gc = new GC (parent);
		gc.setFont (getFont (columnIndex, false));
		computeDisplayText (columnIndex, gc);
		gc.dispose ();
	}

	if (value == null) {
		redrawItem ();	// TODO why the whole item?
		return;
	}

	if (columns.length == 0) {
		if (parent.imageHeight == 0) {
			/* this is the first image being put into the parent Tree */
			Rectangle bounds = value.getBounds ();
			parent.orderedCol0imageWidth = bounds.width;
			parent.setImageHeight (bounds.height);
			parent.redrawItems (0, parent.availableItemsCount - 1, false);
		} else {
			redrawItem ();
		}
		return;
	}

	/* there are 1+ columns */
	TreeColumn column = columns [columnIndex];
	int orderedIndex = column.getOrderIndex ();
	Rectangle bounds = value.getBounds ();
	if (column.itemImageWidth == 0) column.itemImageWidth = bounds.width;

	if (parent.imageHeight == 0) {
		/* this is the first image being put into the parent Tree */
		int oldItemHeight = parent.itemHeight;
		parent.setImageHeight (bounds.height);

		if (orderedIndex == 0) {	/* the first ordered column */
			parent.orderedCol0imageWidth = bounds.width;
			/* 
			 * All column 0 cells will now have less room available for their texts,
			 * so all items must now recompute their column 0 displayTexts.
			 */
			TreeItem[] rootItems = parent.items;
			GC gc = new GC (parent);
			for (int i = 0; i < rootItems.length; i++) {
				rootItems [i].updateColumnWidth (column, gc);
			}
			gc.dispose ();
			if (oldItemHeight != parent.itemHeight) {
				/* the item height grew as a result of the new image height, so redraw everything */
				parent.redraw ();
			} else {
				/* redraw the column since all items should now have image space */
				parent.redraw (column.getX (), 0, column.width, parent.clientArea.height, false);
			}
		} else {	/* not the first ordered column */
			if (oldItemHeight != parent.itemHeight) {
				/* the item height grew as a result of the new image height, so redraw everything */
				parent.redraw ();
			} else {
				redrawItem ();
			}
		}
		return;
	}

	if (orderedIndex == 0 && parent.orderedCol0imageWidth == 0) {
		/* this is the first image being put into the current ordered column 0 */
		parent.orderedCol0imageWidth = bounds.width;
		/* 
		 * All column 0 cells will now have less room available for their texts,
		 * so all items must now recompute their column 0 displayTexts.
		 */
		TreeItem[] rootItems = parent.items;
		GC gc = new GC (parent);
		for (int i = 0; i < rootItems.length; i++) {
			rootItems [i].updateColumnWidth (column, gc);
		}
		gc.dispose ();
		parent.redraw (column.getX (), 0, column.width, parent.clientArea.height, false);
		return;
	}

	redrawItem ();	// TODO why the whole item?
}
/**
 * Sets the number of child 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 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 : availableIndex;
		redrawEnd = parent.availableItemsCount - 1;
		for (int i = count; i < items.length; i++) {
			items [i].dispose (true);
		}
		if (count == 0) {
			items = Tree.NO_ITEMS;
		} else {
			TreeItem[] newItems = new TreeItem [count];
			System.arraycopy (items, 0, newItems, 0, count);
			items = newItems;
		}
		if (count == 0) expanded = false;
	} else {
		int oldAvailableDescendentCount = computeAvailableDescendentCount ();
		int grow = count - items.length;
		redrawStart = items.length == 0 ? availableIndex : items [items.length - 1].availableIndex;
		redrawEnd = expanded && isAvailable () ? parent.availableItemsCount + grow  - 1: redrawStart;
		TreeItem[] newItems = new TreeItem [count];
		System.arraycopy (items, 0, newItems, 0, items.length);
		items = newItems;
		for (int i = items.length - grow; i < count; i++) {
			items [i] = new TreeItem (this, SWT.NONE, i, false);
		}
		
		if (expanded && availableIndex != -1) {
			/* expand the availableItems array if necessary */
			if (parent.availableItems.length < parent.availableItemsCount + grow) {
				TreeItem[] newAvailableItems = new TreeItem [parent.availableItemsCount + grow];
				System.arraycopy (parent.availableItems, 0, newAvailableItems, 0, parent.availableItemsCount);
				parent.availableItems = newAvailableItems;
			}
			TreeItem[] availableItems = parent.availableItems;
			/* shift items right to create space for the new available items */
			int dest = availableIndex + oldAvailableDescendentCount + grow;
			System.arraycopy (
				availableItems,
				availableIndex + oldAvailableDescendentCount,
				availableItems,
				dest,
				availableItems.length - dest);
			parent.availableItemsCount += grow;
			/* copy new items in */
			System.arraycopy (
				items,
				items.length - grow,
				availableItems,
				availableIndex + oldAvailableDescendentCount,
				grow);
			/* update availableIndex for all affected items */
			for (int i = availableIndex + oldAvailableDescendentCount; i < parent.availableItemsCount; i++) {
				availableItems [i].availableIndex = i;
			}
		}
	}

	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;
	if (availableIndex != -1) {
		if (expanded) parent.updateVerticalBar ();
		parent.redrawItems (redrawStart, redrawEnd, false);
	}
}
public void setText (String value) {
	checkWidget ();
	Rectangle bounds = getBounds (false);
	int oldRightX = bounds.x + bounds.width;
	setText (0, value);
	/* horizontal bar could be affected if tree has no columns */
	if (parent.columns.length == 0) {
		bounds = getBounds (false);
		int newRightX = bounds.x + bounds.width;
		parent.updateHorizontalBar (newRightX, newRightX - oldRightX);
	}
}
/**
 * Sets the text for multiple columns in the tree. 
 * 
 * @param strings the array of new strings
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the text 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 void setText (String[] value) {
	checkWidget ();
	if (value == null) error (SWT.ERROR_NULL_ARGUMENT);
	Rectangle bounds = getBounds (false);
	int oldRightX = bounds.x + bounds.width;
	// TODO make a smarter implementation of this
	for (int i = 0; i < value.length; i++) {
		if (value [i] != null) setText (i, value [i]);
	}
	/* horizontal bar could be affected if tree has no columns */
	if (parent.columns.length == 0) {
		bounds = getBounds (false);
		int newRightX = bounds.x + bounds.width;
		parent.updateHorizontalBar (newRightX, newRightX - oldRightX);
	}
}
/**
 * Sets the receiver's text at a column
 *
 * @param index the column index
 * @param string the new text
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the text 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 void setText (int columnIndex, String value) {
	checkWidget ();
	if (value == null) error (SWT.ERROR_NULL_ARGUMENT);
	int validColumnCount = Math.max (1, parent.columns.length);
	if (!(0 <= columnIndex && columnIndex < validColumnCount)) return;
	if (value.equals (getText (columnIndex, false))) return;
	if (columnIndex == 0) {
		super.setText (value);
	} else {
		texts [columnIndex] = value;		
	}
	if ((parent.style & SWT.VIRTUAL) != 0) cached = true;

	int oldWidth = textWidths [columnIndex];
	GC gc = new GC (parent);
	gc.setFont (getFont (columnIndex, false));
	computeDisplayText (columnIndex, gc);
	gc.dispose ();
	if (availableIndex == -1) return;
	if (parent.columns.length == 0) {
		Rectangle bounds = getBounds (false);
		int rightX = bounds.x + bounds.width;
		parent.updateHorizontalBar (rightX, textWidths [columnIndex] - oldWidth);
	}
	if (isInViewport ()) {
		redraw (
			getTextX (columnIndex),
			parent.getItemY (this),
			Math.max (oldWidth, textWidths [columnIndex]) + 2 * MARGIN_TEXT,
			parent.itemHeight,
			columnIndex);
	}
}
/*
 * Perform any internal changes necessary to reflect a changed column width.
 */
void updateColumnWidth (TreeColumn column, GC gc) {
	int columnIndex = column.getIndex ();
	gc.setFont (getFont (columnIndex, false));
	String oldDisplayText = displayTexts [columnIndex];
	computeDisplayText (columnIndex, gc);

	/* the cell must be damaged if there is custom drawing being done or if the alignment is not LEFT */
	if (isInViewport ()) {
		boolean columnIsLeft = (column.style & SWT.LEFT) != 0;
		if (!columnIsLeft || parent.hooks (SWT.EraseItem) || parent.hooks (SWT.PaintItem)) {
			Rectangle cellBounds = getCellBounds (columnIndex);
			parent.redraw (cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height, false);
		} else {
			/* if the display text has changed then the cell text must be damaged in order to repaint */	
			if (oldDisplayText == null || !oldDisplayText.equals (displayTexts [columnIndex])) {
				Rectangle cellBounds = getCellBounds (columnIndex);
				int textX = getTextX (columnIndex);
				parent.redraw (textX, cellBounds.y, cellBounds.x + cellBounds.width - textX, cellBounds.height, false);
			}
		}
	}

	for (int i = 0; i < items.length; i++) {
		items [i].updateColumnWidth (column, gc);
	}
}

/*
 * The parent's font has changed, so if this font was being used by the receiver then
 * recompute its cached text sizes using the gc argument.  Pass this notification on to
 * all child items as well.
 */
void updateFont (GC gc) {
	if (font == null) {		/* receiver is using the Tree's font */
		computeDisplayTexts (gc);
		computeTextWidths (gc);
	}
	/* pass notification on to all children */
	for (int i = 0; i < items.length; i++) {
		items [i].updateFont (gc);
	}
}
}
