Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/eclipse/jface/viewers/AbstractTreeViewer.java


1   /*******************************************************************************
2    * Copyright (c) 2000, 2004 IBM Corporation and others.
3    * All rights reserved. This program and the accompanying materials 
4    * are made available under the terms of the Common Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/cpl-v10.html
7    * 
8    * Contributors:
9    *     IBM Corporation - initial API and implementation
10   *******************************************************************************/
11  
12  package org.eclipse.jface.viewers;
13  
14  import java.util.ArrayList;
15  import java.util.Enumeration;
16  import java.util.List;
17  
18  import org.eclipse.core.runtime.Platform;
19  import org.eclipse.jface.util.Assert;
20  import org.eclipse.jface.util.ListenerList;
21  import org.eclipse.jface.util.SafeRunnable;
22  import org.eclipse.swt.SWT;
23  import org.eclipse.swt.custom.BusyIndicator;
24  import org.eclipse.swt.events.SelectionListener;
25  import org.eclipse.swt.events.TreeEvent;
26  import org.eclipse.swt.events.TreeListener;
27  import org.eclipse.swt.widgets.Control;
28  import org.eclipse.swt.widgets.Item;
29  import org.eclipse.swt.widgets.Widget;
30  
31  /**
32   * Abstract base implementation for tree-structure-oriented viewers (trees and
33   * table trees).
34   * <p>
35   * Nodes in the tree can be in either an expanded or a collapsed state,
36   * depending on whether the children on a node are visible. This class
37   * introduces public methods for controlling the expanding and collapsing of
38   * nodes.
39   * </p>
40   * <p>
41   * Content providers for abstract tree viewers must implement the <code>ITreeContentProvider</code>
42   * interface.
43   * </p>
44   * 
45   * @see TreeViewer
46   * @see TableTreeViewer
47   */
48  public abstract class AbstractTreeViewer extends StructuredViewer {
49  
50    /**
51     * Constant indicating that all levels of the tree should be expanded or
52     * collapsed.
53     * 
54     * @see #expandToLevel
55     * @see #collapseToLevel
56     */
57    public static final int ALL_LEVELS = -1;
58  
59    /**
60     * List of registered tree listeners (element type: <code>TreeListener</code>). */
61    private ListenerList treeListeners = new ListenerList(1);
62  
63    /**
64     * The level to which the tree is automatically expanded each time the
65     * viewer's input is changed (that is, by <code>setInput</code>). A
66     * value of 0 means that auto-expand is off.
67     * 
68     * @see #setAutoExpandLevel
69     */
70    private int expandToLevel = 0;
71  
72    /**
73     * Safe runnable used to update an item.
74     */
75    class UpdateItemSafeRunnable extends SafeRunnable {
76      private Object element;
77      private Item item;
78        UpdateItemSafeRunnable(Item item, Object element) {
79            this.item = item;
80            this.element = element;
81        }
82      public void run() {
83        doUpdateItem(item, element);
84      }
85    }
86  
87    /**
88     * Creates an abstract tree viewer. The viewer has no input, no content
89     * provider, a default label provider, no sorter, no filters, and has
90     * auto-expand turned off.
91     */
92    protected AbstractTreeViewer() {
93        // do nothing
94    }
95    /**
96     * Adds the given child elements to this viewer as children of the given
97     * parent element. If this viewer does not have a sorter, the elements are
98     * added at the end of the parent's list of children in the order given;
99     * otherwise, the elements are inserted at the appropriate positions.
100    * <p>
101    * This method should be called (by the content provider) when elements
102    * have been added to the model, in order to cause the viewer to accurately
103    * reflect the model. This method only affects the viewer, not the model.
104    * </p>
105    * 
106    * @param parentElement
107    *           the parent element
108    * @param childElements
109    *           the child elements to add
110    */
111   public void add(Object parentElement, Object[] childElements) {
112     Assert.isNotNull(parentElement);
113     assertElementsNotNull(childElements);
114     
115     Widget widget = findItem(parentElement);
116     // If parent hasn't been realized yet, just ignore the add.
117     if (widget == null)
118       return;
119 
120     Control tree = getControl();
121 
122     // optimization!
123     // if the widget is not expanded we just invalidate the subtree
124     if (widget instanceof Item) {
125       Item ti = (Item) widget;
126       if (!getExpanded(ti)) {
127         boolean needDummy = isExpandable(parentElement);
128         boolean haveDummy = false;
129         // remove all children
130         Item[] items = getItems(ti);
131         for (int i = 0; i < items.length; i++) {
132           if (items[i].getData() != null) {
133             disassociate(items[i]);
134             items[i].dispose();
135           } else {
136             if (needDummy && !haveDummy) {
137               haveDummy = true;
138             } else {
139               items[i].dispose();
140             }
141           }
142         }
143         // append a dummy if necessary
144         if (needDummy && !haveDummy) {
145           newItem(ti, SWT.NULL, -1);
146         } else {
147           // XXX: Workaround (PR missing)
148           tree.redraw();
149         }
150 
151         return;
152       }
153     }
154 
155     if (childElements.length > 0) {
156       Object[] filtered = filter(childElements);
157       for (int i = 0; i < filtered.length; i++) {
158         createAddedElement(widget,filtered[i]);        
159       }
160     }
161   }
162   
163   /**
164    * Create the new element in the parent widget. If the
165    * child already exists do nothing.
166    * @param widget
167    * @param element
168    */
169   private void createAddedElement(Widget widget, Object element){
170     
171     if(equals(element,widget.getData()))
172       return;
173     
174     Item[] items = getChildren(widget); 
175     for(int i = 0; i < items.length; i++){
176       if(items[i].getData().equals(element))
177         return;
178     }        
179     
180     int index = indexForElement(widget, element);
181     createTreeItem(widget, element, index);
182   }
183 
184   /**
185    * Returns the index where the item should be inserted.
186    * 
187    * @param parent
188    *           The parent widget the element will be inserted into.
189    * @param element
190    *           The element to insert.
191    * @return int
192    */
193   protected int indexForElement(Widget parent, Object element) {
194     ViewerSorter sorter = getSorter();
195     Item[] items = getChildren(parent);
196 
197     if (sorter == null)
198       return items.length;
199     int count = items.length;
200     int min = 0, max = count - 1;
201 
202     while (min <= max) {
203       int mid = (min + max) / 2;
204       Object data = items[mid].getData();
205       int compare = sorter.compare(this, data, element);
206       if (compare == 0) {
207         // find first item > element
208         while (compare == 0) {
209           ++mid;
210           if (mid >= count) {
211             break;
212           }
213           data = items[mid].getData();
214           compare = sorter.compare(this, data, element);
215         }
216         return mid;
217       }
218       if (compare < 0)
219         min = mid + 1;
220       else
221         max = mid - 1;
222     }
223     return min;
224   }
225   /**
226    * Adds the given child element to this viewer as a child of the given
227    * parent element. If this viewer does not have a sorter, the element is
228    * added at the end of the parent's list of children; otherwise, the
229    * element is inserted at the appropriate position.
230    * <p>
231    * This method should be called (by the content provider) when a single
232    * element has been added to the model, in order to cause the viewer to
233    * accurately reflect the model. This method only affects the viewer, not
234    * the model. Note that there is another method for efficiently processing
235    * the simultaneous addition of multiple elements.
236    * </p>
237    * 
238    * @param parentElement
239    *           the parent element
240    * @param childElement
241    *           the child element
242    */
243   public void add(Object parentElement, Object childElement) {
244     add(parentElement, new Object[] { childElement });
245   }
246   /**
247    * Adds the given SWT selection listener to the given SWT control.
248    * 
249    * @param control
250    *           the SWT control
251    * @param listener
252    *           the SWT selection listener
253    * @deprecated
254    */
255   protected void addSelectionListener(
256     Control control,
257     SelectionListener listener) {
258       // do nothing
259   }
260   /**
261    * Adds a listener for expand and collapse events in this viewer. Has no
262    * effect if an identical listener is already registered.
263    * 
264    * @param listener
265    *           a tree viewer listener
266    */
267   public void addTreeListener(ITreeViewerListener listener) {
268     treeListeners.add(listener);
269   }
270   /**
271    * Adds the given SWT tree listener to the given SWT control.
272    * 
273    * @param control
274    *           the SWT control
275    * @param listener
276    *           the SWT tree listener
277    */
278   protected abstract void addTreeListener(
279     Control control,
280     TreeListener listener);
281 
282   /* (non-Javadoc) @see StructuredViewer#associate(Object, Item) */
283   protected void associate(Object element, Item item) {
284     Object data = item.getData();
285     if (data != null && data != element && equals(data, element)) {
286       // workaround for PR 1FV62BT
287       // assumption: elements are equal but not identical
288       // -> remove from map but don't touch children
289       unmapElement(data, item);
290       item.setData(element);
291       mapElement(element, item);
292     } else {
293       // recursively disassociate all
294       super.associate(element, item);
295     }
296   }
297 
298   /**
299    * Collapses all nodes of the viewer's tree, starting with the root. This
300    * method is equivalent to <code>collapseToLevel(ALL_LEVELS)</code>.
301    */
302   public void collapseAll() {
303     Object root = getRoot();
304     if (root != null) {
305       collapseToLevel(root, ALL_LEVELS);
306     }
307   }
308   /**
309    * Collapses the subtree rooted at the given element to the given level.
310    * 
311    * @param element
312    *           the element
313    * @param level
314    *           non-negative level, or <code>ALL_LEVELS</code> to collapse
315    *           all levels of the tree
316    */
317   public void collapseToLevel(Object element, int level) {
318     Assert.isNotNull(element);
319     Widget w = findItem(element);
320     if (w != null)
321       internalCollapseToLevel(w, level);
322   }
323   /**
324    * Creates all children for the given widget.
325    * <p>
326    * The default implementation of this framework method assumes that <code>widget.getData()</code>
327    * returns the element corresponding to the node. Note: the node is not
328    * visually expanded! You may have to call <code>parent.setExpanded(true)</code>.
329    * </p>
330    * 
331    * @param widget
332    *           the widget
333    */
334   protected void createChildren(final Widget widget) {
335     final Item[] tis = getChildren(widget);
336     if (tis != null && tis.length > 0) {
337       Object data = tis[0].getData();
338       if (data != null)
339         return; // children already there!
340     }
341 
342     BusyIndicator.showWhile(widget.getDisplay(), new Runnable() {
343       public void run() {
344         // fix for PR 1FW89L7:
345         // don't complain and remove all "dummies" ...
346         if (tis != null) {
347           for (int i = 0; i < tis.length; i++) {
348             if (tis[i].getData() != null){
349               disassociate(tis[i]);
350               Assert.isTrue(tis[i].getData() == null, "Second or later child is non -null");//$NON-NLS-1$
351               
352             }
353             tis[i].dispose();
354           }
355         }
356         Object d = widget.getData();
357         if (d != null) {
358           Object parentElement = d;
359           Object[] children = getSortedChildren(parentElement);
360           for (int i = 0; i < children.length; i++) {
361             createTreeItem(widget, children[i], -1);
362           }
363         }
364       }
365     });
366   }
367   /**
368    * Creates a single item for the given parent and synchronizes it with the
369    * given element.
370    * 
371    * @param parent
372    *           the parent widget
373    * @param element
374    *           the element
375    * @param index
376    *           if non-negative, indicates the position to insert the item
377    *           into its parent
378    */
379   protected void createTreeItem(Widget parent, Object element, int index) {
380     Item item = newItem(parent, SWT.NULL, index);
381     updateItem(item, element);
382     updatePlus(item, element);
383   }
384   /**
385    * The <code>AbstractTreeViewer</code> implementation of this method also
386    * recurses over children of the corresponding element.
387    */
388   protected void disassociate(Item item) {
389     super.disassociate(item);
390     // recursively unmapping the items is only required when
391     // the hash map is used. In the other case disposing
392     // an item will recursively dispose its children.
393     if (usingElementMap())
394       disassociateChildren(item);
395   }
396   /**
397    * Disassociates the children of the given SWT item from their
398    * corresponding elements.
399    * 
400    * @param item
401    *           the widget
402    */
403   private void disassociateChildren(Item item) {
404     Item[] items = getChildren(item);
405     for (int i = 0; i < items.length; i++) {
406       if (items[i].getData() != null)
407         disassociate(items[i]);
408     }
409   }
410   /* (non-Javadoc) Method declared on StructuredViewer. */
411   protected Widget doFindInputItem(Object element) {
412     // compare with root
413     Object root = getRoot();
414     if (root == null)
415       return null;
416 
417     if (equals(root, element))
418       return getControl();
419     return null;
420   }
421   /* (non-Javadoc) Method declared on StructuredViewer. */
422   protected Widget doFindItem(Object element) {
423     // compare with root
424     Object root = getRoot();
425     if (root == null)
426       return null;
427 
428     Item[] items = getChildren(getControl());
429     if (items != null) {
430       for (int i = 0; i < items.length; i++) {
431         Widget o = internalFindItem(items[i], element);
432         if (o != null)
433           return o;
434       }
435     }
436     return null;
437   }
438   /**
439    * Copies the attributes of the given element into the given SWT item.
440    * 
441    * @param item
442    *           the SWT item
443    * @param element
444    *           the element
445    */
446   protected abstract void doUpdateItem(Item item, Object element);
447   /* (non-Javadoc) Method declared on StructuredViewer. */
448   protected void doUpdateItem(
449     Widget widget,
450     Object element,
451     boolean fullMap) {
452     if (widget instanceof Item) {
453       Item item = (Item) widget;
454 
455       // ensure that backpointer is correct
456       if (fullMap) {
457         associate(element, item);
458       } else {
459         item.setData(element);
460         mapElement(element, item);
461       }
462 
463       // update icon and label
464       Platform.run(new UpdateItemSafeRunnable(item, element));
465     }
466   }
467   /**
468    * Expands all nodes of the viewer's tree, starting with the root. This
469    * method is equivalent to <code>expandToLevel(ALL_LEVELS)</code>.
470    */
471   public void expandAll() {
472     expandToLevel(ALL_LEVELS);
473   }
474   /**
475    * Expands the root of the viewer's tree to the given level.
476    * 
477    * @param level
478    *           non-negative level, or <code>ALL_LEVELS</code> to expand all
479    *           levels of the tree
480    */
481   public void expandToLevel(int level) {
482     expandToLevel(getRoot(), level);
483   }
484   /**
485    * Expands all ancestors of the given element so that the given element
486    * becomes visible in this viewer's tree control, and then expands the
487    * subtree rooted at the given element to the given level.
488    * 
489    * @param element
490    *           the element
491    * @param level
492    *           non-negative level, or <code>ALL_LEVELS</code> to expand all
493    *           levels of the tree
494    */
495   public void expandToLevel(Object element, int level) {
496     Widget w = internalExpand(element, true);
497     if (w != null)
498       internalExpandToLevel(w, level);
499   }
500   /**
501    * Fires a tree collapsed event. Only listeners registered at the time this
502    * method is called are notified.
503    * 
504    * @param event
505    *           the tree expansion event
506    * @see ITreeViewerListener#treeCollapsed
507    */
508   protected void fireTreeCollapsed(final TreeExpansionEvent event) {
509     Object[] listeners = treeListeners.getListeners();
510     for (int i = 0; i < listeners.length; ++i) {
511       final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
512       Platform.run(new SafeRunnable() {
513         public void run() {
514           l.treeCollapsed(event);
515         }
516       });
517     }
518   }
519   /**
520    * Fires a tree expanded event. Only listeners registered at the time this
521    * method is called are notified.
522    * 
523    * @param event
524    *           the tree expansion event
525    * @see ITreeViewerListener#treeExpanded
526    */
527   protected void fireTreeExpanded(final TreeExpansionEvent event) {
528     Object[] listeners = treeListeners.getListeners();
529     for (int i = 0; i < listeners.length; ++i) {
530       final ITreeViewerListener l = (ITreeViewerListener) listeners[i];
531       Platform.run(new SafeRunnable() {
532         public void run() {
533           l.treeExpanded(event);
534         }
535       });
536     }
537 
538   }
539   /**
540    * Returns the auto-expand level.
541    * 
542    * @return non-negative level, or <code>ALL_LEVELS</code> if all levels
543    *         of the tree are expanded automatically
544    * @see #setAutoExpandLevel
545    */
546   public int getAutoExpandLevel() {
547     return expandToLevel;
548   }
549   /**
550    * Returns the SWT child items for the given SWT widget.
551    * 
552    * @param widget
553    *           the widget
554    * @return the child items
555    */
556   protected abstract Item[] getChildren(Widget widget);
557   /**
558    * Returns whether the given SWT item is expanded or collapsed.
559    * 
560    * @param item
561    *           the item
562    * @return <code>true</code> if the item is considered expanded and
563    *         <code>false</code> if collapsed
564    */
565   protected abstract boolean getExpanded(Item item);
566   /**
567    * Returns a list of elements corresponding to expanded nodes in this
568    * viewer's tree, including currently hidden ones that are marked as
569    * expanded but are under a collapsed ancestor.
570    * <p>
571    * This method is typically used when preserving the interesting state of a
572    * viewer; <code>setExpandedElements</code> is used during the restore.
573    * </p>
574    * 
575    * @return the array of expanded elements
576    * @see #setExpandedElements
577    */
578   public Object[] getExpandedElements() {
579     ArrayList v = new ArrayList();
580     internalCollectExpanded(v, getControl());
581     return v.toArray();
582   }
583   /**
584    * Returns whether the node corresponding to the given element is expanded
585    * or collapsed.
586    * 
587    * @param element
588    *           the element
589    * @return <code>true</code> if the node is expanded, and <code>false</code>
590    *         if collapsed
591    */
592   public boolean getExpandedState(Object element) {
593     Assert.isNotNull(element);
594     Widget item = findItem(element);
595     if (item instanceof Item)
596       return getExpanded((Item) item);
597     return false;
598   }
599   /**
600    * Returns the number of child items of the given SWT control.
601    * 
602    * @param control
603    *           the control
604    * @return the number of children
605    */
606   protected abstract int getItemCount(Control control);
607   /**
608    * Returns the number of child items of the given SWT item.
609    * 
610    * @param item
611    *           the item
612    * @return the number of children
613    */
614   protected abstract int getItemCount(Item item);
615   /**
616    * Returns the child items of the given SWT item.
617    * 
618    * @param item
619    *           the item
620    * @return the child items
621    */
622   protected abstract Item[] getItems(Item item);
623   /**
624    * Returns the item after the given item in the tree, or <code>null</code>
625    * if there is no next item.
626    * 
627    * @param item
628    *           the item
629    * @param includeChildren
630    *           <code>true</code> if the children are considered in
631    *           determining which item is next, and <code>false</code> if
632    *           subtrees are ignored
633    * @return the next item, or <code>null</code> if none
634    */
635   protected Item getNextItem(Item item, boolean includeChildren) {
636     if (item == null) {
637       return null;
638     }
639     if (includeChildren && getExpanded(item)) {
640       Item[] children = getItems(item);
641       if (children != null && children.length > 0) {
642         return children[0];
643       }
644     }
645 
646     //next item is either next sibling or next sibling of first
647     //parent that has a next sibling.
648     Item parent = getParentItem(item);
649     if (parent == null) {
650       return null;
651     }
652     Item[] siblings = getItems(parent);
653     if (siblings != null && siblings.length <= 1) {
654       return getNextItem(parent, false);
655     }
656     for (int i = 0; i < siblings.length; i++) {
657       if (siblings[i] == item && i < (siblings.length - 1)) {
658         return siblings[i + 1];
659       }
660     }
661     return getNextItem(parent, false);
662   }
663   /**
664    * Returns the parent item of the given item in the tree, or <code>null</code>
665    * if there is parent item.
666    * 
667    * @param item
668    *           the item
669    * @return the parent item, or <code>null</code> if none
670    */
671   protected abstract Item getParentItem(Item item);
672   /**
673    * Returns the item before the given item in the tree, or <code>null</code>
674    * if there is no previous item.
675    * 
676    * @param item
677    *           the item
678    * @return the previous item, or <code>null</code> if none
679    */
680   protected Item getPreviousItem(Item item) {
681     //previous item is either right-most visible descendent of previous
682     //sibling or parent
683     Item parent = getParentItem(item);
684     if (parent == null) {
685       return null;
686     }
687     Item[] siblings = getItems(parent);
688     if (siblings.length == 0 || siblings[0] == item) {
689       return parent;
690     }
691     Item previous = siblings[0];
692     for (int i = 1; i < siblings.length; i++) {
693       if (siblings[i] == item) {
694         return rightMostVisibleDescendent(previous);
695       }
696       previous = siblings[i];
697     }
698     return null;
699   }
700   /* (non-Javadoc) Method declared on StructuredViewer. */
701   protected Object[] getRawChildren(Object parent) {
702     if (parent != null) {
703       if (equals(parent, getRoot()))
704         return super.getRawChildren(parent);
705       ITreeContentProvider cp =
706         (ITreeContentProvider) getContentProvider();
707       if (cp != null) {
708         Object[] result = cp.getChildren(parent);
709         if (result != null)
710           return result;
711       }
712     }
713     return new Object[0];
714   }
715   /**
716    * Returns all selected items for the given SWT control.
717    * 
718    * @param control
719    *           the control
720    * @return the list of selected items
721    */
722   protected abstract Item[] getSelection(Control control);
723   /* (non-Javadoc) Method declared on StructuredViewer. */
724   protected List getSelectionFromWidget() {
725     Widget[] items = getSelection(getControl());
726     ArrayList list = new ArrayList(items.length);
727     for (int i = 0; i < items.length; i++) {
728       Widget item = items[i];
729       Object e = item.getData();
730       if (e != null)
731         list.add(e);
732     }
733     return list;
734   }
735   /**
736    * Handles a tree collapse event from the SWT widget.
737    * 
738    * @param event
739    *           the SWT tree event
740    */
741   protected void handleTreeCollapse(TreeEvent event) {
742     if (event.item.getData() != null) {
743       fireTreeCollapsed(
744         new TreeExpansionEvent(this, event.item.getData()));
745     }
746   }
747   /**
748    * Handles a tree expand event from the SWT widget.
749    * 
750    * @param event
751    *           the SWT tree event
752    */
753   protected void handleTreeExpand(TreeEvent event) {
754     createChildren(event.item);
755     if (event.item.getData() != null) {
756       fireTreeExpanded(
757         new TreeExpansionEvent(this, event.item.getData()));
758     }
759   }
760   /* (non-Javadoc) Method declared on Viewer. */
761   protected void hookControl(Control control) {
762     super.hookControl(control);
763     addTreeListener(control, new TreeListener() {
764       public void treeExpanded(TreeEvent event) {
765         handleTreeExpand(event);
766       }
767       public void treeCollapsed(TreeEvent event) {
768         handleTreeCollapse(event);
769       }
770     });
771   }
772   /*
773    * (non-Javadoc) Method declared on StructuredViewer. Builds the initial
774    * tree and handles the automatic expand feature.
775    */
776   protected void inputChanged(Object input, Object oldInput) {
777     preservingSelection(new Runnable() {
778       public void run() {
779         Control tree = getControl();
780         boolean useRedraw = true;
781         // (size > REDRAW_THRESHOLD) || (table.getItemCount() >
782         // REDRAW_THRESHOLD);
783         if (useRedraw)
784           tree.setRedraw(false);
785         removeAll(tree);
786         tree.setData(getRoot());
787         createChildren(tree);
788         internalExpandToLevel(tree, expandToLevel);
789         if (useRedraw)
790           tree.setRedraw(true);
791       }
792     });
793   }
794   /**
795    * Recursively collapses the subtree rooted at the given widget to the
796    * given level.
797    * <p>
798    * </p>
799    * Note that the default implementation of this method does not call <code>setRedraw</code>.
800    * 
801    * @param widget
802    *           the widget
803    * @param level
804    *           non-negative level, or <code>ALL_LEVELS</code> to collapse
805    *           all levels of the tree
806    */
807   protected void internalCollapseToLevel(Widget widget, int level) {
808     if (level == ALL_LEVELS || level > 0) {
809 
810       if (widget instanceof Item)
811         setExpanded((Item) widget, false);
812 
813       if (level == ALL_LEVELS || level > 1) {
814         Item[] children = getChildren(widget);
815         if (children != null) {
816           int nextLevel =
817             (level == ALL_LEVELS ? ALL_LEVELS : level - 1);
818           for (int i = 0; i < children.length; i++)
819             internalCollapseToLevel(children[i], nextLevel);
820         }
821       }
822     }
823   }
824   /**
825    * Recursively collects all expanded elements from the given widget.
826    * 
827    * @param result
828    *           a list (element type: <code>Object</code>) into which to
829    *           collect the elements
830    * @param widget
831    *           the widget
832    */
833   private void internalCollectExpanded(List result, Widget widget) {
834     Item[] items = getChildren(widget);
835     for (int i = 0; i < items.length; i++) {
836       Item item = items[i];
837       if (getExpanded(item)) {
838         Object data = item.getData();
839         if (data != null)
840           result.add(data);
841       }
842       internalCollectExpanded(result, item);
843     }
844   }
845   /**
846    * Tries to create a path of tree items for the given element. This method
847    * recursively walks up towards the root of the tree and assumes that
848    * <code>getParent</code> returns the correct parent of an element.
849    * 
850    * @param element
851    *           the element
852    * @param expand
853    *           <code>true</code> if all nodes on the path should be
854    *           expanded, and <code>false</code> otherwise
855    * @return Widget
856    */
857   protected Widget internalExpand(Object element, boolean expand) {
858 
859     if (element == null)
860       return null;
861 
862     Widget w = findItem(element);
863     if (w == null) {
864       if (equals(element, getRoot())) { // stop at root
865         return null;
866       }
867       // my parent has to create me
868       ITreeContentProvider cp =
869         (ITreeContentProvider) getContentProvider();
870       if (cp == null) {
871         return null;
872       }
873       Object parent = cp.getParent(element);
874       if (parent != null) {
875         Widget pw = internalExpand(parent, expand);
876         if (pw != null) {
877           // let my parent create me
878           createChildren(pw);
879           // expand parent and find me
880           if (pw instanceof Item) {
881             Item item = (Item) pw;
882             if (expand)
883               setExpanded(item, true);
884             w = internalFindChild(item, element);
885           }
886         }
887       }
888     }
889     return w;
890   }
891   /**
892    * Recursively expands the subtree rooted at the given widget to the given
893    * level.
894    * <p>
895    * </p>
896    * Note that the default implementation of this method does not call <code>setRedraw</code>.
897    * 
898    * @param widget
899    *           the widget
900    * @param level
901    *           non-negative level, or <code>ALL_LEVELS</code> to collapse
902    *           all levels of the tree
903    */
904   protected void internalExpandToLevel(Widget widget, int level) {
905     if (level == ALL_LEVELS || level > 0) {
906       createChildren(widget);
907       if (widget instanceof Item)
908         setExpanded((Item) widget, true);
909       if (level == ALL_LEVELS || level > 1) {
910         Item[] children = getChildren(widget);
911         if (children != null) {
912           int newLevel =
913             (level == ALL_LEVELS ? ALL_LEVELS : level - 1);
914           for (int i = 0; i < children.length; i++)
915             internalExpandToLevel(children[i], newLevel);
916         }
917       }
918     }
919   }
920   /**
921    * Non-recursively tries to find the given element as a child of the given
922    * parent item.
923    * 
924    * @param parent
925    *           the parent item
926    * @param element
927    *           the element
928    * @return Widget
929    */
930   private Widget internalFindChild(Item parent, Object element) {
931     Item[] items = getChildren(parent);
932     for (int i = 0; i < items.length; i++) {
933       Item item = items[i];
934       Object data = item.getData();
935       if (data != null && equals(data, element))
936         return item;
937     }
938     return null;
939   }
940   /**
941    * Recursively tries to find the given element.
942    * 
943    * @param parent
944    *           the parent item
945    * @param element
946    *           the element
947    * @return Widget
948    */
949   private Widget internalFindItem(Item parent, Object element) {
950 
951     // compare with node
952     Object data = parent.getData();
953     if (data != null) {
954       if (equals(data, element))
955         return parent;
956     }
957     // recurse over children
958     Item[] items = getChildren(parent);
959     for (int i = 0; i < items.length; i++) {
960       Item item = items[i];
961       Widget o = internalFindItem(item, element);
962       if (o != null)
963         return o;
964     }
965     return null;
966   }
967   /* (non-Javadoc) Method declared on StructuredViewer. */
968   protected void internalRefresh(Object element) {
969     internalRefresh(element, true);
970   }
971   /* (non-Javadoc) Method declared on StructuredViewer. */
972   protected void internalRefresh(Object element, boolean updateLabels) {
973     // If element is null, do a full refresh.
974     if (element == null) {
975       internalRefresh(getControl(), getRoot(), true, updateLabels);
976       return;
977     }
978     Widget item = findItem(element);
979     if (item != null) {
980       // pick up structure changes too
981       internalRefresh(item, element, true, updateLabels);
982     }
983   }
984   /**
985    * Refreshes the tree starting at the given widget.
986    * 
987    * @param widget
988    *           the widget
989    * @param element
990    *           the element
991    * @param doStruct
992    *           <code>true</code> if structural changes are to be picked up,
993    *           and <code>false</code> if only label provider changes are of
994    *           interest
995    * @param updateLabels
996    *           <code>true</code> to update labels for existing elements,
997    *           <code>false</code> to only update labels as needed, assuming
998    *           that labels for existing elements are unchanged.
999    */
1000  private void internalRefresh(
1001    Widget widget,
1002    Object element,
1003    boolean doStruct,
1004    boolean updateLabels) {
1005
1006    if (widget instanceof Item) {
1007      if (doStruct) {
1008        updatePlus((Item) widget, element);
1009      }
1010      if (updateLabels || !equals(element, widget.getData())) {
1011        doUpdateItem(widget, element, true);
1012      } else {
1013        associate(element, (Item) widget);
1014      }
1015    }
1016
1017    if (doStruct) {
1018      internalRefreshStruct(widget, element, updateLabels);
1019    } else {
1020      Item[] children = getChildren(widget);
1021      if (children != null) {
1022        for (int i = 0; i < children.length; i++) {
1023          Widget item = children[i];
1024          Object data = item.getData();
1025          if (data != null)
1026            internalRefresh(item, data, doStruct, updateLabels);
1027        }
1028      }
1029    }
1030  }
1031
1032  /**
1033   * Update the structure and recurse. Items are updated in updateChildren,
1034   * as needed.
1035   * @param widget
1036   * @param element
1037   * @param updateLabels
1038   */
1039  private void internalRefreshStruct(
1040    Widget widget,
1041    Object element,
1042    boolean updateLabels) {
1043    updateChildren(widget, element, null, updateLabels);
1044    Item[] children = getChildren(widget);
1045    if (children != null) {
1046      for (int i = 0; i < children.length; i++) {
1047        Widget item = children[i];
1048        Object data = item.getData();
1049        if (data != null)
1050          internalRefreshStruct(item, data, updateLabels);
1051      }
1052    }
1053  }
1054
1055  /**
1056   * Removes the given elements from this viewer.
1057   * 
1058   * @param elements
1059   *           the elements to remove
1060   */
1061  private void internalRemove(Object[] elements) {
1062    Object input = getInput();
1063    // Note: do not use the comparer here since the hashtable
1064    // contains SWT Items, not model elements.
1065    CustomHashtable parentItems = new CustomHashtable(5);
1066    for (int i = 0; i < elements.length; ++i) {
1067      if (equals(elements[i], input)) {
1068        setInput(null);
1069        return;
1070      }
1071      Widget childItem = findItem(elements[i]);
1072      if (childItem instanceof Item) {
1073        Item parentItem = getParentItem((Item) childItem);
1074        if (parentItem != null) {
1075          parentItems.put(parentItem, parentItem);
1076        }
1077        disassociate((Item) childItem);
1078        childItem.dispose();
1079      }
1080    }
1081    Control tree = getControl();
1082    for (Enumeration e = parentItems.keys(); e.hasMoreElements();) {
1083      Item parentItem = (Item) e.nextElement();
1084      if (!getExpanded(parentItem) && getItemCount(parentItem) == 0) {
1085        // append a dummy if necessary
1086        if (isExpandable(parentItem.getData())) {
1087          newItem(parentItem, SWT.NULL, -1);
1088        } else {
1089          // XXX: Workaround (PR missing)
1090          tree.redraw();
1091        }
1092      }
1093    }
1094  }
1095  /**
1096   * Sets the expanded state of all items to correspond to the given set of
1097   * expanded elements.
1098   * 
1099   * @param expandedElements
1100   *           the set (element type: <code>Object</code>) of elements
1101   *           which are expanded
1102   * @param widget
1103   *           the widget
1104   */
1105  private void internalSetExpanded(CustomHashtable expandedElements, Widget widget) {
1106    Item[] items = getChildren(widget);
1107    for (int i = 0; i < items.length; i++) {
1108      Item item = items[i];
1109      Object data = item.getData();
1110      if (data != null) {
1111        // remove the element to avoid an infinite loop
1112        // if the same element appears on a child item
1113        boolean expanded = expandedElements.remove(data) != null;
1114        if (expanded != getExpanded(item)) {
1115          if (expanded) {
1116            createChildren(item);
1117          }
1118          setExpanded(item, expanded);
1119        }
1120      }
1121      internalSetExpanded(expandedElements, item);
1122    }
1123  }
1124  /**
1125   * Return whether the tree node representing the given element can be
1126   * expanded.
1127   * <p>
1128   * The default implementation of this framework method calls <code>hasChildren</code>
1129   * on this viewer's content provider. It may be overridden if necessary.
1130   * </p>
1131   * 
1132   * @param element
1133   *           the element
1134   * @return <code>true</code> if the tree node representing the given
1135   *         element can be expanded, or <code>false</code> if not
1136   */
1137  public boolean isExpandable(Object element) {
1138    ITreeContentProvider cp = (ITreeContentProvider) getContentProvider();
1139    return cp != null && cp.hasChildren(element);
1140  }
1141  /* (non-Javadoc) Method declared on Viewer. */
1142  protected void labelProviderChanged() {
1143    // we have to walk the (visible) tree and update every item
1144    Control tree = getControl();
1145    tree.setRedraw(false);
1146    // don't pick up structure changes, but do force label updates
1147    internalRefresh(tree, getRoot(), false, true);
1148    tree.setRedraw(true);
1149  }
1150  /**
1151   * Creates a new item.
1152   * 
1153   * @param parent
1154   *           the parent widget
1155   * @param style
1156   *           SWT style bits
1157   * @param index
1158   *           if non-negative, indicates the position to insert the item
1159   *           into its parent
1160   * @return the newly-created item
1161   */
1162  protected abstract Item newItem(Widget parent, int style, int index);
1163  /**
1164   * Removes the given elements from this viewer. The selection is updated if
1165   * required.
1166   * <p>
1167   * This method should be called (by the content provider) when elements
1168   * have been removed from the model, in order to cause the viewer to
1169   * accurately reflect the model. This method only affects the viewer, not
1170   * the model.
1171   * </p>
1172   * 
1173   * @param elements
1174   *           the elements to remove
1175   */
1176  public void remove(final Object[] elements) {
1177    assertElementsNotNull(elements);
1178    preservingSelection(new Runnable() {
1179      public void run() {
1180        internalRemove(elements);
1181      }
1182    });
1183  }
1184  /**
1185   * Removes the given element from the viewer. The selection is updated if
1186   * necessary.
1187   * <p>
1188   * This method should be called (by the content provider) when a single
1189   * element has been removed from the model, in order to cause the viewer to
1190   * accurately reflect the model. This method only affects the viewer, not
1191   * the model. Note that there is another method for efficiently processing
1192   * the simultaneous removal of multiple elements.
1193   * </p>
1194   * 
1195   * @param element
1196   *           the element
1197   */
1198  public void remove(Object element) {
1199    remove(new Object[] { element });
1200  }
1201  /**
1202   * Removes all items from the given control.
1203   * 
1204   * @param control
1205   *           the control
1206   */
1207  protected abstract void removeAll(Control control);
1208  /**
1209   * Removes a listener for expand and collapse events in this viewer. Has no
1210   * affect if an identical listener is not registered.
1211   * 
1212   * @param listener
1213   *           a tree viewer listener
1214   */
1215  public void removeTreeListener(ITreeViewerListener listener) {
1216    treeListeners.remove(listener);
1217  }
1218  /* Non-Javadoc. Method defined on StructuredViewer. */
1219  public void reveal(Object element) {
1220    Assert.isNotNull(element);
1221    Widget w = internalExpand(element, true);
1222    if (w instanceof Item)
1223      showItem((Item) w);
1224  }
1225  /**
1226   * Returns the rightmost visible descendent of the given item. Returns the
1227   * item itself if it has no children.
1228   * 
1229   * @param item
1230   *           the item to compute the descendent of
1231   * @return the rightmost visible descendent or the item iself if it has no
1232   *         children
1233   */
1234  private Item rightMostVisibleDescendent(Item item) {
1235    Item[] children = getItems(item);
1236    if (getExpanded(item) && children != null && children.length > 0) {
1237      return rightMostVisibleDescendent(children[children.length - 1]);
1238    } 
1239    return item;
1240  }
1241  /* (non-Javadoc) Method declared on Viewer. */
1242  public Item scrollDown(int x, int y) {
1243    Item current = getItem(x, y);
1244    if (current != null) {
1245      Item next = getNextItem(current, true);
1246      showItem(next == null ? current : next);
1247      return next;
1248    }
1249    return null;
1250  }
1251  /* (non-Javadoc) Method declared on Viewer. */
1252  public Item scrollUp(int x, int y) {
1253    Item current = getItem(x, y);
1254    if (current != null) {
1255      Item previous = getPreviousItem(current);
1256      showItem(previous == null ? current : previous);
1257      return previous;
1258    }
1259    return null;
1260  }
1261  /**
1262   * Sets the auto-expand level. The value 0 means that there is no
1263   * auto-expand; 1 means that top-level elements are expanded, but not their
1264   * children; 2 means that top-level elements are expanded, and their
1265   * children, but not grandchildren; and so on.
1266   * <p>
1267   * The value <code>ALL_LEVELS</code> means that all subtrees should be
1268   * expanded.
1269   * </p>
1270   * 
1271   * @param level
1272   *           non-negative level, or <code>ALL_LEVELS</code> to expand all
1273   *           levels of the tree
1274   */
1275  public void setAutoExpandLevel(int level) {
1276    expandToLevel = level;
1277  }
1278  /**
1279   * The <code>AbstractTreeViewer</code> implementation of this method
1280   * checks to ensure that the content provider is an <code>ITreeContentProvider</code>.
1281   */
1282  public void setContentProvider(IContentProvider provider) {
1283    Assert.isTrue(provider instanceof ITreeContentProvider);
1284    super.setContentProvider(provider);
1285  }
1286  /**
1287   * Sets the expand state of the given item.
1288   * 
1289   * @param item
1290   *           the item
1291   * @param expand
1292   *           the expand state of the item
1293   */
1294  protected abstract void setExpanded(Item item, boolean expand);
1295  /**
1296   * Sets which nodes are expanded in this viewer's tree. The given list
1297   * contains the elements that are to be expanded; all other nodes are to be
1298   * collapsed.
1299   * <p>
1300   * This method is typically used when restoring the interesting state of a
1301   * viewer captured by an earlier call to <code>getExpandedElements</code>.
1302   * </p>
1303   * 
1304   * @param elements
1305   *           the array of expanded elements
1306   * @see #getExpandedElements
1307   */
1308  public void setExpandedElements(Object[] elements) {
1309    assertElementsNotNull(elements);
1310    CustomHashtable expandedElements = newHashtable(elements.length * 2 + 1);
1311    for (int i = 0; i < elements.length; ++i) {
1312        Object element = elements[i];
1313      // Ensure item exists for element
1314      internalExpand(element, false);
1315      expandedElements.put(element, element);
1316    }
1317    internalSetExpanded(expandedElements, getControl());
1318  }
1319  /**
1320   * Sets whether the node corresponding to the given element is expanded or
1321   * collapsed.
1322   * 
1323   * @param element
1324   *           the element
1325   * @param expanded
1326   *           <code>true</code> if the node is expanded, and <code>false</code>
1327   *           if collapsed
1328   */
1329  public void setExpandedState(Object element, boolean expanded) {
1330    Assert.isNotNull(element);
1331    Widget item = internalExpand(element, false);
1332    if (item instanceof Item) {
1333      if (expanded) {
1334        createChildren(item);
1335      }
1336      setExpanded((Item) item, expanded);
1337    }
1338  }
1339  /**
1340   * Sets the selection to the given list of items.
1341   * 
1342   * @param items
1343   *           list of items (element type: <code>org.eclipse.swt.widgets.Item</code>)
1344   */
1345  protected abstract void setSelection(List items);
1346  /* (non-Javadoc) Method declared on StructuredViewer. */
1347  protected void setSelectionToWidget(List v, boolean reveal) {
1348    if (v == null) {
1349      setSelection(new ArrayList(0));
1350      return;
1351    }
1352    int size = v.size();
1353    List newSelection = new ArrayList(size);
1354    for (int i = 0; i < size; ++i) {
1355      // Use internalExpand since item may not yet be created. See
1356      // 1G6B1AR.
1357      Widget w = internalExpand(v.get(i), true);
1358      if (w instanceof Item) {
1359        newSelection.add(w);
1360      }
1361    }
1362    setSelection(newSelection);
1363    if (reveal && newSelection.size() > 0) {
1364      showItem((Item) newSelection.get(0));
1365    }
1366  }
1367  /**
1368   * Shows the given item.
1369   * 
1370   * @param item
1371   *           the item
1372   */
1373  protected abstract void showItem(Item item);
1374
1375  /**
1376   * Updates the tree items to correspond to the child elements of the given
1377   * parent element. If null is passed for the children, this method obtains
1378   * them (only if needed).
1379   * 
1380   * @param widget
1381   *           the widget
1382   * @param parent
1383   *           the parent element
1384   * @param elementChildren
1385   *           the child elements, or null
1386   * @deprecated this is no longer called by the framework
1387   */
1388  protected void updateChildren(
1389    Widget widget,
1390    Object parent,
1391    Object[] elementChildren) {
1392    updateChildren(widget, parent, elementChildren, true);
1393  }
1394
1395  /**
1396   * Updates the tree items to correspond to the child elements of the given
1397   * parent element. If null is passed for the children, this method obtains
1398   * them (only if needed).
1399   * 
1400   * @param widget
1401   *           the widget
1402   * @param parent
1403   *           the parent element
1404   * @param elementChildren
1405   *           the child elements, or null
1406   * @param updateLabels
1407   *           <code>true</code> to update labels for existing elements,
1408   *           <code>false</code> to only update labels as needed, assuming
1409   *           that labels for existing elements are unchanged.
1410   * @since 2.1
1411   */
1412  private void updateChildren(
1413    Widget widget,
1414    Object parent,
1415    Object[] elementChildren,
1416    boolean updateLabels) {
1417    // optimization! prune collapsed subtrees
1418    if (widget instanceof Item) {
1419      Item ti = (Item) widget;
1420      if (!getExpanded(ti)) {
1421        // need a dummy node if element is expandable;
1422        // but try to avoid recreating the dummy node
1423        boolean needDummy = isExpandable(parent);
1424        boolean haveDummy = false;
1425        // remove all children
1426        Item[] items = getItems(ti);
1427        for (int i = 0; i < items.length; i++) {
1428          if (items[i].getData() != null) {
1429            disassociate(items[i]);
1430            items[i].dispose();
1431          } else {
1432            if (needDummy && !haveDummy) {
1433              haveDummy = true;
1434            } else {
1435              items[i].dispose();
1436            }
1437          }
1438        }
1439        if (needDummy && !haveDummy) {
1440          newItem(ti, SWT.NULL, -1);
1441        }
1442
1443        return;
1444      }
1445    }
1446
1447    // If the children weren't passed in, get them now since they're needed
1448    // below.
1449    if (elementChildren == null) {
1450      elementChildren = getSortedChildren(parent);
1451    }
1452
1453    Control tree = getControl();
1454
1455    // WORKAROUND
1456    int oldCnt = -1;
1457    if (widget == tree)
1458      oldCnt = getItemCount(tree);
1459
1460    Item[] items = getChildren(widget);
1461
1462    // save the expanded elements
1463    CustomHashtable expanded = newHashtable(CustomHashtable.DEFAULT_CAPACITY); // assume num expanded is small
1464    for (int i = 0; i < items.length; ++i) {
1465      if (getExpanded(items[i])) {
1466        Object element = items[i].getData();
1467        if (element != null) {
1468          expanded.put(element, element);
1469        }
1470      }
1471    }
1472
1473    int min = Math.min(elementChildren.length, items.length);
1474
1475    // Note: In the code below, doing disassociate calls before associate
1476    // calls is important,
1477    // since a later disassociate can undo an earlier associate,
1478    // if items are changing position.
1479
1480    // dispose of all items beyond the end of the current elements
1481    for (int i = items.length; --i >= min;) {
1482      if (items[i].getData() != null) {
1483        disassociate(items[i]);
1484      }
1485      items[i].dispose();
1486    }
1487
1488    // compare first min items, and update item if necessary
1489    // need to do it in two passes:
1490    // 1: disassociate old items
1491    // 2: associate new items
1492    // because otherwise a later disassociate can remove a mapping made for
1493    // a previous associate,
1494    // making the map inconsistent
1495    for (int i = 0; i < min; ++i) {
1496      Item item = items[i];
1497      Object oldElement = item.getData();
1498      if (oldElement != null) {
1499        Object newElement = elementChildren[i];
1500        if (newElement != oldElement) {
1501          if (equals(newElement, oldElement)) {
1502            // update the data to be the new element, since
1503            // although the elements
1504            // may be equal, they may still have different labels
1505            // or children
1506            item.setData(newElement);
1507            mapElement(newElement, item);
1508          } else {
1509            disassociate(item);
1510            //Clear the text and image to force a label update
1511            item.setImage(null);
1512            item.setText("");//$NON-NLS-1$
1513            
1514          }
1515        }
1516      }
1517    }
1518    for (int i = 0; i < min; ++i) {
1519      Item item = items[i];
1520      Object newElement = elementChildren[i];
1521      if (item.getData() == null) {
1522        // old and new elements are not equal
1523        associate(newElement, item);
1524        updatePlus(item, newElement);
1525        updateItem(item, newElement);
1526        // Restore expanded state for items that changed position.
1527        // Make sure setExpanded is called after updatePlus, since
1528        // setExpanded(false) fails if item has no children.
1529        // Need to call setExpanded for both expanded and unexpanded
1530        // cases
1531        // since the expanded state can change either way.
1532        setExpanded(item, expanded.containsKey(newElement));
1533      } else {
1534        // old and new elements are equal
1535        updatePlus(item, newElement);
1536        if (updateLabels) {
1537          updateItem(item, newElement);
1538        }
1539      }
1540    }
1541
1542    // add any remaining elements
1543    if (min < elementChildren.length) {
1544      for (int i = min; i < elementChildren.length; ++i) {
1545        createTreeItem(widget, elementChildren[i], i);
1546      }
1547
1548      // Need to restore expanded state in a separate pass
1549      // because createTreeItem does not return the new item.
1550      // Avoid doing this unless needed.
1551      if (expanded.size() > 0) {
1552        // get the items again, to include the new items
1553        items = getChildren(widget);
1554        for (int i = min; i < elementChildren.length; ++i) {
1555          // Restore expanded state for items that changed position.
1556          // Make sure setExpanded is called after updatePlus (called
1557          // in createTreeItem), since
1558          // setExpanded(false) fails if item has no children.
1559          // Only need to call setExpanded if element was expanded
1560          // since new items are initially unexpanded.
1561          if (expanded.containsKey(elementChildren[i])) {
1562            setExpanded(items[i], true);
1563          }
1564        }
1565      }
1566    }
1567
1568    // WORKAROUND
1569    if (widget == tree && oldCnt == 0 && getItemCount(tree) != 0) {
1570      //System.out.println("WORKAROUND setRedraw");
1571      tree.setRedraw(false);
1572      tree.setRedraw(true);
1573    }
1574  }
1575  /**
1576   * Updates the "+"/"-" icon of the tree node from the given element. It
1577   * calls <code>isExpandable</code> to determine whether an element is
1578   * expandable.
1579   * 
1580   * @param item
1581   *           the item
1582   * @param element
1583   *           the element
1584   */
1585  protected void updatePlus(Item item, Object element) {
1586    boolean hasPlus = getItemCount(item) > 0;
1587    boolean needsPlus = isExpandable(element);
1588    boolean removeAll = false;
1589    boolean addDummy = false;
1590    Object data = item.getData();
1591    if (data != null && equals(element, data)) {
1592      // item shows same element
1593      if (hasPlus != needsPlus) {
1594        if (needsPlus)
1595          addDummy = true;
1596        else
1597          removeAll = true;
1598      }
1599    } else {
1600      // item shows different element
1601      removeAll = true;
1602      addDummy = needsPlus;
1603
1604      // we cannot maintain expand state so collapse it
1605      setExpanded(item, false);
1606    }
1607    if (removeAll) {
1608      // remove all children
1609      Item[] items = getItems(item);
1610      for (int i = 0; i < items.length; i++) {
1611        if (items[i].getData() != null)
1612          disassociate(items[i]);
1613        items[i].dispose();
1614      }
1615    }
1616    if (addDummy)
1617      newItem(item, SWT.NULL, -1); // append a dummy
1618  }
1619
1620  /**
1621   * Gets the expanded elements that are visible to the user. An expanded
1622   * element is only visible if the parent is expanded.
1623   * 
1624   * @return the visible expanded elements
1625   * @since 2.0
1626   */
1627  public Object[] getVisibleExpandedElements() {
1628    ArrayList v = new ArrayList();
1629    internalCollectVisibleExpanded(v, getControl());
1630    return v.toArray();
1631  }
1632
1633  private void internalCollectVisibleExpanded(
1634    ArrayList result,
1635    Widget widget) {
1636    Item[] items = getChildren(widget);
1637    for (int i = 0; i < items.length; i++) {
1638      Item item = items[i];
1639      if (getExpanded(item)) {
1640        Object data = item.getData();
1641        if (data != null)
1642          result.add(data);
1643        //Only recurse if it is expanded - if
1644        //not then the children aren't visible
1645        internalCollectVisibleExpanded(result, item);
1646      }
1647    }
1648  }
1649}