/****************************************************************************
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Color;
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.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TypedListener;
import org.eclipse.swt.widgets.Widget;
/**
* <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 CTree extends Composite implements Listener {
/**
* true if the platform is detected as being "carbon"
*/
public static final boolean carbon = "carbon".equals(SWT.getPlatform());
/**
* true if the platform is detected as being "gtk"
*/
public static final boolean gtk = "gtk".equals(SWT.getPlatform());
/**
* true if the platform is detected as being "win32"
*/
public static final boolean win32 = "win32".equals(SWT.getPlatform());
private static int checkStyle(int style) {
int mask = SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT
| SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.MULTI
| SWT.NO_FOCUS | SWT.CHECK;
return (style & mask);
}
GC internalGC = new GC(Display.getDefault());
/**
* A list of items that will actually be painted to the screen.<br>
* Subclasses may override the order as the order of paintedItems represents
* the final drawing order of the items - the last item in this list is
* drawn to the screen last and is, therefore, drawn on top of any
* overlapping items.
*/
protected List paintedItems = new ArrayList();
/**
* A list of the selected items. Only items which are in the visibleItems
* list can be in this list.
*/
protected List selection = new ArrayList();
/**
* The first item selected with the SHIFT or CTRL key pressed. Used to
* create the selection array when selecting multiple items.
*/
protected CTreeItem shiftSel;
/**
* Signifies whether or not the items of this container can be selected.
*/
protected boolean selectable = true;
/**
* Signifies whether or not the items of this container are selected when
* their toggle is clicked on, as opposed to their body.
*/
protected boolean selectOnToggle = true;
Class[] cellClasses = null;
// private Point mmPoint = null; // mouse move point
// private Point mdPoint = null; // mouse down point
// private Point muPoint = null; // mouse up point
// private Point mmDelta = null; // mouse move delta
protected int style = 0;
protected Canvas body;
protected Composite header;
protected Table internalTable;
CTreeColumn[] columns = new CTreeColumn[0];
int[] columnOrder = new int[0];
boolean fillerColumnSet = false;
boolean nativeHeader = true;
CTreeColumn sortColumn;
int sortDirection = -1;
protected ScrollBar hBar;
protected ScrollBar vBar;
protected boolean hasFocus = false;
protected String emptyMessage = "";
protected boolean linesVisible = false;
protected boolean lastLine = false;
protected boolean vLines = true;
protected boolean hLines = true;
protected SColors colors;
// public int marginWidth = gtk ? 0 : 1;
// public int marginHeight = gtk ? 0 : 0;
private Listener filter;
private List paintedItemListeners;
protected boolean nativeGrid = true;
protected CTreeLayout layout;
public boolean drawViewportNorth = false;
public boolean drawViewportEast = false;
public boolean drawViewportSouth = false;
public boolean drawViewportWest = false;
// public void addListener(int eventType, Listener listener) {
// switch(eventType) {
// case SWT.MouseDoubleClick:
// case SWT.MouseDown:
// case SWT.MouseEnter:
// case SWT.MouseExit:
// case SWT.MouseHover:
// case SWT.MouseMove:
// case SWT.MouseUp:
// case SWT.MouseWheel:
// case SWT.KeyDown:
// case SWT.KeyUp:
// container.addListener(eventType, listener);
// break;
// default:
// super.addListener(eventType, listener);
// break;
// }
// }
//
// public void addMouseListener(MouseListener listener) {
// container.addMouseListener(listener);
// }
//
// public void addMouseMoveListener(MouseMoveListener listener) {
// container.addMouseMoveListener(listener);
// }
//
// public void addMouseTrackListener(MouseTrackListener listener) {
// container.addMouseTrackListener(listener);
// }
public boolean paintGridAsBackground = false;
List addedItems = new ArrayList();
List removedItems = new ArrayList();
int topOld = -1;
int heightOld = -1;
boolean updatePaintedList = false;
private int checkColumn = -1;
private boolean checkRoots = true;
private int treeColumn = 0;
private int treeIndent = 16;
private boolean selectOnTreeToggle = false;
List itemList = new ArrayList();
private List visibleItems = null;
public CTree(Composite parent, int style) {
super(parent, checkStyle(style));
this.style = style;
colors = new SColors(getDisplay());
updateColors();
// setBackground(getColors().getTableBackground());
setBackground(getDisplay().getSystemColor(SWT.COLOR_RED));
hBar = getHorizontalBar();
if (hBar != null) {
hBar.setVisible(false);
hBar.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
if (internalTable != null) {
if (win32) { // pogramatic scrolling doesn't work on
// win32
int sel = hBar.getSelection();
Rectangle hBounds = internalTable.getBounds();
hBounds.x = -sel;
hBounds.width += sel;
internalTable.setBounds(hBounds);
} else {
internalTable.getHorizontalBar().setSelection(
hBar.getSelection());
}
}
if(gtk) {
body.setLocation(-hBar.getSelection(), body.getLocation().y);
}
body.redraw();
}
});
}
vBar = getVerticalBar();
if (vBar != null) {
vBar.setVisible(false);
vBar.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
if(gtk) {
body.setLocation(body.getLocation().x, layout.headerSize.y-vBar.getSelection());
}
body.redraw();
}
});
}
body = new Canvas(this, SWT.BORDER | SWT.NO_BACKGROUND);
body.setBackground(colors.getTableBackground());
nativeHeader = false;
new CTreeColumn(this, 0);
fillerColumnSet = true;
nativeHeader = true;
filter = new Listener() {
public void handleEvent(Event event) {
if(CTree.this.getShell() == ((Control) event.widget).getShell()) {
handleFocus(SWT.FocusOut);
}
}
};
body.addKeyListener(new KeyAdapter() {}); // traverse does not work without this...
body.addListener(SWT.FocusIn, this);
body.addListener(SWT.MouseDown, this);
body.addListener(SWT.MouseDoubleClick, this);
body.addListener(SWT.MouseMove, this);
body.addListener(SWT.MouseUp, this);
body.addListener(SWT.Paint, new Listener() {
public void handleEvent(Event e) {
if (SWT.Paint == e.type) {
paintBody(e);
}
}
});
body.addListener(SWT.Traverse, this);
addListener(SWT.Dispose, new Listener() {
public void handleEvent(Event event) {
getDisplay().removeFilter(SWT.FocusIn, filter);
removeAll();
if (internalTable != null && !internalTable.isDisposed()) {
internalTable.dispose();
}
if(internalGC != null && internalGC.isDisposed()) {
internalGC.dispose();
}
}
});
if((style & SWT.CHECK) != 0) checkColumn = 0;
setLayout(layout = new CTreeLayout(this));
}
void addColumn(CTreeColumn column, int style) {
if(fillerColumnSet) {
fillerColumnSet = false;
columns[0].dispose();
}
CTreeColumn[] newColumns = new CTreeColumn[columns.length+1];
int[] newColumnOrder = new int[columnOrder.length+1];
System.arraycopy(columns, 0, newColumns, 0, columns.length);
System.arraycopy(columnOrder, 0, newColumnOrder, 0, columnOrder.length);
columns = newColumns;
columnOrder = newColumnOrder;
columns[columns.length-1] = column;
columnOrder[columnOrder.length-1] = columnOrder.length-1;
}
void addItem(CTreeItem item) {
addItem(-1, item);
}
void addItem(int index, CTreeItem item) {
if(index < 0 || index > itemList.size()-1) {
itemList.add(item);
} else {
itemList.add(index, item);
}
addedItems.add(item);
visibleItems = null;
body.redraw();
}
void addItems() {
addedItems = new ArrayList();
layout(true, true);
updatePaintedList = true;
}
/**
* Adds the listener to the collection of listeners who will be notified
* when the Paint Status of an item changes.
* <p>
* An item may be considered visible, and will be returned with
* {@link CTree#getVisibleItems()}, even though it will not be
* painted on the screen. Paint status, on the other hand, refers to whether
* or not an item will actually be painted when the
* {@link CTree#paintBody(Event)} method is called.
* </p>
* <p>
* The Event that is passed to this listener will have the item, whose Paint
* Status has changed, set as Event.item. The actual Paint Status is
* dertermined through the value of the Event's detail field: values > 0
* mean the item will be painted, while values < 0 mean the item will not be
* painted.
* </p>
*
* @param listener
* the listener which should be notified
* @see #removePaintedItemListener
* @see #getPaintedItems()
*/
public void addPaintedItemListener(Listener listener) {
if (paintedItemListeners == null) {
paintedItemListeners = new ArrayList();
}
if (!paintedItemListeners.contains(listener)) {
paintedItemListeners.add(listener);
}
}
/**
* An item may be considered visible, and will be returned with
* {@link CTree#getVisibleItems()}, even though it will not be
* painted on the screen. Paint status, on the other hand, refers to whether
* or not an item will actually be painted when the
* {@link CTree#paintBody(Event)} method is called.
*
* @return an array of items that will be painted to the screen during paint
* events
* @see #getVisibleItems()
*/
// public CTreeItem[] getPaintedItems() {
// return (CTreeItem[]) paintedItems.toArray(new
// CTreeItem[paintedItems.size()]);
// }
public void addSelectionListener(SelectionListener listener) {
checkWidget();
if (listener != null) {
TypedListener typedListener = new TypedListener(listener);
addListener(SWT.Selection, typedListener);
addListener(SWT.DefaultSelection, typedListener);
}
}
public void addTreeListener(TreeListener listener) {
checkWidget ();
if(listener != null) {
TypedListener typedListener = new TypedListener (listener);
addListener (SWT.Collapse, typedListener);
addListener (SWT.Expand, typedListener);
}
}
public void clear(int index, boolean all) {
// TODO Auto-generated method stub
}
public void clearAll(boolean all) {
// TODO Auto-generated method stub
}
public void deselectAll() {
selection = new ArrayList();
finishSelection();
}
private void finishSelection() {
if(!isDisposed()) {
for(Iterator i = selection.iterator(); i.hasNext(); ) {
((CTreeItem) i.next()).setSelected(true);
}
showSelection();
body.redraw();
fireSelectionEvent(false);
}
}
void firePaintedItemEvent(CTreeItem item, boolean isPainted) {
if (paintedItemListeners != null) {
Event event = new Event();
event.detail = isPainted ? 1 : -1;
event.item = item;
Listener[] la = (Listener[]) paintedItemListeners
.toArray(new Listener[paintedItemListeners.size()]);
for (int i = 0; i < la.length; i++) {
la[i].handleEvent(event);
}
}
}
protected void fireSelectionEvent(boolean defaultSelection) {
Event event = new Event();
event.type = defaultSelection ? SWT.DefaultSelection : SWT.Selection;
if (selection.size() == 1)
event.item = (CTreeItem) selection.get(0);
notifyListeners(event.type, event);
}
private void fireTreeEvent(Widget item, boolean collapse) {
Event event = new Event();
event.type = collapse ? SWT.Collapse : SWT.Expand;
event.item = item;
notifyListeners(event.type, event);
}
Composite getBody() {
return body;
}
public int getCheckColumn() {
return checkColumn;
}
public boolean getCheckRoots() {
return checkRoots;
}
public SColors getColors() {
return colors;
}
public int getColumnCount() {
return columns.length;
}
public int[] getColumnOrder() {
int[] order = new int[columnOrder.length];
System.arraycopy(columnOrder, 0, order, 0, columnOrder.length);
return order;
}
int[] getColumnWidths() {
int[] widths = new int[columns.length];
for(int i = 0; i < widths.length; i++) {
widths[i] = columns[i].getWidth();
}
return widths;
}
protected Rectangle getContentArea() {
Rectangle area = getClientArea();
area.y = layout.headerSize.y;
area.height -= layout.headerSize.y;
return area;
}
/**
* The Empty Message is the text message that will be displayed when there
* are no Items to be displayed (the CTable is empty).
*
* @return a String representing the Empty Message. Guaranteed to NOT be
* null.
* @see org.aspencloud.widgets.ccontainer#setEmptyMessage(java.lang.String)
*/
public String getEmptyMessage() {
return emptyMessage;
}
public int getGridLineWidth() {
return (internalTable != null) ? internalTable.getGridLineWidth() : 0;
}
Composite getHeader() {
if (header == null) {
header = new Composite(this, SWT.NONE);
body.moveBelow(header);
}
return header;
}
// public int indexOf(CTreeItem item) {
// return items.indexOf(item);
// }
// public int indexOf(CTreeColumn column) {
// return (internalTable != null) ? internalTable.indexOf(column) : -1;
// }
// public boolean isDirty(int opcode) {
// return ((dirtyFlags & opcode) != 0);
// }
public int getHeaderHeight() {
if(nativeHeader) {
return internalTable.getHeaderHeight();
}
// TODO: getHeaderHeight... get from the first non-native column?
return 0;
}
// public boolean isOperation(int opcode) {
// return operation == opcode;
// }
// public boolean isSelected(int index) {
// return getItem(index).isSelected();
// }
public boolean getHeaderVisible() {
if(nativeHeader) {
return internalTable.getHeaderVisible();
}
return false;
}
Table getInternalTable() {
if(internalTable == null) {
internalTable = new Table(getHeader(), SWT.NONE);
internalTable.setLinesVisible(linesVisible);
}
return internalTable;
}
public CTreeItem getItem(int index) {
if(isEmpty() || index < 0 || index > itemList.size()-1) return null;
return (CTreeItem) itemList.get(index);
}
public int getItemCount() {
return itemList.size();
}
public int getItemHeight() {
return ((CTreeLayout) layout).getItemHeight();
}
/**
* Returns a (possibly empty) array of items contained in the
* receiver that are direct item children of the receiver. These
* are the roots of the tree.
* <p>
* Note: This is not the actual structure used by the receiver
* to maintain its list of items, so modifying the array will
* not affect the receiver.
* </p>
*
* @return the items
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public CTreeItem[] getItems() {
if(isEmpty()) return new CTreeItem[0];
return (CTreeItem[]) itemList.toArray(new CTreeItem[itemList.size()]);
}
/**
* returns a deep list of items belonging to the given item
* {@inheritDoc}
*/
// List getItems(CTreeItem item) {
// return getItems(item, true);
// }
List getItems(CTreeItem item, boolean all) {
List l = new ArrayList();
CTreeItem[] items = item.getItems();
for(int i = 0; i < items.length; i++) {
l.add(items[i]);
if(all || items[i].getExpanded()) {
l.addAll(getItems(items[i], all));
}
}
return l;
}
/**
* Get a list of items within, or touching, the given rectangle.
*
* @param rect
* @return
*/
protected CTreeItem[] getItems(Rectangle rect) {
if(isEmpty()) {
return new CTreeItem[0];
} else {
List il = new ArrayList();
for(Iterator i = paintedItems.iterator(); i.hasNext(); ) {
CTreeItem item = (CTreeItem) i.next();
Rectangle[] ra = item.getCellBounds();
for (int j = 0; j < ra.length; j++) {
if (ra[j].intersects(rect)) {
il.add(item);
break;
}
}
}
return (il.isEmpty()) ? new CTreeItem[0] : (CTreeItem[]) il.toArray(new CTreeItem[il.size()]);
}
}
public boolean getLastLineVisible() {
return lastLine;
}
public boolean getLinesVisible() {
return linesVisible;
}
public boolean getNativeHeader() {
return nativeHeader;
}
protected List getPaintedItems() {
int top = getScrollPosition().y;
int bot = top + body.getClientArea().height;
int itop = 0;
int ibot = 0;
List list = new ArrayList();
boolean painting = false;
for(Iterator i = items(false).iterator(); i.hasNext(); ) {
CTreeItem item = (CTreeItem) i.next();
Rectangle r = item.getBounds();
ibot = r.y+r.height;
if(itop <= top && top < ibot) {
painting = true;
}
if(painting) {
list.add(item);
}
if(itop < bot && bot <= ibot) {
break;
}
itop = r.y+r.height;
}
return list;
}
public CTreeItem getParentItem() {
// TODO Auto-generated method stub
return null;
}
Point getScrollPosition() {
return new Point(
(hBar == null || hBar.isDisposed()) ? 0 : hBar.getSelection(),
(vBar == null || vBar.isDisposed()) ? 0 : vBar.getSelection());
}
public CTreeItem[] getSelection() {
return selection.isEmpty() ?
new CTreeItem[0] :
(CTreeItem[]) selection.toArray(new CTreeItem[selection.size()]);
}
public int getSelectionCount() {
return selection.size();
}
/**
* @see CTree#setSelectOnToggle(boolean)
* @return
*/
public boolean getSelectOnToggle() {
return selectOnToggle;
}
/**
* @see CTree#setSelectOnTreeToggle(boolean)
* @return true if selection state is to changed when a toggle is clicked, false otherwise
*/
public boolean getSelectOnTreeToggle() {
return selectOnTreeToggle;
}
// public void redraw(CTreeItem item) {
// // TODO: paint / redraw individual item
// // Rectangle r = item.getBounds();
// // body.redraw(r.x-1, r.y-1, r.width+2, r.height+2, true);
// redraw();
// }
public int getSortDirection() {
return sortDirection;
}
public int getStyle() {
return style;
}
public CTreeItem getTopItem() {
int top = getScrollPosition().y;
int itop = 0;
int ibot = 0;
for(Iterator i = items(false).iterator(); i.hasNext(); ) {
CTreeItem item = (CTreeItem) i.next();
Rectangle r = item.getBounds();
ibot = r.y+r.height;
if(itop <= top && top < ibot) {
return item;
}
itop = r.y+r.height;
}
return null;
}
// public void removeListener(int eventType, Listener listener) {
// switch(eventType) {
// case SWT.MouseDoubleClick:
// case SWT.MouseDown:
// case SWT.MouseEnter:
// case SWT.MouseExit:
// case SWT.MouseHover:
// case SWT.MouseMove:
// case SWT.MouseUp:
// case SWT.MouseWheel:
// case SWT.KeyDown:
// case SWT.KeyUp:
// container.removeListener(eventType, listener);
// break;
// default:
// super.removeListener(eventType, listener);
// break;
// }
// }
//
// public void removeMouseListener(MouseListener listener) {
// container.removeMouseListener(listener);
// }
//
// public void removeMouseMoveListener(MouseMoveListener listener) {
// container.removeMouseMoveListener(listener);
// }
//
// public void removeMouseTrackListener(MouseTrackListener listener) {
// container.removeMouseTrackListener(listener);
// }
public int getTreeColumn() {
return treeColumn;
}
public int getTreeIndent() {
return treeIndent;
}
public void handleEvent(Event event) {
switch (event.type) {
case SWT.FocusIn:
case SWT.FocusOut:
handleFocus(event.type);
break;
case SWT.MouseDoubleClick:
case SWT.MouseDown:
case SWT.MouseMove:
case SWT.MouseUp:
handleMouseEvents(event);
break;
case SWT.Traverse:
handleTraverse(event);
break;
}
}
private void handleFocus(int type) {
if (isDisposed())
return;
switch (type) {
case SWT.FocusIn: {
if (hasFocus)
return;
hasFocus = true;
updateFocus();
Display display = getDisplay();
display.removeFilter(SWT.FocusIn, filter);
display.addFilter(SWT.FocusIn, filter);
Event e = new Event();
notifyListeners(SWT.FocusIn, e);
break;
}
case SWT.FocusOut: {
if (!hasFocus)
return;
Control focusControl = getDisplay().getFocusControl();
if (focusControl == this)
return;
for(Iterator i = items(false).iterator(); i.hasNext(); ) {
if(((CTreeItem) i.next()).contains(focusControl)) return;
}
hasFocus = false;
updateFocus();
Display display = getDisplay();
display.removeFilter(SWT.FocusIn, filter);
Event e = new Event();
notifyListeners(SWT.FocusOut, e);
}
}
}
protected void handleMouseEvents(Event event) {
CTreeItem item = null;
if (event.widget == body) {
item = getItem(event.x, event.y);
} else if (event.item instanceof CTreeItem) {
item = (CTreeItem) event.item;
}
switch (event.type) {
case SWT.MouseDoubleClick:
if(item != null && (selectOnToggle || !item.isToggle(event.x, event.y))) {
fireSelectionEvent(true);
}
break;
case SWT.MouseDown:
if(!hasFocus) setFocus();
if(item == null) {
if(event.widget == body) {
item = getItem(event.x, event.y);
} else if(event.item instanceof CTreeItem) {
item = (CTreeItem) event.item;
}
}
switch(event.button) {
// TODO - popup menu: not just for mouse down events!
case 3:
Menu menu = getMenu();
if ((menu != null) && ((menu.getStyle() & SWT.POP_UP) != 0)) {
menu.setVisible(true);
}
case 1:
if(selectOnToggle || !item.isToggle(event.x, event.y)) {
if(selectOnTreeToggle || !item.isTreeToggle(event.x,event.y)) {
if((event.stateMask & SWT.SHIFT) != 0) {
if(shiftSel == null) {
if(selection.isEmpty()) selection.add(item);
shiftSel = (CTreeItem) selection.get(selection.size() - 1);
}
setSelection(shiftSel, item);
} else if((event.stateMask & SWT.CONTROL) != 0) {
toggleSelection(item);
shiftSel = null;
} else {
setSelection(item);
shiftSel = null;
}
}
}
break;
}
break;
case SWT.MouseMove:
// TODO: make toggles more dynamic
break;
case SWT.MouseUp:
if(item.isToggle(event.x,event.y)) {
boolean open = item.isOpen(event.x,event.y);
if(item.isTreeToggle(event.x,event.y)) {
// visibleItems = null;
item.setExpanded(!open);
fireTreeEvent(item, !open);
} else {
item.getCell(event.x, event.y).setOpen(!open);
}
}
break;
}
}
private void handleTraverse(Event event) {
switch (event.detail) {
case SWT.TRAVERSE_RETURN:
if (event.data instanceof CTreeCell) {
fireSelectionEvent(true);
}
break;
case SWT.TRAVERSE_ARROW_NEXT:
if(!selection.isEmpty()) {
setSelection(((CTreeItem) selection.get(selection.size()-1)).nextVisible());
}
break;
case SWT.TRAVERSE_ARROW_PREVIOUS:
if(!selection.isEmpty()) {
setSelection(((CTreeItem) selection.get(0)).previousVisible());
}
break;
}
}
/**
* Convenience method indicating whether or not the treeColumn is set to an
* actual column, and thus the tree hierarchy will be displayed.
* <p>Note that if the hierarchy is not displayed, then certain methods are
* able to be optimized and will take advantage of this fact</p>
* @return true if treeColumn is set to an existing column, false otherwise
*/
public boolean hasTreeColumn() {
return treeColumn >= 0 && treeColumn < getColumnCount();
}
public int indexOf(CTreeColumn column) {
return indexOf(column);
}
public int indexOf(CTreeItem item) {
return itemList.indexOf(item);
}
CTreeColumn getColumn(int index) {
if(index >= 0 && index < columns.length) {
return columns[index];
}
return null;
}
CTreeColumn[] getColumns() {
CTreeColumn[] ca = new CTreeColumn[columns.length];
System.arraycopy(columns, 0, ca, 0, columns.length);
return ca;
}
CTreeItem getItem(int x, int y) {
// must iterate in reverse drawing order in case items overlap each other
for(ListIterator i = paintedItems.listIterator(paintedItems.size()); i.hasPrevious();) {
CTreeItem item = (CTreeItem) i.previous();
if(item.contains(x,y)) return item;
}
return null;
}
/**
* returns the sort column of this container as an CTreeColumn.
* subclasses should override to provide an appropriate cast.
* @return the sort column
*/
protected CTreeColumn getSortColumn() {
return sortColumn;
}
public boolean isEmpty() {
return itemList.isEmpty();
}
/**
* Use this method to find out if an item is visible, and thus has the potential to be
* painted.
* <p>An Item will be visible when every parent between it and the root of the tree
* is expanded</p>
* @param item the item in question
* @return true if the item can be painted to the screen, false if it is hidden due to a
* parent item being collapsed
*/
public boolean isVisible(CTreeItem item) {
if(item.getVisible()) {
CTreeItem parentItem = item.getParentItem();
if(parentItem == null) return true;
if(parentItem.getExpanded()) return isVisible(parentItem);
}
return false;
}
List items(boolean all) {
if(visibleItems == null) {
visibleItems = new ArrayList();
for(Iterator i = itemList.iterator(); i.hasNext(); ) {
CTreeItem item = (CTreeItem) i.next();
if(all || item.isVisible()) {
visibleItems.add(item);
if(all || item.getExpanded()) {
visibleItems.addAll(getItems(item, all));
}
}
}
}
return visibleItems;
}
/**
* <p>
* Event types:
* <ul>
* <li>SWT.Collapse</li>
* <li>SWT.Expand</li>
* <li>SWT.Resize</li>
* </ul>
* </p>
* @param eventType
* @param cell
*/
void layout(int eventType, CTreeCell cell) {
layout.layout(eventType, cell);
updatePaintedList = true;
}
/**
* <p>
* Event types:
* <ul>
* <li>SWT.Move</li>
* <li>SWT.Resize</li>
* </ul>
* </p>
* @param eventType
* @param column
*/
void layout(int eventType, CTreeColumn column) {
layout.layout(eventType, column);
// updatePaintedList = true;
}
/**
* <p>
* Event types:
* <ul>
* <li>SWT.Collapse</li>
* <li>SWT.Expand</li>
* <li>SWT.Hide</li>
* <li>SWT.Move</li>
* <li>SWT.Show</li>
* </ul>
* </p>
* @param eventType
* @param item
*/
void layout(int eventType, CTreeItem item) {
if(SWT.Collapse == eventType) {
layout.layout(eventType, item);
visibleItems = null;
updatePaintedList = true;
} else if(SWT.Expand == eventType) {
layout.layout(eventType, item);
visibleItems = null;
updatePaintedList = true;
} else if(SWT.Hide == eventType && isVisible((CTreeItem)item)) {
layout.layout(eventType, item);
updatePaintedList = true;
item.setVisible(false);
} else if(SWT.Show == eventType && !isVisible((CTreeItem)item)) {
layout.layout(eventType, item);
updatePaintedList = true;
item.setVisible(true);
}
}
Rectangle mapRectangle(int x, int y, int width, int height) {
Rectangle r = new Rectangle(x,y,width,height);
Point point = getScrollPosition();
r.x += point.x;
r.y += point.y;
return r;
}
Rectangle mapRectangle(Rectangle rect) {
return mapRectangle(rect.x, rect.y, rect.width, rect.height);
}
protected void paintBackground(GC gc, Rectangle ebounds) {}
protected void paintBody(Event e) {
if(!addedItems.isEmpty()) addItems();
if(!removedItems.isEmpty()) removeItems();
int top = ((vBar == null || vBar.isDisposed()) ? -1 : vBar.getSelection());
int height = getClientArea().height;
if(updatePaintedList || topOld != top || heightOld != height) {
updatePaintedList = false;
topOld = top;
heightOld = height;
updatePaintedItems();
}
Rectangle ebounds = e.getBounds();
Image image = new Image(e.display, ebounds);
GC gc = new GC(image);
paintBackground(gc, ebounds);
if(paintGridAsBackground) paintGridLines(gc, ebounds);
paintItemBackgrounds(gc, ebounds);
paintSelectionIndicators(gc, ebounds);
paintColumns(gc, ebounds);
paintItems(gc, ebounds);
if(!paintGridAsBackground) paintGridLines(gc, ebounds);
paintViewport(gc, ebounds);
paintFocus(gc, ebounds);
e.gc.drawImage(image, ebounds.x, ebounds.y);
gc.dispose();
image.dispose();
}
protected void paintColumns(GC gc, Rectangle ebounds) {
CTreeColumn[] columns = getColumns();
for (int i = 0; i < columns.length; i++) {
columns[i].paint(gc, ebounds);
}
}
protected void paintFocus(GC gc, Rectangle ebounds) {
if (hasFocus) {
if (win32 || (gtk && !paintedItems.isEmpty()))
return;
gc.setAlpha(255);
gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
gc.setLineDash(new int[] { 1, 1 });
Rectangle r = getContentArea();
r.x += (1 - ebounds.x);
r.y += (1 - ebounds.y);
r.width -= 3;
r.height -= 3;
gc.drawRectangle(r);
}
}
protected void paintGridLines(GC gc, Rectangle ebounds) {
Rectangle r = getClientArea();
if(linesVisible && (!nativeGrid || getGridLineWidth() > 0)) {
gc.setForeground(getColors().getGrid());
int y = getScrollPosition().y;
int rowHeight = 0;
if(win32 && nativeGrid) {
if(isEmpty()) {
if(emptyMessage.length() > 0) {
Point tSize = gc.textExtent(emptyMessage);
rowHeight = tSize.y+2;
}
} else {
int gridline = getGridLineWidth();
for(Iterator i = items(false).iterator(); i.hasNext(); ) {
gc.drawLine(r.x, y, r.x+r.width, y);
CTreeItem item = (CTreeItem) i.next();
y += item.getSize().y + gridline;
}
rowHeight = getItemHeight();
}
}
if(hLines) {
int gridline = getGridLineWidth();
while(y < r.height) {
gc.drawLine(r.x, y, r.x+r.width, y);
y += rowHeight + gridline;
}
}
if(vLines) {
if(getColumnWidths() != null && getColumnWidths().length > 1) {
int x = -1;
for(int i = 0; i < getColumnWidths().length; i++) {
x += getColumnWidths()[i];
gc.drawLine(
x,
r.y,
x,
r.y+r.height
);
}
}
}
}
}
protected void paintItemBackgrounds(GC gc, Rectangle ebounds) {
if(gtk && nativeGrid && !paintedItems.isEmpty()) {
Rectangle r = getClientArea();
int gridline = getGridLineWidth();
int firstPaintedIndex = items(false).indexOf(paintedItems.get(0));
for(int i = 0; i < paintedItems.size(); i++) {
CTreeItem item = (CTreeItem) paintedItems.get(i);
if(linesVisible && ((firstPaintedIndex + i + 1) % 2 == 0)) {
gc.setBackground(getColors().getGrid());
gc.fillRectangle(
r.x,
item.getBounds().y-ebounds.y,
r.width,
item.getSize().y+gridline
);
item.setGridLine(true);
} else {
item.setGridLine(false);
}
}
}
}
protected void paintItems(GC gc, Rectangle ebounds) {
if(isEmpty()) {
if (emptyMessage.length() > 0) {
Point bSize = getSize();
Point tSize = gc.textExtent(emptyMessage);
gc.setForeground(colors.getItemForegroundNormal());
gc.drawText(emptyMessage, (bSize.x - tSize.x) / 2 - ebounds.x,
4 - ebounds.y);
}
} else {
Image image = new Image(gc.getDevice(), ebounds);
GC gc2 = new GC(image);
for (Iterator iter = paintedItems.iterator(); iter.hasNext();) {
CTreeItem item = (CTreeItem) iter.next();
for(int i = 0; i < item.cells.length; i++) {
CTreeCell cell = item.cells[i];
cell.paint(gc, ebounds);
Rectangle cb = cell.getClientArea();
gc2.setBackground(getDisplay().getSystemColor(SWT.COLOR_CYAN));
gc2.fillRectangle(ebounds);
if(!cb.isEmpty() &&
cell.paintClientArea(gc2,
new Rectangle(0,0,cb.width,cb.height))) {
gc.drawImage(image,
0,0,Math.min(ebounds.width, cb.width),Math.min(ebounds.height, cb.height),
cb.x-ebounds.x,cb.y-ebounds.y,cb.width,cb.height
);
}
}
}
gc2.dispose();
image.dispose();
}
}
protected void paintSelectionIndicators(GC gc, Rectangle ebounds) {
if(win32 && !selection.isEmpty()) {
for(Iterator i = selection.iterator(); i.hasNext(); ) {
CTreeItem item = (CTreeItem) i.next();
if(item.isVisible()) {
Rectangle r = item.getBounds();
r.x = 0;
r.y -= getScrollPosition().y;
r.width = getClientArea().width;
r.height = item.getSize().y + getGridLineWidth();
gc.setBackground(colors.getItemBackgroundSelected());
gc.fillRectangle(r);
}
}
}
}
protected void paintViewport(GC gc, Rectangle ebounds) {
if (drawViewportNorth || drawViewportEast || drawViewportSouth
|| drawViewportWest) {
gc.setAlpha(255);
gc.setForeground(colors.getBorder());
Rectangle r = getClientArea();
if (drawViewportNorth)
gc.drawLine(r.x, r.y, r.x + r.width, r.y);
if (drawViewportEast)
gc.drawLine(r.x + r.width - 1, r.y, r.x + r.width - 1, r.y
+ r.height);
if (drawViewportSouth)
gc.drawLine(r.x, r.y + r.height - 1, r.x + r.width, r.y
+ r.height - 1);
if (drawViewportWest)
gc.drawLine(r.x, r.y, r.x, r.y + r.height);
}
}
public void redraw(CTreeCell cell) {
Rectangle r = cell.getBounds();
redraw(r.x-1, r.y-1, r.width+2, r.height+2, true);
}
public void redraw(CTreeItem item) {
Rectangle r = item.getBounds();
redraw(r.x-1, r.y-1, r.width+2, r.height+2, true);
}
public void removeAll() {
if(!isEmpty()) {
boolean selChange = false;
if(!selection.isEmpty()) {
selection = new ArrayList();
selChange = true;
}
visibleItems = null;
paintedItems = new ArrayList();
for(Iterator i = items(true).iterator(); i.hasNext(); ) {
CTreeItem item = (CTreeItem) i.next();
if(!item.isDisposed()) {
removedItems.add(item);
item.dispose();
}
}
itemList = new ArrayList();
if(selChange) fireSelectionEvent(false);
}
}
void removeColumn(CTreeColumn column) {
if(columns.length > 0) {
CTreeColumn[] newColumns = new CTreeColumn[columns.length-1];
int[] newColumnOrder = new int[columnOrder.length-1];
System.arraycopy(columns, 0, newColumns, 0, newColumns.length);
System.arraycopy(columnOrder, 0, newColumnOrder, 0, newColumnOrder.length);
columns = newColumns;
columnOrder = newColumnOrder;
// TODO: removeColumn - remove sortcolumn...
}
}
void removeItem(CTreeItem item) {
itemList.remove(item);
if(!removedItems.contains(item)) {
removedItems.add(item);
boolean selChange = selection.remove(item);
if(selChange) fireSelectionEvent(false);
redraw();
}
}
private void removeItems() {
removedItems = new ArrayList();
visibleItems = null;
layout(true, true);
updatePaintedList = true;
}
public void removePaintedItemListener(Listener listener) {
if (paintedItemListeners != null) {
paintedItemListeners.remove(listener);
}
}
public void removeSelectionListener(SelectionListener listener) {
checkWidget();
if (listener != null) {
removeListener(SWT.Selection, listener);
removeListener(SWT.DefaultSelection, listener);
}
}
public void removeTreeListener(TreeListener listener) {
checkWidget ();
if(listener != null) {
removeListener(SWT.Collapse, listener);
removeListener(SWT.Expand, listener);
}
}
void scrollTo(Point pt) {
setScrollPosition(pt);
}
void scrollToX(int x) {
setOrigin(x, getScrollPosition().y);
}
void scrollToY(int y) {
setOrigin(getScrollPosition().x, y);
}
public void selectAll() {
if((getStyle() & SWT.SINGLE) == 0) {
selection = new ArrayList();
for(Iterator i = items(true).iterator(); i.hasNext(); ) {
CTreeItem item = (CTreeItem ) i.next();
item.setSelected(true);
selection.add(item);
}
finishSelection();
}
}
/**
* disabled: use the SColors class instead
*/
public void setBackground(Color color) {
}
public void setCellClass(Class clazz) {
cellClasses = new Class[] { clazz };
}
public void setCellClasses(Class[] classes) {
cellClasses = classes;
}
public void setCheckColumn(int column, boolean roots) {
if(checkColumn != column) {
checkColumn = column;
checkRoots = roots;
// TODO update check cells
}
}
public void setColumnOrder(int[] order) {
if(internalTable != null) internalTable.setColumnOrder(order);
}
/**
* Sets the message that will be displayed when their are no Items to be
* displayed (the Container is empty). The message will span all rows and be
* aligned to the Top and Center of the CTable. Setting <b>string</b> to
* null will disable the Empty Message Display feature.
*
* @param string
* the message to be displayed
*/
public void setEmptyMessage(String string) {
emptyMessage = (string != null) ? string : "";
}
public boolean setFocus() {
return forceFocus();
}
// public CTreeItem[] getVisibleItems() {
// return (visibleItems.isEmpty()) ? new CTreeItem[0] : (CTreeItem[]) visibleItems.toArray(new CTreeItem[visibleItems.size()]);
// }
/**
* currently disabled: use the CTableColors class instead
* <p>
* post a feature request if you need it enabled
* </p>
*/
public void setForeground(Color color) {
}
public void setHeaderVisible(boolean show) {
getInternalTable().setHeaderVisible(show);
}
public void setHorizontalLinesVisible(boolean visible) {
hLines = visible;
}
public void setInsertMark(CTreeItem item, boolean before) {
// TODO Auto-generated method stub
}
public void setItemCount(int count) {
// TODO Auto-generated method stub
}
/**
* Sets the layout which is associated with the receiver to be the argument
* which may be null. If the argument is non-null then it must be a subclass
* of CContainerLayout or this method will simply return.
*
* @param layout
* the receiver's new layout or null
*
* @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 setLayout(Layout layout) {
if (layout instanceof CTreeLayout) {
this.layout = (CTreeLayout) layout;
super.setLayout(layout);
}
}
public void setLinesVisible(boolean visible) {
setLinesVisible(visible, true);
}
/**
* If lastLine == false, then lines are only drawn in between items (if, of
* course, visible == true)
*
* @param visible
* @param lastLine
* whether or not the last line is set visible
* @see org.aspencloud.widgets.ccontainer#setLinesVisible(boolean)
*/
public void setLinesVisible(boolean visible, boolean lastLine) {
if (linesVisible != visible) {
linesVisible = visible;
if (internalTable != null)
internalTable.setLinesVisible(linesVisible);
redraw();
}
this.lastLine = lastLine;
}
public void setNativeHeader(boolean nativeHeader) {
this.nativeHeader = nativeHeader;
}
void setOrigin(int x, int y) {
if(hBar != null && !hBar.isDisposed()) hBar.setSelection(x);
if(vBar != null && !vBar.isDisposed()) vBar.setSelection(y);
redraw();
}
public void setRedraw(boolean redraw) {
super.setRedraw(redraw);
if (internalTable != null) {
internalTable.setRedraw(redraw);
}
}
void setScrollPosition(Point origin) {
setOrigin(origin.x,origin.y);
}
/**
* Enables items in this Container to be selected
*
* @param selectable
*/
public void setSelectable(boolean selectable) {
this.selectable = selectable;
for(Iterator i = items(true).iterator(); i.hasNext();) {
((CTreeItem) i.next()).setEnabled(selectable);
}
}
public void setSelection(CTreeItem item) {
for(Iterator i = selection.iterator(); i.hasNext(); ) {
((CTreeItem) i.next()).setSelected(false);
}
if(item == null) {
selection = new ArrayList();
} else {
selection = new ArrayList(Collections.singleton(item));
shiftSel = item;
}
finishSelection();
}
public void setSelection(CTreeItem from, CTreeItem to) {
// TODO Auto-generated method stub
}
public void setSelection(CTreeItem[] items) {
for(Iterator i = selection.iterator(); i.hasNext(); ) {
((CTreeItem) i.next()).setSelected(false);
}
if(items == null || items.length == 0) {
selection = new ArrayList();
} else {
selection = new ArrayList(Arrays.asList(items));
shiftSel = items[0];
}
finishSelection();
}
/**
* If the the user clicks on the toggle of an item (treeCell or not) the
* corresponding item will become selected if, and only if, selectOnToggle
* is true
*
* @param select
* the new state of selectOnToggle
*/
public void setSelectOnToggle(boolean select) {
selectOnToggle = select;
}
/**
* If the the user clicks on the toggle of the treeCell the corresponding item will
* become selected if, and only if, selectOnTreeToggle is true
* @param select the new state of selectOnTreeToggle
*/
public void setSelectOnTreeToggle(boolean select) {
selectOnToggle = select;
}
public void setSortColumn(CTreeColumn column) {
sortColumn = column;
if(nativeHeader) {
internalTable.setSortColumn(column.nativeColumn);
}
}
public void setSortDirection(int direction) {
sortDirection = direction;
if(nativeHeader) {
internalTable.setSortDirection(direction);
}
}
// protected void addOrderedItem(CTreeItem item) {
// CTreeItem cti = (CTreeItem) item;
// if(!hasTreeColumn() || !cti.hasParentItem()) {
// orderedItems.add(item);
// } else {
// CTreeItem parent = cti.getParentItem();
// int ix = 0;//orderedItems.indexOf(parent);
// if(parent.hasItems()) {
// ix += parent.getItemCount();
// }
// orderedItems.add(ix, item);
// }
// }
public void setTopItem(CTreeItem item) {
setScrollPosition(
new Point(getScrollPosition().x, item.getBounds().y - getContentArea().y));
}
/**
* The Tree Column indicates which column should act as the tree by placing
* the expansion toggle in its cell.
* <p>If column is greater than the number of columns, or is less than zero
* then no column will have an expansion toggle (or room for one).</p>
* @param column the column to use for the tree
*/
public void setTreeColumn(int column) {
if(treeColumn != column) {
treeColumn = column;
}
}
/**
* Sets the amount to indent child items from their parent.
* <p>Suitable defaults are set according to SWT.Platform, but the option
* to customize is still exposed through this metho</p>
* <p>Note that only the Tree Column will be indented; if there is no Tree Column
* then this setting will have no affect. If you need the entire Item and all of
* its columns to be indented, please file a feature request at
* sourceforge.net/projects/calypsorcp</p>
* @param indent
*/
public void setTreeIndent(int indent) {
treeIndent = indent;
}
public void setVerticalLinesVisible(boolean visible) {
vLines = visible;
}
/**
* TODO
* @param column
*/
public void showColumn(CTreeColumn column) {
// TODO Auto-generated method stub
}
// public int getSortDirection() {
// // TODO Auto-generated method stub
// return 0;
// }
public void showItem(CTreeItem item) {
Rectangle r = item.getBounds();
Rectangle c = getContentArea();
Point scroll = getScrollPosition();
if(r.height > c.height || !c.contains(new Point(r.x, r.y - scroll.y))) {
setScrollPosition(new Point(scroll.x, r.y - c.y));
} else if(!c.contains(new Point(r.x, r.y + r.height - 1 - scroll.y))) {
setScrollPosition(new Point(scroll.x, r.y - c.y - c.height + r.height));
}
// TODO: showItem needs to work horizontally as well (call showColumn)
}
public void showSelection() {
if(!selection.isEmpty()) {
showItem((CTreeItem) selection.get(0));
}
}
/**
* toggle the selection state of the item
*
* @param item
* the item whose selection state is to be toggled
* @return true if the item's selection state was toggled on, false
* otherwise
*/
public boolean toggleSelection(CTreeItem item) {
boolean result = false;
if (selection.contains(item)) {
selection.remove(item);
} else {
selection.add(item);
result = true;
}
finishSelection();
return result;
}
public void updateColors() {
super.setBackground(colors.getTableBackground());
}
private void updateFocus() {
colors.setFocus(hasFocus);
if (!selection.isEmpty()) {
for (Iterator i = selection.iterator(); i.hasNext();) {
((CTreeItem) i.next()).updateColors();
}
redraw();
} else if (gtk) {
redraw(); // focus has changed
}
}
// public void setSortColumn(CTreeColumn column) {
//
// }
// public void setSortDirection(int direction) {
// // TODO Auto-generated method stub
// }
protected void updatePaintedItems() {
// System.out.println("updatePaintedItems");
List newPainted = getPaintedItems();
if(!newPainted.equals(paintedItems)) {
// System.out.println("updatePaintedItems: changed");
List oldPainted = (paintedItems == null) ? new ArrayList() : new ArrayList(paintedItems);
paintedItems = new ArrayList(newPainted);
// remove common elements from both lists
for (Iterator i = oldPainted.iterator(); i.hasNext();) {
Object o = i.next();
if (newPainted.contains(o)) {
newPainted.remove(o);
i.remove();
}
}
// update the items
for (Iterator i = oldPainted.iterator(); i.hasNext();) {
((CTreeItem) i.next()).setPainted(false);
}
for (Iterator i = newPainted.iterator(); i.hasNext();) {
((CTreeItem) i.next()).setPainted(true);
}
}
}
// public void showColumn(CTreeColumn column) {
// // TODO Auto-generated method stub
// }
}