blob: e59310fa8c496014af6c64301ebbe50d6753abe8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.viewers;
import java.util.*;
import java.util.List;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.util.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.*;
import org.eclipse.swt.widgets.*;
/**
* Abstract base implementation for tree-structure-oriented viewers
* (trees and table trees).
* <p>
* Nodes in the tree can be in either an expanded or a collapsed state,
* depending on whether the children on a node are visible. This class
* introduces public methods for controlling the expanding and collapsing
* of nodes.
* </p>
* <p>
* Content providers for abstract tree viewers must implement the <code>ITreeContentProvider</code>
* interface.
* </p>
*
* @see TreeViewer
* @see TableTreeViewer
*/
public abstract class AbstractTreeViewer extends StructuredViewer {
/**
* Constant indicating that all levels of the tree should be expanded or collapsed.
*
* @see #expandToLevel
* @see #collapseToLevel
*/
public static final int ALL_LEVELS = -1;
/**
* List of registered tree listeners (element type: <code>TreeListener</code>).
*/
private ListenerList treeListeners = new ListenerList(1);
/**
* The level to which the tree is automatically expanded each time
* the viewer's input is changed (that is, by <code>setInput</code>).
* A value of 0 means that auto-expand is off.
*
* @see #setAutoExpandLevel
*/
private int expandToLevel = 0;
/**
* The safe runnable used to call the label provider.
*/
private UpdateItemSafeRunnable safeUpdateItem =
new UpdateItemSafeRunnable();
class UpdateItemSafeRunnable extends SafeRunnable {
Object element;
Item item;
boolean exception = false;
public void run() {
if (exception)
return;
doUpdateItem(item, element);
}
public void handleException(Throwable e) {
super.handleException(e);
//If and unexpected exception happens, remove it
//to make sure the application keeps running.
exception = true;
}
}
/**
* Creates an abstract tree viewer. The viewer has no input, no content provider, a
* default label provider, no sorter, no filters, and has auto-expand turned off.
*/
protected AbstractTreeViewer() {
}
/**
* Adds the given child elements to this viewer as children of the given parent element.
* If this viewer does not have a sorter, the elements are added at the end of the
* parent's list of children in the order given; otherwise, the elements are inserted
* at the appropriate positions.
* <p>
* This method should be called (by the content provider) when elements
* have been added to the model, in order to cause the viewer to accurately
* reflect the model. This method only affects the viewer, not the model.
* </p>
*
* @param parentElement the parent element
* @param childElements the child elements to add
*/
public void add(Object parentElement, Object[] childElements) {
Assert.isNotNull(parentElement);
Assert.isNotNull(childElements);
Widget widget = findItem(parentElement);
// If parent hasn't been realized yet, just ignore the add.
if (widget == null)
return;
Control tree = getControl();
// optimization!
// if the widget is not expanded we just invalidate the subtree
if (widget instanceof Item) {
Item ti = (Item) widget;
if (!getExpanded(ti)) {
boolean needDummy = isExpandable(parentElement);
boolean haveDummy = false;
// remove all children
Item[] items = getItems(ti);
for (int i = 0; i < items.length; i++) {
if (items[i].getData() != null) {
disassociate(items[i]);
items[i].dispose();
} else {
if (needDummy && !haveDummy) {
haveDummy = true;
} else {
items[i].dispose();
}
}
}
// append a dummy if necessary
if (needDummy && !haveDummy) {
newItem(ti, SWT.NULL, -1);
} else {
// XXX: Workaround (PR missing)
tree.redraw();
}
return;
}
}
if (childElements.length > 0) {
Object[] filtered = filter(childElements);
for (int i = 0; i < filtered.length; i++) {
Object element = filtered[i];
int index = indexForElement(widget, element);
createTreeItem(widget, filtered[i], index);
}
}
}
/**
* Returns the index where the item should be inserted.
* @param parent The parent widget the element will be inserted into.
* @param element The element to insert.
*/
protected int indexForElement(Widget parent, Object element) {
ViewerSorter sorter = getSorter();
Item[] items = getChildren(parent);
if (sorter == null)
return items.length;
int count = items.length;
int min = 0, max = count - 1;
while (min <= max) {
int mid = (min + max) / 2;
Object data = items[mid].getData();
int compare = sorter.compare(this, data, element);
if (compare == 0) {
// find first item > element
while (compare == 0) {
++mid;
if (mid >= count) {
break;
}
data = items[mid].getData();
compare = sorter.compare(this, data, element);
}
return mid;
}
if (compare < 0)
min = mid + 1;
else
max = mid - 1;
}
return min;
}
/**
* Adds the given child element to this viewer as a child of the given parent element.
* If this viewer does not have a sorter, the element is added at the end of the
* parent's list of children; otherwise, the element is inserted at the appropriate position.
* <p>
* This method should be called (by the content provider) when a single element
* has been added to the model, in order to cause the viewer to accurately
* reflect the model. This method only affects the viewer, not the model.
* Note that there is another method for efficiently processing the simultaneous
* addition of multiple elements.
* </p>
*
* @param parentElement the parent element
* @param childElement the child element
*/
public void add(Object parentElement, Object childElement) {
add(parentElement, new Object[] { childElement });
}
/**
* Adds the given SWT selection listener to the given SWT control.
*
* @param control the SWT control
* @param listener the SWT selection listener
*
* @deprecated
*/
protected void addSelectionListener(
Control control,
SelectionListener listener) {
}
/**
* Adds a listener for expand and collapse events in this viewer.
* Has no effect if an identical listener is already registered.
*
* @param listener a tree viewer listener
*/
public void addTreeListener(ITreeViewerListener listener) {
treeListeners.add(listener);
}
/**
* Adds the given SWT tree listener to the given SWT control.
*
* @param control the SWT control
* @param listener the SWT tree listener
*/
protected abstract void addTreeListener(
Control control,
TreeListener listener);
/* (non-Javadoc)
* @see StructuredViewer#associate(Object, Item)
*/
protected void associate(Object element, Item item) {
Object data = item.getData();
if (data != null && data != element && equals(data, element)) {
// workaround for PR 1FV62BT
// assumption: elements are equal but not identical
// -> remove from map but don't touch children
unmapElement(data, item);
item.setData(element);
mapElement(element, item);
} else {
// recursively disassociate all
super.associate(element, item);
}
}
/**
* Collapses all nodes of the viewer's tree, starting with the root.
* This method is equivalent to <code>collapseToLevel(ALL_LEVELS)</code>.
*/
public void collapseAll() {
Object root = getRoot();
if (root != null) {
collapseToLevel(root, ALL_LEVELS);
}
}
/**
* Collapses the subtree rooted at the given element to the given level.
*
* @param element the element
* @param level non-negative level, or <code>ALL_LEVELS</code> to collapse
* all levels of the tree
*/
public void collapseToLevel(Object element, int level) {
Widget w = findItem(element);
if (w != null)
internalCollapseToLevel(w, level);
}
/**
* Creates all children for the given widget.
* <p>
* The default implementation of this framework method assumes
* that <code>widget.getData()</code> returns the element corresponding
* to the node. Note: the node is not visually expanded! You may have to
* call <code>parent.setExpanded(true)</code>.
* </p>
*
* @param widget the widget
*/
protected void createChildren(final Widget widget) {
final Item[] tis = getChildren(widget);
if (tis != null && tis.length > 0) {
Object data = tis[0].getData();
if (data != null)
return; // children already there!
}
BusyIndicator.showWhile(widget.getDisplay(), new Runnable() {
public void run() {
// fix for PR 1FW89L7:
// don't complain and remove all "dummies" ...
if (tis != null) {
for (int i = 0; i < tis.length; i++) {
tis[i].dispose();
}
}
Object d = widget.getData();
if (d != null) {
Object parentElement = d;
Object[] children = getSortedChildren(parentElement);
for (int i = 0; i < children.length; i++) {
createTreeItem(widget, children[i], -1);
}
}
}
});
}
/**
* Creates a single item for the given parent and synchronizes it with
* the given element.
*
* @param parent the parent widget
* @param element the element
* @param index if non-negative, indicates the position to insert the item
* into its parent
*/
protected void createTreeItem(Widget parent, Object element, int index) {
Item item = newItem(parent, SWT.NULL, index);
updateItem(item, element);
updatePlus(item, element);
}
/**
* The <code>AbstractTreeViewer</code> implementation of this method
* also recurses over children of the corresponding element.
*/
protected void disassociate(Item item) {
super.disassociate(item);
// recursively unmapping the items is only required when
// the hash map is used. In the other case disposing
// an item will recursively dispose its children.
if (usingElementMap())
disassociateChildren(item);
}
/**
* Disassociates the children of the given SWT item from their
* corresponding elements.
*
* @param item the widget
*/
private void disassociateChildren(Item item) {
Item[] items = getChildren(item);
for (int i = 0; i < items.length; i++) {
if (items[i].getData() != null)
disassociate(items[i]);
}
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected Widget doFindInputItem(Object element) {
// compare with root
Object root = getRoot();
if (root == null)
return null;
if (equals(root, element))
return getControl();
return null;
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected Widget doFindItem(Object element) {
// compare with root
Object root = getRoot();
if (root == null)
return null;
Item[] items = getChildren(getControl());
if (items != null) {
for (int i = 0; i < items.length; i++) {
Widget o = internalFindItem(items[i], element);
if (o != null)
return o;
}
}
return null;
}
/**
* Copies the attributes of the given element into the given SWT item.
*
* @param item the SWT item
* @param element the element
*/
protected abstract void doUpdateItem(Item item, Object element);
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected void doUpdateItem(
Widget widget,
Object element,
boolean fullMap) {
if (widget instanceof Item) {
Item item = (Item) widget;
// ensure that backpointer is correct
if (fullMap) {
associate(element, item);
} else {
item.setData(element);
mapElement(element, item);
}
// update icon and label
safeUpdateItem.item = item;
safeUpdateItem.element = element;
try {
Platform.run(safeUpdateItem);
} finally {
safeUpdateItem.item = null;
safeUpdateItem.element = null;
}
}
}
/**
* Expands all nodes of the viewer's tree, starting with the root.
* This method is equivalent to <code>expandToLevel(ALL_LEVELS)</code>.
*/
public void expandAll() {
expandToLevel(ALL_LEVELS);
}
/**
* Expands the root of the viewer's tree to the given level.
*
* @param level non-negative level, or <code>ALL_LEVELS</code> to expand
* all levels of the tree
*/
public void expandToLevel(int level) {
expandToLevel(getRoot(), level);
}
/**
* Expands all ancestors of the given element so that the given element
* becomes visible in this viewer's tree control, and then expands the
* subtree rooted at the given element to the given level.
*
* @param element the element
* @param level non-negative level, or <code>ALL_LEVELS</code> to expand
* all levels of the tree
*/
public void expandToLevel(Object element, int level) {
Widget w = internalExpand(element, true);
if (w != null)
internalExpandToLevel(w, level);
}
/**
* Fires a tree collapsed event.
* Only listeners registered at the time this method is called are notified.
*
* @param event the tree expansion event
*
* @see ITreeViewerListener#treeCollapsed
*/
protected void fireTreeCollapsed(final TreeExpansionEvent event) {
Object[] listeners = treeListeners.getListeners();
for (int i = 0; i < listeners.length; ++i) {
final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
Platform.run(new SafeRunnable() {
public void run() {
l.treeCollapsed(event);
}
public void handleException(Throwable e) {
super.handleException(e);
//If and unexpected exception happens, remove it
//to make sure the application keeps running.
removeTreeListener(l);
}
});
}
}
/**
* Fires a tree expanded event.
* Only listeners registered at the time this method is called are notified.
*
* @param event the tree expansion event
*
* @see ITreeViewerListener#treeExpanded
*/
protected void fireTreeExpanded(final TreeExpansionEvent event) {
Object[] listeners = treeListeners.getListeners();
for (int i = 0; i < listeners.length; ++i) {
final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
Platform.run(new SafeRunnable() {
public void run() {
l.treeExpanded(event);
}
public void handleException(Throwable e) {
super.handleException(e);
//If and unexpected exception happens, remove it
//to make sure the application keeps running.
removeTreeListener(l);
}
});
}
}
/**
* Returns the auto-expand level.
*
* @return non-negative level, or <code>ALL_LEVELS</code> if
* all levels of the tree are expanded automatically
* @see #setAutoExpandLevel
*/
public int getAutoExpandLevel() {
return expandToLevel;
}
/**
* Returns the SWT child items for the given SWT widget.
*
* @param widget the widget
* @return the child items
*/
protected abstract Item[] getChildren(Widget widget);
/**
* Returns whether the given SWT item is expanded or collapsed.
*
* @param item the item
* @return <code>true</code> if the item is considered expanded
* and <code>false</code> if collapsed
*/
protected abstract boolean getExpanded(Item item);
/**
* Returns a list of elements corresponding to expanded nodes in this
* viewer's tree, including currently hidden ones that are marked as
* expanded but are under a collapsed ancestor.
* <p>
* This method is typically used when preserving the interesting
* state of a viewer; <code>setExpandedElements</code> is used during the restore.
* </p>
*
* @return the array of expanded elements
*
* @see #setExpandedElements
*/
public Object[] getExpandedElements() {
ArrayList v = new ArrayList();
internalCollectExpanded(v, getControl());
return v.toArray();
}
/**
* Returns whether the node corresponding to the given element is expanded or collapsed.
*
* @param element the element
* @return <code>true</code> if the node is expanded, and <code>false</code> if collapsed
*/
public boolean getExpandedState(Object element) {
Widget item = findItem(element);
if (item instanceof Item)
return getExpanded((Item) item);
return false;
}
/**
* Returns the number of child items of the given SWT control.
*
* @param control the control
* @return the number of children
*/
protected abstract int getItemCount(Control control);
/**
* Returns the number of child items of the given SWT item.
*
* @param item the item
* @return the number of children
*/
protected abstract int getItemCount(Item item);
/**
* Returns the child items of the given SWT item.
*
* @param item the item
* @return the child items
*/
protected abstract Item[] getItems(Item item);
/**
* Returns the item after the given item in the tree, or
* <code>null</code> if there is no next item.
*
* @param item the item
* @param includeChildren <code>true</code> if the children are
* considered in determining which item is next, and <code>false</code>
* if subtrees are ignored
* @return the next item, or <code>null</code> if none
*/
protected Item getNextItem(Item item, boolean includeChildren) {
if (item == null) {
return null;
}
if (includeChildren && getExpanded(item)) {
Item[] children = getItems(item);
if (children != null && children.length > 0) {
return children[0];
}
}
//next item is either next sibling or next sibling of first
//parent that has a next sibling.
Item parent = getParentItem(item);
if (parent == null) {
return null;
}
Item[] siblings = getItems(parent);
if (siblings != null && siblings.length <= 1) {
return getNextItem(parent, false);
}
for (int i = 0; i < siblings.length; i++) {
if (siblings[i] == item && i < (siblings.length - 1)) {
return siblings[i + 1];
}
}
return getNextItem(parent, false);
}
/**
* Returns the parent item of the given item in the tree, or
* <code>null</code> if there is parent item.
*
* @param item the item
* @return the parent item, or <code>null</code> if none
*/
protected abstract Item getParentItem(Item item);
/**
* Returns the item before the given item in the tree, or
* <code>null</code> if there is no previous item.
*
* @param item the item
* @return the previous item, or <code>null</code> if none
*/
protected Item getPreviousItem(Item item) {
//previous item is either right-most visible descendent of previous
//sibling or parent
Item parent = getParentItem(item);
if (parent == null) {
return null;
}
Item[] siblings = getItems(parent);
if (siblings.length == 0 || siblings[0] == item) {
return parent;
}
Item previous = siblings[0];
for (int i = 1; i < siblings.length; i++) {
if (siblings[i] == item) {
return rightMostVisibleDescendent(previous);
}
previous = siblings[i];
}
return null;
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected Object[] getRawChildren(Object parent) {
if (parent != null) {
if (equals(parent, getRoot()))
return super.getRawChildren(parent);
ITreeContentProvider cp =
(ITreeContentProvider) getContentProvider();
if (cp != null) {
Object[] result = cp.getChildren(parent);
if (result != null)
return result;
}
}
return new Object[0];
}
/**
* Returns all selected items for the given SWT control.
*
* @param control the control
* @return the list of selected items
*/
protected abstract Item[] getSelection(Control control);
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected List getSelectionFromWidget() {
Widget[] items = getSelection(getControl());
ArrayList list = new ArrayList(items.length);
for (int i = 0; i < items.length; i++) {
Widget item = items[i];
Object e = item.getData();
if (e != null)
list.add(e);
}
return list;
}
/**
* Handles a tree collapse event from the SWT widget.
*
* @param event the SWT tree event
*/
protected void handleTreeCollapse(TreeEvent event) {
if (event.item.getData() != null) {
fireTreeCollapsed(
new TreeExpansionEvent(this, event.item.getData()));
}
}
/**
* Handles a tree expand event from the SWT widget.
*
* @param event the SWT tree event
*/
protected void handleTreeExpand(TreeEvent event) {
createChildren(event.item);
if (event.item.getData() != null) {
fireTreeExpanded(
new TreeExpansionEvent(this, event.item.getData()));
}
}
/* (non-Javadoc)
* Method declared on Viewer.
*/
protected void hookControl(Control control) {
super.hookControl(control);
addTreeListener(control, new TreeListener() {
public void treeExpanded(TreeEvent event) {
handleTreeExpand(event);
}
public void treeCollapsed(TreeEvent event) {
handleTreeCollapse(event);
}
});
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
* Builds the initial tree and handles the automatic expand feature.
*/
protected void inputChanged(Object input, Object oldInput) {
preservingSelection(new Runnable() {
public void run() {
Control tree = getControl();
boolean useRedraw = true;
// (size > REDRAW_THRESHOLD) || (table.getItemCount() > REDRAW_THRESHOLD);
if (useRedraw)
tree.setRedraw(false);
removeAll(tree);
tree.setData(getRoot());
createChildren(tree);
internalExpandToLevel(tree, expandToLevel);
if (useRedraw)
tree.setRedraw(true);
}
});
}
/**
* Recursively collapses the subtree rooted at the given widget to the
* given level.
* <p>
* </p>
* Note that the default implementation of this method does not call
* <code>setRedraw</code>.
*
* @param widget the widget
* @param level non-negative level, or <code>ALL_LEVELS</code> to collapse
* all levels of the tree
*/
protected void internalCollapseToLevel(Widget widget, int level) {
if (level == ALL_LEVELS || level > 0) {
if (widget instanceof Item)
setExpanded((Item) widget, false);
if (level == ALL_LEVELS || level > 1) {
Item[] children = getChildren(widget);
if (children != null) {
int nextLevel =
(level == ALL_LEVELS ? ALL_LEVELS : level - 1);
for (int i = 0; i < children.length; i++)
internalCollapseToLevel(children[i], nextLevel);
}
}
}
}
/**
* Recursively collects all expanded elements from the given widget.
*
* @param result a list (element type: <code>Object</code>) into which
* to collect the elements
* @param widget the widget
*/
private void internalCollectExpanded(List result, Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
if (getExpanded(item)) {
Object data = item.getData();
if (data != null)
result.add(data);
}
internalCollectExpanded(result, item);
}
}
/**
* Tries to create a path of tree items for the given element.
* This method recursively walks up towards the root of the tree
* and assumes that <code>getParent</code> returns the correct
* parent of an element.
*
* @param element the element
* @param expand <code>true</code> if all nodes on the path should be expanded,
* and <code>false</code> otherwise
*/
protected Widget internalExpand(Object element, boolean expand) {
if (element == null)
return null;
Widget w = findItem(element);
if (w == null) {
if (equals(element, getRoot())) { // stop at root
return null;
}
// my parent has to create me
ITreeContentProvider cp =
(ITreeContentProvider) getContentProvider();
if (cp == null) {
return null;
}
Object parent = cp.getParent(element);
if (parent != null) {
Widget pw = internalExpand(parent, expand);
if (pw != null) {
// let my parent create me
createChildren(pw);
// expand parent and find me
if (pw instanceof Item) {
Item item = (Item) pw;
if (expand)
setExpanded(item, true);
w = internalFindChild(item, element);
}
}
}
}
return w;
}
/**
* Recursively expands the subtree rooted at the given widget to the
* given level.
* <p>
* </p>
* Note that the default implementation of this method does not call
* <code>setRedraw</code>.
*
* @param widget the widget
* @param level non-negative level, or <code>ALL_LEVELS</code> to collapse
* all levels of the tree
*/
protected void internalExpandToLevel(Widget widget, int level) {
if (level == ALL_LEVELS || level > 0) {
createChildren(widget);
if (widget instanceof Item)
setExpanded((Item) widget, true);
if (level == ALL_LEVELS || level > 1) {
Item[] children = getChildren(widget);
if (children != null) {
int newLevel =
(level == ALL_LEVELS ? ALL_LEVELS : level - 1);
for (int i = 0; i < children.length; i++)
internalExpandToLevel(children[i], newLevel);
}
}
}
}
/**
* Non-recursively tries to find the given element as a child of the given parent item.
*
* @param parent the parent item
* @param element the element
*/
private Widget internalFindChild(Item parent, Object element) {
Item[] items = getChildren(parent);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
Object data = item.getData();
if (data != null && equals(data, element))
return item;
}
return null;
}
/**
* Recursively tries to find the given element.
*
* @param parent the parent item
* @param element the element
*/
private Widget internalFindItem(Item parent, Object element) {
// compare with node
Object data = parent.getData();
if (data != null) {
if (equals(data, element))
return parent;
}
// recurse over children
Item[] items = getChildren(parent);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
Widget o = internalFindItem(item, element);
if (o != null)
return o;
}
return null;
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected void internalRefresh(Object element) {
internalRefresh(element, true);
}
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected void internalRefresh(Object element, boolean updateLabels) {
// If element is null, do a full refresh.
if (element == null) {
internalRefresh(getControl(), getRoot(), true, updateLabels);
return;
}
Widget item = findItem(element);
if (item != null) {
// pick up structure changes too
internalRefresh(item, element, true, updateLabels);
}
}
/**
* Refreshes the tree starting at the given widget.
*
* @param widget the widget
* @param element the element
* @param doStruct <code>true</code> if structural changes are to be picked up,
* and <code>false</code> if only label provider changes are of interest
* @param updateLabels <code>true</code> to update labels for existing elements,
* <code>false</code> to only update labels as needed, assuming that labels
* for existing elements are unchanged.
*/
private void internalRefresh(
Widget widget,
Object element,
boolean doStruct,
boolean updateLabels) {
if (widget instanceof Item) {
if (doStruct) {
updatePlus((Item) widget, element);
}
if (updateLabels || !equals(element, widget.getData())) {
doUpdateItem(widget, element, true);
} else {
associate(element, (Item) widget);
}
}
if (doStruct) {
internalRefreshStruct(widget, element, updateLabels);
} else {
Item[] children = getChildren(widget);
if (children != null) {
for (int i = 0; i < children.length; i++) {
Widget item = children[i];
Object data = item.getData();
if (data != null)
internalRefresh(item, data, doStruct, updateLabels);
}
}
}
}
/**
* Update the structure and recurse.
* Items are updated in updateChildren, as needed.
*/
private void internalRefreshStruct(
Widget widget,
Object element,
boolean updateLabels) {
updateChildren(widget, element, null, updateLabels);
Item[] children = getChildren(widget);
if (children != null) {
for (int i = 0; i < children.length; i++) {
Widget item = children[i];
Object data = item.getData();
if (data != null)
internalRefreshStruct(item, data, updateLabels);
}
}
}
/**
* Removes the given elements from this viewer.
*
* @param elements the elements to remove
*/
private void internalRemove(Object[] elements) {
Object input = getInput();
HashSet parentItems = new HashSet(5);
for (int i = 0; i < elements.length; ++i) {
if (equals(elements[i], input)) {
setInput(null);
return;
}
Widget childItem = findItem(elements[i]);
if (childItem instanceof Item) {
Item parentItem = getParentItem((Item) childItem);
if (parentItem != null) {
parentItems.add(parentItem);
}
disassociate((Item) childItem);
childItem.dispose();
}
}
Control tree = getControl();
for (Iterator i = parentItems.iterator(); i.hasNext();) {
Item parentItem = (Item) i.next();
if (!getExpanded(parentItem) && getItemCount(parentItem) == 0) {
// append a dummy if necessary
if (isExpandable(parentItem.getData())) {
newItem(parentItem, SWT.NULL, -1);
} else {
// XXX: Workaround (PR missing)
tree.redraw();
}
}
}
}
/**
* Sets the expanded state of all items to correspond to the given set of expanded elements.
*
* @param expandedElements the set (element type: <code>Object</code>) of elements which are expanded
* @param widget the widget
*/
private void internalSetExpanded(HashSet expandedElements, Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
Object data = item.getData();
if (data != null) {
// remove the element to avoid an infinite loop
// if the same element appears on a child item
boolean expanded = expandedElements.remove(data);
if (expanded != getExpanded(item)) {
if (expanded) {
createChildren(item);
}
setExpanded(item, expanded);
}
}
internalSetExpanded(expandedElements, item);
}
}
/**
* Return whether the tree node representing the given element
* can be expanded.
* <p>
* The default implementation of this framework method calls
* <code>hasChildren</code> on this viewer's content provider.
* It may be overridden if necessary.
* </p>
*
* @param element the element
* @return <code>true</code> if the tree node representing
* the given element can be expanded, or <code>false</code> if not
*/
public boolean isExpandable(Object element) {
ITreeContentProvider cp = (ITreeContentProvider) getContentProvider();
return cp != null && cp.hasChildren(element);
}
/* (non-Javadoc)
* Method declared on Viewer.
*/
protected void labelProviderChanged() {
// we have to walk the (visible) tree and update every item
Control tree = getControl();
tree.setRedraw(false);
// don't pick up structure changes, but do force label updates
internalRefresh(tree, getRoot(), false, true);
tree.setRedraw(true);
}
/**
* Creates a new item.
*
* @param parent the parent widget
* @param style SWT style bits
* @param index if non-negative, indicates the position to insert the item
* into its parent
* @return the newly-created item
*/
protected abstract Item newItem(Widget parent, int style, int index);
/**
* Removes the given elements from this viewer.
* The selection is updated if required.
* <p>
* This method should be called (by the content provider) when elements
* have been removed from the model, in order to cause the viewer to accurately
* reflect the model. This method only affects the viewer, not the model.
* </p>
*
* @param elements the elements to remove
*/
public void remove(final Object[] elements) {
preservingSelection(new Runnable() {
public void run() {
internalRemove(elements);
}
});
}
/**
* Removes the given element from the viewer.
* The selection is updated if necessary.
* <p>
* This method should be called (by the content provider) when a single element
* has been removed from the model, in order to cause the viewer to accurately
* reflect the model. This method only affects the viewer, not the model.
* Note that there is another method for efficiently processing the simultaneous
* removal of multiple elements.
* </p>
*
* @param element the element
*/
public void remove(Object element) {
remove(new Object[] { element });
}
/**
* Removes all items from the given control.
*
* @param control the control
*/
protected abstract void removeAll(Control control);
/**
* Removes a listener for expand and collapse events in this viewer.
* Has no affect if an identical listener is not registered.
*
* @param listener a tree viewer listener
*/
public void removeTreeListener(ITreeViewerListener listener) {
treeListeners.remove(listener);
}
/*
* Non-Javadoc.
* Method defined on StructuredViewer.
*/
public void reveal(Object element) {
Widget w = internalExpand(element, true);
if (w instanceof Item)
showItem((Item) w);
}
/**
* Returns the rightmost visible descendent of the given item.
* Returns the item itself if it has no children.
*
* @param item the item to compute the descendent of
* @return the rightmost visible descendent or the item iself
* if it has no children
*/
private Item rightMostVisibleDescendent(Item item) {
Item[] children = getItems(item);
if (getExpanded(item) && children != null && children.length > 0) {
return rightMostVisibleDescendent(children[children.length - 1]);
} else {
return item;
}
}
/* (non-Javadoc)
* Method declared on Viewer.
*/
public Item scrollDown(int x, int y) {
Item current = getItem(x, y);
if (current != null) {
Item next = getNextItem(current, true);
showItem(next == null ? current : next);
return next;
}
return null;
}
/* (non-Javadoc)
* Method declared on Viewer.
*/
public Item scrollUp(int x, int y) {
Item current = getItem(x, y);
if (current != null) {
Item previous = getPreviousItem(current);
showItem(previous == null ? current : previous);
return previous;
}
return null;
}
/**
* Sets the auto-expand level.
* The value 0 means that there is no auto-expand;
* 1 means that top-level elements are expanded, but not their children;
* 2 means that top-level elements are expanded, and their children,
* but not grandchildren; and so on.
* <p>
* The value <code>ALL_LEVELS</code> means that all subtrees should be
* expanded.
* </p>
*
* @param level non-negative level, or <code>ALL_LEVELS</code> to expand
* all levels of the tree
*/
public void setAutoExpandLevel(int level) {
expandToLevel = level;
}
/**
* The <code>AbstractTreeViewer</code> implementation of this method
* checks to ensure that the content provider is an <code>ITreeContentProvider</code>.
*/
public void setContentProvider(IContentProvider provider) {
Assert.isTrue(provider instanceof ITreeContentProvider);
super.setContentProvider(provider);
}
/**
* Sets the expand state of the given item.
*
* @param item the item
* @param expand the expand state of the item
*/
protected abstract void setExpanded(Item item, boolean expand);
/**
* Sets which nodes are expanded in this viewer's tree.
* The given list contains the elements that are to be expanded;
* all other nodes are to be collapsed.
* <p>
* This method is typically used when restoring the interesting
* state of a viewer captured by an earlier call to <code>getExpandedElements</code>.
* </p>
*
* @param elements the array of expanded elements
* @see #getExpandedElements
*/
public void setExpandedElements(Object[] elements) {
HashSet expandedElements = new HashSet(elements.length * 2 + 1);
for (int i = 0; i < elements.length; ++i) {
// Ensure item exists for element
internalExpand(elements[i], false);
expandedElements.add(elements[i]);
}
internalSetExpanded(expandedElements, getControl());
}
/**
* Sets whether the node corresponding to the given element is expanded or collapsed.
*
* @param element the element
* @param expanded <code>true</code> if the node is expanded, and <code>false</code> if collapsed
*/
public void setExpandedState(Object element, boolean expanded) {
Widget item = internalExpand(element, false);
if (item instanceof Item) {
if (expanded) {
createChildren(item);
}
setExpanded((Item) item, expanded);
}
}
/**
* Sets the selection to the given list of items.
*
* @param items list of items (element type: <code>org.eclipse.swt.widgets.Item</code>)
*/
protected abstract void setSelection(List items);
/* (non-Javadoc)
* Method declared on StructuredViewer.
*/
protected void setSelectionToWidget(List v, boolean reveal) {
if (v == null) {
setSelection(new ArrayList(0));
return;
}
int size = v.size();
List newSelection = new ArrayList(size);
for (int i = 0; i < size; ++i) {
// Use internalExpand since item may not yet be created. See 1G6B1AR.
Widget w = internalExpand(v.get(i), true);
if (w instanceof Item) {
newSelection.add(w);
}
}
setSelection(newSelection);
if (reveal && newSelection.size() > 0) {
showItem((Item) newSelection.get(0));
}
}
/**
* Shows the given item.
*
* @param item the item
*/
protected abstract void showItem(Item item);
/**
* Updates the tree items to correspond to the child elements of the given parent element.
* If null is passed for the children, this method obtains them (only if needed).
*
* @param widget the widget
* @param parent the parent element
* @param elementChildren the child elements, or null
*
* @deprecated this is no longer called by the framework
*/
protected void updateChildren(
Widget widget,
Object parent,
Object[] elementChildren) {
updateChildren(widget, parent, elementChildren, true);
}
/**
* Updates the tree items to correspond to the child elements of the given parent element.
* If null is passed for the children, this method obtains them (only if needed).
*
* @param widget the widget
* @param parent the parent element
* @param elementChildren the child elements, or null
* @param updateLabels <code>true</code> to update labels for existing elements,
* <code>false</code> to only update labels as needed, assuming that labels
* for existing elements are unchanged.
*
* @since 2.1
*/
private void updateChildren(
Widget widget,
Object parent,
Object[] elementChildren,
boolean updateLabels) {
// optimization! prune collapsed subtrees
if (widget instanceof Item) {
Item ti = (Item) widget;
if (!getExpanded(ti)) {
// need a dummy node if element is expandable;
// but try to avoid recreating the dummy node
boolean needDummy = isExpandable(parent);
boolean haveDummy = false;
// remove all children
Item[] items = getItems(ti);
for (int i = 0; i < items.length; i++) {
if (items[i].getData() != null) {
disassociate(items[i]);
items[i].dispose();
} else {
if (needDummy && !haveDummy) {
haveDummy = true;
} else {
items[i].dispose();
}
}
}
if (needDummy && !haveDummy) {
newItem(ti, SWT.NULL, -1);
}
return;
}
}
// If the children weren't passed in, get them now since they're needed below.
if (elementChildren == null) {
elementChildren = getSortedChildren(parent);
}
Control tree = getControl();
// WORKAROUND
int oldCnt = -1;
if (widget == tree)
oldCnt = getItemCount(tree);
Item[] items = getChildren(widget);
// save the expanded elements
HashSet expanded = new HashSet(); // assume num expanded is small
for (int i = 0; i < items.length; ++i) {
if (getExpanded(items[i])) {
Object element = items[i].getData();
if (element != null) {
expanded.add(element);
}
}
}
int min = Math.min(elementChildren.length, items.length);
// Note: In the code below, doing disassociate calls before associate calls is important,
// since a later disassociate can undo an earlier associate,
// if items are changing position.
// dispose of all items beyond the end of the current elements
for (int i = items.length; --i >= min;) {
if (items[i].getData() != null) {
disassociate(items[i]);
}
items[i].dispose();
}
// compare first min items, and update item if necessary
// need to do it in two passes:
// 1: disassociate old items
// 2: associate new items
// because otherwise a later disassociate can remove a mapping made for a previous associate,
// making the map inconsistent
for (int i = 0; i < min; ++i) {
Item item = items[i];
Object oldElement = item.getData();
if (oldElement != null) {
Object newElement = elementChildren[i];
if (newElement != oldElement) {
if (equals(newElement, oldElement)) {
// update the data to be the new element, since although the elements
// may be equal, they may still have different labels or children
item.setData(newElement);
mapElement(newElement, item);
} else {
disassociate(item);
}
}
}
}
for (int i = 0; i < min; ++i) {
Item item = items[i];
Object newElement = elementChildren[i];
if (item.getData() == null) {
// old and new elements are not equal
associate(newElement, item);
updatePlus(item, newElement);
updateItem(item, newElement);
// Restore expanded state for items that changed position.
// Make sure setExpanded is called after updatePlus, since
// setExpanded(false) fails if item has no children.
// Need to call setExpanded for both expanded and unexpanded cases
// since the expanded state can change either way.
setExpanded(item, expanded.contains(newElement));
} else {
// old and new elements are equal
updatePlus(item, newElement);
if (updateLabels) {
updateItem(item, newElement);
}
}
}
// add any remaining elements
if (min < elementChildren.length) {
for (int i = min; i < elementChildren.length; ++i) {
createTreeItem(widget, elementChildren[i], i);
}
// Need to restore expanded state in a separate pass
// because createTreeItem does not return the new item.
// Avoid doing this unless needed.
if (!expanded.isEmpty()) {
// get the items again, to include the new items
items = getChildren(widget);
for (int i = min; i < elementChildren.length; ++i) {
// Restore expanded state for items that changed position.
// Make sure setExpanded is called after updatePlus (called in createTreeItem), since
// setExpanded(false) fails if item has no children.
// Only need to call setExpanded if element was expanded
// since new items are initially unexpanded.
if (expanded.contains(elementChildren[i])) {
setExpanded(items[i], true);
}
}
}
}
// WORKAROUND
if (widget == tree && oldCnt == 0 && getItemCount(tree) != 0) {
//System.out.println("WORKAROUND setRedraw");
tree.setRedraw(false);
tree.setRedraw(true);
}
}
/**
* Updates the "+"/"-" icon of the tree node from the given element.
* It calls <code>isExpandable</code> to determine whether
* an element is expandable.
*
* @param item the item
* @param element the element
*/
protected void updatePlus(Item item, Object element) {
boolean hasPlus = getItemCount(item) > 0;
boolean needsPlus = isExpandable(element);
boolean removeAll = false;
boolean addDummy = false;
Object data = item.getData();
if (data != null && equals(element, data)) {
// item shows same element
if (hasPlus != needsPlus) {
if (needsPlus)
addDummy = true;
else
removeAll = true;
}
} else {
// item shows different element
removeAll = true;
addDummy = needsPlus;
// we cannot maintain expand state so collapse it
setExpanded(item, false);
}
if (removeAll) {
// remove all children
Item[] items = getItems(item);
for (int i = 0; i < items.length; i++) {
if (items[i].getData() != null)
disassociate(items[i]);
items[i].dispose();
}
}
if (addDummy)
newItem(item, SWT.NULL, -1); // append a dummy
}
/**
* Gets the expanded elements that are visible
* to the user. An expanded element is only
* visible if the parent is expanded.
*
* @return the visible expanded elements
* @since 2.0
*/
public Object[] getVisibleExpandedElements() {
ArrayList v = new ArrayList();
internalCollectVisibleExpanded(v, getControl());
return v.toArray();
}
private void internalCollectVisibleExpanded(
ArrayList result,
Widget widget) {
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
if (getExpanded(item)) {
Object data = item.getData();
if (data != null)
result.add(data);
//Only recurse if it is expanded - if
//not then the children aren't visible
internalCollectVisibleExpanded(result, item);
}
}
}
}