/* * Copyright (c) 2005,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.view.swt; 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.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; import com.common.comparison.Comparator; import com.common.debug.Debug; import com.common.exception.InvalidArgumentException; import com.common.util.*; import com.common.util.optimized.IntArray; import com.foundation.controller.DecorationManager; import com.foundation.util.IInlineCollectionObservable; import com.foundation.util.IInlineCollectionObserver; import com.foundation.util.IInlineIndexedCollectionObservable; import com.foundation.util.IInlineIndexedCollectionObserver; import com.foundation.view.*; import com.foundation.view.resource.AbstractResourceService; import com.foundation.view.resource.ResourceReference; import com.foundation.view.swt.SimpleTable.ColumnData; import com.foundation.view.swt.cell.CellComponent; import com.foundation.view.swt.util.SwtUtilities; /** *

Note: In the future we will allow editing of cells and custom cell renderers. To do this we will augment the current text * cell renderers (in other words all cells will still get the text, background color, foreground color, and font properties) with * custom renderers and editors that can use the standard cell associations, and can provide their own associations and properties as * required. These custom renderers and editors will be indexed by the row value type so that they are only valid for some cells based * on the row data type.

*/ public class SimpleTreeTable extends TreeComponent implements TreeListener, ControlListener { public static final int STYLE_SINGLE = SWT.SINGLE; public static final int STYLE_MULTI = SWT.MULTI; public static final int STYLE_CHECK = SWT.CHECK; public static final int STYLE_FULL_SELECTION = SWT.FULL_SELECTION; public static final int STYLE_HIDE_SELECTION = SWT.HIDE_SELECTION; public static final int STYLE_VIRTUAL = SWT.VIRTUAL; public static final int ALIGNMENT_LEFT = SWT.LEFT; public static final int ALIGNMENT_CENTER = SWT.CENTER; public static final int ALIGNMENT_RIGHT = SWT.RIGHT; public static final int AUTO_FIT_NEVER = 0; public static final int AUTO_FIT_MANUAL = 1; public static final int AUTO_FIT_AUTO = 2; public static final int LINK_TARGET_FIT = TreeComponent.LAST_LINK_TARGET + 1; public static final int LINK_TARGET_FILL = TreeComponent.LAST_LINK_TARGET + 2; public static final int LAST_LINK_TARGET = TreeComponent.LAST_LINK_TARGET + 2; /** The background color resource for the column. */ private MultiResourceAssociation rowBackgroundColor = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_COLOR, false, null); /** The foreground color resource for the column. */ private MultiResourceAssociation rowForegroundColor = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_COLOR, false, null); /** The font resource for the column. */ private MultiResourceAssociation rowFont = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_FONT, false, null); /** The row height used. */ private SingleResourceAssociation rowHeight = new SingleResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_INTEGER, false, null); /** 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); /** The events associations that resize all non-auto-fit columns to fit the size of the data in the column. */ private IHashSet fitEventAssociations = null; /** The event associations resize all auto-fill columns to fill empty space in the control. This never makes columns smaller. */ private IHashSet fillEventAssociations = null; /** 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; /** Whether the user is allowed to make more than one item selection. */ private final boolean allowMultiSelection; /** The association that returns the children for any row in the tree. */ private CollectingMultiResourceAssociation children = new CollectingMultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_OBJECT, false); /** The association that returns the groupings for any row in the tree. */ private CollectingMultiResourceAssociation groupings = new CollectingMultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_OBJECT, false); /** The root node for our tree. */ private SimpleNodeData rootNode = new SimpleNodeData(null, false); /** Tree nodes indexed by their item (non-grouping nodes only). */ private IHashMap nodesByItem = new LiteHashMap(100, Comparator.getLogicalComparator(), Comparator.getIdentityComparator()); /** Lists of group tree nodes indexed by their item (grouping nodes only). */ private IHashMap groupNodesByItem = new LiteHashMap(100, Comparator.getLogicalComparator(), Comparator.getIdentityComparator()); /** 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; /** 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 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 large numbers of items. */ private boolean suspendUpdateTreeEditors = false; /** Suspends the laying out of the renderers while adding, removing, or moving large numbers of items. */ private boolean suspendLayoutRenderers = 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; /** 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 data for a single cell. */ public class SimpleTreeTableCellData { /** The cell's desired text, or null for the default text. */ private String text; /** The cell's desired image, or null for the default image. */ private JefImage image; /** The cell's desired background color, or null for the default color. */ private JefColor background; /** The cell's desired foreground color, or null for the default color. */ private JefColor foreground; /** The cell's desired font, or null for the default font. */ private JefFont[] font; /** * SimpleTreeTableCellData constructor. */ public SimpleTreeTableCellData() { }//SimpleTreeTableCellData()// /** * SimpleTreeTableCellData constructor. * @param text The cell's desired text, or null for the default text. * @param image The cell's desired image, or null for the default image. * @param background The cell's desired background color, or null for the default color. * @param foreground The cell's desired foreground color, or null for the default color. * @param font The cell's desired font, or null for the default font. */ public SimpleTreeTableCellData(String text, JefImage image, JefColor background, JefColor foreground, JefFont[] font) { this.text = text; this.image = image; this.background = background; this.foreground = foreground; this.font = font; }//SimpleTreeTableCellData()// /** * Gets the cell's desired text, or null for the default text. * @return The cell's text. */ public String getText() { return text; }//getText()// /** * Gets the cell's desired image, or null for the default image. * @return The cell's image. */ public JefImage getImage() { return image; }//getImage()// /** * Gets the cell's desired background color, or null for the default color. * @return The cell's background color. */ public JefColor getBackground() { return background; }//getBackground()// /** * Gets the cell's desired foreground color, or null for the default color. * @return The cell's foreground color. */ public JefColor getForeground() { return foreground; }//getForeground()// /** * Gets the cell's desired font, or null for the default font. * @return The cell's font. */ public JefFont[] getFont() { return font; }//getFont()// }//SimpleTreeTableCellData// /** * Encapsulates information about an expanded node in the tree. */ private 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. */ private 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 class CollectionListener implements IInlineIndexedCollectionObserver, IInlineCollectionObserver { /** The node to be notified when the collection changes. */ private SimpleNodeData node; /** Whether this is a grouping collection (versus a children collection). */ //private boolean isGrouping; /** The collection to be listened to. */ private ICollection collection; /** * CollectionListener constructor. *

Note: Also registers for the event on the collection.

* @param node The node to be notified when the collection changes. * @param isGrouping Whether this is a grouping collection (versus a children collection). * @param collection The collection to be listened to. */ public CollectionListener(SimpleNodeData node, boolean isGrouping, ICollection collection) { this.node = node; //this.isGrouping = isGrouping; this.collection = collection; if(collection instanceof IInlineIndexedCollectionObservable) { ((IInlineIndexedCollectionObservable) collection).addCollectionObserver((IInlineIndexedCollectionObserver) this); }//if// else if(collection instanceof IInlineCollectionObservable) { ((IInlineCollectionObservable) collection).addCollectionObserver((IInlineCollectionObserver) this); }//else if// }//CollectionListener()// /** * Releases the listener when it is no longer needed. */ public void release() { if(collection instanceof IInlineIndexedCollectionObservable) { ((IInlineIndexedCollectionObservable) collection).removeCollectionObserver((IInlineIndexedCollectionObserver) this); }//if// else if(collection instanceof IInlineCollectionObservable) { ((IInlineCollectionObservable) collection).removeCollectionObserver((IInlineCollectionObserver) this); }//else if// }//release()// /** * Gets the collection being listened to. * @return The collection whose adds and removes are to be used to update the tree. */ public ICollection getCollection() { return collection; }//getCollection()// /* (non-Javadoc) * @see com.foundation.util.IInlineCollectionObserver#valueAdded(java.lang.Object) */ public void valueAdded(Object value) { placeItem(node, value); }//valueAdded()// /* (non-Javadoc) * @see com.foundation.util.IInlineCollectionObserver#valueRemoved(java.lang.Object) */ public void valueRemoved(Object value) { SimpleNodeData child = node.getChildNode(value, false); if(child != null) { pruneNode(node, child); }//if// else { Debug.log(new RuntimeException("Internal Error: failed to locate the child node.")); }//else// }//valueRemoved()// /* (non-Javadoc) * @see com.foundation.util.IInlineCollectionObserver#removingAll() */ public void removingAll() { IList children = new LiteList(node.getChildNodes()); try { startChanges(children.getSize()); for(int index = 0; index < children.getSize(); index++) { SimpleNodeData child = (SimpleNodeData) children.get(index); if(!child.isGrouping()) { pruneNode(node, child); }//if// }//for// }//try// finally { stopChanges(); }//finally// }//removingAll()// /* (non-Javadoc) * @see com.foundation.util.IInlineIndexedCollectionObserver#startChanges(int) */ public void startChanges(int changeCount) { //Allow the collection to suspend activites that would adversly affect performance while the collection make large scale changes.// preChangeCollection(); }//startChanges()// /* (non-Javadoc) * @see com.foundation.util.IInlineIndexedCollectionObserver#stopChanges() */ public void stopChanges() { postChangeCollection(); }//stopChanges()// /* (non-Javadoc) * @see com.foundation.util.IInlineIndexedCollectionObserver#valueAdded(java.lang.Object, int) */ public void valueAdded(Object value, int index) { valueAdded(value); }//valueAdded()// /* (non-Javadoc) * @see com.foundation.util.IInlineIndexedCollectionObserver#valueRemoved(java.lang.Object, int) */ public void valueRemoved(Object value, int index) { valueRemoved(value); }//valueRemoved()// /* (non-Javadoc) * @see com.foundation.util.IInlineIndexedCollectionObserver#valuesSorted(int[]) */ public void valuesSorted(int[] mapping) { //TODO: Should this be implemented? //Ignored// }//valuesSorted()// }//CollectionListener// /** * The node in the virtual tree. This node can have multiple parents because it can appear in more than one place in the tree display. * A virtual tree is used because it is much more flexable than the underlying tree component. * There is only one TreeNode for each model value even if the model value appears in the tree in multiple places at multiple levels of the tree. */ protected class SimpleNodeData extends NodeData { /** The child TreeNode instances. This will be null if the children are unkown. */ private IList childNodes = null; /** The parent tree nodes (the non-grouping parent), or null if this is a root node, the is a grouping node, or has no groupings. This is used to update the groupings when a node's groupings change. It is possible for a node to be referenced by more than one parent. */ private IList parentNodes = null; /** Whether this is a grouping node. */ private boolean isGrouping = false; /** The reference to a CollectionListener or an IList of CollectionListeners which will provide us with adds and removes of the node's children. This will be null if there are no listeners yet. */ private Object childListener = null; /** Whether the node could possibly have children. */ public boolean canHaveChildren = true; /** * SimpleNodeData constructor. *

Note: The caller should not increment the usage counter as it already starts at one.

*

Warning: Callers must initialize the node after creating it. There also must be a release call to allow for unregistering event hooks.

* @param item The data for the node. This is often the row value, but for groupings is the grouping value. * @param isGrouping Whether this node was created from a grouping value. */ public SimpleNodeData(Object value, boolean isGrouping) { super(value); this.isGrouping = isGrouping; }//SimpleNodeData()// /** * Gets the cell data for the given column. * @param columnIndex The zero based index of the column. * @return The cell data for this row and given column. */ public SimpleTreeTableCellData getCellData(int columnIndex) { return (SimpleTreeTableCellData) getColumn(columnIndex).getCellData(getValue()); }//getCellData()// /** * Gets the row data. * @return The row data for this node. */ public SimpleTreeTableCellData getRowData() { return (SimpleTreeTableCellData) getRowHeaderCellData(getValue()); }//getRowData()// /** * Determines whether the node can possibly have children. * @return Whether the node may have children. */ public boolean canHaveChildren() { return canHaveChildren; }//canHaveChildren()// /** * Determines whether this is a grouping node. * @return Whether this node was created from a grouping value. */ public boolean isGrouping() { return isGrouping; }//isGrouping()// /** * Gets the node's child node count. * @return The number of child TreeNode instances. */ public int getChildNodeCount() { return childNodes == null ? 0 : childNodes.getSize(); }//getChildNodeCount()// /** * Gets the indexed child node. * @param index The zero based index of the child to be retrieved. * @return The child tree node at the given index. */ public SimpleNodeData getChildNode(int index) { return (SimpleNodeData) childNodes.get(index); }//getChildNode()// /** * Gets the child nodes. * @return The collection of all child nodes. */ public IList getChildNodes() { return childNodes; }//getChildNodes()// /** * Gets the child node association with the given item. * @param item The item associated with the node we are searching for. * @param isGrouping Whether we are searching for a grouping node. * @return The found tree node, or null. */ public SimpleNodeData getChildNode(Object item, boolean isGrouping) { SimpleNodeData result = null; for(int index = 0; (result == null) && (index < childNodes.getSize()); index++) { SimpleNodeData next = (SimpleNodeData) childNodes.get(index); if((next.isGrouping() == isGrouping) && (Comparator.equals(next.getValue(), item))) { result = next; }//if// }//for// return result; }//getChildNode()// /** * Adds a child node to this node. *

Warning: This method should be called in order to allow future indexing of the child nodes.

* @param childNode The child node to be added. */ public void addChildNode(SimpleNodeData childNode) { if(childNodes == null) { childNodes = new LiteList(20, 100); }//if// childNodes.add(childNode); //Notify the view.// controlAddLinkage(this, childNode); }//addChildNode()// /** * Removes a child node from this node. *

Warning: This method should be called in order to allow future indexing of the child nodes.

* @param childNode The child node to be removed. */ public void removeChildNode(SimpleNodeData childNode) { childNodes.remove(childNode); //Notify the view.// controlRemoveLinkage(this, childNode); }//removeChildNode()// /** * Sets the node's child nodes. This method will index the list of child nodes. * @param childNodes The child TreeNode instances. This will be null if the children are unkown. */ public void setChildNodes(IList childNodes) { this.childNodes = childNodes; }//setChildNodes()// /** * Gets the parents of this node. *

The parent tree nodes (the non-grouping parent), or null if this is a root node, the is a grouping node, or has no groupings. * This is used to update the groupings when a node's groupings change. * It is possible for a node to be referenced by more than one parent.

* @return The parent (non-grouping) tree nodes, or null. */ public IList getParentNodes() { return parentNodes; }//getParentNodes()// /** * Sets the parents of this node. *

The parent tree nodes (the non-grouping parent), or null if this is a root node, the is a grouping node, or has no groupings. * This is used to update the groupings when a node's groupings change. * It is possible for a node to be referenced by more than one parent.

* @param parentNodes The parent (non-grouping) tree nodes, or null. */ public void setParentNodes(IList parentNodes) { this.parentNodes = parentNodes; }//setParentNodes()// /** * Adds a child collection listener to pickup adds and removes from the collection of child items. * @param collection The collection to be listened to. */ public void addChildListener(ICollection collection) { CollectionListener listener = new CollectionListener(this, false, collection); if(childListener != null) { if(childListener instanceof IList) { ((IList) childListener).add(listener); }//if// else { IList list = new LiteList(5, 20); list.add(childListener); list.add(listener); childListener = list; }//else// }//if// else { childListener = listener; }//else// }//addChildListener()// /** * Removes a child collection listener given the collection it was created with. * @param collection The collection that the listener is listening to. */ public void removeChildListener(ICollection collection) { CollectionListener result = null; if(childListener instanceof IList) { IList listeners = (IList) childListener; for(int index = 0; (result == null) && (index < listeners.getSize()); index++) { CollectionListener listener = (CollectionListener) listeners.get(index); if(listener.getCollection() == collection) { listeners.remove(index); result = listener; }//if// }//for// }//if// else if(childListener instanceof CollectionListener) { if(((CollectionListener) childListener).getCollection() == collection) { result = (CollectionListener) childListener; }//if// }//else if// if(result != null) { result.release(); }//if// }//removeChildListener()// /** * Removes all child collection listeners. */ public void removeChildListeners() { CollectionListener result = null; if(childListener instanceof IList) { IList listeners = (IList) childListener; for(int index = 0; (result == null) && (index < listeners.getSize()); index++) { ((CollectionListener) listeners.get(index)).release(); }//for// listeners.removeAll(); }//if// else if(childListener instanceof CollectionListener) { ((CollectionListener) childListener).release(); }//else if// }//removeChildListeners()// /** * Initializes the node by placing it in all mappings. */ public void initialize() { super.initialize(); //Register listeners and furthor index the node.// if(!isGrouping()) { nodesByItem.put(getValue(), this); groupings.registerItem(getValue(), null); children.registerItem(getValue(), null); }//if// else { IList groupNodes = (IList) groupNodesByItem.get(getValue()); if(groupNodes == null) { groupNodes = new LiteList(5, 25); groupNodesByItem.put(getValue(), groupNodes); }//if// groupNodes.add(this); }//else// //Register item listeners.// rowBackgroundColor.registerItem(getValue(), null); rowForegroundColor.registerItem(getValue(), null); rowFont.registerItem(getValue(), null); canHaveChildren = isGrouping || this == rootNode ? true : children.hasAssociations(getValue()); }//initialize()// /** * Removes the node from all mappings. */ public void release() { if(this != rootNode) { super.release(); if(!isGrouping()) { nodesByItem.remove(getValue()); groupings.unregisterItem(getValue()); children.unregisterItem(getValue()); }//if// else { IList groupNodes = (IList) groupNodesByItem.get(getValue()); if(groupNodes != null) { groupNodes.remove(this); if(groupNodes.getSize() == 0) { groupNodesByItem.remove(getValue()); }//if// }//if// }//else// rowBackgroundColor.unregisterItem(getValue()); rowBackgroundColorHolder.remove(getValue()); rowForegroundColor.unregisterItem(getValue()); rowForegroundColorHolder.remove(getValue()); rowFont.unregisterItem(getValue()); rowFontHolder.remove(getValue()); removeChildListeners(); }//if// }//release()// }//SimpleNodeData// public static class Renderer { public Class type = null; public CellComponent component = null; public Renderer(Class type, CellComponent component) { if(type == null || component == null) { throw new InvalidArgumentException(); }//if// this.type = type; this.component = component; }//Renderer()// }//Renderer// /** * Encapsulates all the data pertaining to a single column in the table component. */ public class ColumnData extends AbstractColumn implements IInternalAbstractComponent, IResourceHolderComponent { /** The zero based index of this column in the set of columns. Note that this is not display order, just an identifier. */ private int index = 0; /** The text resource for the column. */ private SingleResourceAssociation headerText = new SingleResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_TEXT, false, ""); /** The image resource for the column. */ private SingleResourceAssociation headerImage = new SingleResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_IMAGE, false, null); /** The text resource for the cell. */ private MultiResourceAssociation cellText = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_TEXT, false, null); /** The image resource for the cell. */ private MultiResourceAssociation cellImage = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_IMAGE, false, null); /** The background color resource for the cell. */ private MultiResourceAssociation backgroundColor = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_COLOR, false, null); /** The foreground color resource for the cell. */ private MultiResourceAssociation foregroundColor = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_COLOR, false, null); /** The font resource for the cell. */ private MultiResourceAssociation font = new MultiResourceAssociation(this, this, getViewContext(), ResourceAssociation.TYPE_FONT, false, null); /** A holder for the value of the cell background color. */ private MultiResourceHolder backgroundColorHolder = new MultiResourceHolder(this); /** A holder for the value of the cell foreground color. */ private MultiResourceHolder foregroundColorHolder = new MultiResourceHolder(this); /** A holder for the value of the cell foreground color. */ private MultiResourceHolder fontHolder = new MultiResourceHolder(this); /** A holder for the value of the cell image. */ private MultiResourceHolder cellImageHolder = new MultiResourceHolder(this); /** A holder for the value of the column's tool tip text. */ private ResourceHolder toolTipTextHolder = new ResourceHolder(this); /** A holder for the value of the column's header text. */ private ResourceHolder headerTextHolder = new ResourceHolder(this); /** A holder for the value of the column's header image. */ private ResourceHolder headerImageHolder = new ResourceHolder(this); /** Whether the column header is resizable. */ private boolean headerResizable = false; /** The alginment of the column data. */ private int alignment = ALIGNMENT_LEFT; /** The width of the column. */ private int width = 100; /** The minimum width of the column. */ private int minimumWidth = 20; /** Whether the column is moveable. */ private boolean moveable = true; /** The collection of Renderer instances in the order they were specified. */ private IList renderers = null; /** 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); /** A counter for the number of renderers that will paint their data. */ private int paintRendererCounter = 0; /** A counter for the number of renderers that will generate a component to display their data. */ private int componentRendererCounter = 0; /** The table column being served. */ private TreeColumn column; /** * ColumnData constructor. */ public ColumnData(int index) { this.index = index; }//ColumnData()// /* (non-Javadoc) * @see com.foundation.view.IAbstractComponent#verifyThread() */ public void verifyThread() { SimpleTreeTable.this.verifyThread(); }//verifyThread()// /** * Sets the swt tree column reference. * @param column The actual column. */ public void setTreeColumn(TreeColumn column) { this.column = column; }//setTreeColumn()// /* (non-Javadoc) * @see com.foundation.view.IAbstractComponent#getName() */ public String getName() { return "__Column__"; }//getName()// /** * 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()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#registerItem(java.lang.Object, java.lang.Object) */ protected void registerItem(Object item, Object data) { cellText.registerItem(item, data); cellImage.registerItem(item, data); backgroundColor.registerItem(item, data); foregroundColor.registerItem(item, data); font.registerItem(item, data); if(renderers != null) { for(int index = 0; index < renderers.getSize(); index++) { ((Renderer) renderers.get(index)).component.registerRowItem(item, data); }//for// }//if// }//registerItem()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#unregisterItem(java.lang.Object) */ protected void unregisterItem(Object item) { cellText.unregisterItem(item); cellImage.unregisterItem(item); backgroundColor.unregisterItem(item); backgroundColorHolder.remove(item); foregroundColor.unregisterItem(item); foregroundColorHolder.remove(item); font.unregisterItem(item); fontHolder.remove(item); if(renderers != null) { for(int index = 0; index < renderers.getSize(); index++) { ((Renderer) renderers.get(index)).component.unregisterRowItem(item); }//for// }//if// }//unregisterItem()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#unregisterAllItems() */ protected void unregisterAllItems() { cellText.unregisterAllItems(); cellImage.unregisterAllItems(); backgroundColor.unregisterAllItems(); backgroundColorHolder.removeAll(); foregroundColor.unregisterAllItems(); foregroundColorHolder.removeAll(); font.unregisterAllItems(); fontHolder.removeAll(); if(renderers != null) { for(int index = 0; index < renderers.getSize(); index++) { ((Renderer) renderers.get(index)).component.unregisterRowItems(); }//for// }//if// }//unregisterAllItems()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#refreshHeaderData() */ protected boolean refreshHeaderData() { return headerText.refresh() || headerImage.refresh(); }//refreshHeaderData()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#getHeaderData() */ protected Object getHeaderData() { return new Object[] {headerTextHolder.getValue(), headerImageHolder.getValue()}; }//getHeaderValue()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#refreshCellData(java.lang.Object) */ protected boolean refreshCellData(Object item) { boolean result = false; result |= cellText.refresh(item); if(cellImage.refresh(item)) { cellImageHolder.setValue(item, cellImage.getValue(item), true); result = true; }//if// if(backgroundColor.refresh(item)) { backgroundColorHolder.setValue(item, backgroundColor.getValue(item), true); result = true; }//if// if(foregroundColor.refresh(item)) { foregroundColorHolder.setValue(item, foregroundColor.getValue(item), true); result = true; }//if// if(font.refresh(item)) { fontHolder.setValue(item, font.getValue(item), true); result = true; }//if// return result; }//refreshCellData()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#getCellData(java.lang.Object) */ protected Object getCellData(Object item) { return new SimpleTreeTableCellData((String) cellText.getValue(item), (JefImage) cellImageHolder.getValue(item), (JefColor) backgroundColorHolder.getValue(item), (JefColor) foregroundColorHolder.getValue(item), (JefFont[]) fontHolder.getValue(item)); }//getCellValue()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#getIndex() */ protected int getIndex() { return index; }//getIndex()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#setIndex(int) */ protected void setIndex(int index) { this.index = index; }//setIndex()// /* (non-Javadoc) * @see com.foundation.view.IAbstractComponent#getContainer() */ public IAbstractContainer getContainer() { return SimpleTreeTable.this.getContainer(); }//getContainer()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#addMessageHold() */ public void addMessageHold() { }//addMessageHold()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#removeMessageHold() */ public void removeMessageHold() { }//removeMessageHold()// /* (non-Javadoc) * @see com.foundation.view.IAbstractComponent#onEventFired(com.foundation.view.IEventAssociation, java.lang.Object[]) */ public void onEventFired(IEventAssociation eventAssociation, Object[] eventArguments) { }//onEventFired()// /* (non-Javadoc) * @see com.foundation.view.IAbstractComponent#onValueChanged(com.foundation.view.IAttributeAssociation) */ public void onValueChanged(IAttributeAssociation attributeAssociation) { }//onValueChanged()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#onValueChanged(com.foundation.view.ResourceAssociation, int) */ public void onValueChanged(ResourceAssociation resourceAssociation, int flags) { //getEventLoop().processRequest(this, REQUEST_ON_VALUE_CHANGED, resourceAssociation, false); //TODO:Remove me once we verify it isn't necessary. verifyThread(); if(resourceAssociation instanceof SingleResourceAssociation) { internalOnValueChanged((SingleResourceAssociation) resourceAssociation); }//if// }//onValueChanged()// /* (non-Javadoc) * @see com.foundation.view.IMultiResourceAssociationChangeListener#onValueChanged(com.foundation.view.ResourceAssociation, java.lang.Object, java.lang.Object, boolean) */ public void onValueChanged(ResourceAssociation resourceAssociation, Object item, Object data, boolean isUpdate) { //getEventLoop().processRequest(this, REQUEST_ON_VALUE_CHANGED, resourceAssociation, item, false); //TODO:Remove me once we verify it isn't necessary. verifyThread(); if(resourceAssociation instanceof MultiResourceAssociation) { internalOnValueChanged((MultiResourceAssociation) resourceAssociation, item, data, isUpdate); }//if// }//onValueChanged()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#onModelExternallyChanged(com.foundation.view.ResourceAssociation, boolean, java.lang.Object) */ public void onModelExternallyChanged(ResourceAssociation resourceAssociation, boolean isCleared, Object originalValue) { verifyThread(); if(isInitialized()) { internalOnModelExternallyChanged((SingleResourceAssociation) resourceAssociation, isCleared, originalValue); }//if// }//onModelExternallyChanged()// /* (non-Javadoc) * @see com.foundation.view.IMultiResourceAssociationChangeListener#onModelExternallyChanged(com.foundation.view.ResourceAssociation, java.lang.Object, java.lang.Object, boolean, java.lang.Object) */ public void onModelExternallyChanged(ResourceAssociation resourceAssociation, Object alteredItem, Object data, boolean isCleared, Object originalValue) { verifyThread(); if(isInitialized()) { internalOnModelExternallyChanged((MultiResourceAssociation) resourceAssociation, alteredItem, data, isCleared, originalValue); }//if// }//onModelExternallyChanged()// /* (non-Javadoc) * @see com.foundation.view.IResourceHolderComponent#resourceHolderChanged(com.foundation.view.ResourceHolder, java.lang.Object, java.lang.Object, int) */ public void resourceHolderChanged(AbstractResourceHolder resourceHolder, Object oldValue, Object newValue, int flags) { internalResourceHolderChanged((ResourceHolder) resourceHolder, oldValue, newValue, flags); }//resourceHolderChanged()// /* (non-Javadoc) * @see com.foundation.view.IResourceHolderComponent#resourceHolderChanged(com.foundation.view.AbstractMultiResourceHolder, com.common.util.IHashSet, java.lang.Object, java.lang.Object) */ public void resourceHolderChanged(AbstractMultiResourceHolder resourceHolder, IHashSet rows, Object oldValue, Object newValue) { //Never used.// }//resourceHolderChanged()// /* (non-Javadoc) * @see com.foundation.view.IResourceHolderComponent#resourceHolderChanged(com.foundation.view.AbstractMultiResourceHolder, java.lang.Object, java.lang.Object, java.lang.Object) */ public void resourceHolderChanged(AbstractMultiResourceHolder resourceHolder, Object row, Object oldValue, Object newValue) { //Never used.// }//resourceHolderChanged()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#onValueChanged(com.foundation.view.ResourceAssociation) */ protected void internalOnValueChanged(ResourceAssociation resourceAssociation) { if(resourceAssociation == headerText) { if(headerText.refresh()) { headerTextHolder.setValue(headerText.getValue()); }//if// }//if// else if(resourceAssociation == headerImage) { if(headerImage.refresh()) { headerImageHolder.setValue(headerImage.getValue()); }//if// }//else if// }//internalOnValueChanged()// /* (non-Javadoc) * @see com.foundation.view.IMultiResourceAssociationChangeListener#onValueChanged(com.foundation.view.ResourceAssociation, java.lang.Object, java.lang.Object, boolean) */ protected void internalOnValueChanged(ResourceAssociation resourceAssociation, Object item, Object data, boolean isUpdate) { if(isInitialized()) { //Refresh the item cell data.// if(resourceAssociation == cellText) { if(cellText.refresh(item)) { int columnIndex = getColumnIndex(); String text = (String) cellText.getValue(item); text = text == null ? "" : text; if(item != null) { SimpleNodeData rowObject = (SimpleNodeData) getNodeDataByItem(item); if(rowObject.getDisplayNodes() != null) { for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) { ((TreeItem) rowObject.getDisplayNodes().get(index)).setText(columnIndex, text); }//for// }//if// }//if// else { IIterator iterator = getNodesByItem().valueIterator(); while(iterator.hasNext()) { SimpleNodeData rowObject = (SimpleNodeData) iterator.next(); if(rowObject.getDisplayNodes() != null) { for(int index = 0; index < rowObject.getDisplayNodes().getSize(); index++) { ((TreeItem) rowObject.getDisplayNodes().get(index)).setText(columnIndex, text); }//for// }//if// }//while// }//else// }//if// }//if// else if(resourceAssociation == cellImage) { //Refresh the item cell data.// if(cellImage.refresh(null)) { if(item != null) { cellImageHolder.setValue(item, cellImage.getValue(item)); }//if// else { Object value = cellImage.getValue(null); IIterator iterator = getNodesByItem().keyIterator(); while(iterator.hasNext()) { cellImageHolder.setValue(iterator.next(), value); }//while// }//else// }//if// }//else if// else if(resourceAssociation == backgroundColor) { //Refresh the item cell data.// if(backgroundColor.refresh(null)) { if(item != null) { backgroundColorHolder.setValue(item, backgroundColor.getValue(item)); }//if// else { Object value = backgroundColor.getValue(null); IIterator iterator = getNodesByItem().keyIterator(); while(iterator.hasNext()) { backgroundColorHolder.setValue(iterator.next(), value); }//while// }//else// }//if// }//else if// else if(resourceAssociation == foregroundColor) { //Refresh the item cell data.// if(foregroundColor.refresh(null)) { if(item != null) { foregroundColorHolder.setValue(item, foregroundColor.getValue(item)); }//if// else { Object value = foregroundColor.getValue(null); IIterator iterator = getNodesByItem().keyIterator(); while(iterator.hasNext()) { foregroundColorHolder.setValue(iterator.next(), value); }//while// }//else// }//if// }//else if// else if(resourceAssociation == font) { //Refresh the item cell data.// if(font.refresh(null)) { if(item != null) { fontHolder.setValue(item, font.getValue(item)); }//if// else { Object value = font.getValue(null); IIterator iterator = getNodesByItem().keyIterator(); while(iterator.hasNext()) { fontHolder.setValue(iterator.next(), value); }//while// }//else// }//if// }//else if// }//if// }//internalOnValueChanged()// /** * Called when a resource holder's value has been altered. * Subclasses must override this to handle change to resource holders they define. * @param resourceHolder The resource holder whose value changed. * @param oldValue The old value. * @param newValue The new value. */ protected void internalResourceHolderChanged(ResourceHolder resourceHolder, Object oldValue, Object newValue, int flags) { if(resourceHolder == toolTipTextHolder) { getSwtTree().getColumn(index).setToolTipText((String) toolTipTextHolder.getValue()); }//if// else if(resourceHolder == headerTextHolder) { column.setText((String) headerTextHolder.getValue()); }//else if// else if(resourceHolder == headerImageHolder) { if(oldValue != null) { destroyImage((JefImage) oldValue); }//if// column.setImage(createImage((JefImage) headerImageHolder.getValue())); }//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 row The row whose value has changed. * @param oldValue The old value for the resource. * @param newValue The new value for the resource. */ 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); ((TreeItem) 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); ((TreeItem) 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); ((TreeItem) 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); ((TreeItem) 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 rows The rows whose values have changed. * @param oldValue The old value for the resource. * @param newValue The new value for the resource. */ 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); ((TreeItem) 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); ((TreeItem) rowObject.getDisplayNodes().get(index)).setBackground(columnIndex, createColor((JefColor) newValue)); }//for// }//if// }//while// }//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); ((TreeItem) 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); ((TreeItem) 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()// /** * 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.view.swt.IInternalAbstractComponent#getShell() */ public Shell getShell() { return getSwtControl().getShell(); }//getShell()// /* (non-Javadoc) * @see com.foundation.view.swt.IInternalAbstractComponent#getSwtWidget() */ public Widget getSwtWidget() { return null; }//getSwtWidget()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#initialize() */ protected void initialize() { headerText.initialize(); headerImage.initialize(); cellText.initialize(); cellImage.initialize(); backgroundColor.initialize(); foregroundColor.initialize(); font.initialize(); //Force the header data to refresh since it gets set after sending the new header message to the client.// if(refreshHeaderData()) { headerTextHolder.setValue(headerText.getValue()); headerImageHolder.setValue(headerImage.getValue()); }//if// }//initialize()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#refresh() */ protected void refresh() { }//refresh()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent.AbstractColumn#release() */ protected void release() { unregisterAllItems(); headerText.release(); headerTextHolder.release(); headerImage.release(); headerImageHolder.release(); cellText.release(); cellImage.release(); cellImageHolder.release(); backgroundColor.release(); backgroundColorHolder.release(); foregroundColor.release(); foregroundColorHolder.release(); font.release(); fontHolder.release(); }//release()// /** * Determines whether the column header is resizable by the user. * @param headerResizable Whether the header is resizable. */ public void setHeaderResizeable(boolean headerResizable) { if(this.headerResizable != headerResizable) { this.headerResizable = headerResizable; getSwtTree().getColumn(index).setResizable(headerResizable); }//if// }//setHeadersResizeable()// /** * Sets the columns alignment. * @param alignment The column's alignment */ public void setAlignment(int alignment) { if(this.alignment != alignment) { this.alignment = alignment; getSwtTree().getColumn(index).setAlignment(alignment); }//if// }//setAlignment()// /** * Sets the column's width. * @param width The number of pixels of width for the column. */ public void setWidth(int width) { if(this.width != width) { this.width = width; getSwtTree().getColumn(index).setWidth(width); }//if// }//setWidth()// /** * 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(getSwtTree().getColumn(index).getWidth() < minimumWidth) { getSwtTree().getColumn(index).setWidth(minimumWidth); }//if// }//if// }//setMinimumWidth()// /** * Sets the column's tool tip text. * @param toolTipText The tool tip for the column. */ public void setToolTipText(String toolTipText) { toolTipTextHolder.setValue(toolTipText); }//setToolTipText()// /** * Sets the column's tool tip text. * @param toolTipText The tool tip for the column. */ public void setToolTipText(ResourceReference toolTipText) { toolTipTextHolder.setValue(toolTipText); }//setToolTipText()// /** * Sets whether the column is moveable by the user. * @param moveable Whether the column can be moved. */ public void setMoveable(boolean moveable) { if(this.moveable != moveable) { this.moveable = moveable; getSwtTree().getColumn(index).setResizable(moveable); }//if// }//setMoveable()// /** * Sets the column's default header text which is used when no other text is available. * @param headerText The column's default header text. */ public void setHeaderText(String headerText) { verifyThread(); this.headerText.setDefaultValue(headerText); }//setHeaderText()// /** * Sets the column's default header text which is used when no other text is available. * @param headerText The column's default header text. */ public void setHeaderText(ResourceReference headerText) { verifyThread(); this.headerText.setDefaultValue(headerText); }//setHeaderText()// /** * Sets the column's default header image which is used when no other image is available. * @param headerImage The column's default header image. */ public void setHeaderImage(JefImage headerImage) { verifyThread(); this.headerImage.setDefaultValue(headerImage); }//setHeaderImage()// /** * Sets the column's default header image which is used when no other image is available. * @param headerImage The column's default header image. */ public void setHeaderImage(ResourceReference headerImage) { verifyThread(); this.headerImage.setDefaultValue(headerImage); }//setHeaderImage()// /** * Sets the column's default cell text which is used when no other text is available. * @param cellText The column's default cell text. */ public void setCellText(String cellText) { verifyThread(); this.cellText.setDefaultValue(cellText); }//setCellText()// /** * Sets the column's default cell image which is used when no other image is available. * @param cellImage The column's default cell image. */ public void setCellImage(JefImage cellImage) { verifyThread(); this.cellImage.setDefaultValue(cellImage); }//setCellImage()// /** * Sets the column's default cell image which is used when no other image is available. * @param cellImage The column's default cell image. */ public void setCellImage(ResourceReference cellImage) { verifyThread(); this.cellImage.setDefaultValue(cellImage); }//setCellImage()// /** * Sets the column's default background color which is used when no other background color is available. * @param backgroundColor The column's default background color. */ public void setBackgroundColor(JefColor backgroundColor) { verifyThread(); this.backgroundColor.setDefaultValue(backgroundColor); }//setBackgroundColor()// /** * Sets the column's default background color which is used when no other background color is available. * @param backgroundColor The column's default background color. */ public void setBackgroundColor(ResourceReference backgroundColor) { verifyThread(); this.backgroundColor.setDefaultValue(backgroundColor); }//setBackgroundColor()// /** * Sets the column's default background color which is used when no other foreground color is available. * @param foregroundColor The column's default foreground color. */ public void setForegroundColor(JefColor foregroundColor) { verifyThread(); this.foregroundColor.setDefaultValue(foregroundColor); }//setForegroundColor()// /** * Sets the column's default background color which is used when no other foreground color is available. * @param foregroundColor The column's default foreground color. */ public void setForegroundColor(ResourceReference foregroundColor) { verifyThread(); this.foregroundColor.setDefaultValue(foregroundColor); }//setForegroundColor()// /** * Sets the column's default background color which is used when no other font is available. * @param font The column's default font. */ public void setFont(JefFont[] font) { verifyThread(); this.font.setDefaultValue(font); }//setFont()// /** * Sets the column's default background color which is used when no other font is available. * @param font The column's default font. */ public void setFont(ResourceReference font) { verifyThread(); this.font.setDefaultValue(font); }//setFont()// /** * Sets the association container used to access the header text. * @param container The header text association metadata. */ public void setHeaderTextAssociation(SingleAssociationContainer container) { verifyThread(); this.headerText.setAssociations(container); }//setHeaderTextAssociation()// /** * Sets the association container used to access the header image. * @param container The header image association metadata. */ public void setHeaderImageAssociation(SingleAssociationContainer container) { verifyThread(); this.headerImage.setAssociations(container); }//setHeaderImageAssociation()// /** * Sets the association container used to access the cell text. * @param container The cell text association metadata. */ public void setCellTextAssociation(MultiAssociationContainer container) { verifyThread(); this.cellText.setAssociations(container); }//setCellTextAssociation()// /** * Sets the association container used to access the cell image. * @param container The cell image association metadata. */ public void setCellImageAssociation(MultiAssociationContainer container) { verifyThread(); this.cellImage.setAssociations(container); }//setCellImageAssociation()// /** * Sets the association container used to access the background color. * @param container The background color association metadata. */ public void setBackgroundColorAssociation(MultiAssociationContainer container) { verifyThread(); this.backgroundColor.setAssociations(container); }//setBackgroundColorAssociation()// /** * Sets the association container used to access the foreground color. * @param container The foreground color association metadata. */ public void setForegroundColorAssociation(MultiAssociationContainer container) { verifyThread(); this.foregroundColor.setAssociations(container); }//setForegroundColorAssociation()// /** * Sets the association container used to access the font. * @param container The font association metadata. */ public void setFontAssociation(MultiAssociationContainer container) { verifyThread(); this.font.setAssociations(container); }//setFontAssociation()// /** * Determines whether there are renderers capable of painting their data. * @return Whether one or more renderers, that support painting a cell, were defined for this column. */ public boolean hasPaintCapableRenderers() { return paintRendererCounter != 0; }//hasPaintCapableRenderers()// /** * Determines whether there are renderers capable of creating a component. * @return Whether one or more renderers, that support creating a component, were defined for this column. */ public boolean hasComponentCapableRenderers() { return componentRendererCounter != 0; }//hasComponentCapableRenderers()// /** * Adds a cell component to the column for use with rows that match the given type name. * @param typeName The name of the class associated with the rows that will use the component to display cell data. * @param component The component that lets the user view and optionally interact with cell data. */ public void addCellComponent(Class type, CellComponent component) { if(renderers == null) { renderers = new LiteList(5, 20); }//if// renderers.add(new Renderer(type, component)); if(component.usePaint()) { paintRendererCounter++; }//if// if(component.useControl() != CellComponent.USE_CONTROL_NEVER) { componentRendererCounter++; }//if// }//addCellComponent()// /** * Gets the cell component for the given row type. * @param rowType The type of data displayed on the row for which the cell component will be used. * @return The cell component used to render and optionally interact with the cell data. This may be null in which case the default rendering will be used. */ public CellComponent getCellComponent(Class rowType) { CellComponent result = null; if(renderers != null) { for(int index = 0; (result == null) && (index < renderers.getSize()); index++) { Renderer renderer = (Renderer) renderers.get(index); if(renderer.type.isAssignableFrom(rowType)) { result = renderer.component; }//if// }//for// }//if// return result; }//getCellComponent()// /* (non-Javadoc) * @see com.foundation.view.IMultiResourceAssociationChangeListener#getResourceService() */ public AbstractResourceService getResourceService() { return SimpleTreeTable.this.getResourceService(); }//getResourceService()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#getDecorationManager() */ public DecorationManager getDecorationManager() { return SimpleTreeTable.this.getDecorationManager(); }//getDecorationManager()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#addDecoration(com.foundation.view.AbstractDecoration) */ public void addDecoration(AbstractDecoration decoration) { //Never used.// }//addDecoration()// /* (non-Javadoc) * @see com.foundation.view.ISingleResourceAssociationChangeListener#removeDecoration(com.foundation.view.AbstractDecoration) */ public void removeDecoration(AbstractDecoration decoration) { //Never used.// }//removeDecoration()// /* (non-Javadoc) * @see com.foundation.view.IMultiResourceAssociationChangeListener#addDecoration(com.foundation.view.ResourceAssociation, java.lang.Object, java.lang.Object, com.foundation.view.AbstractDecoration) */ public void addDecoration(ResourceAssociation association, Object row, Object data, AbstractDecoration decoration) { //TODO: Implement. }//addDecoration()// /* (non-Javadoc) * @see com.foundation.view.IMultiResourceAssociationChangeListener#removeDecoration(com.foundation.view.ResourceAssociation, java.lang.Object, java.lang.Object, com.foundation.view.AbstractDecoration) */ public void removeDecoration(ResourceAssociation association, Object row, Object data, AbstractDecoration decoration) { //TODO: Implement. }//removeDecoration()// }//ColumnData// /** * SimpleTreeTable constructor. *

Not Supported Styles (Yet): STYLE_CHECK, STYLE_VIRTUAL

* @param parent The parent container for this component. * @param name The name of the component. * @param style The style of control to construct. * @see #STYLE_MULTI * @see #STYLE_SINGLE * @see #STYLE_FULL_SELECTION * @see #STYLE_HIDE_SELECTION */ public SimpleTreeTable(Container parent, String name, int style) { super(parent, name, style); allowMultiSelection = ((style & STYLE_MULTI) > 0); }//SimpleTreeTable()// /* (non-Javadoc) * @see com.foundation.view.swt.AbstractComponent#initializeControl(int) */ protected void initializeControl(int style, Object data) { //Create the SWT widget.// setSwtWidget(new org.eclipse.swt.widgets.Tree(((Container) 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(((Container) getContainer()).isInitialized()) { ((Container) getContainer()).getSwtComposite().layout(true, true); }//if// }//initializeControl()// /** * Gets the SWT tree that represents this simple tree. * @return The SWT tree providing visualization for this simple tree. */ public org.eclipse.swt.widgets.Tree getSwtTree() { return (org.eclipse.swt.widgets.Tree) getSwtWidget(); }//getSwtTree()// /* (non-Javadoc) * @see com.foundation.view.swt.IAbstractSwtContainer#getSwtComposite() */ public Composite getSwtComposite() { return getSwtTree(); }//getSwtComposite()// /* (non-Javadoc) * @see com.foundation.view.swt.ICellContainer#getSwtComposite(java.lang.Object) */ public Composite getSwtComposite(Object rowObject) { return getSwtTree(); }//getSwtComposite()// /** * Adjusts the visible range of items to show some or all of the selections. */ public void showSelection() { getSwtTree().showSelection(); }//showSelection()// /** * Sets the row's default background color. * @param backgroundColor The default background color. */ public void setRowBackgroundColor(JefColor backgroundColor) { verifyThread(); this.rowBackgroundColor.setDefaultValue(backgroundColor); }//setRowBackgroundColor()// /** * Sets the row's default background color. * @param backgroundColor The default background color. */ public void setRowBackgroundColor(ResourceReference backgroundColor) { verifyThread(); this.rowBackgroundColor.setDefaultValue(backgroundColor); }//setRowBackgroundColor()// /** * Sets the row's default foreground color. * @param foregroundColor The default foreground color. */ public void setRowForegroundColor(JefColor foregroundColor) { verifyThread(); this.rowForegroundColor.setDefaultValue(foregroundColor); }//setRowForegroundColor()// /** * Sets the row's default foreground color. * @param foregroundColor The default foreground color. */ public void setRowForegroundColor(ResourceReference foregroundColor) { verifyThread(); this.rowForegroundColor.setDefaultValue(foregroundColor); }//setRowForegroundColor()// /** * Sets the default font for the row. * @param font The font used by the row unless specified otherwise by the column. */ public void setRowFont(JefFont[] font) { verifyThread(); this.rowFont.setDefaultValue(font); }//setRowFont()// /** * Sets the default font for the row. * @param font The font used by the row unless specified otherwise by the column. */ public void setRowFont(ResourceReference font) { verifyThread(); this.rowFont.setDefaultValue(font); }//setRowFont()// /** * Sets the height for the rows. * @param height The row height in pixels. */ public void setRowHeight(Integer height) { verifyThread(); this.rowHeight.setDefaultValue(height); }//setRowHeight()// /** * Sets the association container used to access the row background color. * @param container The row background color association metadata. */ public void setRowBackgroundColorAssociation(MultiAssociationContainer container) { verifyThread(); this.rowBackgroundColor.setAssociations(container); }//setRowBackgroundColorAssociation()// /** * Sets the association container used to access the row foreground color. * @param container The row foreground color association metadata. */ public void setRowForegroundColorAssociation(MultiAssociationContainer container) { verifyThread(); this.rowForegroundColor.setAssociations(container); }//setRowForegroundColorAssociation()// /** * Sets the association container used to access the row font. * @param container The row font association metadata. */ public void setRowFontAssociation(MultiAssociationContainer container) { verifyThread(); this.rowFont.setAssociations(container); }//setRowFontAssociation()// /** * Sets the association container used to access the row height. * @param container The row font association metadata. */ public void setRowHeightAssociation(SingleAssociationContainer container) { verifyThread(); this.rowHeight.setAssociations(container); }//setRowHeightAssociation()// /** * Sets the association container used to access the row's groupings. * @param container The grouping association metadata. */ public void setGroupAssociation(CollectingMultiAssociationContainer container) { verifyThread(); this.groupings.setAssociations(container); }//setGroupAssociation()// /** * Sets the association container used to access the row's children. * @param container The children association metadata. */ public void setChildAssociation(CollectingMultiAssociationContainer container) { verifyThread(); this.children.setAssociations(container); }//setChildAssociation()// /** * 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()// /** * Adds an association for the fit event. * @param association The association. */ public void addFitEventAssociation(IEventAssociation association) { if(fitEventAssociations == null) { fitEventAssociations = new LiteHashSet(4); }//if// association.setChangeListener(this); fitEventAssociations.add(association); if(isInitialized()) { association.register(); }//if// }//setFitEventAssociation()// /** * Adds an association for the fill event. * @param association The association. */ public void addFillEventAssociation(IEventAssociation association) { if(fillEventAssociations == null) { fillEventAssociations = new LiteHashSet(4); }//if// association.setChangeListener(this); fillEventAssociations.add(association); if(isInitialized()) { association.register(); }//if// }//setFillEventAssociation()// /** * Determines whether the view manages the sorting of data. *

TODO: Enable this again if we allow server side sorting.

* @param inViewSorting Whether the view should handle data sorting, otherwise the model will handle it. */ public void setInViewSorting(boolean inViewSorting) { //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.// this.inViewSorting = inViewSorting; }//setInViewSorting()// /** * Determines whether multiple selections are allowed. * @return Whether the user may make multiple collection selections. */ protected boolean allowMultiSelection() { return allowMultiSelection; }//allowMultiSelection()// /** * Determines whether to show the header. * @param showHeader Whether the header is visible. */ public void showHeaders(boolean showHeader) { if(getSwtTree().getHeaderVisible() != showHeader) { getSwtTree().setHeaderVisible(showHeader); }//if// }//showHeaders()// /** * Determines whether to show the grid lines. * @param showGridLines Whether the grid lines are visible. */ public void showGridLines(boolean showGridLines) { if(getSwtTree().getLinesVisible() != showGridLines) { getSwtTree().setLinesVisible(showGridLines); }//if// }//showGridLines()// /** * Adds a new column to the right of the existing columns. *

Note: I have not yet allowed the removal of columns or addition of columns at specific indices or adding columns after initializing the control.

* @return The column that was added. This can be used to setup the column's display properties. */ public ColumnData addColumn() { ColumnData result = null; if(!isInitialized()) { result = new ColumnData(getColumnCount()); addColumn(result); }//if// //TODO: Support adding columns post-initialization. //If this is supported make sure we call postChangeCollection(). return result; }//addColumn()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#internalOnValueChanged(com.foundation.view.SingleResourceAssociation) */ protected void internalOnValueChanged(SingleResourceAssociation resourceAssociation, int flags) { if(resourceAssociation == rowHeight) { internalViewRefreshRowHeight(); }//if// else { super.internalOnValueChanged(resourceAssociation, flags); }//else// }//internalOnValueChanged()// /* (non-Javadoc) * @see com.foundation.view.swt.AbstractComponent#internalOnValueChanged(com.foundation.view.CollectingMultiResourceAssociation, java.lang.Object) */ protected void internalOnValueChanged(CollectingMultiResourceAssociation resourceAssociation, Object item, Object data) { if(isInitialized()) { if(resourceAssociation == children) { if(item != null) { //Verify that the item cell data has actually been altered.// if(children.refresh(item)) { SimpleNodeData node = (SimpleNodeData) nodesByItem.get(item); if(node != null) { //TODO: Should the client or server retain any open node state? //TODO: Place a message hold. pruneAllChildren(node); openNode(node); }//if// else { Debug.log("Internal Error: Failed to locate a node by its item."); }//else// }//if// }//if// else { updateCellData(null); }//else// }//if// else if(resourceAssociation == groupings) { if(item != null) { IList oldGroupings = groupings.getValues(item, null); //TODO: Reuse list. //Verify that the item cell data has actually been altered.// if(groupings.refresh(item)) { SimpleNodeData node = (SimpleNodeData) nodesByItem.get(item); regroupItem(node, oldGroupings, groupings.getValues(item, null)); //TODO: Reuse list. }//if// }//if// else { updateCellData(null); }//else// }//else if// else { super.internalOnValueChanged(resourceAssociation, item, data); }//else// }//if// }//internalOnValueChanged()// /* (non-Javadoc) * @see com.foundation.view.swt.AbstractComponent#internalOnValueChanged(com.foundation.view.MultiResourceAssociation, java.lang.Object) */ protected void internalOnValueChanged(MultiResourceAssociation resourceAssociation, Object item, Object data, boolean isUpdate) { if(isInitialized()) { if(resourceAssociation == rowBackgroundColor) { if(item != null) { //Verify that the item cell data has actually been altered.// if(rowBackgroundColor.refresh(item)) { rowBackgroundColorHolder.setValue(item, rowBackgroundColor.getValue(item)); }//if// }//if// else { updateCellData(null); }//else// }//if// else if(resourceAssociation == rowForegroundColor) { if(item != null) { //Verify that the item cell data has actually been altered.// if(rowForegroundColor.refresh(item)) { rowForegroundColorHolder.setValue(item, rowForegroundColor.getValue(item)); }//if// }//if// else { updateCellData(null); }//else// }//else if// else if(resourceAssociation == rowFont) { if(item != null) { //Verify that the item cell data has actually been altered.// if(rowFont.refresh(item)) { rowFontHolder.setValue(item, rowFont.getValue(item)); }//if// }//if// else { updateCellData(null); }//else// }//else if// else { super.internalOnValueChanged(resourceAssociation, item, data, isUpdate); }//else// }//if// }//internalOnValueChanged()// /* (non-Javadoc) * @see com.foundation.view.swt.AbstractComponent#internalResourceHolderChanged(com.foundation.view.swt.MultiResourceHolder, com.common.util.IHashSet, java.lang.Object, java.lang.Object) */ protected void internalResourceHolderChanged(MultiResourceHolder resourceHolder, IHashSet rows, Object oldValue, Object newValue) { 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// }//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.view.swt.AbstractComponent#internalResourceHolderChanged(com.foundation.view.swt.MultiResourceHolder, java.lang.Object, java.lang.Object, java.lang.Object) */ protected void internalResourceHolderChanged(MultiResourceHolder resourceHolder, Object row, Object oldValue, Object newValue) { 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// }//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.view.swt.CollectionComponent#internalViewRefreshSelection(java.lang.Object) */ protected void internalViewRefreshSelection(Object selectedItem) { if(selectedItem != null) { SimpleNodeData treeNode = getTreeNodeByItem(selectedItem); if(treeNode == null) { //TODO: Check the currently selected node's children if they haven't been loaded yet. //TODO: Search the entire tree of closed nodes for the node - ensure we don't encounter recursion in the tree. isSelectionInvalid = true; internalViewRefreshSelections((SimpleNodeData[]) null); }//if// else { isSelectionInvalid = false; internalViewRefreshSelections(new SimpleNodeData[] {treeNode}); }//else// }//if// else { //No selection.// internalViewRefreshSelections((SimpleNodeData[]) null); }//else// }//internalViewRefreshSelection()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#internalViewRefreshSelections(com.common.util.ICollection, com.common.util.ICollection) */ protected void internalViewRefreshSelections(ICollection newSelections, ICollection oldSelections) { if(newSelections != null) { LiteList selectedNodes = new LiteList(newSelections.getSize()); IIterator selectionIterator = newSelections.iterator(); SimpleNodeData[] nodeArray = null; //Apply differences between the selection collection and the control selection. Also remove all impossible selections.// while(selectionIterator.hasNext()) { Object selection = selectionIterator.next(); SimpleNodeData treeNode = getTreeNodeByItem(selection); if(treeNode == null) { //TODO: Check the currently selected node's children if they haven't been loaded yet. //TODO: Search the entire tree of closed nodes for the node - ensure we don't encounter recursion in the tree. //An invalid selection because the selection is not in the collection of displayed values.// selectionIterator.remove(); }//if// else { selectedNodes.add(treeNode); }//else// }//while// selectedNodes.toArray(nodeArray = new SimpleNodeData[selectedNodes.getSize()]); internalViewRefreshSelections(nodeArray); }//if// else { //Remove all selections.// internalViewRefreshSelections((NodeData[]) null); }//else// }//internalViewRefreshSelections()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#itemAdded(java.lang.Object, int) */ protected void itemAdded(Object item, int index) { placeItem(rootNode, item); }//itemAdded()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#itemRemoved(java.lang.Object, int) */ protected void itemRemoved(Object item, int index) { SimpleNodeData node = getTreeNodeByItem(item); if(node != null) { pruneNode(rootNode, node); }//if// else { Debug.log("Internal Tree Error: Couldn't find the requested node."); }//else// }//itemRemoved()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#itemAllRemoved() */ protected void itemAllRemoved() { pruneAllChildren(rootNode); }//itemAllRemoved()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#itemSorted(int[]) */ protected void itemSorted(int[] mapping) { //Do nothing.// }//itemSorted()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#refreshRowHeaderCellData(java.lang.Object) */ protected boolean refreshRowHeaderCellData(Object item) { boolean result = false; if(rowBackgroundColor.refresh(item)) { rowBackgroundColorHolder.setValue(item, rowBackgroundColor.getValue(item), true); result = true; }//if// if(rowForegroundColor.refresh(item)) { rowForegroundColorHolder.setValue(item, rowForegroundColor.getValue(item), true); result = true; }//if// if(rowFont.refresh(item)) { rowFontHolder.setValue(item, rowFont.getValue(item), true); result = true; }//if// return result; }//refreshRowHeaderCellData()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#getRowHeaderCellData(java.lang.Object) */ protected Object getRowHeaderCellData(Object item) { return new SimpleTreeTableCellData(null, null, (JefColor) rowBackgroundColorHolder.getValue(item), (JefColor) rowForegroundColorHolder.getValue(item), (JefFont[]) rowFontHolder.getValue(item)); }//getCellValue()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#registerItem(java.lang.Object) */ protected void registerItem(Object item) { //Does nothing.// //This is performed in the TreeNode.initialize() method.// }//registerItem()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#unregisterItem(java.lang.Object) */ protected void unregisterItem(Object item) { //Do nothing.// //This is performed in the SimpleNodeData.release() method.// }//unregisterItem()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#unregisterItems() */ protected void unregisterItems() { //Do nothing.// //This is performed in the SimpleNodeData.release() method.// }//unregisterItems()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#updateCellData(java.lang.Object, com.foundation.view.swt.TreeComponent.AbstractColumn, java.lang.Object) */ protected void updateCellData(Object item, AbstractColumn column, Object data) { SimpleNodeData node = getTreeNodeByItem(item); IList groupNodes = getGroupTreeNodesByItem(item); //Notify the table component that the cell data needs refreshing.// controlSetCellData(node, column != null ? column.getIndex() : -1, data); //Notify the grouping nodes.// for(int index = 0; index < groupNodes.getSize(); index++) { SimpleNodeData groupNode = (SimpleNodeData) groupNodes.get(index); controlSetCellData(groupNode, column != null ? column.getIndex() : -1, data); }//for// }//updateCellData()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#updateCellData(com.foundation.view.swt.TreeComponent.AbstractColumn) */ protected void updateCellData(AbstractColumn column) { IIterator iterator = nodesByItem.valueIterator(); //Update the non-group nodes.// while(iterator.hasNext()) { SimpleNodeData node = (SimpleNodeData) iterator.next(); if(column == null) { if(refreshRowHeaderCellData(node.getValue())) { Object data = getRowHeaderCellData(node.getValue()); //Notify the table component that the row header cell data needs refreshing.// controlSetCellData(node, -1, data); }//if// }//if// else { if(column.refreshCellData(node.getValue())) { Object data = column.getCellData(node.getValue()); //Notify the table component that the cell data needs refreshing.// controlSetCellData(node, column.getIndex(), data); }//if// }//else// }//while// iterator = groupNodesByItem.valueIterator(); //Update the group nodes.// while(iterator.hasNext()) { IList nodes = (IList) iterator.next(); if(column == null) { for(int index = 0; index < nodes.getSize(); index++) { SimpleNodeData node = (SimpleNodeData) nodes.get(index); if(refreshRowHeaderCellData(node.getValue())) { Object data = getRowHeaderCellData(node.getValue()); //Notify the table component that the row header cell data needs refreshing.// controlSetCellData(node, -1, data); }//if// }//for// }//if// else { for(int index = 0; index < nodes.getSize(); index++) { SimpleNodeData node = (SimpleNodeData) nodes.get(index); if(column.refreshCellData(node.getValue())) { Object data = column.getCellData(node.getValue()); //Notify the table component that the cell data needs refreshing.// controlSetCellData(node, column.getIndex(), data); }//if// }//for// }//else// }//while// }//updateCellData()// /** * 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() { if(!suspendLayoutRenderers) { stopRendering(); try { for(int columnIndex = getColumnCount() - 1; columnIndex >= 0; columnIndex--) { ((ColumnData) getColumn(columnIndex)).layoutRenderers(); }//for// }//try// finally { startRendering(); redraw(); }//finally// }//if// }//layoutRenderers()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#getNodeDataByItem(java.lang.Object) */ protected NodeData getNodeDataByItem(Object item) { return (NodeData) (nodesByItem.containsKey(item) ? nodesByItem.get(item) : groupNodesByItem.get(item)); }//getNodeDataByItem()// /** * Gets the mapping of nodes indexed by the item they are representing. * @return The tree nodes indexed by the item for the row. */ protected IHashMap getNodesByItem() { return nodesByItem; }//getNodesByItem()// /** * Gets the tree node (non-grouping only) for the given item. * @param item The item object. * @return The existing tree node representing the item, or null if one doesn't exist yet. */ protected SimpleNodeData getTreeNodeByItem(Object item) { return (SimpleNodeData) nodesByItem.get(item); }//getTreeNodeByItem()// /** * Gets the group tree nodes for the given item. * @param item The item object. * @return The collection of grouping tree nodes for the given item. This list should never be modified directly. This will never be null, but may be empty. */ protected IList getGroupTreeNodesByItem(Object item) { IList result = (IList) groupNodesByItem.get(item); if(result == null) { result = LiteList.EMPTY_LIST; }//if// return result; }//getTreeNodeByItem()// /** * Opens a node in the tree which will populate its children. *

Note: A call to closeNode must be made for each call to open node. Open node does register for node data change events.

* @param node The node whose children are currently unkown. */ protected void openNode(SimpleNodeData node) { IList childItemCollections = null; //TODO: Use a reusable collection. //Note: This is commeted because the node value is registered when the node is initialized.// //children.registerItem(node.getValue()); children.refresh(node.getValue()); childItemCollections = children.getValues(node.getValue(), childItemCollections); node.setChildNodes(new LiteList(50, 500)); if(childItemCollections != null) { //Iterate over the items or collections of child items and place them in the tree.// for(int index = 0; index < childItemCollections.getSize(); index++) { Object childItemCollection = childItemCollections.get(index); if((childItemCollection instanceof IInlineIndexedCollectionObservable) || (childItemCollection instanceof IInlineCollectionObservable)) { node.addChildListener((ICollection) childItemCollection); }//if// else if(childItemCollection instanceof Object[]) { for(int i = 0; i < ((Object[]) childItemCollection).length; i++) { placeItem(node, ((Object[]) childItemCollection)[i]); }//for// }//else if// else if(childItemCollection instanceof IList) { for(int i = 0; i < ((IList) childItemCollection).getSize(); i++) { placeItem(node, ((IList) childItemCollection).get(i)); }//for// }//else if// else if(childItemCollection instanceof ICollection) { IIterator iterator = ((ICollection) childItemCollection).iterator(); while(iterator.hasNext()) { placeItem(node, iterator.next()); }//while// }//if// else { placeItem(node, childItemCollection); }//else// }//for// }//if// }//openNode()// /** * Places the item as a child of the given node. It may be grouped so that it is an indirect child of the given node. * @param node The node that will have the item as a child. * @param item The non-grouping item that is placed under the node. */ protected void placeItem(SimpleNodeData node, Object item) { SimpleNodeData newNode = getTreeNodeByItem(item); IList groupingItems = null; //TODO: Use a reusable collection. SimpleNodeData nextNode = node; //Reuse existing nodes to allow for a recursive tree.// if(newNode == null) { //Setup the next node (after adding the groupings) with the item node.// newNode = new SimpleNodeData(item, false); newNode.initialize(); }//if// else { newNode.incrementReferenceCount(); }//else// groupings.refresh(item); groupingItems = groupings.getValues(item, null); //Iterate over the grouping items.// for(int groupingItemsIndex = 0; groupingItemsIndex < groupingItems.getSize(); groupingItemsIndex++) { Object grouping = groupingItems.get(groupingItemsIndex); //Ignore null groupings.// //TODO: Allow the developer to replace null with a value via the vml. if(grouping != null) { nextNode = groupItem(nextNode, grouping); }//if// }//for// nextNode.addChildNode(newNode); //If there were groupings then we must link the new node with its parent so that if the groupings change, it can find its parent(s) to alter the grouping nodes it falls under.// if(groupingItems.getSize() > 0) { if(newNode.getParentNodes() == null) { newNode.setParentNodes(new LiteList((Object) node, 10)); }//if// else { newNode.getParentNodes().add(node); }//else// }//if// }//placeItem()// /** * Creates the tree nodes necessary for the given group item and returns the tree node for the grouping. * @param node The node that the grouping belongs under. * @param groupItem The group item data used to create the grouping or find the existing grouping. * @return The grouping tree node. */ protected SimpleNodeData groupItem(SimpleNodeData node, Object groupItem) { int nextNodeChildrenSize = node.getChildNodeCount(); boolean found = false; //Search for an existing grouping of the same or equivalent value.// for(int nextNodeChildIndex = 0; (!found) && (nextNodeChildIndex < nextNodeChildrenSize); nextNodeChildIndex++) { SimpleNodeData nodeChild = node.getChildNode(nextNodeChildIndex); //If we found an existing grouping then use it.// if((nodeChild.isGrouping()) && (Comparator.equals(nodeChild.getValue(), groupItem))) { node = nodeChild; found = true; }//if// }//for// //We didn't find an existing grouping, so create one.// if(!found) { SimpleNodeData newNode = new SimpleNodeData(groupItem, true); newNode.initialize(); newNode.setChildNodes(new LiteList(50, 500)); node.addChildNode(newNode); node = newNode; }//if// return node; }//groupItem()// /** * Changes the groupings that the given node falls under. * @param childNode The node that is grouped. * @param oldGroupings The old list of groupings. * @param newGroupings The new list of groupings. */ protected void regroupItem(SimpleNodeData childNode, IList oldGroupings, IList newGroupings) { IList parentNodes = childNode.getParentNodes(); int changeIndex; int size = Math.min(oldGroupings.getSize(), newGroupings.getSize()); SimpleNodeData[] oldGroupingStack = null; /* The number of groupings shouldn't change unless we count null group values as non-groupings (Note: we are not counting on this though). If we assume static group counts, then we need to find the first changed grouping between the old and new list. Next we must for each parent of the node, iterate through the groupings in reverse order to remove the item and unused groupings. Then for each parent of the node we must iterate through the groupings in forward order to add the item under the new groupings (creating as necessary). Note that we must then build a stack of TreeNodes, starting with the first unchanged grouping, or the parent node, and going to the end. To remove we can then iterate up the stack toward the unchanged node, and then we can iterate from the unchanged node down to where we add the node back to the tree. */ //Determine the index first grouping that has changed from the top (the parent) towards the bottom (the child). for(changeIndex = 0; changeIndex < size; changeIndex++) { if(!oldGroupings.get(changeIndex).equals(newGroupings.get(changeIndex))) { break; }//if// }//for// oldGroupingStack = new SimpleNodeData[oldGroupings.getSize() - changeIndex]; //Iterate over the parents (which if null indicate a root) to regroup the node in all parents.// for(int parentIndex = 0; parentIndex < parentNodes.getSize(); parentIndex++) { SimpleNodeData unchangedNode = (SimpleNodeData) parentNodes.get(parentIndex); SimpleNodeData indexNode = null; //Find the last unchanged node.// for(int index = 0; index < changeIndex; index++) { unchangedNode = unchangedNode.getChildNode(newGroupings.get(index), true); }//for// indexNode = unchangedNode; //Build a stack of changed nodes so we can reverse iterate over them to cleanup.// for(int index = changeIndex, stackIndex = 0; index < oldGroupings.getSize(); index++, stackIndex++) { indexNode = oldGroupingStack[stackIndex] = indexNode.getChildNode(oldGroupings.get(index), true); }//for// //Remove the child node from the last grouping node.// oldGroupingStack[oldGroupingStack.length].removeChildNode(childNode); indexNode = oldGroupingStack[oldGroupingStack.length]; //Reverse iterate over the changed tree nodes to clean them up.// for(int index = oldGroupingStack.length - 1; index >= 0; index--) { oldGroupingStack[index].removeChildNode(indexNode); indexNode.release(); indexNode = oldGroupingStack[index]; //Exit the loop if one of the grouping nodes has more than the removed node as its child.// if(oldGroupingStack[index].getChildNodeCount() > 0) { break; }//if// }//for// indexNode = unchangedNode; //Place the child node under the new groupings.// for(int index = changeIndex; index < newGroupings.getSize(); index++) { SimpleNodeData groupingNode = indexNode.getChildNode(newGroupings.get(index), true); if(groupingNode == null) { groupingNode = new SimpleNodeData(newGroupings.get(index), true); groupingNode.initialize(); indexNode.addChildNode(groupingNode); }//if// indexNode = groupingNode; }//for// }//for// }//regroupItem()// /** * Removes a node and all children from the tree. * This is called when the parent no longer references the given node. * @param parent The node's parent node, or null if the node is a root node. * @param node The node to be removed, along with any groupings it defines. */ protected void pruneNode(SimpleNodeData parent, SimpleNodeData node) { if(!node.isGrouping()) { IList nodeGroups = groupings.getValues(node.getValue(), null); //If the node was grouped, then cleanup the groupings which are no longer required by disconnecting them from their parents.// if(nodeGroups.getSize() > 0) { SimpleNodeData childNode = node; //Build a stack of grouping nodes from closest to the parent to furthest.// for(int groupingIndex = 0; groupingIndex < nodeGroups.getSize(); groupingIndex++) { Object group = nodeGroups.get(groupingIndex); //TODO: Allow null groupings to be converted to a fixed value. if(group == null) { nodeGroups.set(groupingIndex, null); }//if// else { //Replace the grouping item with the actual grouping node in our list.// nodeGroups.set(groupingIndex, parent.getChildNode(group, true)); }//else// }//for// //Reverse iterate over the groupings to remove any that are no longer required.// for(int groupingIndex = nodeGroups.getSize() - 1; (childNode != null) && (groupingIndex >= 0); groupingIndex--) { SimpleNodeData groupNode = (SimpleNodeData) nodeGroups.get(groupingIndex); //Ignore null groupings.// if(groupNode != null) { groupNode.removeChildNode(childNode); if(groupNode.getChildNodeCount() == 0) { childNode = groupNode; }//if// else { childNode = null; }//else// }//if// }//for// }//if// else { parent.removeChildNode(node); }//else// pruneAllChildren(node); }//if// }//pruneNode()// /** * Recursively prunes all the children of the given node. * @param node The node whose children will recursively be pruned from the tree. */ private void pruneAllChildren(SimpleNodeData node) { boolean pruneChildren = node == rootNode; //Ensure we don't try to release a node that is already released (which can occur if the prune operation encounters recusion).// if((node != rootNode) && (node.getReferenceCount() > 0)) { node.decrementReferenceCount(); if(node.getReferenceCount() == 0) { pruneChildren = true; }//if// }//if// //Prune the children if the node was released, or is the root node (which is never released).// if(pruneChildren) { //Recursively prune all children.// while(node.getChildNodeCount() > 0) { SimpleNodeData child = node.getChildNode(node.getChildNodeCount() - 1); //First remove the linkage to the child.// node.removeChildNode(child); //Recursively prune the child's children.// pruneAllChildren(child); }//for// if(node != rootNode) { //Release the node (after the above two lines so the client can find the child/parent node when removing linkage).// node.release(); }//if// }//if// }//pruneAllChildren()// /* (non-Javadoc) * @see com.foundation.view.IViewComponent#viewInitialize() */ protected void internalViewInitialize() { getSwtTree().addSelectionListener(this); getSwtTree().addControlListener(this); getSwtTree().addTreeListener(this); getSwtTree().getVerticalBar().addSelectionListener(this); children.initialize(); groupings.initialize(); rowBackgroundColor.initialize(); rowFont.initialize(); rowForegroundColor.initialize(); rowHeight.initialize(); 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// // if((rowHeightValue > 0) && (getSwtTree().getItemHeight() != rowHeightValue)) { // event.height = rowHeightValue; // SwtUtilities.setRedraw(getSwtTree(), false); // // getSwtTree().getDisplay().asyncExec(new Runnable() { // public void run() { // try { // /* // if(getAutoFit()) { // int controlWidth = getSwtTree().getClientArea().width; // // if(controlWidth != lastControlWidth) { // lastControlWidth = controlWidth; // fit(); // }//if// // }//if// // */ // for(int columnIndex = getColumnCount() - 1; columnIndex >= 0; columnIndex--) { // ((ColumnData) getColumn(columnIndex)).layoutRenderers(); // }//for// // }//try// // finally { // SwtUtilities.setRedraw(getSwtTree(), true); // getSwtTree().redraw(); // }//finally// // }//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(); if(columnData.hasPaintCapableRenderers()) { CellComponent cellComponent = columnData.getCellComponent(rowObject.value.getClass()); //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.value, gc); //Reset the clipping area.// gc.setClipping(rectangle); }//if// }//if// }//handleEvent()// }); if(fillEventAssociations != null) { IIterator iterator = fillEventAssociations.iterator(); while(iterator.hasNext()) { ((IEventAssociation) iterator.next()).register(); }//while// }//if// if(fitEventAssociations != null) { IIterator iterator = fitEventAssociations.iterator(); while(iterator.hasNext()) { ((IEventAssociation) iterator.next()).register(); }//while// }//if// 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().removeSelectionListener(this); getSwtTree().removeControlListener(this); getSwtTree().getVerticalBar().removeSelectionListener(this); getSwtTree().removeTreeListener(this); }//if// children.release(); groupings.release(); rowBackgroundColor.release(); rowBackgroundColorHolder.release(); rowForegroundColor.release(); rowForegroundColorHolder.release(); rowFont.release(); rowFontHolder.release(); rowHeight.release(); if(fillEventAssociations != null) { IIterator iterator = fillEventAssociations.iterator(); while(iterator.hasNext()) { ((IEventAssociation) iterator.next()).unregister(); }//while// }//if// if(fitEventAssociations != null) { IIterator iterator = fitEventAssociations.iterator(); while(iterator.hasNext()) { ((IEventAssociation) iterator.next()).unregister(); }//while// }//if// super.internalViewRelease(); }//internalViewRelease()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#internalViewRefresh() */ protected void internalViewRefresh() { internalViewRefreshRowHeight(); super.internalViewRefresh(); }//internalViewRefresh()// /** * Refreshes the component's row height. */ protected void internalViewRefreshRowHeight() { if(rowHeight.refresh()) { Integer height = (Integer) rowHeight.getValue(); if(height != null) { rowHeightValue = height.intValue(); }//if// else { rowHeightValue = -1; }//else// }//if// }//internalViewRefreshRowHeight()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalViewSynchronize() */ protected void internalViewSynchronize() { super.internalViewSynchronize(); }//internalViewSynchronize()// /* (non-Javadoc) * @see com.foundation.view.swt.AbstractComponent#internalOnEventFired(com.foundation.view.IEventAssociation, java.lang.Object[]) */ protected void internalOnEventFired(IEventAssociation eventAssociation, Object[] eventArguments) { if((fillEventAssociations != null) && (fillEventAssociations.containsValue(eventAssociation))) { lastControlWidth = getSwtTree().getClientArea().width; fill(); }//if// else if((fitEventAssociations != null) && (fitEventAssociations.containsValue(eventAssociation))) { lastControlWidth = getSwtTree().getClientArea().width; fit(); }//else if// else { super.internalOnEventFired(eventAssociation, eventArguments); }//else// }//internalOnEventFired()// /* (non-Javadoc) * @see com.foundation.view.swt.AbstractComponent#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()// /** * 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) { stopRendering(); 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; startRendering(); 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) { int controlWidth = lastControlWidth; TreeColumn[] columns = getSwtTree().getColumns(); int extraWidth = 0; LiteList resizeableColumns = new LiteList(columns.length); int totalMinimumWidth = 0; int availableWidth = controlWidth - extraWidth; int currentColumnWidthTotal = 0; int fixedWidth = 0; 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// stopRendering(); suspendColumnResizeEvents = true; try { for(int index = 0; index < resizeableColumns.getSize(); index++) { ((TreeColumn) resizeableColumns.get(index)).setWidth(columnSizes[index]); }//for// }//try// finally { suspendColumnResizeEvents = false; startRendering(); redraw(); }//finally// }//if// else if((availableWidth < 0) && (ignoredColumn != null)) { int minimumWidth = ((ColumnData) ignoredColumn.getData()).getMinimumWidth(); stopRendering(); 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; startRendering(); 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 table. * @param column The column added. */ protected void internalColumnAdded(ColumnData columnData, final TreeColumn column) { columnData.setTreeColumn(column); column.setData(columnData); //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 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); 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// stopRendering(); 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 { startRendering(); }//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()// }); }//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.view.swt.TreeComponent#controlRemoveAll() */ protected void controlRemoveAll() { getSwtTree().removeAll(); }//controlRemoveAll()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.TreeComponent#controlAddLinkage(com.foundation.tcv.swt.client.TreeComponent.NodeData, com.foundation.tcv.swt.client.TreeComponent.NodeData) */ protected void controlAddLinkage(NodeData parent, NodeData child) { suspendResizeEvents = true; try { 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(); if(parent == rootNode) { TreeItem childItem = null; if((inViewSorting) && (lastSortedColumnIndex != -1) && (getSwtTree().getItemCount() > 0)) { TreeItem[] children = getSwtTree().getItems(); String newText = ((SimpleTreeTableCellData) ((ColumnData) getColumn(lastSortedColumnIndex)).getCellData(child.getValue())).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(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// initializeChildItem(childItem, child); }//if// else { //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 = ((SimpleTreeTableCellData) ((ColumnData) getColumn(lastSortedColumnIndex)).getCellData(child.getValue())).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); //TODO: This code doesn't work so well when opening a node that is displayed multiple times in the view. All instances of the node are opened, instead of just the previously one. We could add data to the tree item that would indicate whether it has ever been opened... //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, child); }//for// }//else// }//try// finally { suspendResizeEvents = false; }//finally// updateTreeEditors(); layoutRenderers(); }//controlAddLinkage()// /** * Initializes the child item with the necessary data components. * @param childItem The child display item to be initialized. * @param parent The parent data node. * @param child The child data node from which the item will be initialized. */ protected void initializeChildItem(TreeItem childItem, NodeData child) { SimpleNodeData childNode = (SimpleNodeData) child; SimpleTreeTableCellData cellData; refreshRowHeaderCellData(childNode.value); cellData = ((SimpleNodeData) child).getRowData(); childItem.setBackground(createColor(cellData.getBackground())); childItem.setForeground(createColor(cellData.getForeground())); childItem.setFont(createFont(cellData.getFont())); for(int columnIndex = 0; columnIndex < getColumnCount(); columnIndex++) { getColumn(columnIndex).refreshCellData(childNode.value); cellData = childNode.getCellData(columnIndex); childItem.setText(columnIndex, cellData.getText() == null ? "" : cellData.getText()); childItem.setImage(columnIndex, createImage(cellData.getImage())); childItem.setBackground(columnIndex, createColor(cellData.getBackground())); childItem.setForeground(columnIndex, createColor(cellData.getForeground())); childItem.setFont(columnIndex, createFont(cellData.getFont())); }//for// //If there may be children then create a dummy child which will be replaced upon opening the tree node.// if((childNode.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#controlRemoveLinkage(com.foundation.tcv.swt.client.TreeComponent.NodeData, com.foundation.tcv.swt.client.TreeComponent.NodeData) */ protected void controlRemoveLinkage(NodeData parent, NodeData child) { if((parent != null) && (parent != rootNode)) { IList displayNodes = parent.getDisplayNodes(); parent.getChildren().remove(child); child.decrementParentCount(); //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(); }//controlRemoveLinkage()// /** * 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#controlSetCellData(com.foundation.tcv.swt.client.TreeComponent.NodeData, int, java.lang.Object) */ protected void controlSetCellData(NodeData nodeData, int columnIndex, Object data) { SimpleTreeTableCellData cellData = (SimpleTreeTableCellData) data; IList treeItems = nodeData.getDisplayNodes(); for(int index = 0; index < treeItems.getSize(); index++) { TreeItem treeItem = (TreeItem) treeItems.get(index); if(!getSwtTree().isDisposed()) { if(columnIndex != -1) { ColumnData columnData = (ColumnData) getSwtTree().getColumn(columnIndex).getData(); treeItem.setText(columnIndex, cellData.getText() == null ? "" : cellData.getText()); columnData.backgroundColorHolder.setValue(nodeData.getValue(), cellData.getBackground()); columnData.foregroundColorHolder.setValue(nodeData.getValue(), cellData.getForeground()); columnData.fontHolder.setValue(nodeData.getValue(), cellData.getFont()); }//if// else { //TODO: Re-enable this if SWT ever allows for header text. Currently the header text is redirected to the first column's text.// //tableItem.setText(cellData.getText() == null ? "" : cellData.getText()); rowBackgroundColorHolder.setValue(nodeData.getValue(), cellData.getBackground()); rowForegroundColorHolder.setValue(nodeData.getValue(), cellData.getForeground()); rowFontHolder.setValue(nodeData.getValue(), cellData.getFont()); }//else// }//if// }//for// }//controlSetCellData()// /* (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.view.swt.TreeComponent#controlAddColumn() */ protected void controlAddColumn(AbstractColumn column) { if(!getSwtTree().isDisposed()) { internalColumnAdded((ColumnData) column, new TreeColumn(getSwtTree(), 0)); }//if// }//controlAddColumn()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#controlAddColumn(com.foundation.view.swt.TreeComponent.AbstractColumn, int) */ protected void controlAddColumn(AbstractColumn column, int columnIndex) { if(!getSwtTree().isDisposed()) { internalColumnAdded((ColumnData) column, new TreeColumn(getSwtTree(), 0, columnIndex)); }//if// }//controlAddColumn()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#controlRemoveColumn(int) */ protected void controlRemoveColumn(int columnIndex) { if(!getSwtTree().getColumn(columnIndex).isDisposed()) { getSwtTree().getColumn(columnIndex).dispose(); }//if// }//controlRemoveColumn()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#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.view.swt.TreeComponent#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.view.swt.TreeComponent#controlRemoveAllSelections() */ protected void controlRemoveAllSelections() { if(!getSwtTree().isDisposed()) { getSwtTree().deselectAll(); }//if// }//controlRemoveAllSelections()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#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.view.swt.TreeComponent#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.view.swt.TreeComponent#controlGetSelection() */ protected NodeData controlGetSelection() { return getSwtTree().getSelectionCount() == 1 ? (NodeData) getSwtTree().getSelection()[0].getData() : null; }//controlGetSelection()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#controlGetSelections() */ protected NodeData[] controlGetSelections() { NodeData[] results = null; if(getSwtTree().getSelectionCount() > 0) { TreeItem[] treeItems = getSwtTree().getSelection(); results = new NodeData[treeItems.length]; for(int index = 0; index < treeItems.length; index++) { results[index] = (NodeData) treeItems[index].getData(); }//for// }//if// return results; }//controlGetSelections()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#controlGetSelectionCount() */ protected int controlGetSelectionCount() { return getSwtTree().getSelectionCount(); }//controlGetSelectionCount()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#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; }//controlIsSelected()// /* (non-Javadoc) * @see com.foundation.view.swt.TreeComponent#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()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#internalPreChangeCollection() */ protected void internalPreChangeCollection() { super.internalPreChangeCollection(); suspendUpdateTreeEditors = true; suspendLayoutRenderers = true; }//internalPreChangeCollection()// /* (non-Javadoc) * @see com.foundation.view.swt.CollectionComponent#internalPostChangeCollection() */ protected void internalPostChangeCollection() { super.internalPostChangeCollection(); suspendUpdateTreeEditors = false; suspendLayoutRenderers = false; //Update the editors and renderers after large scale changes to the collection.// updateTreeEditors(); layoutRenderers(); }//internalPostChangeCollection()// /** * 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.destroyCellControl(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(); if(columnData.hasComponentCapableRenderers()) { CellComponent cellComponent = columnData.getCellComponent(rowObject.value != null ? rowObject.value.getClass() : null); //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.value); 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// }//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() { 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(); //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// }//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()// /* (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(getDoubleClickMethod() != null) { //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.// if(getDoubleClickMethod().getIsValueHolderAssociated()) { getDoubleClickMethod().invoke(null, true); //For now we will assume no parameters. It would be nice to pass the selection perhaps.// }//if// else { Object selection = null; //Determine which selection to invoke the method on.// if(allowMultiSelection()) { ICollection selections = getModelSelections(); if((selections != null) && (selections.getSize() > 0)) { selection = selections.iterator().next(); }//if// }//if// else { selection = getModelSelection(); }//else// if(selection != null) { //Invoke the method on the selection object if there is one.// getDoubleClickMethod().invoke(selection, null, true); }//if// }//else// }//try// finally { //Reset the delay and send the messages.// disableAutoSynchronizeDelay = false; }//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; //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// //Open the node by adding the child TreeNode instances.// openNode(parent); //If the children are empty then there are no children for this node.// if(parent.getChildren() == null) { parent.setChildren(LiteList.EMPTY_LIST); }//if// //TODO: Remove this unless it turns out to be required. //Expand the parent node.// //parentItem.setExpanded(true); }//if// else { replaceTreeItemChildren(parentItem, parent); }//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}); }//if// updateTreeEditors(); layoutRenderers(); }//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(); //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, child); }//for// }//replaceTreeItemChildren()// /** * Determines whether the selection event is forced on a row when a node is opened by the user. * @param selectOnOpen Whether the selection even is forced. */ public void setSelectOnOpen(boolean selectOnOpen) { if(this.selectOnOpen != selectOnOpen) { this.selectOnOpen = selectOnOpen; }//if// }//selectOnOpen()// /** * 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()// }//SimpleTreeTable//