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}