KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > viewers > AbstractTreeViewer


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  * Tom Schindl <tom.schindl@bestsolution.at> - bug 153993, bug 167323, bug 175192
11  *******************************************************************************/

12
13 package org.eclipse.jface.viewers;
14
15 import java.util.ArrayList JavaDoc;
16 import java.util.Arrays JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.LinkedList JavaDoc;
19 import java.util.List JavaDoc;
20
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.core.runtime.ListenerList;
23 import org.eclipse.jface.util.SafeRunnable;
24 import org.eclipse.swt.SWT;
25 import org.eclipse.swt.custom.BusyIndicator;
26 import org.eclipse.swt.events.SelectionEvent;
27 import org.eclipse.swt.events.SelectionListener;
28 import org.eclipse.swt.events.TreeEvent;
29 import org.eclipse.swt.events.TreeListener;
30 import org.eclipse.swt.graphics.Point;
31 import org.eclipse.swt.widgets.Control;
32 import org.eclipse.swt.widgets.Item;
33 import org.eclipse.swt.widgets.Widget;
34
35 /**
36  * Abstract base implementation for tree-structure-oriented viewers (trees and
37  * table trees).
38  * <p>
39  * Nodes in the tree can be in either an expanded or a collapsed state,
40  * depending on whether the children on a node are visible. This class
41  * introduces public methods for controlling the expanding and collapsing of
42  * nodes.
43  * </p>
44  * <p>
45  * As of 3.2, AbstractTreeViewer supports multiple equal elements (each with a
46  * different parent chain) in the tree. This support requires that clients
47  * enable the element map by calling <code>setUseHashLookup(true)</code>.
48  * </p>
49  * <p>
50  * Content providers for abstract tree viewers must implement one of the
51  * interfaces <code>ITreeContentProvider</code> or (as of 3.2, to support
52  * multiple equal elements) <code>ITreePathContentProvider</code>.
53  * </p>
54  *
55  * @see TreeViewer
56  */

57 public abstract class AbstractTreeViewer extends ColumnViewer {
58
59     /**
60      * Constant indicating that all levels of the tree should be expanded or
61      * collapsed.
62      *
63      * @see #expandToLevel(int)
64      * @see #collapseToLevel(Object, int)
65      */

66     public static final int ALL_LEVELS = -1;
67
68     /**
69      * List of registered tree listeners (element type:
70      * <code>TreeListener</code>).
71      */

72     private ListenerList treeListeners = new ListenerList();
73
74     /**
75      * The level to which the tree is automatically expanded each time the
76      * viewer's input is changed (that is, by <code>setInput</code>). A value
77      * of 0 means that auto-expand is off.
78      *
79      * @see #setAutoExpandLevel
80      */

81     private int expandToLevel = 0;
82
83     /**
84      * Safe runnable used to update an item.
85      */

86     class UpdateItemSafeRunnable extends SafeRunnable {
87         private Object JavaDoc element;
88
89         private Item item;
90
91         UpdateItemSafeRunnable(Item item, Object JavaDoc element) {
92             this.item = item;
93             this.element = element;
94         }
95
96         public void run() {
97             doUpdateItem(item, element);
98         }
99
100     }
101
102     /**
103      * Creates an abstract tree viewer. The viewer has no input, no content
104      * provider, a default label provider, no sorter, no filters, and has
105      * auto-expand turned off.
106      */

107     protected AbstractTreeViewer() {
108         // do nothing
109
}
110
111     /**
112      * Adds the given child elements to this viewer as children of the given
113      * parent element. If this viewer does not have a sorter, the elements are
114      * added at the end of the parent's list of children in the order given;
115      * otherwise, the elements are inserted at the appropriate positions.
116      * <p>
117      * This method should be called (by the content provider) when elements have
118      * been added to the model, in order to cause the viewer to accurately
119      * reflect the model. This method only affects the viewer, not the model.
120      * </p>
121      *
122      * @param parentElementOrTreePath
123      * the parent element
124      * @param childElements
125      * the child elements to add
126      */

127     public void add(Object JavaDoc parentElementOrTreePath, Object JavaDoc[] childElements) {
128         Assert.isNotNull(parentElementOrTreePath);
129         assertElementsNotNull(childElements);
130         if (isBusy())
131             return;
132         Widget[] widgets = internalFindItems(parentElementOrTreePath);
133         // If parent hasn't been realized yet, just ignore the add.
134
if (widgets.length == 0) {
135             return;
136         }
137
138         for (int i = 0; i < widgets.length; i++) {
139             internalAdd(widgets[i], parentElementOrTreePath, childElements);
140         }
141     }
142
143     /**
144      * Find the items for the given element of tree path
145      *
146      * @param parentElementOrTreePath
147      * the element or tree path
148      * @return the items for that element
149      *
150      * @since 3.3
151      */

152     final protected Widget[] internalFindItems(Object JavaDoc parentElementOrTreePath) {
153         Widget[] widgets;
154         if (parentElementOrTreePath instanceof TreePath) {
155             TreePath path = (TreePath) parentElementOrTreePath;
156             Widget w = internalFindItem(path);
157             if (w == null) {
158                 widgets = new Widget[] {};
159             } else {
160                 widgets = new Widget[] { w };
161             }
162         } else {
163             widgets = findItems(parentElementOrTreePath);
164         }
165         return widgets;
166     }
167
168     /**
169      * Return the item at the given path or <code>null</code>
170      *
171      * @param path
172      * the path
173      * @return {@link Widget} the item at that path
174      */

175     private Widget internalFindItem(TreePath path) {
176         Widget[] widgets = findItems(path.getLastSegment());
177         for (int i = 0; i < widgets.length; i++) {
178             Widget widget = widgets[i];
179             if (widget instanceof Item) {
180                 Item item = (Item) widget;
181                 TreePath p = getTreePathFromItem(item);
182                 if (p.equals(path)) {
183                     return widget;
184                 }
185             }
186         }
187         return null;
188     }
189
190     /**
191      * Adds the given child elements to this viewer as children of the given
192      * parent element.
193      * <p>
194      * EXPERIMENTAL. Not to be used except by JDT. This method was added to
195      * support JDT's explorations into grouping by working sets, which requires
196      * viewers to support multiple equal elements. See bug 76482 for more
197      * details. This support will likely be removed in Eclipse 3.2 in favor of
198      * proper support for multiple equal elements.
199      * </p>
200      *
201      * @param widget
202      * the widget for the parent element
203      * @param parentElementOrTreePath
204      * the parent element
205      * @param childElements
206      * the child elements to add
207      * @since 3.1
208      */

209     protected void internalAdd(Widget widget, Object JavaDoc parentElementOrTreePath,
210             Object JavaDoc[] childElements) {
211         Object JavaDoc parent;
212         TreePath path;
213         if (parentElementOrTreePath instanceof TreePath) {
214             path = (TreePath) parentElementOrTreePath;
215             parent = path.getLastSegment();
216         } else {
217             parent = parentElementOrTreePath;
218             path = null;
219         }
220
221         // optimization!
222
// if the widget is not expanded we just invalidate the subtree
223
if (widget instanceof Item) {
224             Item ti = (Item) widget;
225             if (!getExpanded(ti)) {
226                 boolean needDummy = isExpandable(ti, path, parent);
227                 boolean haveDummy = false;
228                 // remove all children
229
Item[] items = getItems(ti);
230                 for (int i = 0; i < items.length; i++) {
231                     if (items[i].getData() != null) {
232                         disassociate(items[i]);
233                         items[i].dispose();
234                     } else {
235                         if (needDummy && !haveDummy) {
236                             haveDummy = true;
237                         } else {
238                             items[i].dispose();
239                         }
240                     }
241                 }
242                 // append a dummy if necessary
243
if (needDummy && !haveDummy) {
244                     newItem(ti, SWT.NULL, -1);
245                 }
246                 return;
247             }
248         }
249
250         if (childElements.length > 0) {
251             // TODO: Add filtering back?
252
Object JavaDoc[] filtered = filter(parentElementOrTreePath, childElements);
253             ViewerComparator comparator = getComparator();
254             if (comparator != null) {
255                 if (comparator instanceof TreePathViewerSorter) {
256                     TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;
257                     if (path == null) {
258                         path = internalGetSorterParentPath(widget, comparator);
259                     }
260                     tpvs.sort(this, path, filtered);
261                 } else {
262                     comparator.sort(this, filtered);
263                 }
264             }
265             createAddedElements(widget, filtered);
266         }
267     }
268
269     /**
270      * Filter the children elements.
271      *
272      * @param parentElementOrTreePath
273      * the parent element or path
274      * @param elements
275      * the child elements
276      * @return the filter list of children
277      */

278     private Object JavaDoc[] filter(Object JavaDoc parentElementOrTreePath, Object JavaDoc[] elements) {
279         ViewerFilter[] filters = getFilters();
280         if (filters != null) {
281             ArrayList JavaDoc filtered = new ArrayList JavaDoc(elements.length);
282             for (int i = 0; i < elements.length; i++) {
283                 boolean add = true;
284                 for (int j = 0; j < filters.length; j++) {
285                     add = filters[j].select(this, parentElementOrTreePath,
286                             elements[i]);
287                     if (!add) {
288                         break;
289                     }
290                 }
291                 if (add) {
292                     filtered.add(elements[i]);
293                 }
294             }
295             return filtered.toArray();
296         }
297         return elements;
298     }
299
300     /**
301      * Create the new elements in the parent widget. If the child already exists
302      * do nothing.
303      *
304      * @param widget
305      * @param elements
306      * Sorted list of elements to add.
307      */

308     private void createAddedElements(Widget widget, Object JavaDoc[] elements) {
309
310         if (elements.length == 1) {
311             if (equals(elements[0], widget.getData())) {
312                 return;
313             }
314         }
315
316         ViewerComparator comparator = getComparator();
317         TreePath parentPath = internalGetSorterParentPath(widget, comparator);
318         Item[] items = getChildren(widget);
319
320         // As the items are sorted already we optimize for a
321
// start position
322
int lastInsertion = 0;
323
324         // Optimize for the empty case
325
if (items.length == 0) {
326             for (int i = 0; i < elements.length; i++) {
327                 createTreeItem(widget, elements[i], -1);
328             }
329             return;
330         }
331
332         for (int i = 0; i < elements.length; i++) {
333             boolean newItem = true;
334             Object JavaDoc element = elements[i];
335             int index;
336             if (comparator == null) {
337                 if (itemExists(items, element)) {
338                     internalRefresh(element);
339                     newItem = false;
340                 }
341                 index = -1;
342             } else {
343                 lastInsertion = insertionPosition(items, comparator,
344                         lastInsertion, element, parentPath);
345                 // As we are only searching the original array we keep track of
346
// those positions only
347
if (lastInsertion == items.length) {
348                     index = -1;
349                 } else {// See if we should just refresh
350
while (lastInsertion < items.length
351                             && internalCompare(comparator, parentPath, element,
352                                     items[lastInsertion].getData()) == 0) {
353                         // As we cannot assume the sorter is consistent with
354
// equals() - therefore we can
355
// just check against the item prior to this index (if
356
// any)
357
if (items[lastInsertion].getData().equals(element)) {
358                             // refresh the element in case it has new children
359
internalRefresh(element);
360                             newItem = false;
361                         }
362                         lastInsertion++;// We had an insertion so increment
363
}
364                     // Did we get to the end?
365
if (lastInsertion == items.length) {
366                         index = -1;
367                     } else {
368                         index = lastInsertion + i; // Add the index as the
369
// array is growing
370
}
371                 }
372             }
373             if (newItem) {
374                 createTreeItem(widget, element, index);
375             }
376         }
377     }
378
379     /**
380      * See if element is the data of one of the elements in items.
381      *
382      * @param items
383      * @param element
384      * @return <code>true</code> if the element matches.
385      */

386     private boolean itemExists(Item[] items, Object JavaDoc element) {
387         if (usingElementMap()) {
388             Widget[] existingItems = findItems(element);
389             // optimization for two common cases
390
if (existingItems.length == 0) {
391                 return false;
392             } else if (existingItems.length == 1) {
393                 if (items.length > 0 && existingItems[0] instanceof Item) {
394                     Item existingItem = (Item) existingItems[0];
395                     return getParentItem(existingItem) == getParentItem(items[0]);
396                 }
397             }
398         }
399         for (int i = 0; i < items.length; i++) {
400             if (items[i].getData().equals(element)) {
401                 return true;
402             }
403         }
404         return false;
405     }
406
407     /**
408      * Returns the index where the item should be inserted. It uses sorter to
409      * determine the correct position, if sorter is not assigned, returns the
410      * index of the element after the last.
411      *
412      * @param items
413      * the items to search
414      * @param comparator
415      * The comparator to use.
416      * @param lastInsertion
417      * the start index to start search for position from this allows
418      * optimizing search for multiple elements that are sorted
419      * themselves.
420      * @param element
421      * element to find position for.
422      * @param parentPath
423      * the tree path for the element's parent or <code>null</code>
424      * if the element is a root element or the sorter is not a
425      * {@link TreePathViewerSorter}
426      * @return the index to use when inserting the element.
427      *
428      */

429
430     private int insertionPosition(Item[] items, ViewerComparator comparator,
431             int lastInsertion, Object JavaDoc element, TreePath parentPath) {
432
433         int size = items.length;
434         if (comparator == null) {
435             return size;
436         }
437         int min = lastInsertion, max = size - 1;
438
439         while (min <= max) {
440             int mid = (min + max) / 2;
441             Object JavaDoc data = items[mid].getData();
442             int compare = internalCompare(comparator, parentPath, data, element);
443             if (compare == 0) {
444                 return mid;// Return if we already match
445
}
446             if (compare < 0) {
447                 min = mid + 1;
448             } else {
449                 max = mid - 1;
450             }
451         }
452         return min;
453
454     }
455
456     /**
457      * Returns the index where the item should be inserted. It uses sorter to
458      * determine the correct position, if sorter is not assigned, returns the
459      * index of the element after the last.
460      *
461      * @param parent
462      * The parent widget
463      * @param sorter
464      * The sorter to use.
465      * @param startIndex
466      * the start index to start search for position from this allows
467      * optimizing search for multiple elements that are sorted
468      * themselves.
469      * @param element
470      * element to find position for.
471      * @param currentSize
472      * the current size of the collection
473      * @return the index to use when inserting the element.
474      *
475      */

476
477     /**
478      * Returns the index where the item should be inserted.
479      *
480      * @param parent
481      * The parent widget the element will be inserted into.
482      * @param element
483      * The element to insert.
484      * @return the index of the element
485      */

486     protected int indexForElement(Widget parent, Object JavaDoc element) {
487         ViewerComparator comparator = getComparator();
488         TreePath parentPath = internalGetSorterParentPath(parent, comparator);
489
490         Item[] items = getChildren(parent);
491         int count = items.length;
492
493         if (comparator == null) {
494             return count;
495         }
496         int min = 0, max = count - 1;
497
498         while (min <= max) {
499             int mid = (min + max) / 2;
500             Object JavaDoc data = items[mid].getData();
501             int compare = internalCompare(comparator, parentPath, data, element);
502             if (compare == 0) {
503                 // find first item > element
504
while (compare == 0) {
505                     ++mid;
506                     if (mid >= count) {
507                         break;
508                     }
509                     data = items[mid].getData();
510                     compare = internalCompare(comparator, parentPath, data,
511                             element);
512                 }
513                 return mid;
514             }
515             if (compare < 0) {
516                 min = mid + 1;
517             } else {
518                 max = mid - 1;
519             }
520         }
521         return min;
522     }
523
524     /**
525      * Return the tree path that should be used as the parent path for the given
526      * widget and sorter. A <code>null</code> is returned if either the sorter
527      * is not a {@link TreePathViewerSorter} or if the parent widget is not an
528      * {@link Item} (i.e. is the root of the tree).
529      *
530      * @param parent
531      * the parent widget
532      * @param comparator
533      * the sorter
534      * @return the tree path that should be used as the parent path for the
535      * given widget and sorter
536      */

537     private TreePath internalGetSorterParentPath(Widget parent,
538             ViewerComparator comparator) {
539         TreePath path;
540         if (comparator instanceof TreePathViewerSorter
541                 && parent instanceof Item) {
542             Item item = (Item) parent;
543             path = getTreePathFromItem(item);
544         } else {
545             path = null;
546         }
547         return path;
548     }
549
550     /**
551      * Compare the two elements using the given sorter. If the sorter is a
552      * {@link TreePathViewerSorter}, the provided tree path will be used. If
553      * the tree path is null and the sorter is a tree path sorter, then the
554      * elements are root elements
555      *
556      * @param comparator
557      * the sorter
558      * @param parentPath
559      * the path of the elements' parent
560      * @param e1
561      * the first element
562      * @param e2
563      * the second element
564      * @return the result of comparing the two elements
565      */

566     private int internalCompare(ViewerComparator comparator,
567             TreePath parentPath, Object JavaDoc e1, Object JavaDoc e2) {
568         if (comparator instanceof TreePathViewerSorter) {
569             TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;
570             return tpvs.compare(this, parentPath, e1, e2);
571         }
572         return comparator.compare(this, e1, e2);
573     }
574
575     /*
576      * (non-Javadoc)
577      *
578      * @see org.eclipse.jface.viewers.StructuredViewer#getSortedChildren(java.lang.Object)
579      */

580     protected Object JavaDoc[] getSortedChildren(Object JavaDoc parentElementOrTreePath) {
581         Object JavaDoc[] result = getFilteredChildren(parentElementOrTreePath);
582         ViewerComparator comparator = getComparator();
583         if (parentElementOrTreePath != null
584                 && comparator instanceof TreePathViewerSorter) {
585             TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;
586
587             // be sure we're not modifying the original array from the model
588
result = (Object JavaDoc[]) result.clone();
589
590             TreePath path = null;
591             if (parentElementOrTreePath instanceof TreePath) {
592                 path = (TreePath) parentElementOrTreePath;
593             } else {
594                 Object JavaDoc parent = parentElementOrTreePath;
595                 Widget w = internalGetWidgetToSelect(parent);
596                 if (w != null) {
597                     path = internalGetSorterParentPath(w, comparator);
598                 }
599             }
600             tpvs.sort(this, path, result);
601         } else if (comparator != null) {
602             // be sure we're not modifying the original array from the model
603
result = (Object JavaDoc[]) result.clone();
604             comparator.sort(this, result);
605         }
606         return result;
607     }
608
609     /*
610      * (non-Javadoc)
611      *
612      * @see org.eclipse.jface.viewers.StructuredViewer#getFilteredChildren(java.lang.Object)
613      */

614     protected Object JavaDoc[] getFilteredChildren(Object JavaDoc parentElementOrTreePath) {
615         Object JavaDoc[] result = getRawChildren(parentElementOrTreePath);
616         ViewerFilter[] filters = getFilters();
617         for (int i = 0; i < filters.length; i++) {
618             ViewerFilter filter = filters[i];
619             result = filter.filter(this, parentElementOrTreePath, result);
620         }
621         return result;
622     }
623
624     /**
625      * Adds the given child element to this viewer as a child of the given
626      * parent element. If this viewer does not have a sorter, the element is
627      * added at the end of the parent's list of children; otherwise, the element
628      * is inserted at the appropriate position.
629      * <p>
630      * This method should be called (by the content provider) when a single
631      * element has been added to the model, in order to cause the viewer to
632      * accurately reflect the model. This method only affects the viewer, not
633      * the model. Note that there is another method for efficiently processing
634      * the simultaneous addition of multiple elements.
635      * </p>
636      *
637      * @param parentElementOrTreePath
638      * the parent element or path
639      * @param childElement
640      * the child element
641      */

642     public void add(Object JavaDoc parentElementOrTreePath, Object JavaDoc childElement) {
643         add(parentElementOrTreePath, new Object JavaDoc[] { childElement });
644     }
645
646     /**
647      * Adds the given SWT selection listener to the given SWT control.
648      *
649      * @param control
650      * the SWT control
651      * @param listener
652      * the SWT selection listener
653      * @deprecated
654      */

655     protected void addSelectionListener(Control control,
656             SelectionListener listener) {
657         // do nothing
658
}
659
660     /**
661      * Adds a listener for expand and collapse events in this viewer. Has no
662      * effect if an identical listener is already registered.
663      *
664      * @param listener
665      * a tree viewer listener
666      */

667     public void addTreeListener(ITreeViewerListener listener) {
668         treeListeners.add(listener);
669     }
670
671     /**
672      * Adds the given SWT tree listener to the given SWT control.
673      *
674      * @param control
675      * the SWT control
676      * @param listener
677      * the SWT tree listener
678      */

679     protected abstract void addTreeListener(Control control,
680             TreeListener listener);
681
682     /*
683      * (non-Javadoc)
684      *
685      * @see StructuredViewer#associate(Object, Item)
686      */

687     protected void associate(Object JavaDoc element, Item item) {
688         Object JavaDoc data = item.getData();
689         if (data != null && data != element && equals(data, element)) {
690             // workaround for PR 1FV62BT
691
// assumption: elements are equal but not identical
692
// -> remove from map but don't touch children
693
unmapElement(data, item);
694             item.setData(element);
695             mapElement(element, item);
696         } else {
697             // recursively disassociate all
698
super.associate(element, item);
699         }
700     }
701
702     /**
703      * Collapses all nodes of the viewer's tree, starting with the root. This
704      * method is equivalent to <code>collapseToLevel(ALL_LEVELS)</code>.
705      */

706     public void collapseAll() {
707         Object JavaDoc root = getRoot();
708         if (root != null) {
709             collapseToLevel(root, ALL_LEVELS);
710         }
711     }
712
713     /**
714      * Collapses the subtree rooted at the given element or tree path to the
715      * given level.
716      *
717      * @param elementOrTreePath
718      * the element or tree path
719      * @param level
720      * non-negative level, or <code>ALL_LEVELS</code> to collapse
721      * all levels of the tree
722      */

723     public void collapseToLevel(Object JavaDoc elementOrTreePath, int level) {
724         Assert.isNotNull(elementOrTreePath);
725         Widget w = internalGetWidgetToSelect(elementOrTreePath);
726         if (w != null) {
727             internalCollapseToLevel(w, level);
728         }
729     }
730
731     /**
732      * Creates all children for the given widget.
733      * <p>
734      * The default implementation of this framework method assumes that
735      * <code>widget.getData()</code> returns the element corresponding to the
736      * node. Note: the node is not visually expanded! You may have to call
737      * <code>parent.setExpanded(true)</code>.
738      * </p>
739      *
740      * @param widget
741      * the widget
742      */

743     protected void createChildren(final Widget widget) {
744         boolean oldBusy = busy;
745         busy = true;
746         try {
747             final Item[] tis = getChildren(widget);
748             if (tis != null && tis.length > 0) {
749                 Object JavaDoc data = tis[0].getData();
750                 if (data != null) {
751                     return; // children already there!
752
}
753             }
754     
755             BusyIndicator.showWhile(widget.getDisplay(), new Runnable JavaDoc() {
756                 public void run() {
757                     // fix for PR 1FW89L7:
758
// don't complain and remove all "dummies" ...
759
if (tis != null) {
760                         for (int i = 0; i < tis.length; i++) {
761                             if (tis[i].getData() != null) {
762                                 disassociate(tis[i]);
763                                 Assert.isTrue(tis[i].getData() == null,
764                                         "Second or later child is non -null");//$NON-NLS-1$
765

766                             }
767                             tis[i].dispose();
768                         }
769                     }
770                     Object JavaDoc d = widget.getData();
771                     if (d != null) {
772                         Object JavaDoc parentElement = d;
773                         Object JavaDoc[] children;
774                         if (isTreePathContentProvider() && widget instanceof Item) {
775                             TreePath path = getTreePathFromItem((Item) widget);
776                             children = getSortedChildren(path);
777                         } else {
778                             children = getSortedChildren(parentElement);
779                         }
780                         for (int i = 0; i < children.length; i++) {
781                             createTreeItem(widget, children[i], -1);
782                         }
783                     }
784                 }
785     
786             });
787         } finally {
788             busy = oldBusy;
789         }
790     }
791
792     /**
793      * Creates a single item for the given parent and synchronizes it with the
794      * given element.
795      *
796      * @param parent
797      * the parent widget
798      * @param element
799      * the element
800      * @param index
801      * if non-negative, indicates the position to insert the item
802      * into its parent
803      */

804     protected void createTreeItem(Widget parent, Object JavaDoc element, int index) {
805         Item item = newItem(parent, SWT.NULL, index);
806         updateItem(item, element);
807         updatePlus(item, element);
808     }
809
810     /**
811      * The <code>AbstractTreeViewer</code> implementation of this method also
812      * recurses over children of the corresponding element.
813      */

814     protected void disassociate(Item item) {
815         super.disassociate(item);
816         // recursively unmapping the items is only required when
817
// the hash map is used. In the other case disposing
818
// an item will recursively dispose its children.
819
if (usingElementMap()) {
820             disassociateChildren(item);
821         }
822     }
823
824     /**
825      * Disassociates the children of the given SWT item from their corresponding
826      * elements.
827      *
828      * @param item
829      * the widget
830      */

831     private void disassociateChildren(Item item) {
832         Item[] items = getChildren(item);
833         for (int i = 0; i < items.length; i++) {
834             if (items[i].getData() != null) {
835                 disassociate(items[i]);
836             }
837         }
838     }
839
840     /* (non-Javadoc) Method declared on StructuredViewer. */
841     protected Widget doFindInputItem(Object JavaDoc element) {
842         // compare with root
843
Object JavaDoc root = getRoot();
844         if (root == null) {
845             return null;
846         }
847
848         if (equals(root, element)) {
849             return getControl();
850         }
851         return null;
852     }
853
854     /* (non-Javadoc) Method declared on StructuredViewer. */
855     protected Widget doFindItem(Object JavaDoc element) {
856         // compare with root
857
Object JavaDoc root = getRoot();
858         if (root == null) {
859             return null;
860         }
861
862         Item[] items = getChildren(getControl());
863         if (items != null) {
864             for (int i = 0; i < items.length; i++) {
865                 Widget o = internalFindItem(items[i], element);
866                 if (o != null) {
867                     return o;
868                 }
869             }
870         }
871         return null;
872     }
873
874     /**
875      * Copies the attributes of the given element into the given SWT item.
876      *
877      * @param item
878      * the SWT item
879      * @param element
880      * the element
881      */

882     protected void doUpdateItem(final Item item, Object JavaDoc element) {
883         if (item.isDisposed()) {
884             unmapElement(element, item);
885             return;
886         }
887
888         int columnCount = doGetColumnCount();
889         if (columnCount == 0)// If no columns are created then fake one
890
columnCount = 1;
891
892         ViewerRow viewerRowFromItem = getViewerRowFromItem(item);
893
894         boolean isVirtual = (getControl().getStyle() & SWT.VIRTUAL) != 0;
895
896         // If the control is virtual, we cannot use the cached viewer row object. See bug 188663.
897
if (isVirtual) {
898             viewerRowFromItem = (ViewerRow) viewerRowFromItem.clone();
899         }
900         
901         for (int column = 0; column < columnCount; column++) {
902             ViewerColumn columnViewer = getViewerColumn(column);
903             ViewerCell cellToUpdate = updateCell(viewerRowFromItem, column,
904                     element);
905             
906             // If the control is virtual, we cannot use the cached cell object. See bug 188663.
907
if (isVirtual) {
908                 cellToUpdate = new ViewerCell(cellToUpdate.getViewerRow(), cellToUpdate.getColumnIndex(), element);
909             }
910             
911             columnViewer.refresh(cellToUpdate);
912
913             // clear cell (see bug 201280)
914
updateCell(null, 0, null);
915
916             // As it is possible for user code to run the event
917
// loop check here.
918
if (item.isDisposed()) {
919                 unmapElement(element, item);
920                 return;
921             }
922
923         }
924     }
925     
926     /**
927      * Returns <code>true</code> if the given list and array of items refer to
928      * the same model elements. Order is unimportant.
929      * <p>
930      * This method is not intended to be overridden by subclasses.
931      * </p>
932      *
933      * @param items
934      * the list of items
935      * @param current
936      * the array of items
937      * @return <code>true</code> if the refer to the same elements,
938      * <code>false</code> otherwise
939      *
940      * @since 3.1 in TreeViewer, moved to AbstractTreeViewer in 3.3
941      */

942     protected boolean isSameSelection(List JavaDoc items, Item[] current) {
943         // If they are not the same size then they are not equivalent
944
int n = items.size();
945         if (n != current.length) {
946             return false;
947         }
948
949         CustomHashtable itemSet = newHashtable(n * 2 + 1);
950         for (Iterator JavaDoc i = items.iterator(); i.hasNext();) {
951             Item item = (Item) i.next();
952             Object JavaDoc element = item.getData();
953             itemSet.put(element, element);
954         }
955
956         // Go through the items of the current collection
957
// If there is a mismatch return false
958
for (int i = 0; i < current.length; i++) {
959             if (current[i].getData() == null
960                     || !itemSet.containsKey(current[i].getData())) {
961                 return false;
962             }
963         }
964
965         return true;
966     }
967
968     
969     
970     /* (non-Javadoc) Method declared on StructuredViewer. */
971     protected void doUpdateItem(Widget widget, Object JavaDoc element, boolean fullMap) {
972         boolean oldBusy = busy;
973         busy = true;
974         try {
975             if (widget instanceof Item) {
976                 Item item = (Item) widget;
977                 
978                 // ensure that back pointer is correct
979
if (fullMap) {
980                     associate(element, item);
981                 } else {
982                     Object JavaDoc data = item.getData();
983                     if (data != null) {
984                         unmapElement(data, item);
985                     }
986                     item.setData(element);
987                     mapElement(element, item);
988                 }
989                 
990                 // update icon and label
991
SafeRunnable.run(new UpdateItemSafeRunnable(item, element));
992             }
993         } finally {
994             busy = oldBusy;
995         }
996     }
997
998     /**
999      * Expands all nodes of the viewer's tree, starting with the root. This
1000     * method is equivalent to <code>expandToLevel(ALL_LEVELS)</code>.
1001     */

1002    public void expandAll() {
1003        expandToLevel(ALL_LEVELS);
1004    }
1005
1006    /**
1007     * Expands the root of the viewer's tree to the given level.
1008     *
1009     * @param level
1010     * non-negative level, or <code>ALL_LEVELS</code> to expand all
1011     * levels of the tree
1012     */

1013    public void expandToLevel(int level) {
1014        expandToLevel(getRoot(), level);
1015    }
1016
1017    /**
1018     * Expands all ancestors of the given element or tree path so that the given
1019     * element becomes visible in this viewer's tree control, and then expands
1020     * the subtree rooted at the given element to the given level.
1021     *
1022     * @param elementOrTreePath
1023     * the element
1024     * @param level
1025     * non-negative level, or <code>ALL_LEVELS</code> to expand all
1026     * levels of the tree
1027     */

1028    public void expandToLevel(Object JavaDoc elementOrTreePath, int level) {
1029        if (isBusy())
1030            return;
1031        Widget w = internalExpand(elementOrTreePath, true);
1032        if (w != null) {
1033            internalExpandToLevel(w, level);
1034        }
1035    }
1036
1037    /**
1038     * Fires a tree collapsed event. Only listeners registered at the time this
1039     * method is called are notified.
1040     *
1041     * @param event
1042     * the tree expansion event
1043     * @see ITreeViewerListener#treeCollapsed
1044     */

1045    protected void fireTreeCollapsed(final TreeExpansionEvent event) {
1046        Object JavaDoc[] listeners = treeListeners.getListeners();
1047        for (int i = 0; i < listeners.length; ++i) {
1048            final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
1049            SafeRunnable.run(new SafeRunnable() {
1050                public void run() {
1051                    l.treeCollapsed(event);
1052                }
1053            });
1054        }
1055    }
1056
1057    /**
1058     * Fires a tree expanded 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#treeExpanded
1064     */

1065    protected void fireTreeExpanded(final TreeExpansionEvent event) {
1066        Object JavaDoc[] listeners = treeListeners.getListeners();
1067        for (int i = 0; i < listeners.length; ++i) {
1068            final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
1069            SafeRunnable.run(new SafeRunnable() {
1070                public void run() {
1071                    l.treeExpanded(event);
1072                }
1073            });
1074        }
1075
1076    }
1077
1078    /**
1079     * Returns the auto-expand level.
1080     *
1081     * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of
1082     * the tree are expanded automatically
1083     * @see #setAutoExpandLevel
1084     */

1085    public int getAutoExpandLevel() {
1086        return expandToLevel;
1087    }
1088
1089    /**
1090     * Returns the SWT child items for the given SWT widget.
1091     *
1092     * @param widget
1093     * the widget
1094     * @return the child items
1095     */

1096    protected abstract Item[] getChildren(Widget widget);
1097
1098    /**
1099     * Get the child for the widget at index. Note that the default
1100     * implementation is not very efficient and should be overridden if this
1101     * class is implemented.
1102     *
1103     * @param widget
1104     * the widget to check
1105     * @param index
1106     * the index of the widget
1107     * @return Item or <code>null</code> if widget is not a type that can
1108     * contain items.
1109     *
1110     * @throws ArrayIndexOutOfBoundsException
1111     * if the index is not valid.
1112     * @since 3.1
1113     */

1114    protected Item getChild(Widget widget, int index) {
1115        return getChildren(widget)[index];
1116    }
1117
1118    /**
1119     * Returns whether the given SWT item is expanded or collapsed.
1120     *
1121     * @param item
1122     * the item
1123     * @return <code>true</code> if the item is considered expanded and
1124     * <code>false</code> if collapsed
1125     */

1126    protected abstract boolean getExpanded(Item item);
1127
1128    /**
1129     * Returns a list of elements corresponding to expanded nodes in this
1130     * viewer's tree, including currently hidden ones that are marked as
1131     * expanded but are under a collapsed ancestor.
1132     * <p>
1133     * This method is typically used when preserving the interesting state of a
1134     * viewer; <code>setExpandedElements</code> is used during the restore.
1135     * </p>
1136     *
1137     * @return the array of expanded elements
1138     * @see #setExpandedElements
1139     */

1140    public Object JavaDoc[] getExpandedElements() {
1141        ArrayList JavaDoc items = new ArrayList JavaDoc();
1142        internalCollectExpandedItems(items, getControl());
1143        ArrayList JavaDoc result = new ArrayList JavaDoc(items.size());
1144        for (Iterator JavaDoc it = items.iterator(); it.hasNext();) {
1145            Item item = (Item) it.next();
1146            Object JavaDoc data = item.getData();
1147            if (data != null) {
1148                result.add(data);
1149            }
1150        }
1151        return result.toArray();
1152    }
1153
1154    /**
1155     * Returns whether the node corresponding to the given element or tree path
1156     * is expanded or collapsed.
1157     *
1158     * @param elementOrTreePath
1159     * the element
1160     * @return <code>true</code> if the node is expanded, and
1161     * <code>false</code> if collapsed
1162     */

1163    public boolean getExpandedState(Object JavaDoc elementOrTreePath) {
1164        Assert.isNotNull(elementOrTreePath);
1165        Widget item = internalGetWidgetToSelect(elementOrTreePath);
1166        if (item instanceof Item) {
1167            return getExpanded((Item) item);
1168        }
1169        return false;
1170    }
1171
1172    /**
1173     * Returns the number of child items of the given SWT control.
1174     *
1175     * @param control
1176     * the control
1177     * @return the number of children
1178     */

1179    protected abstract int getItemCount(Control control);
1180
1181    /**
1182     * Returns the number of child items of the given SWT item.
1183     *
1184     * @param item
1185     * the item
1186     * @return the number of children
1187     */

1188    protected abstract int getItemCount(Item item);
1189
1190    /**
1191     * Returns the child items of the given SWT item.
1192     *
1193     * @param item
1194     * the item
1195     * @return the child items
1196     */

1197    protected abstract Item[] getItems(Item item);
1198
1199    /**
1200     * Returns the item after the given item in the tree, or <code>null</code>
1201     * if there is no next item.
1202     *
1203     * @param item
1204     * the item
1205     * @param includeChildren
1206     * <code>true</code> if the children are considered in
1207     * determining which item is next, and <code>false</code> if
1208     * subtrees are ignored
1209     * @return the next item, or <code>null</code> if none
1210     */

1211    protected Item getNextItem(Item item, boolean includeChildren) {
1212        if (item == null) {
1213            return null;
1214        }
1215        if (includeChildren && getExpanded(item)) {
1216            Item[] children = getItems(item);
1217            if (children != null && children.length > 0) {
1218                return children[0];
1219            }
1220        }
1221
1222        // next item is either next sibling or next sibling of first
1223
// parent that has a next sibling.
1224
Item parent = getParentItem(item);
1225        if (parent == null) {
1226            return null;
1227        }
1228        Item[] siblings = getItems(parent);
1229        if (siblings != null) {
1230            if (siblings.length <= 1) {
1231                return getNextItem(parent, false);
1232            }
1233
1234            for (int i = 0; i < siblings.length; i++) {
1235                if (siblings[i] == item && i < (siblings.length - 1)) {
1236                    return siblings[i + 1];
1237                }
1238            }
1239        }
1240        return getNextItem(parent, false);
1241    }
1242
1243    /**
1244     * Returns the parent item of the given item in the tree, or
1245     * <code>null</code> if there is no parent item.
1246     *
1247     * @param item
1248     * the item
1249     * @return the parent item, or <code>null</code> if none
1250     */

1251    protected abstract Item getParentItem(Item item);
1252
1253    /**
1254     * Returns the item before the given item in the tree, or <code>null</code>
1255     * if there is no previous item.
1256     *
1257     * @param item
1258     * the item
1259     * @return the previous item, or <code>null</code> if none
1260     */

1261    protected Item getPreviousItem(Item item) {
1262        // previous item is either right-most visible descendent of previous
1263
// sibling or parent
1264
Item parent = getParentItem(item);
1265        if (parent == null) {
1266            return null;
1267        }
1268        Item[] siblings = getItems(parent);
1269        if (siblings.length == 0 || siblings[0] == item) {
1270            return parent;
1271        }
1272        Item previous = siblings[0];
1273        for (int i = 1; i < siblings.length; i++) {
1274            if (siblings[i] == item) {
1275                return rightMostVisibleDescendent(previous);
1276            }
1277            previous = siblings[i];
1278        }
1279        return null;
1280    }
1281
1282    /* (non-Javadoc) Method declared on StructuredViewer. */
1283    protected Object JavaDoc[] getRawChildren(Object JavaDoc parentElementOrTreePath) {
1284        boolean oldBusy = busy;
1285        busy = true;
1286        try {
1287            Object JavaDoc parent;
1288            TreePath path;
1289            if (parentElementOrTreePath instanceof TreePath) {
1290                path = (TreePath) parentElementOrTreePath;
1291                parent = path.getLastSegment();
1292            } else {
1293                parent = parentElementOrTreePath;
1294                path = null;
1295            }
1296            if (parent != null) {
1297                if (equals(parent, getRoot())) {
1298                    return super.getRawChildren(parent);
1299                }
1300                IContentProvider cp = getContentProvider();
1301                if (cp instanceof ITreePathContentProvider) {
1302                    ITreePathContentProvider tpcp = (ITreePathContentProvider) cp;
1303                    if (path == null) {
1304                        // A path was not provided so try and find one
1305
Widget w = findItem(parent);
1306                        if (w instanceof Item) {
1307                            Item item = (Item) w;
1308                            path = getTreePathFromItem(item);
1309                        }
1310                        if (path == null) {
1311                            path = new TreePath(new Object JavaDoc[] { parent });
1312                        }
1313                    }
1314                    Object JavaDoc[] result = tpcp.getChildren(path);
1315                    if (result != null) {
1316                        return result;
1317                    }
1318                } else if (cp instanceof ITreeContentProvider) {
1319                    ITreeContentProvider tcp = (ITreeContentProvider) cp;
1320                    Object JavaDoc[] result = tcp.getChildren(parent);
1321                    if (result != null) {
1322                        return result;
1323                    }
1324                }
1325            }
1326            return new Object JavaDoc[0];
1327        } finally {
1328            busy = oldBusy;
1329        }
1330    }
1331
1332    /**
1333     * Returns all selected items for the given SWT control.
1334     *
1335     * @param control
1336     * the control
1337     * @return the list of selected items
1338     */

1339    protected abstract Item[] getSelection(Control control);
1340
1341    /*
1342     * (non-Javadoc)
1343     *
1344     * @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget()
1345     */

1346    protected List JavaDoc getSelectionFromWidget() {
1347        Widget[] items = getSelection(getControl());
1348        ArrayList JavaDoc list = new ArrayList JavaDoc(items.length);
1349        for (int i = 0; i < items.length; i++) {
1350            Widget item = items[i];
1351            Object JavaDoc e = item.getData();
1352            if (e != null) {
1353                list.add(e);
1354            }
1355        }
1356        return list;
1357    }
1358
1359    /*
1360     * Overridden in AbstractTreeViewer to fix bug 108102 (code copied from
1361     * StructuredViewer to avoid introducing new API) (non-Javadoc)
1362     *
1363     * @see org.eclipse.jface.viewers.StructuredViewer#handleDoubleSelect(org.eclipse.swt.events.SelectionEvent)
1364     */

1365    protected void handleDoubleSelect(SelectionEvent event) {
1366        // handle case where an earlier selection listener disposed the control.
1367
Control control = getControl();
1368        if (control != null && !control.isDisposed()) {
1369            // If the double-clicked element can be obtained from the event, use
1370
// it
1371
// otherwise get it from the control. Some controls like List do
1372
// not have the notion of item.
1373
// For details, see bug 90161 [Navigator] DefaultSelecting folders
1374
// shouldn't always expand first one
1375
ISelection selection;
1376            if (event.item != null && event.item.getData() != null) {
1377
1378                // changes to fix bug 108102 follow
1379
TreePath treePath = getTreePathFromItem((Item) event.item);
1380                selection = new TreeSelection(treePath);
1381                // end of changes
1382

1383            } else {
1384                selection = getSelection();
1385                updateSelection(selection);
1386            }
1387            fireDoubleClick(new DoubleClickEvent(this, selection));
1388        }
1389    }
1390
1391    /**
1392     * Handles a tree collapse event from the SWT widget.
1393     *
1394     * @param event
1395     * the SWT tree event
1396     */

1397    protected void handleTreeCollapse(TreeEvent event) {
1398        if (event.item.getData() != null) {
1399            fireTreeCollapsed(new TreeExpansionEvent(this, event.item.getData()));
1400        }
1401    }
1402
1403    /**
1404     * Handles a tree expand event from the SWT widget.
1405     *
1406     * @param event
1407     * the SWT tree event
1408     */

1409    protected void handleTreeExpand(TreeEvent event) {
1410        createChildren(event.item);
1411        if (event.item.getData() != null) {
1412            fireTreeExpanded(new TreeExpansionEvent(this, event.item.getData()));
1413        }
1414    }
1415
1416    /* (non-Javadoc) Method declared on Viewer. */
1417    protected void hookControl(Control control) {
1418        super.hookControl(control);
1419        addTreeListener(control, new TreeListener() {
1420            public void treeExpanded(TreeEvent event) {
1421                handleTreeExpand(event);
1422            }
1423
1424            public void treeCollapsed(TreeEvent event) {
1425                handleTreeCollapse(event);
1426            }
1427        });
1428    }
1429
1430    /*
1431     * (non-Javadoc) Method declared on StructuredViewer. Builds the initial
1432     * tree and handles the automatic expand feature.
1433     */

1434    protected void inputChanged(Object JavaDoc input, Object JavaDoc oldInput) {
1435        preservingSelection(new Runnable JavaDoc() {
1436            public void run() {
1437                Control tree = getControl();
1438                boolean useRedraw = true;
1439                // (size > REDRAW_THRESHOLD) || (table.getItemCount() >
1440
// REDRAW_THRESHOLD);
1441
if (useRedraw) {
1442                    tree.setRedraw(false);
1443                }
1444                removeAll(tree);
1445                tree.setData(getRoot());
1446                internalInitializeTree(tree);
1447                if (useRedraw) {
1448                    tree.setRedraw(true);
1449                }
1450            }
1451
1452        });
1453    }
1454
1455    /**
1456     * Initializes the tree with root items, expanding to the appropriate
1457     * level if necessary.
1458     *
1459     * @param tree the tree control
1460     * @since 3.3
1461     */

1462    protected void internalInitializeTree(Control tree) {
1463        createChildren(tree);
1464        internalExpandToLevel(tree, expandToLevel);
1465    }
1466
1467    /**
1468     * Recursively collapses the subtree rooted at the given widget to the given
1469     * level.
1470     * <p>
1471     * </p>
1472     * Note that the default implementation of this method does not call
1473     * <code>setRedraw</code>.
1474     *
1475     * @param widget
1476     * the widget
1477     * @param level
1478     * non-negative level, or <code>ALL_LEVELS</code> to collapse
1479     * all levels of the tree
1480     */

1481    protected void internalCollapseToLevel(Widget widget, int level) {
1482        if (level == ALL_LEVELS || level > 0) {
1483
1484            if (widget instanceof Item) {
1485                setExpanded((Item) widget, false);
1486            }
1487
1488            if (level == ALL_LEVELS || level > 1) {
1489                Item[] children = getChildren(widget);
1490                if (children != null) {
1491                    int nextLevel = (level == ALL_LEVELS ? ALL_LEVELS
1492                            : level - 1);
1493                    for (int i = 0; i < children.length; i++) {
1494                        internalCollapseToLevel(children[i], nextLevel);
1495                    }
1496                }
1497            }
1498        }
1499    }
1500
1501    /**
1502     * Recursively collects all expanded items from the given widget.
1503     *
1504     * @param result
1505     * a list (element type: <code>Item</code>) into which to
1506     * collect the elements
1507     * @param widget
1508     * the widget
1509     */

1510    private void internalCollectExpandedItems(List JavaDoc result, Widget widget) {
1511        Item[] items = getChildren(widget);
1512        for (int i = 0; i < items.length; i++) {
1513            Item item = items[i];
1514            if (getExpanded(item)) {
1515                result.add(item);
1516            }
1517            internalCollectExpandedItems(result, item);
1518        }
1519    }
1520
1521    /**
1522     * Tries to create a path of tree items for the given element or tree path.
1523     * This method recursively walks up towards the root of the tree and in the
1524     * case of an element (rather than a tree path) assumes that
1525     * <code>getParent</code> returns the correct parent of an element.
1526     *
1527     * @param elementOrPath
1528     * the element
1529     * @param expand
1530     * <code>true</code> if all nodes on the path should be
1531     * expanded, and <code>false</code> otherwise
1532     * @return Widget
1533     */

1534    protected Widget internalExpand(Object JavaDoc elementOrPath, boolean expand) {
1535
1536        if (elementOrPath == null) {
1537            return null;
1538        }
1539
1540        Widget w = internalGetWidgetToSelect(elementOrPath);
1541        if (w == null) {
1542            if (equals(elementOrPath, getRoot())) { // stop at root
1543
return null;
1544            }
1545            // my parent has to create me
1546
Object JavaDoc parent = getParentElement(elementOrPath);
1547            if (parent != null) {
1548                Widget pw = internalExpand(parent, false);
1549                if (pw != null) {
1550                    // let my parent create me
1551
createChildren(pw);
1552                    Object JavaDoc element = internalToElement(elementOrPath);
1553                    w = internalFindChild(pw, element);
1554                    if (expand && pw instanceof Item) {
1555                        // expand parent items top-down
1556
Item item = (Item) pw;
1557                        LinkedList JavaDoc toExpandList = new LinkedList JavaDoc();
1558                        while (item != null && !getExpanded(item)) {
1559                            toExpandList.addFirst(item);
1560                            item = getParentItem(item);
1561                        }
1562                        for (Iterator JavaDoc it = toExpandList.iterator(); it
1563                                .hasNext();) {
1564                            Item toExpand = (Item) it.next();
1565                            setExpanded(toExpand, true);
1566                        }
1567                    }
1568                }
1569            }
1570        }
1571        return w;
1572    }
1573
1574    /**
1575     * If the argument is a tree path, returns its last segment, otherwise
1576     * return the argument
1577     *
1578     * @param elementOrPath
1579     * an element or a tree path
1580     * @return the element, or the last segment of the tree path
1581     */

1582    private Object JavaDoc internalToElement(Object JavaDoc elementOrPath) {
1583        if (elementOrPath instanceof TreePath) {
1584            return ((TreePath) elementOrPath).getLastSegment();
1585        }
1586        return elementOrPath;
1587    }
1588
1589    /**
1590     * This method takes a tree path or an element. If the argument is not a
1591     * tree path, returns the parent of the given element or <code>null</code>
1592     * if the parent is not known. If the argument is a tree path with more than
1593     * one segment, returns its parent tree path, otherwise returns
1594     * <code>null</code>.
1595     *
1596     * @param elementOrTreePath
1597     * @return the parent element, or parent path, or <code>null</code>
1598     *
1599     * @since 3.2
1600     */

1601    protected Object JavaDoc getParentElement(Object JavaDoc elementOrTreePath) {
1602        if (elementOrTreePath instanceof TreePath) {
1603            TreePath treePath = (TreePath) elementOrTreePath;
1604            return (treePath).getParentPath();
1605        }
1606        IContentProvider cp = getContentProvider();
1607        if (cp instanceof ITreePathContentProvider) {
1608            ITreePathContentProvider tpcp = (ITreePathContentProvider) cp;
1609            TreePath[] paths = tpcp.getParents(elementOrTreePath);
1610            if (paths.length > 0) {
1611                if (paths[0].getSegmentCount() == 0) {
1612                    return getInput();
1613                }
1614                return paths[0].getLastSegment();
1615            }
1616        }
1617        if (cp instanceof ITreeContentProvider) {
1618            ITreeContentProvider tcp = (ITreeContentProvider) cp;
1619            return tcp.getParent(elementOrTreePath);
1620        }
1621        return null;
1622    }
1623
1624    /**
1625     * Returns the widget to be selected for the given element or tree path.
1626     *
1627     * @param elementOrTreePath
1628     * the element or tree path to select
1629     * @return the widget to be selected, or <code>null</code> if not found
1630     *
1631     * @since 3.1
1632     */

1633    protected Widget internalGetWidgetToSelect(Object JavaDoc elementOrTreePath) {
1634        if (elementOrTreePath instanceof TreePath) {
1635            TreePath treePath = (TreePath) elementOrTreePath;
1636            if (treePath.getSegmentCount() == 0) {
1637                return getControl();
1638            }
1639            Widget[] candidates = findItems(treePath.getLastSegment());
1640            for (int i = 0; i < candidates.length; i++) {
1641                Widget candidate = candidates[i];
1642                if (!(candidate instanceof Item)) {
1643                    continue;
1644                }
1645                if (treePath.equals(getTreePathFromItem((Item) candidate),
1646                        getComparer())) {
1647                    return candidate;
1648                }
1649            }
1650            return null;
1651        }
1652        return findItem(elementOrTreePath);
1653    }
1654
1655    /**
1656     * Recursively expands the subtree rooted at the given widget to the given
1657     * level.
1658     * <p>
1659     * </p>
1660     * Note that the default implementation of this method does not call
1661     * <code>setRedraw</code>.
1662     *
1663     * @param widget
1664     * the widget
1665     * @param level
1666     * non-negative level, or <code>ALL_LEVELS</code> to collapse
1667     * all levels of the tree
1668     */

1669    protected void internalExpandToLevel(Widget widget, int level) {
1670        if (level == ALL_LEVELS || level > 0) {
1671            if (widget instanceof Item && widget.getData() != null
1672                    && !isExpandable((Item) widget, null, widget.getData())) {
1673                return;
1674            }
1675            createChildren(widget);
1676            if (widget instanceof Item) {
1677                setExpanded((Item) widget, true);
1678            }
1679            if (level == ALL_LEVELS || level > 1) {
1680                Item[] children = getChildren(widget);
1681                if (children != null) {
1682                    int newLevel = (level == ALL_LEVELS ? ALL_LEVELS
1683                            : level - 1);
1684                    for (int i = 0; i < children.length; i++) {
1685                        internalExpandToLevel(children[i], newLevel);
1686                    }
1687                }
1688            }
1689        }
1690    }
1691
1692    /**
1693     * Non-recursively tries to find the given element as a child of the given
1694     * parent (item or tree).
1695     *
1696     * @param parent
1697     * the parent item
1698     * @param element
1699     * the element
1700     * @return Widget
1701     */

1702    private Widget internalFindChild(Widget parent, Object JavaDoc element) {
1703        Item[] items = getChildren(parent);
1704        for (int i = 0; i < items.length; i++) {
1705            Item item = items[i];
1706            Object JavaDoc data = item.getData();
1707            if (data != null && equals(data, element)) {
1708                return item;
1709            }
1710        }
1711        return null;
1712    }
1713
1714    /**
1715     * Recursively tries to find the given element.
1716     *
1717     * @param parent
1718     * the parent item
1719     * @param element
1720     * the element
1721     * @return Widget
1722     */

1723    private Widget internalFindItem(Item parent, Object JavaDoc element) {
1724
1725        // compare with node
1726
Object JavaDoc data = parent.getData();
1727        if (data != null) {
1728            if (equals(data, element)) {
1729                return parent;
1730            }
1731        }
1732        // recurse over children
1733
Item[] items = getChildren(parent);
1734        for (int i = 0; i < items.length; i++) {
1735            Item item = items[i];
1736            Widget o = internalFindItem(item, element);
1737            if (o != null) {
1738                return o;
1739            }
1740        }
1741        return null;
1742    }
1743
1744    /* (non-Javadoc) Method declared on StructuredViewer. */
1745    protected void internalRefresh(Object JavaDoc element) {
1746        internalRefresh(element, true);
1747    }
1748
1749    /* (non-Javadoc) Method declared on StructuredViewer. */
1750    protected void internalRefresh(Object JavaDoc element, boolean updateLabels) {
1751        // If element is null, do a full refresh.
1752
if (element == null) {
1753            internalRefresh(getControl(), getRoot(), true, updateLabels);
1754            return;
1755        }
1756        Widget[] items = findItems(element);
1757        if (items.length != 0) {
1758            for (int i = 0; i < items.length; i++) {
1759                // pick up structure changes too
1760
internalRefresh(items[i], element, true, updateLabels);
1761            }
1762        }
1763    }
1764
1765    /**
1766     * Refreshes the tree starting at the given widget.
1767     * <p>
1768     * EXPERIMENTAL. Not to be used except by JDT. This method was added to
1769     * support JDT's explorations into grouping by working sets, which requires
1770     * viewers to support multiple equal elements. See bug 76482 for more
1771     * details. This support will likely be removed in Eclipse 3.2 in favor of
1772     * proper support for multiple equal elements.
1773     * </p>
1774     *
1775     * @param widget
1776     * the widget
1777     * @param element
1778     * the element
1779     * @param doStruct
1780     * <code>true</code> if structural changes are to be picked up,
1781     * and <code>false</code> if only label provider changes are of
1782     * interest
1783     * @param updateLabels
1784     * <code>true</code> to update labels for existing elements,
1785     * <code>false</code> to only update labels as needed, assuming
1786     * that labels for existing elements are unchanged.
1787     * @since 3.1
1788     */

1789    protected void internalRefresh(Widget widget, Object JavaDoc element,
1790            boolean doStruct, boolean updateLabels) {
1791
1792        if (widget instanceof Item) {
1793            if (doStruct) {
1794                updatePlus((Item) widget, element);
1795            }
1796            if (updateLabels || !equals(element, widget.getData())) {
1797                doUpdateItem(widget, element, true);
1798            } else {
1799                associate(element, (Item) widget);
1800            }
1801        }
1802
1803        if (doStruct) {
1804            internalRefreshStruct(widget, element, updateLabels);
1805        } else {
1806            Item[] children = getChildren(widget);
1807            if (children != null) {
1808                for (int i = 0; i < children.length; i++) {
1809                    Widget item = children[i];
1810                    Object JavaDoc data = item.getData();
1811                    if (data != null) {
1812                        internalRefresh(item, data, doStruct, updateLabels);
1813                    }
1814                }
1815            }
1816        }
1817    }
1818
1819    /**
1820     * Update the structure and recurse. Items are updated in updateChildren, as
1821     * needed.
1822     *
1823     * @param widget
1824     * @param element
1825     * @param updateLabels
1826     */

1827    /* package */void internalRefreshStruct(Widget widget, Object JavaDoc element,
1828            boolean updateLabels) {
1829        updateChildren(widget, element, null, updateLabels);
1830        Item[] children = getChildren(widget);
1831        if (children != null) {
1832            for (int i = 0; i < children.length; i++) {
1833                Widget item = children[i];
1834                Object JavaDoc data = item.getData();
1835                if (data != null) {
1836                    internalRefreshStruct(item, data, updateLabels);
1837                }
1838            }
1839        }
1840    }
1841
1842    /**
1843     * Removes the given elements from this viewer.
1844     * <p>
1845     * EXPERIMENTAL. Not to be used except by JDT. This method was added to
1846     * support JDT's explorations into grouping by working sets, which requires
1847     * viewers to support multiple equal elements. See bug 76482 for more
1848     * details. This support will likely be removed in Eclipse 3.2 in favor of
1849     * proper support for multiple equal elements.
1850     * </p>
1851     *
1852     * @param elementsOrPaths
1853     * the elements or element paths to remove
1854     * @since 3.1
1855     */

1856    protected void internalRemove(Object JavaDoc[] elementsOrPaths) {
1857        Object JavaDoc input = getInput();
1858        for (int i = 0; i < elementsOrPaths.length; ++i) {
1859            Object JavaDoc element = elementsOrPaths[i];
1860            if (equals(element, input)) {
1861                setInput(null);
1862                return;
1863            }
1864            Widget[] childItems = internalFindItems(element);
1865            for (int j = 0; j < childItems.length; j++) {
1866                Widget childItem = childItems[j];
1867                if (childItem instanceof Item) {
1868                    disassociate((Item) childItem);
1869                    childItem.dispose();
1870                }
1871            }
1872        }
1873    }
1874
1875    /**
1876     * Removes the given elements from this viewer, whenever those elements
1877     * appear as children of the given parent.
1878     *
1879     * @param parent the parent element
1880     * @param elements
1881     * the elements to remove
1882     * @since 3.1
1883     */

1884    protected void internalRemove(Object JavaDoc parent, Object JavaDoc[] elements) {
1885
1886        CustomHashtable toRemove = new CustomHashtable(getComparer());
1887        for (int i = 0; i < elements.length; i++) {
1888            toRemove.put(elements[i], elements[i]);
1889        }
1890
1891        // Find each place the parent appears in the tree
1892
Widget[] parentItemArray = findItems(parent);
1893        for (int i = 0; i < parentItemArray.length; i++) {
1894            Widget parentItem = parentItemArray[i];
1895
1896            // Iterate over the child items and remove each one
1897
Item[] children = getChildren(parentItem);
1898
1899            for (int j = 0; j < children.length; j++) {
1900                Item child = children[j];
1901
1902                Object JavaDoc data = child.getData();
1903                if (data != null && toRemove.containsKey(data)) {
1904                    disassociate(child);
1905                    child.dispose();
1906                }
1907            }
1908        }
1909    }
1910
1911    /**
1912     * Sets the expanded state of all items to correspond to the given set of
1913     * expanded elements.
1914     *
1915     * @param expandedElements
1916     * the set (element type: <code>Object</code>) of elements
1917     * which are expanded
1918     * @param widget
1919     * the widget
1920     */

1921    private void internalSetExpanded(CustomHashtable expandedElements,
1922            Widget widget) {
1923        Item[] items = getChildren(widget);
1924        for (int i = 0; i < items.length; i++) {
1925            Item item = items[i];
1926            Object JavaDoc data = item.getData();
1927            if (data != null) {
1928                // remove the element to avoid an infinite loop
1929
// if the same element appears on a child item
1930
boolean expanded = expandedElements.remove(data) != null;
1931                if (expanded != getExpanded(item)) {
1932                    if (expanded) {
1933                        createChildren(item);
1934                    }
1935                    setExpanded(item, expanded);
1936                }
1937            }
1938            if (expandedElements.size() > 0) {
1939                internalSetExpanded(expandedElements, item);
1940            }
1941        }
1942    }
1943
1944    /**
1945     * Sets the expanded state of all items to correspond to the given set of
1946     * expanded tree paths.
1947     *
1948     * @param expandedTreePaths
1949     * the set (element type: <code>TreePath</code>) of elements
1950     * which are expanded
1951     * @param widget
1952     * the widget
1953     */

1954    private void internalSetExpandedTreePaths(
1955            CustomHashtable expandedTreePaths, Widget widget,
1956            TreePath currentPath) {
1957        Item[] items = getChildren(widget);
1958        for (int i = 0; i < items.length; i++) {
1959            Item item = items[i];
1960            Object JavaDoc data = item.getData();
1961            TreePath childPath = data == null ? null : currentPath
1962                    .createChildPath(data);
1963            if (data != null && childPath != null) {
1964                // remove the element to avoid an infinite loop
1965
// if the same element appears on a child item
1966
boolean expanded = expandedTreePaths.remove(childPath) != null;
1967                if (expanded != getExpanded(item)) {
1968                    if (expanded) {
1969                        createChildren(item);
1970                    }
1971                    setExpanded(item, expanded);
1972                }
1973            }
1974            internalSetExpandedTreePaths(expandedTreePaths, item, childPath);
1975        }
1976    }
1977
1978    /**
1979     * Return whether the tree node representing the given element or path can
1980     * be expanded. Clients should query expandability by path if the viewer's
1981     * content provider is an {@link ITreePathContentProvider}.
1982     * <p>
1983     * The default implementation of this framework method calls
1984     * <code>hasChildren</code> on this viewer's content provider. It may be
1985     * overridden if necessary.
1986     * </p>
1987     *
1988     * @param elementOrTreePath
1989     * the element or path
1990     * @return <code>true</code> if the tree node representing the given
1991     * element can be expanded, or <code>false</code> if not
1992     */

1993    public boolean isExpandable(Object JavaDoc elementOrTreePath) {
1994        Object JavaDoc element;
1995        TreePath path;
1996        if (elementOrTreePath instanceof TreePath) {
1997            path = (TreePath) elementOrTreePath;
1998            element = path.getLastSegment();
1999        } else {
2000            element = elementOrTreePath;
2001            path = null;
2002        }
2003        IContentProvider cp = getContentProvider();
2004        if (cp instanceof ITreePathContentProvider) {
2005            ITreePathContentProvider tpcp = (ITreePathContentProvider) cp;
2006            if (path == null) {
2007                // A path was not provided so try and find one
2008
Widget w = findItem(element);
2009                if (w instanceof Item) {
2010                    Item item = (Item) w;
2011                    path = getTreePathFromItem(item);
2012                }
2013                if (path == null) {
2014                    path = new TreePath(new Object JavaDoc[] { element });
2015                }
2016            }
2017            return tpcp.hasChildren(path);
2018        }
2019        if (cp instanceof ITreeContentProvider) {
2020            ITreeContentProvider tcp = (ITreeContentProvider) cp;
2021            return tcp.hasChildren(element);
2022        }
2023        return false;
2024    }
2025
2026    /**
2027     * Return whether the given element is expandable.
2028     *
2029     * @param item
2030     * the tree item for the element
2031     * @param parentPath
2032     * the parent path if it is known or <code>null</code> if it
2033     * needs to be determines
2034     * @param element
2035     * the element
2036     * @return whether the given element is expandable
2037     */

2038    private boolean isExpandable(Item item, TreePath parentPath, Object JavaDoc element) {
2039        Object JavaDoc elementOrTreePath = element;
2040        if (isTreePathContentProvider()) {
2041            if (parentPath != null) {
2042                elementOrTreePath = parentPath.createChildPath(element);
2043            } else {
2044                elementOrTreePath = getTreePathFromItem(item);
2045            }
2046        }
2047        return isExpandable(elementOrTreePath);
2048    }
2049
2050    /* (non-Javadoc) Method declared on Viewer. */
2051    protected void labelProviderChanged() {
2052        // we have to walk the (visible) tree and update every item
2053
Control tree = getControl();
2054        tree.setRedraw(false);
2055        // don't pick up structure changes, but do force label updates
2056
internalRefresh(tree, getRoot(), false, true);
2057        tree.setRedraw(true);
2058    }
2059
2060    /**
2061     * Creates a new item.
2062     *
2063     * @param parent
2064     * the parent widget
2065     * @param style
2066     * SWT style bits
2067     * @param index
2068     * if non-negative, indicates the position to insert the item
2069     * into its parent
2070     * @return the newly-created item
2071     */

2072    protected abstract Item newItem(Widget parent, int style, int index);
2073
2074    /**
2075     * Removes the given elements from this viewer. The selection is updated if
2076     * required.
2077     * <p>
2078     * This method should be called (by the content provider) when elements have
2079     * been removed from the model, in order to cause the viewer to accurately
2080     * reflect the model. This method only affects the viewer, not the model.
2081     * </p>
2082     *
2083     * @param elementsOrTreePaths
2084     * the elements to remove
2085     */

2086    public void remove(final Object JavaDoc[] elementsOrTreePaths) {
2087        assertElementsNotNull(elementsOrTreePaths);
2088        if (elementsOrTreePaths.length == 0) {
2089            return;
2090        }
2091        if (isBusy())
2092            return;
2093        preservingSelection(new Runnable JavaDoc() {
2094            public void run() {
2095                internalRemove(elementsOrTreePaths);
2096            }
2097        });
2098    }
2099
2100    /**
2101     * Removes the given elements from this viewer whenever they appear as
2102     * children of the given parent element. If the given elements also appear
2103     * as children of some other parent, the other parent will remain unchanged.
2104     * The selection is updated if required.
2105     * <p>
2106     * This method should be called (by the content provider) when elements have
2107     * been removed from the model, in order to cause the viewer to accurately
2108     * reflect the model. This method only affects the viewer, not the model.
2109     * </p>
2110     *
2111     * @param parent
2112     * the parent of the elements to remove
2113     * @param elements
2114     * the elements to remove
2115     *
2116     * @since 3.2
2117     */

2118    public void remove(final Object JavaDoc parent, final Object JavaDoc[] elements) {
2119        assertElementsNotNull(elements);
2120        if (elements.length == 0) {
2121            return;
2122        }
2123        if (isBusy())
2124            return;
2125        preservingSelection(new Runnable JavaDoc() {
2126            public void run() {
2127                internalRemove(parent, elements);
2128            }
2129        });
2130    }
2131
2132    /**
2133     * Removes the given element from the viewer. The selection is updated if
2134     * necessary.
2135     * <p>
2136     * This method should be called (by the content provider) when a single
2137     * element has been removed from the model, in order to cause the viewer to
2138     * accurately reflect the model. This method only affects the viewer, not
2139     * the model. Note that there is another method for efficiently processing
2140     * the simultaneous removal of multiple elements.
2141     * </p>
2142     *
2143     * @param elementsOrTreePaths
2144     * the element
2145     */

2146    public void remove(Object JavaDoc elementsOrTreePaths) {
2147        remove(new Object JavaDoc[] { elementsOrTreePaths });
2148    }
2149
2150    /**
2151     * Removes all items from the given control.
2152     *
2153     * @param control
2154     * the control
2155     */

2156    protected abstract void removeAll(Control control);
2157
2158    /**
2159     * Removes a listener for expand and collapse events in this viewer. Has no
2160     * affect if an identical listener is not registered.
2161     *
2162     * @param listener
2163     * a tree viewer listener
2164     */

2165    public void removeTreeListener(ITreeViewerListener listener) {
2166        treeListeners.remove(listener);
2167    }
2168
2169    /**
2170     * This implementation of reveal() reveals the given element or tree path.
2171     */

2172    public void reveal(Object JavaDoc elementOrTreePath) {
2173        Assert.isNotNull(elementOrTreePath);
2174        Widget w = internalExpand(elementOrTreePath, true);
2175        if (w instanceof Item) {
2176            showItem((Item) w);
2177        }
2178    }
2179
2180    /**
2181     * Returns the rightmost visible descendent of the given item. Returns the
2182     * item itself if it has no children.
2183     *
2184     * @param item
2185     * the item to compute the descendent of
2186     * @return the rightmost visible descendent or the item itself if it has no
2187     * children
2188     */

2189    private Item rightMostVisibleDescendent(Item item) {
2190        Item[] children = getItems(item);
2191        if (getExpanded(item) && children != null && children.length > 0) {
2192            return rightMostVisibleDescendent(children[children.length - 1]);
2193        }
2194        return item;
2195    }
2196
2197    /* (non-Javadoc) Method declared on Viewer. */
2198    public Item scrollDown(int x, int y) {
2199        Item current = getItem(x, y);
2200        if (current != null) {
2201            Item next = getNextItem(current, true);
2202            showItem(next == null ? current : next);
2203            return next;
2204        }
2205        return null;
2206    }
2207
2208    /* (non-Javadoc) Method declared on Viewer. */
2209    public Item scrollUp(int x, int y) {
2210        Item current = getItem(x, y);
2211        if (current != null) {
2212            Item previous = getPreviousItem(current);
2213            showItem(previous == null ? current : previous);
2214            return previous;
2215        }
2216        return null;
2217    }
2218
2219    /**
2220     * Sets the auto-expand level. The value 0 means that there is no
2221     * auto-expand; 1 means that top-level elements are expanded, but not their
2222     * children; 2 means that top-level elements are expanded, and their
2223     * children, but not grandchildren; and so on.
2224     * <p>
2225     * The value <code>ALL_LEVELS</code> means that all subtrees should be
2226     * expanded.
2227     * </p>
2228     *
2229     * @param level
2230     * non-negative level, or <code>ALL_LEVELS</code> to expand all
2231     * levels of the tree
2232     */

2233    public void setAutoExpandLevel(int level) {
2234        expandToLevel = level;
2235    }
2236
2237    /**
2238     * The <code>AbstractTreeViewer</code> implementation of this method
2239     * checks to ensure that the content provider is an
2240     * <code>ITreeContentProvider</code>.
2241     */

2242    public void setContentProvider(IContentProvider provider) {
2243        // the actual check is in assertContentProviderType
2244
super.setContentProvider(provider);
2245    }
2246
2247    protected void assertContentProviderType(IContentProvider provider) {
2248        Assert.isTrue(provider instanceof ITreeContentProvider
2249                || provider instanceof ITreePathContentProvider);
2250    }
2251
2252    /**
2253     * Sets the expand state of the given item.
2254     *
2255     * @param item
2256     * the item
2257     * @param expand
2258     * the expand state of the item
2259     */

2260    protected abstract void setExpanded(Item item, boolean expand);
2261
2262    /**
2263     * Sets which nodes are expanded in this viewer's tree. The given list
2264     * contains the elements that are to be expanded; all other nodes are to be
2265     * collapsed.
2266     * <p>
2267     * This method is typically used when restoring the interesting state of a
2268     * viewer captured by an earlier call to <code>getExpandedElements</code>.
2269     * </p>
2270     *
2271     * @param elements
2272     * the array of expanded elements
2273     * @see #getExpandedElements
2274     */

2275    public void setExpandedElements(Object JavaDoc[] elements) {
2276        assertElementsNotNull(elements);
2277        if (isBusy()) {
2278            return;
2279        }
2280        CustomHashtable expandedElements = newHashtable(elements.length * 2 + 1);
2281        for (int i = 0; i < elements.length; ++i) {
2282            Object JavaDoc element = elements[i];
2283            // Ensure item exists for element. This will materialize items for
2284
// each element and their parents, if possible. This is important
2285
// to support expanding of inner tree nodes without necessarily
2286
// expanding their parents.
2287
internalExpand(element, false);
2288            expandedElements.put(element, element);
2289        }
2290        // this will traverse all existing items, and create children for
2291
// elements that need to be expanded. If the tree contains multiple
2292
// equal elements, and those are in the set of elements to be expanded,
2293
// only the first item found for each element will be expanded.
2294
internalSetExpanded(expandedElements, getControl());
2295    }
2296
2297    /**
2298     * Sets which nodes are expanded in this viewer's tree. The given list
2299     * contains the tree paths that are to be expanded; all other nodes are to
2300     * be collapsed.
2301     * <p>
2302     * This method is typically used when restoring the interesting state of a
2303     * viewer captured by an earlier call to <code>getExpandedTreePaths</code>.
2304     * </p>
2305     *
2306     * @param treePaths
2307     * the array of expanded tree paths
2308     * @see #getExpandedTreePaths()
2309     *
2310     * @since 3.2
2311     */

2312    public void setExpandedTreePaths(TreePath[] treePaths) {
2313        assertElementsNotNull(treePaths);
2314        if (isBusy())
2315            return;
2316        final IElementComparer comparer = getComparer();
2317        IElementComparer treePathComparer = new IElementComparer() {
2318
2319            public boolean equals(Object JavaDoc a, Object JavaDoc b) {
2320                return ((TreePath) a).equals(((TreePath) b), comparer);
2321            }
2322
2323            public int hashCode(Object JavaDoc element) {
2324                return ((TreePath) element).hashCode(comparer);
2325            }
2326        };
2327        CustomHashtable expandedTreePaths = new CustomHashtable(
2328                treePaths.length * 2 + 1, treePathComparer);
2329        for (int i = 0; i < treePaths.length; ++i) {
2330            TreePath treePath = treePaths[i];
2331            // Ensure item exists for element. This will materialize items for
2332
// each element and their parents, if possible. This is important
2333
// to support expanding of inner tree nodes without necessarily
2334
// expanding their parents.
2335
internalExpand(treePath, false);
2336            expandedTreePaths.put(treePath, treePath);
2337        }
2338        // this will traverse all existing items, and create children for
2339
// elements that need to be expanded. If the tree contains multiple
2340
// equal elements, and those are in the set of elements to be expanded,
2341
// only the first item found for each element will be expanded.
2342
internalSetExpandedTreePaths(expandedTreePaths, getControl(),
2343                new TreePath(new Object JavaDoc[0]));
2344    }
2345
2346    /**
2347     * Sets whether the node corresponding to the given element or tree path is
2348     * expanded or collapsed.
2349     *
2350     * @param elementOrTreePath
2351     * the element
2352     * @param expanded
2353     * <code>true</code> if the node is expanded, and
2354     * <code>false</code> if collapsed
2355     */

2356    public void setExpandedState(Object JavaDoc elementOrTreePath, boolean expanded) {
2357        Assert.isNotNull(elementOrTreePath);
2358        if (isBusy())
2359            return;
2360        Widget item = internalExpand(elementOrTreePath, false);
2361        if (item instanceof Item) {
2362            if (expanded) {
2363                createChildren(item);
2364            }
2365            setExpanded((Item) item, expanded);
2366        }
2367    }
2368
2369    /**
2370     * Sets the selection to the given list of items.
2371     *
2372     * @param items
2373     * list of items (element type:
2374     * <code>org.eclipse.swt.widgets.Item</code>)
2375     */

2376    protected abstract void setSelection(List JavaDoc items);
2377
2378    /**
2379     * This implementation of setSelectionToWidget accepts a list of elements or
2380     * a list of tree paths.
2381     */

2382    protected void setSelectionToWidget(List JavaDoc v, boolean reveal) {
2383        if (v == null) {
2384            setSelection(new ArrayList JavaDoc(0));
2385            return;
2386        }
2387        int size = v.size();
2388        List JavaDoc newSelection = new ArrayList JavaDoc(size);
2389        for (int i = 0; i < size; ++i) {
2390            Object JavaDoc elementOrTreePath = v.get(i);
2391            // Use internalExpand since item may not yet be created. See
2392
// 1G6B1AR.
2393
Widget w = internalExpand(elementOrTreePath, false);
2394            if (w instanceof Item) {
2395                newSelection.add(w);
2396            } else if (w == null && elementOrTreePath instanceof TreePath) {
2397                TreePath treePath = (TreePath) elementOrTreePath;
2398                Object JavaDoc element = treePath.getLastSegment();
2399                if (element != null) {
2400                    w = internalExpand(element, false);
2401                    if (w instanceof Item) {
2402                        newSelection.add(w);
2403                    }
2404                }
2405            }
2406        }
2407        setSelection(newSelection);
2408
2409        // Although setting the selection in the control should reveal it,
2410
// setSelection may be a no-op if the selection is unchanged,
2411
// so explicitly reveal the first item in the selection here.
2412
// See bug 100565 for more details.
2413
if (reveal && newSelection.size() > 0) {
2414            showItem((Item) newSelection.get(0));
2415        }
2416    }
2417
2418    /**
2419     * Shows the given item.
2420     *
2421     * @param item
2422     * the item
2423     */

2424    protected abstract void showItem(Item item);
2425
2426    /**
2427     * Updates the tree items to correspond to the child elements of the given
2428     * parent element. If null is passed for the children, this method obtains
2429     * them (only if needed).
2430     *
2431     * @param widget
2432     * the widget
2433     * @param parent
2434     * the parent element
2435     * @param elementChildren
2436     * the child elements, or null
2437     * @deprecated this is no longer called by the framework
2438     */

2439    protected void updateChildren(Widget widget, Object JavaDoc parent,
2440            Object JavaDoc[] elementChildren) {
2441        updateChildren(widget, parent, elementChildren, true);
2442    }
2443
2444    /**
2445     * Updates the tree items to correspond to the child elements of the given
2446     * parent element. If null is passed for the children, this method obtains
2447     * them (only if needed).
2448     *
2449     * @param widget
2450     * the widget
2451     * @param parent
2452     * the parent element
2453     * @param elementChildren
2454     * the child elements, or null
2455     * @param updateLabels
2456     * <code>true</code> to update labels for existing elements,
2457     * <code>false</code> to only update labels as needed, assuming
2458     * that labels for existing elements are unchanged.
2459     * @since 2.1
2460     */

2461    private void updateChildren(Widget widget, Object JavaDoc parent,
2462            Object JavaDoc[] elementChildren, boolean updateLabels) {
2463        // optimization! prune collapsed subtrees
2464
if (widget instanceof Item) {
2465            Item ti = (Item) widget;
2466            if (!getExpanded(ti)) {
2467                // need a dummy node if element is expandable;
2468
// but try to avoid recreating the dummy node
2469
boolean needDummy = isExpandable(ti, null, parent);
2470                boolean haveDummy = false;
2471                // remove all children
2472
Item[] items = getItems(ti);
2473                for (int i = 0; i < items.length; i++) {
2474                    if (items[i].getData() != null) {
2475                        disassociate(items[i]);
2476                        items[i].dispose();
2477                    } else {
2478                        if (needDummy && !haveDummy) {
2479                            haveDummy = true;
2480                        } else {
2481                            items[i].dispose();
2482                        }
2483                    }
2484                }
2485                if (needDummy && !haveDummy) {
2486                    newItem(ti, SWT.NULL, -1);
2487                }
2488
2489                return;
2490            }
2491        }
2492
2493        // If the children weren't passed in, get them now since they're needed
2494
// below.
2495
if (elementChildren == null) {
2496            if (isTreePathContentProvider() && widget instanceof Item) {
2497                TreePath path = getTreePathFromItem((Item) widget);
2498                elementChildren = getSortedChildren(path);
2499            } else {
2500                elementChildren = getSortedChildren(parent);
2501            }
2502        }
2503
2504        Control tree = getControl();
2505
2506        // WORKAROUND
2507
int oldCnt = -1;
2508        if (widget == tree) {
2509            oldCnt = getItemCount(tree);
2510        }
2511
2512        Item[] items = getChildren(widget);
2513
2514        // save the expanded elements
2515
CustomHashtable expanded = newHashtable(CustomHashtable.DEFAULT_CAPACITY); // assume
2516
// num
2517
// expanded
2518
// is
2519
// small
2520
for (int i = 0; i < items.length; ++i) {
2521            if (getExpanded(items[i])) {
2522                Object JavaDoc element = items[i].getData();
2523                if (element != null) {
2524                    expanded.put(element, element);
2525                }
2526            }
2527        }
2528
2529        int min = Math.min(elementChildren.length, items.length);
2530
2531        // dispose of surplus items, optimizing for the case where elements have
2532
// been deleted but not reordered, or all elements have been removed.
2533
int numItemsToDispose = items.length - min;
2534        if (numItemsToDispose > 0) {
2535            CustomHashtable children = newHashtable(elementChildren.length * 2);
2536            for (int i = 0; i < elementChildren.length; i++) {
2537                Object JavaDoc elementChild = elementChildren[i];
2538                children.put(elementChild, elementChild);
2539            }
2540            int i = 0;
2541            while (numItemsToDispose > 0 && i < items.length) {
2542                Object JavaDoc data = items[i].getData();
2543                if (data == null || items.length - i <= numItemsToDispose || !children.containsKey(data)) {
2544                    if (data != null) {
2545                        disassociate(items[i]);
2546                    }
2547                    items[i].dispose();
2548                    if (i + 1 < items.length) {
2549                        // The components at positions i+1 through
2550
// items.length-1 in the source array are copied into
2551
// positions i through items.length-2
2552
System.arraycopy(items, i + 1, items, i, items.length - (i+1));
2553                    }
2554                    numItemsToDispose--;
2555                } else {
2556                    i++;
2557                }
2558            }
2559        }
2560
2561        // compare first min items, and update item if necessary
2562
// need to do it in two passes:
2563
// 1: disassociate old items
2564
// 2: associate new items
2565
// because otherwise a later disassociate can remove a mapping made for
2566
// a previous associate,
2567
// making the map inconsistent
2568
for (int i = 0; i < min; ++i) {
2569            Item item = items[i];
2570            Object JavaDoc oldElement = item.getData();
2571            if (oldElement != null) {
2572                Object JavaDoc newElement = elementChildren[i];
2573                if (newElement != oldElement) {
2574                    if (equals(newElement, oldElement)) {
2575                        // update the data to be the new element, since
2576
// although the elements
2577
// may be equal, they may still have different labels
2578
// or children
2579
Object JavaDoc data = item.getData();
2580                        if (data != null) {
2581                            unmapElement(data, item);
2582                        }
2583                        item.setData(newElement);
2584                        mapElement(newElement, item);
2585                    } else {
2586                        disassociate(item);
2587                        // Clear the text and image to force a label update
2588
item.setImage(null);
2589                        item.setText("");//$NON-NLS-1$
2590

2591                    }
2592                }
2593            }
2594        }
2595
2596        for (int i = 0; i < min; ++i) {
2597            Item item = items[i];
2598            Object JavaDoc newElement = elementChildren[i];
2599            if (item.getData() == null) {
2600                // old and new elements are not equal
2601
associate(newElement, item);
2602                updatePlus(item, newElement);
2603                updateItem(item, newElement);
2604            } else {
2605                // old and new elements are equal
2606
updatePlus(item, newElement);
2607                if (updateLabels) {
2608                    updateItem(item, newElement);
2609                }
2610            }
2611        }
2612
2613        // Restore expanded state for items that changed position.
2614
// Make sure setExpanded is called after updatePlus, since
2615
// setExpanded(false) fails if item has no children.
2616
// Need to call setExpanded for both expanded and unexpanded
2617
// cases since the expanded state can change either way.
2618
// This needs to be done in a second loop, see bug 148025.
2619
for (int i = 0; i < min; ++i) {
2620            Item item = items[i];
2621            Object JavaDoc newElement = elementChildren[i];
2622            setExpanded(item, expanded.containsKey(newElement));
2623        }
2624
2625        // add any remaining elements
2626
if (min < elementChildren.length) {
2627            for (int i = min; i < elementChildren.length; ++i) {
2628                createTreeItem(widget, elementChildren[i], i);
2629            }
2630
2631            // Need to restore expanded state in a separate pass
2632
// because createTreeItem does not return the new item.
2633
// Avoid doing this unless needed.
2634
if (expanded.size() > 0) {
2635                // get the items again, to include the new items
2636
items = getChildren(widget);
2637                for (int i = min; i < elementChildren.length; ++i) {
2638                    // Restore expanded state for items that changed position.
2639
// Make sure setExpanded is called after updatePlus (called
2640
// in createTreeItem), since
2641
// setExpanded(false) fails if item has no children.
2642
// Only need to call setExpanded if element was expanded
2643
// since new items are initially unexpanded.
2644
if (expanded.containsKey(elementChildren[i])) {
2645                        setExpanded(items[i], true);
2646                    }
2647                }
2648            }
2649        }
2650
2651        // WORKAROUND
2652
if (widget == tree && oldCnt == 0 && getItemCount(tree) != 0) {
2653            // System.out.println("WORKAROUND setRedraw");
2654
tree.setRedraw(false);
2655            tree.setRedraw(true);
2656        }
2657    }
2658
2659    /**
2660     * Updates the "+"/"-" icon of the tree node from the given element. It
2661     * calls <code>isExpandable</code> to determine whether an element is
2662     * expandable.
2663     *
2664     * @param item
2665     * the item
2666     * @param element
2667     * the element
2668     */

2669    protected void updatePlus(Item item, Object JavaDoc element) {
2670        boolean hasPlus = getItemCount(item) > 0;
2671        boolean needsPlus = isExpandable(item, null, element);
2672        boolean removeAll = false;
2673        boolean addDummy = false;
2674        Object JavaDoc data = item.getData();
2675        if (data != null && equals(element, data)) {
2676            // item shows same element
2677
if (hasPlus != needsPlus) {
2678                if (needsPlus) {
2679                    addDummy = true;
2680                } else {
2681                    removeAll = true;
2682                }
2683            }
2684        } else {
2685            // item shows different element
2686
removeAll = true;
2687            addDummy = needsPlus;
2688
2689            // we cannot maintain expand state so collapse it
2690
setExpanded(item, false);
2691        }
2692        if (removeAll) {
2693            // remove all children
2694
Item[] items = getItems(item);
2695            for (int i = 0; i < items.length; i++) {
2696                if (items[i].getData() != null) {
2697                    disassociate(items[i]);
2698                }
2699                items[i].dispose();
2700            }
2701        }
2702        if (addDummy) {
2703            newItem(item, SWT.NULL, -1); // append a dummy
2704
}
2705    }
2706
2707    /**
2708     * Gets the expanded elements that are visible to the user. An expanded
2709     * element is only visible if the parent is expanded.
2710     *
2711     * @return the visible expanded elements
2712     * @since 2.0
2713     */

2714    public Object JavaDoc[] getVisibleExpandedElements() {
2715        ArrayList JavaDoc v = new ArrayList JavaDoc();
2716        internalCollectVisibleExpanded(v, getControl());
2717        return v.toArray();
2718    }
2719
2720    private void internalCollectVisibleExpanded(ArrayList JavaDoc result, Widget widget) {
2721        Item[] items = getChildren(widget);
2722        for (int i = 0; i < items.length; i++) {
2723            Item item = items[i];
2724            if (getExpanded(item)) {
2725                Object JavaDoc data = item.getData();
2726                if (data != null) {
2727                    result.add(data);
2728                }
2729                // Only recurse if it is expanded - if
2730
// not then the children aren't visible
2731
internalCollectVisibleExpanded(result, item);
2732            }
2733        }
2734    }
2735
2736    /**
2737     * Returns the tree path for the given item.
2738     * @param item
2739     * @return {@link TreePath}
2740     *
2741     * @since 3.2
2742     */

2743    protected TreePath getTreePathFromItem(Item item) {
2744        LinkedList JavaDoc segments = new LinkedList JavaDoc();
2745        while (item != null) {
2746            Object JavaDoc segment = item.getData();
2747            Assert.isNotNull(segment);
2748            segments.addFirst(segment);
2749            item = getParentItem(item);
2750        }
2751        return new TreePath(segments.toArray());
2752    }
2753
2754    /**
2755     * This implementation of getSelection() returns an instance of
2756     * ITreeSelection.
2757     *
2758     * @since 3.2
2759     */

2760    public ISelection getSelection() {
2761        Control control = getControl();
2762        if (control == null || control.isDisposed()) {
2763            return TreeSelection.EMPTY;
2764        }
2765        Widget[] items = getSelection(getControl());
2766        ArrayList JavaDoc list = new ArrayList JavaDoc(items.length);
2767        for (int i = 0; i < items.length; i++) {
2768            Widget item = items[i];
2769            if (item.getData() != null) {
2770                list.add(getTreePathFromItem((Item) item));
2771            }
2772        }
2773        return new TreeSelection((TreePath[]) list.toArray(new TreePath[list
2774                .size()]), getComparer());
2775    }
2776
2777    protected void setSelectionToWidget(ISelection selection, boolean reveal) {
2778        if (selection instanceof ITreeSelection) {
2779            ITreeSelection treeSelection = (ITreeSelection) selection;
2780            setSelectionToWidget(Arrays.asList(treeSelection.getPaths()),
2781                    reveal);
2782        } else {
2783            super.setSelectionToWidget(selection, reveal);
2784        }
2785    }
2786
2787    /**
2788     * Returns a list of tree paths corresponding to expanded nodes in this
2789     * viewer's tree, including currently hidden ones that are marked as
2790     * expanded but are under a collapsed ancestor.
2791     * <p>
2792     * This method is typically used when preserving the interesting state of a
2793     * viewer; <code>setExpandedElements</code> is used during the restore.
2794     * </p>
2795     *
2796     * @return the array of expanded tree paths
2797     * @see #setExpandedElements
2798     *
2799     * @since 3.2
2800     */

2801    public TreePath[] getExpandedTreePaths() {
2802        ArrayList JavaDoc items = new ArrayList JavaDoc();
2803        internalCollectExpandedItems(items, getControl());
2804        ArrayList JavaDoc result = new ArrayList JavaDoc(items.size());
2805        for (Iterator JavaDoc it = items.iterator(); it.hasNext();) {
2806            Item item = (Item) it.next();
2807            TreePath treePath = getTreePathFromItem(item);
2808            if (treePath != null) {
2809                result.add(treePath);
2810            }
2811        }
2812        return (TreePath[]) result.toArray(new TreePath[items.size()]);
2813    }
2814
2815    private boolean isTreePathContentProvider() {
2816        return getContentProvider() instanceof ITreePathContentProvider;
2817    }
2818
2819    /**
2820     * Inserts the given element as a new child element of the given parent
2821     * element at the given position. If this viewer has a sorter, the position
2822     * is ignored and the element is inserted at the correct position in the
2823     * sort order.
2824     * <p>
2825     * This method should be called (by the content provider) when elements have
2826     * been added to the model, in order to cause the viewer to accurately
2827     * reflect the model. This method only affects the viewer, not the model.
2828     * </p>
2829     *
2830     * @param parentElementOrTreePath
2831     * the parent element, or the tree path to the parent
2832     * @param element
2833     * the element
2834     * @param position
2835     * a 0-based position relative to the model, or -1 to indicate
2836     * the last position
2837     *
2838     * @since 3.2
2839     */

2840    public void insert(Object JavaDoc parentElementOrTreePath, Object JavaDoc element,
2841            int position) {
2842        Assert.isNotNull(parentElementOrTreePath);
2843        Assert.isNotNull(element);
2844        if (isBusy())
2845            return;
2846        if (getComparator() != null || hasFilters()) {
2847            add(parentElementOrTreePath, new Object JavaDoc[] { element });
2848            return;
2849        }
2850        Widget[] items;
2851        if (internalIsInputOrEmptyPath(parentElementOrTreePath)) {
2852            items = new Widget[] { getControl() };
2853        } else {
2854            items = internalFindItems(parentElementOrTreePath);
2855        }
2856
2857        for (int i = 0; i < items.length; i++) {
2858            Widget widget = items[i];
2859            if (widget instanceof Item) {
2860                Item item = (Item) widget;
2861
2862                Item[] childItems = getChildren(item);
2863                if (getExpanded(item)
2864                        || (childItems.length > 0 && childItems[0].getData() != null)) {
2865                    // item has real children, go ahead and add
2866
int insertionPosition = position;
2867                    if (insertionPosition == -1) {
2868                        insertionPosition = getItemCount(item);
2869                    }
2870
2871                    createTreeItem(item, element, insertionPosition);
2872                }
2873            } else {
2874                int insertionPosition = position;
2875                if (insertionPosition == -1) {
2876                    insertionPosition = getItemCount((Control) widget);
2877                }
2878
2879                createTreeItem(widget, element, insertionPosition);
2880            }
2881        }
2882    }
2883
2884    /*
2885     * (non-Javadoc)
2886     *
2887     * @see org.eclipse.jface.viewers.ColumnViewer#getColumnViewerOwner(int)
2888     */

2889    protected Widget getColumnViewerOwner(int columnIndex) {
2890        // Return null by default
2891
return null;
2892    }
2893        
2894    /**
2895     * This implementation of {@link #getItemAt(Point)} returns null to ensure
2896     * API backwards compatibility. Subclasses should override.
2897     *
2898     * @since 3.3
2899     */

2900    protected Item getItemAt(Point point) {
2901        return null;
2902    }
2903    
2904    /**
2905     * This implementation of {@link #createViewerEditor()} returns null to ensure
2906     * API backwards compatibility. Subclasses should override.
2907     *
2908     * @since 3.3
2909     */

2910    protected ColumnViewerEditor createViewerEditor() {
2911        return null;
2912    }
2913    
2914    /**
2915     * Returns the number of columns of this viewer.
2916     * <p><b>Subclasses should overwrite this method, which has a default
2917     * implementation (returning 0) for API backwards compatility reasons</b></p>
2918     *
2919     * @return the number of columns
2920     *
2921     * @since 3.3
2922     */

2923    protected int doGetColumnCount() {
2924        return 0;
2925    }
2926    
2927    
2928    /**
2929     * This implementation of buildLabel handles tree paths as well as elements.
2930     *
2931     * @param updateLabel
2932     * the ViewerLabel to collect the result in
2933     * @param elementOrPath
2934     * the element or tree path for which a label should be built
2935     *
2936     * @see org.eclipse.jface.viewers.StructuredViewer#buildLabel(org.eclipse.jface.viewers.ViewerLabel,
2937     * java.lang.Object)
2938     */

2939    protected void buildLabel(ViewerLabel updateLabel, Object JavaDoc elementOrPath) {
2940        Object JavaDoc element;
2941        if (elementOrPath instanceof TreePath) {
2942            TreePath path = (TreePath) elementOrPath;
2943            IBaseLabelProvider provider = getLabelProvider();
2944            if (provider instanceof ITreePathLabelProvider) {
2945                ITreePathLabelProvider pprov = (ITreePathLabelProvider) provider;
2946                buildLabel(updateLabel, path, pprov);
2947                return;
2948            }
2949            element = path.getLastSegment();
2950        } else {
2951            element = elementOrPath;
2952        }
2953        super.buildLabel(updateLabel, element);
2954    }
2955    
2956    /**
2957     * Returns true if the given object is either the input or an empty tree path.
2958     *
2959     * @param elementOrTreePath an element which could either be the viewer's input, or a tree path
2960     *
2961     * @return <code>true</code> if the given object is either the input or an empty tree path,
2962     * <code>false</code> otherwise.
2963     * @since 3.3
2964     */

2965    final protected boolean internalIsInputOrEmptyPath(final Object JavaDoc elementOrTreePath) {
2966        if (elementOrTreePath.equals(getInput()))
2967            return true;
2968        if (!(elementOrTreePath instanceof TreePath))
2969            return false;
2970        return ((TreePath) elementOrTreePath).getSegmentCount() == 0;
2971    }
2972    
2973    /*
2974     * Subclasses should implement
2975     */

2976    protected ViewerRow getViewerRowFromItem(Widget item) {
2977        return null;
2978    }
2979}
2980
Popular Tags