| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.jface.viewers; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.jface.util.Assert; |
| import org.eclipse.jface.util.SafeRunnable; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.events.TreeEvent; |
| import org.eclipse.swt.events.TreeListener; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Item; |
| import org.eclipse.swt.widgets.Widget; |
| |
| /** |
| * 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(); |
| |
| /** |
| * 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; |
| |
| /** |
| * Safe runnable used to update an item. |
| */ |
| class UpdateItemSafeRunnable extends SafeRunnable { |
| private Object element; |
| |
| private Item item; |
| |
| UpdateItemSafeRunnable(Item item, Object element) { |
| this.item = item; |
| this.element = element; |
| } |
| |
| public void run() { |
| doUpdateItem(item, element); |
| } |
| |
| } |
| |
| /** |
| * 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() { |
| // do nothing |
| } |
| |
| /** |
| * 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); |
| assertElementsNotNull(childElements); |
| |
| Widget[] widgets = findItems(parentElement); |
| // If parent hasn't been realized yet, just ignore the add. |
| if (widgets.length == 0) |
| return; |
| |
| for (int i = 0; i < widgets.length; i++) { |
| internalAdd(widgets[i], parentElement, childElements); |
| } |
| } |
| |
| /** |
| * Adds the given child elements to this viewer as children of the given |
| * parent element. |
| * <p> |
| * EXPERIMENTAL. Not to be used except by JDT. |
| * This method was added to support JDT's explorations |
| * into grouping by working sets, which requires viewers to support multiple |
| * equal elements. See bug 76482 for more details. This support will |
| * likely be removed in Eclipse 3.2 in favour of proper support for |
| * multiple equal elements. |
| * </p> |
| * |
| * @param widget |
| * the widget for the parent element |
| * @param parentElement |
| * the parent element |
| * @param childElements |
| * the child elements to add |
| * @since 3.1 |
| */ |
| protected void internalAdd(Widget widget, Object parentElement, Object[] childElements) { |
| |
| // 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); |
| return; |
| } |
| } |
| |
| if (childElements.length > 0) { |
| Object[] filtered = filter(childElements); |
| if(getSorter() != null) |
| getSorter().sort(this,filtered); |
| createAddedElements(widget, filtered); |
| } |
| } |
| |
| /** |
| * Create the new elements in the parent widget. If the |
| * child already exists do nothing. |
| * @param widget |
| * @param elements Sorted list of elements to add. |
| */ |
| private void createAddedElements(Widget widget, Object[] elements) { |
| |
| if(elements.length == 1){ |
| if (equals(elements[0], widget.getData())) |
| return; |
| } |
| |
| ViewerSorter sorter = getSorter (); |
| Item[] items = getChildren(widget); |
| |
| //As the items are sorted already we optimize for a |
| //start position |
| int lastInsertion = 0; |
| |
| //Optimize for the empty case |
| if(items.length == 0){ |
| for (int i = 0; i < elements.length; i++) { |
| createTreeItem(widget, elements[i], -1); |
| } |
| return; |
| } |
| |
| for (int i = 0; i < elements.length; i++) { |
| boolean newItem = true; |
| Object element = elements[i]; |
| int index; |
| if(sorter == null){ |
| if(itemExists(items,element)){ |
| refresh(element); |
| newItem = false; |
| } |
| index = -1; |
| } |
| else{ |
| lastInsertion = insertionPosition(items,sorter,lastInsertion, element); |
| //As we are only searching the original array we keep track of those positions only |
| if(lastInsertion == items.length) |
| index = -1; |
| else{//See if we should just refresh |
| while(lastInsertion < items.length && sorter.compare(this,element,items[lastInsertion].getData()) == 0){ |
| //As we cannot assume the sorter is consistent with equals() - therefore we can |
| // just check against the item prior to this index (if any) |
| if (items[lastInsertion].getData().equals(element)) { |
| //refresh the element in case it has new children |
| refresh(element); |
| newItem = false; |
| } |
| lastInsertion ++;//We had an insertion so increment |
| } |
| //Did we get to the end? |
| if(lastInsertion == items.length) |
| index = -1; |
| else |
| index = lastInsertion + i; //Add the index as the array is growing |
| } |
| } |
| if(newItem) |
| createTreeItem(widget, element, index); |
| } |
| } |
| |
| |
| /** |
| * See if element is the data of one of the elements in |
| * items. |
| * @param items |
| * @param element |
| * @return <code>true</code> if the element matches. |
| */ |
| private boolean itemExists(Item[] items, Object element) { |
| if(usingElementMap())//if we can do a constant time lookup find it |
| return findItem(element) != null; |
| for (int i = 0; i < items.length; i++) { |
| if(items[i].getData().equals(element)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the index where the item should be inserted. It uses sorter to |
| * determine the correct position, if sorter is not assigned, returns the |
| * index of the element after the last. |
| * |
| * @param items the items to search |
| * @param sorter The sorter to use. |
| * @param lastInsertion |
| * the start index to start search for position from this allows |
| * optimising search for multiple elements that are sorted |
| * themself. |
| * @param element |
| * element to find position for. |
| * @return the index to use when inserting the element. |
| * |
| */ |
| |
| private int insertionPosition(Item[] items, ViewerSorter sorter, int lastInsertion, Object element) { |
| |
| int size = items.length; |
| if (sorter == null) |
| return size; |
| int min = lastInsertion, max = size - 1; |
| |
| while (min <= max) { |
| int mid = (min + max) / 2; |
| Object data = items[mid].getData(); |
| int compare = sorter.compare(this, data, element); |
| if (compare == 0) { |
| return mid;//Return if we already match |
| } |
| if (compare < 0) |
| min = mid + 1; |
| else |
| max = mid - 1; |
| } |
| return min; |
| |
| |
| } |
| |
| /** |
| * Returns the index where the item should be inserted. It uses sorter to |
| * determine the correct position, if sorter is not assigned, returns the |
| * index of the element after the last. |
| * |
| * @param parent The parent widget |
| * @param sorter The sorter to use. |
| * @param startIndex |
| * the start index to start search for position from this allows |
| * optimising search for multiple elements that are sorted |
| * themself. |
| * @param element |
| * element to find position for. |
| * @param currentSize |
| * the current size of the collection |
| * @return the index to use when inserting the element. |
| * |
| */ |
| |
| /** |
| * 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. |
| * @return int |
| */ |
| protected int indexForElement(Widget parent, Object element) { |
| ViewerSorter sorter = getSorter(); |
| |
| |
| Item[] items = getChildren(parent); |
| int count = items.length; |
| |
| if (sorter == null) |
| return count; |
| 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) { |
| // do nothing |
| } |
| |
| /** |
| * 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 or tree path to the given level. |
| * |
| * @param elementOrTreePath |
| * the element or tree path |
| * @param level |
| * non-negative level, or <code>ALL_LEVELS</code> to collapse |
| * all levels of the tree |
| */ |
| public void collapseToLevel(Object elementOrTreePath, int level) { |
| Assert.isNotNull(elementOrTreePath); |
| Widget w = internalGetWidgetToSelect(elementOrTreePath); |
| 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++) { |
| if (tis[i].getData() != null) { |
| disassociate(tis[i]); |
| Assert.isTrue(tis[i].getData() == null, |
| "Second or later child is non -null");//$NON-NLS-1$ |
| |
| } |
| 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 { |
| Object data = item.getData(); |
| if (data != null) { |
| unmapElement(data, item); |
| } |
| item.setData(element); |
| mapElement(element, item); |
| } |
| |
| // update icon and label |
| SafeRunnable.run(new UpdateItemSafeRunnable(item, element)); |
| } |
| } |
| |
| /** |
| * 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 or tree path 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 elementOrTreePath |
| * the element |
| * @param level |
| * non-negative level, or <code>ALL_LEVELS</code> to expand all |
| * levels of the tree |
| */ |
| public void expandToLevel(Object elementOrTreePath, int level) { |
| Widget w = internalExpand(elementOrTreePath, 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]; |
| SafeRunnable.run(new SafeRunnable() { |
| public void run() { |
| l.treeCollapsed(event); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * 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]; |
| SafeRunnable.run(new SafeRunnable() { |
| public void run() { |
| l.treeExpanded(event); |
| } |
| }); |
| } |
| |
| } |
| |
| /** |
| * 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); |
| |
| /** |
| * Get the child for the widget at index. Note that the default |
| * implementation is not very effecient and should be overridden |
| * if this class is implemented. |
| * @param widget the widget to check |
| * @param index the index of the widget |
| * @return Item or <code>null</code> if widget is not a type |
| * that can contain items. |
| * |
| * @throws ArrayIndexOutOfBoundsException if the index is not valid. |
| * @since 3.1 |
| */ |
| protected Item getChild (Widget widget, int index) { |
| return getChildren(widget)[index]; |
| } |
| |
| /** |
| * 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 items = new ArrayList(); |
| internalCollectExpandedItems(items, getControl()); |
| ArrayList result = new ArrayList(items.size()); |
| for(Iterator it = items.iterator(); it.hasNext();) { |
| Item item = (Item) it.next(); |
| Object data = item.getData(); |
| if (data != null) { |
| result.add(data); |
| } |
| } |
| return result.toArray(); |
| } |
| |
| /** |
| * Returns whether the node corresponding to the given element or tree path is expanded |
| * or collapsed. |
| * |
| * @param elementOrTreePath |
| * the element |
| * @return <code>true</code> if the node is expanded, and <code>false</code> |
| * if collapsed |
| */ |
| public boolean getExpandedState(Object elementOrTreePath) { |
| Assert.isNotNull(elementOrTreePath); |
| Widget item = internalGetWidgetToSelect(elementOrTreePath); |
| 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){ |
| if(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 no 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) { |
| if (!(getContentProvider() instanceof ILazyTreeContentProvider)) { |
| 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 items from the given widget. |
| * |
| * @param result |
| * a list (element type: <code>Item</code>) into which to |
| * collect the elements |
| * @param widget |
| * the widget |
| */ |
| private void internalCollectExpandedItems(List result, Widget widget) { |
| Item[] items = getChildren(widget); |
| for (int i = 0; i < items.length; i++) { |
| Item item = items[i]; |
| if (getExpanded(item)) { |
| result.add(item); |
| } |
| internalCollectExpandedItems(result, item); |
| } |
| } |
| |
| /** |
| * Tries to create a path of tree items for the given element or tree path. |
| * This method recursively walks up towards the root of the tree and in the |
| * case of an element (rather than a tree path) assumes that |
| * <code>getParent</code> returns the correct parent of an element. |
| * |
| * @param elementOrPath |
| * the element |
| * @param expand |
| * <code>true</code> if all nodes on the path should be |
| * expanded, and <code>false</code> otherwise |
| * @return Widget |
| */ |
| protected Widget internalExpand(Object elementOrPath, boolean expand) { |
| |
| if (elementOrPath == null) |
| return null; |
| |
| Widget w = internalGetWidgetToSelect(elementOrPath); |
| if (w == null) { |
| if (equals(elementOrPath, getRoot())) { // stop at root |
| return null; |
| } |
| // my parent has to create me |
| Object parent = getParentElement(elementOrPath); |
| if (parent != null) { |
| Widget pw = internalExpand(parent, false); |
| if (pw != null) { |
| // let my parent create me |
| createChildren(pw); |
| // expand parent and find me |
| if (pw instanceof Item) { |
| Item item = (Item) pw; |
| Object element = internalToElement(elementOrPath); |
| w = internalFindChild(item, element); |
| if (expand) { |
| // expand parent items top-down |
| LinkedList toExpandList = new LinkedList(); |
| while (item != null && !getExpanded(item)) { |
| toExpandList.addFirst(item); |
| item = getParentItem(item); |
| } |
| for(Iterator it = toExpandList.iterator(); it.hasNext();) { |
| Item toExpand = (Item) it.next(); |
| setExpanded(toExpand, true); |
| } |
| } |
| } |
| } |
| } |
| } |
| return w; |
| } |
| |
| /** |
| * If the argument is a tree path, returns its last segment, otherwise |
| * return the argument |
| * @param elementOrPath an element or a tree path |
| * @return the element, or the last segment of the tree path |
| */ |
| private Object internalToElement(Object elementOrPath) { |
| if (elementOrPath instanceof TreePath) { |
| return ((TreePath)elementOrPath).getLastSegment(); |
| } |
| return elementOrPath; |
| } |
| |
| /** |
| * This method takes a tree path or an element. If the argument is not a |
| * tree path, returns the parent of the given element or <code>null</code> |
| * if the parent is not known. If the argument is a tree path with more than |
| * one segment, returns its parent tree path, otherwise returns |
| * <code>null</code>. |
| * |
| * @param elementOrTreePath |
| * @return the parent element, or parent path, or <code>null</code> |
| */ |
| protected Object getParentElement(Object elementOrTreePath) { |
| if(elementOrTreePath instanceof TreePath) { |
| TreePath treePath = (TreePath)elementOrTreePath; |
| if(treePath.getSegmentCount() <= 1) { |
| return null; |
| } |
| return (treePath).getParentPath(); |
| } |
| ITreeContentProvider cp = (ITreeContentProvider) getContentProvider(); |
| if (cp == null) { |
| return null; |
| } |
| return cp.getParent(elementOrTreePath); |
| } |
| |
| /** |
| * Returns the widget to be selected for the given element or tree path. |
| * |
| * @param elementOrTreePath the element or tree path to select |
| * @return the widget to be selected, or <code>null</code> if not found |
| * |
| * @since 3.1 |
| */ |
| protected Widget internalGetWidgetToSelect(Object elementOrTreePath) { |
| if(elementOrTreePath instanceof TreePath) { |
| TreePath treePath = (TreePath) elementOrTreePath; |
| if(treePath.getSegmentCount()==0) { |
| return null; |
| } |
| Widget[] candidates = findItems(treePath.getLastSegment()); |
| for (int i = 0; i < candidates.length; i++) { |
| Widget candidate = candidates[i]; |
| if(!(candidate instanceof Item)) { |
| continue; |
| } |
| if(treePath.equals(getTreePathFromItem((Item) candidate), getComparer())) { |
| return candidate; |
| } |
| } |
| return null; |
| } |
| return findItem(elementOrTreePath); |
| } |
| |
| /** |
| * 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 |
| * @return Widget |
| */ |
| 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 |
| * @return Widget |
| */ |
| 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[] items = findItems(element); |
| if (items.length != 0) { |
| for (int i = 0; i < items.length; i++) { |
| // pick up structure changes too |
| internalRefresh(items[i], element, true, updateLabels); |
| } |
| } |
| } |
| |
| /** |
| * Refreshes the tree starting at the given widget. |
| * <p> |
| * EXPERIMENTAL. Not to be used except by JDT. |
| * This method was added to support JDT's explorations |
| * into grouping by working sets, which requires viewers to support multiple |
| * equal elements. See bug 76482 for more details. This support will |
| * likely be removed in Eclipse 3.2 in favour of proper support for |
| * multiple equal elements. |
| * </p> |
| * |
| * @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. |
| * @since 3.1 |
| */ |
| protected 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. |
| * @param widget |
| * @param element |
| * @param updateLabels |
| */ |
| 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. |
| * <p> |
| * EXPERIMENTAL. Not to be used except by JDT. |
| * This method was added to support JDT's explorations |
| * into grouping by working sets, which requires viewers to support multiple |
| * equal elements. See bug 76482 for more details. This support will |
| * likely be removed in Eclipse 3.2 in favour of proper support for |
| * multiple equal elements. |
| * </p> |
| * |
| * @param elements |
| * the elements to remove |
| * @since 3.1 |
| */ |
| protected void internalRemove(Object[] elements) { |
| Object input = getInput(); |
| // Note: do not use the comparer here since the hashtable |
| // contains SWT Items, not model elements. |
| CustomHashtable parentItems = new CustomHashtable(5); |
| for (int i = 0; i < elements.length; ++i) { |
| if (equals(elements[i], input)) { |
| setInput(null); |
| return; |
| } |
| Widget[] childItems = findItems(elements[i]); |
| for (int j = 0; j < childItems.length; j++) { |
| Widget childItem = childItems[j]; |
| if (childItem instanceof Item) { |
| Item parentItem = getParentItem((Item) childItem); |
| if (parentItem != null) { |
| parentItems.put(parentItem, parentItem); |
| } |
| disassociate((Item) childItem); |
| childItem.dispose(); |
| } |
| } |
| } |
| Control tree = getControl(); |
| for (Enumeration e = parentItems.keys(); e.hasMoreElements();) { |
| Item parentItem = (Item) e.nextElement(); |
| if(parentItem.isDisposed()) |
| continue; |
| 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(CustomHashtable 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) != null; |
| if (expanded != getExpanded(item)) { |
| if (expanded) { |
| createChildren(item); |
| } |
| setExpanded(item, expanded); |
| } |
| } |
| internalSetExpanded(expandedElements, item); |
| } |
| } |
| |
| /** |
| * Sets the expanded state of all items to correspond to the given set of |
| * expanded tree paths. |
| * |
| * @param expandedTreePaths |
| * the set (element type: <code>TreePath</code>) of elements |
| * which are expanded |
| * @param widget |
| * the widget |
| */ |
| private void internalSetExpandedTreePaths(CustomHashtable expandedTreePaths, Widget widget, TreePath currentPath) { |
| Item[] items = getChildren(widget); |
| for (int i = 0; i < items.length; i++) { |
| Item item = items[i]; |
| Object data = item.getData(); |
| TreePath childPath = data==null ? null: currentPath.createChildPath(data); |
| if (data != null && childPath!=null) { |
| // remove the element to avoid an infinite loop |
| // if the same element appears on a child item |
| boolean expanded = expandedTreePaths.remove(childPath) != null; |
| if (expanded != getExpanded(item)) { |
| if (expanded) { |
| createChildren(item); |
| } |
| setExpanded(item, expanded); |
| } |
| } |
| internalSetExpandedTreePaths(expandedTreePaths, item, childPath); |
| } |
| } |
| |
| /** |
| * 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) { |
| assertElementsNotNull(elements); |
| if (elements.length == 0) { |
| return; |
| } |
| 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); |
| } |
| |
| /** |
| * This implementation of reveal() reveals the given element or tree path. |
| */ |
| public void reveal(Object elementOrTreePath) { |
| Assert.isNotNull(elementOrTreePath); |
| Widget w = internalExpand(elementOrTreePath, 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]); |
| } |
| 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) { |
| // the actual check is in assertContentProviderType |
| super.setContentProvider(provider); |
| } |
| |
| protected void assertContentProviderType(IContentProvider provider) { |
| Assert.isTrue(provider instanceof ITreeContentProvider); |
| } |
| |
| /** |
| * 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) { |
| assertElementsNotNull(elements); |
| CustomHashtable expandedElements = newHashtable(elements.length * 2 + 1); |
| for (int i = 0; i < elements.length; ++i) { |
| Object element = elements[i]; |
| // Ensure item exists for element. This will materialize items for |
| // each element and their parents, if possible. This is important |
| // to support expanding of inner tree nodes without necessarily |
| // expanding their parents. |
| internalExpand(element, false); |
| expandedElements.put(element, element); |
| } |
| // this will traverse all existing items, and create children for |
| // elements that need to be expanded. If the tree contains multiple |
| // equal elements, and those are in the set of elements to be expanded, |
| // only the first item found for each element will be expanded. |
| internalSetExpanded(expandedElements, getControl()); |
| } |
| |
| /** |
| * Sets which nodes are expanded in this viewer's tree. The given list |
| * contains the tree paths 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>getExpandedTreePaths</code>. |
| * </p> |
| * |
| * @param treePaths |
| * the array of expanded tree paths |
| * @see #getExpandedTreePaths() |
| */ |
| public void setExpandedTreePaths(TreePath[] treePaths) { |
| assertElementsNotNull(treePaths); |
| final IElementComparer comparer = getComparer(); |
| IElementComparer treePathComparer = new IElementComparer(){ |
| |
| public boolean equals(Object a, Object b) { |
| return ((TreePath)a).equals(((TreePath)b), comparer); |
| } |
| |
| public int hashCode(Object element) { |
| return ((TreePath)element).hashCode(comparer); |
| }}; |
| CustomHashtable expandedTreePaths = new CustomHashtable(treePaths.length * 2 + 1, treePathComparer); |
| for (int i = 0; i < treePaths.length; ++i) { |
| TreePath treePath = treePaths[i]; |
| // Ensure item exists for element. This will materialize items for |
| // each element and their parents, if possible. This is important |
| // to support expanding of inner tree nodes without necessarily |
| // expanding their parents. |
| internalExpand(treePath, false); |
| expandedTreePaths.put(treePath, treePath); |
| } |
| // this will traverse all existing items, and create children for |
| // elements that need to be expanded. If the tree contains multiple |
| // equal elements, and those are in the set of elements to be expanded, |
| // only the first item found for each element will be expanded. |
| internalSetExpandedTreePaths(expandedTreePaths, getControl(), new TreePath(new Object[0])); |
| } |
| |
| /** |
| * Sets whether the node corresponding to the given element or tree path is expanded or |
| * collapsed. |
| * |
| * @param elementOrTreePath |
| * the element |
| * @param expanded |
| * <code>true</code> if the node is expanded, and <code>false</code> |
| * if collapsed |
| */ |
| public void setExpandedState(Object elementOrTreePath, boolean expanded) { |
| Assert.isNotNull(elementOrTreePath); |
| Widget item = internalExpand(elementOrTreePath, 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); |
| |
| /** |
| * This implementation of setSelectionToWidget accepts a list of elements |
| * or a list of tree paths. |
| **/ |
| 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), false); |
| if (w instanceof Item) { |
| newSelection.add(w); |
| } |
| } |
| setSelection(newSelection); |
| |
| // Although setting the selection in the control should reveal it, |
| // setSelection may be a no-op if the selection is unchanged, |
| // so explicitly reveal the first item in the selection here. |
| // See bug 100565 for more details. |
| 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 |
| CustomHashtable expanded = newHashtable(CustomHashtable.DEFAULT_CAPACITY); // 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.put(element, 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 |
| Object data = item.getData(); |
| if (data != null) { |
| unmapElement(data, item); |
| } |
| item.setData(newElement); |
| mapElement(newElement, item); |
| } else { |
| disassociate(item); |
| //Clear the text and image to force a label update |
| item.setImage(null); |
| item.setText("");//$NON-NLS-1$ |
| |
| } |
| } |
| } |
| } |
| 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.containsKey(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.size() > 0) { |
| // 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.containsKey(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); |
| } |
| } |
| } |
| |
| protected TreePath getTreePathFromItem(Item item) { |
| LinkedList segments = new LinkedList(); |
| while(item!=null) { |
| Object segment = item.getData(); |
| Assert.isNotNull(segment); |
| segments.addFirst(segment); |
| item = getParentItem(item); |
| } |
| return new TreePath(segments.toArray()); |
| } |
| |
| /** |
| * This implementation of getSelection() returns an instance of |
| * ITreeSelection. |
| * |
| * @since 3.2 |
| */ |
| public ISelection getSelection() { |
| Control control = getControl(); |
| if (control == null || control.isDisposed()) { |
| return TreeSelection.EMPTY; |
| } |
| Widget[] items = getSelection(getControl()); |
| ArrayList list = new ArrayList(items.length); |
| for (int i = 0; i < items.length; i++) { |
| Widget item = items[i]; |
| if (item.getData() != null) { |
| list.add(getTreePathFromItem((Item) item)); |
| } |
| } |
| return new TreeSelection((TreePath[]) list.toArray(new TreePath[list |
| .size()])); |
| } |
| |
| protected void setSelectionToWidget(ISelection selection, boolean reveal) { |
| if(selection instanceof ITreeSelection) { |
| ITreeSelection treeSelection = (ITreeSelection) selection; |
| setSelectionToWidget(Arrays.asList(treeSelection.getPaths()), reveal); |
| } else { |
| super.setSelectionToWidget(selection, reveal); |
| } |
| } |
| |
| /** |
| * Returns a list of tree paths 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 tree paths |
| * @see #setExpandedElements |
| */ |
| public TreePath[] getExpandedTreePaths() { |
| ArrayList items = new ArrayList(); |
| internalCollectExpandedItems(items, getControl()); |
| ArrayList result = new ArrayList(items.size()); |
| for(Iterator it = items.iterator(); it.hasNext();) { |
| Item item = (Item) it.next(); |
| TreePath treePath = getTreePathFromItem(item); |
| if (treePath != null) { |
| result.add(treePath); |
| } |
| } |
| return (TreePath[]) result.toArray(new TreePath[items.size()]); |
| } |
| |
| } |