Files
Brainstorm/Foundation TCV SWT Client/src/com/foundation/tcv/swt/client/SimpleTreeTable.java
2014-05-30 10:31:51 -07:00

2551 lines
96 KiB
Java

/*
* Copyright (c) 2004,2009 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.tcv.swt.client;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.GC;
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.Listener;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import com.common.comparison.Comparator;
import com.common.debug.Debug;
import com.common.util.IHashSet;
import com.common.util.IIterator;
import com.common.util.IList;
import com.common.util.LiteHashMap;
import com.common.util.LiteHashSet;
import com.common.util.LiteList;
import com.common.util.optimized.IntArray;
import com.foundation.tcv.client.view.IAbstractClientViewComponent;
import com.foundation.tcv.client.view.MultiResourceHolder;
import com.foundation.tcv.client.view.ResourceHolder;
import com.foundation.tcv.swt.ISimpleTreeTable;
import com.foundation.tcv.swt.SimpleTreeTableCellData;
import com.foundation.tcv.swt.client.cell.CellComponent;
import com.foundation.tcv.view.ViewMessage;
import com.foundation.view.JefColor;
import com.foundation.view.JefFont;
import com.foundation.view.JefImage;
import com.foundation.view.swt.SimpleTreeTable.ColumnData;
import com.foundation.view.swt.util.SwtUtilities;
public class SimpleTreeTable extends TreeComponent implements ISimpleTreeTable, TreeListener, ControlListener {
/** A holder for the value of the row background color. */
private MultiResourceHolder rowBackgroundColorHolder = new MultiResourceHolder(this);
/** A holder for the value of the row foreground color. */
private MultiResourceHolder rowForegroundColorHolder = new MultiResourceHolder(this);
/** A holder for the value of the row font. */
private MultiResourceHolder rowFontHolder = new MultiResourceHolder(this);
/** A flag to temporarily disable the delay when synchronizing the selection before sending the double click message. */
private boolean disableAutoSynchronizeDelay = false;
/** Whether the sorting is managed by the view versus the model. */
private boolean inViewSorting = true;
/** The last column to be locally sorted. This allows the user to reverse sort a column. */
private int lastSortedColumnIndex = -1;
/** Whether the last sorted column was reverse sorted. */
private boolean isSortReversed = false;
/** Fixes a bug in the table where by a double click event gets fired twice. */
private int lastDoubleClickTime = 0;
/** The tree item whose children we are waiting for. This will be non-null when sending a request for children. While this is non-null the tree will be disabled. */
private TreeItem waitingItem = null;
/** Used along with waitingItem to retain information on which control had the focus at the time a tree node is opened and the server is told to load the data. */
private Control focusControl = null;
/** Stores some data about the state of the component prior to rebuilding the component. This is normally null. */
private StateData stateData = null;
/** Sets whether or not the opening of a node causes the selection even to occur for that row. */
private boolean selectOnOpen = false;
/** The collection of visible columns ordered by their server side zero based index. */
private IList columns = new LiteList(20, 10);
/** The set of currently visible tree items. */
private LiteHashSet currentlyVisibleTreeItems = null;
/** Allows us to ignore resize events while creating items during initialization. */
private boolean suspendResizeEvents = false;
/** Allows us to ignore tree table editor updates while adding, removing, or moving items. */
private boolean suspendUpdateTreeEditors = false;
/** Allows us to ignore resize events for the column while resizing the columns. */
private boolean suspendColumnResizeEvents = false;
/** The desired row height. This may be set to a positive number if the measure item event need be used to set the row height. */
private int rowHeightValue = -1;
/** Whether resizeable columns will be called upon to fit the control's client space when the control resizes or a column resizes. */
private boolean autoFit = false;
/** Whether resizeable columns will be streched to fill available space during the control's initialization. */
private boolean fillOnInitialize = false;
/** Whether resizeable columns will be streched to fill available space after the control is resized. */
private boolean fillOnResize = false;
/** The last known width for the control's client area. This is used to avoid filling or fitting the columns unless the width of the control is altered. */
private int lastControlWidth = 0;
/** The minimum tree editor height for all tested tree editors. */
private int minimumTreeEditorHeight = -1;
/**
* Encapsulates the reusable parts of a tree node.
*/
protected class SimpleNodeData extends NodeData {
/** The mapping of cell components for each column for this row. This may be null if no cell components have been defined. */
public CellComponent[] cellComponents = null;
/**
* SimpleNodeData constructor.
* @param objectId The identifier used to bind this node with the one on the server.
* @param hiddenDataCount The count of hidden data columns defined for the component.
*/
public SimpleNodeData(int objectId, int hiddenDataCount) {
super(objectId, hiddenDataCount);
}//SimpleNodeData()//
/**
* Registers the row object with the cell component for a given column.
* @param columnData The column with which to register.
* @param cellComponentId The component id for the cell. A value of -1 indicates that there is no component associated with the cell.
*/
public void registerCellComponent(ColumnData columnData, int cellComponentId) {
if(cellComponents == null) {
cellComponents = new CellComponent[getColumnCount()];
}//if//
if(cellComponents[columnData.serverColumnIndex] != null) {
cellComponents[columnData.serverColumnIndex].unregister(this);
}//if//
//Store the cell component id at the server index for the column.//
cellComponents[columnData.serverColumnIndex] = cellComponentId != -1 ? (CellComponent) getComponent(cellComponentId) : null;
if(cellComponents[columnData.serverColumnIndex] != null) {
cellComponents[columnData.serverColumnIndex].register(this);
}//if//
}//registerCellComponent()//
/**
* Unregisters the row object with the cell component for a given column.
* @param columnData The column with which to unregister.
*/
public void unregisterCellComponent(ColumnData columnData) {
if(cellComponents != null) {
registerCellComponent(columnData, -1);
}//if//
}//unregisterCellComponent()//
/**
* Unregisters the row object with the cell component for all columns.
*/
public void unregisterCellComponents() {
if(cellComponents != null) {
for(int index = 0; index < cellComponents.length; index++) {
if(cellComponents[index] != null) {
cellComponents[index].unregister(this);
cellComponents[index] = null;
}//if//
}//for//
}//if//
}//unregisterCellComponents()//
/**
* Gets the cell component for a given column and this row.
* @param columnData The column data representing the column.
* @return The cell component used to render the cell, or null if none is given.
*/
public CellComponent getCellComponent(ColumnData columnData) {
CellComponent result = null;
if(cellComponents != null) {
result = cellComponents[columnData.serverColumnIndex];
}//if//
return result;
}//getCellComponent()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent.NodeData#dispose()
*/
public void dispose() {
IList controlItems = getDisplayNodes();
//TODO: Is it necessary to unregister with the cell components?
if(controlItems != null) {
for(int index = 0, length = controlItems.getSize(); index < length; index++) {
((TreeItem) controlItems.get(index)).dispose();
}//for//
}//if//
super.dispose();
}//dispose()//
}//SimpleNodeData//
/**
* Encapsulates information about an expanded node in the tree.
*/
protected static class StateData {
/** A (potentially null) list of StateExpansionData instances, one for each root node which was expanded. */
private IList nodes = null;
/** The top item's identifying data. */
private String[] topItemPath = null;
public StateData() {
}//StateData()//
public void addNode(StateExpansionData node) {
if(nodes == null) {
nodes = new LiteList(node);
}//if//
else {
nodes.add(node);
}//else//
}//addNode()//
public IList getNodes() {
return nodes;
}//getNodes()//
public String[] getTopItemPath() {
return topItemPath;
}//getTopItemPath()//
public void setTopItemPath(String[] topItemPath) {
this.topItemPath = topItemPath;
}//setTopItemPath()//
//The following methods were placed here to keep them out of the general population of methods in the SimpleTreeTable class (since they are all related to the state data anyhow).//
/**
* Recursively collects the expansion data for a tree item.
* @param parentData The parent expansion data (to which the children will be added).
* @param parentItem The parent item whose children will be examined to see if they are expanded.
*/
private static void collectChildExpansionData(StateExpansionData parentData, TreeItem parentItem) {
TreeItem[] items = parentItem.getItems();
for(int index = 0; index < items.length; index++) {
if(items[index].getExpanded()) {
StateExpansionData data = new StateExpansionData(items[index].getText());
parentData.addChild(data);
collectChildExpansionData(data, items[index]);
}//if//
}//for//
}//collectChildExpansionData()//
/**
* Finds the first matching item in the tree
* @param items The tree items to start searching in.
* @param itemIdentifier The identifier text used to find the old top item in the new set of items.
* @param depth The depth of the search.
* @return Whether the top item was found.
*/
private static TreeItem findItem(TreeItem[] items, String[] itemIdentifier, int depth) {
TreeItem result = null;
//Search the items for a match at the current depth.//
if(items != null) {
for(int index = 0; (result == null) && (index < items.length); index++) {
String itemText = items[index].getText();
if(Comparator.getStringComparator().compare(itemText, itemIdentifier[depth]) == Comparator.EQUAL) {
result = items[index];
}//if//
}//for//
}//if//
//If we could find a match, then search its children for a match if necessary.//
if((result != null) && (depth + 1 < itemIdentifier.length)) {
TreeItem child = findItem(result.getItems(), itemIdentifier, depth + 1);
//If we couldn't find a matching child, then use the parent.//
if(child != null) {
result = child;
}//if//
}//if//
return result;
}//restoreTopItem()//
/**
* Recursively expands previously expanded nodes using the list of expanded nodes and the parent tree item.
* @param items The tree items to start searching in.
* @param stateExpansionData The collection of StateExpansionData instances which each represent a previously expanded node at this level of the tree.
*/
private static void expandItems(TreeItem[] items, IList stateExpansionData) {
if(stateExpansionData != null) {
IIterator iterator = stateExpansionData.iterator();
while(iterator.hasNext()) {
StateExpansionData data = (StateExpansionData) iterator.next();
TreeItem item = expandItem(items, data);
if((item != null) && (data.getChildren() != null)) {
expandItems(item.getItems(), data.getChildren());
}//if//
}//while//
}//if//
}//expandItems()//
/**
* Expands the tree item that is represented by the given data.
* @param items The tree items to start searching in.
* @param data The expansion state data representing the previously expanded node.
* @return The tree item that was expanded because it matched the expansion data, or null if no matching node was found.
*/
private static TreeItem expandItem(TreeItem[] items, StateExpansionData data) {
TreeItem item = null;
if(items != null) {
for(int index = 0; (item == null) && (index < items.length); index++) {
String itemText = items[index].getText();
if(Comparator.getStringComparator().compare(itemText, data.getIdentifier()) == Comparator.EQUAL) {
item = items[index];
item.setExpanded(true);
}//if//
}//for//
}//if//
return item;
}//expandItem()//
}//StateData//
/**
* Encapsulates information about an expanded node in the tree.
*/
protected static class StateExpansionData {
/** The identifier which will be used to match this node when the view is rebuilt. This is not a perfect solution, but works 99% of the time. */
private String identifier = null;
/** A (potentially null) list of child StateExpansionData instances, one for each child of this node which was expanded. */
private IList children = null;
public StateExpansionData(String identifier) {
this.identifier = identifier;
}//StateExpansionData()//
public void addChild(StateExpansionData child) {
if(children == null) {
children = new LiteList(child);
}//if//
else {
children.add(child);
}//else//
}//addChild()//
public String getIdentifier() {
return identifier;
}//getIdentifier()//
public IList getChildren() {
return children;
}//getChildren()//
}//StateExpansionData//
public static class ColumnMultiResourceHolder extends MultiResourceHolder {
private ColumnData column;
/**
* ColumnMultiResourceHolder constructor.
* @param component The component that will be notified when the resource value changes.
* @param column The column defining the holder.
*/
public ColumnMultiResourceHolder(IAbstractClientViewComponent component, ColumnData column) {
super(component);
this.column = column;
}//ColumnMultiResourceHolder()//
/**
* Gets the column the holder is a member of.
* @return The column defining the holder.
*/
public ColumnData getColumn() {
return column;
}//getColumn()//
}//ColumnMultiResourceHolder//
public static class ColumnResourceHolder extends ResourceHolder {
private ColumnData column;
/**
* ColumnMultiResourceHolder constructor.
* @param component The component that will be notified when the resource value changes.
* @param column The column defining the holder.
*/
public ColumnResourceHolder(IAbstractClientViewComponent component, ColumnData column) {
super(component);
this.column = column;
}//ColumnMultiResourceHolder()//
/**
* Gets the column the holder is a member of.
* @return The column defining the holder.
*/
public ColumnData getColumn() {
return column;
}//getColumn()//
}//ColumnResourceHolder//
/**
* Metadata and functionality attached to the column.
*/
protected class ColumnData {
/** A holder for the value of the cell background color. */
private ColumnMultiResourceHolder backgroundColorHolder = new ColumnMultiResourceHolder(SimpleTreeTable.this, this);
/** A holder for the value of the cell foreground color. */
private ColumnMultiResourceHolder foregroundColorHolder = new ColumnMultiResourceHolder(SimpleTreeTable.this, this);
/** A holder for the value of the cell foreground color. */
private ColumnMultiResourceHolder fontHolder = new ColumnMultiResourceHolder(SimpleTreeTable.this, this);
/** A holder for the value of the cell image. */
private ColumnMultiResourceHolder cellImageHolder = new ColumnMultiResourceHolder(SimpleTreeTable.this, this);
/** A holder for the value of the column's tool tip text. */
private ColumnResourceHolder toolTipTextHolder = new ColumnResourceHolder(SimpleTreeTable.this, this);
/** A holder for the value of the column's header text. */
private ColumnResourceHolder headerTextHolder = new ColumnResourceHolder(SimpleTreeTable.this, this);
/** A holder for the value of the column's header image. */
private ColumnResourceHolder headerImageHolder = new ColumnResourceHolder(SimpleTreeTable.this, this);
/** The server side column index for this column. This will never change once the column is initialized. */
private int serverColumnIndex = 0;
/** The table column being served. */
private TreeColumn column;
/** The set of TreeEditor instances mapped by the TreeItem they are serving. This is used primarily to release the editors when the are out of view. */
private LiteHashMap editorByTreeItem = new LiteHashMap(50);
/** The minimum width of the column. */
private int minimumWidth = 20;
public ColumnData(TreeColumn column, int serverColumnIndex) {
this.column = column;
this.serverColumnIndex = serverColumnIndex;
}//ColumnData()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent.ColumnData#dispose()
*/
public void dispose() {
IIterator iterator = editorByTreeItem.valueIterator();
backgroundColorHolder.release();
foregroundColorHolder.release();
fontHolder.release();
cellImageHolder.release();
toolTipTextHolder.release();
headerTextHolder.release();
headerImageHolder.release();
if(column != null && !column.isDisposed()) {
column.dispose();
}//if//
while(iterator.hasNext()) {
TableEditor editor = (TableEditor) iterator.next();
editor.dispose();
//TODO: Dispose the control? We should actually be removing the column from the row object since it tracks the array of cell components, one per column.
}//while//
}//dispose()//
/**
* Forces the renderers to layout or reposition.
*/
public void layoutRenderers() {
IIterator iterator = editorByTreeItem.valueIterator();
while(iterator.hasNext()) {
((TreeEditor) iterator.next()).layout();
}//while//
}//layoutRenderers()//
/**
* Gets the tree editor associated with the given row object.
* @param treeItem The tree item whose editor should be retrieved.
* @return The editor, or null if the row does not have an editor for this column.
*/
public TreeEditor getTreeEditor(TreeItem treeItem) {
return (TreeEditor) editorByTreeItem.get(treeItem);
}//getTreeEditor()//
/**
* Sets the tree editor associated with the given row object.
* @param treeItem The tree item whose editor should be set.
* @param treeEditor The editor, or null if the row does not have an editor for this column.
*/
public void setTreeEditor(TreeItem treeItem, TreeEditor treeEditor) {
editorByTreeItem.put(treeItem, treeEditor);
}//setTreeEditor()//
/**
* Removes and returns the tree editor associated with the given row object.
* @param treeItem The tree item whose editor should be removed.
* @return The editor, or null if the row does not have an editor for this column.
*/
public TreeEditor removeTreeEditor(TreeItem treeItem) {
return (TreeEditor) editorByTreeItem.remove(treeItem);
}//getTreeEditor()//
/**
* Gets the column's minimum width.
* @return The minimum number of pixels of width for the column.
*/
public int getMinimumWidth() {
return minimumWidth;
}//getMinimumWidth()//
/**
* Sets the column's minimum width.
* @param width The minimum number of pixels of width for the column.
*/
public void setMinimumWidth(int minimumWidth) {
if(this.minimumWidth != minimumWidth) {
this.minimumWidth = minimumWidth;
if(column.getWidth() < minimumWidth) {
column.setWidth(minimumWidth);
}//if//
}//if//
}//setMinimumWidth()//
/**
* Sets the tool tip text which can either be a resource reference or a string.
* @param toolTipText The tool tip.
*/
public void setToolTipText(Object toolTipText) {
toolTipTextHolder.setValue(toolTipText);
}//setToolTipText()//
/**
* Gets the index of this column.
* @return The column's zero based index.
*/
public int getColumnIndex() {
int result = -1;
for(int index = 0; (result == -1) && (index < getSwtTree().getColumnCount()); index++) {
if(getSwtTree().getColumn(index) == column) {
result = index;
}//if//
}//for//
return result;
}//getColumnIndex()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.AbstractComponent#internalResourceHolderChanged(com.foundation.tcv.client.view.MultiResourceHolder, com.common.util.IHashSet, java.lang.Object, java.lang.Object)
*/
protected void internalResourceHolderChanged(MultiResourceHolder resourceHolder, IHashSet rows, Object oldValue, Object newValue) {
int columnIndex = getColumnIndex();
if(resourceHolder == cellImageHolder) {
IIterator iterator = rows.iterator();
while(iterator.hasNext()) {
SimpleNodeData rowObject = (SimpleNodeData) iterator.next();
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyImage((JefImage) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setImage(columnIndex, createImage((JefImage) newValue));
}//for//
}//if//
}//while//
}//if//
else if(resourceHolder == backgroundColorHolder) {
IIterator iterator = rows.iterator();
while(iterator.hasNext()) {
SimpleNodeData rowObject = (SimpleNodeData) iterator.next();
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setBackground(columnIndex, createColor((JefColor) newValue));
}//for//
}//if//
}//while//
}//else if//
else if(resourceHolder == foregroundColorHolder) {
IIterator iterator = rows.iterator();
while(iterator.hasNext()) {
SimpleNodeData rowObject = (SimpleNodeData) iterator.next();
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setForeground(columnIndex, createColor((JefColor) newValue));
}//for//
}//if//
}//while//
}//else if//
else if(resourceHolder == fontHolder) {
IIterator iterator = rows.iterator();
while(iterator.hasNext()) {
SimpleNodeData rowObject = (SimpleNodeData) iterator.next();
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyFont((JefFont[]) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setFont(columnIndex, createFont((JefFont[]) newValue));
}//for//
}//if//
}//while//
}//else if//
else {
Debug.log("Error: Unhandled resource holder change event found. ResourceHolder: " + resourceHolder);
}//else//
}//internalResourceHolderChanged()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.AbstractComponent#internalResourceHolderChanged(com.foundation.tcv.client.view.MultiResourceHolder, java.lang.Object, java.lang.Object, java.lang.Object)
*/
protected void internalResourceHolderChanged(MultiResourceHolder resourceHolder, Object row, Object oldValue, Object newValue) {
int columnIndex = getColumnIndex();
if(resourceHolder == cellImageHolder) {
SimpleNodeData rowObject = (SimpleNodeData) row;
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyImage((JefImage) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setImage(columnIndex, createImage((JefImage) newValue));
}//for//
}//if//
}//if//
else if(resourceHolder == backgroundColorHolder) {
SimpleNodeData rowObject = (SimpleNodeData) row;
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setBackground(columnIndex, createColor((JefColor) newValue));
}//for//
}//if//
}//else if//
else if(resourceHolder == foregroundColorHolder) {
SimpleNodeData rowObject = (SimpleNodeData) row;
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setForeground(columnIndex, createColor((JefColor) newValue));
}//for//
}//if//
}//else if//
else if(resourceHolder == fontHolder) {
SimpleNodeData rowObject = (SimpleNodeData) row;
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyFont((JefFont[]) oldValue);
((TableItem) rowObject.getDisplayNodes().get(index)).setFont(columnIndex, createFont((JefFont[]) newValue));
}//for//
}//if//
}//else if//
else {
Debug.log("Error: Unhandled resource holder change event found. ResourceHolder: " + resourceHolder);
}//else//
}//internalResourceHolderChanged()//
/**
* Called by a resource holder when the held value changes either due to a new value being set, or due to a resource being changed.
* @param resourceHolder The resource holder that is sending the notification.
* @param oldValue The old value for the resource.
* @param newValue The new value for the resource.
*/
protected void internalResourceHolderChanged(ResourceHolder resourceHolder, Object oldValue, Object newValue, int flags) {
if(resourceHolder == toolTipTextHolder) {
column.setToolTipText((String) toolTipTextHolder.getValue());
}//if//
else if(resourceHolder == headerTextHolder) {
column.setText((String) newValue);
}//else if//
else if(resourceHolder == headerImageHolder) {
destroyImage((JefImage) oldValue);
column.setImage(createImage((JefImage) newValue));
}//else if//
else {
Debug.log("Error: Unhandled resource holder change event found. ResourceHolder: " + resourceHolder);
}//else//
}//internalResourceHolderChanged()//
}//ColumnData//
/**
* SimpleTable constructor.
*/
public SimpleTreeTable() {
super();
}//SimpleTreeTable()//
/**
* Gets the column with the given index.
* @param serverColumnIndex The server side zero based index for the desired column.
* @return The column data representing the column.
*/
protected ColumnData getColumn(int serverColumnIndex) {
return (ColumnData) columns.get(serverColumnIndex);
}//getColumn()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#getColumnCount()
*/
protected int getColumnCount() {
return getSwtTree().getColumnCount();
}//getColumnCount()//
/**
* Gets the SWT tree that represents this simple tree table.
* @return The SWT tree providing visualization for this simple tree table.
*/
public org.eclipse.swt.widgets.Tree getSwtTree() {
return (org.eclipse.swt.widgets.Tree) getSwtWidget();
}//getSwtTree()//
/* (non-Javadoc)
* @see com.foundation.view.IViewComponent#viewInitialize()
*/
protected void internalViewInitialize() {
getSwtTree().addSelectionListener(this);
getSwtTree().addTreeListener(this);
getSwtTree().addControlListener(this);
getSwtTree().getVerticalBar().addSelectionListener(this);
getSwtTree().addListener(SWT.Resize, new Listener() {
public void handleEvent(Event event) {
if(getAutoFit()) {
int controlWidth = getSwtTree().getClientArea().width;
if(controlWidth != lastControlWidth) {
lastControlWidth = controlWidth;
fit();
}//if//
}//if//
//Debug.log("Widthx: " + getSwtTree().getClientArea().width);
}//handleEvent()//
});
getSwtTree().addListener(SWT.MeasureItem, new Listener() {
boolean isFirstCall = true;
public void handleEvent(Event event) {
switch(event.type) {
case SWT.MeasureItem: {
if(rowHeightValue > 0) {
event.height = rowHeightValue;
}//if//
else {
TreeItem item = (TreeItem) event.item;
//TableRowObject rowObject = (TableRowObject) item.getData();
ColumnData column = (ColumnData) getColumn(event.index);
TreeEditor editor = column.getTreeEditor(item);
if(minimumTreeEditorHeight > event.height) {
event.height = minimumTreeEditorHeight;
}//if//
//Note: minimumTreeEditorHeight should suffice, but are we properly updating it as the editors change content?
//TODO: Cache the editor's control's preferred height? It is unlikely to change (but I suppose it is possible for things like multi line text controls without scroll bars?).
if(editor != null) {
if(event.height < editor.minimumHeight) {
event.height = editor.minimumHeight;
}//if//
Control control = editor.getEditor();
Point point = control.computeSize(event.width, event.height);
if(point.y > event.height) {
event.height = point.y;
}//if//
}//if//
}//else//
//TODO: This code should not be necessary. It forces the repaint of the control since we are having a problem with the widgets not being the right size or position when first rendered.
if(isFirstCall) {
isFirstCall = false;
getSwtTree().getDisplay().asyncExec(new Runnable() {
public void run() {
// SwtUtilities.setRedraw(getSwtTree(), true);
// getSwtTree().redraw();
layoutRenderers();
}//run()//
});
}//if//
break;
}//case//
}//switch//
}//handleEvent()//
});
getSwtTree().addListener(SWT.PaintItem, new Listener() {
public void handleEvent(Event event) {
SimpleNodeData rowObject = (SimpleNodeData) ((TreeItem) event.item).getData();
TreeColumn column = getSwtTree().getColumn(event.index);
ColumnData columnData = (ColumnData) column.getData();
CellComponent cellComponent = rowObject.getCellComponent(columnData);
//Only paint the cell if there is a cell component and it supports painting the cell.//
if((cellComponent != null) && (cellComponent.supportsPaint()) && (cellComponent.usePaint())) {
GC gc = event.gc;
Rectangle rectangle = gc.getClipping();
//Set the clip area so the component knows where to draw.//
gc.setClipping(event.x, event.y, column.getWidth(), event.height);
//Send GC to the column's cell component.//
cellComponent.paintCell(rowObject, gc);
//Reset the clipping area.//
gc.setClipping(rectangle);
}//if//
}//handleEvent()//
});
super.internalViewInitialize();
if(getAutoFit()) {
fit();
}//if//
else if(getFillOnInitialize()) {
fill();
}//else if//
}//internalViewInitialize()//
/* (non-Javadoc)
* @see com.foundation.view.IViewComponent#viewRelease()
*/
protected void internalViewRelease() {
if(!getSwtTree().isDisposed()) {
getSwtTree().removeControlListener(this);
getSwtTree().removeSelectionListener(this);
getSwtTree().removeTreeListener(this);
getSwtTree().getVerticalBar().removeSelectionListener(this);
}//if//
rowBackgroundColorHolder.release();
rowForegroundColorHolder.release();
rowFontHolder.release();
for(int index = 0; index < columns.getSize(); index++) {
((ColumnData) columns.get(index)).dispose();
}//for//
super.internalViewRelease();
}//internalViewRelease()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.AbstractComponent#internalViewSynchronize()
*/
protected void internalViewSynchronize() {
super.internalViewSynchronize();
}//internalViewSynchronize()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.Component#internalOnLinkInvoked(int, java.lang.Object)
*/
protected void internalOnLinkInvoked(int linkTarget, Object data) {
switch(linkTarget) {
case LINK_TARGET_FILL: {
lastControlWidth = getSwtTree().getClientArea().width;
fill();
break;
}//case//
case LINK_TARGET_FIT: {
lastControlWidth = getSwtTree().getClientArea().width;
fit();
break;
}//case//
default: {
super.internalOnLinkInvoked(linkTarget, data);
break;
}//default//
}//switch//
}//internalOnLinkInvoked()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.AbstractComponent#internalResourceHolderChanged(com.foundation.tcv.client.view.MultiResourceHolder, com.common.util.IHashSet, java.lang.Object, java.lang.Object)
*/
protected void internalResourceHolderChanged(MultiResourceHolder resourceHolder, IHashSet rows, Object oldValue, Object newValue) {
if(resourceHolder instanceof ColumnMultiResourceHolder) {
ColumnData column = ((ColumnMultiResourceHolder) resourceHolder).column;
column.internalResourceHolderChanged(resourceHolder, rows, oldValue, newValue);
}//if//
else if(resourceHolder == rowBackgroundColorHolder) {
IIterator iterator = rows.iterator();
while(iterator.hasNext()) {
SimpleNodeData rowObject = (SimpleNodeData) iterator.next();
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TreeItem) rowObject.getDisplayNodes().get(index)).setBackground(createColor((JefColor) newValue));
}//for//
}//if//
}//while//
}//else if//
else if(resourceHolder == rowForegroundColorHolder) {
IIterator iterator = rows.iterator();
while(iterator.hasNext()) {
SimpleNodeData rowObject = (SimpleNodeData) iterator.next();
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TreeItem) rowObject.getDisplayNodes().get(index)).setForeground(createColor((JefColor) newValue));
}//for//
}//if//
}//while//
}//else if//
else if(resourceHolder == rowFontHolder) {
IIterator iterator = rows.iterator();
while(iterator.hasNext()) {
SimpleNodeData rowObject = (SimpleNodeData) iterator.next();
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyFont((JefFont[]) oldValue);
((TreeItem) rowObject.getDisplayNodes().get(index)).setFont(createFont((JefFont[]) newValue));
}//for//
}//if//
}//while//
}//else if//
else {
super.internalResourceHolderChanged(resourceHolder, rows, oldValue, newValue);
}//else//
}//internalResourceHolderChanged()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.AbstractComponent#internalResourceHolderChanged(com.foundation.tcv.client.view.MultiResourceHolder, java.lang.Object, java.lang.Object, java.lang.Object)
*/
protected void internalResourceHolderChanged(MultiResourceHolder resourceHolder, Object row, Object oldValue, Object newValue) {
if(resourceHolder instanceof ColumnMultiResourceHolder) {
ColumnData column = ((ColumnMultiResourceHolder) resourceHolder).column;
column.internalResourceHolderChanged(resourceHolder, row, oldValue, newValue);
}//if//
else if(resourceHolder == rowBackgroundColorHolder) {
SimpleNodeData rowObject = (SimpleNodeData) row;
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TreeItem) rowObject.getDisplayNodes().get(index)).setBackground(createColor((JefColor) newValue));
}//for//
}//if//
}//else if//
else if(resourceHolder == rowForegroundColorHolder) {
SimpleNodeData rowObject = (SimpleNodeData) row;
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyColor((JefColor) oldValue);
((TreeItem) rowObject.getDisplayNodes().get(index)).setForeground(createColor((JefColor) newValue));
}//for//
}//if//
}//else if//
else if(resourceHolder == rowFontHolder) {
SimpleNodeData rowObject = (SimpleNodeData) row;
if(rowObject.getDisplayNodes() != null) {
for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) {
destroyFont((JefFont[]) oldValue);
((TreeItem) rowObject.getDisplayNodes().get(index)).setFont(createFont((JefFont[]) newValue));
}//for//
}//if//
}//else if//
else {
super.internalResourceHolderChanged(resourceHolder, row, oldValue, newValue);
}//else//
}//internalResourceHolderChanged()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.Component#internalResourceHolderChanged(com.foundation.tcv.client.view.ResourceHolder, java.lang.Object, java.lang.Object)
*/
protected void internalResourceHolderChanged(ResourceHolder resourceHolder, Object oldValue, Object newValue, int flags) {
if(resourceHolder instanceof ColumnResourceHolder) {
ColumnData column = ((ColumnResourceHolder) resourceHolder).column;
column.internalResourceHolderChanged(resourceHolder, oldValue, newValue, flags);
}//if//
else {
super.internalResourceHolderChanged(resourceHolder, oldValue, newValue, flags);
}//else//
}//internalResourceHolderChanged()//
/**
* Fills the available space by expanding all resizeable columns.
*/
protected void fill() {
int width = lastControlWidth;
TreeColumn[] columns = getSwtTree().getColumns();
int totalColumnWidth = 0;
int increaseWidth = 0;
int extraIncreaseWidth = 0;
int resizeableColumnCount = 0;
for(int index = 0; index < columns.length; index++) {
totalColumnWidth += columns[index].getWidth();
if(columns[index].getResizable()) {
resizeableColumnCount++;
}//if//
}//for//
if(width > totalColumnWidth) {
if(resizeableColumnCount > 0) {
SwtUtilities.setRedraw(getSwtTree(), false);
suspendColumnResizeEvents = true;
try {
increaseWidth = (int) Math.floor((width - totalColumnWidth) / (double) resizeableColumnCount);
extraIncreaseWidth = (width - totalColumnWidth) % resizeableColumnCount;
for(int index = 0; index < columns.length; index++) {
if(columns[index].getResizable()) {
int newWidth = columns[index].getWidth() + increaseWidth;
//Add one to the new width for the extra pixels.//
if(resizeableColumnCount <= extraIncreaseWidth) {
newWidth++;
}//if//
//Set the new width and decrement the resizeable column count so we track how many columns to add the extra pixel to.//
columns[index].setWidth(newWidth);
resizeableColumnCount--;
}//if//
}//for//
}//try//
finally {
suspendColumnResizeEvents = false;
SwtUtilities.setRedraw(getSwtTree(), true);
getSwtTree().redraw();
}//finally//
}//if//
}//if//
}//fill()//
/**
* Fits the resizeable columns to fill all available table space without needing a horizontal scroll bar.
*/
protected void fit() {
fit(null);
}//fit()//
/**
* Fits the resizeable columns to fill all available table space without needing a horizontal scroll bar.
* @param ignoredColumn The column to be ignored in the fit operation. Note that there must be at least 2 resizeable columns for this algorithm to work, and no column should be resized less than the minimum size.
*/
protected void fit(TreeColumn ignoredColumn) {
TreeColumn[] columns = getSwtTree().getColumns();
int extraWidth = 0;
LiteList resizeableColumns = new LiteList(columns.length);
int totalMinimumWidth = 0;
int currentColumnWidthTotal = 0;
int fixedWidth = 0;
int controlWidth = lastControlWidth;
int availableWidth = controlWidth - extraWidth;
if(lastControlWidth == 0) {
lastControlWidth = getSwtControl().getBounds().width;
controlWidth = lastControlWidth;
availableWidth = controlWidth - extraWidth;
}//if//
for(int index = 0; index < columns.length; index++) {
if((columns[index] != ignoredColumn) && (columns[index].getResizable())) {
resizeableColumns.add(columns[index]);
totalMinimumWidth += ((ColumnData) columns[index].getData()).getMinimumWidth();
}//if//
else {
if(!columns[index].getResizable()) {
fixedWidth += columns[index].getWidth();
}//if//
availableWidth -= columns[index].getWidth();
}//else//
currentColumnWidthTotal += columns[index].getWidth();
}//for//
//The width of the control which can be divied up between the resizable columns.//
availableWidth -= totalMinimumWidth;
//Detect formatting errors that cannot be worked around.//
if((controlWidth > 0) && (resizeableColumns.getSize() > 0) && (availableWidth > 0) && (currentColumnWidthTotal != controlWidth)) {
int[] columnSizes = new int[resizeableColumns.getSize()];
int totalColumnSizes = 0;
int newTotalColumnSizes = 0;
//Record the previous sizes (minus the minimum width to make the equation simpler).//
for(int index = 0; index < resizeableColumns.getSize(); index++) {
TreeColumn next = (TreeColumn) resizeableColumns.get(index);
totalColumnSizes += (columnSizes[index] = (next.getWidth() - ((ColumnData) next.getData()).getMinimumWidth()));
}//for//
//Resize the columns relative to their previous sizes.//
for(int index = 0; index < columnSizes.length; index++) {
TreeColumn next = (TreeColumn) resizeableColumns.get(index);
newTotalColumnSizes += (columnSizes[index] = ((int) Math.round((columnSizes[index] / (double) totalColumnSizes) * availableWidth)) + ((ColumnData) next.getData()).getMinimumWidth());
}//for//
availableWidth += totalMinimumWidth;
//Debug.log("New Total Column Sizes: " + newTotalColumnSizes + " Control Width - Fixed Width: " + availableWidth);
//Adjust the column sizes to take up the exact right amount of space.//
for(int index = 0; (index < columnSizes.length) && (newTotalColumnSizes != availableWidth); index++) {
if(newTotalColumnSizes < availableWidth) {
//Debug.log("Adding");
columnSizes[index]++;
newTotalColumnSizes++;
}//if//
else {
//Debug.log("Removing");
columnSizes[index]--;
newTotalColumnSizes--;
}//else//
}//for//
SwtUtilities.setRedraw(getSwtTree(), false);
suspendColumnResizeEvents = true;
try {
for(int index = 0; index < resizeableColumns.getSize(); index++) {
((TreeColumn) resizeableColumns.get(index)).setWidth(columnSizes[index]);
}//for//
}//try//
finally {
suspendColumnResizeEvents = false;
SwtUtilities.setRedraw(getSwtTree(), true);
getSwtTree().redraw();
}//finally//
}//if//
else if((availableWidth < 0) && (ignoredColumn != null)) {
int minimumWidth = ((ColumnData) ignoredColumn.getData()).getMinimumWidth();
SwtUtilities.setRedraw(getSwtTree(), false);
suspendColumnResizeEvents = true;
try {
//Ensure all columns that can be resized are set to their minimum width.//
for(int index = 0; index < resizeableColumns.getSize(); index++) {
TreeColumn column = (TreeColumn) resizeableColumns.get(index);
column.setWidth(((ColumnData) column.getData()).getMinimumWidth());
}//for//
//Adjust the ignored column to either its minimum width, or to fill available space.//
if((ignoredColumn.getWidth() - minimumWidth + availableWidth) > 0) {
ignoredColumn.setWidth(controlWidth - extraWidth - fixedWidth - totalMinimumWidth);
}//if//
else {
ignoredColumn.setWidth(minimumWidth);
}//else//
}//try//
finally {
suspendColumnResizeEvents = false;
SwtUtilities.setRedraw(getSwtTree(), true);
getSwtTree().redraw();
}//finally//
}//else if//
//Test Code//
//int newColumnWidthTotal = 0;
//for(int index = 0; index < columns.length; index++) {
// newColumnWidthTotal += columns[index].getWidth();
//}//for//
//Debug.log("Total column width: " + newColumnWidthTotal + " Control client width: " + getSwtTree().getClientArea().width + "\n");
}//fit()//
/**
* Gets the index of the column.
* @param column The column whose index is to be determined.
* @return The index of the column, or -1 if the column is not displayed by the tree.
*/
private int getColumnIndex(TreeColumn column) {
int result = -1;
TreeColumn[] columns = getSwtTree().getColumns();
for(int index = 0; (result == -1) && (index < columns.length); index++) {
if(columns[index] == column) {
result = index;
}//if//
}//for//
return result;
}//getColumnIndex()//
/**
* Called when a column is added to the tree table.
* @param column The column added.
* @param columnIndex The initial index for the column. This will not change on the server, but may change on the client, so to preserve a mapping we must record it.
*/
protected void internalColumnAdded(final TreeColumn column, int columnIndex) {
ColumnData columnData = new ColumnData(column, columnIndex);
//Add the column data to the columns list and update the server column indices.//
if(columnIndex >= 0) {
columnData.serverColumnIndex = columnIndex;
columns.add(columnIndex, columnData);
for(int index = columnIndex + 1; index < columns.getSize(); index++) {
((ColumnData) columns.get(index)).serverColumnIndex++;
}//for//
}//if//
else {
columnData.serverColumnIndex = columns.getSize();
columns.add(columnData);
}//else//
//Set the initial width.//
column.setWidth(100);
//TODO: Make the moveable feature be configurable.
column.setMoveable(true);
//Add a listener to resize the columns to fit the space if we are performing auto-fit.//
column.addControlListener(new ControlListener() {
public void controlResized(ControlEvent event) {
if(isInitialized() && !suspendColumnResizeEvents) {
if(((ColumnData) column.getData()).getMinimumWidth() > column.getWidth()) {
//Enforce the minimum width.//
column.setWidth(((ColumnData) column.getData()).getMinimumWidth());
}//if//
else if(autoFit) {
fit(column);
}//else if//
}//if//
}//controlResized()//
public void controlMoved(ControlEvent event) {
}//controlMoved()//
});
//Add a sorting handler to the column.//
column.addSelectionListener(new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
}//widgetDefaultSelected()//
public void widgetSelected(SelectionEvent e) {
if((inViewSorting) && (getSwtTree().getColumnCount() > 1)) {
int columnCount = getSwtTree().getColumnCount();
int columnIndex = getColumnIndex(column);
TreeItem[] mapping = getSwtTree().getItems();
boolean reverse = isSortReversed = (columnIndex == lastSortedColumnIndex ? !isSortReversed : false);
if(getSwtTree().getItemCount() > 0) {
boolean done = false;
//Perform a bubble sort on the tree items.//
while(!done) {
String text1 = mapping[0].getText(columnIndex);
done = true;
for(int index = 1; index < mapping.length; index++) {
String text2 = mapping[index].getText(columnIndex);
if(requiresSwap(text1, text2, reverse)) {
TreeItem temp = mapping[index];
mapping[index] = mapping[index - 1];
mapping[index - 1] = temp;
done = false;
}//if//
text1 = text2;
}//for//
}//while//
SwtUtilities.setRedraw(getSwtComposite(), false);
try {
//Rebuild the tree items since we can't reuse them.//
for(int index = 0; index < mapping.length; index++) {
TreeItem treeItem = (TreeItem) mapping[index];
TreeItem newTreeItem = new TreeItem(getSwtTree(), treeItem.getStyle(), index);
newTreeItem.setChecked(treeItem.getChecked());
newTreeItem.setGrayed(treeItem.getGrayed());
newTreeItem.setText(treeItem.getText());
newTreeItem.setImage(treeItem.getImage());
newTreeItem.setBackground(treeItem.getBackground());
newTreeItem.setForeground(treeItem.getForeground());
newTreeItem.setFont(treeItem.getFont());
newTreeItem.setData(treeItem.getData());
for(int column = 0; column < columnCount; column++) {
newTreeItem.setText(column, treeItem.getText(column));
newTreeItem.setImage(column, treeItem.getImage(column));
newTreeItem.setBackground(column, treeItem.getBackground(column));
newTreeItem.setForeground(column, treeItem.getForeground(column));
newTreeItem.setFont(column, treeItem.getFont(column));
}//for//
//The data can be null if this is a temporary standin for possible children.//
if(treeItem.getData() != null) {
//Update the mapping to/from the node data.//
((NodeData) treeItem.getData()).getDisplayNodes().replace(treeItem, newTreeItem);
}//if//
//Sort the children.//
sortChildren(newTreeItem, treeItem, columnIndex, columnCount, reverse);
//Dispose of the child.//
treeItem.dispose();
}//for//
}//try//
finally {
SwtUtilities.setRedraw(getSwtComposite(), true);
}//finally//
}//if//
//Save the last sorted column index so we know when to reverse the order of the sort.//
lastSortedColumnIndex = columnIndex;
getSwtTree().setSortColumn(column);
getSwtTree().setSortDirection(reverse ? SWT.UP : SWT.DOWN);
}//if//
}//widgetSelected()//
});
column.setData(columnData);
}//internalColumnAdded()//
/**
* Sorts the children of a tree item.
* @param newParent The new parent item which doesn't have children yet.
* @param oldParent The old parent item which may have unsorted children.
* @param columnIndex The index of the column we are sorting by.
* @param columnCount The number of columns in the control.
* @param reverse Whether the sort should be reversed.
*/
private void sortChildren(TreeItem newParent, TreeItem oldParent, int columnIndex, int columnCount, boolean reverse) {
if(oldParent.getItemCount() > 0) {
boolean done = false;
TreeItem[] mapping = oldParent.getItems();
//Perform a bubble sort on the table items.//
while(!done) {
String text1 = mapping[0].getText(columnIndex);
done = true;
for(int index = 1; index < mapping.length; index++) {
String text2 = mapping[index].getText(columnIndex);
int compare = text1.compareTo(text2);
if((reverse && (compare > 0)) || (!reverse && (compare < 0))) {
TreeItem temp = mapping[index];
mapping[index] = mapping[index - 1];
mapping[index - 1] = temp;
done = false;
}//if//
text1 = text2;
}//for//
}//while//
//Rebuild the tree items since we can't reuse them.//
for(int index = 0; index < mapping.length; index++) {
TreeItem treeItem = (TreeItem) mapping[index];
TreeItem newTreeItem = new TreeItem(newParent, treeItem.getStyle(), index);
newTreeItem.setChecked(treeItem.getChecked());
newTreeItem.setGrayed(treeItem.getGrayed());
newTreeItem.setText(treeItem.getText());
newTreeItem.setImage(treeItem.getImage());
newTreeItem.setBackground(treeItem.getBackground());
newTreeItem.setForeground(treeItem.getForeground());
newTreeItem.setFont(treeItem.getFont());
newTreeItem.setData(treeItem.getData());
for(int column = 0; column < columnCount; column++) {
newTreeItem.setText(column, treeItem.getText(column));
newTreeItem.setImage(column, treeItem.getImage(column));
newTreeItem.setBackground(column, treeItem.getBackground(column));
newTreeItem.setForeground(column, treeItem.getForeground(column));
newTreeItem.setFont(column, treeItem.getFont(column));
}//for//
//The data can be null if this is a temporary standin for possible children.//
if(treeItem.getData() != null) {
//Update the mapping to/from the node data.//
((NodeData) treeItem.getData()).getDisplayNodes().replace(treeItem, newTreeItem);
}//if//
//Sort the children.//
sortChildren(newTreeItem, treeItem, columnIndex, columnCount, reverse);
//Dispose of the child.//
treeItem.dispose();
}//for//
//Set the expanded state of the new parent node.//
newParent.setExpanded(oldParent.getExpanded());
}//if//
}//sortChildren()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.AbstractComponent#internalProcessMessage(com.foundation.tcv.model.ViewMessage)
*/
public Object internalProcessMessage(ViewMessage viewMessage) {
Object retVal = null;
switch(viewMessage.getMessageNumber()) {
case MESSAGE_INITIALIZE: {
if(getSwtControl() == null) {
int[] data = (int[]) viewMessage.getMessageData();
int style = data[1];
//Link to the parent container.//
setContainer((Container) getComponent(data[0]));
getContainer().addComponent(this);
//Create the SWT widget.//
setSwtWidget(new org.eclipse.swt.widgets.Tree(getContainer().getSwtParent(), style | SWT.FULL_SELECTION));
getSwtWidget().setData(this);
//Set the allows multiple selections flag.//
setAllowMultiSelection((style & SWT.MULTI) > 0);
//Make sure the headers are visible by default.//
getSwtTree().setHeaderVisible(true);
//If the container has already been initialized then force the parent to re-layout so that this component will appear.//
if(getContainer().isInitialized()) {
getContainer().getSwtComposite().layout(true, true);
}//if//
}//if//
break;
}//case//
case MESSAGE_SET_ROW_HEIGHT: {
Integer height = (Integer) viewMessage.getMessageData();
if(height != null) {
rowHeightValue = height.intValue();
}//if//
else {
rowHeightValue = -1;
}//else//
break;
}//case//
case MESSAGE_IN_VIEW_SORTING: {
//Warning: This is only settable when creating the control since once the user sorts things we don't have code to put the humpty dumpty back together again.//
inViewSorting = ((Boolean) viewMessage.getMessageData()).booleanValue();
break;
}//case//
case MESSAGE_SHOW_GRID_LINES: {
getSwtTree().setLinesVisible(((Boolean) viewMessage.getMessageData()).booleanValue());
break;
}//case//
case MESSAGE_SHOW_HEADERS: {
getSwtTree().setHeaderVisible(((Boolean) viewMessage.getMessageData()).booleanValue());
break;
}//case//
case MESSAGE_SET_HEADER_RESIZEABLE: {
int columnNumber = ((Integer) ((Object[]) viewMessage.getMessageData())[0]).intValue();
boolean isResizable = ((Boolean) ((Object[]) viewMessage.getMessageData())[1]).booleanValue();
getSwtTree().getColumn(columnNumber).setResizable(isResizable);
break;
}//case//
case MESSAGE_SET_COLUMN_ALIGNMENT: {
int columnNumber = ((int[]) viewMessage.getMessageData())[0];
int alignment = ((int[]) viewMessage.getMessageData())[1];
getSwtTree().getColumn(columnNumber).setAlignment(alignment == ALIGNMENT_RIGHT ? SWT.RIGHT : alignment == ALIGNMENT_CENTER ? SWT.CENTER : SWT.LEFT);
break;
}//case//
case MESSAGE_SET_COLUMN_WIDTH: {
int columnNumber = ((int[]) viewMessage.getMessageData())[0];
int width = ((int[]) viewMessage.getMessageData())[1];
getSwtTree().getColumn(columnNumber).setWidth(width);
break;
}//case//
case MESSAGE_SET_COLUMN_MINIMUM_WIDTH: {
int columnNumber = viewMessage.getMessageInteger();
int minimumWidth = viewMessage.getMessageSecondaryInteger();
getColumn(columnNumber).setMinimumWidth(minimumWidth);
break;
}//case//
case MESSAGE_SET_COLUMN_TOOL_TIP: {
int columnNumber = viewMessage.getMessageInteger();
Object toolTip = viewMessage.getMessageData();
getColumn(columnNumber).setToolTipText(toolTip);
break;
}//case//
case MESSAGE_SET_COLUMN_MOVEABLE: {
int columnNumber = ((Integer) ((Object[]) viewMessage.getMessageData())[0]).intValue();
boolean moveable = ((Boolean) ((Object[]) viewMessage.getMessageData())[1]).booleanValue();
getSwtTree().getColumn(columnNumber).setMoveable(moveable);
break;
}//case//
case MESSAGE_SHOW_SELECTION: {
getSwtTree().showSelection();
break;
}//case//
case MESSAGE_NODE_OPENED: {
NodeData node = getNodeData(viewMessage.getMessageInteger());
NodeData waitingNode = (NodeData) waitingItem.getData();
TreeItem topItem;
if(waitingNode == node) {
if(node.getChildren() == null) {
node.setChildren(LiteList.EMPTY_LIST);
}//if//
//replaceTreeItemChildren(waitingItem, node);
waitingItem.setExpanded(true);
waitingItem = null;
getSwtTree().setEnabled(true);
}//if//
updateTreeEditors(false);
layoutRenderers();
//TODO: The following work around code should be replaceable by calling the layoutRenderers() method on all ColumnData instances which should reposition all renderers.
topItem = getSwtTree().getTopItem();
//getShell()SwtUtilities.setRedraw(, false);
getSwtTree().pack();
getShell().layout(true, true);
getSwtTree().setTopItem(topItem);
SwtUtilities.setRedraw(getShell(), true);
getShell().redraw();
if((focusControl != null) && (!focusControl.isDisposed())) {
focusControl.setFocus();
}//if//
break;
}//case//
case MESSAGE_SET_SELECT_ON_OPEN: {
this.selectOnOpen = ((Boolean) viewMessage.getMessageData()).booleanValue();
break;
}//case//
case MESSAGE_SET_AUTO_FIT: {
setAutoFit(((Boolean) viewMessage.getMessageData()).booleanValue());
break;
}//case//
case MESSAGE_SET_FILL_ON_INITIALIZE: {
setFillOnInitialize(((Boolean) viewMessage.getMessageData()).booleanValue());
break;
}//case//
case MESSAGE_SET_FILL_ON_RESIZE: {
setFillOnResize(((Boolean) viewMessage.getMessageData()).booleanValue());
break;
}//case//
case MESSAGE_FILL: {
lastControlWidth = getSwtTree().getClientArea().width;
fill();
break;
}//case//
case MESSAGE_FIT: {
lastControlWidth = getSwtTree().getClientArea().width;
fit();
break;
}//case//
default: {
retVal = super.internalProcessMessage(viewMessage);
}//default//
}//switch//
return retVal;
}//internalProcessMessage()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlRemoveAll()
*/
protected void controlRemoveAll() {
IIterator iterator = getRowObjects();
//Release all cell component data and mappings.//
while(iterator.hasNext()) {
SimpleNodeData nodeData = (SimpleNodeData) iterator.next();
nodeData.unregisterCellComponents();
}//while//
getSwtTree().removeAll();
}//controlRemoveAll()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlAddNodeLink(com.foundation.tcv.swt.client.TreeComponent.NodeData, com.foundation.tcv.swt.client.TreeComponent.NodeData, int[])
*/
protected void controlAddNodeLink(NodeData parent, NodeData child, int[] columnIndexMap) {
suspendResizeEvents = true;
try {
if(parent != null) {
IList displayNodes = parent.getDisplayNodes();
if((parent.getChildren() == null) || (!parent.getChildren().isChangeable())) {
parent.setChildren(new LiteList(50, 500));
}//if//
//Add the child node data to the parent's children collection.//
parent.getChildren().add(child);
//Increment the parent count for the child so we know when it can be GC'd.//
child.incrementParentCount();
//Setup the display item for the child under all visible instances of the parent data.//
for(int index = 0, length = displayNodes.getSize(); index < length; index++) {
TreeItem parentItem = (TreeItem) displayNodes.get(index);
TreeItem childItem = null;
TreeItem removedItem = null;
if((parentItem.getItemCount() == 1) && (parentItem.getItem(0).getData() == null)) {
removedItem = parentItem.getItem(0);
}//if//
if((removedItem == null) && (inViewSorting) && (lastSortedColumnIndex != -1) && (parentItem.getItemCount() > 0)) {
TreeItem[] children = parentItem.getItems();
String newText = (String) ((SimpleTreeTableCellData) child.getData()[lastSortedColumnIndex + 1]).getText();
for(int childIndex = 0; (childItem == null) && (childIndex < children.length); childIndex++) {
String nextText = children[childIndex].getText(lastSortedColumnIndex);
if(!requiresSwap(newText, nextText, isSortReversed)) {
childItem = new TreeItem(parentItem, 0, childIndex);
}//if//
}//for//
//Add the child to the end.//
if(childItem == null) {
childItem = new TreeItem(parentItem, 0);
}//if//
}//if//
else {
childItem = new TreeItem(parentItem, 0);
//Force the parent item to expand since this is the first child and the parent must have been previously opened since it is being notified of the child.//
// parentItem.setExpanded(true);
}//else//
if(removedItem != null) {
removedItem.dispose();
}//if//
initializeChildItem(childItem, (SimpleNodeData) child, columnIndexMap);
}//for//
}//if//
else { //This will be run if the parent is the root node.//
TreeItem childItem = null;
if((inViewSorting) && (lastSortedColumnIndex != -1) && (getSwtTree().getItemCount() > 0)) {
TreeItem[] children = getSwtTree().getItems();
String newText = (String) ((SimpleTreeTableCellData) child.getData()[lastSortedColumnIndex + 1]).getText();
for(int childIndex = 0; childIndex < children.length; childIndex++) {
String nextText = children[childIndex].getText(lastSortedColumnIndex);
if(!requiresSwap(newText, nextText, isSortReversed)) {
childItem = new TreeItem(getSwtTree(), 0, childIndex);
}//if//
}//for//
//Add the child to the end.//
if(childItem == null) {
childItem = new TreeItem(getSwtTree(), 0);
}//if//
}//if//
else {
childItem = new TreeItem(getSwtTree(), 0);
}//else//
//Increment the parent count for the child so we know when it can be GC'd.//
child.incrementParentCount();
initializeChildItem(childItem, (SimpleNodeData) child, columnIndexMap);
}//else//
}//try//
finally {
suspendResizeEvents = false;
}//finally//
updateTreeEditors();
layoutRenderers();
}//controlAddNodeLink()//
/**
* Initializes the child item with the necessary data components.
* @param childItem The child display item to be initialized.
* @param child The child data node from which the item will be initialized.
* @param columnIndexMap The map of client side column indices indexed by the server side column index.
*/
protected void initializeChildItem(TreeItem childItem, SimpleNodeData child, int[] columnIndexMap) {
SimpleTreeTableCellData cellData = (SimpleTreeTableCellData) child.getData()[0];
rowBackgroundColorHolder.setValue(child, cellData.getBackground(), true);
childItem.setBackground(createColor((JefColor) rowBackgroundColorHolder.getValue(child)));
rowForegroundColorHolder.setValue(child, cellData.getForeground(), true);
childItem.setForeground(createColor((JefColor) rowForegroundColorHolder.getValue(child)));
rowFontHolder.setValue(child, cellData.getFont(), true);
childItem.setFont(createFont((JefFont[]) rowFontHolder.getValue(child)));
for(int columnIndex = 1; columnIndex < child.getData().length; columnIndex++) {
int clientColumnIndex = columnIndexMap[columnIndex - 1];
ColumnData columnData = (ColumnData) getSwtTree().getColumn(clientColumnIndex).getData();
cellData = (SimpleTreeTableCellData) child.getData()[columnIndex];
childItem.setText(clientColumnIndex, cellData.getText() == null ? "" : (String) cellData.getText());
columnData.cellImageHolder.setValue(child, cellData.getImage(), true);
childItem.setImage(clientColumnIndex, createImage((JefImage) columnData.cellImageHolder.getValue(child)));
columnData.backgroundColorHolder.setValue(child, cellData.getBackground(), true);
childItem.setBackground(clientColumnIndex, createColor((JefColor) columnData.backgroundColorHolder.getValue(child)));
columnData.foregroundColorHolder.setValue(child, cellData.getForeground(), true);
childItem.setForeground(clientColumnIndex, createColor((JefColor) columnData.foregroundColorHolder.getValue(child)));
columnData.fontHolder.setValue(child, cellData.getFont(), true);
childItem.setFont(clientColumnIndex, createFont((JefFont[]) columnData.fontHolder.getValue(child)));
if(cellData.getCellComponentId() != -1) {
//Sets the mapping between the column/row and the cell component and it registers the row data with the cell component.//
child.registerCellComponent(columnData, cellData.getCellComponentId());
}//if//
}//for//
//TODO: if it is known that a node has no child associations then the server should pass that info to the client which should use the EMPTY_LIST identifier defined by LiteList.
//If there may be children then create a dummy child which will be replaced upon opening the tree node.//
if((((SimpleNodeData) child).canHaveChildren()) && ((child.getChildren() == null) || (child.getChildren().getSize() > 0))) {
TreeItem dummyChild = new TreeItem(childItem, 0);
dummyChild.setText("...");
}//if//
childItem.setData(child);
child.getDisplayNodes().add(childItem);
}//initializeChildItem()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlRemoveNodeLink(com.foundation.tcv.swt.client.TreeComponent.NodeData, com.foundation.tcv.swt.client.TreeComponent.NodeData)
*/
protected void controlRemoveNodeLink(NodeData parent, NodeData child) {
if(parent != null) {
IList displayNodes = parent.getDisplayNodes();
parent.getChildren().remove(child);
child.decrementParentCount();
//Note: Calling controlRemoveNode(child) is not necessary since the server will send a message when the node data is to be removed (in the TreeNode.release() method).//
//Iterate over the parent nodes to remove any children.//
for(int parentIndex = 0; parentIndex < displayNodes.getSize(); parentIndex++) {
TreeItem parentItem = (TreeItem) displayNodes.get(parentIndex);
//The parent could be disposed already in a recursive scenario.//
if(!parentItem.isDisposed()) {
TreeItem[] childItems = parentItem.getItems();
boolean found = false;
//Search for the correct child and dispose of it (which also disposes of all its children).//
if(childItems != null) {
for(int childIndex = 0; (!found) && (childIndex < childItems.length); childIndex++) {
TreeItem childItem = childItems[childIndex];
//If this is the correct child item then remove all it's children's linkages (recursively) and dispose of it and its children.//
if(childItem.getData() == child) {
found = true;
removeNodeLinkages(childItem);
childItem.dispose();
}//if//
}//for//
}//if//
}//if//
}//for//
}//if//
else {
//Ensure that the tree is not disposed already.//
if(!getSwtTree().isDisposed()) {
TreeItem[] childItems = getSwtTree().getItems();
boolean found = false;
//Search for the correct child and dispose of it (which also disposes of all its children).//
if(childItems != null) {
for(int childIndex = 0; (!found) && (childIndex < childItems.length); childIndex++) {
TreeItem childItem = childItems[childIndex];
//If this is the correct child item then remove all it's children's linkages (recursively) and dispose of it and its children.//
if(childItem.getData() == child) {
found = true;
removeNodeLinkages(childItem);
childItem.dispose();
}//if//
}//for//
}//if//
}//if//
}//else//
updateTreeEditors();
layoutRenderers();
}//controlRemoveNodeLink()//
/**
* Removes the linkages for the item's children in a recursive fashion.
* @param item The item whose children are to be unlinked to their NodeData instances.
*/
private void removeNodeLinkages(TreeItem item) {
if(item.getItemCount() > 0) {
TreeItem[] items = item.getItems();
for(int index = 0; index < items.length; index++) {
NodeData nodeData = (NodeData) items[index].getData();
if(nodeData != null) {
nodeData.getDisplayNodes().remove(items[index]);
removeNodeLinkages(items[index]);
}//if//
}//for//
}//if//
}//removeNodeLinkages()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#createServerToClientColumnMapping()
*/
protected int[] createServerToClientColumnMapping() {
int[] clientToServerColumnMapping = getSwtTree().getColumnOrder();
int[] result = new int[clientToServerColumnMapping.length];
for(int index = 0; index < result.length; index++) {
result[clientToServerColumnMapping[index]] = index;
}//for//
return result;
}//createServerToClientColumnMapping()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#createClientToServerColumnMapping()
*/
protected int[] createClientToServerColumnMapping() {
return getSwtTree().getColumnOrder();
}//createClientToServerColumnMapping()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.CollectionComponent#createRowObject(int)
*/
protected RowObject createRowObject(int objectId) {
return new SimpleNodeData(objectId, getHiddenDataCount());
}//createRowObject()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSetCellText(com.foundation.tcv.swt.client.TreeComponent.NodeData, int, java.lang.Object)
*/
protected void controlSetCellText(NodeData nodeData, int columnIndex, Object data) {
if(!getSwtTree().isDisposed()) {
IList treeItems = nodeData.getDisplayNodes();
((SimpleTreeTableCellData) nodeData.getData()[columnIndex + 1]).setText(data);
for(int index = 0; index < treeItems.getSize(); index++) {
TreeItem treeItem = (TreeItem) treeItems.get(index);
if(columnIndex != -1) {
treeItem.setText(columnIndex, data == null ? "" : (String) data);
}//if//
}//for//
}//if//
}//controlSetCellText()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSetCellImage(com.foundation.tcv.swt.client.TreeComponent.NodeData, int, java.lang.Object)
*/
protected void controlSetCellImage(NodeData nodeData, int columnIndex, Object data) {
if(!getSwtTree().isDisposed()) {
((SimpleTreeTableCellData) nodeData.getData()[columnIndex + 1]).setImage(data);
if(columnIndex != -1) {
ColumnData columnData = (ColumnData) getSwtTree().getColumn(columnIndex).getData();
columnData.cellImageHolder.setValue(nodeData, data);
}//if//
}//if//
}//controlSetCellImage()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSetCellBackgroundColor(com.foundation.tcv.swt.client.TreeComponent.NodeData, int, java.lang.Object)
*/
protected void controlSetCellBackgroundColor(NodeData nodeData, int columnIndex, Object data) {
if(!getSwtTree().isDisposed()) {
((SimpleTreeTableCellData) nodeData.getData()[columnIndex + 1]).setImage(data);
if(columnIndex != -1) {
ColumnData columnData = (ColumnData) getSwtTree().getColumn(columnIndex).getData();
columnData.backgroundColorHolder.setValue(nodeData, data);
}//if//
else {
rowBackgroundColorHolder.setValue(nodeData, data);
}//else//
}//if//
}//controlSetCellBackgroundColor()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSetCellForegroundColor(com.foundation.tcv.swt.client.TreeComponent.NodeData, int, java.lang.Object)
*/
protected void controlSetCellForegroundColor(NodeData nodeData, int columnIndex, Object data) {
if(!getSwtTree().isDisposed()) {
((SimpleTreeTableCellData) nodeData.getData()[columnIndex + 1]).setImage(data);
if(columnIndex != -1) {
ColumnData columnData = (ColumnData) getSwtTree().getColumn(columnIndex).getData();
columnData.foregroundColorHolder.setValue(nodeData, data);
}//if//
else {
rowForegroundColorHolder.setValue(nodeData, data);
}//else//
}//if//
}//controlSetCellForegroundColor()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSetCellFont(com.foundation.tcv.swt.client.TreeComponent.NodeData, int, java.lang.Object)
*/
protected void controlSetCellFont(NodeData nodeData, int columnIndex, Object data) {
if(!getSwtTree().isDisposed()) {
((SimpleTreeTableCellData) nodeData.getData()[columnIndex + 1]).setImage(data);
if(columnIndex != -1) {
ColumnData columnData = (ColumnData) getSwtTree().getColumn(columnIndex).getData();
columnData.fontHolder.setValue(nodeData, data);
}//if//
else {
rowFontHolder.setValue(nodeData, data);
}//else//
}//if//
}//controlSetCellFont()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSetColumnHeaderText(int, java.lang.Object)
*/
protected void controlSetColumnHeaderText(int columnIndex, Object text) {
ColumnData columnData = (ColumnData) getSwtTree().getColumn(columnIndex).getData();
columnData.headerTextHolder.setValue(text);
}//controlSetColumnHeaderText()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSetColumnHeaderImage(int, java.lang.Object)
*/
protected void controlSetColumnHeaderImage(int columnIndex, Object image) {
ColumnData columnData = (ColumnData) getSwtTree().getColumn(columnIndex).getData();
columnData.headerImageHolder.setValue(image);
}//controlSetColumnHeaderImage()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlSaveViewState()
*/
protected void controlSaveViewState() {
if(!getSwtTree().isDisposed()) {
TreeItem[] items = getSwtTree().getItems();
stateData = new StateData();
//Build the array of identifiers that is the path to the top item.//
if(getSwtTree().getTopItem() != null) {
int arraySize = 0;
int index = 0;
TreeItem item = getSwtTree().getTopItem();
String[] topItemPath = null;
while(item != null) {
item = item.getParentItem();
arraySize++;
}//while//
topItemPath = new String[arraySize];
item = getSwtTree().getTopItem();
while(item != null) {
topItemPath[index++] = item.getText();
item = item.getParentItem();
}//while//
stateData.setTopItemPath(topItemPath);
}//if//
for(int index = 0; index < items.length; index++) {
if(items[index].getExpanded()) {
StateExpansionData data = new StateExpansionData(items[index].getText());
stateData.addNode(data);
StateData.collectChildExpansionData(data, items[index]);
}//if//
}//for//
}//if//
}//controlSaveViewState()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TreeComponent#controlRestoreViewState()
*/
protected void controlRestoreViewState() {
if((!getSwtTree().isDisposed()) && (stateData != null)) {
StateData.expandItems(getSwtTree().getItems(), stateData.getNodes());
if(stateData.getTopItemPath() != null) {
TreeItem item = StateData.findItem(getSwtTree().getItems(), stateData.getTopItemPath(), 0);
if(item != null) {
getSwtTree().setTopItem(item);
}//if//
}//if//
}//if//
}//controlRestoreViewState()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlAddColumn()
*/
protected void controlAddColumn() {
if(!getSwtTree().isDisposed()) {
internalColumnAdded(new TreeColumn(getSwtTree(), 0), getColumnCount());
}//if//
}//controlAddColumn()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlAddColumn(int)
*/
protected void controlAddColumn(int columnIndex) {
if(!getSwtTree().isDisposed()) {
internalColumnAdded(new TreeColumn(getSwtTree(), 0, columnIndex), columnIndex);
}//if//
}//controlAddColumn()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlRemoveColumn(int)
*/
protected void controlRemoveColumn(int columnIndex) {
if(!getSwtTree().getColumn(columnIndex).isDisposed()) {
getSwtTree().getColumn(columnIndex).dispose();
}//if//
}//controlRemoveColumn()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlSetColumnHeaderData(int, java.lang.Object)
*/
protected void controlSetColumnHeaderData(int columnIndex, Object data) {
if(!getSwtTree().isDisposed()) {
getSwtTree().getColumn(columnIndex).setText(data == null ? "" : data.toString());
}//if//
}//controlSetColumnHeaderData()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlAddSelection(java.lang.Object)
*/
protected void controlAddSelection(Object treeItemData) {
if(!getSwtTree().isDisposed()) {
TreeItem treeItem = (TreeItem) treeItemData;
TreeItem[] newSelections;
if(getSwtTree().getSelectionCount() > 0) {
TreeItem[] oldSelections = getSwtTree().getSelection();
newSelections = new TreeItem[oldSelections.length];
System.arraycopy(oldSelections, 0, newSelections, 0, oldSelections.length);
newSelections[oldSelections.length] = treeItem;
}//if//
else {
newSelections = new TreeItem[] {treeItem};
}//else//
getSwtTree().setSelection(newSelections);
if(newSelections.length == 1) {
getSwtTree().showItem(newSelections[0]);
}//if//
}//if//
}//controlAddSelection()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlRemoveSelection(java.lang.Object)
*/
protected void controlRemoveSelection(Object treeItemData) {
if(!getSwtTree().isDisposed()) {
TreeItem treeItem = (TreeItem) treeItemData;
if(getSwtTree().getSelectionCount() > 0) {
TreeItem[] oldSelections = getSwtTree().getSelection();
int treeItemIndex = -1;
for(int index = 0; (treeItemIndex == -1) && (index < oldSelections.length); index++) {
if(oldSelections[index] == treeItem) {
treeItemIndex = index;
}//if//
}//for//
if(treeItemIndex != -1) {
TreeItem[] newSelections = new TreeItem[oldSelections.length];
System.arraycopy(oldSelections, 0, newSelections, 0, treeItemIndex);
System.arraycopy(oldSelections, treeItemIndex + 1, newSelections, treeItemIndex, newSelections.length - treeItemIndex);
getSwtTree().setSelection(newSelections);
}//if//
}//if//
//getSwtTree().deselect(getSwtTree().indexOf(treeItem));
}//if//
}//controlRemoveSelection()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlRemoveAllSelections()
*/
protected void controlRemoveAllSelections() {
if(!getSwtTree().isDisposed()) {
getSwtTree().deselectAll();
}//if//
}//controlRemoveAllSelections()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlSetSelection(java.lang.Object)
*/
protected void controlSetSelection(Object treeItemData) {
if(!getSwtTree().isDisposed()) {
TreeItem treeItem = (TreeItem) treeItemData;
getSwtTree().setSelection(new TreeItem[] {treeItem});
getSwtTree().showItem(treeItem);
}//if//
}//controlSetSelection()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlSetSelections(com.common.util.IList)
*/
protected void controlSetSelections(IList treeItemData) {
if(!getSwtTree().isDisposed()) {
TreeItem[] treeItems = new TreeItem[treeItemData.getSize()];
//Collect the table items for the selected rows.//
for(int index = 0; index < treeItems.length; index++) {
treeItems[index] = (TreeItem) treeItemData.get(index);
}//for//
getSwtTree().setSelection(treeItems);
if(treeItems.length > 0) {
getSwtTree().showItem(treeItems[0]);
}//if//
}//if//
}//controlSetSelections()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlGetSelection()
*/
protected int controlGetSelection() {
return getSwtTree().getSelectionCount() == 1 ? ((NodeData) getSwtTree().getSelection()[0].getData()).getObjectId() : -1;
}//controlGetSelection()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlGetSelections()
*/
protected int[] controlGetSelections() {
int[] results = null;
if(getSwtTree().getSelectionCount() > 0) {
TreeItem[] treeItems = getSwtTree().getSelection();
results = new int[treeItems.length];
for(int index = 0; index < treeItems.length; index++) {
results[index] = ((NodeData) treeItems[index].getData()).getObjectId();
}//for//
}//if//
return results;
}//controlGetSelections()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlIsSelected(java.lang.Object)
*/
protected boolean controlIsSelected(Object treeItemData) {
TreeItem treeItem = (TreeItem) treeItemData;
boolean result = false;
if(getSwtTree().getSelectionCount() > 0) {
TreeItem[] oldSelections = getSwtTree().getSelection();
for(int index = 0; (!result) && (index < oldSelections.length); index++) {
result = oldSelections[index] == treeItem;
}//for//
}//if//
return result;
//return getSwtTree().isSelected(getSwtTree().indexOf(treeItem));
}//controlIsSelected()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#controlGetObjectIds()
*/
protected int[] controlGetObjectIds() {
TreeItem[] treeItems = getSwtTree().getItems();
int[] result = new int[treeItems.length];
//Collect the objectId for each row.//
for(int index = treeItems.length - 1; index >= 0; index--) {
result[index] = ((NodeData) treeItems[index].getData()).getObjectId();
}//for//
return result;
}//controlGetObjectIds()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.CollectionComponent#controlGetSelectionCount()
*/
protected int controlGetSelectionCount() {
return getSwtTree().getSelectionCount();
}//controlGetSelectionCount()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.TableComponent#forceSelectionEvent()
*/
protected void forceSelectionEvent() {
if(!getSwtTree().isDisposed()) {
widgetSelected(null);
}//if//
}//forceSelectionEvent()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.CollectionComponent#getAutoSynchronizeSelectionDelay()
*/
protected long getAutoSynchronizeSelectionDelay() {
return disableAutoSynchronizeDelay ? 0 : super.getAutoSynchronizeSelectionDelay();
}//getAutoSynchronizeSelectionDelay()//
/**
* Gets whether resizeable columns will be called upon to fit the control's client space when the control resizes or a column resizes.
* @return Whether resizeable columns will be told to fit available space.
*/
public boolean getAutoFit() {
return autoFit;
}//getAutoFill()//
/**
* Sets whether resizeable columns will be called upon to fit the control's client space when the control resizes or a column resizes.
* @param autoFit Whether resizeable columns will be told to fit available space.
*/
public void setAutoFit(boolean autoFit) {
this.autoFit = autoFit;
}//setAutoFit()//
/**
* Gets whether resizeable columns will be streched to fill available space during the control's initialization.
* @return Whether resizeable columns will be expanded to fill in empty space.
*/
public boolean getFillOnInitialize() {
return fillOnInitialize;
}//getFillOnInitialize()//
/**
* Sets whether resizeable columns will be streched to fill available space during the control's initialization.
* @param fillOnInitialize Whether resizeable columns will be expanded to fill in empty space.
*/
public void setFillOnInitialize(boolean fillOnInitialize) {
this.fillOnInitialize = fillOnInitialize;
}//setFillOnInitialize()//
/**
* Gets whether resizeable columns will be streched to fill available space when resizing the control.
* @return Whether resizeable columns will be expanded to fill in empty space.
*/
public boolean getFillOnResize() {
return fillOnResize;
}//getFillOnResize()//
/**
* Sets whether resizeable columns will be streched to fill available space when resizing the control.
* @param fillOnResize Whether resizeable columns will be expanded to fill in empty space.
*/
public void setFillOnResize(boolean fillOnResize) {
this.fillOnResize = fillOnResize;
}//setFillOnResize()//
/**
* Releases the tree editors for the given item.
* @param treeItem The tree item to be released.
*/
protected void releaseTreeEditors(TreeItem treeItem) {
int columnCount = getColumnCount();
//Release the editors for the row and each column.//
for(int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
TreeColumn treeColumn = getSwtTree().getColumn(columnIndex);
ColumnData columnData = (ColumnData) treeColumn.getData();
TreeEditor treeEditor = columnData.removeTreeEditor(treeItem);
if(treeEditor != null) {
CellComponent cellComponent = ((CellComponent.CellControl) treeEditor.getEditor().getData()).getCellComponent();
cellComponent.destroyControl(treeEditor.getEditor());
treeEditor.dispose();
}//if//
}//for//
}//releaseTreeEditors()//
/**
* Creates the tree editors for the given item.
* @param treeItem The tree item to be setup.
*/
protected void createTreeEditors(TreeItem treeItem) {
int columnCount = getColumnCount();
SimpleNodeData rowObject = (SimpleNodeData) treeItem.getData();
//Release the editors for the row and each column.//
for(int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
TreeColumn treeColumn = getSwtTree().getColumn(columnIndex);
ColumnData columnData = (ColumnData) treeColumn.getData();
CellComponent cellComponent = rowObject.getCellComponent(columnData);
//If there is a cell component and it allows us to use a control for this row, then set up a new control and table editor.//
if((cellComponent != null) && (cellComponent.supportsControl()) && ((cellComponent.useControl() == CellComponent.USE_CONTROL_ALWAYS) || ((cellComponent.useControl() == CellComponent.USE_CONTROL_ON_SELECTION) && ((getSwtTree().getSelectionCount() == 1) && (getSwtTree().getSelection()[0] == treeItem))))) {
Control control = cellComponent.createCellControl(rowObject);
TreeEditor treeEditor = new TreeEditor(getSwtTree());
Point size;
treeEditor.setEditor(control, treeItem, columnIndex);
columnData.setTreeEditor(treeItem, treeEditor);
treeEditor.grabHorizontal = cellComponent.grabHorizontalSpace();
treeEditor.grabVertical = cellComponent.grabVerticalSpace();
treeEditor.horizontalAlignment = cellComponent.getHorizontalAlignment();
treeEditor.verticalAlignment = cellComponent.getVerticalAlignment();
size = treeEditor.getEditor().computeSize(SWT.DEFAULT, SWT.DEFAULT);
if(size.y > minimumTreeEditorHeight) {
minimumTreeEditorHeight = size.y;
}//if//
}//if//
}//for//
}//createTreeEditors()//
/**
* Updates the tree editors so that all visible rows have appropriate tree editors when the tree scrolls, expands, or contracts.
*/
public void updateTreeEditors() {
updateTreeEditors(false);
}//updateTreeEditors()//
/**
* Updates the tree editors so that all visible rows have appropriate tree editors when the tree scrolls, expands, or contracts.
*/
public void updateTreeEditors(boolean forceRebuild) {
if(!suspendUpdateTreeEditors) {
TreeItem item = getSwtTree().getTopItem();
IIterator iterator;
if(item != null && !item.isDisposed()) {
Rectangle clientArea = getSwtTree().getClientArea();
int visibleRowCount = (int) Math.ceil(clientArea.height / (double) getSwtTree().getItemHeight());
LiteHashSet visibleTreeItems = new LiteHashSet(visibleRowCount);
TreeItem stackItem = item;
IntArray itemIndexStack = new IntArray(40);
int currentItemIndex = 0;
boolean lastItemFound = false;
//Build the stack of indices showing the location of the children in their parents down to the top item.//
while(stackItem.getParentItem() != null) {
itemIndexStack.add(0, stackItem.getParentItem().indexOf(stackItem));
stackItem = stackItem.getParentItem();
}//while//
//Add the index of the root level item within the tree to the stack.//
itemIndexStack.add(0, stackItem.getParent().indexOf(stackItem));
//Use the last index in the stack as the current index.//
currentItemIndex = itemIndexStack.removeLast();
//Navigate the visible tree items and add them to the set.//
while((!lastItemFound) && (visibleTreeItems.getSize() < visibleRowCount)) {
//Add the visible item to the set.//
visibleTreeItems.add(item);
if(item.getExpanded() && (item.getItemCount() > 0)) {
itemIndexStack.add(currentItemIndex);
currentItemIndex = 0;
item = item.getItem(0);
}//if//
else {
boolean hasPeer = false;
//Get the next item peer (or search up the parent chain until a peer is found).//
while(!lastItemFound && !hasPeer) {
if(item.getParentItem() != null) {
if(item.getParentItem().getItemCount() > ++currentItemIndex) {
item = item.getParentItem().getItem(currentItemIndex);
hasPeer = true;
}//if//
else {
item = item.getParentItem();
currentItemIndex = itemIndexStack.removeLast();
}//else//
}//if//
else {
if(item.getParent().getItemCount() > ++currentItemIndex) {
item = item.getParent().getItem(currentItemIndex);
hasPeer = true;
}//if//
else {
lastItemFound = true;
}//else//
}//else//
}//while//
}//else//
}//while//
if(currentlyVisibleTreeItems != null) {
iterator = currentlyVisibleTreeItems.iterator();
if(forceRebuild) {
//Release all the editors.//
while(iterator.hasNext()) {
TreeItem next = (TreeItem) iterator.next();
releaseTreeEditors(next);
}//while//
iterator = visibleTreeItems.iterator();
//Setup the editors.//
while(iterator.hasNext()) {
TreeItem next = (TreeItem) iterator.next();
createTreeEditors(next);
}//while//
}//if//
else {
//Release the editors for items no longer visible.//
while(iterator.hasNext()) {
TreeItem next = (TreeItem) iterator.next();
if(!visibleTreeItems.containsValue(next)) {
releaseTreeEditors(next);
}//if//
}//while//
iterator = visibleTreeItems.iterator();
//Setup the editors for newly visible items.//
while(iterator.hasNext()) {
TreeItem next = (TreeItem) iterator.next();
if(!currentlyVisibleTreeItems.containsValue(next)) {
createTreeEditors(next);
}//if//
}//while//
}//else//
}//if//
else {
iterator = visibleTreeItems.iterator();
//Setup the editors for newly visible items.//
while(iterator.hasNext()) {
TreeItem next = (TreeItem) iterator.next();
createTreeEditors(next);
}//while//
}//else//
currentlyVisibleTreeItems = visibleTreeItems;
}//if//
else if(currentlyVisibleTreeItems != null) {
iterator = currentlyVisibleTreeItems.iterator();
//Setup the editors for newly visible items.//
while(iterator.hasNext()) {
TreeItem next = (TreeItem) iterator.next();
releaseTreeEditors(next);
}//while//
currentlyVisibleTreeItems = null;
}//else if//
}//if//
}//updateTreeEditors()//
/**
* Forces the renderers to reposition themselves since SWT has a bug where by the renderers are not updated on tree adds, removes, or row height resizing.
*/
protected void layoutRenderers() {
// SwtUtilities.setRedraw(getSwtTree(), false);
try {
for(int columnIndex = getColumnCount() - 1; columnIndex >= 0; columnIndex--) {
((ColumnData) getColumn(columnIndex)).layoutRenderers();
}//for//
}//try//
finally {
// SwtUtilities.setRedraw(getSwtTree(), true);
// getSwtTree().redraw();
}//finally//
}//layoutRenderers()//
/* (non-Javadoc)
* @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
*/
public void widgetDefaultSelected(SelectionEvent event) {
//Fix a bug in the table where a double click event is fired twice.//
if((event.widget == getSwtTree()) && (lastDoubleClickTime != event.time)) {
//Invoke linkages associated with the selection.//
updateSelectionLinks();
if(getSendDoubleClick()) {
//Hold the messages so they get sent together.//
addMessageHold();
//Set the synchronize delay to zero so the synchronize occurs immediatly.//
disableAutoSynchronizeDelay = true;
try {
//Synchronize the selection.//
synchronizeSelection();
//Force any selection task to complete prior to sending the double click.//
synchronized(this) {
if(autoSynchronizeSelectionTask != null) {
autoSynchronizeSelectionTask.run();
}//if//
}//synchronized//
//Send the double click message.//
sendRoundTripMessage(MESSAGE_SEND_DOUBLE_CLICK, null, null);
}//try//
finally {
//Reset the delay and send the messages.//
disableAutoSynchronizeDelay = false;
removeMessageHold();
}//finally//
lastDoubleClickTime = event.time;
}//if//
else {
widgetSelected(event);
}//else//
}//if//
else if(event.widget == getSwtTree().getVerticalBar()) {
updateTreeEditors();
}//else//
}//widgetDefaultSelected()//
/* (non-Javadoc)
* @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
public void widgetSelected(SelectionEvent event) {
if((event == null) || (event.widget == getSwtTree())) {
//Invoke linkages associated with the selection.//
updateSelectionLinks();
//TODO: Is there some way to determine exactly what selection or deselection occured given the event object?//
synchronizeSelection();
}//if//
else if(event.widget == getSwtTree().getVerticalBar()) {
updateTreeEditors();
}//else//
}//widgetSelected()//
/* (non-Javadoc)
* @see org.eclipse.swt.events.ControlListener#controlMoved(org.eclipse.swt.events.ControlEvent)
*/
public void controlMoved(ControlEvent event) {
//Ignore this.//
}//controlMoved()//
/* (non-Javadoc)
* @see org.eclipse.swt.events.ControlListener#controlResized(org.eclipse.swt.events.ControlEvent)
*/
public void controlResized(ControlEvent event) {
if(!suspendResizeEvents) {
if(getAutoFit()) {
fit();
}//if//
else if(getFillOnResize()) {
fill();
}//else if//
updateTreeEditors();
}//if//
}//controlResized()//
/* (non-Javadoc)
* @see org.eclipse.swt.events.TreeListener#treeCollapsed(org.eclipse.swt.events.TreeEvent)
*/
public void treeCollapsed(TreeEvent event) {
updateTreeEditors();
layoutRenderers();
}//treeCollapsed()//
/* (non-Javadoc)
* @see org.eclipse.swt.events.TreeListener#treeExpanded(org.eclipse.swt.events.TreeEvent)
*/
public void treeExpanded(TreeEvent event) {
TreeItem parentItem = (TreeItem) event.item;
focusControl = getDisplay().getFocusControl();
//If we haven't already loaded the tree items then do so now. If the parent can't have children then a dummy child won't have been created so it would be impossible for this method to be called.//
if((parentItem.getItemCount() == 1) && (parentItem.getItems()[0].getData() == null)) {
SimpleNodeData parent = (SimpleNodeData) parentItem.getData();
if(parent.getChildren() == null) {
TreeItem[] items = parentItem.getItems();
//Dispose of the old node(s).//
for(int index = 0; index < items.length; index++) {
items[index].dispose();
}//for//
//Send a message to open the node which will send a reply message indicating that the node has finished opening. Disable the control while this is occuring.//
getSwtTree().setEnabled(false);
waitingItem = parentItem;
SwtUtilities.setRedraw(getShell(), false);
sendMessage(MESSAGE_OPEN_NODE, null, null, parent.getObjectId(), 0);
}//if//
else {
replaceTreeItemChildren(parentItem, parent);
updateTreeEditors();
layoutRenderers();
}//else//
}//if//
//If the previous if block was called we could add this code to the server instead of sending two messages.//
if(getSelectOnOpen()) {
getSwtTree().setSelection(new TreeItem[] {parentItem});
synchronizeSelection();
}//if//
}//treeExpanded()//
/**
* Replaces the children TreeItems with the latest data from the parent.
* <p>Note: This method expects that the children are not linked to the node data in any way. It was designed for use when there is a temporary child that needs replacing once the node is opened.</p>
* @param parentItem The parent display item.
* @param parent The parent node data.
*/
protected void replaceTreeItemChildren(TreeItem parentItem, NodeData parent) {
TreeItem[] items = parentItem.getItems();
int[] columnMapping = createServerToClientColumnMapping();
//Dispose of the old node(s).//
for(int index = 0; index < items.length; index++) {
if(items[index].getData() != null) {
((NodeData) items[index].getData()).getDisplayNodes().remove(items[index]);
}//if//
items[index].dispose();
}//for//
//Initialize the new child display items.//
for(int index = 0; index < parent.getChildren().getSize(); index++) {
NodeData child = (NodeData) parent.getChildren().get(index);
TreeItem childItem = new TreeItem(parentItem, 0);
initializeChildItem(childItem, (SimpleNodeData) child, columnMapping);
}//for//
}//replaceTreeItemChildren()//
/**
* Denotes whether or not opening a node should trigger the selection event.
* @return boolean False by default, true if the selection event should be occuring.
*/
public boolean getSelectOnOpen() {
return this.selectOnOpen;
}//getSelectOnOpen()//
/* (non-Javadoc)
* @see com.foundation.tcv.swt.client.CollectionComponent#postChangeCollection()
*/
protected void internalPostChangeCollection() {
//Update the editors and re-layout the renderers after making changes to the data structures.//
updateTreeEditors();
layoutRenderers();
}//internalPostChangeCollection()//
}//SimpleTreeTable//