/****************************************************************************
* Copyright (c) 2005-2006 Jeremy Dowdall
* 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:
* Jeremy Dowdall <jeremyd@aspencloud.com> - 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());
}
}
}