/* * 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. *
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.
* @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//