| /******************************************************************************* |
| * Copyright (c) 2000, 2007 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 |
| * Tom Schindl <tom.schindl@bestsolution.at> - bug 153993, bug 167323, bug 175192 |
| *******************************************************************************/ |
| |
| package org.eclipse.jface.viewers; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.jface.util.SafeRunnable; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.events.TreeEvent; |
| import org.eclipse.swt.events.TreeListener; |
| import org.eclipse.swt.graphics.Point; |
| 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> |
| * As of 3.2, AbstractTreeViewer supports multiple equal elements (each with a |
| * different parent chain) in the tree. This support requires that clients |
| * enable the element map by calling <code>setUseHashLookup(true)</code>. |
| * </p> |
| * <p> |
| * Content providers for abstract tree viewers must implement one of the |
| * interfaces <code>ITreeContentProvider</code> or (as of 3.2, to support |
| * multiple equal elements) <code>ITreePathContentProvider</code>. |
| * </p> |
| * |
| * @see TreeViewer |
| */ |
| public abstract class AbstractTreeViewer extends ColumnViewer { |
| |
| /** |
| * Constant indicating that all levels of the tree should be expanded or |
| * collapsed. |
| * |
| * @see #expandToLevel(int) |
| * @see #collapseToLevel(Object, int) |
| */ |
| 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 parentElementOrTreePath |
| * the parent element |
| * @param childElements |
| * the child elements to add |
| */ |
| public void add(Object parentElementOrTreePath, Object[] childElements) { |
| Assert.isNotNull(parentElementOrTreePath); |
| assertElementsNotNull(childElements); |
| if (isBusy()) |
| return; |
| Widget[] widgets = internalFindItems(parentElementOrTreePath); |
| // 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], parentElementOrTreePath, childElements); |
| } |
| } |
| |
| /** |
| * Find the items for the given element of tree path |
| * |
| * @param parentElementOrTreePath |
| * the element or tree path |
| * @return the items for that element |
| * |
| * @since 3.3 |
| */ |
| final protected Widget[] internalFindItems(Object parentElementOrTreePath) { |
| Widget[] widgets; |
| if (parentElementOrTreePath instanceof TreePath) { |
| TreePath path = (TreePath) parentElementOrTreePath; |
| Widget w = internalFindItem(path); |
| if (w == null) { |
| widgets = new Widget[] {}; |
| } else { |
| widgets = new Widget[] { w }; |
| } |
| } else { |
| widgets = findItems(parentElementOrTreePath); |
| } |
| return widgets; |
| } |
| |
| /** |
| * Return the item at the given path or <code>null</code> |
| * |
| * @param path |
| * the path |
| * @return {@link Widget} the item at that path |
| */ |
| private Widget internalFindItem(TreePath path) { |
| Widget[] widgets = findItems(path.getLastSegment()); |
| for (int i = 0; i < widgets.length; i++) { |
| Widget widget = widgets[i]; |
| if (widget instanceof Item) { |
| Item item = (Item) widget; |
| TreePath p = getTreePathFromItem(item); |
| if (p.equals(path)) { |
| return widget; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 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 favor of |
| * proper support for multiple equal elements. |
| * </p> |
| * |
| * @param widget |
| * the widget for the parent element |
| * @param parentElementOrTreePath |
| * the parent element |
| * @param childElements |
| * the child elements to add |
| * @since 3.1 |
| */ |
| protected void internalAdd(Widget widget, Object parentElementOrTreePath, |
| Object[] childElements) { |
| Object parent; |
| TreePath path; |
| if (parentElementOrTreePath instanceof TreePath) { |
| path = (TreePath) parentElementOrTreePath; |
| parent = path.getLastSegment(); |
| } else { |
| parent = parentElementOrTreePath; |
| path = null; |
| } |
| |
| // 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(ti, path, 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(); |
| } |
| } |
| } |
| // append a dummy if necessary |
| if (needDummy && !haveDummy) { |
| newItem(ti, SWT.NULL, -1); |
| } |
| return; |
| } |
| } |
| |
| if (childElements.length > 0) { |
| // TODO: Add filtering back? |
| Object[] filtered = filter(parentElementOrTreePath, childElements); |
| ViewerComparator comparator = getComparator(); |
| if (comparator != null) { |
| if (comparator instanceof TreePathViewerSorter) { |
| TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator; |
| if (path == null) { |
| path = internalGetSorterParentPath(widget, comparator); |
| } |
| tpvs.sort(this, path, filtered); |
| } else { |
| comparator.sort(this, filtered); |
| } |
| } |
| createAddedElements(widget, filtered); |
| } |
| } |
| |
| /** |
| * Filter the children elements. |
| * |
| * @param parentElementOrTreePath |
| * the parent element or path |
| * @param elements |
| * the child elements |
| * @return the filter list of children |
| */ |
| private Object[] filter(Object parentElementOrTreePath, Object[] elements) { |
| ViewerFilter[] filters = getFilters(); |
| if (filters != null) { |
| ArrayList filtered = new ArrayList(elements.length); |
| for (int i = 0; i < elements.length; i++) { |
| boolean add = true; |
| for (int j = 0; j < filters.length; j++) { |
| add = filters[j].select(this, parentElementOrTreePath, |
| elements[i]); |
| if (!add) { |
| break; |
| } |
| } |
| if (add) { |
| filtered.add(elements[i]); |
| } |
| } |
| return filtered.toArray(); |
| } |
| return elements; |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| ViewerComparator comparator = getComparator(); |
| TreePath parentPath = internalGetSorterParentPath(widget, comparator); |
| 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 (comparator == null) { |
| if (itemExists(items, element)) { |
| internalRefresh(element); |
| newItem = false; |
| } |
| index = -1; |
| } else { |
| lastInsertion = insertionPosition(items, comparator, |
| lastInsertion, element, parentPath); |
| // 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 |
| && internalCompare(comparator, parentPath, 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 |
| internalRefresh(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()) { |
| Widget[] existingItems = findItems(element); |
| // optimization for two common cases |
| if (existingItems.length == 0) { |
| return false; |
| } else if (existingItems.length == 1) { |
| if (items.length > 0 && existingItems[0] instanceof Item) { |
| Item existingItem = (Item) existingItems[0]; |
| return getParentItem(existingItem) == getParentItem(items[0]); |
| } |
| } |
| } |
| 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 comparator |
| * The comparator to use. |
| * @param lastInsertion |
| * the start index to start search for position from this allows |
| * optimizing search for multiple elements that are sorted |
| * themselves. |
| * @param element |
| * element to find position for. |
| * @param parentPath |
| * the tree path for the element's parent or <code>null</code> |
| * if the element is a root element or the sorter is not a |
| * {@link TreePathViewerSorter} |
| * @return the index to use when inserting the element. |
| * |
| */ |
| |
| private int insertionPosition(Item[] items, ViewerComparator comparator, |
| int lastInsertion, Object element, TreePath parentPath) { |
| |
| int size = items.length; |
| if (comparator == null) { |
| return size; |
| } |
| int min = lastInsertion, max = size - 1; |
| |
| while (min <= max) { |
| int mid = (min + max) / 2; |
| Object data = items[mid].getData(); |
| int compare = internalCompare(comparator, parentPath, 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 |
| * optimizing search for multiple elements that are sorted |
| * themselves. |
| * @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 the index of the element |
| */ |
| protected int indexForElement(Widget parent, Object element) { |
| ViewerComparator comparator = getComparator(); |
| TreePath parentPath = internalGetSorterParentPath(parent, comparator); |
| |
| Item[] items = getChildren(parent); |
| int count = items.length; |
| |
| if (comparator == null) { |
| return count; |
| } |
| int min = 0, max = count - 1; |
| |
| while (min <= max) { |
| int mid = (min + max) / 2; |
| Object data = items[mid].getData(); |
| int compare = internalCompare(comparator, parentPath, data, element); |
| if (compare == 0) { |
| // find first item > element |
| while (compare == 0) { |
| ++mid; |
| if (mid >= count) { |
| break; |
| } |
| data = items[mid].getData(); |
| compare = internalCompare(comparator, parentPath, data, |
| element); |
| } |
| return mid; |
| } |
| if (compare < 0) { |
| min = mid + 1; |
| } else { |
| max = mid - 1; |
| } |
| } |
| return min; |
| } |
| |
| /** |
| * Return the tree path that should be used as the parent path for the given |
| * widget and sorter. A <code>null</code> is returned if either the sorter |
| * is not a {@link TreePathViewerSorter} or if the parent widget is not an |
| * {@link Item} (i.e. is the root of the tree). |
| * |
| * @param parent |
| * the parent widget |
| * @param comparator |
| * the sorter |
| * @return the tree path that should be used as the parent path for the |
| * given widget and sorter |
| */ |
| private TreePath internalGetSorterParentPath(Widget parent, |
| ViewerComparator comparator) { |
| TreePath path; |
| if (comparator instanceof TreePathViewerSorter |
| && parent instanceof Item) { |
| Item item = (Item) parent; |
| path = getTreePathFromItem(item); |
| } else { |
| path = null; |
| } |
| return path; |
| } |
| |
| /** |
| * Compare the two elements using the given sorter. If the sorter is a |
| * {@link TreePathViewerSorter}, the provided tree path will be used. If |
| * the tree path is null and the sorter is a tree path sorter, then the |
| * elements are root elements |
| * |
| * @param comparator |
| * the sorter |
| * @param parentPath |
| * the path of the elements' parent |
| * @param e1 |
| * the first element |
| * @param e2 |
| * the second element |
| * @return the result of comparing the two elements |
| */ |
| private int internalCompare(ViewerComparator comparator, |
| TreePath parentPath, Object e1, Object e2) { |
| if (comparator instanceof TreePathViewerSorter) { |
| TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator; |
| return tpvs.compare(this, parentPath, e1, e2); |
| } |
| return comparator.compare(this, e1, e2); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.StructuredViewer#getSortedChildren(java.lang.Object) |
| */ |
| protected Object[] getSortedChildren(Object parentElementOrTreePath) { |
| Object[] result = getFilteredChildren(parentElementOrTreePath); |
| ViewerComparator comparator = getComparator(); |
| if (parentElementOrTreePath != null |
| && comparator instanceof TreePathViewerSorter) { |
| TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator; |
| |
| // be sure we're not modifying the original array from the model |
| result = (Object[]) result.clone(); |
| |
| TreePath path = null; |
| if (parentElementOrTreePath instanceof TreePath) { |
| path = (TreePath) parentElementOrTreePath; |
| } else { |
| Object parent = parentElementOrTreePath; |
| Widget w = internalGetWidgetToSelect(parent); |
| if (w != null) { |
| path = internalGetSorterParentPath(w, comparator); |
| } |
| } |
| tpvs.sort(this, path, result); |
| } else if (comparator != null) { |
| // be sure we're not modifying the original array from the model |
| result = (Object[]) result.clone(); |
| comparator.sort(this, result); |
| } |
| return result; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.StructuredViewer#getFilteredChildren(java.lang.Object) |
| */ |
| protected Object[] getFilteredChildren(Object parentElementOrTreePath) { |
| Object[] result = getRawChildren(parentElementOrTreePath); |
| ViewerFilter[] filters = getFilters(); |
| for (int i = 0; i < filters.length; i++) { |
| ViewerFilter filter = filters[i]; |
| result = filter.filter(this, parentElementOrTreePath, result); |
| } |
| return result; |
| } |
| |
| /** |
| * 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 parentElementOrTreePath |
| * the parent element or path |
| * @param childElement |
| * the child element |
| */ |
| public void add(Object parentElementOrTreePath, Object childElement) { |
| add(parentElementOrTreePath, 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) { |
| boolean oldBusy = busy; |
| busy = true; |
| try { |
| 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; |
| if (isTreePathContentProvider() && widget instanceof Item) { |
| TreePath path = getTreePathFromItem((Item) widget); |
| children = getSortedChildren(path); |
| } else { |
| children = getSortedChildren(parentElement); |
| } |
| for (int i = 0; i < children.length; i++) { |
| createTreeItem(widget, children[i], -1); |
| } |
| } |
| } |
| |
| }); |
| } finally { |
| busy = oldBusy; |
| } |
| } |
| |
| /** |
| * 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 void doUpdateItem(final Item item, Object element) { |
| if (item.isDisposed()) { |
| unmapElement(element, item); |
| return; |
| } |
| |
| int columnCount = doGetColumnCount(); |
| if (columnCount == 0)// If no columns are created then fake one |
| columnCount = 1; |
| |
| ViewerRow viewerRowFromItem = getViewerRowFromItem(item); |
| |
| boolean isVirtual = (getControl().getStyle() & SWT.VIRTUAL) != 0; |
| |
| // If the control is virtual, we cannot use the cached viewer row object. See bug 188663. |
| if (isVirtual) { |
| viewerRowFromItem = (ViewerRow) viewerRowFromItem.clone(); |
| } |
| |
| for (int column = 0; column < columnCount; column++) { |
| ViewerColumn columnViewer = getViewerColumn(column); |
| ViewerCell cellToUpdate = updateCell(viewerRowFromItem, column, |
| element); |
| |
| // If the control is virtual, we cannot use the cached cell object. See bug 188663. |
| if (isVirtual) { |
| cellToUpdate = new ViewerCell(cellToUpdate.getViewerRow(), cellToUpdate.getColumnIndex(), element); |
| } |
| |
| columnViewer.refresh(cellToUpdate); |
| |
| // As it is possible for user code to run the event |
| // loop check here. |
| if (item.isDisposed()) { |
| unmapElement(element, item); |
| return; |
| } |
| |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if the given list and array of items refer to |
| * the same model elements. Order is unimportant. |
| * <p> |
| * This method is not intended to be overridden by subclasses. |
| * </p> |
| * |
| * @param items |
| * the list of items |
| * @param current |
| * the array of items |
| * @return <code>true</code> if the refer to the same elements, |
| * <code>false</code> otherwise |
| * |
| * @since 3.1 in TreeViewer, moved to AbstractTreeViewer in 3.3 |
| */ |
| protected boolean isSameSelection(List items, Item[] current) { |
| // If they are not the same size then they are not equivalent |
| int n = items.size(); |
| if (n != current.length) { |
| return false; |
| } |
| |
| CustomHashtable itemSet = newHashtable(n * 2 + 1); |
| for (Iterator i = items.iterator(); i.hasNext();) { |
| Item item = (Item) i.next(); |
| Object element = item.getData(); |
| itemSet.put(element, element); |
| } |
| |
| // Go through the items of the current collection |
| // If there is a mismatch return false |
| for (int i = 0; i < current.length; i++) { |
| if (current[i].getData() == null |
| || !itemSet.containsKey(current[i].getData())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| |
| /* (non-Javadoc) Method declared on StructuredViewer. */ |
| protected void doUpdateItem(Widget widget, Object element, boolean fullMap) { |
| boolean oldBusy = busy; |
| busy = true; |
| try { |
| if (widget instanceof Item) { |
| Item item = (Item) widget; |
| |
| // ensure that back pointer 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)); |
| } |
| } finally { |
| busy = oldBusy; |
| } |
| } |
| |
| /** |
| * 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) { |
| if (isBusy()) |
| return; |
| 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 efficient 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 parentElementOrTreePath) { |
| boolean oldBusy = busy; |
| busy = true; |
| try { |
| Object parent; |
| TreePath path; |
| if (parentElementOrTreePath instanceof TreePath) { |
| path = (TreePath) parentElementOrTreePath; |
| parent = path.getLastSegment(); |
| } else { |
| parent = parentElementOrTreePath; |
| path = null; |
| } |
| if (parent != null) { |
| if (equals(parent, getRoot())) { |
| return super.getRawChildren(parent); |
| } |
| IContentProvider cp = getContentProvider(); |
| if (cp instanceof ITreePathContentProvider) { |
| ITreePathContentProvider tpcp = (ITreePathContentProvider) cp; |
| if (path == null) { |
| // A path was not provided so try and find one |
| Widget w = findItem(parent); |
| if (w instanceof Item) { |
| Item item = (Item) w; |
| path = getTreePathFromItem(item); |
| } |
| if (path == null) { |
| path = new TreePath(new Object[] { parent }); |
| } |
| } |
| Object[] result = tpcp.getChildren(path); |
| if (result != null) { |
| return result; |
| } |
| } else if (cp instanceof ITreeContentProvider) { |
| ITreeContentProvider tcp = (ITreeContentProvider) cp; |
| Object[] result = tcp.getChildren(parent); |
| if (result != null) { |
| return result; |
| } |
| } |
| } |
| return new Object[0]; |
| } finally { |
| busy = oldBusy; |
| } |
| } |
| |
| /** |
| * 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) |
| * |
| * @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget() |
| */ |
| 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; |
| } |
| |
| /* |
| * Overridden in AbstractTreeViewer to fix bug 108102 (code copied from |
| * StructuredViewer to avoid introducing new API) (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.StructuredViewer#handleDoubleSelect(org.eclipse.swt.events.SelectionEvent) |
| */ |
| protected void handleDoubleSelect(SelectionEvent event) { |
| // handle case where an earlier selection listener disposed the control. |
| Control control = getControl(); |
| if (control != null && !control.isDisposed()) { |
| // If the double-clicked element can be obtained from the event, use |
| // it |
| // otherwise get it from the control. Some controls like List do |
| // not have the notion of item. |
| // For details, see bug 90161 [Navigator] DefaultSelecting folders |
| // shouldn't always expand first one |
| ISelection selection; |
| if (event.item != null && event.item.getData() != null) { |
| |
| // changes to fix bug 108102 follow |
| TreePath treePath = getTreePathFromItem((Item) event.item); |
| selection = new TreeSelection(treePath); |
| // end of changes |
| |
| } else { |
| selection = getSelection(); |
| updateSelection(selection); |
| } |
| fireDoubleClick(new DoubleClickEvent(this, selection)); |
| } |
| } |
| |
| /** |
| * 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()); |
| internalInitializeTree(tree); |
| if (useRedraw) { |
| tree.setRedraw(true); |
| } |
| } |
| |
| }); |
| } |
| |
| /** |
| * @param tree |
| */ |
| protected void internalInitializeTree(Control tree) { |
| createChildren(tree); |
| internalExpandToLevel(tree, expandToLevel); |
| } |
| |
| /** |
| * 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); |
| Object element = internalToElement(elementOrPath); |
| w = internalFindChild(pw, element); |
| if (expand && pw instanceof Item) { |
| // expand parent items top-down |
| Item item = (Item) pw; |
| 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> |
| * |
| * @since 3.2 |
| */ |
| protected Object getParentElement(Object elementOrTreePath) { |
| if (elementOrTreePath instanceof TreePath) { |
| TreePath treePath = (TreePath) elementOrTreePath; |
| return (treePath).getParentPath(); |
| } |
| IContentProvider cp = getContentProvider(); |
| if (cp instanceof ITreePathContentProvider) { |
| ITreePathContentProvider tpcp = (ITreePathContentProvider) cp; |
| TreePath[] paths = tpcp.getParents(elementOrTreePath); |
| if (paths.length > 0) { |
| if (paths[0].getSegmentCount() == 0) { |
| return getInput(); |
| } |
| return paths[0].getLastSegment(); |
| } |
| } |
| if (cp instanceof ITreeContentProvider) { |
| ITreeContentProvider tcp = (ITreeContentProvider) cp; |
| return tcp.getParent(elementOrTreePath); |
| } |
| return null; |
| } |
| |
| /** |
| * 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 getControl(); |
| } |
| 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) { |
| if (widget instanceof Item && widget.getData() != null |
| && !isExpandable((Item) widget, null, widget.getData())) { |
| return; |
| } |
| 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 or tree). |
| * |
| * @param parent |
| * the parent item |
| * @param element |
| * the element |
| * @return Widget |
| */ |
| private Widget internalFindChild(Widget 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 favor 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 |
| */ |
| /* package */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 favor of |
| * proper support for multiple equal elements. |
| * </p> |
| * |
| * @param elementsOrPaths |
| * the elements or element paths to remove |
| * @since 3.1 |
| */ |
| protected void internalRemove(Object[] elementsOrPaths) { |
| Object input = getInput(); |
| for (int i = 0; i < elementsOrPaths.length; ++i) { |
| Object element = elementsOrPaths[i]; |
| if (equals(element, input)) { |
| setInput(null); |
| return; |
| } |
| Widget[] childItems = internalFindItems(element); |
| for (int j = 0; j < childItems.length; j++) { |
| Widget childItem = childItems[j]; |
| if (childItem instanceof Item) { |
| disassociate((Item) childItem); |
| childItem.dispose(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Removes the given elements from this viewer, whenever those elements |
| * appear as children of the given parent. |
| * |
| * @param parent the parent element |
| * @param elements |
| * the elements to remove |
| * @since 3.1 |
| */ |
| protected void internalRemove(Object parent, Object[] elements) { |
| |
| CustomHashtable toRemove = new CustomHashtable(getComparer()); |
| for (int i = 0; i < elements.length; i++) { |
| toRemove.put(elements[i], elements[i]); |
| } |
| |
| // Find each place the parent appears in the tree |
| Widget[] parentItemArray = findItems(parent); |
| for (int i = 0; i < parentItemArray.length; i++) { |
| Widget parentItem = parentItemArray[i]; |
| |
| // Iterate over the child items and remove each one |
| Item[] children = getChildren(parentItem); |
| |
| for (int j = 0; j < children.length; j++) { |
| Item child = children[j]; |
| |
| Object data = child.getData(); |
| if (data != null && toRemove.containsKey(data)) { |
| disassociate(child); |
| child.dispose(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| if (expandedElements.size() > 0) { |
| 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 or path can |
| * be expanded. Clients should query expandability by path if the viewer's |
| * content provider is an {@link ITreePathContentProvider}. |
| * <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 elementOrTreePath |
| * the element or path |
| * @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 elementOrTreePath) { |
| Object element; |
| TreePath path; |
| if (elementOrTreePath instanceof TreePath) { |
| path = (TreePath) elementOrTreePath; |
| element = path.getLastSegment(); |
| } else { |
| element = elementOrTreePath; |
| path = null; |
| } |
| IContentProvider cp = getContentProvider(); |
| if (cp instanceof ITreePathContentProvider) { |
| ITreePathContentProvider tpcp = (ITreePathContentProvider) cp; |
| if (path == null) { |
| // A path was not provided so try and find one |
| Widget w = findItem(element); |
| if (w instanceof Item) { |
| Item item = (Item) w; |
| path = getTreePathFromItem(item); |
| } |
| if (path == null) { |
| path = new TreePath(new Object[] { element }); |
| } |
| } |
| return tpcp.hasChildren(path); |
| } |
| if (cp instanceof ITreeContentProvider) { |
| ITreeContentProvider tcp = (ITreeContentProvider) cp; |
| return tcp.hasChildren(element); |
| } |
| return false; |
| } |
| |
| /** |
| * Return whether the given element is expandable. |
| * |
| * @param item |
| * the tree item for the element |
| * @param parentPath |
| * the parent path if it is known or <code>null</code> if it |
| * needs to be determines |
| * @param element |
| * the element |
| * @return whether the given element is expandable |
| */ |
| private boolean isExpandable(Item item, TreePath parentPath, Object element) { |
| Object elementOrTreePath = element; |
| if (isTreePathContentProvider()) { |
| if (parentPath != null) { |
| elementOrTreePath = parentPath.createChildPath(element); |
| } else { |
| elementOrTreePath = getTreePathFromItem(item); |
| } |
| } |
| return isExpandable(elementOrTreePath); |
| } |
| |
| /* (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 elementsOrTreePaths |
| * the elements to remove |
| */ |
| public void remove(final Object[] elementsOrTreePaths) { |
| assertElementsNotNull(elementsOrTreePaths); |
| if (elementsOrTreePaths.length == 0) { |
| return; |
| } |
| if (isBusy()) |
| return; |
| preservingSelection(new Runnable() { |
| public void run() { |
| internalRemove(elementsOrTreePaths); |
| } |
| }); |
| } |
| |
| /** |
| * Removes the given elements from this viewer whenever they appear as |
| * children of the given parent element. If the given elements also appear |
| * as children of some other parent, the other parent will remain unchanged. |
| * 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 parent |
| * the parent of the elements to remove |
| * @param elements |
| * the elements to remove |
| * |
| * @since 3.2 |
| */ |
| public void remove(final Object parent, final Object[] elements) { |
| assertElementsNotNull(elements); |
| if (elements.length == 0) { |
| return; |
| } |
| if (isBusy()) |
| return; |
| preservingSelection(new Runnable() { |
| public void run() { |
| internalRemove(parent, 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 elementsOrTreePaths |
| * the element |
| */ |
| public void remove(Object elementsOrTreePaths) { |
| remove(new Object[] { elementsOrTreePaths }); |
| } |
| |
| /** |
| * 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 itself 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 |
| || provider instanceof ITreePathContentProvider); |
| } |
| |
| /** |
| * 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); |
| if (isBusy()) { |
| return; |
| } |
| 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() |
| * |
| * @since 3.2 |
| */ |
| public void setExpandedTreePaths(TreePath[] treePaths) { |
| assertElementsNotNull(treePaths); |
| if (isBusy()) |
| return; |
| 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); |
| if (isBusy()) |
| return; |
| 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) { |
| Object elementOrTreePath = v.get(i); |
| // Use internalExpand since item may not yet be created. See |
| // 1G6B1AR. |
| Widget w = internalExpand(elementOrTreePath, false); |
| if (w instanceof Item) { |
| newSelection.add(w); |
| } else if (w == null && elementOrTreePath instanceof TreePath) { |
| TreePath treePath = (TreePath) elementOrTreePath; |
| Object element = treePath.getLastSegment(); |
| if (element != null) { |
| w = internalExpand(element, 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(ti, null, 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) { |
| if (isTreePathContentProvider() && widget instanceof Item) { |
| TreePath path = getTreePathFromItem((Item) widget); |
| elementChildren = getSortedChildren(path); |
| } else { |
| 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); |
| |
| // dispose of surplus items, optimizing for the case where elements have |
| // been deleted but not reordered, or all elements have been removed. |
| int numItemsToDispose = items.length - min; |
| if (numItemsToDispose > 0) { |
| CustomHashtable children = newHashtable(elementChildren.length * 2); |
| for (int i = 0; i < elementChildren.length; i++) { |
| Object elementChild = elementChildren[i]; |
| children.put(elementChild, elementChild); |
| } |
| int i = 0; |
| while (numItemsToDispose > 0 && i < items.length) { |
| Object data = items[i].getData(); |
| if (data == null || items.length - i <= numItemsToDispose || !children.containsKey(data)) { |
| if (data != null) { |
| disassociate(items[i]); |
| } |
| items[i].dispose(); |
| if (i + 1 < items.length) { |
| // The components at positions i+1 through |
| // items.length-1 in the source array are copied into |
| // positions i through items.length-2 |
| System.arraycopy(items, i + 1, items, i, items.length - (i+1)); |
| } |
| numItemsToDispose--; |
| } else { |
| i++; |
| } |
| } |
| } |
| |
| // 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); |
| } else { |
| // old and new elements are equal |
| updatePlus(item, newElement); |
| if (updateLabels) { |
| 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. |
| // This needs to be done in a second loop, see bug 148025. |
| for (int i = 0; i < min; ++i) { |
| Item item = items[i]; |
| Object newElement = elementChildren[i]; |
| setExpanded(item, expanded.containsKey(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(item, null, 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); |
| } |
| } |
| } |
| |
| /** |
| * Returns the tree path for the given item. |
| * @param item |
| * @return {@link TreePath} |
| * |
| * @since 3.2 |
| */ |
| 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()]), getComparer()); |
| } |
| |
| 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 |
| * |
| * @since 3.2 |
| */ |
| 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()]); |
| } |
| |
| private boolean isTreePathContentProvider() { |
| return getContentProvider() instanceof ITreePathContentProvider; |
| } |
| |
| /** |
| * Inserts the given element as a new child element of the given parent |
| * element at the given position. If this viewer has a sorter, the position |
| * is ignored and the element is inserted at the correct position in the |
| * sort order. |
| * <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 parentElementOrTreePath |
| * the parent element, or the tree path to the parent |
| * @param element |
| * the element |
| * @param position |
| * a 0-based position relative to the model, or -1 to indicate |
| * the last position |
| * |
| * @since 3.2 |
| */ |
| public void insert(Object parentElementOrTreePath, Object element, |
| int position) { |
| Assert.isNotNull(parentElementOrTreePath); |
| Assert.isNotNull(element); |
| if (isBusy()) |
| return; |
| if (getComparator() != null || hasFilters()) { |
| add(parentElementOrTreePath, new Object[] { element }); |
| return; |
| } |
| Widget[] items; |
| if (internalIsInputOrEmptyPath(parentElementOrTreePath)) { |
| items = new Widget[] { getControl() }; |
| } else { |
| items = internalFindItems(parentElementOrTreePath); |
| } |
| |
| for (int i = 0; i < items.length; i++) { |
| Widget widget = items[i]; |
| if (widget instanceof Item) { |
| Item item = (Item) widget; |
| |
| Item[] childItems = getChildren(item); |
| if (getExpanded(item) |
| || (childItems.length > 0 && childItems[0].getData() != null)) { |
| // item has real children, go ahead and add |
| int insertionPosition = position; |
| if (insertionPosition == -1) { |
| insertionPosition = getItemCount(item); |
| } |
| |
| createTreeItem(item, element, insertionPosition); |
| } |
| } else { |
| int insertionPosition = position; |
| if (insertionPosition == -1) { |
| insertionPosition = getItemCount((Control) widget); |
| } |
| |
| createTreeItem(widget, element, insertionPosition); |
| } |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.ColumnViewer#getColumnViewerOwner(int) |
| */ |
| protected Widget getColumnViewerOwner(int columnIndex) { |
| // Return null by default |
| return null; |
| } |
| |
| /** |
| * This implementation of {@link #getItemAt(Point)} returns null to ensure |
| * API backwards compatibility. Subclasses should override. |
| * |
| * @since 3.3 |
| */ |
| protected Item getItemAt(Point point) { |
| return null; |
| } |
| |
| /** |
| * This implementation of {@link #createViewerEditor()} returns null to ensure |
| * API backwards compatibility. Subclasses should override. |
| * |
| * @since 3.3 |
| */ |
| protected ColumnViewerEditor createViewerEditor() { |
| return null; |
| } |
| |
| /** |
| * Returns the number of columns of this viewer. |
| * <p><b>Subclasses should overwrite this method, which has a default |
| * implementation (returning 0) for API backwards compatility reasons</b></p> |
| * |
| * @return the number of columns |
| * |
| * @since 3.3 |
| */ |
| protected int doGetColumnCount() { |
| return 0; |
| } |
| |
| |
| /** |
| * This implementation of buildLabel handles tree paths as well as elements. |
| * |
| * @param updateLabel |
| * the ViewerLabel to collect the result in |
| * @param elementOrPath |
| * the element or tree path for which a label should be built |
| * |
| * @see org.eclipse.jface.viewers.StructuredViewer#buildLabel(org.eclipse.jface.viewers.ViewerLabel, |
| * java.lang.Object) |
| */ |
| protected void buildLabel(ViewerLabel updateLabel, Object elementOrPath) { |
| Object element; |
| if (elementOrPath instanceof TreePath) { |
| TreePath path = (TreePath) elementOrPath; |
| IBaseLabelProvider provider = getLabelProvider(); |
| if (provider instanceof ITreePathLabelProvider) { |
| ITreePathLabelProvider pprov = (ITreePathLabelProvider) provider; |
| buildLabel(updateLabel, path, pprov); |
| return; |
| } |
| element = path.getLastSegment(); |
| } else { |
| element = elementOrPath; |
| } |
| super.buildLabel(updateLabel, element); |
| } |
| |
| /** |
| * Returns true if the given object is either the input or an empty tree path. |
| * |
| * @param elementOrTreePath an element which could either be the viewer's input, or a tree path |
| * |
| * @return <code>true</code> if the given object is either the input or an empty tree path, |
| * <code>false</code> otherwise. |
| * @since 3.3 |
| */ |
| final protected boolean internalIsInputOrEmptyPath(final Object elementOrTreePath) { |
| if (elementOrTreePath.equals(getInput())) |
| return true; |
| if (!(elementOrTreePath instanceof TreePath)) |
| return false; |
| return ((TreePath) elementOrTreePath).getSegmentCount() == 0; |
| } |
| |
| /* |
| * Subclasses should implement |
| */ |
| protected ViewerRow getViewerRowFromItem(Widget item) { |
| return null; |
| } |
| } |