/****************************************************************************
* Copyright (c) 2005-2006 Jeremy Dowdall
*
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * SPDX-License-Identifier: EPL-2.0
*
* Contributors:
*    Jeremy Dowdall <[email protected]> - initial API and implementation
*****************************************************************************/

package org.eclipse.nebula.widgets.ctree;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;


/**
 * <p>
 * NOTE:  THIS WIDGET AND ITS API ARE STILL UNDER DEVELOPMENT.  THIS IS A PRE-RELEASE ALPHA 
 * VERSION.  USERS SHOULD EXPECT API CHANGES IN FUTURE VERSIONS.
 * </p> 
 */
public class CTreeItem extends Item {


	/**
	 * The container to which this item belongs.
	 */
	protected CTree ctree;
	/**
	 * The cells which belong to, or are contained by, this item.
	 */
	protected CTreeCell[] cells;
	/**
	 * Whether or not this item is enabled.
	 */
	protected boolean enabled = true;
	/**
	 * Whether or not this item is visible.
	 */
	protected boolean visible = true;
	/**
	 * Whether or not this item is actually painted to the screen.
	 */
	boolean painted = false;
	
	private int top = -1;
	private int height = -1;
	
	private int checkCell;
	private int treeCell;
	private CTreeItem parentItem;
	private List items = new ArrayList();
	private boolean autoHeight;

	private CTreeItem next;
	private CTreeItem previous;
	int computedHeight = -1;
	
	private CTreeItem(CTree parent, CTreeItem parentItem, int style, int index) {
		super(parent, style);
		this.ctree = parent;
		this.parentItem = parentItem;
		this.cells = new CTreeCell[parent.getColumnCount()];
		createCells(parent.cellClasses);
		this.treeCell = parent.getTreeColumn();
		if(parentItem != null) {
			this.checkCell = parent.getCheckColumn();
			this.parentItem = (CTreeItem) parentItem;
			((CTreeItem) parentItem).addItem(index, this);
		} else {
			this.checkCell = parent.getCheckRoots() ? parent.getCheckColumn() : -1;
			parent.addItem(index, this);
		}
	}
	
	public CTreeItem(CTree parent, int style) {
		this(parent, null, style, -1);
	}

	public CTreeItem(CTree parent, int style, int index) {
		this(parent, null, style, index);
	}
	
	public CTreeItem(CTreeItem parent, int style) {
		this(parent.ctree, parent, style, -1);
	}
	
	public CTreeItem(CTreeItem parent, int style, int index) {
		this(parent.ctree, parent, style, index);
	}

	void addItem(int index, CTreeItem item) {
		if(index < 0 || index > items.size()-1) {
			items.add(item);
		} else {
			items.add(index, item);
		}
		ctree.addedItems.add(item);
		redraw();
	}

	public void addListener(int eventType, Listener handler) {
		for(int i = 0; i < cells.length; i++) {
			cells[i].addListener(eventType, handler);
		}
	}
	
	protected void checkSubclass() {
	}
	
	/**
	 * Computes the size of each cell using the widthHint and heightHint with the same
	 * index as the cell.
	 * @return int the computed height
	 */
	public int computeHeight() {
		int[] widths = ctree.getColumnWidths();
		computedHeight = cells[0].computeSize(widths[0], -1).y;
		for(int i = 1; i < cells.length; i++) {
			computedHeight = Math.max(computedHeight, cells[i].computeSize(widths[i], -1).y);
		}
		return computedHeight;
	}
	
	boolean contains(Control control) {
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].contains(control)) return true;
		}
		return false;
	}
	
	/**
	 * The Cells of a CTreeItem are considered contiguous and unified, therefore
	 * the contains method is overridden
	 */
	public boolean contains(Point pt) {
		return contains(pt.x,pt.y);
	}
	
	/**
	 * The Cells of a CTreeItem are considered contiguous and unified, therefore
	 * the contains method is overridden
	 */
	public boolean contains(int x, int y) {
		Rectangle[] ba = getCellBounds();
		for(int i = 0; i < ba.length; i++) {
			if(ba[i].contains(x,y)) return true;
		}
		return false;
	}

	/**
	 * Creates a cell of the default class, as determined by the implementation
	 * <p>Is used to auto-fill a cell when no specific cell class is provided for the 
	 * given column</p>
	 * @param index the index the new cell
	 * @return the new cell
	 */
	protected void createCell(int index, int style) {
		if(hasCell(index)) {
			cells[index] = new CTreeCell(this, style);
		}
	}
	
	
	
	public void createCell(int index, int style, Class clazz) {
		if(!hasCell(index)) return;
		
		Map memento = null;
		if(cells[index] != null) memento = cells[index].saveState();
		if(clazz != null) {
			boolean failed = true;
			try {
				// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4301875
				Constructor[] constructors = clazz.getConstructors();
				for(int j = 0; j < constructors.length; j++) {
					Class[] params = constructors[j].getParameterTypes();
					if(params.length == 2 && 
							params[0].isInstance(this) && 
							params[1].equals(int.class)) {
						cells[index] = (CTreeCell) constructors[j].newInstance(
								new Object[] { this, new Integer(style) } );
						failed = false;
						break;
					}
				}
			} catch (Exception e) {
			}
			if(failed) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
		} else {
			createCell(index, style);
		}
		if(memento != null) cells[index].restoreState(memento);
	}

	protected void createCells(Object parent) {
		for(int i = 0; i < cells.length; i++) {
			if(ctree.cellClasses != null && i < ctree.cellClasses.length) {
				createCell(i, getCellStyle(i), ctree.cellClasses[i]);
			} else {
				createCell(i, getCellStyle(i));
			}
		}
	}
	
	public void dispose() {
		if((parentItem != null) && (!parentItem.isDisposed())) parentItem.removeItem(this);
		
		List l = new ArrayList(items);
		for(Iterator i = l.iterator(); i.hasNext(); ) {
			((CTreeItem) i.next()).dispose();
		}
		// TODO dispose listener
		for(int i = 0; i < cells.length; i++) {
			cells[i].dispose();
		}
		ctree.removeItem(this);
		super.dispose();
	}

	public boolean getAutoHeight() {
		return autoHeight;
	}
	
	public Color getBackground() {
		return cells[0].getBackground();
	}
	
	public Color getBackground(int index) {
		if(hasCell(index)) return cells[index].getBackground();
		return null;
	}
	
//	private CTreeCell[] createCells(Class[] cellClasses) {
//		final int cellStyle = getStyle();
//
//		if(!container.autoFillCells && (cellClasses == null || cellClasses.length == 0)) {
//			return new CTreeCell[] { createCell(0, cellStyle) };
//		} else {
//			if(cellClasses == null) cellClasses = new Class[0]; 
//			CTreeCell[] ca = new CTreeCell[
//	                                         container.autoFillCells ? 
//	                                        		 container.getColumnCount() :
//	                                        			 cellClasses.length];
//			for(int i = 0; i < ca.length; i++) {
//				CTreeCell cell = null;
//				if(i < cellClasses.length && cellClasses[i] != null) {
//					try {
//						// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4301875
//						Constructor[] constructors = cellClasses[i].getConstructors();
//						for(int j = 0; j < constructors.length; j++) {
//							Class[] params = constructors[j].getParameterTypes();
//							if(params.length == 2 && 
//									params[0].isInstance(this) && 
//									params[1].equals(int.class)) {
//								cell = (CTreeCell) constructors[j].newInstance(
//										new Object[] { this, new Integer(cellStyle) } );
//								break;
//							}
//						}
//					} catch (Exception e) {
//						e.printStackTrace();
//					}
//				}
//				ca[i] = (cell != null) ? cell : createCell(i, cellStyle);
//			}
//			return ca;
//		}
//	}
	
//	/**
//	 * Provides a chance for subclasses to initialize themselves before the cells are created and the item
//	 * is added to its parent Container.
//	 * @param params the parameters
//	 */
//	protected abstract void initialize(Object[] params);
	
	int getBottom() {
		return top+height;
	}

	public Rectangle getBounds() {
		return new Rectangle(0,top,ctree.getClientArea().width,height);
	}
	
	public Rectangle getBounds(int index) {
		if(hasCell(index)) {
			CTreeColumn column = ctree.getColumn(index);
			return new Rectangle(column.getLeft(),top,column.getWidth(),height);
		}
		return null;
	}
	
	public CTreeCell getCell(int cell) {
		if(cell >= 0 && cell < cells.length) {
			return cells[cell];
		}
		return null;
	}
	
	public CTreeCell getCell(Point pt) {
		return getCell(pt.x,pt.y);
	}
	
	public CTreeCell getCell(int x, int y) {
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].getBounds().contains(x,y)) {
				return cells[i];
			}
		}
		return null;
	}

	Rectangle[] getCellBounds() {
		Rectangle[] bounds = new Rectangle[cells.length];
		for(int i = 0; i < bounds.length; i++) {
			bounds[i] = cells[i].getBounds();
		}
		return bounds;
	}
	
	public CTreeColumn getCellColumn(CTreeCell cell) {
		return ctree.getColumn(getCellIndex(cell));
	}
	
	public int getCellIndex(CTreeCell cell) {
		return Arrays.asList(cells).indexOf(cell);
	}
	
	public CTreeCell[] getCells() {
		return cells;
	}
	
	Point[] getCellSizes() {
		Point[] sa = new Point[cells.length];
		for(int i = 0; i < sa.length; i++) {
			sa[i] = cells[i].getSize();
		}
		return sa;
	}

	protected int getCellStyle(int index) {
		if(hasCell(index)) {
			int style = ctree.getColumn(index).getStyle();
			return style;
		}
		return 0;
	}
	
	public CTreeCell getCheckCell() {
		return hasCheckCell() ? (CTreeCell) cells[checkCell] : null;
	}
	
	public CTree getContainer() {
		return ctree;
	}

	public CTree getCTree() {
		return (CTree) ctree;
	}

//	public Composite getChildArea(int column) {
//		if((column >= 0) && (column < cells.length)) {
//			return cells[column].getChildArea();
//		}
//		return null;
//	}

//	protected List getColorManagedControls() {
//		List list = new ArrayList();
//		for(int i = 0; i < cells.length; i++) {
//			list.addAll(cells[i].getColorManagedControls());
//		}
//		return list;
//	}

	public CTreeCell getCTreeCell(int column) {
		return (CTreeCell) getCell(column);
	}
	
//	protected List getEventManagedControls() {
//		if(enabled) {
//			List list = new ArrayList();
//			for(int i = 0; i < cells.length; i++) {
//				list.addAll(cells[i].getEventManagedControls());
//			}
//			return list;
//		} else {
//			return Collections.EMPTY_LIST;
//		}
//	}

	/**
	 * Returns the Tree Cell expansion state
	 * <p>If there is no Tree Cell, simply returns false</p>
	 * @return
	 * @see org.aspencloud.widgets.ccontainer#getExpanded(boolean)
	 */
	public boolean getExpanded() {
		return (hasTreeCell()) ? getTreeCell().isOpen() : false;
	}
	
	/**
	 * Get the font being used by the first cell
	 * @return Font
	 */
	public Font getFont() {
		return cells[0].getFont();
	}
	
//	public int getHeight() {
//		int height = cells[0].getBounds().height;
//		for(int i = 1; i < cells.length; i++) {
//			height = Math.max(height, cells[i].getBounds().height);
//		}
//		return height;
//	}
	
//	public Point[] getLocation() {
//		Point[] la = new Point[cells.length];
//		for(int i = 0; i < la.length; i++) {
//			la[i] = cells[i].getLocation();
//		}
//		return la;
//	}

	/**
	 * Get the font being used by the specified cell
	 * @param index an int used to specify the cell by an index
	 * @return Font
	 */
	public Font getFont(int index) {
		if(hasCell(index)) return cells[index].getFont();
		return null;
	}
	
//	public Composite getTitleArea(int column) {
//		if((column >= 0) && (column < cells.length)) {
//			return cells[column].getChildArea();
//		}
//		return null;
//	}

	public Color getForeground() {
		return cells[0].getForeground();
	}
	
//	ItemIterator iterator() {
//		return new ItemIterator(this);
//	}

	public Color getForeground(int index) {
		if(hasCell(index)) return cells[index].getForeground();
		return null;
	}
	
	int getHeight() {
		return height;
	}
	
	/**
	 * If this item has a tree column, this method will return the first image from that column
	 * @return the image from the tree column, null if neither exist
	 */
	public Image getImage() {
		if(hasTreeCell()) {
			return getTreeCell().getImage();
		}
		return null;
	}
	
	/**
	 * @param column the column from which to get the image
	 * @return the first image from the given column
	 */
	public Image getImage(int column) {
		if(column >= 0 && column < cells.length) {
			return ((CTreeCell) cells[column]).getImage();
		}
		return null;
	}
	
	/**
	 * @param column the column from which to get the images
	 * @return the images from the given column
	 */
	public Image[] getImages(int column) {
		if(column >= 0 && column < cells.length) {
			return ((CTreeCell) cells[column]).getImages();
		}
		return new Image[0];
	}
	
	CTreeItem getItem(boolean up) {
		if(hasParentItem()) {
			CTreeItem parent = getParentItem();
			int ix = parent.indexOf(this);
			if(up) {
				if(ix == 0) return parent;
				return parent.getItem(ix - 1);
			} else {
				if(ix > parent.getItemCount() - 1) return parent.getItem(false);
				return parent.getItem(ix + 1);
			}
		} else {
			CTree parent = getParent();
			int ix = parent.indexOf(this);
			if(up) {
				if(ix == 0) return null;
				return parent.getItem(ix - 1);
			} else {
				if(ix > parent.getItemCount() - 1) return null;
				return parent.getItem(ix + 1);
			}
		}
	}
	
	public CTreeItem getItem(int index) {
		if((index >= 0) && (index < items.size())) {
			return (CTreeItem) items.get(index);
		}
		return null;
	}
	
//	private int[] order;
//	private void updateCellOrder() {
//		int[] newOrder = container.getColumnOrder();
//		if(!Arrays.equals(order, newOrder)) {
//			order = newOrder;
//			for(int i = 0; i < order.length; i++) {
//				cells[order[i]].bounds.x = container.internalGetColumn(i).getLeft();
//			}
//		}
//	}
	
	public int getItemCount() {
		return items.size();
	}
	
	public CTreeItem[] getItems() {
		if(items == null) return new CTreeItem[0];
		return (CTreeItem[]) items.toArray(new CTreeItem[items.size()]);
	}

	public CTree getParent() {
		return getCTree();
	}
	
	public int getParentIndent() {
		return hasParentItem() ? parentItem.getTreeIndent() : 0;
	}
	
	public CTreeItem getParentItem() {
		return parentItem;
	}

	public Point getSize() {
		return new Point(ctree.getClientArea().width, height);
	}
	
	/**
	 * If this item has a tree column, this method will return the first image from that column
	 * @return the image from the tree column, null if neither exist
	 */
	public String getText() {
		if(hasTreeCell()) {
			return getTreeCell().getText();
		}
		return null;
	}
	
	/**
	 * @param column the column from which to get the image
	 * @return the first image from the given column
	 */
	public String getText(int column) {
		if(column >= 0 && column < cells.length) {
			return ((CTreeCell) cells[column]).getText();
		}
		return null;
	}
	
	int getTop() {
		return top;
	}
	
	public CTreeCell getTreeCell() {
		return hasTreeCell() ? (CTreeCell) cells[treeCell] : null;
	}
	
//	public void setLocation(int cell, Point location) {
//		if(cell >= 0 && cell < cells.length) {
//			cells[cell].setLocation(location);
//		}
//	}

//	public void setLocation(Point location) {
//		cells[0].setLocation(location);
//	}

//	public void setLocation(Point[] location) {
//		for(int i = 0; i < cells.length; i++) {
//			cells[i].setLocation(location[i]);
//		}
//	}
	
	public int getTreeIndent() {
		if(hasTreeCell()) {
			return getTreeCell().getIndent();
		}
		return 0;
	}

//	public Rectangle getTreeToggleBounds() {
//		if(hasTreeCell() && getTreeCell().getToggleVisible()) {
//			return getTreeCell().getToggleBounds();
//		}
//		return null;
//	}

	/**
	 * Returns whether or not this CContainerItem is requesting to be visible.
	 * In other words, if its internal visibility flag is set to true.
	 * There may exist other conditions which make this item actually not visible within
	 * its container.
	 * @return true if internally considered visible, false otherwise
	 * @see Control#getVisible()
	 * @see CTreeItem#isVisible()
	 */
	public boolean getVisible() {
		return visible;
	}

//	protected abstract int getFirstPaintedCellIndex();
//	protected abstract int getLastPaintedCellIndex();

	/**
	 * Give the Item a chance to handle the mouse event
	 * @param event the Event
	 * @return 0: do nothing, 1: redraw, 2: layout
	 */
	public int handleMouseEvent(Event event, boolean selectionActive) {
		int result = 0;
//		for(int i = 0; i < cells.length; i++) {
//			result |= cells[i].handleMouseEvent(event, selectionActive);
//		}
		return result;
	}
	
	boolean hasCell(int index) {
		return (index >= 0 && index < cells.length);
	}

	public boolean hasCheckCell() {
		return (checkCell >= 0 && checkCell < cells.length);
	}
	
	public boolean hasItems() {
		return !items.isEmpty();
	}
	
	boolean hasNext() { return next != null; }
	
	public boolean hasParentItem() {
		return parentItem != null;
	}
	boolean hasPrevious() { return previous != null; }

//	void addItem(CTreeItem item) {
//		addItem(-1, item);
//	}

	public boolean hasTreeCell() {
		return (treeCell >= 0 && treeCell < cells.length);
	}
	public int indexOf(CTreeItem item) {
		return items.indexOf(item);
	}

	/**
	 * This method returns true if ANY of the cells are open.
	 * @return
	 * @see org.aspencloud.widgets.ccontainer#getExpanded()
	 */
	public boolean isOpen() {
		boolean ex = false;
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].isOpen()) {
				ex = true;
				break;
			}
		}
		return ex;
	}
	
	public boolean isOpen(int cell) {
		if(cell >= 0 && cell < cells.length) {
			return cells[cell].isOpen();
		}
		return false;
	}
	
	public boolean isOpen(int x, int y) {
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].getBounds().contains(x,y)) return cells[i].isOpen();
		}
		return false;
	}

	public boolean isSelected() {
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].isSelected()) return true;
		}
		return false;
	}
	
	public boolean isToggle(int x, int y) {
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].isToggle(x, y)) return true;
		}
		return false;
	}
	
	public boolean isTreeToggle(int x, int y) {
		if(hasTreeCell()) {
			return getTreeCell().isToggle(x, y);
		}
		return false;
	}
	
	/**
	 * Returns true if the receiver is visible and all parents up to and including the 
	 * root of its container are visible. Otherwise, false is returned.
	 * @return true if visible, false otherwise
	 * @see CTreeItem#getVisible()
	 * @see Control#isVisible()
	 */
	public boolean isVisible() {
		return ((CTree) ctree).isVisible(this);
	}
	
	CTreeItem next() { return next; }

	CTreeItem nextVisible() {
		for(CTreeItem i = this; i.hasNext(); ) {
			CTreeItem item = i.next();
			if(item.getVisible()) return item;
		}
		return null;
	}

//	protected int getFirstPaintedCellIndex() {
//		return container.getFirstPaintedColumnIndex();
//	}
	
	public void paint(GC gc, Rectangle ebounds) {
//		updateCellOrder();
		updatePaintedCells();
		for(int i = 0; i < cells.length; i++) {
			cells[i].paint(gc, ebounds);
		}
	}
	
	CTreeItem previous() { return previous; }

//	int getIndex() {
//		if(hasParentItem()) return getParentItem().indexOf(this);
//		return getParent().indexOf(this);
//	}
	
	CTreeItem previousVisible() {
		for(CTreeItem i = this; i.hasPrevious(); ) {
			CTreeItem item = i.previous();
			if(item.getVisible()) return item;
		}
		return null;
	}
	
	public void redraw() {
		if(painted) ctree.redraw(this);
	}
	
	void removeItem(CTreeItem item) {
		items.remove(item);
		if(!ctree.removedItems.contains(item)) {
			ctree.removedItems.add(item);
			boolean selChange = ctree.selection.remove(item);
			if(selChange) ctree.fireSelectionEvent(false);
			redraw();
		}
		if(items.isEmpty() && hasTreeCell()) {
			getTreeCell().setToggleVisible(false);
		}
	}

	public void removeListener(int eventType, Listener handler) {
		for(int i = 0; i < cells.length; i++) {
			cells[i].removeListener(eventType, handler);
		}
	}
	
	public void setBackground(Color color) {
		for(int i = 0; i < cells.length; i++) {
			cells[i].setBackground(color);
		}
	}

//	protected int getLastPaintedCellIndex() {
//		return container.getLastPaintedColumnIndex();
//	}
	
	public void setBackground(int index, Color color) {
		if(hasCell(index)) cells[index].setBackground(color);
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	/**
	 * Sets the Tree Expansion state of the Item
	 * <p>Does not affect the expansion state of individual expandable cells</p>
	 * @param expanded
	 */
	public void setExpanded(boolean expanded) {
		if(hasTreeCell() && getTreeCell().isOpen() != expanded) {
			getTreeCell().setOpen(expanded);
			layout(expanded ? SWT.Expand : SWT.Collapse);
		}
	}
	
	public void layout(int eventType) {
		ctree.layout(eventType, this);
	}
	
	public boolean setFocus() {
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].setFocus()) return true;
		}
		return false;
	}

	public void setFont(Font font) {
		for(int i = 0; i < cells.length; i++) {
			cells[i].setFont(font);
		}
	}
	
	public void setFont(int index, Font font) {
		if(hasCell(index)) cells[index].setFont(font);
	}
	
	public void setForeground(Color color) {
		for(int i = 0; i < cells.length; i++) {
			cells[i].setForeground(color);
		}
	}
	
	public void setForeground(int index, Color color) {
		if(hasCell(index)) cells[index].setForeground(color);
	}

	public void setGridLine(boolean gridLine) {
		for(int i = 0; i < cells.length; i++) {
			((CTreeCell) cells[i]).setGridLine(gridLine);
		}
	}
	
	void setHeight(int height) {
		this.height = height;
		redraw();
	}
	
	public void setImage(Image image) {
		if(hasTreeCell()) {
			getTreeCell().setImage(image);
		}
	}

	public void setImage(int column, Image image) {
		if((column >= 0) && (column < cells.length)) {
			((CTreeCell) cells[column]).setImage(image);
		}
	}
	
	public void setImages(Image[] images) {
		CTreeCell[] cells = getCells();
		if(cells.length >= images.length) {
			for(int i = 0; i < images.length; i++) {
				((CTreeCell) cells[i]).setImage(images[i]);
			}
		}
	}

	public void setImages(int column, Image[] images) {
		if((column >= 0) && (column < cells.length)) {
			((CTreeCell) cells[column]).setImages(images);
		}
	}
	
	void setNext(CTreeItem item) { next = item; }

	public void setOpen(boolean open) {
		for(int i = 0; i < cells.length; i++) {
			cells[i].setOpen(open);
		}
	}
	
	public void setOpen(int cell, boolean open) {
		if(cell >= 0 && cell < cells.length) {
			cells[cell].setOpen(open);
		}
	}

	public void setOpen(int x, int y, boolean open) {
		for(int i = 0; i < cells.length; i++) {
			if(cells[i].getBounds().contains(x,y)) {
				cells[i].setOpen(open);
				break;
			}
		}
	}
	
	void setPainted(boolean painted) {
		if(this.painted != painted) {
			this.painted = painted;
			if(painted) {
				updatePaintedCells();
			} else {
				for(int i = 0; i < cells.length; i++) {
					cells[i].setPainted(false);
				}
			}
			ctree.firePaintedItemEvent(this, painted);
		}
	}

	void setPrevious(CTreeItem item) {
		previous = item;
	}
	
	public void setSelected(boolean selected) {
		if(enabled) {
			for(int i = 0; i < cells.length; i++) {
				cells[i].setSelected(selected);
			}
		}
	}

	public void setText(int column, String string) {
		CTreeCell[] cells = getCells();
		if((column >= 0) && (column < cells.length)) {
			((CTreeCell) cells[column]).setText(string);
		}
	}

	public void setText(String string) {
		CTreeCell[] cells = getCells();
		if(cells.length > 0) {
			((CTreeCell) cells[0]).setText(string);
		}
	}
	
	public void setText(String[] strings) {
		CTreeCell[] cells = getCells();
		if(cells.length >= strings.length) {
			for(int i = 0; i < strings.length; i++) {
				((CTreeCell) cells[i]).setText(strings[i]);
			}
		}
	}

	void setTop(int top) {
		this.top = top;
	}

	public void setTreeIndent(int indent) {
		if(hasTreeCell()) {
			getTreeCell().setIndent(indent);
		}
	}

	public void setVisible(boolean visible) {
		if(this.visible != visible) {
			this.visible = visible;
			for(int i = 0; i < cells.length; i++) {
				cells[i].setVisible(visible);
			}
			ctree.layout(visible ? SWT.Show : SWT.Hide, this);
		}
	}
	
	public void update() {
		for(int i = 0; i < cells.length; i++) {
			cells[i].update();
		}
	}
	
	protected void updateColors() {
		for(int i = 0; i < cells.length; i++) {
			cells[i].updateColors();
		}
	}
	
	private void updatePaintedCells() {
		for(int i = 0; i < cells.length; i++) {
			cells[i].setPainted(ctree.getColumn(i).isVisible());
		}
	}	
}