0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: * Tom Schindl <tom.schindl@bestsolution.at> - bug 153993, bug 167323, bug 175192
0011: *******************************************************************************/package org.eclipse.jface.viewers;
0012:
0013: import java.util.ArrayList;
0014: import java.util.Arrays;
0015: import java.util.Iterator;
0016: import java.util.LinkedList;
0017: import java.util.List;
0018:
0019: import org.eclipse.core.runtime.Assert;
0020: import org.eclipse.core.runtime.ListenerList;
0021: import org.eclipse.jface.util.SafeRunnable;
0022: import org.eclipse.swt.SWT;
0023: import org.eclipse.swt.custom.BusyIndicator;
0024: import org.eclipse.swt.events.SelectionEvent;
0025: import org.eclipse.swt.events.SelectionListener;
0026: import org.eclipse.swt.events.TreeEvent;
0027: import org.eclipse.swt.events.TreeListener;
0028: import org.eclipse.swt.graphics.Point;
0029: import org.eclipse.swt.widgets.Control;
0030: import org.eclipse.swt.widgets.Item;
0031: import org.eclipse.swt.widgets.Widget;
0032:
0033: /**
0034: * Abstract base implementation for tree-structure-oriented viewers (trees and
0035: * table trees).
0036: * <p>
0037: * Nodes in the tree can be in either an expanded or a collapsed state,
0038: * depending on whether the children on a node are visible. This class
0039: * introduces public methods for controlling the expanding and collapsing of
0040: * nodes.
0041: * </p>
0042: * <p>
0043: * As of 3.2, AbstractTreeViewer supports multiple equal elements (each with a
0044: * different parent chain) in the tree. This support requires that clients
0045: * enable the element map by calling <code>setUseHashLookup(true)</code>.
0046: * </p>
0047: * <p>
0048: * Content providers for abstract tree viewers must implement one of the
0049: * interfaces <code>ITreeContentProvider</code> or (as of 3.2, to support
0050: * multiple equal elements) <code>ITreePathContentProvider</code>.
0051: * </p>
0052: *
0053: * @see TreeViewer
0054: */
0055: public abstract class AbstractTreeViewer extends ColumnViewer {
0056:
0057: /**
0058: * Constant indicating that all levels of the tree should be expanded or
0059: * collapsed.
0060: *
0061: * @see #expandToLevel(int)
0062: * @see #collapseToLevel(Object, int)
0063: */
0064: public static final int ALL_LEVELS = -1;
0065:
0066: /**
0067: * List of registered tree listeners (element type:
0068: * <code>TreeListener</code>).
0069: */
0070: private ListenerList treeListeners = new ListenerList();
0071:
0072: /**
0073: * The level to which the tree is automatically expanded each time the
0074: * viewer's input is changed (that is, by <code>setInput</code>). A value
0075: * of 0 means that auto-expand is off.
0076: *
0077: * @see #setAutoExpandLevel
0078: */
0079: private int expandToLevel = 0;
0080:
0081: /**
0082: * Safe runnable used to update an item.
0083: */
0084: class UpdateItemSafeRunnable extends SafeRunnable {
0085: private Object element;
0086:
0087: private Item item;
0088:
0089: UpdateItemSafeRunnable(Item item, Object element) {
0090: this .item = item;
0091: this .element = element;
0092: }
0093:
0094: public void run() {
0095: doUpdateItem(item, element);
0096: }
0097:
0098: }
0099:
0100: /**
0101: * Creates an abstract tree viewer. The viewer has no input, no content
0102: * provider, a default label provider, no sorter, no filters, and has
0103: * auto-expand turned off.
0104: */
0105: protected AbstractTreeViewer() {
0106: // do nothing
0107: }
0108:
0109: /**
0110: * Adds the given child elements to this viewer as children of the given
0111: * parent element. If this viewer does not have a sorter, the elements are
0112: * added at the end of the parent's list of children in the order given;
0113: * otherwise, the elements are inserted at the appropriate positions.
0114: * <p>
0115: * This method should be called (by the content provider) when elements have
0116: * been added to the model, in order to cause the viewer to accurately
0117: * reflect the model. This method only affects the viewer, not the model.
0118: * </p>
0119: *
0120: * @param parentElementOrTreePath
0121: * the parent element
0122: * @param childElements
0123: * the child elements to add
0124: */
0125: public void add(Object parentElementOrTreePath,
0126: Object[] childElements) {
0127: Assert.isNotNull(parentElementOrTreePath);
0128: assertElementsNotNull(childElements);
0129: if (isBusy())
0130: return;
0131: Widget[] widgets = internalFindItems(parentElementOrTreePath);
0132: // If parent hasn't been realized yet, just ignore the add.
0133: if (widgets.length == 0) {
0134: return;
0135: }
0136:
0137: for (int i = 0; i < widgets.length; i++) {
0138: internalAdd(widgets[i], parentElementOrTreePath,
0139: childElements);
0140: }
0141: }
0142:
0143: /**
0144: * Find the items for the given element of tree path
0145: *
0146: * @param parentElementOrTreePath
0147: * the element or tree path
0148: * @return the items for that element
0149: *
0150: * @since 3.3
0151: */
0152: final protected Widget[] internalFindItems(
0153: Object parentElementOrTreePath) {
0154: Widget[] widgets;
0155: if (parentElementOrTreePath instanceof TreePath) {
0156: TreePath path = (TreePath) parentElementOrTreePath;
0157: Widget w = internalFindItem(path);
0158: if (w == null) {
0159: widgets = new Widget[] {};
0160: } else {
0161: widgets = new Widget[] { w };
0162: }
0163: } else {
0164: widgets = findItems(parentElementOrTreePath);
0165: }
0166: return widgets;
0167: }
0168:
0169: /**
0170: * Return the item at the given path or <code>null</code>
0171: *
0172: * @param path
0173: * the path
0174: * @return {@link Widget} the item at that path
0175: */
0176: private Widget internalFindItem(TreePath path) {
0177: Widget[] widgets = findItems(path.getLastSegment());
0178: for (int i = 0; i < widgets.length; i++) {
0179: Widget widget = widgets[i];
0180: if (widget instanceof Item) {
0181: Item item = (Item) widget;
0182: TreePath p = getTreePathFromItem(item);
0183: if (p.equals(path)) {
0184: return widget;
0185: }
0186: }
0187: }
0188: return null;
0189: }
0190:
0191: /**
0192: * Adds the given child elements to this viewer as children of the given
0193: * parent element.
0194: * <p>
0195: * EXPERIMENTAL. Not to be used except by JDT. This method was added to
0196: * support JDT's explorations into grouping by working sets, which requires
0197: * viewers to support multiple equal elements. See bug 76482 for more
0198: * details. This support will likely be removed in Eclipse 3.2 in favor of
0199: * proper support for multiple equal elements.
0200: * </p>
0201: *
0202: * @param widget
0203: * the widget for the parent element
0204: * @param parentElementOrTreePath
0205: * the parent element
0206: * @param childElements
0207: * the child elements to add
0208: * @since 3.1
0209: */
0210: protected void internalAdd(Widget widget,
0211: Object parentElementOrTreePath, Object[] childElements) {
0212: Object parent;
0213: TreePath path;
0214: if (parentElementOrTreePath instanceof TreePath) {
0215: path = (TreePath) parentElementOrTreePath;
0216: parent = path.getLastSegment();
0217: } else {
0218: parent = parentElementOrTreePath;
0219: path = null;
0220: }
0221:
0222: // optimization!
0223: // if the widget is not expanded we just invalidate the subtree
0224: if (widget instanceof Item) {
0225: Item ti = (Item) widget;
0226: if (!getExpanded(ti)) {
0227: boolean needDummy = isExpandable(ti, path, parent);
0228: boolean haveDummy = false;
0229: // remove all children
0230: Item[] items = getItems(ti);
0231: for (int i = 0; i < items.length; i++) {
0232: if (items[i].getData() != null) {
0233: disassociate(items[i]);
0234: items[i].dispose();
0235: } else {
0236: if (needDummy && !haveDummy) {
0237: haveDummy = true;
0238: } else {
0239: items[i].dispose();
0240: }
0241: }
0242: }
0243: // append a dummy if necessary
0244: if (needDummy && !haveDummy) {
0245: newItem(ti, SWT.NULL, -1);
0246: }
0247: return;
0248: }
0249: }
0250:
0251: if (childElements.length > 0) {
0252: // TODO: Add filtering back?
0253: Object[] filtered = filter(parentElementOrTreePath,
0254: childElements);
0255: ViewerComparator comparator = getComparator();
0256: if (comparator != null) {
0257: if (comparator instanceof TreePathViewerSorter) {
0258: TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;
0259: if (path == null) {
0260: path = internalGetSorterParentPath(widget,
0261: comparator);
0262: }
0263: tpvs.sort(this , path, filtered);
0264: } else {
0265: comparator.sort(this , filtered);
0266: }
0267: }
0268: createAddedElements(widget, filtered);
0269: }
0270: }
0271:
0272: /**
0273: * Filter the children elements.
0274: *
0275: * @param parentElementOrTreePath
0276: * the parent element or path
0277: * @param elements
0278: * the child elements
0279: * @return the filter list of children
0280: */
0281: private Object[] filter(Object parentElementOrTreePath,
0282: Object[] elements) {
0283: ViewerFilter[] filters = getFilters();
0284: if (filters != null) {
0285: ArrayList filtered = new ArrayList(elements.length);
0286: for (int i = 0; i < elements.length; i++) {
0287: boolean add = true;
0288: for (int j = 0; j < filters.length; j++) {
0289: add = filters[j].select(this ,
0290: parentElementOrTreePath, elements[i]);
0291: if (!add) {
0292: break;
0293: }
0294: }
0295: if (add) {
0296: filtered.add(elements[i]);
0297: }
0298: }
0299: return filtered.toArray();
0300: }
0301: return elements;
0302: }
0303:
0304: /**
0305: * Create the new elements in the parent widget. If the child already exists
0306: * do nothing.
0307: *
0308: * @param widget
0309: * @param elements
0310: * Sorted list of elements to add.
0311: */
0312: private void createAddedElements(Widget widget, Object[] elements) {
0313:
0314: if (elements.length == 1) {
0315: if (equals(elements[0], widget.getData())) {
0316: return;
0317: }
0318: }
0319:
0320: ViewerComparator comparator = getComparator();
0321: TreePath parentPath = internalGetSorterParentPath(widget,
0322: comparator);
0323: Item[] items = getChildren(widget);
0324:
0325: // As the items are sorted already we optimize for a
0326: // start position
0327: int lastInsertion = 0;
0328:
0329: // Optimize for the empty case
0330: if (items.length == 0) {
0331: for (int i = 0; i < elements.length; i++) {
0332: createTreeItem(widget, elements[i], -1);
0333: }
0334: return;
0335: }
0336:
0337: for (int i = 0; i < elements.length; i++) {
0338: boolean newItem = true;
0339: Object element = elements[i];
0340: int index;
0341: if (comparator == null) {
0342: if (itemExists(items, element)) {
0343: internalRefresh(element);
0344: newItem = false;
0345: }
0346: index = -1;
0347: } else {
0348: lastInsertion = insertionPosition(items, comparator,
0349: lastInsertion, element, parentPath);
0350: // As we are only searching the original array we keep track of
0351: // those positions only
0352: if (lastInsertion == items.length) {
0353: index = -1;
0354: } else {// See if we should just refresh
0355: while (lastInsertion < items.length
0356: && internalCompare(comparator, parentPath,
0357: element, items[lastInsertion]
0358: .getData()) == 0) {
0359: // As we cannot assume the sorter is consistent with
0360: // equals() - therefore we can
0361: // just check against the item prior to this index (if
0362: // any)
0363: if (items[lastInsertion].getData().equals(
0364: element)) {
0365: // refresh the element in case it has new children
0366: internalRefresh(element);
0367: newItem = false;
0368: }
0369: lastInsertion++;// We had an insertion so increment
0370: }
0371: // Did we get to the end?
0372: if (lastInsertion == items.length) {
0373: index = -1;
0374: } else {
0375: index = lastInsertion + i; // Add the index as the
0376: // array is growing
0377: }
0378: }
0379: }
0380: if (newItem) {
0381: createTreeItem(widget, element, index);
0382: }
0383: }
0384: }
0385:
0386: /**
0387: * See if element is the data of one of the elements in items.
0388: *
0389: * @param items
0390: * @param element
0391: * @return <code>true</code> if the element matches.
0392: */
0393: private boolean itemExists(Item[] items, Object element) {
0394: if (usingElementMap()) {
0395: Widget[] existingItems = findItems(element);
0396: // optimization for two common cases
0397: if (existingItems.length == 0) {
0398: return false;
0399: } else if (existingItems.length == 1) {
0400: if (items.length > 0
0401: && existingItems[0] instanceof Item) {
0402: Item existingItem = (Item) existingItems[0];
0403: return getParentItem(existingItem) == getParentItem(items[0]);
0404: }
0405: }
0406: }
0407: for (int i = 0; i < items.length; i++) {
0408: if (items[i].getData().equals(element)) {
0409: return true;
0410: }
0411: }
0412: return false;
0413: }
0414:
0415: /**
0416: * Returns the index where the item should be inserted. It uses sorter to
0417: * determine the correct position, if sorter is not assigned, returns the
0418: * index of the element after the last.
0419: *
0420: * @param items
0421: * the items to search
0422: * @param comparator
0423: * The comparator to use.
0424: * @param lastInsertion
0425: * the start index to start search for position from this allows
0426: * optimizing search for multiple elements that are sorted
0427: * themselves.
0428: * @param element
0429: * element to find position for.
0430: * @param parentPath
0431: * the tree path for the element's parent or <code>null</code>
0432: * if the element is a root element or the sorter is not a
0433: * {@link TreePathViewerSorter}
0434: * @return the index to use when inserting the element.
0435: *
0436: */
0437:
0438: private int insertionPosition(Item[] items,
0439: ViewerComparator comparator, int lastInsertion,
0440: Object element, TreePath parentPath) {
0441:
0442: int size = items.length;
0443: if (comparator == null) {
0444: return size;
0445: }
0446: int min = lastInsertion, max = size - 1;
0447:
0448: while (min <= max) {
0449: int mid = (min + max) / 2;
0450: Object data = items[mid].getData();
0451: int compare = internalCompare(comparator, parentPath, data,
0452: element);
0453: if (compare == 0) {
0454: return mid;// Return if we already match
0455: }
0456: if (compare < 0) {
0457: min = mid + 1;
0458: } else {
0459: max = mid - 1;
0460: }
0461: }
0462: return min;
0463:
0464: }
0465:
0466: /**
0467: * Returns the index where the item should be inserted. It uses sorter to
0468: * determine the correct position, if sorter is not assigned, returns the
0469: * index of the element after the last.
0470: *
0471: * @param parent
0472: * The parent widget
0473: * @param sorter
0474: * The sorter to use.
0475: * @param startIndex
0476: * the start index to start search for position from this allows
0477: * optimizing search for multiple elements that are sorted
0478: * themselves.
0479: * @param element
0480: * element to find position for.
0481: * @param currentSize
0482: * the current size of the collection
0483: * @return the index to use when inserting the element.
0484: *
0485: */
0486:
0487: /**
0488: * Returns the index where the item should be inserted.
0489: *
0490: * @param parent
0491: * The parent widget the element will be inserted into.
0492: * @param element
0493: * The element to insert.
0494: * @return the index of the element
0495: */
0496: protected int indexForElement(Widget parent, Object element) {
0497: ViewerComparator comparator = getComparator();
0498: TreePath parentPath = internalGetSorterParentPath(parent,
0499: comparator);
0500:
0501: Item[] items = getChildren(parent);
0502: int count = items.length;
0503:
0504: if (comparator == null) {
0505: return count;
0506: }
0507: int min = 0, max = count - 1;
0508:
0509: while (min <= max) {
0510: int mid = (min + max) / 2;
0511: Object data = items[mid].getData();
0512: int compare = internalCompare(comparator, parentPath, data,
0513: element);
0514: if (compare == 0) {
0515: // find first item > element
0516: while (compare == 0) {
0517: ++mid;
0518: if (mid >= count) {
0519: break;
0520: }
0521: data = items[mid].getData();
0522: compare = internalCompare(comparator, parentPath,
0523: data, element);
0524: }
0525: return mid;
0526: }
0527: if (compare < 0) {
0528: min = mid + 1;
0529: } else {
0530: max = mid - 1;
0531: }
0532: }
0533: return min;
0534: }
0535:
0536: /**
0537: * Return the tree path that should be used as the parent path for the given
0538: * widget and sorter. A <code>null</code> is returned if either the sorter
0539: * is not a {@link TreePathViewerSorter} or if the parent widget is not an
0540: * {@link Item} (i.e. is the root of the tree).
0541: *
0542: * @param parent
0543: * the parent widget
0544: * @param comparator
0545: * the sorter
0546: * @return the tree path that should be used as the parent path for the
0547: * given widget and sorter
0548: */
0549: private TreePath internalGetSorterParentPath(Widget parent,
0550: ViewerComparator comparator) {
0551: TreePath path;
0552: if (comparator instanceof TreePathViewerSorter
0553: && parent instanceof Item) {
0554: Item item = (Item) parent;
0555: path = getTreePathFromItem(item);
0556: } else {
0557: path = null;
0558: }
0559: return path;
0560: }
0561:
0562: /**
0563: * Compare the two elements using the given sorter. If the sorter is a
0564: * {@link TreePathViewerSorter}, the provided tree path will be used. If
0565: * the tree path is null and the sorter is a tree path sorter, then the
0566: * elements are root elements
0567: *
0568: * @param comparator
0569: * the sorter
0570: * @param parentPath
0571: * the path of the elements' parent
0572: * @param e1
0573: * the first element
0574: * @param e2
0575: * the second element
0576: * @return the result of comparing the two elements
0577: */
0578: private int internalCompare(ViewerComparator comparator,
0579: TreePath parentPath, Object e1, Object e2) {
0580: if (comparator instanceof TreePathViewerSorter) {
0581: TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;
0582: return tpvs.compare(this , parentPath, e1, e2);
0583: }
0584: return comparator.compare(this , e1, e2);
0585: }
0586:
0587: /*
0588: * (non-Javadoc)
0589: *
0590: * @see org.eclipse.jface.viewers.StructuredViewer#getSortedChildren(java.lang.Object)
0591: */
0592: protected Object[] getSortedChildren(Object parentElementOrTreePath) {
0593: Object[] result = getFilteredChildren(parentElementOrTreePath);
0594: ViewerComparator comparator = getComparator();
0595: if (parentElementOrTreePath != null
0596: && comparator instanceof TreePathViewerSorter) {
0597: TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;
0598:
0599: // be sure we're not modifying the original array from the model
0600: result = (Object[]) result.clone();
0601:
0602: TreePath path = null;
0603: if (parentElementOrTreePath instanceof TreePath) {
0604: path = (TreePath) parentElementOrTreePath;
0605: } else {
0606: Object parent = parentElementOrTreePath;
0607: Widget w = internalGetWidgetToSelect(parent);
0608: if (w != null) {
0609: path = internalGetSorterParentPath(w, comparator);
0610: }
0611: }
0612: tpvs.sort(this , path, result);
0613: } else if (comparator != null) {
0614: // be sure we're not modifying the original array from the model
0615: result = (Object[]) result.clone();
0616: comparator.sort(this , result);
0617: }
0618: return result;
0619: }
0620:
0621: /*
0622: * (non-Javadoc)
0623: *
0624: * @see org.eclipse.jface.viewers.StructuredViewer#getFilteredChildren(java.lang.Object)
0625: */
0626: protected Object[] getFilteredChildren(
0627: Object parentElementOrTreePath) {
0628: Object[] result = getRawChildren(parentElementOrTreePath);
0629: ViewerFilter[] filters = getFilters();
0630: for (int i = 0; i < filters.length; i++) {
0631: ViewerFilter filter = filters[i];
0632: result = filter.filter(this , parentElementOrTreePath,
0633: result);
0634: }
0635: return result;
0636: }
0637:
0638: /**
0639: * Adds the given child element to this viewer as a child of the given
0640: * parent element. If this viewer does not have a sorter, the element is
0641: * added at the end of the parent's list of children; otherwise, the element
0642: * is inserted at the appropriate position.
0643: * <p>
0644: * This method should be called (by the content provider) when a single
0645: * element has been added to the model, in order to cause the viewer to
0646: * accurately reflect the model. This method only affects the viewer, not
0647: * the model. Note that there is another method for efficiently processing
0648: * the simultaneous addition of multiple elements.
0649: * </p>
0650: *
0651: * @param parentElementOrTreePath
0652: * the parent element or path
0653: * @param childElement
0654: * the child element
0655: */
0656: public void add(Object parentElementOrTreePath, Object childElement) {
0657: add(parentElementOrTreePath, new Object[] { childElement });
0658: }
0659:
0660: /**
0661: * Adds the given SWT selection listener to the given SWT control.
0662: *
0663: * @param control
0664: * the SWT control
0665: * @param listener
0666: * the SWT selection listener
0667: * @deprecated
0668: */
0669: protected void addSelectionListener(Control control,
0670: SelectionListener listener) {
0671: // do nothing
0672: }
0673:
0674: /**
0675: * Adds a listener for expand and collapse events in this viewer. Has no
0676: * effect if an identical listener is already registered.
0677: *
0678: * @param listener
0679: * a tree viewer listener
0680: */
0681: public void addTreeListener(ITreeViewerListener listener) {
0682: treeListeners.add(listener);
0683: }
0684:
0685: /**
0686: * Adds the given SWT tree listener to the given SWT control.
0687: *
0688: * @param control
0689: * the SWT control
0690: * @param listener
0691: * the SWT tree listener
0692: */
0693: protected abstract void addTreeListener(Control control,
0694: TreeListener listener);
0695:
0696: /*
0697: * (non-Javadoc)
0698: *
0699: * @see StructuredViewer#associate(Object, Item)
0700: */
0701: protected void associate(Object element, Item item) {
0702: Object data = item.getData();
0703: if (data != null && data != element && equals(data, element)) {
0704: // workaround for PR 1FV62BT
0705: // assumption: elements are equal but not identical
0706: // -> remove from map but don't touch children
0707: unmapElement(data, item);
0708: item.setData(element);
0709: mapElement(element, item);
0710: } else {
0711: // recursively disassociate all
0712: super .associate(element, item);
0713: }
0714: }
0715:
0716: /**
0717: * Collapses all nodes of the viewer's tree, starting with the root. This
0718: * method is equivalent to <code>collapseToLevel(ALL_LEVELS)</code>.
0719: */
0720: public void collapseAll() {
0721: Object root = getRoot();
0722: if (root != null) {
0723: collapseToLevel(root, ALL_LEVELS);
0724: }
0725: }
0726:
0727: /**
0728: * Collapses the subtree rooted at the given element or tree path to the
0729: * given level.
0730: *
0731: * @param elementOrTreePath
0732: * the element or tree path
0733: * @param level
0734: * non-negative level, or <code>ALL_LEVELS</code> to collapse
0735: * all levels of the tree
0736: */
0737: public void collapseToLevel(Object elementOrTreePath, int level) {
0738: Assert.isNotNull(elementOrTreePath);
0739: Widget w = internalGetWidgetToSelect(elementOrTreePath);
0740: if (w != null) {
0741: internalCollapseToLevel(w, level);
0742: }
0743: }
0744:
0745: /**
0746: * Creates all children for the given widget.
0747: * <p>
0748: * The default implementation of this framework method assumes that
0749: * <code>widget.getData()</code> returns the element corresponding to the
0750: * node. Note: the node is not visually expanded! You may have to call
0751: * <code>parent.setExpanded(true)</code>.
0752: * </p>
0753: *
0754: * @param widget
0755: * the widget
0756: */
0757: protected void createChildren(final Widget widget) {
0758: boolean oldBusy = busy;
0759: busy = true;
0760: try {
0761: final Item[] tis = getChildren(widget);
0762: if (tis != null && tis.length > 0) {
0763: Object data = tis[0].getData();
0764: if (data != null) {
0765: return; // children already there!
0766: }
0767: }
0768:
0769: BusyIndicator.showWhile(widget.getDisplay(),
0770: new Runnable() {
0771: public void run() {
0772: // fix for PR 1FW89L7:
0773: // don't complain and remove all "dummies" ...
0774: if (tis != null) {
0775: for (int i = 0; i < tis.length; i++) {
0776: if (tis[i].getData() != null) {
0777: disassociate(tis[i]);
0778: Assert
0779: .isTrue(
0780: tis[i]
0781: .getData() == null,
0782: "Second or later child is non -null");//$NON-NLS-1$
0783:
0784: }
0785: tis[i].dispose();
0786: }
0787: }
0788: Object d = widget.getData();
0789: if (d != null) {
0790: Object parentElement = d;
0791: Object[] children;
0792: if (isTreePathContentProvider()
0793: && widget instanceof Item) {
0794: TreePath path = getTreePathFromItem((Item) widget);
0795: children = getSortedChildren(path);
0796: } else {
0797: children = getSortedChildren(parentElement);
0798: }
0799: for (int i = 0; i < children.length; i++) {
0800: createTreeItem(widget, children[i],
0801: -1);
0802: }
0803: }
0804: }
0805:
0806: });
0807: } finally {
0808: busy = oldBusy;
0809: }
0810: }
0811:
0812: /**
0813: * Creates a single item for the given parent and synchronizes it with the
0814: * given element.
0815: *
0816: * @param parent
0817: * the parent widget
0818: * @param element
0819: * the element
0820: * @param index
0821: * if non-negative, indicates the position to insert the item
0822: * into its parent
0823: */
0824: protected void createTreeItem(Widget parent, Object element,
0825: int index) {
0826: Item item = newItem(parent, SWT.NULL, index);
0827: updateItem(item, element);
0828: updatePlus(item, element);
0829: }
0830:
0831: /**
0832: * The <code>AbstractTreeViewer</code> implementation of this method also
0833: * recurses over children of the corresponding element.
0834: */
0835: protected void disassociate(Item item) {
0836: super .disassociate(item);
0837: // recursively unmapping the items is only required when
0838: // the hash map is used. In the other case disposing
0839: // an item will recursively dispose its children.
0840: if (usingElementMap()) {
0841: disassociateChildren(item);
0842: }
0843: }
0844:
0845: /**
0846: * Disassociates the children of the given SWT item from their corresponding
0847: * elements.
0848: *
0849: * @param item
0850: * the widget
0851: */
0852: private void disassociateChildren(Item item) {
0853: Item[] items = getChildren(item);
0854: for (int i = 0; i < items.length; i++) {
0855: if (items[i].getData() != null) {
0856: disassociate(items[i]);
0857: }
0858: }
0859: }
0860:
0861: /* (non-Javadoc) Method declared on StructuredViewer. */
0862: protected Widget doFindInputItem(Object element) {
0863: // compare with root
0864: Object root = getRoot();
0865: if (root == null) {
0866: return null;
0867: }
0868:
0869: if (equals(root, element)) {
0870: return getControl();
0871: }
0872: return null;
0873: }
0874:
0875: /* (non-Javadoc) Method declared on StructuredViewer. */
0876: protected Widget doFindItem(Object element) {
0877: // compare with root
0878: Object root = getRoot();
0879: if (root == null) {
0880: return null;
0881: }
0882:
0883: Item[] items = getChildren(getControl());
0884: if (items != null) {
0885: for (int i = 0; i < items.length; i++) {
0886: Widget o = internalFindItem(items[i], element);
0887: if (o != null) {
0888: return o;
0889: }
0890: }
0891: }
0892: return null;
0893: }
0894:
0895: /**
0896: * Copies the attributes of the given element into the given SWT item.
0897: *
0898: * @param item
0899: * the SWT item
0900: * @param element
0901: * the element
0902: */
0903: protected void doUpdateItem(final Item item, Object element) {
0904: if (item.isDisposed()) {
0905: unmapElement(element, item);
0906: return;
0907: }
0908:
0909: int columnCount = doGetColumnCount();
0910: if (columnCount == 0)// If no columns are created then fake one
0911: columnCount = 1;
0912:
0913: ViewerRow viewerRowFromItem = getViewerRowFromItem(item);
0914:
0915: boolean isVirtual = (getControl().getStyle() & SWT.VIRTUAL) != 0;
0916:
0917: // If the control is virtual, we cannot use the cached viewer row object. See bug 188663.
0918: if (isVirtual) {
0919: viewerRowFromItem = (ViewerRow) viewerRowFromItem.clone();
0920: }
0921:
0922: for (int column = 0; column < columnCount; column++) {
0923: ViewerColumn columnViewer = getViewerColumn(column);
0924: ViewerCell cellToUpdate = updateCell(viewerRowFromItem,
0925: column, element);
0926:
0927: // If the control is virtual, we cannot use the cached cell object. See bug 188663.
0928: if (isVirtual) {
0929: cellToUpdate = new ViewerCell(cellToUpdate
0930: .getViewerRow(), cellToUpdate.getColumnIndex(),
0931: element);
0932: }
0933:
0934: columnViewer.refresh(cellToUpdate);
0935:
0936: // As it is possible for user code to run the event
0937: // loop check here.
0938: if (item.isDisposed()) {
0939: unmapElement(element, item);
0940: return;
0941: }
0942:
0943: }
0944: }
0945:
0946: /**
0947: * Returns <code>true</code> if the given list and array of items refer to
0948: * the same model elements. Order is unimportant.
0949: * <p>
0950: * This method is not intended to be overridden by subclasses.
0951: * </p>
0952: *
0953: * @param items
0954: * the list of items
0955: * @param current
0956: * the array of items
0957: * @return <code>true</code> if the refer to the same elements,
0958: * <code>false</code> otherwise
0959: *
0960: * @since 3.1 in TreeViewer, moved to AbstractTreeViewer in 3.3
0961: */
0962: protected boolean isSameSelection(List items, Item[] current) {
0963: // If they are not the same size then they are not equivalent
0964: int n = items.size();
0965: if (n != current.length) {
0966: return false;
0967: }
0968:
0969: CustomHashtable itemSet = newHashtable(n * 2 + 1);
0970: for (Iterator i = items.iterator(); i.hasNext();) {
0971: Item item = (Item) i.next();
0972: Object element = item.getData();
0973: itemSet.put(element, element);
0974: }
0975:
0976: // Go through the items of the current collection
0977: // If there is a mismatch return false
0978: for (int i = 0; i < current.length; i++) {
0979: if (current[i].getData() == null
0980: || !itemSet.containsKey(current[i].getData())) {
0981: return false;
0982: }
0983: }
0984:
0985: return true;
0986: }
0987:
0988: /* (non-Javadoc) Method declared on StructuredViewer. */
0989: protected void doUpdateItem(Widget widget, Object element,
0990: boolean fullMap) {
0991: boolean oldBusy = busy;
0992: busy = true;
0993: try {
0994: if (widget instanceof Item) {
0995: Item item = (Item) widget;
0996:
0997: // ensure that back pointer is correct
0998: if (fullMap) {
0999: associate(element, item);
1000: } else {
1001: Object data = item.getData();
1002: if (data != null) {
1003: unmapElement(data, item);
1004: }
1005: item.setData(element);
1006: mapElement(element, item);
1007: }
1008:
1009: // update icon and label
1010: SafeRunnable.run(new UpdateItemSafeRunnable(item,
1011: element));
1012: }
1013: } finally {
1014: busy = oldBusy;
1015: }
1016: }
1017:
1018: /**
1019: * Expands all nodes of the viewer's tree, starting with the root. This
1020: * method is equivalent to <code>expandToLevel(ALL_LEVELS)</code>.
1021: */
1022: public void expandAll() {
1023: expandToLevel(ALL_LEVELS);
1024: }
1025:
1026: /**
1027: * Expands the root of the viewer's tree to the given level.
1028: *
1029: * @param level
1030: * non-negative level, or <code>ALL_LEVELS</code> to expand all
1031: * levels of the tree
1032: */
1033: public void expandToLevel(int level) {
1034: expandToLevel(getRoot(), level);
1035: }
1036:
1037: /**
1038: * Expands all ancestors of the given element or tree path so that the given
1039: * element becomes visible in this viewer's tree control, and then expands
1040: * the subtree rooted at the given element to the given level.
1041: *
1042: * @param elementOrTreePath
1043: * the element
1044: * @param level
1045: * non-negative level, or <code>ALL_LEVELS</code> to expand all
1046: * levels of the tree
1047: */
1048: public void expandToLevel(Object elementOrTreePath, int level) {
1049: if (isBusy())
1050: return;
1051: Widget w = internalExpand(elementOrTreePath, true);
1052: if (w != null) {
1053: internalExpandToLevel(w, level);
1054: }
1055: }
1056:
1057: /**
1058: * Fires a tree collapsed event. Only listeners registered at the time this
1059: * method is called are notified.
1060: *
1061: * @param event
1062: * the tree expansion event
1063: * @see ITreeViewerListener#treeCollapsed
1064: */
1065: protected void fireTreeCollapsed(final TreeExpansionEvent event) {
1066: Object[] listeners = treeListeners.getListeners();
1067: boolean oldBusy = busy;
1068: busy = true;
1069: try {
1070: for (int i = 0; i < listeners.length; ++i) {
1071: final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
1072: SafeRunnable.run(new SafeRunnable() {
1073: public void run() {
1074: l.treeCollapsed(event);
1075: }
1076: });
1077: }
1078: } finally {
1079: busy = oldBusy;
1080: }
1081: }
1082:
1083: /**
1084: * Fires a tree expanded event. Only listeners registered at the time this
1085: * method is called are notified.
1086: *
1087: * @param event
1088: * the tree expansion event
1089: * @see ITreeViewerListener#treeExpanded
1090: */
1091: protected void fireTreeExpanded(final TreeExpansionEvent event) {
1092: Object[] listeners = treeListeners.getListeners();
1093: boolean oldBusy = busy;
1094: busy = true;
1095: try {
1096: for (int i = 0; i < listeners.length; ++i) {
1097: final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
1098: SafeRunnable.run(new SafeRunnable() {
1099: public void run() {
1100: l.treeExpanded(event);
1101: }
1102: });
1103: }
1104: } finally {
1105: busy = oldBusy;
1106: }
1107: }
1108:
1109: /**
1110: * Returns the auto-expand level.
1111: *
1112: * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of
1113: * the tree are expanded automatically
1114: * @see #setAutoExpandLevel
1115: */
1116: public int getAutoExpandLevel() {
1117: return expandToLevel;
1118: }
1119:
1120: /**
1121: * Returns the SWT child items for the given SWT widget.
1122: *
1123: * @param widget
1124: * the widget
1125: * @return the child items
1126: */
1127: protected abstract Item[] getChildren(Widget widget);
1128:
1129: /**
1130: * Get the child for the widget at index. Note that the default
1131: * implementation is not very efficient and should be overridden if this
1132: * class is implemented.
1133: *
1134: * @param widget
1135: * the widget to check
1136: * @param index
1137: * the index of the widget
1138: * @return Item or <code>null</code> if widget is not a type that can
1139: * contain items.
1140: *
1141: * @throws ArrayIndexOutOfBoundsException
1142: * if the index is not valid.
1143: * @since 3.1
1144: */
1145: protected Item getChild(Widget widget, int index) {
1146: return getChildren(widget)[index];
1147: }
1148:
1149: /**
1150: * Returns whether the given SWT item is expanded or collapsed.
1151: *
1152: * @param item
1153: * the item
1154: * @return <code>true</code> if the item is considered expanded and
1155: * <code>false</code> if collapsed
1156: */
1157: protected abstract boolean getExpanded(Item item);
1158:
1159: /**
1160: * Returns a list of elements corresponding to expanded nodes in this
1161: * viewer's tree, including currently hidden ones that are marked as
1162: * expanded but are under a collapsed ancestor.
1163: * <p>
1164: * This method is typically used when preserving the interesting state of a
1165: * viewer; <code>setExpandedElements</code> is used during the restore.
1166: * </p>
1167: *
1168: * @return the array of expanded elements
1169: * @see #setExpandedElements
1170: */
1171: public Object[] getExpandedElements() {
1172: ArrayList items = new ArrayList();
1173: internalCollectExpandedItems(items, getControl());
1174: ArrayList result = new ArrayList(items.size());
1175: for (Iterator it = items.iterator(); it.hasNext();) {
1176: Item item = (Item) it.next();
1177: Object data = item.getData();
1178: if (data != null) {
1179: result.add(data);
1180: }
1181: }
1182: return result.toArray();
1183: }
1184:
1185: /**
1186: * Returns whether the node corresponding to the given element or tree path
1187: * is expanded or collapsed.
1188: *
1189: * @param elementOrTreePath
1190: * the element
1191: * @return <code>true</code> if the node is expanded, and
1192: * <code>false</code> if collapsed
1193: */
1194: public boolean getExpandedState(Object elementOrTreePath) {
1195: Assert.isNotNull(elementOrTreePath);
1196: Widget item = internalGetWidgetToSelect(elementOrTreePath);
1197: if (item instanceof Item) {
1198: return getExpanded((Item) item);
1199: }
1200: return false;
1201: }
1202:
1203: /**
1204: * Returns the number of child items of the given SWT control.
1205: *
1206: * @param control
1207: * the control
1208: * @return the number of children
1209: */
1210: protected abstract int getItemCount(Control control);
1211:
1212: /**
1213: * Returns the number of child items of the given SWT item.
1214: *
1215: * @param item
1216: * the item
1217: * @return the number of children
1218: */
1219: protected abstract int getItemCount(Item item);
1220:
1221: /**
1222: * Returns the child items of the given SWT item.
1223: *
1224: * @param item
1225: * the item
1226: * @return the child items
1227: */
1228: protected abstract Item[] getItems(Item item);
1229:
1230: /**
1231: * Returns the item after the given item in the tree, or <code>null</code>
1232: * if there is no next item.
1233: *
1234: * @param item
1235: * the item
1236: * @param includeChildren
1237: * <code>true</code> if the children are considered in
1238: * determining which item is next, and <code>false</code> if
1239: * subtrees are ignored
1240: * @return the next item, or <code>null</code> if none
1241: */
1242: protected Item getNextItem(Item item, boolean includeChildren) {
1243: if (item == null) {
1244: return null;
1245: }
1246: if (includeChildren && getExpanded(item)) {
1247: Item[] children = getItems(item);
1248: if (children != null && children.length > 0) {
1249: return children[0];
1250: }
1251: }
1252:
1253: // next item is either next sibling or next sibling of first
1254: // parent that has a next sibling.
1255: Item parent = getParentItem(item);
1256: if (parent == null) {
1257: return null;
1258: }
1259: Item[] siblings = getItems(parent);
1260: if (siblings != null) {
1261: if (siblings.length <= 1) {
1262: return getNextItem(parent, false);
1263: }
1264:
1265: for (int i = 0; i < siblings.length; i++) {
1266: if (siblings[i] == item && i < (siblings.length - 1)) {
1267: return siblings[i + 1];
1268: }
1269: }
1270: }
1271: return getNextItem(parent, false);
1272: }
1273:
1274: /**
1275: * Returns the parent item of the given item in the tree, or
1276: * <code>null</code> if there is no parent item.
1277: *
1278: * @param item
1279: * the item
1280: * @return the parent item, or <code>null</code> if none
1281: */
1282: protected abstract Item getParentItem(Item item);
1283:
1284: /**
1285: * Returns the item before the given item in the tree, or <code>null</code>
1286: * if there is no previous item.
1287: *
1288: * @param item
1289: * the item
1290: * @return the previous item, or <code>null</code> if none
1291: */
1292: protected Item getPreviousItem(Item item) {
1293: // previous item is either right-most visible descendent of previous
1294: // sibling or parent
1295: Item parent = getParentItem(item);
1296: if (parent == null) {
1297: return null;
1298: }
1299: Item[] siblings = getItems(parent);
1300: if (siblings.length == 0 || siblings[0] == item) {
1301: return parent;
1302: }
1303: Item previous = siblings[0];
1304: for (int i = 1; i < siblings.length; i++) {
1305: if (siblings[i] == item) {
1306: return rightMostVisibleDescendent(previous);
1307: }
1308: previous = siblings[i];
1309: }
1310: return null;
1311: }
1312:
1313: /* (non-Javadoc) Method declared on StructuredViewer. */
1314: protected Object[] getRawChildren(Object parentElementOrTreePath) {
1315: boolean oldBusy = busy;
1316: busy = true;
1317: try {
1318: Object parent;
1319: TreePath path;
1320: if (parentElementOrTreePath instanceof TreePath) {
1321: path = (TreePath) parentElementOrTreePath;
1322: parent = path.getLastSegment();
1323: } else {
1324: parent = parentElementOrTreePath;
1325: path = null;
1326: }
1327: if (parent != null) {
1328: if (equals(parent, getRoot())) {
1329: return super .getRawChildren(parent);
1330: }
1331: IContentProvider cp = getContentProvider();
1332: if (cp instanceof ITreePathContentProvider) {
1333: ITreePathContentProvider tpcp = (ITreePathContentProvider) cp;
1334: if (path == null) {
1335: // A path was not provided so try and find one
1336: Widget w = findItem(parent);
1337: if (w instanceof Item) {
1338: Item item = (Item) w;
1339: path = getTreePathFromItem(item);
1340: }
1341: if (path == null) {
1342: path = new TreePath(new Object[] { parent });
1343: }
1344: }
1345: Object[] result = tpcp.getChildren(path);
1346: if (result != null) {
1347: return result;
1348: }
1349: } else if (cp instanceof ITreeContentProvider) {
1350: ITreeContentProvider tcp = (ITreeContentProvider) cp;
1351: Object[] result = tcp.getChildren(parent);
1352: if (result != null) {
1353: return result;
1354: }
1355: }
1356: }
1357: return new Object[0];
1358: } finally {
1359: busy = oldBusy;
1360: }
1361: }
1362:
1363: /**
1364: * Returns all selected items for the given SWT control.
1365: *
1366: * @param control
1367: * the control
1368: * @return the list of selected items
1369: */
1370: protected abstract Item[] getSelection(Control control);
1371:
1372: /*
1373: * (non-Javadoc)
1374: *
1375: * @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget()
1376: */
1377: protected List getSelectionFromWidget() {
1378: Widget[] items = getSelection(getControl());
1379: ArrayList list = new ArrayList(items.length);
1380: for (int i = 0; i < items.length; i++) {
1381: Widget item = items[i];
1382: Object e = item.getData();
1383: if (e != null) {
1384: list.add(e);
1385: }
1386: }
1387: return list;
1388: }
1389:
1390: /*
1391: * Overridden in AbstractTreeViewer to fix bug 108102 (code copied from
1392: * StructuredViewer to avoid introducing new API) (non-Javadoc)
1393: *
1394: * @see org.eclipse.jface.viewers.StructuredViewer#handleDoubleSelect(org.eclipse.swt.events.SelectionEvent)
1395: */
1396: protected void handleDoubleSelect(SelectionEvent event) {
1397: // handle case where an earlier selection listener disposed the control.
1398: Control control = getControl();
1399: if (control != null && !control.isDisposed()) {
1400: // If the double-clicked element can be obtained from the event, use
1401: // it
1402: // otherwise get it from the control. Some controls like List do
1403: // not have the notion of item.
1404: // For details, see bug 90161 [Navigator] DefaultSelecting folders
1405: // shouldn't always expand first one
1406: ISelection selection;
1407: if (event.item != null && event.item.getData() != null) {
1408:
1409: // changes to fix bug 108102 follow
1410: TreePath treePath = getTreePathFromItem((Item) event.item);
1411: selection = new TreeSelection(treePath);
1412: // end of changes
1413:
1414: } else {
1415: selection = getSelection();
1416: updateSelection(selection);
1417: }
1418: fireDoubleClick(new DoubleClickEvent(this , selection));
1419: }
1420: }
1421:
1422: /**
1423: * Handles a tree collapse event from the SWT widget.
1424: *
1425: * @param event
1426: * the SWT tree event
1427: */
1428: protected void handleTreeCollapse(TreeEvent event) {
1429: if (event.item.getData() != null) {
1430: fireTreeCollapsed(new TreeExpansionEvent(this , event.item
1431: .getData()));
1432: }
1433: }
1434:
1435: /**
1436: * Handles a tree expand event from the SWT widget.
1437: *
1438: * @param event
1439: * the SWT tree event
1440: */
1441: protected void handleTreeExpand(TreeEvent event) {
1442: createChildren(event.item);
1443: if (event.item.getData() != null) {
1444: fireTreeExpanded(new TreeExpansionEvent(this , event.item
1445: .getData()));
1446: }
1447: }
1448:
1449: /* (non-Javadoc) Method declared on Viewer. */
1450: protected void hookControl(Control control) {
1451: super .hookControl(control);
1452: addTreeListener(control, new TreeListener() {
1453: public void treeExpanded(TreeEvent event) {
1454: handleTreeExpand(event);
1455: }
1456:
1457: public void treeCollapsed(TreeEvent event) {
1458: handleTreeCollapse(event);
1459: }
1460: });
1461: }
1462:
1463: /*
1464: * (non-Javadoc) Method declared on StructuredViewer. Builds the initial
1465: * tree and handles the automatic expand feature.
1466: */
1467: protected void inputChanged(Object input, Object oldInput) {
1468: preservingSelection(new Runnable() {
1469: public void run() {
1470: Control tree = getControl();
1471: boolean useRedraw = true;
1472: // (size > REDRAW_THRESHOLD) || (table.getItemCount() >
1473: // REDRAW_THRESHOLD);
1474: if (useRedraw) {
1475: tree.setRedraw(false);
1476: }
1477: removeAll(tree);
1478: tree.setData(getRoot());
1479: internalInitializeTree(tree);
1480: if (useRedraw) {
1481: tree.setRedraw(true);
1482: }
1483: }
1484:
1485: });
1486: }
1487:
1488: /**
1489: * Initializes the tree with root items, expanding to the appropriate
1490: * level if necessary.
1491: *
1492: * @param tree the tree control
1493: * @since 3.3
1494: */
1495: protected void internalInitializeTree(Control tree) {
1496: createChildren(tree);
1497: internalExpandToLevel(tree, expandToLevel);
1498: }
1499:
1500: /**
1501: * Recursively collapses the subtree rooted at the given widget to the given
1502: * level.
1503: * <p>
1504: * </p>
1505: * Note that the default implementation of this method does not call
1506: * <code>setRedraw</code>.
1507: *
1508: * @param widget
1509: * the widget
1510: * @param level
1511: * non-negative level, or <code>ALL_LEVELS</code> to collapse
1512: * all levels of the tree
1513: */
1514: protected void internalCollapseToLevel(Widget widget, int level) {
1515: if (level == ALL_LEVELS || level > 0) {
1516:
1517: if (widget instanceof Item) {
1518: setExpanded((Item) widget, false);
1519: }
1520:
1521: if (level == ALL_LEVELS || level > 1) {
1522: Item[] children = getChildren(widget);
1523: if (children != null) {
1524: int nextLevel = (level == ALL_LEVELS ? ALL_LEVELS
1525: : level - 1);
1526: for (int i = 0; i < children.length; i++) {
1527: internalCollapseToLevel(children[i], nextLevel);
1528: }
1529: }
1530: }
1531: }
1532: }
1533:
1534: /**
1535: * Recursively collects all expanded items from the given widget.
1536: *
1537: * @param result
1538: * a list (element type: <code>Item</code>) into which to
1539: * collect the elements
1540: * @param widget
1541: * the widget
1542: */
1543: private void internalCollectExpandedItems(List result, Widget widget) {
1544: Item[] items = getChildren(widget);
1545: for (int i = 0; i < items.length; i++) {
1546: Item item = items[i];
1547: if (getExpanded(item)) {
1548: result.add(item);
1549: }
1550: internalCollectExpandedItems(result, item);
1551: }
1552: }
1553:
1554: /**
1555: * Tries to create a path of tree items for the given element or tree path.
1556: * This method recursively walks up towards the root of the tree and in the
1557: * case of an element (rather than a tree path) assumes that
1558: * <code>getParent</code> returns the correct parent of an element.
1559: *
1560: * @param elementOrPath
1561: * the element
1562: * @param expand
1563: * <code>true</code> if all nodes on the path should be
1564: * expanded, and <code>false</code> otherwise
1565: * @return Widget
1566: */
1567: protected Widget internalExpand(Object elementOrPath, boolean expand) {
1568:
1569: if (elementOrPath == null) {
1570: return null;
1571: }
1572:
1573: Widget w = internalGetWidgetToSelect(elementOrPath);
1574: if (w == null) {
1575: if (equals(elementOrPath, getRoot())) { // stop at root
1576: return null;
1577: }
1578: // my parent has to create me
1579: Object parent = getParentElement(elementOrPath);
1580: if (parent != null) {
1581: Widget pw = internalExpand(parent, false);
1582: if (pw != null) {
1583: // let my parent create me
1584: createChildren(pw);
1585: Object element = internalToElement(elementOrPath);
1586: w = internalFindChild(pw, element);
1587: if (expand && pw instanceof Item) {
1588: // expand parent items top-down
1589: Item item = (Item) pw;
1590: LinkedList toExpandList = new LinkedList();
1591: while (item != null && !getExpanded(item)) {
1592: toExpandList.addFirst(item);
1593: item = getParentItem(item);
1594: }
1595: for (Iterator it = toExpandList.iterator(); it
1596: .hasNext();) {
1597: Item toExpand = (Item) it.next();
1598: setExpanded(toExpand, true);
1599: }
1600: }
1601: }
1602: }
1603: }
1604: return w;
1605: }
1606:
1607: /**
1608: * If the argument is a tree path, returns its last segment, otherwise
1609: * return the argument
1610: *
1611: * @param elementOrPath
1612: * an element or a tree path
1613: * @return the element, or the last segment of the tree path
1614: */
1615: private Object internalToElement(Object elementOrPath) {
1616: if (elementOrPath instanceof TreePath) {
1617: return ((TreePath) elementOrPath).getLastSegment();
1618: }
1619: return elementOrPath;
1620: }
1621:
1622: /**
1623: * This method takes a tree path or an element. If the argument is not a
1624: * tree path, returns the parent of the given element or <code>null</code>
1625: * if the parent is not known. If the argument is a tree path with more than
1626: * one segment, returns its parent tree path, otherwise returns
1627: * <code>null</code>.
1628: *
1629: * @param elementOrTreePath
1630: * @return the parent element, or parent path, or <code>null</code>
1631: *
1632: * @since 3.2
1633: */
1634: protected Object getParentElement(Object elementOrTreePath) {
1635: if (elementOrTreePath instanceof TreePath) {
1636: TreePath treePath = (TreePath) elementOrTreePath;
1637: return (treePath).getParentPath();
1638: }
1639: IContentProvider cp = getContentProvider();
1640: if (cp instanceof ITreePathContentProvider) {
1641: ITreePathContentProvider tpcp = (ITreePathContentProvider) cp;
1642: TreePath[] paths = tpcp.getParents(elementOrTreePath);
1643: if (paths.length > 0) {
1644: if (paths[0].getSegmentCount() == 0) {
1645: return getInput();
1646: }
1647: return paths[0].getLastSegment();
1648: }
1649: }
1650: if (cp instanceof ITreeContentProvider) {
1651: ITreeContentProvider tcp = (ITreeContentProvider) cp;
1652: return tcp.getParent(elementOrTreePath);
1653: }
1654: return null;
1655: }
1656:
1657: /**
1658: * Returns the widget to be selected for the given element or tree path.
1659: *
1660: * @param elementOrTreePath
1661: * the element or tree path to select
1662: * @return the widget to be selected, or <code>null</code> if not found
1663: *
1664: * @since 3.1
1665: */
1666: protected Widget internalGetWidgetToSelect(Object elementOrTreePath) {
1667: if (elementOrTreePath instanceof TreePath) {
1668: TreePath treePath = (TreePath) elementOrTreePath;
1669: if (treePath.getSegmentCount() == 0) {
1670: return getControl();
1671: }
1672: Widget[] candidates = findItems(treePath.getLastSegment());
1673: for (int i = 0; i < candidates.length; i++) {
1674: Widget candidate = candidates[i];
1675: if (!(candidate instanceof Item)) {
1676: continue;
1677: }
1678: if (treePath.equals(
1679: getTreePathFromItem((Item) candidate),
1680: getComparer())) {
1681: return candidate;
1682: }
1683: }
1684: return null;
1685: }
1686: return findItem(elementOrTreePath);
1687: }
1688:
1689: /**
1690: * Recursively expands the subtree rooted at the given widget to the given
1691: * level.
1692: * <p>
1693: * </p>
1694: * Note that the default implementation of this method does not call
1695: * <code>setRedraw</code>.
1696: *
1697: * @param widget
1698: * the widget
1699: * @param level
1700: * non-negative level, or <code>ALL_LEVELS</code> to collapse
1701: * all levels of the tree
1702: */
1703: protected void internalExpandToLevel(Widget widget, int level) {
1704: if (level == ALL_LEVELS || level > 0) {
1705: if (widget instanceof Item
1706: && widget.getData() != null
1707: && !isExpandable((Item) widget, null, widget
1708: .getData())) {
1709: return;
1710: }
1711: createChildren(widget);
1712: if (widget instanceof Item) {
1713: setExpanded((Item) widget, true);
1714: }
1715: if (level == ALL_LEVELS || level > 1) {
1716: Item[] children = getChildren(widget);
1717: if (children != null) {
1718: int newLevel = (level == ALL_LEVELS ? ALL_LEVELS
1719: : level - 1);
1720: for (int i = 0; i < children.length; i++) {
1721: internalExpandToLevel(children[i], newLevel);
1722: }
1723: }
1724: }
1725: }
1726: }
1727:
1728: /**
1729: * Non-recursively tries to find the given element as a child of the given
1730: * parent (item or tree).
1731: *
1732: * @param parent
1733: * the parent item
1734: * @param element
1735: * the element
1736: * @return Widget
1737: */
1738: private Widget internalFindChild(Widget parent, Object element) {
1739: Item[] items = getChildren(parent);
1740: for (int i = 0; i < items.length; i++) {
1741: Item item = items[i];
1742: Object data = item.getData();
1743: if (data != null && equals(data, element)) {
1744: return item;
1745: }
1746: }
1747: return null;
1748: }
1749:
1750: /**
1751: * Recursively tries to find the given element.
1752: *
1753: * @param parent
1754: * the parent item
1755: * @param element
1756: * the element
1757: * @return Widget
1758: */
1759: private Widget internalFindItem(Item parent, Object element) {
1760:
1761: // compare with node
1762: Object data = parent.getData();
1763: if (data != null) {
1764: if (equals(data, element)) {
1765: return parent;
1766: }
1767: }
1768: // recurse over children
1769: Item[] items = getChildren(parent);
1770: for (int i = 0; i < items.length; i++) {
1771: Item item = items[i];
1772: Widget o = internalFindItem(item, element);
1773: if (o != null) {
1774: return o;
1775: }
1776: }
1777: return null;
1778: }
1779:
1780: /* (non-Javadoc) Method declared on StructuredViewer. */
1781: protected void internalRefresh(Object element) {
1782: internalRefresh(element, true);
1783: }
1784:
1785: /* (non-Javadoc) Method declared on StructuredViewer. */
1786: protected void internalRefresh(Object element, boolean updateLabels) {
1787: // If element is null, do a full refresh.
1788: if (element == null) {
1789: internalRefresh(getControl(), getRoot(), true, updateLabels);
1790: return;
1791: }
1792: Widget[] items = findItems(element);
1793: if (items.length != 0) {
1794: for (int i = 0; i < items.length; i++) {
1795: // pick up structure changes too
1796: internalRefresh(items[i], element, true, updateLabels);
1797: }
1798: }
1799: }
1800:
1801: /**
1802: * Refreshes the tree starting at the given widget.
1803: * <p>
1804: * EXPERIMENTAL. Not to be used except by JDT. This method was added to
1805: * support JDT's explorations into grouping by working sets, which requires
1806: * viewers to support multiple equal elements. See bug 76482 for more
1807: * details. This support will likely be removed in Eclipse 3.2 in favor of
1808: * proper support for multiple equal elements.
1809: * </p>
1810: *
1811: * @param widget
1812: * the widget
1813: * @param element
1814: * the element
1815: * @param doStruct
1816: * <code>true</code> if structural changes are to be picked up,
1817: * and <code>false</code> if only label provider changes are of
1818: * interest
1819: * @param updateLabels
1820: * <code>true</code> to update labels for existing elements,
1821: * <code>false</code> to only update labels as needed, assuming
1822: * that labels for existing elements are unchanged.
1823: * @since 3.1
1824: */
1825: protected void internalRefresh(Widget widget, Object element,
1826: boolean doStruct, boolean updateLabels) {
1827:
1828: if (widget instanceof Item) {
1829: if (doStruct) {
1830: updatePlus((Item) widget, element);
1831: }
1832: if (updateLabels || !equals(element, widget.getData())) {
1833: doUpdateItem(widget, element, true);
1834: } else {
1835: associate(element, (Item) widget);
1836: }
1837: }
1838:
1839: if (doStruct) {
1840: internalRefreshStruct(widget, element, updateLabels);
1841: } else {
1842: Item[] children = getChildren(widget);
1843: if (children != null) {
1844: for (int i = 0; i < children.length; i++) {
1845: Widget item = children[i];
1846: Object data = item.getData();
1847: if (data != null) {
1848: internalRefresh(item, data, doStruct,
1849: updateLabels);
1850: }
1851: }
1852: }
1853: }
1854: }
1855:
1856: /**
1857: * Update the structure and recurse. Items are updated in updateChildren, as
1858: * needed.
1859: *
1860: * @param widget
1861: * @param element
1862: * @param updateLabels
1863: */
1864: /* package */void internalRefreshStruct(Widget widget,
1865: Object element, boolean updateLabels) {
1866: updateChildren(widget, element, null, updateLabels);
1867: Item[] children = getChildren(widget);
1868: if (children != null) {
1869: for (int i = 0; i < children.length; i++) {
1870: Widget item = children[i];
1871: Object data = item.getData();
1872: if (data != null) {
1873: internalRefreshStruct(item, data, updateLabels);
1874: }
1875: }
1876: }
1877: }
1878:
1879: /**
1880: * Removes the given elements from this viewer.
1881: * <p>
1882: * EXPERIMENTAL. Not to be used except by JDT. This method was added to
1883: * support JDT's explorations into grouping by working sets, which requires
1884: * viewers to support multiple equal elements. See bug 76482 for more
1885: * details. This support will likely be removed in Eclipse 3.2 in favor of
1886: * proper support for multiple equal elements.
1887: * </p>
1888: *
1889: * @param elementsOrPaths
1890: * the elements or element paths to remove
1891: * @since 3.1
1892: */
1893: protected void internalRemove(Object[] elementsOrPaths) {
1894: Object input = getInput();
1895: for (int i = 0; i < elementsOrPaths.length; ++i) {
1896: Object element = elementsOrPaths[i];
1897: if (equals(element, input)) {
1898: setInput(null);
1899: return;
1900: }
1901: Widget[] childItems = internalFindItems(element);
1902: for (int j = 0; j < childItems.length; j++) {
1903: Widget childItem = childItems[j];
1904: if (childItem instanceof Item) {
1905: disassociate((Item) childItem);
1906: childItem.dispose();
1907: }
1908: }
1909: }
1910: }
1911:
1912: /**
1913: * Removes the given elements from this viewer, whenever those elements
1914: * appear as children of the given parent.
1915: *
1916: * @param parent the parent element
1917: * @param elements
1918: * the elements to remove
1919: * @since 3.1
1920: */
1921: protected void internalRemove(Object parent, Object[] elements) {
1922:
1923: CustomHashtable toRemove = new CustomHashtable(getComparer());
1924: for (int i = 0; i < elements.length; i++) {
1925: toRemove.put(elements[i], elements[i]);
1926: }
1927:
1928: // Find each place the parent appears in the tree
1929: Widget[] parentItemArray = findItems(parent);
1930: for (int i = 0; i < parentItemArray.length; i++) {
1931: Widget parentItem = parentItemArray[i];
1932:
1933: // Iterate over the child items and remove each one
1934: Item[] children = getChildren(parentItem);
1935:
1936: for (int j = 0; j < children.length; j++) {
1937: Item child = children[j];
1938:
1939: Object data = child.getData();
1940: if (data != null && toRemove.containsKey(data)) {
1941: disassociate(child);
1942: child.dispose();
1943: }
1944: }
1945: }
1946: }
1947:
1948: /**
1949: * Sets the expanded state of all items to correspond to the given set of
1950: * expanded elements.
1951: *
1952: * @param expandedElements
1953: * the set (element type: <code>Object</code>) of elements
1954: * which are expanded
1955: * @param widget
1956: * the widget
1957: */
1958: private void internalSetExpanded(CustomHashtable expandedElements,
1959: Widget widget) {
1960: Item[] items = getChildren(widget);
1961: for (int i = 0; i < items.length; i++) {
1962: Item item = items[i];
1963: Object data = item.getData();
1964: if (data != null) {
1965: // remove the element to avoid an infinite loop
1966: // if the same element appears on a child item
1967: boolean expanded = expandedElements.remove(data) != null;
1968: if (expanded != getExpanded(item)) {
1969: if (expanded) {
1970: createChildren(item);
1971: }
1972: setExpanded(item, expanded);
1973: }
1974: }
1975: if (expandedElements.size() > 0) {
1976: internalSetExpanded(expandedElements, item);
1977: }
1978: }
1979: }
1980:
1981: /**
1982: * Sets the expanded state of all items to correspond to the given set of
1983: * expanded tree paths.
1984: *
1985: * @param expandedTreePaths
1986: * the set (element type: <code>TreePath</code>) of elements
1987: * which are expanded
1988: * @param widget
1989: * the widget
1990: */
1991: private void internalSetExpandedTreePaths(
1992: CustomHashtable expandedTreePaths, Widget widget,
1993: TreePath currentPath) {
1994: Item[] items = getChildren(widget);
1995: for (int i = 0; i < items.length; i++) {
1996: Item item = items[i];
1997: Object data = item.getData();
1998: TreePath childPath = data == null ? null : currentPath
1999: .createChildPath(data);
2000: if (data != null && childPath != null) {
2001: // remove the element to avoid an infinite loop
2002: // if the same element appears on a child item
2003: boolean expanded = expandedTreePaths.remove(childPath) != null;
2004: if (expanded != getExpanded(item)) {
2005: if (expanded) {
2006: createChildren(item);
2007: }
2008: setExpanded(item, expanded);
2009: }
2010: }
2011: internalSetExpandedTreePaths(expandedTreePaths, item,
2012: childPath);
2013: }
2014: }
2015:
2016: /**
2017: * Return whether the tree node representing the given element or path can
2018: * be expanded. Clients should query expandability by path if the viewer's
2019: * content provider is an {@link ITreePathContentProvider}.
2020: * <p>
2021: * The default implementation of this framework method calls
2022: * <code>hasChildren</code> on this viewer's content provider. It may be
2023: * overridden if necessary.
2024: * </p>
2025: *
2026: * @param elementOrTreePath
2027: * the element or path
2028: * @return <code>true</code> if the tree node representing the given
2029: * element can be expanded, or <code>false</code> if not
2030: */
2031: public boolean isExpandable(Object elementOrTreePath) {
2032: Object element;
2033: TreePath path;
2034: if (elementOrTreePath instanceof TreePath) {
2035: path = (TreePath) elementOrTreePath;
2036: element = path.getLastSegment();
2037: } else {
2038: element = elementOrTreePath;
2039: path = null;
2040: }
2041: IContentProvider cp = getContentProvider();
2042: if (cp instanceof ITreePathContentProvider) {
2043: ITreePathContentProvider tpcp = (ITreePathContentProvider) cp;
2044: if (path == null) {
2045: // A path was not provided so try and find one
2046: Widget w = findItem(element);
2047: if (w instanceof Item) {
2048: Item item = (Item) w;
2049: path = getTreePathFromItem(item);
2050: }
2051: if (path == null) {
2052: path = new TreePath(new Object[] { element });
2053: }
2054: }
2055: return tpcp.hasChildren(path);
2056: }
2057: if (cp instanceof ITreeContentProvider) {
2058: ITreeContentProvider tcp = (ITreeContentProvider) cp;
2059: return tcp.hasChildren(element);
2060: }
2061: return false;
2062: }
2063:
2064: /**
2065: * Return whether the given element is expandable.
2066: *
2067: * @param item
2068: * the tree item for the element
2069: * @param parentPath
2070: * the parent path if it is known or <code>null</code> if it
2071: * needs to be determines
2072: * @param element
2073: * the element
2074: * @return whether the given element is expandable
2075: */
2076: private boolean isExpandable(Item item, TreePath parentPath,
2077: Object element) {
2078: Object elementOrTreePath = element;
2079: if (isTreePathContentProvider()) {
2080: if (parentPath != null) {
2081: elementOrTreePath = parentPath.createChildPath(element);
2082: } else {
2083: elementOrTreePath = getTreePathFromItem(item);
2084: }
2085: }
2086: return isExpandable(elementOrTreePath);
2087: }
2088:
2089: /* (non-Javadoc) Method declared on Viewer. */
2090: protected void labelProviderChanged() {
2091: // we have to walk the (visible) tree and update every item
2092: Control tree = getControl();
2093: tree.setRedraw(false);
2094: // don't pick up structure changes, but do force label updates
2095: internalRefresh(tree, getRoot(), false, true);
2096: tree.setRedraw(true);
2097: }
2098:
2099: /**
2100: * Creates a new item.
2101: *
2102: * @param parent
2103: * the parent widget
2104: * @param style
2105: * SWT style bits
2106: * @param index
2107: * if non-negative, indicates the position to insert the item
2108: * into its parent
2109: * @return the newly-created item
2110: */
2111: protected abstract Item newItem(Widget parent, int style, int index);
2112:
2113: /**
2114: * Removes the given elements from this viewer. The selection is updated if
2115: * required.
2116: * <p>
2117: * This method should be called (by the content provider) when elements have
2118: * been removed from the model, in order to cause the viewer to accurately
2119: * reflect the model. This method only affects the viewer, not the model.
2120: * </p>
2121: *
2122: * @param elementsOrTreePaths
2123: * the elements to remove
2124: */
2125: public void remove(final Object[] elementsOrTreePaths) {
2126: assertElementsNotNull(elementsOrTreePaths);
2127: if (elementsOrTreePaths.length == 0) {
2128: return;
2129: }
2130: if (isBusy())
2131: return;
2132: preservingSelection(new Runnable() {
2133: public void run() {
2134: internalRemove(elementsOrTreePaths);
2135: }
2136: });
2137: }
2138:
2139: /**
2140: * Removes the given elements from this viewer whenever they appear as
2141: * children of the given parent element. If the given elements also appear
2142: * as children of some other parent, the other parent will remain unchanged.
2143: * The selection is updated if required.
2144: * <p>
2145: * This method should be called (by the content provider) when elements have
2146: * been removed from the model, in order to cause the viewer to accurately
2147: * reflect the model. This method only affects the viewer, not the model.
2148: * </p>
2149: *
2150: * @param parent
2151: * the parent of the elements to remove
2152: * @param elements
2153: * the elements to remove
2154: *
2155: * @since 3.2
2156: */
2157: public void remove(final Object parent, final Object[] elements) {
2158: assertElementsNotNull(elements);
2159: if (elements.length == 0) {
2160: return;
2161: }
2162: if (isBusy())
2163: return;
2164: preservingSelection(new Runnable() {
2165: public void run() {
2166: internalRemove(parent, elements);
2167: }
2168: });
2169: }
2170:
2171: /**
2172: * Removes the given element from the viewer. The selection is updated if
2173: * necessary.
2174: * <p>
2175: * This method should be called (by the content provider) when a single
2176: * element has been removed from the model, in order to cause the viewer to
2177: * accurately reflect the model. This method only affects the viewer, not
2178: * the model. Note that there is another method for efficiently processing
2179: * the simultaneous removal of multiple elements.
2180: * </p>
2181: *
2182: * @param elementsOrTreePaths
2183: * the element
2184: */
2185: public void remove(Object elementsOrTreePaths) {
2186: remove(new Object[] { elementsOrTreePaths });
2187: }
2188:
2189: /**
2190: * Removes all items from the given control.
2191: *
2192: * @param control
2193: * the control
2194: */
2195: protected abstract void removeAll(Control control);
2196:
2197: /**
2198: * Removes a listener for expand and collapse events in this viewer. Has no
2199: * affect if an identical listener is not registered.
2200: *
2201: * @param listener
2202: * a tree viewer listener
2203: */
2204: public void removeTreeListener(ITreeViewerListener listener) {
2205: treeListeners.remove(listener);
2206: }
2207:
2208: /**
2209: * This implementation of reveal() reveals the given element or tree path.
2210: */
2211: public void reveal(Object elementOrTreePath) {
2212: Assert.isNotNull(elementOrTreePath);
2213: Widget w = internalExpand(elementOrTreePath, true);
2214: if (w instanceof Item) {
2215: showItem((Item) w);
2216: }
2217: }
2218:
2219: /**
2220: * Returns the rightmost visible descendent of the given item. Returns the
2221: * item itself if it has no children.
2222: *
2223: * @param item
2224: * the item to compute the descendent of
2225: * @return the rightmost visible descendent or the item itself if it has no
2226: * children
2227: */
2228: private Item rightMostVisibleDescendent(Item item) {
2229: Item[] children = getItems(item);
2230: if (getExpanded(item) && children != null
2231: && children.length > 0) {
2232: return rightMostVisibleDescendent(children[children.length - 1]);
2233: }
2234: return item;
2235: }
2236:
2237: /* (non-Javadoc) Method declared on Viewer. */
2238: public Item scrollDown(int x, int y) {
2239: Item current = getItem(x, y);
2240: if (current != null) {
2241: Item next = getNextItem(current, true);
2242: showItem(next == null ? current : next);
2243: return next;
2244: }
2245: return null;
2246: }
2247:
2248: /* (non-Javadoc) Method declared on Viewer. */
2249: public Item scrollUp(int x, int y) {
2250: Item current = getItem(x, y);
2251: if (current != null) {
2252: Item previous = getPreviousItem(current);
2253: showItem(previous == null ? current : previous);
2254: return previous;
2255: }
2256: return null;
2257: }
2258:
2259: /**
2260: * Sets the auto-expand level. The value 0 means that there is no
2261: * auto-expand; 1 means that top-level elements are expanded, but not their
2262: * children; 2 means that top-level elements are expanded, and their
2263: * children, but not grandchildren; and so on.
2264: * <p>
2265: * The value <code>ALL_LEVELS</code> means that all subtrees should be
2266: * expanded.
2267: * </p>
2268: *
2269: * @param level
2270: * non-negative level, or <code>ALL_LEVELS</code> to expand all
2271: * levels of the tree
2272: */
2273: public void setAutoExpandLevel(int level) {
2274: expandToLevel = level;
2275: }
2276:
2277: /**
2278: * The <code>AbstractTreeViewer</code> implementation of this method
2279: * checks to ensure that the content provider is an
2280: * <code>ITreeContentProvider</code>.
2281: */
2282: public void setContentProvider(IContentProvider provider) {
2283: // the actual check is in assertContentProviderType
2284: super .setContentProvider(provider);
2285: }
2286:
2287: protected void assertContentProviderType(IContentProvider provider) {
2288: Assert.isTrue(provider instanceof ITreeContentProvider
2289: || provider instanceof ITreePathContentProvider);
2290: }
2291:
2292: /**
2293: * Sets the expand state of the given item.
2294: *
2295: * @param item
2296: * the item
2297: * @param expand
2298: * the expand state of the item
2299: */
2300: protected abstract void setExpanded(Item item, boolean expand);
2301:
2302: /**
2303: * Sets which nodes are expanded in this viewer's tree. The given list
2304: * contains the elements that are to be expanded; all other nodes are to be
2305: * collapsed.
2306: * <p>
2307: * This method is typically used when restoring the interesting state of a
2308: * viewer captured by an earlier call to <code>getExpandedElements</code>.
2309: * </p>
2310: *
2311: * @param elements
2312: * the array of expanded elements
2313: * @see #getExpandedElements
2314: */
2315: public void setExpandedElements(Object[] elements) {
2316: assertElementsNotNull(elements);
2317: if (isBusy()) {
2318: return;
2319: }
2320: CustomHashtable expandedElements = newHashtable(elements.length * 2 + 1);
2321: for (int i = 0; i < elements.length; ++i) {
2322: Object element = elements[i];
2323: // Ensure item exists for element. This will materialize items for
2324: // each element and their parents, if possible. This is important
2325: // to support expanding of inner tree nodes without necessarily
2326: // expanding their parents.
2327: internalExpand(element, false);
2328: expandedElements.put(element, element);
2329: }
2330: // this will traverse all existing items, and create children for
2331: // elements that need to be expanded. If the tree contains multiple
2332: // equal elements, and those are in the set of elements to be expanded,
2333: // only the first item found for each element will be expanded.
2334: internalSetExpanded(expandedElements, getControl());
2335: }
2336:
2337: /**
2338: * Sets which nodes are expanded in this viewer's tree. The given list
2339: * contains the tree paths that are to be expanded; all other nodes are to
2340: * be collapsed.
2341: * <p>
2342: * This method is typically used when restoring the interesting state of a
2343: * viewer captured by an earlier call to <code>getExpandedTreePaths</code>.
2344: * </p>
2345: *
2346: * @param treePaths
2347: * the array of expanded tree paths
2348: * @see #getExpandedTreePaths()
2349: *
2350: * @since 3.2
2351: */
2352: public void setExpandedTreePaths(TreePath[] treePaths) {
2353: assertElementsNotNull(treePaths);
2354: if (isBusy())
2355: return;
2356: final IElementComparer comparer = getComparer();
2357: IElementComparer treePathComparer = new IElementComparer() {
2358:
2359: public boolean equals(Object a, Object b) {
2360: return ((TreePath) a).equals(((TreePath) b), comparer);
2361: }
2362:
2363: public int hashCode(Object element) {
2364: return ((TreePath) element).hashCode(comparer);
2365: }
2366: };
2367: CustomHashtable expandedTreePaths = new CustomHashtable(
2368: treePaths.length * 2 + 1, treePathComparer);
2369: for (int i = 0; i < treePaths.length; ++i) {
2370: TreePath treePath = treePaths[i];
2371: // Ensure item exists for element. This will materialize items for
2372: // each element and their parents, if possible. This is important
2373: // to support expanding of inner tree nodes without necessarily
2374: // expanding their parents.
2375: internalExpand(treePath, false);
2376: expandedTreePaths.put(treePath, treePath);
2377: }
2378: // this will traverse all existing items, and create children for
2379: // elements that need to be expanded. If the tree contains multiple
2380: // equal elements, and those are in the set of elements to be expanded,
2381: // only the first item found for each element will be expanded.
2382: internalSetExpandedTreePaths(expandedTreePaths, getControl(),
2383: new TreePath(new Object[0]));
2384: }
2385:
2386: /**
2387: * Sets whether the node corresponding to the given element or tree path is
2388: * expanded or collapsed.
2389: *
2390: * @param elementOrTreePath
2391: * the element
2392: * @param expanded
2393: * <code>true</code> if the node is expanded, and
2394: * <code>false</code> if collapsed
2395: */
2396: public void setExpandedState(Object elementOrTreePath,
2397: boolean expanded) {
2398: Assert.isNotNull(elementOrTreePath);
2399: if (isBusy())
2400: return;
2401: Widget item = internalExpand(elementOrTreePath, false);
2402: if (item instanceof Item) {
2403: if (expanded) {
2404: createChildren(item);
2405: }
2406: setExpanded((Item) item, expanded);
2407: }
2408: }
2409:
2410: /**
2411: * Sets the selection to the given list of items.
2412: *
2413: * @param items
2414: * list of items (element type:
2415: * <code>org.eclipse.swt.widgets.Item</code>)
2416: */
2417: protected abstract void setSelection(List items);
2418:
2419: /**
2420: * This implementation of setSelectionToWidget accepts a list of elements or
2421: * a list of tree paths.
2422: */
2423: protected void setSelectionToWidget(List v, boolean reveal) {
2424: if (v == null) {
2425: setSelection(new ArrayList(0));
2426: return;
2427: }
2428: int size = v.size();
2429: List newSelection = new ArrayList(size);
2430: for (int i = 0; i < size; ++i) {
2431: Object elementOrTreePath = v.get(i);
2432: // Use internalExpand since item may not yet be created. See
2433: // 1G6B1AR.
2434: Widget w = internalExpand(elementOrTreePath, false);
2435: if (w instanceof Item) {
2436: newSelection.add(w);
2437: } else if (w == null
2438: && elementOrTreePath instanceof TreePath) {
2439: TreePath treePath = (TreePath) elementOrTreePath;
2440: Object element = treePath.getLastSegment();
2441: if (element != null) {
2442: w = internalExpand(element, false);
2443: if (w instanceof Item) {
2444: newSelection.add(w);
2445: }
2446: }
2447: }
2448: }
2449: setSelection(newSelection);
2450:
2451: // Although setting the selection in the control should reveal it,
2452: // setSelection may be a no-op if the selection is unchanged,
2453: // so explicitly reveal the first item in the selection here.
2454: // See bug 100565 for more details.
2455: if (reveal && newSelection.size() > 0) {
2456: showItem((Item) newSelection.get(0));
2457: }
2458: }
2459:
2460: /**
2461: * Shows the given item.
2462: *
2463: * @param item
2464: * the item
2465: */
2466: protected abstract void showItem(Item item);
2467:
2468: /**
2469: * Updates the tree items to correspond to the child elements of the given
2470: * parent element. If null is passed for the children, this method obtains
2471: * them (only if needed).
2472: *
2473: * @param widget
2474: * the widget
2475: * @param parent
2476: * the parent element
2477: * @param elementChildren
2478: * the child elements, or null
2479: * @deprecated this is no longer called by the framework
2480: */
2481: protected void updateChildren(Widget widget, Object parent,
2482: Object[] elementChildren) {
2483: updateChildren(widget, parent, elementChildren, true);
2484: }
2485:
2486: /**
2487: * Updates the tree items to correspond to the child elements of the given
2488: * parent element. If null is passed for the children, this method obtains
2489: * them (only if needed).
2490: *
2491: * @param widget
2492: * the widget
2493: * @param parent
2494: * the parent element
2495: * @param elementChildren
2496: * the child elements, or null
2497: * @param updateLabels
2498: * <code>true</code> to update labels for existing elements,
2499: * <code>false</code> to only update labels as needed, assuming
2500: * that labels for existing elements are unchanged.
2501: * @since 2.1
2502: */
2503: private void updateChildren(Widget widget, Object parent,
2504: Object[] elementChildren, boolean updateLabels) {
2505: // optimization! prune collapsed subtrees
2506: if (widget instanceof Item) {
2507: Item ti = (Item) widget;
2508: if (!getExpanded(ti)) {
2509: // need a dummy node if element is expandable;
2510: // but try to avoid recreating the dummy node
2511: boolean needDummy = isExpandable(ti, null, parent);
2512: boolean haveDummy = false;
2513: // remove all children
2514: Item[] items = getItems(ti);
2515: for (int i = 0; i < items.length; i++) {
2516: if (items[i].getData() != null) {
2517: disassociate(items[i]);
2518: items[i].dispose();
2519: } else {
2520: if (needDummy && !haveDummy) {
2521: haveDummy = true;
2522: } else {
2523: items[i].dispose();
2524: }
2525: }
2526: }
2527: if (needDummy && !haveDummy) {
2528: newItem(ti, SWT.NULL, -1);
2529: }
2530:
2531: return;
2532: }
2533: }
2534:
2535: // If the children weren't passed in, get them now since they're needed
2536: // below.
2537: if (elementChildren == null) {
2538: if (isTreePathContentProvider() && widget instanceof Item) {
2539: TreePath path = getTreePathFromItem((Item) widget);
2540: elementChildren = getSortedChildren(path);
2541: } else {
2542: elementChildren = getSortedChildren(parent);
2543: }
2544: }
2545:
2546: Control tree = getControl();
2547:
2548: // WORKAROUND
2549: int oldCnt = -1;
2550: if (widget == tree) {
2551: oldCnt = getItemCount(tree);
2552: }
2553:
2554: Item[] items = getChildren(widget);
2555:
2556: // save the expanded elements
2557: CustomHashtable expanded = newHashtable(CustomHashtable.DEFAULT_CAPACITY); // assume
2558: // num
2559: // expanded
2560: // is
2561: // small
2562: for (int i = 0; i < items.length; ++i) {
2563: if (getExpanded(items[i])) {
2564: Object element = items[i].getData();
2565: if (element != null) {
2566: expanded.put(element, element);
2567: }
2568: }
2569: }
2570:
2571: int min = Math.min(elementChildren.length, items.length);
2572:
2573: // dispose of surplus items, optimizing for the case where elements have
2574: // been deleted but not reordered, or all elements have been removed.
2575: int numItemsToDispose = items.length - min;
2576: if (numItemsToDispose > 0) {
2577: CustomHashtable children = newHashtable(elementChildren.length * 2);
2578: for (int i = 0; i < elementChildren.length; i++) {
2579: Object elementChild = elementChildren[i];
2580: children.put(elementChild, elementChild);
2581: }
2582: int i = 0;
2583: while (numItemsToDispose > 0 && i < items.length) {
2584: Object data = items[i].getData();
2585: if (data == null
2586: || items.length - i <= numItemsToDispose
2587: || !children.containsKey(data)) {
2588: if (data != null) {
2589: disassociate(items[i]);
2590: }
2591: items[i].dispose();
2592: if (i + 1 < items.length) {
2593: // The components at positions i+1 through
2594: // items.length-1 in the source array are copied into
2595: // positions i through items.length-2
2596: System.arraycopy(items, i + 1, items, i,
2597: items.length - (i + 1));
2598: }
2599: numItemsToDispose--;
2600: } else {
2601: i++;
2602: }
2603: }
2604: }
2605:
2606: // compare first min items, and update item if necessary
2607: // need to do it in two passes:
2608: // 1: disassociate old items
2609: // 2: associate new items
2610: // because otherwise a later disassociate can remove a mapping made for
2611: // a previous associate,
2612: // making the map inconsistent
2613: for (int i = 0; i < min; ++i) {
2614: Item item = items[i];
2615: Object oldElement = item.getData();
2616: if (oldElement != null) {
2617: Object newElement = elementChildren[i];
2618: if (newElement != oldElement) {
2619: if (equals(newElement, oldElement)) {
2620: // update the data to be the new element, since
2621: // although the elements
2622: // may be equal, they may still have different labels
2623: // or children
2624: Object data = item.getData();
2625: if (data != null) {
2626: unmapElement(data, item);
2627: }
2628: item.setData(newElement);
2629: mapElement(newElement, item);
2630: } else {
2631: disassociate(item);
2632: // Clear the text and image to force a label update
2633: item.setImage(null);
2634: item.setText("");//$NON-NLS-1$
2635:
2636: }
2637: }
2638: }
2639: }
2640:
2641: for (int i = 0; i < min; ++i) {
2642: Item item = items[i];
2643: Object newElement = elementChildren[i];
2644: if (item.getData() == null) {
2645: // old and new elements are not equal
2646: associate(newElement, item);
2647: updatePlus(item, newElement);
2648: updateItem(item, newElement);
2649: } else {
2650: // old and new elements are equal
2651: updatePlus(item, newElement);
2652: if (updateLabels) {
2653: updateItem(item, newElement);
2654: }
2655: }
2656: }
2657:
2658: // Restore expanded state for items that changed position.
2659: // Make sure setExpanded is called after updatePlus, since
2660: // setExpanded(false) fails if item has no children.
2661: // Need to call setExpanded for both expanded and unexpanded
2662: // cases since the expanded state can change either way.
2663: // This needs to be done in a second loop, see bug 148025.
2664: for (int i = 0; i < min; ++i) {
2665: Item item = items[i];
2666: Object newElement = elementChildren[i];
2667: setExpanded(item, expanded.containsKey(newElement));
2668: }
2669:
2670: // add any remaining elements
2671: if (min < elementChildren.length) {
2672: for (int i = min; i < elementChildren.length; ++i) {
2673: createTreeItem(widget, elementChildren[i], i);
2674: }
2675:
2676: // Need to restore expanded state in a separate pass
2677: // because createTreeItem does not return the new item.
2678: // Avoid doing this unless needed.
2679: if (expanded.size() > 0) {
2680: // get the items again, to include the new items
2681: items = getChildren(widget);
2682: for (int i = min; i < elementChildren.length; ++i) {
2683: // Restore expanded state for items that changed position.
2684: // Make sure setExpanded is called after updatePlus (called
2685: // in createTreeItem), since
2686: // setExpanded(false) fails if item has no children.
2687: // Only need to call setExpanded if element was expanded
2688: // since new items are initially unexpanded.
2689: if (expanded.containsKey(elementChildren[i])) {
2690: setExpanded(items[i], true);
2691: }
2692: }
2693: }
2694: }
2695:
2696: // WORKAROUND
2697: if (widget == tree && oldCnt == 0 && getItemCount(tree) != 0) {
2698: // System.out.println("WORKAROUND setRedraw");
2699: tree.setRedraw(false);
2700: tree.setRedraw(true);
2701: }
2702: }
2703:
2704: /**
2705: * Updates the "+"/"-" icon of the tree node from the given element. It
2706: * calls <code>isExpandable</code> to determine whether an element is
2707: * expandable.
2708: *
2709: * @param item
2710: * the item
2711: * @param element
2712: * the element
2713: */
2714: protected void updatePlus(Item item, Object element) {
2715: boolean hasPlus = getItemCount(item) > 0;
2716: boolean needsPlus = isExpandable(item, null, element);
2717: boolean removeAll = false;
2718: boolean addDummy = false;
2719: Object data = item.getData();
2720: if (data != null && equals(element, data)) {
2721: // item shows same element
2722: if (hasPlus != needsPlus) {
2723: if (needsPlus) {
2724: addDummy = true;
2725: } else {
2726: removeAll = true;
2727: }
2728: }
2729: } else {
2730: // item shows different element
2731: removeAll = true;
2732: addDummy = needsPlus;
2733:
2734: // we cannot maintain expand state so collapse it
2735: setExpanded(item, false);
2736: }
2737: if (removeAll) {
2738: // remove all children
2739: Item[] items = getItems(item);
2740: for (int i = 0; i < items.length; i++) {
2741: if (items[i].getData() != null) {
2742: disassociate(items[i]);
2743: }
2744: items[i].dispose();
2745: }
2746: }
2747: if (addDummy) {
2748: newItem(item, SWT.NULL, -1); // append a dummy
2749: }
2750: }
2751:
2752: /**
2753: * Gets the expanded elements that are visible to the user. An expanded
2754: * element is only visible if the parent is expanded.
2755: *
2756: * @return the visible expanded elements
2757: * @since 2.0
2758: */
2759: public Object[] getVisibleExpandedElements() {
2760: ArrayList v = new ArrayList();
2761: internalCollectVisibleExpanded(v, getControl());
2762: return v.toArray();
2763: }
2764:
2765: private void internalCollectVisibleExpanded(ArrayList result,
2766: Widget widget) {
2767: Item[] items = getChildren(widget);
2768: for (int i = 0; i < items.length; i++) {
2769: Item item = items[i];
2770: if (getExpanded(item)) {
2771: Object data = item.getData();
2772: if (data != null) {
2773: result.add(data);
2774: }
2775: // Only recurse if it is expanded - if
2776: // not then the children aren't visible
2777: internalCollectVisibleExpanded(result, item);
2778: }
2779: }
2780: }
2781:
2782: /**
2783: * Returns the tree path for the given item.
2784: * @param item
2785: * @return {@link TreePath}
2786: *
2787: * @since 3.2
2788: */
2789: protected TreePath getTreePathFromItem(Item item) {
2790: LinkedList segments = new LinkedList();
2791: while (item != null) {
2792: Object segment = item.getData();
2793: Assert.isNotNull(segment);
2794: segments.addFirst(segment);
2795: item = getParentItem(item);
2796: }
2797: return new TreePath(segments.toArray());
2798: }
2799:
2800: /**
2801: * This implementation of getSelection() returns an instance of
2802: * ITreeSelection.
2803: *
2804: * @since 3.2
2805: */
2806: public ISelection getSelection() {
2807: Control control = getControl();
2808: if (control == null || control.isDisposed()) {
2809: return TreeSelection.EMPTY;
2810: }
2811: Widget[] items = getSelection(getControl());
2812: ArrayList list = new ArrayList(items.length);
2813: for (int i = 0; i < items.length; i++) {
2814: Widget item = items[i];
2815: if (item.getData() != null) {
2816: list.add(getTreePathFromItem((Item) item));
2817: }
2818: }
2819: return new TreeSelection((TreePath[]) list
2820: .toArray(new TreePath[list.size()]), getComparer());
2821: }
2822:
2823: protected void setSelectionToWidget(ISelection selection,
2824: boolean reveal) {
2825: if (selection instanceof ITreeSelection) {
2826: ITreeSelection treeSelection = (ITreeSelection) selection;
2827: setSelectionToWidget(Arrays
2828: .asList(treeSelection.getPaths()), reveal);
2829: } else {
2830: super .setSelectionToWidget(selection, reveal);
2831: }
2832: }
2833:
2834: /**
2835: * Returns a list of tree paths corresponding to expanded nodes in this
2836: * viewer's tree, including currently hidden ones that are marked as
2837: * expanded but are under a collapsed ancestor.
2838: * <p>
2839: * This method is typically used when preserving the interesting state of a
2840: * viewer; <code>setExpandedElements</code> is used during the restore.
2841: * </p>
2842: *
2843: * @return the array of expanded tree paths
2844: * @see #setExpandedElements
2845: *
2846: * @since 3.2
2847: */
2848: public TreePath[] getExpandedTreePaths() {
2849: ArrayList items = new ArrayList();
2850: internalCollectExpandedItems(items, getControl());
2851: ArrayList result = new ArrayList(items.size());
2852: for (Iterator it = items.iterator(); it.hasNext();) {
2853: Item item = (Item) it.next();
2854: TreePath treePath = getTreePathFromItem(item);
2855: if (treePath != null) {
2856: result.add(treePath);
2857: }
2858: }
2859: return (TreePath[]) result.toArray(new TreePath[items.size()]);
2860: }
2861:
2862: private boolean isTreePathContentProvider() {
2863: return getContentProvider() instanceof ITreePathContentProvider;
2864: }
2865:
2866: /**
2867: * Inserts the given element as a new child element of the given parent
2868: * element at the given position. If this viewer has a sorter, the position
2869: * is ignored and the element is inserted at the correct position in the
2870: * sort order.
2871: * <p>
2872: * This method should be called (by the content provider) when elements have
2873: * been added to the model, in order to cause the viewer to accurately
2874: * reflect the model. This method only affects the viewer, not the model.
2875: * </p>
2876: *
2877: * @param parentElementOrTreePath
2878: * the parent element, or the tree path to the parent
2879: * @param element
2880: * the element
2881: * @param position
2882: * a 0-based position relative to the model, or -1 to indicate
2883: * the last position
2884: *
2885: * @since 3.2
2886: */
2887: public void insert(Object parentElementOrTreePath, Object element,
2888: int position) {
2889: Assert.isNotNull(parentElementOrTreePath);
2890: Assert.isNotNull(element);
2891: if (isBusy())
2892: return;
2893: if (getComparator() != null || hasFilters()) {
2894: add(parentElementOrTreePath, new Object[] { element });
2895: return;
2896: }
2897: Widget[] items;
2898: if (internalIsInputOrEmptyPath(parentElementOrTreePath)) {
2899: items = new Widget[] { getControl() };
2900: } else {
2901: items = internalFindItems(parentElementOrTreePath);
2902: }
2903:
2904: for (int i = 0; i < items.length; i++) {
2905: Widget widget = items[i];
2906: if (widget instanceof Item) {
2907: Item item = (Item) widget;
2908:
2909: Item[] childItems = getChildren(item);
2910: if (getExpanded(item)
2911: || (childItems.length > 0 && childItems[0]
2912: .getData() != null)) {
2913: // item has real children, go ahead and add
2914: int insertionPosition = position;
2915: if (insertionPosition == -1) {
2916: insertionPosition = getItemCount(item);
2917: }
2918:
2919: createTreeItem(item, element, insertionPosition);
2920: }
2921: } else {
2922: int insertionPosition = position;
2923: if (insertionPosition == -1) {
2924: insertionPosition = getItemCount((Control) widget);
2925: }
2926:
2927: createTreeItem(widget, element, insertionPosition);
2928: }
2929: }
2930: }
2931:
2932: /*
2933: * (non-Javadoc)
2934: *
2935: * @see org.eclipse.jface.viewers.ColumnViewer#getColumnViewerOwner(int)
2936: */
2937: protected Widget getColumnViewerOwner(int columnIndex) {
2938: // Return null by default
2939: return null;
2940: }
2941:
2942: /**
2943: * This implementation of {@link #getItemAt(Point)} returns null to ensure
2944: * API backwards compatibility. Subclasses should override.
2945: *
2946: * @since 3.3
2947: */
2948: protected Item getItemAt(Point point) {
2949: return null;
2950: }
2951:
2952: /**
2953: * This implementation of {@link #createViewerEditor()} returns null to ensure
2954: * API backwards compatibility. Subclasses should override.
2955: *
2956: * @since 3.3
2957: */
2958: protected ColumnViewerEditor createViewerEditor() {
2959: return null;
2960: }
2961:
2962: /**
2963: * Returns the number of columns of this viewer.
2964: * <p><b>Subclasses should overwrite this method, which has a default
2965: * implementation (returning 0) for API backwards compatility reasons</b></p>
2966: *
2967: * @return the number of columns
2968: *
2969: * @since 3.3
2970: */
2971: protected int doGetColumnCount() {
2972: return 0;
2973: }
2974:
2975: /**
2976: * This implementation of buildLabel handles tree paths as well as elements.
2977: *
2978: * @param updateLabel
2979: * the ViewerLabel to collect the result in
2980: * @param elementOrPath
2981: * the element or tree path for which a label should be built
2982: *
2983: * @see org.eclipse.jface.viewers.StructuredViewer#buildLabel(org.eclipse.jface.viewers.ViewerLabel,
2984: * java.lang.Object)
2985: */
2986: protected void buildLabel(ViewerLabel updateLabel,
2987: Object elementOrPath) {
2988: Object element;
2989: if (elementOrPath instanceof TreePath) {
2990: TreePath path = (TreePath) elementOrPath;
2991: IBaseLabelProvider provider = getLabelProvider();
2992: if (provider instanceof ITreePathLabelProvider) {
2993: ITreePathLabelProvider pprov = (ITreePathLabelProvider) provider;
2994: buildLabel(updateLabel, path, pprov);
2995: return;
2996: }
2997: element = path.getLastSegment();
2998: } else {
2999: element = elementOrPath;
3000: }
3001: super .buildLabel(updateLabel, element);
3002: }
3003:
3004: /**
3005: * Returns true if the given object is either the input or an empty tree path.
3006: *
3007: * @param elementOrTreePath an element which could either be the viewer's input, or a tree path
3008: *
3009: * @return <code>true</code> if the given object is either the input or an empty tree path,
3010: * <code>false</code> otherwise.
3011: * @since 3.3
3012: */
3013: final protected boolean internalIsInputOrEmptyPath(
3014: final Object elementOrTreePath) {
3015: if (elementOrTreePath.equals(getInput()))
3016: return true;
3017: if (!(elementOrTreePath instanceof TreePath))
3018: return false;
3019: return ((TreePath) elementOrTreePath).getSegmentCount() == 0;
3020: }
3021:
3022: /*
3023: * Subclasses should implement
3024: */
3025: protected ViewerRow getViewerRowFromItem(Widget item) {
3026: return null;
3027: }
3028: }
|