diff dwtx/jface/viewers/AbstractTreeViewer.d @ 10:b6c35faf97c8

Viewers
author Frank Benoit <benoit@tionex.de>
date Mon, 31 Mar 2008 00:47:19 +0200
parents
children 644f1334b451
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/viewers/AbstractTreeViewer.d	Mon Mar 31 00:47:19 2008 +0200
@@ -0,0 +1,3026 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Tom Schindl <tom.schindl@bestsolution.at> - bug 153993, bug 167323, bug 175192
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+
+module dwtx.jface.viewers.AbstractTreeViewer;
+
+import dwtx.jface.viewers.ColumnViewer;
+import dwtx.jface.viewers.TreePath;
+import dwtx.jface.viewers.ViewerComparator;
+import dwtx.jface.viewers.ITreeViewerListener;
+import dwtx.jface.viewers.TreeExpansionEvent;
+import dwtx.jface.viewers.CustomHashtable;
+import dwtx.jface.viewers.IContentProvider;
+import dwtx.jface.viewers.ISelection;
+import dwtx.jface.viewers.ColumnViewerEditor;
+import dwtx.jface.viewers.ViewerLabel;
+import dwtx.jface.viewers.ViewerRow;
+import dwtx.jface.viewers.TreePathViewerSorter;
+import dwtx.jface.viewers.ViewerFilter;
+import dwtx.jface.viewers.ViewerColumn;
+import dwtx.jface.viewers.ViewerCell;
+import dwtx.jface.viewers.ITreePathContentProvider;
+import dwtx.jface.viewers.ITreeContentProvider;
+import dwtx.jface.viewers.TreeSelection;
+import dwtx.jface.viewers.DoubleClickEvent;
+import dwtx.jface.viewers.IElementComparer;
+import dwtx.jface.viewers.ITreeSelection;
+import dwtx.jface.viewers.IBaseLabelProvider;
+import dwtx.jface.viewers.ITreePathLabelProvider;
+
+import tango.util.collection.LinkSeq;
+import tango.util.collection.ArraySeq;
+import tango.util.collection.model.Seq;
+import tango.util.collection.model.SeqView;
+
+
+import dwt.DWT;
+import dwt.custom.BusyIndicator;
+import dwt.events.SelectionEvent;
+import dwt.events.SelectionListener;
+import dwt.events.TreeEvent;
+import dwt.events.TreeListener;
+import dwt.graphics.Point;
+import dwt.widgets.Control;
+import dwt.widgets.Item;
+import dwt.widgets.Widget;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.ListenerList;
+import dwtx.jface.util.SafeRunnable;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+
+/**
+ * Abstract base implementation for tree-structure-oriented viewers (trees and
+ * table trees).
+ * <p>
+ * Nodes in the tree can be in either an expanded or a collapsed state,
+ * depending on whether the children on a node are visible. This class
+ * introduces public methods for controlling the expanding and collapsing of
+ * nodes.
+ * </p>
+ * <p>
+ * As of 3.2, AbstractTreeViewer supports multiple equal elements (each with a
+ * different parent chain) in the tree. This support requires that clients
+ * enable the element map by calling <code>setUseHashLookup(true)</code>.
+ * </p>
+ * <p>
+ * Content providers for abstract tree viewers must implement one of the
+ * interfaces <code>ITreeContentProvider</code> or (as of 3.2, to support
+ * multiple equal elements) <code>ITreePathContentProvider</code>.
+ * </p>
+ *
+ * @see TreeViewer
+ */
+public abstract class AbstractTreeViewer : ColumnViewer {
+
+    alias ColumnViewer.buildLabel buildLabel;
+    alias ColumnViewer.setSelection setSelection;
+
+    /**
+     * Constant indicating that all levels of the tree should be expanded or
+     * collapsed.
+     *
+     * @see #expandToLevel(int)
+     * @see #collapseToLevel(Object, int)
+     */
+    public static const int ALL_LEVELS = -1;
+
+    /**
+     * List of registered tree listeners (element type:
+     * <code>TreeListener</code>).
+     */
+    private ListenerList treeListeners;
+
+    /**
+     * The level to which the tree is automatically expanded each time the
+     * viewer's input is changed (that is, by <code>setInput</code>). A value
+     * of 0 means that auto-expand is off.
+     *
+     * @see #setAutoExpandLevel
+     */
+    private int expandToLevel_ = 0;
+
+    /**
+     * Safe runnable used to update an item.
+     */
+    class UpdateItemSafeRunnable : SafeRunnable {
+        private Object element;
+
+        private Item item;
+
+        this(Item item, Object element) {
+            this.item = item;
+            this.element = element;
+        }
+
+        public void run() {
+            doUpdateItem(item, element);
+        }
+
+    }
+
+    /**
+     * Creates an abstract tree viewer. The viewer has no input, no content
+     * provider, a default label provider, no sorter, no filters, and has
+     * auto-expand turned off.
+     */
+    protected this() {
+        treeListeners = new ListenerList();
+        // do nothing
+    }
+
+    /**
+     * Adds the given child elements to this viewer as children of the given
+     * parent element. If this viewer does not have a sorter, the elements are
+     * added at the end of the parent's list of children in the order given;
+     * otherwise, the elements are inserted at the appropriate positions.
+     * <p>
+     * This method should be called (by the content provider) when elements have
+     * been added to the model, in order to cause the viewer to accurately
+     * reflect the model. This method only affects the viewer, not the model.
+     * </p>
+     *
+     * @param parentElementOrTreePath
+     *            the parent element
+     * @param childElements
+     *            the child elements to add
+     */
+    public void add(Object parentElementOrTreePath, Object[] childElements) {
+        Assert.isNotNull(parentElementOrTreePath);
+        assertElementsNotNull(childElements);
+        if (isBusy())
+            return;
+        Widget[] widgets = internalFindItems(parentElementOrTreePath);
+        // If parent hasn't been realized yet, just ignore the add.
+        if (widgets.length is 0) {
+            return;
+        }
+
+        for (int i = 0; i < widgets.length; i++) {
+            internalAdd(widgets[i], parentElementOrTreePath, childElements);
+        }
+    }
+
+    /**
+     * Find the items for the given element of tree path
+     *
+     * @param parentElementOrTreePath
+     *            the element or tree path
+     * @return the items for that element
+     *
+     * @since 3.3
+     */
+    final protected Widget[] internalFindItems(Object parentElementOrTreePath) {
+        Widget[] widgets;
+        if ( auto path = cast(TreePath) parentElementOrTreePath ) {
+            Widget w = internalFindItem(path);
+            if (w is null) {
+                widgets = null;
+            } else {
+                widgets = [ w ];
+            }
+        } else {
+            widgets = findItems(parentElementOrTreePath);
+        }
+        return widgets;
+    }
+
+    /**
+     * Return the item at the given path or <code>null</code>
+     *
+     * @param path
+     *            the path
+     * @return {@link Widget} the item at that path
+     */
+    private Widget internalFindItem(TreePath path) {
+        Widget[] widgets = findItems(path.getLastSegment());
+        for (int i = 0; i < widgets.length; i++) {
+            Widget widget = widgets[i];
+            if ( auto item = cast(Item)widget ) {
+                TreePath p = getTreePathFromItem(item);
+                if (p.opEquals(path)) {
+                    return widget;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds the given child elements to this viewer as children of the given
+     * parent element.
+     * <p>
+     * EXPERIMENTAL. Not to be used except by JDT. This method was added to
+     * support JDT's explorations into grouping by working sets, which requires
+     * viewers to support multiple equal elements. See bug 76482 for more
+     * details. This support will likely be removed in Eclipse 3.2 in favor of
+     * proper support for multiple equal elements.
+     * </p>
+     *
+     * @param widget
+     *            the widget for the parent element
+     * @param parentElementOrTreePath
+     *            the parent element
+     * @param childElements
+     *            the child elements to add
+     * @since 3.1
+     */
+    protected void internalAdd(Widget widget, Object parentElementOrTreePath,
+            Object[] childElements) {
+        Object parent;
+        TreePath path;
+        if ( auto path = cast(TreePath) parentElementOrTreePath ) {
+            parent = path.getLastSegment();
+        } else {
+            parent = parentElementOrTreePath;
+            path = null;
+        }
+
+        // optimization!
+        // if the widget is not expanded we just invalidate the subtree
+        if ( auto ti = cast(Item)widget ) {
+            if (!getExpanded(ti)) {
+                bool needDummy = isExpandable(ti, path, parent);
+                bool haveDummy = false;
+                // remove all children
+                Item[] items = getItems(ti);
+                for (int i = 0; i < items.length; i++) {
+                    if (items[i].getData() !is null) {
+                        disassociate(items[i]);
+                        items[i].dispose();
+                    } else {
+                        if (needDummy && !haveDummy) {
+                            haveDummy = true;
+                        } else {
+                            items[i].dispose();
+                        }
+                    }
+                }
+                // append a dummy if necessary
+                if (needDummy && !haveDummy) {
+                    newItem(ti, DWT.NULL, -1);
+                }
+                return;
+            }
+        }
+
+        if (childElements.length > 0) {
+            // TODO: Add filtering back?
+            Object[] filtered = filter(parentElementOrTreePath, childElements);
+            ViewerComparator comparator = getComparator();
+            if (comparator !is null) {
+                if ( auto tpvs = cast(TreePathViewerSorter) comparator ) {
+                    if (path is null) {
+                        path = internalGetSorterParentPath(widget, comparator);
+                    }
+                    tpvs.sort(this, path, filtered);
+                } else {
+                    comparator.sort(this, filtered);
+                }
+            }
+            createAddedElements(widget, filtered);
+        }
+    }
+
+    /**
+     * Filter the children elements.
+     *
+     * @param parentElementOrTreePath
+     *            the parent element or path
+     * @param elements
+     *            the child elements
+     * @return the filter list of children
+     */
+    private Object[] filter(Object parentElementOrTreePath, Object[] elements) {
+        ViewerFilter[] filters = getFilters();
+        if (filters !is null) {
+            auto filtered = new ArraySeq!(Object);
+            filtered.capacity(elements.length);
+            for (int i = 0; i < elements.length; i++) {
+                bool add = true;
+                for (int j = 0; j < filters.length; j++) {
+                    add = filters[j].select(this, parentElementOrTreePath,
+                            elements[i]);
+                    if (!add) {
+                        break;
+                    }
+                }
+                if (add) {
+                    filtered.append(elements[i]);
+                }
+            }
+            return filtered.toArray();
+        }
+        return elements;
+    }
+
+    /**
+     * Create the new elements in the parent widget. If the child already exists
+     * do nothing.
+     *
+     * @param widget
+     * @param elements
+     *            Sorted list of elements to add.
+     */
+    private void createAddedElements(Widget widget, Object[] elements) {
+
+        if (elements.length is 1) {
+            if (opEquals(elements[0], widget.getData())) {
+                return;
+            }
+        }
+
+        ViewerComparator comparator = getComparator();
+        TreePath parentPath = internalGetSorterParentPath(widget, comparator);
+        Item[] items = getChildren(widget);
+
+        // As the items are sorted already we optimize for a
+        // start position
+        int lastInsertion = 0;
+
+        // Optimize for the empty case
+        if (items.length is 0) {
+            for (int i = 0; i < elements.length; i++) {
+                createTreeItem(widget, elements[i], -1);
+            }
+            return;
+        }
+
+        for (int i = 0; i < elements.length; i++) {
+            bool newItem = true;
+            Object element = elements[i];
+            int index;
+            if (comparator is null) {
+                if (itemExists(items, element)) {
+                    internalRefresh(element);
+                    newItem = false;
+                }
+                index = -1;
+            } else {
+                lastInsertion = insertionPosition(items, comparator,
+                        lastInsertion, element, parentPath);
+                // As we are only searching the original array we keep track of
+                // those positions only
+                if (lastInsertion is items.length) {
+                    index = -1;
+                } else {// See if we should just refresh
+                    while (lastInsertion < items.length
+                            && internalCompare(comparator, parentPath, element,
+                                    items[lastInsertion].getData()) is 0) {
+                        // As we cannot assume the sorter is consistent with
+                        // equals() - therefore we can
+                        // just check against the item prior to this index (if
+                        // any)
+                        if (items[lastInsertion].getData().opEquals(element)) {
+                            // refresh the element in case it has new children
+                            internalRefresh(element);
+                            newItem = false;
+                        }
+                        lastInsertion++;// We had an insertion so increment
+                    }
+                    // Did we get to the end?
+                    if (lastInsertion is items.length) {
+                        index = -1;
+                    } else {
+                        index = lastInsertion + i; // Add the index as the
+                                                    // array is growing
+                    }
+                }
+            }
+            if (newItem) {
+                createTreeItem(widget, element, index);
+            }
+        }
+    }
+
+    /**
+     * See if element is the data of one of the elements in items.
+     *
+     * @param items
+     * @param element
+     * @return <code>true</code> if the element matches.
+     */
+    private bool itemExists(Item[] items, Object element) {
+        if (usingElementMap()) {
+            Widget[] existingItems = findItems(element);
+            // optimization for two common cases
+            if (existingItems.length is 0) {
+                return false;
+            } else if (existingItems.length is 1) {
+                if (items.length > 0 && null !is cast(Item)existingItems[0] ) {
+                    Item existingItem = cast(Item) existingItems[0];
+                    return getParentItem(existingItem) is getParentItem(items[0]);
+                }
+            }
+        }
+        for (int i = 0; i < items.length; i++) {
+            if (items[i].getData().opEquals(element)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the index where the item should be inserted. It uses sorter to
+     * determine the correct position, if sorter is not assigned, returns the
+     * index of the element after the last.
+     *
+     * @param items
+     *            the items to search
+     * @param comparator
+     *            The comparator to use.
+     * @param lastInsertion
+     *            the start index to start search for position from this allows
+     *            optimizing search for multiple elements that are sorted
+     *            themselves.
+     * @param element
+     *            element to find position for.
+     * @param parentPath
+     *            the tree path for the element's parent or <code>null</code>
+     *            if the element is a root element or the sorter is not a
+     *            {@link TreePathViewerSorter}
+     * @return the index to use when inserting the element.
+     *
+     */
+
+    private int insertionPosition(Item[] items, ViewerComparator comparator,
+            int lastInsertion, Object element, TreePath parentPath) {
+
+        int size = items.length;
+        if (comparator is null) {
+            return size;
+        }
+        int min = lastInsertion, max = size - 1;
+
+        while (min <= max) {
+            int mid = (min + max) / 2;
+            Object data = items[mid].getData();
+            int compare = internalCompare(comparator, parentPath, data, element);
+            if (compare is 0) {
+                return mid;// Return if we already match
+            }
+            if (compare < 0) {
+                min = mid + 1;
+            } else {
+                max = mid - 1;
+            }
+        }
+        return min;
+
+    }
+
+    /**
+     * Returns the index where the item should be inserted. It uses sorter to
+     * determine the correct position, if sorter is not assigned, returns the
+     * index of the element after the last.
+     *
+     * @param parent
+     *            The parent widget
+     * @param sorter
+     *            The sorter to use.
+     * @param startIndex
+     *            the start index to start search for position from this allows
+     *            optimizing search for multiple elements that are sorted
+     *            themselves.
+     * @param element
+     *            element to find position for.
+     * @param currentSize
+     *            the current size of the collection
+     * @return the index to use when inserting the element.
+     *
+     */
+
+    /**
+     * Returns the index where the item should be inserted.
+     *
+     * @param parent
+     *            The parent widget the element will be inserted into.
+     * @param element
+     *            The element to insert.
+     * @return the index of the element
+     */
+    protected int indexForElement(Widget parent, Object element) {
+        ViewerComparator comparator = getComparator();
+        TreePath parentPath = internalGetSorterParentPath(parent, comparator);
+
+        Item[] items = getChildren(parent);
+        int count = items.length;
+
+        if (comparator is null) {
+            return count;
+        }
+        int min = 0, max = count - 1;
+
+        while (min <= max) {
+            int mid = (min + max) / 2;
+            Object data = items[mid].getData();
+            int compare = internalCompare(comparator, parentPath, data, element);
+            if (compare is 0) {
+                // find first item > element
+                while (compare is 0) {
+                    ++mid;
+                    if (mid >= count) {
+                        break;
+                    }
+                    data = items[mid].getData();
+                    compare = internalCompare(comparator, parentPath, data,
+                            element);
+                }
+                return mid;
+            }
+            if (compare < 0) {
+                min = mid + 1;
+            } else {
+                max = mid - 1;
+            }
+        }
+        return min;
+    }
+
+    /**
+     * Return the tree path that should be used as the parent path for the given
+     * widget and sorter. A <code>null</code> is returned if either the sorter
+     * is not a {@link TreePathViewerSorter} or if the parent widget is not an
+     * {@link Item} (i.e. is the root of the tree).
+     *
+     * @param parent
+     *            the parent widget
+     * @param comparator
+     *            the sorter
+     * @return the tree path that should be used as the parent path for the
+     *         given widget and sorter
+     */
+    private TreePath internalGetSorterParentPath(Widget parent,
+            ViewerComparator comparator) {
+        TreePath path;
+        if ( null !is cast(TreePathViewerSorter)comparator
+                && null !is cast(Item)parent ) {
+            Item item = cast(Item) parent;
+            path = getTreePathFromItem(item);
+        } else {
+            path = null;
+        }
+        return path;
+    }
+
+    /**
+     * Compare the two elements using the given sorter. If the sorter is a
+     * {@link TreePathViewerSorter}, the provided tree path will be used. If
+     * the tree path is null and the sorter is a tree path sorter, then the
+     * elements are root elements
+     *
+     * @param comparator
+     *            the sorter
+     * @param parentPath
+     *            the path of the elements' parent
+     * @param e1
+     *            the first element
+     * @param e2
+     *            the second element
+     * @return the result of comparing the two elements
+     */
+    private int internalCompare(ViewerComparator comparator,
+            TreePath parentPath, Object e1, Object e2) {
+        if ( auto tpvs = cast(TreePathViewerSorter) comparator ) {
+            return tpvs.compare(this, parentPath, e1, e2);
+        }
+        return comparator.compare(this, e1, e2);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.viewers.StructuredViewer#getSortedChildren(java.lang.Object)
+     */
+    protected Object[] getSortedChildren(Object parentElementOrTreePath) {
+        Object[] result = getFilteredChildren(parentElementOrTreePath);
+        ViewerComparator comparator = getComparator();
+        if (parentElementOrTreePath !is null
+                && null !is cast(TreePathViewerSorter) comparator ) {
+            TreePathViewerSorter tpvs = cast(TreePathViewerSorter) comparator;
+
+            // be sure we're not modifying the original array from the model
+            result = result.dup;
+
+            TreePath path = null;
+            if ( auto p = cast(TreePath) parentElementOrTreePath ) {
+                path = p;
+            } else {
+                Object parent = parentElementOrTreePath;
+                Widget w = internalGetWidgetToSelect(parent);
+                if (w !is null) {
+                    path = internalGetSorterParentPath(w, comparator);
+                }
+            }
+            tpvs.sort(this, path, result);
+        } else if (comparator !is null) {
+            // be sure we're not modifying the original array from the model
+            result = result.dup;
+            comparator.sort(this, result);
+        }
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.viewers.StructuredViewer#getFilteredChildren(java.lang.Object)
+     */
+    protected Object[] getFilteredChildren(Object parentElementOrTreePath) {
+        Object[] result = getRawChildren(parentElementOrTreePath);
+        ViewerFilter[] filters = getFilters();
+        for (int i = 0; i < filters.length; i++) {
+            ViewerFilter filter = filters[i];
+            result = filter.filter(this, parentElementOrTreePath, result);
+        }
+        return result;
+    }
+
+    /**
+     * Adds the given child element to this viewer as a child of the given
+     * parent element. If this viewer does not have a sorter, the element is
+     * added at the end of the parent's list of children; otherwise, the element
+     * is inserted at the appropriate position.
+     * <p>
+     * This method should be called (by the content provider) when a single
+     * element has been added to the model, in order to cause the viewer to
+     * accurately reflect the model. This method only affects the viewer, not
+     * the model. Note that there is another method for efficiently processing
+     * the simultaneous addition of multiple elements.
+     * </p>
+     *
+     * @param parentElementOrTreePath
+     *            the parent element or path
+     * @param childElement
+     *            the child element
+     */
+    public void add(Object parentElementOrTreePath, Object childElement) {
+        add(parentElementOrTreePath, [ childElement ]);
+    }
+
+    /**
+     * Adds the given DWT selection listener to the given DWT control.
+     *
+     * @param control
+     *            the DWT control
+     * @param listener
+     *            the DWT selection listener
+     * @deprecated
+     */
+    protected void addSelectionListener(Control control,
+            SelectionListener listener) {
+        // do nothing
+    }
+
+    /**
+     * Adds a listener for expand and collapse events in this viewer. Has no
+     * effect if an identical listener is already registered.
+     *
+     * @param listener
+     *            a tree viewer listener
+     */
+    public void addTreeListener(ITreeViewerListener listener) {
+        treeListeners.add(cast(Object)listener);
+    }
+
+    /**
+     * Adds the given DWT tree listener to the given DWT control.
+     *
+     * @param control
+     *            the DWT control
+     * @param listener
+     *            the DWT tree listener
+     */
+    protected abstract void addTreeListener(Control control,
+            TreeListener listener);
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see StructuredViewer#associate(Object, Item)
+     */
+    protected void associate(Object element, Item item) {
+        Object data = item.getData();
+        if (data !is null && data !is element && opEquals(data, element)) {
+            // workaround for PR 1FV62BT
+            // assumption: elements are equal but not identical
+            // -> remove from map but don't touch children
+            unmapElement(data, item);
+            item.setData(element);
+            mapElement(element, item);
+        } else {
+            // recursively disassociate all
+            super.associate(element, item);
+        }
+    }
+
+    /**
+     * Collapses all nodes of the viewer's tree, starting with the root. This
+     * method is equivalent to <code>collapseToLevel(ALL_LEVELS)</code>.
+     */
+    public void collapseAll() {
+        Object root = getRoot();
+        if (root !is null) {
+            collapseToLevel(root, ALL_LEVELS);
+        }
+    }
+
+    /**
+     * Collapses the subtree rooted at the given element or tree path to the
+     * given level.
+     *
+     * @param elementOrTreePath
+     *            the element or tree path
+     * @param level
+     *            non-negative level, or <code>ALL_LEVELS</code> to collapse
+     *            all levels of the tree
+     */
+    public void collapseToLevel(Object elementOrTreePath, int level) {
+        Assert.isNotNull(elementOrTreePath);
+        Widget w = internalGetWidgetToSelect(elementOrTreePath);
+        if (w !is null) {
+            internalCollapseToLevel(w, level);
+        }
+    }
+
+    /**
+     * Creates all children for the given widget.
+     * <p>
+     * The default implementation of this framework method assumes that
+     * <code>widget.getData()</code> returns the element corresponding to the
+     * node. Note: the node is not visually expanded! You may have to call
+     * <code>parent.setExpanded(true)</code>.
+     * </p>
+     *
+     * @param widget
+     *            the widget
+     */
+    protected void createChildren(Widget widget) {
+        bool oldBusy = busy;
+        busy = true;
+        try {
+            final Item[] tis = getChildren(widget);
+            if (tis !is null && tis.length > 0) {
+                Object data = tis[0].getData();
+                if (data !is null) {
+                    return; // children already there!
+                }
+            }
+
+            BusyIndicator.showWhile(widget.getDisplay(), new class Runnable {
+                Widget widget_;
+                Item[] tis_;
+                this(){
+                    widget_ = widget;
+                    tis_=tis;
+                }
+                public void run() {
+                    // fix for PR 1FW89L7:
+                    // don't complain and remove all "dummies" ...
+                    if (tis_ !is null) {
+                        for (int i = 0; i < tis_.length; i++) {
+                            if (tis_[i].getData() !is null) {
+                                disassociate(tis_[i]);
+                                Assert.isTrue(tis_[i].getData() is null,
+                                        "Second or later child is non -null");//$NON-NLS-1$
+
+                            }
+                            tis_[i].dispose();
+                        }
+                    }
+                    Object d = widget_.getData();
+                    if (d !is null) {
+                        Object parentElement = d;
+                        Object[] children;
+                        if (isTreePathContentProvider() && null !is cast(Item)widget_ ) {
+                            TreePath path = getTreePathFromItem(cast(Item) widget_);
+                            children = getSortedChildren(path);
+                        } else {
+                            children = getSortedChildren(parentElement);
+                        }
+                        for (int i = 0; i < children.length; i++) {
+                            createTreeItem(widget_, children[i], -1);
+                        }
+                    }
+                }
+
+            });
+        } finally {
+            busy = oldBusy;
+        }
+    }
+
+    /**
+     * Creates a single item for the given parent and synchronizes it with the
+     * given element.
+     *
+     * @param parent
+     *            the parent widget
+     * @param element
+     *            the element
+     * @param index
+     *            if non-negative, indicates the position to insert the item
+     *            into its parent
+     */
+    protected void createTreeItem(Widget parent, Object element, int index) {
+        Item item = newItem(parent, DWT.NULL, index);
+        updateItem(item, element);
+        updatePlus(item, element);
+    }
+
+    /**
+     * The <code>AbstractTreeViewer</code> implementation of this method also
+     * recurses over children of the corresponding element.
+     */
+    protected void disassociate(Item item) {
+        super.disassociate(item);
+        // recursively unmapping the items is only required when
+        // the hash map is used. In the other case disposing
+        // an item will recursively dispose its children.
+        if (usingElementMap()) {
+            disassociateChildren(item);
+        }
+    }
+
+    /**
+     * Disassociates the children of the given DWT item from their corresponding
+     * elements.
+     *
+     * @param item
+     *            the widget
+     */
+    private void disassociateChildren(Item item) {
+        Item[] items = getChildren(item);
+        for (int i = 0; i < items.length; i++) {
+            if (items[i].getData() !is null) {
+                disassociate(items[i]);
+            }
+        }
+    }
+
+    /* (non-Javadoc) Method declared on StructuredViewer. */
+    protected Widget doFindInputItem(Object element) {
+        // compare with root
+        Object root = getRoot();
+        if (root is null) {
+            return null;
+        }
+
+        if (opEquals(root, element)) {
+            return getControl();
+        }
+        return null;
+    }
+
+    /* (non-Javadoc) Method declared on StructuredViewer. */
+    protected Widget doFindItem(Object element) {
+        // compare with root
+        Object root = getRoot();
+        if (root is null) {
+            return null;
+        }
+
+        Item[] items = getChildren(getControl());
+        if (items !is null) {
+            for (int i = 0; i < items.length; i++) {
+                Widget o = internalFindItem(items[i], element);
+                if (o !is null) {
+                    return o;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Copies the attributes of the given element into the given DWT item.
+     *
+     * @param item
+     *            the DWT item
+     * @param element
+     *            the element
+     */
+    protected void doUpdateItem(Item item, Object element) {
+        if (item.isDisposed()) {
+            unmapElement(element, item);
+            return;
+        }
+
+        int columnCount = doGetColumnCount();
+        if (columnCount is 0)// If no columns are created then fake one
+            columnCount = 1;
+
+        ViewerRow viewerRowFromItem = getViewerRowFromItem(item);
+
+        bool isVirtual = (getControl().getStyle() & DWT.VIRTUAL) !is 0;
+
+        // If the control is virtual, we cannot use the cached viewer row object. See bug 188663.
+        if (isVirtual) {
+            viewerRowFromItem = cast(ViewerRow) viewerRowFromItem.clone();
+        }
+
+        for (int column = 0; column < columnCount; column++) {
+            ViewerColumn columnViewer = getViewerColumn(column);
+            ViewerCell cellToUpdate = updateCell(viewerRowFromItem, column,
+                    element);
+
+            // If the control is virtual, we cannot use the cached cell object. See bug 188663.
+            if (isVirtual) {
+                cellToUpdate = new ViewerCell(cellToUpdate.getViewerRow(), cellToUpdate.getColumnIndex(), element);
+            }
+
+            columnViewer.refresh(cellToUpdate);
+
+            // clear cell (see bug 201280)
+            updateCell(null, 0, null);
+
+            // As it is possible for user code to run the event
+            // loop check here.
+            if (item.isDisposed()) {
+                unmapElement(element, item);
+                return;
+            }
+
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if the given list and array of items refer to
+     * the same model elements. Order is unimportant.
+     * <p>
+     * This method is not intended to be overridden by subclasses.
+     * </p>
+     *
+     * @param items
+     *            the list of items
+     * @param current
+     *            the array of items
+     * @return <code>true</code> if the refer to the same elements,
+     *         <code>false</code> otherwise
+     *
+     * @since 3.1 in TreeViewer, moved to AbstractTreeViewer in 3.3
+     */
+    protected bool isSameSelection(SeqView!(Item) items, Item[] current) {
+        // If they are not the same size then they are not equivalent
+        int n = items.size();
+        if (n !is current.length) {
+            return false;
+        }
+
+        CustomHashtable itemSet = newHashtable(n * 2 + 1);
+        foreach( item; items ){
+            Object element = item.getData();
+            itemSet.put(element, element);
+        }
+
+        // Go through the items of the current collection
+        // If there is a mismatch return false
+        for (int i = 0; i < current.length; i++) {
+            if (current[i].getData() is null
+                    || !itemSet.containsKey(current[i].getData())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+
+    /* (non-Javadoc) Method declared on StructuredViewer. */
+    protected void doUpdateItem(Widget widget, Object element, bool fullMap) {
+        bool oldBusy = busy;
+        busy = true;
+        try {
+            if ( auto item = cast(Item)widget ) {
+
+                // ensure that back pointer is correct
+                if (fullMap) {
+                    associate(element, item);
+                } else {
+                    Object data = item.getData();
+                    if (data !is null) {
+                        unmapElement(data, item);
+                    }
+                    item.setData(element);
+                    mapElement(element, item);
+                }
+
+                // update icon and label
+                SafeRunnable.run(new UpdateItemSafeRunnable(item, element));
+            }
+        } finally {
+            busy = oldBusy;
+        }
+    }
+
+    /**
+     * Expands all nodes of the viewer's tree, starting with the root. This
+     * method is equivalent to <code>expandToLevel(ALL_LEVELS)</code>.
+     */
+    public void expandAll() {
+        expandToLevel(ALL_LEVELS);
+    }
+
+    /**
+     * Expands the root of the viewer's tree to the given level.
+     *
+     * @param level
+     *            non-negative level, or <code>ALL_LEVELS</code> to expand all
+     *            levels of the tree
+     */
+    public void expandToLevel(int level) {
+        expandToLevel(getRoot(), level);
+    }
+
+    /**
+     * Expands all ancestors of the given element or tree path so that the given
+     * element becomes visible in this viewer's tree control, and then expands
+     * the subtree rooted at the given element to the given level.
+     *
+     * @param elementOrTreePath
+     *            the element
+     * @param level
+     *            non-negative level, or <code>ALL_LEVELS</code> to expand all
+     *            levels of the tree
+     */
+    public void expandToLevel(Object elementOrTreePath, int level) {
+        if (isBusy())
+            return;
+        Widget w = internalExpand(elementOrTreePath, true);
+        if (w !is null) {
+            internalExpandToLevel(w, level);
+        }
+    }
+
+    /**
+     * Fires a tree collapsed event. Only listeners registered at the time this
+     * method is called are notified.
+     *
+     * @param event
+     *            the tree expansion event
+     * @see ITreeViewerListener#treeCollapsed
+     */
+    protected void fireTreeCollapsed(TreeExpansionEvent event) {
+        Object[] listeners = treeListeners.getListeners();
+        for (int i = 0; i < listeners.length; ++i) {
+            SafeRunnable.run(new class SafeRunnable {
+                TreeExpansionEvent event_;
+                ITreeViewerListener l;
+                this(){
+                    event_=event;
+                    l = cast(ITreeViewerListener) listeners[i];
+                }
+                public void run() {
+                    l.treeCollapsed(event_);
+                }
+            });
+        }
+    }
+
+    /**
+     * Fires a tree expanded event. Only listeners registered at the time this
+     * method is called are notified.
+     *
+     * @param event
+     *            the tree expansion event
+     * @see ITreeViewerListener#treeExpanded
+     */
+    protected void fireTreeExpanded(TreeExpansionEvent event) {
+        Object[] listeners = treeListeners.getListeners();
+        for (int i = 0; i < listeners.length; ++i) {
+            SafeRunnable.run(new class SafeRunnable {
+                TreeExpansionEvent event_;
+                ITreeViewerListener l;
+                this(){
+                    event_=event;
+                    l = cast(ITreeViewerListener) listeners[i];
+                }
+                public void run() {
+                    l.treeExpanded(event_);
+                }
+            });
+        }
+
+    }
+
+    /**
+     * Returns the auto-expand level.
+     *
+     * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of
+     *         the tree are expanded automatically
+     * @see #setAutoExpandLevel
+     */
+    public int getAutoExpandLevel() {
+        return expandToLevel_;
+    }
+
+    /**
+     * Returns the DWT child items for the given DWT widget.
+     *
+     * @param widget
+     *            the widget
+     * @return the child items
+     */
+    protected abstract Item[] getChildren(Widget widget);
+
+    /**
+     * Get the child for the widget at index. Note that the default
+     * implementation is not very efficient and should be overridden if this
+     * class is implemented.
+     *
+     * @param widget
+     *            the widget to check
+     * @param index
+     *            the index of the widget
+     * @return Item or <code>null</code> if widget is not a type that can
+     *         contain items.
+     *
+     * @throws ArrayIndexOutOfBoundsException
+     *             if the index is not valid.
+     * @since 3.1
+     */
+    protected Item getChild(Widget widget, int index) {
+        return getChildren(widget)[index];
+    }
+
+    /**
+     * Returns whether the given DWT item is expanded or collapsed.
+     *
+     * @param item
+     *            the item
+     * @return <code>true</code> if the item is considered expanded and
+     *         <code>false</code> if collapsed
+     */
+    protected abstract bool getExpanded(Item item);
+
+    /**
+     * Returns a list of elements corresponding to expanded nodes in this
+     * viewer's tree, including currently hidden ones that are marked as
+     * expanded but are under a collapsed ancestor.
+     * <p>
+     * This method is typically used when preserving the interesting state of a
+     * viewer; <code>setExpandedElements</code> is used during the restore.
+     * </p>
+     *
+     * @return the array of expanded elements
+     * @see #setExpandedElements
+     */
+    public Object[] getExpandedElements() {
+        auto items = new ArraySeq!(Item);
+        internalCollectExpandedItems(items, getControl());
+        auto result = new ArraySeq!(Object);
+        result.capacity(items.size());
+        foreach ( item; items ) {
+            Object data = item.getData();
+            if (data !is null) {
+                result.append(data);
+            }
+        }
+        return result.toArray();
+    }
+
+    /**
+     * Returns whether the node corresponding to the given element or tree path
+     * is expanded or collapsed.
+     *
+     * @param elementOrTreePath
+     *            the element
+     * @return <code>true</code> if the node is expanded, and
+     *         <code>false</code> if collapsed
+     */
+    public bool getExpandedState(Object elementOrTreePath) {
+        Assert.isNotNull(elementOrTreePath);
+        Widget item = internalGetWidgetToSelect(elementOrTreePath);
+        if ( auto i = cast(Item)item ) {
+            return getExpanded(i);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the number of child items of the given DWT control.
+     *
+     * @param control
+     *            the control
+     * @return the number of children
+     */
+    protected abstract int getItemCount(Control control);
+
+    /**
+     * Returns the number of child items of the given DWT item.
+     *
+     * @param item
+     *            the item
+     * @return the number of children
+     */
+    protected abstract int getItemCount(Item item);
+
+    /**
+     * Returns the child items of the given DWT item.
+     *
+     * @param item
+     *            the item
+     * @return the child items
+     */
+    protected abstract Item[] getItems(Item item);
+
+    /**
+     * Returns the item after the given item in the tree, or <code>null</code>
+     * if there is no next item.
+     *
+     * @param item
+     *            the item
+     * @param includeChildren
+     *            <code>true</code> if the children are considered in
+     *            determining which item is next, and <code>false</code> if
+     *            subtrees are ignored
+     * @return the next item, or <code>null</code> if none
+     */
+    protected Item getNextItem(Item item, bool includeChildren) {
+        if (item is null) {
+            return null;
+        }
+        if (includeChildren && getExpanded(item)) {
+            Item[] children = getItems(item);
+            if (children !is null && children.length > 0) {
+                return children[0];
+            }
+        }
+
+        // next item is either next sibling or next sibling of first
+        // parent that has a next sibling.
+        Item parent = getParentItem(item);
+        if (parent is null) {
+            return null;
+        }
+        Item[] siblings = getItems(parent);
+        if (siblings !is null) {
+            if (siblings.length <= 1) {
+                return getNextItem(parent, false);
+            }
+
+            for (int i = 0; i < siblings.length; i++) {
+                if (siblings[i] is item && i < (siblings.length - 1)) {
+                    return siblings[i + 1];
+                }
+            }
+        }
+        return getNextItem(parent, false);
+    }
+
+    /**
+     * Returns the parent item of the given item in the tree, or
+     * <code>null</code> if there is no parent item.
+     *
+     * @param item
+     *            the item
+     * @return the parent item, or <code>null</code> if none
+     */
+    protected abstract Item getParentItem(Item item);
+
+    /**
+     * Returns the item before the given item in the tree, or <code>null</code>
+     * if there is no previous item.
+     *
+     * @param item
+     *            the item
+     * @return the previous item, or <code>null</code> if none
+     */
+    protected Item getPreviousItem(Item item) {
+        // previous item is either right-most visible descendent of previous
+        // sibling or parent
+        Item parent = getParentItem(item);
+        if (parent is null) {
+            return null;
+        }
+        Item[] siblings = getItems(parent);
+        if (siblings.length is 0 || siblings[0] is item) {
+            return parent;
+        }
+        Item previous = siblings[0];
+        for (int i = 1; i < siblings.length; i++) {
+            if (siblings[i] is item) {
+                return rightMostVisibleDescendent(previous);
+            }
+            previous = siblings[i];
+        }
+        return null;
+    }
+
+    /* (non-Javadoc) Method declared on StructuredViewer. */
+    protected Object[] getRawChildren(Object parentElementOrTreePath) {
+        bool oldBusy = busy;
+        busy = true;
+        try {
+            Object parent;
+            TreePath path;
+            if ( auto p = cast(TreePath)parentElementOrTreePath ) {
+                path = p;
+                parent = path.getLastSegment();
+            } else {
+                parent = parentElementOrTreePath;
+                path = null;
+            }
+            if (parent !is null) {
+                if (opEquals(parent, getRoot())) {
+                    return super.getRawChildren(parent);
+                }
+                IContentProvider cp = getContentProvider();
+                if ( auto tpcp = cast(ITreePathContentProvider)cp ) {
+                    if (path is null) {
+                        // A path was not provided so try and find one
+                        Widget w = findItem(parent);
+                        if ( auto item = cast(Item)w ) {
+                            path = getTreePathFromItem(item);
+                        }
+                        if (path is null) {
+                            path = new TreePath([parent ]);
+                        }
+                    }
+                    Object[] result = tpcp.getChildren(path);
+                    if (result !is null) {
+                        return result;
+                    }
+                } else if ( auto tcp = cast(ITreeContentProvider)cp ) {
+                    Object[] result = tcp.getChildren(parent);
+                    if (result !is null) {
+                        return result;
+                    }
+                }
+            }
+            return null;
+        } finally {
+            busy = oldBusy;
+        }
+    }
+
+    /**
+     * Returns all selected items for the given DWT control.
+     *
+     * @param control
+     *            the control
+     * @return the list of selected items
+     */
+    protected abstract Item[] getSelection(Control control);
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.viewers.StructuredViewer#getSelectionFromWidget()
+     */
+    protected SeqView!(Object) getSelectionFromWidget() {
+        Widget[] items = getSelection(getControl());
+        ArraySeq!(Object) list = new ArraySeq!(Object);
+        list.capacity(items.length);
+        for (int i = 0; i < items.length; i++) {
+            Widget item = items[i];
+            Object e = item.getData();
+            if (e !is null) {
+                list.append(e);
+            }
+        }
+        return list;
+    }
+
+    /*
+     * Overridden in AbstractTreeViewer to fix bug 108102 (code copied from
+     * StructuredViewer to avoid introducing new API) (non-Javadoc)
+     *
+     * @see dwtx.jface.viewers.StructuredViewer#handleDoubleSelect(dwt.events.SelectionEvent)
+     */
+    protected void handleDoubleSelect(SelectionEvent event) {
+        // handle case where an earlier selection listener disposed the control.
+        Control control = getControl();
+        if (control !is null && !control.isDisposed()) {
+            // If the double-clicked element can be obtained from the event, use
+            // it
+            // otherwise get it from the control. Some controls like List do
+            // not have the notion of item.
+            // For details, see bug 90161 [Navigator] DefaultSelecting folders
+            // shouldn't always expand first one
+            ISelection selection;
+            if (event.item !is null && event.item.getData() !is null) {
+
+                // changes to fix bug 108102 follow
+                TreePath treePath = getTreePathFromItem(cast(Item) event.item);
+                selection = new TreeSelection(treePath);
+                // end of changes
+
+            } else {
+                selection = getSelection();
+                updateSelection(selection);
+            }
+            fireDoubleClick(new DoubleClickEvent(this, selection));
+        }
+    }
+
+    /**
+     * Handles a tree collapse event from the DWT widget.
+     *
+     * @param event
+     *            the DWT tree event
+     */
+    protected void handleTreeCollapse(TreeEvent event) {
+        if (event.item.getData() !is null) {
+            fireTreeCollapsed(new TreeExpansionEvent(this, event.item.getData()));
+        }
+    }
+
+    /**
+     * Handles a tree expand event from the DWT widget.
+     *
+     * @param event
+     *            the DWT tree event
+     */
+    protected void handleTreeExpand(TreeEvent event) {
+        createChildren(event.item);
+        if (event.item.getData() !is null) {
+            fireTreeExpanded(new TreeExpansionEvent(this, event.item.getData()));
+        }
+    }
+
+    /* (non-Javadoc) Method declared on Viewer. */
+    protected void hookControl(Control control) {
+        super.hookControl(control);
+        addTreeListener(control, new class TreeListener {
+            public void treeExpanded(TreeEvent event) {
+                handleTreeExpand(event);
+            }
+
+            public void treeCollapsed(TreeEvent event) {
+                handleTreeCollapse(event);
+            }
+        });
+    }
+
+    /*
+     * (non-Javadoc) Method declared on StructuredViewer. Builds the initial
+     * tree and handles the automatic expand feature.
+     */
+    protected void inputChanged(Object input, Object oldInput) {
+        preservingSelection(new class Runnable {
+            public void run() {
+                Control tree = getControl();
+                bool useRedraw = true;
+                // (size > REDRAW_THRESHOLD) || (table.getItemCount() >
+                // REDRAW_THRESHOLD);
+                if (useRedraw) {
+                    tree.setRedraw(false);
+                }
+                removeAll(tree);
+                tree.setData(getRoot());
+                internalInitializeTree(tree);
+                if (useRedraw) {
+                    tree.setRedraw(true);
+                }
+            }
+
+        });
+    }
+
+    /**
+     * Initializes the tree with root items, expanding to the appropriate
+     * level if necessary.
+     *
+     * @param tree the tree control
+     * @since 3.3
+     */
+    protected void internalInitializeTree(Control tree) {
+        createChildren(tree);
+        internalExpandToLevel(tree, expandToLevel_);
+    }
+
+    /**
+     * Recursively collapses the subtree rooted at the given widget to the given
+     * level.
+     * <p>
+     * </p>
+     * Note that the default implementation of this method does not call
+     * <code>setRedraw</code>.
+     *
+     * @param widget
+     *            the widget
+     * @param level
+     *            non-negative level, or <code>ALL_LEVELS</code> to collapse
+     *            all levels of the tree
+     */
+    protected void internalCollapseToLevel(Widget widget, int level) {
+        if (level is ALL_LEVELS || level > 0) {
+
+            if ( auto i = cast(Item)widget ) {
+                setExpanded(i, false);
+            }
+
+            if (level is ALL_LEVELS || level > 1) {
+                Item[] children = getChildren(widget);
+                if (children !is null) {
+                    int nextLevel = (level is ALL_LEVELS ? ALL_LEVELS
+                            : level - 1);
+                    for (int i = 0; i < children.length; i++) {
+                        internalCollapseToLevel(children[i], nextLevel);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Recursively collects all expanded items from the given widget.
+     *
+     * @param result
+     *            a list (element type: <code>Item</code>) into which to
+     *            collect the elements
+     * @param widget
+     *            the widget
+     */
+    private void internalCollectExpandedItems(Seq!(Item) result, Widget widget) {
+        Item[] items = getChildren(widget);
+        for (int i = 0; i < items.length; i++) {
+            Item item = items[i];
+            if (getExpanded(item)) {
+                result.append(item);
+            }
+            internalCollectExpandedItems(result, item);
+        }
+    }
+
+    /**
+     * Tries to create a path of tree items for the given element or tree path.
+     * This method recursively walks up towards the root of the tree and in the
+     * case of an element (rather than a tree path) assumes that
+     * <code>getParent</code> returns the correct parent of an element.
+     *
+     * @param elementOrPath
+     *            the element
+     * @param expand
+     *            <code>true</code> if all nodes on the path should be
+     *            expanded, and <code>false</code> otherwise
+     * @return Widget
+     */
+    protected Widget internalExpand(Object elementOrPath, bool expand) {
+
+        if (elementOrPath is null) {
+            return null;
+        }
+
+        Widget w = internalGetWidgetToSelect(elementOrPath);
+        if (w is null) {
+            if (opEquals(elementOrPath, getRoot())) { // stop at root
+                return null;
+            }
+            // my parent has to create me
+            Object parent = getParentElement(elementOrPath);
+            if (parent !is null) {
+                Widget pw = internalExpand(parent, false);
+                if (pw !is null) {
+                    // let my parent create me
+                    createChildren(pw);
+                    Object element = internalToElement(elementOrPath);
+                    w = internalFindChild(pw, element);
+                    if (expand && null !is cast(Item)pw ) {
+                        // expand parent items top-down
+                        Item item = cast(Item) pw;
+                        auto toExpandList = new LinkSeq!(Item);
+                        while (item !is null && !getExpanded(item)) {
+                            toExpandList.prepend(item);
+                            item = getParentItem(item);
+                        }
+                        foreach( toExpand; toExpandList ){
+                            setExpanded(toExpand, true);
+                        }
+                    }
+                }
+            }
+        }
+        return w;
+    }
+
+    /**
+     * If the argument is a tree path, returns its last segment, otherwise
+     * return the argument
+     *
+     * @param elementOrPath
+     *            an element or a tree path
+     * @return the element, or the last segment of the tree path
+     */
+    private Object internalToElement(Object elementOrPath) {
+        if (auto tp = cast(TreePath)elementOrPath ) {
+            return tp.getLastSegment();
+        }
+        return elementOrPath;
+    }
+
+    /**
+     * This method takes a tree path or an element. If the argument is not a
+     * tree path, returns the parent of the given element or <code>null</code>
+     * if the parent is not known. If the argument is a tree path with more than
+     * one segment, returns its parent tree path, otherwise returns
+     * <code>null</code>.
+     *
+     * @param elementOrTreePath
+     * @return the parent element, or parent path, or <code>null</code>
+     *
+     * @since 3.2
+     */
+    protected Object getParentElement(Object elementOrTreePath) {
+        if (auto tp = cast(TreePath)elementOrTreePath) {
+            return tp.getParentPath();
+        }
+        IContentProvider cp = getContentProvider();
+        if ( auto tpcp = cast(ITreePathContentProvider)cp ) {
+            TreePath[] paths = tpcp.getParents(elementOrTreePath);
+            if (paths.length > 0) {
+                if (paths[0].getSegmentCount() is 0) {
+                    return getInput();
+                }
+                return paths[0].getLastSegment();
+            }
+        }
+        if ( auto tcp = cast(ITreeContentProvider) cp ) {
+            return tcp.getParent(elementOrTreePath);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the widget to be selected for the given element or tree path.
+     *
+     * @param elementOrTreePath
+     *            the element or tree path to select
+     * @return the widget to be selected, or <code>null</code> if not found
+     *
+     * @since 3.1
+     */
+    protected Widget internalGetWidgetToSelect(Object elementOrTreePath) {
+        if ( auto treePath = cast(TreePath) elementOrTreePath ) {
+            if (treePath.getSegmentCount() is 0) {
+                return getControl();
+            }
+            Widget[] candidates = findItems(treePath.getLastSegment());
+            for (int i = 0; i < candidates.length; i++) {
+                Widget candidate = candidates[i];
+                if (!(cast(Item)candidate )) {
+                    continue;
+                }
+                if (treePath.opEquals(getTreePathFromItem(cast(Item) candidate),
+                        getComparer())) {
+                    return candidate;
+                }
+            }
+            return null;
+        }
+        return findItem(elementOrTreePath);
+    }
+
+    /**
+     * Recursively expands the subtree rooted at the given widget to the given
+     * level.
+     * <p>
+     * </p>
+     * Note that the default implementation of this method does not call
+     * <code>setRedraw</code>.
+     *
+     * @param widget
+     *            the widget
+     * @param level
+     *            non-negative level, or <code>ALL_LEVELS</code> to collapse
+     *            all levels of the tree
+     */
+    protected void internalExpandToLevel(Widget widget, int level) {
+        if (level is ALL_LEVELS || level > 0) {
+            if ( cast(Item)widget && widget.getData() !is null
+                    && !isExpandable(cast(Item) widget, null, widget.getData())) {
+                return;
+            }
+            createChildren(widget);
+            if ( auto i = cast(Item)widget ) {
+                setExpanded(i, true);
+            }
+            if (level is ALL_LEVELS || level > 1) {
+                Item[] children = getChildren(widget);
+                if (children !is null) {
+                    int newLevel = (level is ALL_LEVELS ? ALL_LEVELS
+                            : level - 1);
+                    for (int i = 0; i < children.length; i++) {
+                        internalExpandToLevel(children[i], newLevel);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Non-recursively tries to find the given element as a child of the given
+     * parent (item or tree).
+     *
+     * @param parent
+     *            the parent item
+     * @param element
+     *            the element
+     * @return Widget
+     */
+    private Widget internalFindChild(Widget parent, Object element) {
+        Item[] items = getChildren(parent);
+        for (int i = 0; i < items.length; i++) {
+            Item item = items[i];
+            Object data = item.getData();
+            if (data !is null && opEquals(data, element)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Recursively tries to find the given element.
+     *
+     * @param parent
+     *            the parent item
+     * @param element
+     *            the element
+     * @return Widget
+     */
+    private Widget internalFindItem(Item parent, Object element) {
+
+        // compare with node
+        Object data = parent.getData();
+        if (data !is null) {
+            if (opEquals(data, element)) {
+                return parent;
+            }
+        }
+        // recurse over children
+        Item[] items = getChildren(parent);
+        for (int i = 0; i < items.length; i++) {
+            Item item = items[i];
+            Widget o = internalFindItem(item, element);
+            if (o !is null) {
+                return o;
+            }
+        }
+        return null;
+    }
+
+    /* (non-Javadoc) Method declared on StructuredViewer. */
+    protected void internalRefresh(Object element) {
+        internalRefresh(element, true);
+    }
+
+    /* (non-Javadoc) Method declared on StructuredViewer. */
+    protected void internalRefresh(Object element, bool updateLabels) {
+        // If element is null, do a full refresh.
+        if (element is null) {
+            internalRefresh(getControl(), getRoot(), true, updateLabels);
+            return;
+        }
+        Widget[] items = findItems(element);
+        if (items.length !is 0) {
+            for (int i = 0; i < items.length; i++) {
+                // pick up structure changes too
+                internalRefresh(items[i], element, true, updateLabels);
+            }
+        }
+    }
+
+    /**
+     * Refreshes the tree starting at the given widget.
+     * <p>
+     * EXPERIMENTAL. Not to be used except by JDT. This method was added to
+     * support JDT's explorations into grouping by working sets, which requires
+     * viewers to support multiple equal elements. See bug 76482 for more
+     * details. This support will likely be removed in Eclipse 3.2 in favor of
+     * proper support for multiple equal elements.
+     * </p>
+     *
+     * @param widget
+     *            the widget
+     * @param element
+     *            the element
+     * @param doStruct
+     *            <code>true</code> if structural changes are to be picked up,
+     *            and <code>false</code> if only label provider changes are of
+     *            interest
+     * @param updateLabels
+     *            <code>true</code> to update labels for existing elements,
+     *            <code>false</code> to only update labels as needed, assuming
+     *            that labels for existing elements are unchanged.
+     * @since 3.1
+     */
+    protected void internalRefresh(Widget widget, Object element,
+            bool doStruct, bool updateLabels) {
+
+        if ( auto i = cast(Item)widget ) {
+            if (doStruct) {
+                updatePlus(i, element);
+            }
+            if (updateLabels || !opEquals(element, widget.getData())) {
+                doUpdateItem(widget, element, true);
+            } else {
+                associate(element, cast(Item) widget);
+            }
+        }
+
+        if (doStruct) {
+            internalRefreshStruct(widget, element, updateLabels);
+        } else {
+            Item[] children = getChildren(widget);
+            if (children !is null) {
+                for (int i = 0; i < children.length; i++) {
+                    Widget item = children[i];
+                    Object data = item.getData();
+                    if (data !is null) {
+                        internalRefresh(item, data, doStruct, updateLabels);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the structure and recurse. Items are updated in updateChildren, as
+     * needed.
+     *
+     * @param widget
+     * @param element
+     * @param updateLabels
+     */
+    /* package */void internalRefreshStruct(Widget widget, Object element,
+            bool updateLabels) {
+        updateChildren(widget, element, null, updateLabels);
+        Item[] children = getChildren(widget);
+        if (children !is null) {
+            for (int i = 0; i < children.length; i++) {
+                Widget item = children[i];
+                Object data = item.getData();
+                if (data !is null) {
+                    internalRefreshStruct(item, data, updateLabels);
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes the given elements from this viewer.
+     * <p>
+     * EXPERIMENTAL. Not to be used except by JDT. This method was added to
+     * support JDT's explorations into grouping by working sets, which requires
+     * viewers to support multiple equal elements. See bug 76482 for more
+     * details. This support will likely be removed in Eclipse 3.2 in favor of
+     * proper support for multiple equal elements.
+     * </p>
+     *
+     * @param elementsOrPaths
+     *            the elements or element paths to remove
+     * @since 3.1
+     */
+    protected void internalRemove(Object[] elementsOrPaths) {
+        Object input = getInput();
+        for (int i = 0; i < elementsOrPaths.length; ++i) {
+            Object element = elementsOrPaths[i];
+            if (opEquals(element, input)) {
+                setInput(null);
+                return;
+            }
+            Widget[] childItems = internalFindItems(element);
+            for (int j = 0; j < childItems.length; j++) {
+                Widget childItem = childItems[j];
+                if ( auto it = cast(Item)childItem ) {
+                    disassociate(it);
+                    childItem.dispose();
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes the given elements from this viewer, whenever those elements
+     * appear as children of the given parent.
+     *
+     * @param parent the parent element
+     * @param elements
+     *            the elements to remove
+     * @since 3.1
+     */
+    protected void internalRemove(Object parent, Object[] elements) {
+
+        CustomHashtable toRemove = new CustomHashtable(getComparer());
+        for (int i = 0; i < elements.length; i++) {
+            toRemove.put(elements[i], elements[i]);
+        }
+
+        // Find each place the parent appears in the tree
+        Widget[] parentItemArray = findItems(parent);
+        for (int i = 0; i < parentItemArray.length; i++) {
+            Widget parentItem = parentItemArray[i];
+
+            // Iterate over the child items and remove each one
+            Item[] children = getChildren(parentItem);
+
+            for (int j = 0; j < children.length; j++) {
+                Item child = children[j];
+
+                Object data = child.getData();
+                if (data !is null && toRemove.containsKey(data)) {
+                    disassociate(child);
+                    child.dispose();
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the expanded state of all items to correspond to the given set of
+     * expanded elements.
+     *
+     * @param expandedElements
+     *            the set (element type: <code>Object</code>) of elements
+     *            which are expanded
+     * @param widget
+     *            the widget
+     */
+    private void internalSetExpanded(CustomHashtable expandedElements,
+            Widget widget) {
+        Item[] items = getChildren(widget);
+        for (int i = 0; i < items.length; i++) {
+            Item item = items[i];
+            Object data = item.getData();
+            if (data !is null) {
+                // remove the element to avoid an infinite loop
+                // if the same element appears on a child item
+                bool expanded = expandedElements.remove(data) !is null;
+                if (expanded !is getExpanded(item)) {
+                    if (expanded) {
+                        createChildren(item);
+                    }
+                    setExpanded(item, expanded);
+                }
+            }
+            if (expandedElements.size() > 0) {
+                internalSetExpanded(expandedElements, item);
+            }
+        }
+    }
+
+    /**
+     * Sets the expanded state of all items to correspond to the given set of
+     * expanded tree paths.
+     *
+     * @param expandedTreePaths
+     *            the set (element type: <code>TreePath</code>) of elements
+     *            which are expanded
+     * @param widget
+     *            the widget
+     */
+    private void internalSetExpandedTreePaths(
+            CustomHashtable expandedTreePaths, Widget widget,
+            TreePath currentPath) {
+        Item[] items = getChildren(widget);
+        for (int i = 0; i < items.length; i++) {
+            Item item = items[i];
+            Object data = item.getData();
+            TreePath childPath = data is null ? null : currentPath
+                    .createChildPath(data);
+            if (data !is null && childPath !is null) {
+                // remove the element to avoid an infinite loop
+                // if the same element appears on a child item
+                bool expanded = expandedTreePaths.remove(childPath) !is null;
+                if (expanded !is getExpanded(item)) {
+                    if (expanded) {
+                        createChildren(item);
+                    }
+                    setExpanded(item, expanded);
+                }
+            }
+            internalSetExpandedTreePaths(expandedTreePaths, item, childPath);
+        }
+    }
+
+    /**
+     * Return whether the tree node representing the given element or path can
+     * be expanded. Clients should query expandability by path if the viewer's
+     * content provider is an {@link ITreePathContentProvider}.
+     * <p>
+     * The default implementation of this framework method calls
+     * <code>hasChildren</code> on this viewer's content provider. It may be
+     * overridden if necessary.
+     * </p>
+     *
+     * @param elementOrTreePath
+     *            the element or path
+     * @return <code>true</code> if the tree node representing the given
+     *         element can be expanded, or <code>false</code> if not
+     */
+    public bool isExpandable(Object elementOrTreePath) {
+        Object element;
+        TreePath path;
+        if (auto p = cast(TreePath)elementOrTreePath) {
+            path = p;
+            element = path.getLastSegment();
+        } else {
+            element = elementOrTreePath;
+            path = null;
+        }
+        IContentProvider cp = getContentProvider();
+        if ( auto tpcp = cast(ITreePathContentProvider) cp ) {
+            if (path is null) {
+                // A path was not provided so try and find one
+                Widget w = findItem(element);
+                if ( auto item = cast(Item)w ) {
+                    path = getTreePathFromItem(item);
+                }
+                if (path is null) {
+                    path = new TreePath([ element ]);
+                }
+            }
+            return tpcp.hasChildren(path);
+        }
+        if (auto tcp = cast(ITreeContentProvider)cp ) {
+            return tcp.hasChildren(element);
+        }
+        return false;
+    }
+
+    /**
+     * Return whether the given element is expandable.
+     *
+     * @param item
+     *            the tree item for the element
+     * @param parentPath
+     *            the parent path if it is known or <code>null</code> if it
+     *            needs to be determines
+     * @param element
+     *            the element
+     * @return whether the given element is expandable
+     */
+    private bool isExpandable(Item item, TreePath parentPath, Object element) {
+        Object elementOrTreePath = element;
+        if (isTreePathContentProvider()) {
+            if (parentPath !is null) {
+                elementOrTreePath = parentPath.createChildPath(element);
+            } else {
+                elementOrTreePath = getTreePathFromItem(item);
+            }
+        }
+        return isExpandable(elementOrTreePath);
+    }
+
+    /* (non-Javadoc) Method declared on Viewer. */
+    protected void labelProviderChanged() {
+        // we have to walk the (visible) tree and update every item
+        Control tree = getControl();
+        tree.setRedraw(false);
+        // don't pick up structure changes, but do force label updates
+        internalRefresh(tree, getRoot(), false, true);
+        tree.setRedraw(true);
+    }
+
+    /**
+     * Creates a new item.
+     *
+     * @param parent
+     *            the parent widget
+     * @param style
+     *            DWT style bits
+     * @param index
+     *            if non-negative, indicates the position to insert the item
+     *            into its parent
+     * @return the newly-created item
+     */
+    protected abstract Item newItem(Widget parent, int style, int index);
+
+    /**
+     * Removes the given elements from this viewer. The selection is updated if
+     * required.
+     * <p>
+     * This method should be called (by the content provider) when elements have
+     * been removed from the model, in order to cause the viewer to accurately
+     * reflect the model. This method only affects the viewer, not the model.
+     * </p>
+     *
+     * @param elementsOrTreePaths
+     *            the elements to remove
+     */
+    public void remove(Object[] elementsOrTreePaths) {
+        assertElementsNotNull(elementsOrTreePaths);
+        if (elementsOrTreePaths.length is 0) {
+            return;
+        }
+        if (isBusy())
+            return;
+        preservingSelection(new class Runnable {
+            Object[] elementsOrTreePaths_;
+            this(){
+                elementsOrTreePaths_=elementsOrTreePaths;
+            }
+            public void run() {
+                internalRemove(elementsOrTreePaths_);
+            }
+        });
+    }
+
+    /**
+     * Removes the given elements from this viewer whenever they appear as
+     * children of the given parent element. If the given elements also appear
+     * as children of some other parent, the other parent will remain unchanged.
+     * The selection is updated if required.
+     * <p>
+     * This method should be called (by the content provider) when elements have
+     * been removed from the model, in order to cause the viewer to accurately
+     * reflect the model. This method only affects the viewer, not the model.
+     * </p>
+     *
+     * @param parent
+     *            the parent of the elements to remove
+     * @param elements
+     *            the elements to remove
+     *
+     * @since 3.2
+     */
+    public void remove(Object parent, Object[] elements) {
+        assertElementsNotNull(elements);
+        if (elements.length is 0) {
+            return;
+        }
+        if (isBusy())
+            return;
+        preservingSelection(new class Runnable {
+            Object parent_;
+            Object[] elements_;
+            this(){
+                parent_=parent;
+                elements_=elements;
+            }
+            public void run() {
+                internalRemove(parent_, elements_);
+            }
+        });
+    }
+
+    /**
+     * Removes the given element from the viewer. The selection is updated if
+     * necessary.
+     * <p>
+     * This method should be called (by the content provider) when a single
+     * element has been removed from the model, in order to cause the viewer to
+     * accurately reflect the model. This method only affects the viewer, not
+     * the model. Note that there is another method for efficiently processing
+     * the simultaneous removal of multiple elements.
+     * </p>
+     *
+     * @param elementsOrTreePaths
+     *            the element
+     */
+    public void remove(Object elementsOrTreePaths) {
+        remove([ elementsOrTreePaths ]);
+    }
+
+    /**
+     * Removes all items from the given control.
+     *
+     * @param control
+     *            the control
+     */
+    protected abstract void removeAll(Control control);
+
+    /**
+     * Removes a listener for expand and collapse events in this viewer. Has no
+     * affect if an identical listener is not registered.
+     *
+     * @param listener
+     *            a tree viewer listener
+     */
+    public void removeTreeListener(ITreeViewerListener listener) {
+        treeListeners.remove(cast(Object)listener);
+    }
+
+    /**
+     * This implementation of reveal() reveals the given element or tree path.
+     */
+    public void reveal(Object elementOrTreePath) {
+        Assert.isNotNull(elementOrTreePath);
+        Widget w = internalExpand(elementOrTreePath, true);
+        if ( auto item = cast(Item)w ) {
+            showItem(item);
+        }
+    }
+
+    /**
+     * Returns the rightmost visible descendent of the given item. Returns the
+     * item itself if it has no children.
+     *
+     * @param item
+     *            the item to compute the descendent of
+     * @return the rightmost visible descendent or the item itself if it has no
+     *         children
+     */
+    private Item rightMostVisibleDescendent(Item item) {
+        Item[] children = getItems(item);
+        if (getExpanded(item) && children !is null && children.length > 0) {
+            return rightMostVisibleDescendent(children[children.length - 1]);
+        }
+        return item;
+    }
+
+    /* (non-Javadoc) Method declared on Viewer. */
+    public Item scrollDown(int x, int y) {
+        Item current = getItem(x, y);
+        if (current !is null) {
+            Item next = getNextItem(current, true);
+            showItem(next is null ? current : next);
+            return next;
+        }
+        return null;
+    }
+
+    /* (non-Javadoc) Method declared on Viewer. */
+    public Item scrollUp(int x, int y) {
+        Item current = getItem(x, y);
+        if (current !is null) {
+            Item previous = getPreviousItem(current);
+            showItem(previous is null ? current : previous);
+            return previous;
+        }
+        return null;
+    }
+
+    /**
+     * Sets the auto-expand level. The value 0 means that there is no
+     * auto-expand; 1 means that top-level elements are expanded, but not their
+     * children; 2 means that top-level elements are expanded, and their
+     * children, but not grandchildren; and so on.
+     * <p>
+     * The value <code>ALL_LEVELS</code> means that all subtrees should be
+     * expanded.
+     * </p>
+     *
+     * @param level
+     *            non-negative level, or <code>ALL_LEVELS</code> to expand all
+     *            levels of the tree
+     */
+    public void setAutoExpandLevel(int level) {
+        expandToLevel_ = level;
+    }
+
+    /**
+     * The <code>AbstractTreeViewer</code> implementation of this method
+     * checks to ensure that the content provider is an
+     * <code>ITreeContentProvider</code>.
+     */
+    public void setContentProvider(IContentProvider provider) {
+        // the actual check is in assertContentProviderType
+        super.setContentProvider(provider);
+    }
+
+    protected void assertContentProviderType(IContentProvider provider) {
+        Assert.isTrue(cast(ITreeContentProvider)provider
+                || cast(ITreePathContentProvider)provider );
+    }
+
+    /**
+     * Sets the expand state of the given item.
+     *
+     * @param item
+     *            the item
+     * @param expand
+     *            the expand state of the item
+     */
+    protected abstract void setExpanded(Item item, bool expand);
+
+    /**
+     * Sets which nodes are expanded in this viewer's tree. The given list
+     * contains the elements that are to be expanded; all other nodes are to be
+     * collapsed.
+     * <p>
+     * This method is typically used when restoring the interesting state of a
+     * viewer captured by an earlier call to <code>getExpandedElements</code>.
+     * </p>
+     *
+     * @param elements
+     *            the array of expanded elements
+     * @see #getExpandedElements
+     */
+    public void setExpandedElements(Object[] elements) {
+        assertElementsNotNull(elements);
+        if (isBusy()) {
+            return;
+        }
+        CustomHashtable expandedElements = newHashtable(elements.length * 2 + 1);
+        for (int i = 0; i < elements.length; ++i) {
+            Object element = elements[i];
+            // Ensure item exists for element. This will materialize items for
+            // each element and their parents, if possible. This is important
+            // to support expanding of inner tree nodes without necessarily
+            // expanding their parents.
+            internalExpand(element, false);
+            expandedElements.put(element, element);
+        }
+        // this will traverse all existing items, and create children for
+        // elements that need to be expanded. If the tree contains multiple
+        // equal elements, and those are in the set of elements to be expanded,
+        // only the first item found for each element will be expanded.
+        internalSetExpanded(expandedElements, getControl());
+    }
+
+    /**
+     * Sets which nodes are expanded in this viewer's tree. The given list
+     * contains the tree paths that are to be expanded; all other nodes are to
+     * be collapsed.
+     * <p>
+     * This method is typically used when restoring the interesting state of a
+     * viewer captured by an earlier call to <code>getExpandedTreePaths</code>.
+     * </p>
+     *
+     * @param treePaths
+     *            the array of expanded tree paths
+     * @see #getExpandedTreePaths()
+     *
+     * @since 3.2
+     */
+    public void setExpandedTreePaths(TreePath[] treePaths) {
+        assertElementsNotNull(treePaths);
+        if (isBusy())
+            return;
+        IElementComparer treePathComparer = new class IElementComparer {
+            IElementComparer comparer;
+            this(){
+                comparer = getComparer();
+            }
+            public int opEquals(Object a, Object b) {
+                return (cast(TreePath) a).opEquals((cast(TreePath) b), comparer);
+            }
+
+            public hash_t toHash(Object element) {
+                return (cast(TreePath) element).toHash(comparer);
+            }
+        };
+        CustomHashtable expandedTreePaths = new CustomHashtable(
+                treePaths.length * 2 + 1, treePathComparer);
+        for (int i = 0; i < treePaths.length; ++i) {
+            TreePath treePath = treePaths[i];
+            // Ensure item exists for element. This will materialize items for
+            // each element and their parents, if possible. This is important
+            // to support expanding of inner tree nodes without necessarily
+            // expanding their parents.
+            internalExpand(treePath, false);
+            expandedTreePaths.put(treePath, treePath);
+        }
+        // this will traverse all existing items, and create children for
+        // elements that need to be expanded. If the tree contains multiple
+        // equal elements, and those are in the set of elements to be expanded,
+        // only the first item found for each element will be expanded.
+        internalSetExpandedTreePaths(expandedTreePaths, getControl(),
+                new TreePath(new Object[0]));
+    }
+
+    /**
+     * Sets whether the node corresponding to the given element or tree path is
+     * expanded or collapsed.
+     *
+     * @param elementOrTreePath
+     *            the element
+     * @param expanded
+     *            <code>true</code> if the node is expanded, and
+     *            <code>false</code> if collapsed
+     */
+    public void setExpandedState(Object elementOrTreePath, bool expanded) {
+        Assert.isNotNull(elementOrTreePath);
+        if (isBusy())
+            return;
+        Widget item = internalExpand(elementOrTreePath, false);
+        if ( cast(Item)item ) {
+            if (expanded) {
+                createChildren(item);
+            }
+            setExpanded(cast(Item) item, expanded);
+        }
+    }
+
+    /**
+     * Sets the selection to the given list of items.
+     *
+     * @param items
+     *            list of items (element type:
+     *            <code>dwt.widgets.Item</code>)
+     */
+    protected abstract void setSelection(SeqView!(Item) items);
+
+    /**
+     * This implementation of setSelectionToWidget accepts a list of elements or
+     * a list of tree paths.
+     */
+    protected void setSelectionToWidget(SeqView!(Object) v, bool reveal) {
+        if (v is null) {
+            setSelection(new ArraySeq!(Item));
+            return;
+        }
+        int size = v.size();
+        auto newSelection = new ArraySeq!(Item);
+        newSelection.capacity(size);
+        for (int i = 0; i < size; ++i) {
+            Object elementOrTreePath = v.get(i);
+            // Use internalExpand since item may not yet be created. See
+            // 1G6B1AR.
+            Widget w = internalExpand(elementOrTreePath, false);
+            if ( auto it = cast(Item)w ) {
+                newSelection.append(it);
+            } else if (w is null && null !is cast(TreePath)elementOrTreePath ) {
+                TreePath treePath = cast(TreePath) elementOrTreePath;
+                Object element = treePath.getLastSegment();
+                if (element !is null) {
+                    w = internalExpand(element, false);
+                    if ( auto it = cast(Item)w ) {
+                        newSelection.append(it);
+                    }
+                }
+            }
+        }
+        setSelection(newSelection);
+
+        // Although setting the selection in the control should reveal it,
+        // setSelection may be a no-op if the selection is unchanged,
+        // so explicitly reveal the first item in the selection here.
+        // See bug 100565 for more details.
+        if (reveal && newSelection.size() > 0) {
+            showItem(cast(Item) newSelection.get(0));
+        }
+    }
+
+    /**
+     * Shows the given item.
+     *
+     * @param item
+     *            the item
+     */
+    protected abstract void showItem(Item item);
+
+    /**
+     * Updates the tree items to correspond to the child elements of the given
+     * parent element. If null is passed for the children, this method obtains
+     * them (only if needed).
+     *
+     * @param widget
+     *            the widget
+     * @param parent
+     *            the parent element
+     * @param elementChildren
+     *            the child elements, or null
+     * @deprecated this is no longer called by the framework
+     */
+    protected void updateChildren(Widget widget, Object parent,
+            Object[] elementChildren) {
+        updateChildren(widget, parent, elementChildren, true);
+    }
+
+    /**
+     * Updates the tree items to correspond to the child elements of the given
+     * parent element. If null is passed for the children, this method obtains
+     * them (only if needed).
+     *
+     * @param widget
+     *            the widget
+     * @param parent
+     *            the parent element
+     * @param elementChildren
+     *            the child elements, or null
+     * @param updateLabels
+     *            <code>true</code> to update labels for existing elements,
+     *            <code>false</code> to only update labels as needed, assuming
+     *            that labels for existing elements are unchanged.
+     * @since 2.1
+     */
+    private void updateChildren(Widget widget, Object parent,
+            Object[] elementChildren, bool updateLabels) {
+        // optimization! prune collapsed subtrees
+        if (auto ti = cast(Item)widget ) {
+            if (!getExpanded(ti)) {
+                // need a dummy node if element is expandable;
+                // but try to avoid recreating the dummy node
+                bool needDummy = isExpandable(ti, null, parent);
+                bool haveDummy = false;
+                // remove all children
+                Item[] items = getItems(ti);
+                for (int i = 0; i < items.length; i++) {
+                    if (items[i].getData() !is null) {
+                        disassociate(items[i]);
+                        items[i].dispose();
+                    } else {
+                        if (needDummy && !haveDummy) {
+                            haveDummy = true;
+                        } else {
+                            items[i].dispose();
+                        }
+                    }
+                }
+                if (needDummy && !haveDummy) {
+                    newItem(ti, DWT.NULL, -1);
+                }
+
+                return;
+            }
+        }
+
+        // If the children weren't passed in, get them now since they're needed
+        // below.
+        if (elementChildren is null) {
+            if (isTreePathContentProvider() && null !is cast(Item) widget ) {
+                TreePath path = getTreePathFromItem(cast(Item) widget);
+                elementChildren = getSortedChildren(path);
+            } else {
+                elementChildren = getSortedChildren(parent);
+            }
+        }
+
+        Control tree = getControl();
+
+        // WORKAROUND
+        int oldCnt = -1;
+        if (widget is tree) {
+            oldCnt = getItemCount(tree);
+        }
+
+        Item[] items = getChildren(widget);
+
+        // save the expanded elements
+        CustomHashtable expanded = newHashtable(CustomHashtable.DEFAULT_CAPACITY); // assume
+                                                                                    // num
+                                                                                    // expanded
+                                                                                    // is
+                                                                                    // small
+        for (int i = 0; i < items.length; ++i) {
+            if (getExpanded(items[i])) {
+                Object element = items[i].getData();
+                if (element !is null) {
+                    expanded.put(element, element);
+                }
+            }
+        }
+
+        int min = Math.min(elementChildren.length, items.length);
+
+        // dispose of surplus items, optimizing for the case where elements have
+        // been deleted but not reordered, or all elements have been removed.
+        int numItemsToDispose = items.length - min;
+        if (numItemsToDispose > 0) {
+            CustomHashtable children = newHashtable(elementChildren.length * 2);
+            for (int i = 0; i < elementChildren.length; i++) {
+                Object elementChild = elementChildren[i];
+                children.put(elementChild, elementChild);
+            }
+            int i = 0;
+            while (numItemsToDispose > 0 && i < items.length) {
+                Object data = items[i].getData();
+                if (data is null || items.length - i <= numItemsToDispose || !children.containsKey(data)) {
+                    if (data !is null) {
+                        disassociate(items[i]);
+                    }
+                    items[i].dispose();
+                    if (i + 1 < items.length) {
+                        // The components at positions i+1 through
+                        // items.length-1 in the source array are copied into
+                        // positions i through items.length-2
+                        System.arraycopy(items, i + 1, items, i, items.length - (i+1));
+                    }
+                    numItemsToDispose--;
+                } else {
+                    i++;
+                }
+            }
+        }
+
+        // compare first min items, and update item if necessary
+        // need to do it in two passes:
+        // 1: disassociate old items
+        // 2: associate new items
+        // because otherwise a later disassociate can remove a mapping made for
+        // a previous associate,
+        // making the map inconsistent
+        for (int i = 0; i < min; ++i) {
+            Item item = items[i];
+            Object oldElement = item.getData();
+            if (oldElement !is null) {
+                Object newElement = elementChildren[i];
+                if (newElement !is oldElement) {
+                    if (opEquals(newElement, oldElement)) {
+                        // update the data to be the new element, since
+                        // although the elements
+                        // may be equal, they may still have different labels
+                        // or children
+                        Object data = item.getData();
+                        if (data !is null) {
+                            unmapElement(data, item);
+                        }
+                        item.setData(newElement);
+                        mapElement(newElement, item);
+                    } else {
+                        disassociate(item);
+                        // Clear the text and image to force a label update
+                        item.setImage(null);
+                        item.setText("");//$NON-NLS-1$
+
+                    }
+                }
+            }
+        }
+
+        for (int i = 0; i < min; ++i) {
+            Item item = items[i];
+            Object newElement = elementChildren[i];
+            if (item.getData() is null) {
+                // old and new elements are not equal
+                associate(newElement, item);
+                updatePlus(item, newElement);
+                updateItem(item, newElement);
+            } else {
+                // old and new elements are equal
+                updatePlus(item, newElement);
+                if (updateLabels) {
+                    updateItem(item, newElement);
+                }
+            }
+        }
+
+        // Restore expanded state for items that changed position.
+        // Make sure setExpanded is called after updatePlus, since
+        // setExpanded(false) fails if item has no children.
+        // Need to call setExpanded for both expanded and unexpanded
+        // cases since the expanded state can change either way.
+        // This needs to be done in a second loop, see bug 148025.
+        for (int i = 0; i < min; ++i) {
+            Item item = items[i];
+            Object newElement = elementChildren[i];
+            setExpanded(item, expanded.containsKey(newElement));
+        }
+
+        // add any remaining elements
+        if (min < elementChildren.length) {
+            for (int i = min; i < elementChildren.length; ++i) {
+                createTreeItem(widget, elementChildren[i], i);
+            }
+
+            // Need to restore expanded state in a separate pass
+            // because createTreeItem does not return the new item.
+            // Avoid doing this unless needed.
+            if (expanded.size() > 0) {
+                // get the items again, to include the new items
+                items = getChildren(widget);
+                for (int i = min; i < elementChildren.length; ++i) {
+                    // Restore expanded state for items that changed position.
+                    // Make sure setExpanded is called after updatePlus (called
+                    // in createTreeItem), since
+                    // setExpanded(false) fails if item has no children.
+                    // Only need to call setExpanded if element was expanded
+                    // since new items are initially unexpanded.
+                    if (expanded.containsKey(elementChildren[i])) {
+                        setExpanded(items[i], true);
+                    }
+                }
+            }
+        }
+
+        // WORKAROUND
+        if (widget is tree && oldCnt is 0 && getItemCount(tree) !is 0) {
+            // System.out.println("WORKAROUND setRedraw");
+            tree.setRedraw(false);
+            tree.setRedraw(true);
+        }
+    }
+
+    /**
+     * Updates the "+"/"-" icon of the tree node from the given element. It
+     * calls <code>isExpandable</code> to determine whether an element is
+     * expandable.
+     *
+     * @param item
+     *            the item
+     * @param element
+     *            the element
+     */
+    protected void updatePlus(Item item, Object element) {
+        bool hasPlus = getItemCount(item) > 0;
+        bool needsPlus = isExpandable(item, null, element);
+        bool removeAll = false;
+        bool addDummy = false;
+        Object data = item.getData();
+        if (data !is null && opEquals(element, data)) {
+            // item shows same element
+            if (hasPlus !is needsPlus) {
+                if (needsPlus) {
+                    addDummy = true;
+                } else {
+                    removeAll = true;
+                }
+            }
+        } else {
+            // item shows different element
+            removeAll = true;
+            addDummy = needsPlus;
+
+            // we cannot maintain expand state so collapse it
+            setExpanded(item, false);
+        }
+        if (removeAll) {
+            // remove all children
+            Item[] items = getItems(item);
+            for (int i = 0; i < items.length; i++) {
+                if (items[i].getData() !is null) {
+                    disassociate(items[i]);
+                }
+                items[i].dispose();
+            }
+        }
+        if (addDummy) {
+            newItem(item, DWT.NULL, -1); // append a dummy
+        }
+    }
+
+    /**
+     * Gets the expanded elements that are visible to the user. An expanded
+     * element is only visible if the parent is expanded.
+     *
+     * @return the visible expanded elements
+     * @since 2.0
+     */
+    public Object[] getVisibleExpandedElements() {
+        auto v = new ArraySeq!(Object);
+        internalCollectVisibleExpanded(v, getControl());
+        return v.toArray();
+    }
+
+    private void internalCollectVisibleExpanded(ArraySeq!(Object) result, Widget widget) {
+        Item[] items = getChildren(widget);
+        for (int i = 0; i < items.length; i++) {
+            Item item = items[i];
+            if (getExpanded(item)) {
+                Object data = item.getData();
+                if (data !is null) {
+                    result.append(data);
+                }
+                // Only recurse if it is expanded - if
+                // not then the children aren't visible
+                internalCollectVisibleExpanded(result, item);
+            }
+        }
+    }
+
+    /**
+     * Returns the tree path for the given item.
+     * @param item
+     * @return {@link TreePath}
+     *
+     * @since 3.2
+     */
+    protected TreePath getTreePathFromItem(Item item) {
+        auto segments = new LinkSeq!(Object);
+        while (item !is null) {
+            Object segment = item.getData();
+            Assert.isNotNull(segment);
+            segments.prepend(segment);
+            item = getParentItem(item);
+        }
+        return new TreePath(segments.toArray());
+    }
+    package TreePath getTreePathFromItem_package(Item item) {
+        return getTreePathFromItem_package(item);
+    }
+
+    /**
+     * This implementation of getSelection() returns an instance of
+     * ITreeSelection.
+     *
+     * @since 3.2
+     */
+    public ISelection getSelection() {
+        Control control = getControl();
+        if (control is null || control.isDisposed()) {
+            return TreeSelection.EMPTY;
+        }
+        Widget[] items = getSelection(getControl());
+        auto list = new ArraySeq!(TreePath);
+        list.capacity(items.length);
+        for (int i = 0; i < items.length; i++) {
+            Widget item = items[i];
+            if (item.getData() !is null) {
+                list.append(getTreePathFromItem(cast(Item) item));
+            }
+        }
+        return new TreeSelection( list.toArray(), getComparer());
+    }
+
+    protected void setSelectionToWidget(ISelection selection, bool reveal) {
+        if ( auto treeSelection = cast(ITreeSelection)selection ) {
+            auto list = new ArraySeq!(Object);
+            auto paths = treeSelection.getPaths();
+            list.capacity(paths.length);
+            foreach( path; paths ){
+                list.append(path);
+            }
+            setSelectionToWidget(list, reveal);
+        } else {
+            super.setSelectionToWidget(selection, reveal);
+        }
+    }
+
+    /**
+     * Returns a list of tree paths corresponding to expanded nodes in this
+     * viewer's tree, including currently hidden ones that are marked as
+     * expanded but are under a collapsed ancestor.
+     * <p>
+     * This method is typically used when preserving the interesting state of a
+     * viewer; <code>setExpandedElements</code> is used during the restore.
+     * </p>
+     *
+     * @return the array of expanded tree paths
+     * @see #setExpandedElements
+     *
+     * @since 3.2
+     */
+    public TreePath[] getExpandedTreePaths() {
+        auto items = new ArraySeq!(Item);
+        internalCollectExpandedItems(items, getControl());
+        auto result = new ArraySeq!(TreePath);
+        result.capacity(items.size());
+        foreach( item; items ){
+            TreePath treePath = getTreePathFromItem(item);
+            if (treePath !is null) {
+                result.append(treePath);
+            }
+        }
+        return result.toArray();
+    }
+
+    private bool isTreePathContentProvider() {
+        return null !is cast(ITreePathContentProvider)getContentProvider() ;
+    }
+
+    /**
+     * Inserts the given element as a new child element of the given parent
+     * element at the given position. If this viewer has a sorter, the position
+     * is ignored and the element is inserted at the correct position in the
+     * sort order.
+     * <p>
+     * This method should be called (by the content provider) when elements have
+     * been added to the model, in order to cause the viewer to accurately
+     * reflect the model. This method only affects the viewer, not the model.
+     * </p>
+     *
+     * @param parentElementOrTreePath
+     *            the parent element, or the tree path to the parent
+     * @param element
+     *            the element
+     * @param position
+     *            a 0-based position relative to the model, or -1 to indicate
+     *            the last position
+     *
+     * @since 3.2
+     */
+    public void insert(Object parentElementOrTreePath, Object element,
+            int position) {
+        Assert.isNotNull(parentElementOrTreePath);
+        Assert.isNotNull(element);
+        if (isBusy())
+            return;
+        if (getComparator() !is null || hasFilters()) {
+            add(parentElementOrTreePath, [ element ]);
+            return;
+        }
+        Widget[] items;
+        if (internalIsInputOrEmptyPath(parentElementOrTreePath)) {
+            items = [ getControl() ];
+        } else {
+            items = internalFindItems(parentElementOrTreePath);
+        }
+
+        for (int i = 0; i < items.length; i++) {
+            Widget widget = items[i];
+            if (auto item = cast(Item)widget ) {
+
+                Item[] childItems = getChildren(item);
+                if (getExpanded(item)
+                        || (childItems.length > 0 && childItems[0].getData() !is null)) {
+                    // item has real children, go ahead and add
+                    int insertionPosition = position;
+                    if (insertionPosition is -1) {
+                        insertionPosition = getItemCount(item);
+                    }
+
+                    createTreeItem(item, element, insertionPosition);
+                }
+            } else {
+                int insertionPosition = position;
+                if (insertionPosition is -1) {
+                    insertionPosition = getItemCount(cast(Control) widget);
+                }
+
+                createTreeItem(widget, element, insertionPosition);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see dwtx.jface.viewers.ColumnViewer#getColumnViewerOwner(int)
+     */
+    protected Widget getColumnViewerOwner(int columnIndex) {
+        // Return null by default
+        return null;
+    }
+
+    /**
+     * This implementation of {@link #getItemAt(Point)} returns null to ensure
+     * API backwards compatibility. Subclasses should override.
+     *
+     * @since 3.3
+     */
+    protected Item getItemAt(Point point) {
+        return null;
+    }
+
+    /**
+     * This implementation of {@link #createViewerEditor()} returns null to ensure
+     * API backwards compatibility. Subclasses should override.
+     *
+     * @since 3.3
+     */
+    protected ColumnViewerEditor createViewerEditor() {
+        return null;
+    }
+
+    /**
+     * Returns the number of columns of this viewer.
+     * <p><b>Subclasses should overwrite this method, which has a default
+     * implementation (returning 0) for API backwards compatility reasons</b></p>
+     *
+     * @return the number of columns
+     *
+     * @since 3.3
+     */
+    protected int doGetColumnCount() {
+        return 0;
+    }
+
+
+    /**
+     * This implementation of buildLabel handles tree paths as well as elements.
+     *
+     * @param updateLabel
+     *            the ViewerLabel to collect the result in
+     * @param elementOrPath
+     *            the element or tree path for which a label should be built
+     *
+     * @see dwtx.jface.viewers.StructuredViewer#buildLabel(dwtx.jface.viewers.ViewerLabel,
+     *      java.lang.Object)
+     */
+    protected void buildLabel(ViewerLabel updateLabel, Object elementOrPath) {
+        Object element;
+        if (auto path = cast(TreePath)elementOrPath ) {
+            IBaseLabelProvider provider = getLabelProvider();
+            if ( auto pprov = cast(ITreePathLabelProvider) provider ) {
+                buildLabel(updateLabel, path, pprov);
+                return;
+            }
+            element = path.getLastSegment();
+        } else {
+            element = elementOrPath;
+        }
+        super.buildLabel(updateLabel, element);
+    }
+
+    /**
+     * Returns true if the given object is either the input or an empty tree path.
+     *
+     * @param elementOrTreePath an element which could either be the viewer's input, or a tree path
+     *
+     * @return <code>true</code> if the given object is either the input or an empty tree path,
+     * <code>false</code> otherwise.
+     * @since 3.3
+     */
+    final protected bool internalIsInputOrEmptyPath(Object elementOrTreePath) {
+        if (elementOrTreePath.opEquals(getInput()))
+            return true;
+        if (!(cast(TreePath)elementOrTreePath ))
+            return false;
+        return (cast(TreePath) elementOrTreePath).getSegmentCount() is 0;
+    }
+
+    /*
+     * Subclasses should implement
+     */
+    protected ViewerRow getViewerRowFromItem(Widget item) {
+        return null;
+    }
+}