org.eclipse.swt.widgets.Tree.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.widgets.Tree.java

Source

/*******************************************************************************
 * Copyright (c) 2002, 2015 Innoopract Informationssysteme GmbH 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:
 *    Innoopract Informationssysteme GmbH - initial API and implementation
 *    EclipseSource - ongoing development
 ******************************************************************************/
package org.eclipse.swt.widgets;

import static org.eclipse.rap.rwt.internal.textsize.TextSizeUtil.stringExtent;
import static org.eclipse.swt.internal.widgets.MarkupUtil.isMarkupEnabledFor;
import static org.eclipse.swt.internal.widgets.MarkupUtil.isToolTipMarkupEnabledFor;
import static org.eclipse.swt.internal.widgets.MarkupValidator.isValidationDisabledFor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.internal.lifecycle.WidgetLCA;
import org.eclipse.rap.rwt.internal.textsize.TextSizeUtil;
import org.eclipse.rap.rwt.internal.theme.Size;
import org.eclipse.rap.rwt.internal.theme.ThemeAdapter;
import org.eclipse.rap.rwt.template.Template;
import org.eclipse.rap.rwt.theme.BoxDimensions;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.SerializableCompatibility;
import org.eclipse.swt.internal.widgets.ICellToolTipAdapter;
import org.eclipse.swt.internal.widgets.ICellToolTipProvider;
import org.eclipse.swt.internal.widgets.IControlAdapter;
import org.eclipse.swt.internal.widgets.IItemHolderAdapter;
import org.eclipse.swt.internal.widgets.ITreeAdapter;
import org.eclipse.swt.internal.widgets.ItemHolder;
import org.eclipse.swt.internal.widgets.MarkupValidator;
import org.eclipse.swt.internal.widgets.WidgetTreeUtil;
import org.eclipse.swt.internal.widgets.WidgetTreeVisitor;
import org.eclipse.swt.internal.widgets.treekit.TreeLCA;
import org.eclipse.swt.internal.widgets.treekit.TreeThemeAdapter;

/**
 * Instances of this class provide a selectable user interface object that
 * displays a hierarchy of items and issues notification when an item in the
 * hierarchy is selected.
 * <p>
 * The item children that may be added to instances of this class must be of
 * type <code>TreeItem</code>.
 * </p>
 * <p>
 * Style <code>VIRTUAL</code> is used to create a <code>Tree</code> whose
 * <code>TreeItem</code>s are to be populated by the client on an on-demand
 * basis instead of up-front. This can provide significant performance
 * improvements for trees that are very large or for which <code>TreeItem</code>
 * population is expensive (for example, retrieving values from an external
 * source).
 * </p>
 * <p>
 * Here is an example of using a <code>Tree</code> with style
 * <code>VIRTUAL</code>: <code><pre>
 *  final Tree tree = new Tree(parent, SWT.VIRTUAL | SWT.BORDER);
 *  tree.setItemCount(20);
 *  tree.addListener(SWT.SetData, new Listener() {
 *      public void handleEvent(Event event) {
 *          TreeItem item = (TreeItem)event.item;
 *          TreeItem parentItem = item.getParentItem();
 *          String text = null;
 *          if (parentItem == null) {
 *              text = "node " + tree.indexOf(item);
 *          } else {
 *              text = parentItem.getText() + " - " + parentItem.indexOf(item);
 *          }
 *          item.setText(text);
 *          System.out.println(text);
 *          item.setItemCount(10);
 *      }
 *  });
 * </pre></code>
 * </p>
 * <p>
 * Note that although this class is a subclass of <code>Composite</code>, it
 * does not make sense to add <code>Control</code> children to it, or set a
 * layout on it.
 * </p>
 * <p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>SINGLE, MULTI, CHECK, FULL_SELECTION, VIRTUAL, NO_SCROLL</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection, DefaultSelection, Collapse, Expand, SetData<!--, MeasureItem,
 * EraseItem, PaintItem--></dd>
 * </dl>
 * </p>
 * <p>
 * Note: Only one of the styles SINGLE and MULTI may be specified.
 * </p>
 * <p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @since 1.0
 */
public class Tree extends Composite {

    private static final TreeItem[] EMPTY_SELECTION = new TreeItem[0];
    // This values must be kept in sync with appearance of list items
    private static final int MIN_ITEM_HEIGHT = 16;
    private static final int GRID_WIDTH = 1;

    private static final Rectangle TEXT_MARGIN = new Rectangle(3, 0, 8, 0);

    private int itemCount;
    private int customItemHeight;
    private TreeItem[] items;
    final ItemHolder<TreeColumn> columnHolder;
    private TreeItem[] selection;
    private boolean linesVisible;
    private int[] columnOrder;
    private int itemImageCount;
    private TreeColumn sortColumn;
    private int sortDirection;
    private boolean headerVisible;
    private final ITreeAdapter treeAdapter;
    private int scrollLeft;
    private int topItemIndex;
    private boolean hasVScrollBar;
    private boolean hasHScrollBar;
    private Point itemImageSize;
    LayoutCache layoutCache;
    boolean isFlatIndexValid;
    private int visibleItemsCount;
    private int preloadedItems;

    /**
     * Constructs a new instance of this class given its parent and a style value
     * describing its behavior and appearance.
     * <p>
     * The style value is either one of the style constants defined in class
     * <code>SWT</code> which is applicable to instances of this class, or must be
     * built by <em>bitwise OR</em>'ing together (that is, using the
     * <code>int</code> "|" operator) two or more of those <code>SWT</code> style
     * constants. The class description lists the style constants that are
     * applicable to the class. Style bits are also inherited from superclasses.
     * </p>
     *
     * @param parent a composite control which will be the parent of the new
     *          instance (cannot be null)
     * @param style the style of control to construct
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the parent</li>
     *              <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed
     *              subclass</li>
     *              </ul>
     * @see SWT#SINGLE
     * @see SWT#MULTI
     * @see SWT#CHECK
     * @see SWT#FULL_SELECTION
     * @see SWT#NO_SCROLL
     * @see Widget#checkSubclass
     * @see Widget#getStyle
     */
    public Tree(Composite parent, int style) {
        super(parent, checkStyle(style));
        columnHolder = new ItemHolder<>(TreeColumn.class);
        treeAdapter = new InternalTreeAdapter();
        setTreeEmpty();
        sortDirection = SWT.NONE;
        selection = EMPTY_SELECTION;
        customItemHeight = -1;
        layoutCache = new LayoutCache();
    }

    TreeItem[] getCreatedItems() {
        TreeItem[] result;
        if (isVirtual()) {
            int count = 0;
            for (int i = 0; i < itemCount; i++) {
                if (items[i] != null) {
                    count++;
                }
            }
            result = new TreeItem[count];
            count = 0;
            for (int i = 0; i < itemCount; i++) {
                if (items[i] != null) {
                    result[count] = items[i];
                    count++;
                }
            }
        } else {
            result = new TreeItem[itemCount];
            System.arraycopy(items, 0, result, 0, itemCount);
        }
        return result;
    }

    private void setTreeEmpty() {
        items = new TreeItem[4];
        // TODO: Not sure if we have to clear the image size???!!!
        //    clearItemImageSize();
    }

    @Override
    void initState() {
        removeState( /* CANVAS | */THEME_BACKGROUND);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getAdapter(Class<T> adapter) {
        if (adapter == IItemHolderAdapter.class) {
            return (T) new CompositeItemHolder();
        }
        if (adapter == ITreeAdapter.class || adapter == ICellToolTipAdapter.class) {
            return (T) treeAdapter;
        }
        if (adapter == WidgetLCA.class) {
            return (T) TreeLCA.INSTANCE;
        }
        return super.getAdapter(adapter);
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        for (int i = 0; i < itemCount; i++) {
            if (items[i] != null) {
                items[i].clearPreferredWidthBuffers(true);
            }
        }
        clearCachedHeights();
        updateScrollBars();
    }

    // /////////////////////////
    // Methods to manage items
    /**
     * Sets the number of root-level items contained in the receiver.
     *
     * @param count the number of items
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void setItemCount(int count) {
        checkWidget();
        int oldItemCount = itemCount;
        int newItemCount = Math.max(0, count);
        if (newItemCount != oldItemCount) {
            int deleteIndex = oldItemCount - 1;
            while (deleteIndex >= newItemCount) {
                TreeItem item = items[deleteIndex];
                if (item != null && !item.isDisposed()) {
                    item.dispose();
                } else {
                    destroyItem(deleteIndex);
                }
                deleteIndex--;
            }
            int length = Math.max(4, (newItemCount + 3) / 4 * 4);
            TreeItem[] newItems = new TreeItem[length];
            System.arraycopy(items, 0, newItems, 0, Math.min(newItemCount, itemCount));
            items = newItems;
            if (!isVirtual()) {
                for (int i = itemCount; i < newItemCount; i++) {
                    items[i] = new TreeItem(this, SWT.NONE, i);
                }
            }
            itemCount = newItemCount;
            invalidateFlatIndex();
            updateScrollBars();
            redraw();
        }
    }

    /**
     * Returns the number of items contained in the receiver that are direct item
     * children of the receiver. The number that is returned is the number of
     * roots in the tree.
     *
     * @return the number of items
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public int getItemCount() {
        checkWidget();
        return itemCount;
    }

    /**
     * Returns a (possibly empty) array of items contained in the receiver that
     * are direct item children of the receiver. These are the roots of the tree.
     * <p>
     * Note: This is not the actual structure used by the receiver to maintain its
     * list of items, so modifying the array will not affect the receiver.
     * </p>
     *
     * @return the items
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public TreeItem[] getItems() {
        checkWidget();
        TreeItem[] result = new TreeItem[itemCount];
        if (isVirtual()) {
            for (int i = 0; i < itemCount; i++) {
                result[i] = _getItem(i);
            }
        } else {
            System.arraycopy(items, 0, result, 0, itemCount);
        }
        return result;
    }

    private TreeItem _getItem(int index) {
        if (isVirtual() && items[index] == null) {
            items[index] = new TreeItem(this, null, SWT.NONE, index, false);
        }
        return items[index];
    }

    /**
     * Returns the item at the given, zero-relative index in the receiver. Throws
     * an exception if the index is out of range.
     *
     * @param index the index of the item to return
     * @return the item at the given index
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_INVALID_RANGE - if the index is not between 0 and
     *              the number of elements in the list minus 1 (inclusive)</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public TreeItem getItem(int index) {
        checkWidget();
        if (index < 0 || index >= itemCount) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        return _getItem(index);
    }

    /**
     * Searches the receiver's list starting at the first item (index 0) until an
     * item is found that is equal to the argument, and returns the index of that
     * item. If no item is found, returns -1.
     *
     * @param item the search item
     * @return the index of the item
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the tool item is null</li>
     *              <li>ERROR_INVALID_ARGUMENT - if the tool item has been
     *              disposed</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public int indexOf(TreeItem item) {
        checkWidget();
        if (item == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (item.isDisposed()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return item.parent == this ? item.index : -1;
    }

    /**
     * Returns the receiver's parent item, which must be a <code>TreeItem</code>
     * or null when the receiver is a root.
     *
     * @return the receiver's parent item
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public TreeItem getParentItem() {
        checkWidget();
        return null;
    }

    /**
     * Removes all of the items from the receiver.
     *
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void removeAll() {
        checkWidget();
        for (int i = itemCount - 1; i >= 0; i--) {
            if (items[i] != null) {
                items[i].dispose();
            } else {
                itemCount--;
            }
        }
        setTreeEmpty();
        selection = EMPTY_SELECTION;
    }

    /**
     * Shows the item. If the item is already showing in the receiver, this method
     * simply returns. Otherwise, the items are scrolled and expanded until the
     * item is visible.
     *
     * @param item the item to be shown
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     *              <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see Tree#showSelection()
     */
    public void showItem(TreeItem item) {
        checkWidget();
        if (item == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (item.isDisposed()) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (item.getParent() == this) {
            TreeItem parent = item.getParentItem();
            while (parent != null) {
                parent.setExpanded(true);
                parent = parent.getParentItem();
            }
            int flatIndex = item.getFlatIndex();
            int topIndex = getTopItemIndex();
            int visibleRows = getVisibleRowCount(false);
            if (flatIndex < topIndex) {
                // Show item as top item
                setTopItemIndex(flatIndex);
            } else if (visibleRows > 0 && flatIndex >= topIndex + visibleRows) {
                // Show item as last item
                setTopItemIndex(flatIndex - visibleRows + 1);
            }
        }
    }

    /**
     * Sets the item which is currently at the top of the receiver.
     * This item can change when items are expanded, collapsed, scrolled
     * or new items are added or removed.
     *
     * @param item the item to be shown
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see Tree#getTopItem()
     *
     * @since 1.4
     */
    public void setTopItem(TreeItem item) {
        checkWidget();
        if (item == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (item.isDisposed()) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (item.getParent() == this) {
            TreeItem parent = item.getParentItem();
            while (parent != null) {
                parent.setExpanded(true);
                parent = parent.getParentItem();
            }
            setTopItemIndex(item.getFlatIndex());
        }
    }

    /**
     * Returns the item which is currently at the top of the receiver.
     * This item can change when items are expanded, collapsed, scrolled
     * or new items are added or removed.
     *
     * @return the item at the top of the receiver
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 1.4
     */
    public TreeItem getTopItem() {
        checkWidget();
        TreeItem result = null;
        if (itemCount > 0) {
            List<TreeItem> visibleItems = collectVisibleItems(null);
            result = visibleItems.get(getTopItemIndex());
        }
        return result;
    }

    private void setTopItemIndex(int index) {
        if (index != topItemIndex) {
            topItemIndex = index;
            adjustTopItemIndex();
            updateAllItems();
        }
    }

    int getTopItemIndex() {
        if (!isVisibleItemsCountValid()) {
            adjustTopItemIndex();
        }
        return topItemIndex;
    }

    /**
     * Shows the column.  If the column is already showing in the receiver,
     * this method simply returns.  Otherwise, the columns are scrolled until
     * the column is visible.
     *
     * @param column the column to be shown
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the column is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the column has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 1.3
     */
    public void showColumn(TreeColumn column) {
        checkWidget();
        if (column == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (column.isDisposed()) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (column.getParent() == this) {
            int index = indexOf(column);
            if (0 <= index && index < getColumnCount()) {
                int leftColumnsWidth = 0;
                int columnWidth = column.getWidth();
                int clientWidth = getClientArea().width;
                int[] columnOrder = getColumnOrder();
                boolean found = false;
                for (int i = 0; i < columnOrder.length; i++) {
                    if (index != columnOrder[i]) {
                        int currentColumnWidth = getColumn(columnOrder[i]).getWidth();
                        if (!found) {
                            if (isFixedColumn(columnOrder[i])) {
                                clientWidth -= currentColumnWidth;
                            } else {
                                leftColumnsWidth += currentColumnWidth;
                            }
                        }
                    } else {
                        found = true;
                    }
                }
                if (getColumnLeftOffset(index) > leftColumnsWidth) {
                    scrollLeft = leftColumnsWidth;
                } else if (scrollLeft < leftColumnsWidth + columnWidth - clientWidth) {
                    scrollLeft = leftColumnsWidth + columnWidth - clientWidth;
                }
            }
        }
    }

    /**
     * Shows the selection. If the selection is already showing in the receiver,
     * this method simply returns. Otherwise, the items are scrolled until the
     * selection is visible.
     *
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see Tree#showItem(TreeItem)
     */
    public void showSelection() {
        checkWidget();
        if (selection.length == 0) {
            return;
        }
        showItem(selection[0]);
    }

    // ///////////////////////////////////
    // Methods to get/set/clear selection
    /**
     * Returns an array of <code>TreeItem</code>s that are currently selected in
     * the receiver. The order of the items is unspecified. An empty array
     * indicates that no items are selected.
     * <p>
     * Note: This is not the actual structure used by the receiver to maintain its
     * selection, so modifying the array will not affect the receiver.
     * </p>
     *
     * @return an array representing the selection
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public TreeItem[] getSelection() {
        checkWidget();
        TreeItem[] result = new TreeItem[selection.length];
        System.arraycopy(selection, 0, result, 0, selection.length);
        return result;
    }

    /**
     * Returns the number of selected items contained in the receiver.
     *
     * @return the number of selected items
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public int getSelectionCount() {
        checkWidget();
        return selection.length;
    }

    /**
     * Sets the receiver's selection to the given item. The current selection is
     * cleared before the new item is selected.
     * <p>
     * If the item is not in the receiver, then it is ignored.
     * </p>
     *
     * @param selection the item to select
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     *              <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void setSelection(TreeItem selection) {
        checkWidget();
        if (selection == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        setSelection(new TreeItem[] { selection });
    }

    /**
     * Sets the receiver's selection to be the given array of items. The current
     * selection is cleared before the new items are selected.
     * <p>
     * Items that are not in the receiver are ignored. If the receiver is
     * single-select and multiple items are specified, then all items are ignored.
     * </p>
     *
     * @param selection the array of items
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the array of items is null</li>
     *              <li>ERROR_INVALID_ARGUMENT - if one of the items has been
     *              disposed</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see Tree#deselectAll()
     */
    public void setSelection(TreeItem[] selection) {
        checkWidget();
        if (selection == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        int length = selection.length;
        if ((style & SWT.SINGLE) != 0) {
            if (length == 0 || length > 1) {
                deselectAll();
            } else {
                TreeItem item = selection[0];
                if (item != null) {
                    if (item.isDisposed()) {
                        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                    }
                    this.selection = new TreeItem[] { item };
                }
            }
        } else {
            if (length == 0) {
                deselectAll();
            } else {
                // Construct an array that contains all non-null items to be selected
                TreeItem[] validSelection = new TreeItem[length];
                int validLength = 0;
                for (int i = 0; i < length; i++) {
                    if (selection[i] != null) {
                        if (selection[i].isDisposed()) {
                            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                        }
                        validSelection[validLength] = selection[i];
                        validLength++;
                    }
                }
                if (validLength > 0) {
                    // Copy the above created array to its 'final destination'
                    this.selection = new TreeItem[validLength];
                    System.arraycopy(validSelection, 0, this.selection, 0, validLength);
                }
            }
        }
    }

    /**
     * Selects an item in the receiver.  If the item was already
     * selected, it remains selected.
     *
     * @param item the item to be selected
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 1.3
     */
    public void select(TreeItem item) {
        checkWidget();
        if (item == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (item.isDisposed()) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if ((style & SWT.SINGLE) != 0) {
            setSelection(item);
        } else {
            List<TreeItem> selItems = new ArrayList<>(Arrays.asList(selection));
            if (!selItems.contains(item)) {
                selItems.add(item);
                selection = new TreeItem[selItems.size()];
                selItems.toArray(selection);
            }
        }
    }

    /**
     * Selects all of the items in the receiver.
     * <p>
     * If the receiver is single-select, do nothing.
     * </p>
     *
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void selectAll() {
        checkWidget();
        if ((style & SWT.MULTI) != 0) {
            final java.util.List<TreeItem> allItems = new ArrayList<>();
            WidgetTreeUtil.accept(this, new WidgetTreeVisitor() {
                @Override
                public boolean visit(Widget widget) {
                    if (widget instanceof TreeItem) {
                        allItems.add((TreeItem) widget);
                    }
                    return true;
                }
            });
            selection = new TreeItem[allItems.size()];
            allItems.toArray(selection);
        }
    }

    /**
     * Deselects an item in the receiver.  If the item was already
     * deselected, it remains deselected.
     *
     * @param item the item to be deselected
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 1.3
     */
    public void deselect(TreeItem item) {
        checkWidget();
        if (item == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (item.isDisposed()) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        List<TreeItem> selItems = new ArrayList<>(Arrays.asList(selection));
        if (selItems.contains(item)) {
            selItems.remove(item);
            selection = new TreeItem[selItems.size()];
            selItems.toArray(selection);
        }
    }

    /**
     * Deselects all selected items in the receiver.
     *
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void deselectAll() {
        checkWidget();
        selection = EMPTY_SELECTION;
    }

    /**
     * Marks the receiver's lines as visible if the argument is <code>true</code>,
     * and marks it invisible otherwise.
     * <p>
     * If one of the receiver's ancestors is not visible or some other condition
     * makes the receiver not visible, marking it visible may not actually cause
     * it to be displayed.
     * </p>
     *
     * @param value the new visibility state
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void setLinesVisible(boolean value) {
        checkWidget();
        if (linesVisible == value) {
            return; /* no change */
        }
        linesVisible = value;
    }

    /**
     * Returns the width in pixels of a grid line.
     *
     * @return the width of a grid line in pixels
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 1.4
     */
    public int getGridLineWidth() {
        checkWidget();
        return GRID_WIDTH;
    }

    /**
     * Returns <code>true</code> if the receiver's lines are visible, and
     * <code>false</code> otherwise.
     * <p>
     * If one of the receiver's ancestors is not visible or some other condition
     * makes the receiver not visible, this method may still indicate that it is
     * considered visible even though it may not actually be showing.
     * </p>
     *
     * @return the visibility state of the lines
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public boolean getLinesVisible() {
        checkWidget();
        return linesVisible;
    }

    /**
     * Clears the item at the given zero-relative index in the receiver. The text,
     * icon and other attributes of the item are set to the default value. If the
     * tree was created with the <code>SWT.VIRTUAL</code> style, these attributes
     * are requested again as needed.
     *
     * @param index the index of the item to clear
     * @param recursive <code>true</code> if all child items of the indexed item
     *          should be cleared recursively, and <code>false</code> otherwise
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_INVALID_RANGE - if the index is not between 0 and
     *              the number of elements in the list minus 1 (inclusive)</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see SWT#VIRTUAL
     * @see SWT#SetData
     */
    public void clear(int index, boolean recursive) {
        checkWidget();
        if (index < 0 || index >= itemCount) {
            error(SWT.ERROR_INVALID_RANGE);
        }
        TreeItem item = items[index];
        if (item != null) {
            item.clear();
            if (recursive) {
                item.clearAll(true, false);
            }
            if (isVirtual()) {
                redraw();
            }
        }
    }

    /**
     * Returns the item at the given point in the receiver or null if no such item
     * exists. The point is in the coordinate system of the receiver.
     * <p>
     * The item that is returned represents an item that could be selected by the
     * user. For example, if selection only occurs in items in the first column,
     * then null is returned if the point is outside of the item. Note that the
     * SWT.FULL_SELECTION style hint, which specifies the selection policy,
     * determines the extent of the selection.
     * </p>
     *
     * @param point the point used to locate the item
     * @return the item at the given point, or null if the point is not in a
     *         selectable item
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the point is null</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public TreeItem getItem(Point point) {
        checkWidget();
        if (point == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        TreeItem result = null;
        int index = (point.y - getHeaderHeight()) / getItemHeight() + getTopItemIndex();
        List<TreeItem> visibleItems = collectVisibleItems(null);
        if (0 <= index && index < visibleItems.size()) {
            result = visibleItems.get(index);
        }
        return result;
    }

    /**
     * Returns the height of the area which would be used to display <em>one</em>
     * of the items in the tree.
     *
     * @return the height of one item
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     *
     * @since 1.3
     */
    public int getItemHeight() {
        checkWidget();
        int result = customItemHeight;
        if (result == -1) {
            if (!layoutCache.hasItemHeight()) {
                layoutCache.itemHeight = computeItemHeight();
            }
            result = layoutCache.itemHeight;
        }
        return result;
    }

    /**
     * Clears all the items in the receiver. The text, icon and other attributes
     * of the items are set to their default values. If the tree was created with
     * the <code>SWT.VIRTUAL</code> style, these attributes are requested again as
     * needed.
     *
     * @param recursive <code>true</code> if all child items should be cleared
     *          recursively, and <code>false</code> otherwise
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see SWT#VIRTUAL
     * @see SWT#SetData
     */
    public void clearAll(boolean recursive) {
        checkWidget();
        for (int i = 0; i < itemCount; i++) {
            TreeItem item = items[i];
            if (item != null) {
                item.clear();
                if (recursive) {
                    item.clearAll(true, false);
                }
            }
        }
        if (isVirtual()) {
            redraw();
        }
    }

    @Override
    public void changed(Control[] changed) {
        clearItemsPreferredWidthBuffer();
        super.changed(changed);
    }

    private void clearItemsPreferredWidthBuffer() {
        for (int i = 0; i < itemCount; i++) {
            TreeItem item = items[i];
            if (item != null) {
                item.clearPreferredWidthBuffers(true);
            }
        }
    }

    /**
     * Returns the number of columns contained in the receiver. If no
     * <code>TreeColumn</code>s were created by the programmer, this value is
     * zero, despite the fact that visually, one column of items may be visible.
     * This occurs when the programmer uses the tree like a list, adding items but
     * never creating a column.
     *
     * @return the number of columns
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public int getColumnCount() {
        checkWidget();
        return columnHolder.size();
    }

    void createColumn(TreeColumn column, int index) {
        columnHolder.insert(column, index);
        if (columnOrder == null) {
            columnOrder = new int[] { index };
        } else {
            int length = columnOrder.length;
            for (int i = index; i < length; i++) {
                columnOrder[i]++;
            }
            int[] newColumnOrder = new int[length + 1];
            System.arraycopy(columnOrder, 0, newColumnOrder, 0, index);
            System.arraycopy(columnOrder, index, newColumnOrder, index + 1, length - index);
            columnOrder = newColumnOrder;
            columnOrder[index] = index;
        }
        for (int i = 0; i < itemCount; i++) {
            if (items[i] != null) {
                items[i].shiftData(index);
            }
        }
        updateScrollBars();
    }

    final void destroyColumn(TreeColumn column) {
        if (!isInDispose()) {
            int index = indexOf(column);
            // Remove data from TreeItems
            for (int i = 0; i < itemCount; i++) {
                if (items[i] != null) {
                    items[i].removeData(index);
                }
            }
            // Reset sort column if necessary
            if (column == sortColumn) {
                sortColumn = null;
            }
            // Remove from column holder
            columnHolder.remove(column);
            // Remove from column order
            int length = columnOrder.length;
            int[] newColumnOrder = new int[length - 1];
            int count = 0;
            for (int i = 0; i < length; i++) {
                if (columnOrder[i] != index) {
                    int newOrder = columnOrder[i];
                    if (index < newOrder) {
                        newOrder--;
                    }
                    newColumnOrder[count] = newOrder;
                    count++;
                }
            }
            columnOrder = newColumnOrder;
            updateScrollBars();
        }
    }

    /**
     * Returns the height of the receiver's header
     *
     * @return the height of the header or zero if the header is not visible
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public int getHeaderHeight() {
        checkWidget();
        if (!layoutCache.hasHeaderHeight()) {
            layoutCache.headerHeight = computeHeaderHeight();
        }
        return layoutCache.headerHeight;
    }

    /**
     * Marks the receiver's header as visible if the argument is <code>true</code>
     * , and marks it invisible otherwise.
     * <p>
     * If one of the receiver's ancestors is not visible or some other condition
     * makes the receiver not visible, marking it visible may not actually cause
     * it to be displayed.
     * </p>
     *
     * @param value the new visibility state
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void setHeaderVisible(boolean value) {
        checkWidget();
        if (headerVisible != value) {
            headerVisible = value;
            layoutCache.invalidateHeaderHeight();
            updateScrollBars();
        }
    }

    /**
     * Returns <code>true</code> if the receiver's header is visible, and
     * <code>false</code> otherwise.
     * <p>
     * If one of the receiver's ancestors is not visible or some other condition
     * makes the receiver not visible, this method may still indicate that it is
     * considered visible even though it may not actually be showing.
     * </p>
     *
     * @return the receiver's header's visibility state
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public boolean getHeaderVisible() {
        checkWidget();
        return headerVisible;
    }

    /**
     * Searches the receiver's list starting at the first column (index 0) until a
     * column is found that is equal to the argument, and returns the index of
     * that column. If no column is found, returns -1.
     *
     * @param column the search column
     * @return the index of the column
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the column is null</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public int indexOf(TreeColumn column) {
        checkWidget();
        if (column == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        if (column.isDisposed()) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        return columnHolder.indexOf(column);
    }

    /**
     * Returns the column at the given, zero-relative index in the receiver.
     * Throws an exception if the index is out of range. Columns are returned in
     * the order that they were created. If no <code>TreeColumn</code>s were
     * created by the programmer, this method will throw
     * <code>ERROR_INVALID_RANGE</code> despite the fact that a single column of
     * data may be visible in the tree. This occurs when the programmer uses the
     * tree like a list, adding items but never creating a column.
     *
     * @param index the index of the column to return
     * @return the column at the given index
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_INVALID_RANGE - if the index is not between 0 and
     *              the number of elements in the list minus 1 (inclusive)</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see Tree#getColumnOrder()
     * @see Tree#setColumnOrder(int[])
     * @see TreeColumn#getMoveable()
     * @see TreeColumn#setMoveable(boolean)
     * @see SWT#Move
     */
    public TreeColumn getColumn(int index) {
        checkWidget();
        if (!(0 <= index && index < columnHolder.size())) {
            error(SWT.ERROR_INVALID_RANGE);
        }
        return columnHolder.getItem(index);
    }

    /**
     * Returns an array of <code>TreeColumn</code>s which are the columns in the
     * receiver. Columns are returned in the order that they were created. If no
     * <code>TreeColumn</code>s were created by the programmer, the array is
     * empty, despite the fact that visually, one column of items may be visible.
     * This occurs when the programmer uses the tree like a list, adding items but
     * never creating a column.
     * <p>
     * Note: This is not the actual structure used by the receiver to maintain its
     * list of items, so modifying the array will not affect the receiver.
     * </p>
     *
     * @return the items in the receiver
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see Tree#getColumnOrder()
     * @see Tree#setColumnOrder(int[])
     * @see TreeColumn#getMoveable()
     * @see TreeColumn#setMoveable(boolean)
     * @see SWT#Move
     */
    public TreeColumn[] getColumns() {
        checkWidget();
        return columnHolder.getItems();
    }

    /**
     * Sets the order that the items in the receiver should be displayed in to the
     * given argument which is described in terms of the zero-relative ordering of
     * when the items were added.
     *
     * @param order the new order to display the items
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the item order is null</li>
     *              <li>ERROR_INVALID_ARGUMENT - if the item order is not the same
     *              length as the number of items</li>
     *              </ul>
     * @see Tree#getColumnOrder()
     * @see TreeColumn#getMoveable()
     * @see TreeColumn#setMoveable(boolean)
     * @see SWT#Move
     */
    public void setColumnOrder(int[] order) {
        checkWidget();
        if (order == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        int columnCount = getColumnCount();
        if (order.length != columnCount) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (columnCount > 0) {
            int[] oldOrder = new int[columnCount];
            System.arraycopy(columnOrder, 0, oldOrder, 0, columnOrder.length);
            boolean reorder = false;
            boolean[] seen = new boolean[columnCount];
            for (int i = 0; i < order.length; i++) {
                int index = order[i];
                if (index < 0 || index >= columnCount) {
                    error(SWT.ERROR_INVALID_RANGE);
                }
                if (seen[index]) {
                    error(SWT.ERROR_INVALID_ARGUMENT);
                }
                seen[index] = true;
                if (index != oldOrder[i]) {
                    reorder = true;
                }
            }
            if (reorder) {
                System.arraycopy(order, 0, columnOrder, 0, columnOrder.length);
                for (int i = 0; i < seen.length; i++) {
                    if (oldOrder[i] != columnOrder[i]) {
                        TreeColumn column = getColumn(columnOrder[i]);
                        column.notifyListeners(SWT.Move, new Event());
                    }
                }
            }
        }
    }

    /**
     * Sets the column used by the sort indicator for the receiver. A null value
     * will clear the sort indicator. The current sort column is cleared before
     * the new column is set.
     *
     * @param column the column used by the sort indicator or <code>null</code>
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_INVALID_ARGUMENT - if the column is disposed</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void setSortColumn(TreeColumn column) {
        checkWidget();
        if (column != null && column.isDisposed()) {
            error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (column == sortColumn) {
            return;
        }
        if (sortColumn != null && !sortColumn.isDisposed()) {
            sortColumn.setSortDirection(SWT.NONE);
        }
        sortColumn = column;
        if (sortColumn != null) {
            sortColumn.setSortDirection(sortDirection);
        }
    }

    /**
     * Sets the direction of the sort indicator for the receiver. The value can be
     * one of <code>UP</code>, <code>DOWN</code> or <code>NONE</code>.
     *
     * @param direction the direction of the sort indicator
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     */
    public void setSortDirection(int direction) {
        checkWidget();
        if (direction != SWT.UP && direction != SWT.DOWN && direction != SWT.NONE) {
            return;
        }
        sortDirection = direction;
        if (sortColumn == null || sortColumn.isDisposed()) {
            return;
        }
        sortColumn.setSortDirection(sortDirection);
    }

    /**
     * Returns the column which shows the sort indicator for the receiver. The
     * value may be null if no column shows the sort indicator.
     *
     * @return the sort indicator
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see #setSortColumn(TreeColumn)
     */
    public TreeColumn getSortColumn() {
        checkWidget();
        return sortColumn;
    }

    /**
     * Returns the direction of the sort indicator for the receiver. The value
     * will be one of <code>UP</code>, <code>DOWN</code> or <code>NONE</code>.
     *
     * @return the sort direction
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see #setSortDirection(int)
     */
    public int getSortDirection() {
        checkWidget();
        return sortDirection;
    }

    /**
     * Returns an array of zero-relative integers that map the creation order of
     * the receiver's items to the order in which they are currently being
     * displayed.
     * <p>
     * Specifically, the indices of the returned array represent the current
     * visual order of the items, and the contents of the array represent the
     * creation order of the items.
     * </p>
     * <p>
     * Note: This is not the actual structure used by the receiver to maintain its
     * list of items, so modifying the array will not affect the receiver.
     * </p>
     *
     * @return the current visual order of the receiver's items
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see Tree#setColumnOrder(int[])
     * @see TreeColumn#getMoveable()
     * @see TreeColumn#setMoveable(boolean)
     * @see SWT#Move
     */
    public int[] getColumnOrder() {
        checkWidget();
        int[] result;
        if (columnHolder.size() == 0) {
            result = new int[0];
        } else {
            result = new int[columnOrder.length];
            System.arraycopy(columnOrder, 0, result, 0, columnOrder.length);
        }
        return result;
    }

    ///////////////////////////////////////
    // Listener registration/deregistration

    /**
     * Adds the listener to the collection of listeners who will be notified when
     * the receiver's selection changes, by sending it one of the messages defined
     * in the <code>SelectionListener</code> interface.
     * <p>
     * When <code>widgetSelected</code> is called, the item field of the event
     * object is valid. If the receiver has <code>SWT.CHECK</code> style set and
     * the check selection changes, the event object detail field contains the
     * value <code>SWT.CHECK</code>. <code>widgetDefaultSelected</code> is
     * typically called when an item is double-clicked. The item field of the
     * event object is valid for default selection, but the detail field is not
     * used.
     * </p>
     *
     * @param listener the listener which should be notified
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see SelectionListener
     * @see #removeSelectionListener
     * @see SelectionEvent
     */
    public void addSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        TypedListener typedListener = new TypedListener(listener);
        addListener(SWT.Selection, typedListener);
        addListener(SWT.DefaultSelection, typedListener);
    }

    /**
     * Removes the listener from the collection of listeners who will be notified
     * when the receiver's selection changes.
     *
     * @param listener the listener which should no longer be notified
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see SelectionListener
     * @see #addSelectionListener
     */
    public void removeSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        removeListener(SWT.Selection, listener);
        removeListener(SWT.DefaultSelection, listener);
    }

    /**
     * Adds the listener to the collection of listeners who will be notified when
     * an item in the receiver is expanded or collapsed by sending it one of the
     * messages defined in the <code>TreeListener</code> interface.
     *
     * @param listener the listener which should be notified
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see TreeListener
     * @see #removeTreeListener
     */
    public void addTreeListener(TreeListener listener) {
        checkWidget();
        if (listener == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        TypedListener typedListener = new TypedListener(listener);
        addListener(SWT.Expand, typedListener);
        addListener(SWT.Collapse, typedListener);
    }

    /**
     * Removes the listener from the collection of listeners who will be notified
     * when items in the receiver are expanded or collapsed.
     *
     * @param listener the listener which should no longer be notified
     * @exception IllegalArgumentException <ul>
     *              <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     *              </ul>
     * @exception SWTException <ul>
     *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *              thread that created the receiver</li>
     *              </ul>
     * @see TreeListener
     * @see #addTreeListener
     */
    public void removeTreeListener(TreeListener listener) {
        checkWidget();
        if (listener == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        removeListener(SWT.Expand, listener);
        removeListener(SWT.Collapse, listener);
    }

    @Override
    public void setData(String key, Object value) {
        if (RWT.CUSTOM_VARIANT.equals(key)) {
            layoutCache.invalidateAll();
        } else if (RWT.CUSTOM_ITEM_HEIGHT.equals(key)) {
            setCustomItemHeight(value);
        } else if (RWT.PRELOADED_ITEMS.equals(key)) {
            setPreloadedItems(value);
        }
        if (!RWT.MARKUP_ENABLED.equals(key) || !isMarkupEnabledFor(this)) {
            super.setData(key, value);
        }
    }

    /////////////////////////////////
    // Methods to cleanup on dispose

    @Override
    void releaseChildren() {
        for (int i = items.length - 1; i >= 0; i--) {
            if (items[i] != null) {
                items[i].dispose();
            }
        }
        TreeColumn[] cols = columnHolder.getItems();
        for (int c = 0; c < cols.length; c++) {
            cols[c].dispose();
            columnHolder.remove(cols[c]);
        }
        super.releaseChildren();
    }

    void removeFromSelection(TreeItem item) {
        int index = -1;
        for (int i = 0; index == -1 && i < selection.length; i++) {
            if (selection[i] == item) {
                index = i;
            }
        }
        if (index != -1) {
            TreeItem[] newSelection = new TreeItem[selection.length - 1];
            System.arraycopy(selection, 0, newSelection, 0, index);
            if (index < selection.length - 1) {
                int length = selection.length - index - 1;
                System.arraycopy(selection, index + 1, newSelection, index, length);
            }
            selection = newSelection;
        }
    }

    /////////////////////
    // Widget dimensions

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        checkWidget();
        int width = 0;
        int height = 0;
        if (getColumnCount() > 0) {
            for (int i = 0; i < getColumnCount(); i++) {
                width += getColumn(i).getWidth();
            }
        } else {
            for (int i = 0; i < itemCount; i++) {
                TreeItem item = items[i];
                if (item != null && item.isCached()) {
                    int itemWidth = getPreferredCellWidth(item, 0);
                    width = Math.max(width, itemWidth);
                    if (item.getExpanded()) {
                        int innerWidth = getMaxInnerWidth(item.items, 0, 1, false);
                        width = Math.max(width, innerWidth);
                    }
                }
            }
        }
        height += getHeaderHeight();
        height += itemCount * getItemHeight();
        for (int i = 0; i < itemCount; i++) {
            TreeItem item = items[i];
            if (item != null && !item.isInDispose() && item.getExpanded()) {
                height += item.getInnerHeight();
            }
        }
        if (width == 0) {
            width = DEFAULT_WIDTH;
        }
        if (height == 0) {
            height = DEFAULT_HEIGHT;
        }
        if (wHint != SWT.DEFAULT) {
            width = wHint;
        }
        if (hHint != SWT.DEFAULT) {
            height = hHint;
        }
        BoxDimensions border = getBorder();
        width += border.left + border.right;
        height += border.top + border.bottom;
        if ((style & SWT.V_SCROLL) != 0) {
            width += getVerticalBar().getSize().x;
        }
        if ((style & SWT.H_SCROLL) != 0) {
            height += getHorizontalBar().getSize().y;
        }
        return new Point(width, height);
    }

    private void setCustomItemHeight(Object value) {
        if (value == null) {
            customItemHeight = -1;
        } else {
            if (!(value instanceof Integer)) {
                error(SWT.ERROR_INVALID_ARGUMENT);
            }
            int itemHeight = ((Integer) value).intValue();
            if (itemHeight < 0) {
                error(SWT.ERROR_INVALID_RANGE);
            }
            customItemHeight = itemHeight;
        }
    }

    private void setPreloadedItems(Object value) {
        if (value == null) {
            preloadedItems = 0;
        } else {
            if (!(value instanceof Integer)) {
                error(SWT.ERROR_INVALID_ARGUMENT);
            }
            preloadedItems = ((Integer) value).intValue();
            if (preloadedItems < 0) {
                error(SWT.ERROR_INVALID_RANGE);
            }
        }
    }

    /////////////////////
    // item layout helper

    int getMaxContentWidth(TreeColumn column) {
        return getMaxInnerWidth(items, indexOf(column), 1, true);
    }

    private int getMaxInnerWidth(TreeItem[] items, int columnIndex, int level, boolean clearBuffer) {
        int maxInnerWidth = 0;
        for (int i = 0; i < items.length; i++) {
            TreeItem item = items[i];
            if (item != null && item.isCached()) {
                int indention = columnIndex == 0 ? level * getIndentionWidth() : 0; // TODO [tb] : test
                if (clearBuffer) {
                    item.clearPreferredWidthBuffers(false);
                }
                int itemWidth = getPreferredCellWidth(item, columnIndex) + indention;
                maxInnerWidth = Math.max(maxInnerWidth, itemWidth);
                if (item.getExpanded()) {
                    int innerWidth = getMaxInnerWidth(item.items, columnIndex, level + 1, clearBuffer);
                    maxInnerWidth = Math.max(maxInnerWidth, innerWidth);
                }
            }
        }
        return maxInnerWidth;
    }

    int getCellLeft(int index) {
        return getColumnCount() == 0 ? 0 : getColumn(index).getLeft();
    }

    private int getCellWidth(int index) {
        return getColumnCount() == 0 && index == 0 ? getMaxInnerWidth(items, 0, 1, false)
                : getColumn(index).getWidth();
    }

    int getImageOffset(int index) {
        // Note: The left cell-padding is visually ignored for the tree-column
        int result = isTreeColumn(index) ? 0 : getCellPadding().left;
        if (hasCheckBoxes(index)) {
            result += getCheckImageOuterSize().width;
        }
        return result;
    }

    private int getTextOffset(int index) {
        int result = getImageOffset(index);
        result += getItemImageOuterWidth(index);
        if (isTreeColumn(index)) {
            result += TEXT_MARGIN.x;
        }
        return result;
    }

    int getTextWidth(int index) {
        BoxDimensions padding = getCellPadding();
        int result = getCellWidth(index) - getTextOffset(index) - padding.right;
        if (isTreeColumn(index)) {
            result -= (TEXT_MARGIN.width - TEXT_MARGIN.x);
        }
        return Math.max(0, result);
    }

    int getIndentionOffset(TreeItem item) {
        return getIndentionWidth() * (item.depth + 1);
    }

    int getVisualTextLeft(TreeItem item, int index) {
        return getVisualCellLeft(item, index) + getCellPadding().left + getItemImageOuterWidth(index);
    }

    int getVisualCellLeft(TreeItem item, int index) {
        int result = getCellLeft(index) - getColumnLeftOffset(index);
        if (isTreeColumn(index)) {
            result += getIndentionOffset(item);
        }
        if (hasCheckBoxes(index)) {
            result += getCheckImageOuterSize().width;
        }
        return result;
    }

    int getVisualTextWidth(TreeItem item, int index) {
        int result = 0;
        if (index == 0 && getColumnCount() == 0) {
            result = getStringExtent(item.getFont(), item.getText(0)).x + TEXT_MARGIN.width;
        } else if (index >= 0 && index < getColumnCount()) {
            result = getTextWidth(index) - getIndentionOffset(item);
            result = Math.max(0, result);
        }
        return result;
    }

    int getVisualCellWidth(TreeItem item, int index) {
        int result;
        if (getColumnCount() == 0 && index == 0) {
            BoxDimensions padding = getCellPadding();
            int paddingWidth = padding.left + padding.right;
            int textWidth = getStringExtent(item.getFont(), item.getText(0)).x;
            result = paddingWidth + getItemImageOuterWidth(index) + textWidth + TEXT_MARGIN.width;
        } else {
            result = getColumn(index).getWidth();
            if (isTreeColumn(index)) {
                result -= getIndentionOffset(item);
            }
            if (hasCheckBoxes(index)) {
                result -= getCheckImageOuterSize().width;
            }
            result = Math.max(0, result);
        }
        return result;
    }

    int getPreferredCellWidth(TreeItem item, int index) {
        int result = item.getPreferredWidthBuffer(index);
        if (!item.hasPreferredWidthBuffer(index)) {
            BoxDimensions padding = getCellPadding();
            result = getTextOffset(index);
            result += getStringExtent(item.getFont(), item.getTextWithoutMaterialize(index)).x;
            result += padding.right;
            if (isTreeColumn(index)) {
                result += TEXT_MARGIN.width - TEXT_MARGIN.x;
            }
            item.setPreferredWidthBuffer(index, result);
        }
        return result;
    }

    private Point getStringExtent(Font font, String text) {
        return stringExtent(font, text, isMarkupEnabledFor(this));
    }

    boolean isTreeColumn(int index) {
        return index == 0 && getColumnCount() == 0 || getColumnCount() > 0 && getColumnOrder()[0] == index;
    }

    /**
     * Returns the scroll-offset of the column, which is the leftOffset unless it is a fixed column.
     */
    final int getColumnLeftOffset(int columnIndex) {
        int result = scrollLeft;
        if (columnIndex >= 0) {
            result = isFixedColumn(columnIndex) ? 0 : scrollLeft;
        }
        return result;
    }

    private boolean isFixedColumn(int index) {
        int[] columnOrder = getColumnOrder();
        int visualIndex = -1;
        for (int i = 0; i < columnOrder.length && visualIndex == -1; i++) {
            if (index == columnOrder[i]) {
                visualIndex = i;
            }
        }
        return visualIndex < getFixedColumns();
    }

    private int getFixedColumns() {
        Object fixedColumns = getData(RWT.FIXED_COLUMNS);
        if (fixedColumns instanceof Integer) {
            if (!(getData(RWT.ROW_TEMPLATE) instanceof Template)) {
                return ((Integer) fixedColumns).intValue();
            }
        }
        return -1;
    }

    private boolean hasCheckBoxes(int index) {
        return (style & SWT.CHECK) != 0 && isTreeColumn(index);
    }

    private boolean hasColumnImages(int columnIndex) {
        int count = columnIndex == 0 ? itemImageCount : getColumn(columnIndex).itemImageCount;
        return count > 0;
    }

    void updateColumnImageCount(int columnIndex, Image oldImage, Image newImage) {
        int delta = 0;
        if (oldImage == null && newImage != null) {
            delta = +1;
        } else if (oldImage != null && newImage == null) {
            delta = -1;
        }
        if (delta != 0) {
            if (columnIndex == 0) {
                itemImageCount += delta;
            } else {
                TreeColumn column = getColumn(columnIndex);
                column.itemImageCount += delta;
            }
        }
    }

    void updateItemImageSize(Image image) {
        if (image != null && itemImageSize == null) {
            Rectangle imageBounds = image.getBounds();
            itemImageSize = new Point(imageBounds.width, imageBounds.height);
            layoutCache.invalidateItemHeight();
        }
    }

    Point getItemImageSize(int index) {
        Point result;
        if (hasColumnImages(index)) {
            result = getItemImageSize();
            if (getColumnCount() > 0) {
                int availWidth = getColumn(index).getWidth();
                availWidth -= getCellPadding().left;
                availWidth = Math.max(0, availWidth);
                result.x = Math.min(result.x, availWidth);
            }
        } else {
            result = new Point(0, 0);
        }
        return result;
    }

    private int getItemImageOuterWidth(int index) {
        int result = 0;
        if (hasColumnImages(index)) {
            result += getItemImageSize(index).x;
            result += getCellSpacing();
        }
        return result;
    }

    private Point getItemImageSize() {
        Point result = new Point(0, 0);
        if (itemImageSize != null) {
            result.x = itemImageSize.x;
            result.y = itemImageSize.y;
        }
        return result;
    }

    private Size getCheckImageSize() {
        return getThemeAdapter().getCheckBoxImageSize(this);
    }

    TreeThemeAdapter getThemeAdapter() {
        return (TreeThemeAdapter) getAdapter(ThemeAdapter.class);
    }

    private Size getCheckImageOuterSize() {
        Size result = getCheckImageSize();
        BoxDimensions margin = getCheckBoxMargin();
        int width = result.width + margin.left + margin.right;
        int height = result.height + margin.top + margin.bottom;
        return new Size(width, height);
    }

    private BoxDimensions getCheckBoxMargin() {
        if (!layoutCache.hasCheckBoxMargin()) {
            layoutCache.checkBoxMargin = getThemeAdapter().getCheckBoxMargin(this);
        }
        return layoutCache.checkBoxMargin;
    }

    private int getIndentionWidth() {
        return getThemeAdapter().getIndentionWidth(this);
    }

    private int computeHeaderHeight() {
        int result = 0;
        if (headerVisible) {
            Font headerFont = getHeaderFont();
            int textHeight = TextSizeUtil.getCharHeight(headerFont);
            int imageHeight = 0;
            for (int i = 0; i < getColumnCount(); i++) {
                TreeColumn column = columnHolder.getItem(i);
                if (column.getText().contains("\n")) {
                    int columnTextHeight = TextSizeUtil.textExtent(headerFont, column.getText(), 0).y;
                    textHeight = Math.max(textHeight, columnTextHeight);
                }
                Image image = getColumn(i).getImage();
                int height = image == null ? 0 : image.getBounds().height;
                if (height > imageHeight) {
                    imageHeight = height;
                }
            }
            result = Math.max(textHeight, imageHeight);
            TreeThemeAdapter themeAdapter = getThemeAdapter();
            BoxDimensions headerPadding = themeAdapter.getHeaderPadding(this);
            result += themeAdapter.getHeaderBorderBottomWidth(this);
            result += headerPadding.top + headerPadding.bottom;
        }
        return result;
    }

    Font getHeaderFont() {
        IControlAdapter controlAdapter = getAdapter(IControlAdapter.class);
        Font result = controlAdapter.getUserFont();
        if (result == null) {
            result = getThemeAdapter().getHeaderFont(this);
        }
        return result;
    }

    private int computeItemHeight() {
        BoxDimensions padding = getCellPadding();
        int paddingHeight = padding.top + padding.bottom;
        int textHeight = TextSizeUtil.getCharHeight(getFont());
        textHeight += TEXT_MARGIN.height + paddingHeight;
        int itemImageHeight = getItemImageSize().y + paddingHeight;
        int result = Math.max(itemImageHeight, textHeight);
        if (hasCheckBoxes(0)) {
            result = Math.max(getCheckImageOuterSize().height, result);
        }
        result += 1; // The space needed for horizontal gridline is always added, even if not visible
        result = Math.max(result, MIN_ITEM_HEIGHT);
        return result;
    }

    ///////////////////
    // Helping methods

    @Override
    void notifyResize(Point oldSize) {
        if (!oldSize.equals(getSize()) && !TextSizeUtil.isTemporaryResize()) {
            clearCachedHeights();
            updateAllItems();
            updateScrollBars();
            adjustTopItemIndex();
        }
        super.notifyResize(oldSize);
    }

    void clearCachedHeights() {
        layoutCache.invalidateHeaderHeight();
        layoutCache.invalidateItemHeight();
    }

    private void adjustTopItemIndex() {
        int visibleItems = getVisibleItemsCount();
        int visibleRows = getVisibleRowCount(false);
        int correction = visibleRows == 0 ? 1 : 0;
        if (topItemIndex > visibleItems - visibleRows - correction) {
            topItemIndex = Math.max(0, visibleItems - visibleRows - correction);
        }
    }

    final int getVisibleRowCount(boolean includePartlyVisible) {
        int clientHeight = getClientArea().height - getHeaderHeight();
        int result = 0;
        if (clientHeight >= 0) {
            int itemHeight = getItemHeight();
            result = clientHeight / itemHeight;
            if (includePartlyVisible && clientHeight % itemHeight != 0) {
                result++;
            }
        }
        return result;
    }

    private int getVisibleItemsCount() {
        if (!isVisibleItemsCountValid()) {
            visibleItemsCount = collectVisibleItems(null).size();
        }
        return visibleItemsCount;
    }

    private boolean isVisibleItemsCountValid() {
        return visibleItemsCount != -1;
    }

    private List<TreeItem> collectVisibleItems(TreeItem parentItem) {
        List<TreeItem> result = new ArrayList<>();
        TreeItem[] items = parentItem == null ? this.items : parentItem.items;
        int itemCount = parentItem == null ? this.itemCount : parentItem.itemCount;
        for (int i = 0; i < itemCount; i++) {
            TreeItem item = items[i];
            result.add(item);
            if (item != null && item.getExpanded()) {
                result.addAll(collectVisibleItems(item));
            }
        }
        return result;
    }

    void updateAllItems() {
        int flatIndex = 0;
        for (int index = 0; index < itemCount; index++) {
            flatIndex = updateAllItemsRecursively(null, index, flatIndex);
        }
        isFlatIndexValid = true;
        visibleItemsCount = flatIndex;
    }

    private int updateAllItemsRecursively(TreeItem parent, int index, int flatIndex) {
        int newFlatIndex = flatIndex;
        TreeItem item = parent == null ? items[index] : parent.items[index];
        if (shouldResolveItem(flatIndex)) {
            if (item == null) {
                item = parent == null ? _getItem(index) : parent._getItem(index);
            }
            checkData(item, index);
        }
        if (item != null) {
            item.setFlatIndex(newFlatIndex);
        }
        newFlatIndex++;
        if (item != null && item.getExpanded()) {
            for (int i = 0; i < item.itemCount; i++) {
                newFlatIndex = updateAllItemsRecursively(item, i, newFlatIndex);
            }
        }
        return newFlatIndex;
    }

    private boolean shouldResolveItem(int flatIndex) {
        int visibleRows = getVisibleRowCount(true);
        int topIndex = getTopItemIndex();
        int startIndex = topIndex - preloadedItems;
        int endIndex = topIndex + visibleRows + preloadedItems;
        return isVirtual() ? flatIndex >= startIndex && flatIndex < endIndex : false;
    }

    final boolean checkData(TreeItem item, int index) {
        boolean result = true;
        if (isVirtual() && !item.isCached()) {
            item.markCached();
            Event event = new Event();
            event.item = item;
            event.index = index;
            notifyListeners(SWT.SetData, event);
            // widget could be disposed at this point
            if (isDisposed() || item.isDisposed()) {
                result = false;
            }
        }
        return result;
    }

    void invalidateFlatIndex() {
        visibleItemsCount = -1;
        isFlatIndexValid = false;
    }

    private static int checkStyle(int style) {
        int result = style;
        if ((style & SWT.NO_SCROLL) == 0) {
            result |= SWT.H_SCROLL | SWT.V_SCROLL;
        }
        return checkBits(result, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0);
    }

    BoxDimensions getCellPadding() {
        if (!layoutCache.hasCellPadding()) {
            layoutCache.cellPadding = getThemeAdapter().getCellPadding(this);
        }
        return layoutCache.cellPadding;
    }

    int getCellSpacing() {
        if (!layoutCache.hasCellSpacing()) {
            layoutCache.cellSpacing = getThemeAdapter().getCellSpacing(this);
        }
        return layoutCache.cellSpacing;
    }

    ///////////////////////////////////////
    // Helping methods - dynamic scrollbars

    boolean hasVScrollBar() {
        return hasVScrollBar;
    }

    boolean hasHScrollBar() {
        return hasHScrollBar;
    }

    @Override
    int getVScrollBarWidth() {
        int result = 0;
        if (hasVScrollBar()) {
            result = getVerticalBar().getSize().x;
        }
        return result;
    }

    @Override
    int getHScrollBarHeight() {
        int result = 0;
        if (hasHScrollBar()) {
            result = getHorizontalBar().getSize().y;
        }
        return result;
    }

    boolean needsVScrollBar() {
        int availableHeight = getClientArea().height;
        int height = getHeaderHeight();
        height += itemCount * getItemHeight();
        for (int i = 0; i < itemCount; i++) {
            TreeItem item = items[i];
            if (item != null && item.getExpanded()) {
                height += item.getInnerHeight();
            }
        }
        return height > availableHeight;
    }

    boolean needsHScrollBar() {
        boolean result = false;
        int availableWidth = getClientArea().width;
        int columnCount = getColumnCount();
        if (columnCount > 0) {
            int totalWidth = 0;
            for (int i = 0; i < columnCount; i++) {
                TreeColumn column = getColumn(i);
                totalWidth += column.getWidth();
            }
            result = totalWidth > availableWidth;
        } else {
            int maxWidth = 0;
            for (int i = 0; i < itemCount; i++) {
                TreeItem item = items[i];
                if (item != null && !item.isInDispose() && item.isCached()) {
                    int itemWidth = getPreferredCellWidth(item, 0);
                    maxWidth = Math.max(maxWidth, itemWidth);
                    if (item.getExpanded()) {
                        int innerWidth = getMaxInnerWidth(item.items, 0, 1, false);
                        maxWidth = Math.max(maxWidth, innerWidth);
                    }
                }
            }
            result = maxWidth > availableWidth;
        }
        return result;
    }

    void updateScrollBars() {
        if ((style & SWT.NO_SCROLL) == 0) {
            hasVScrollBar = false;
            hasHScrollBar = needsHScrollBar();
            if (needsVScrollBar()) {
                hasVScrollBar = true;
                hasHScrollBar = needsHScrollBar();
            }
            getHorizontalBar().setVisible(hasHScrollBar);
            getVerticalBar().setVisible(hasVScrollBar);
        }
    }

    boolean isVirtual() {
        return (style & SWT.VIRTUAL) != 0;
    }

    void createItem(TreeItem item, int index) {
        if (itemCount == items.length) {
            /*
             * Grow the array faster when redraw is off or the table is not visible.
             * When the table is painted, the items array is resized to be smaller to
             * reduce memory usage.
             */
            boolean small = /* drawCount == 0 && */isVisible();
            int length = small ? items.length + 4 : Math.max(4, items.length * 3 / 2);
            TreeItem[] newItems = new TreeItem[length];
            System.arraycopy(items, 0, newItems, 0, items.length);
            items = newItems;
        }
        System.arraycopy(items, index, items, index + 1, itemCount - index);
        items[index] = item;
        itemCount++;
        adjustItemIndices(index);
    }

    void destroyItem(int index) {
        itemCount--;
        if (itemCount == 0) {
            setTreeEmpty();
        } else {
            System.arraycopy(items, index + 1, items, index, itemCount - index);
            items[itemCount] = null;
        }
        adjustItemIndices(index);
    }

    private void adjustItemIndices(int start) {
        for (int i = start; i < itemCount; i++) {
            if (items[i] != null) {
                items[i].index = i;
            }
        }
    }

    ///////////////////
    // Skinning support

    @Override
    void reskinChildren(int flags) {
        for (int i = 0; i < itemCount; i++) {
            if (items[i] != null) {
                items[i].reskinChildren(flags);
            }
        }
        TreeColumn[] columns = getColumns();
        if (columns != null) {
            for (int i = 0; i < columns.length; i++) {
                TreeColumn column = columns[i];
                if (!column.isDisposed()) {
                    column.reskinChildren(flags);
                }
            }
        }
        super.reskinChildren(flags);
    }

    ////////////////
    // Inner classes

    private final class CompositeItemHolder implements IItemHolderAdapter<Item> {
        @Override
        public void add(Item item) {
            if (item instanceof TreeColumn) {
                columnHolder.add((TreeColumn) item);
            } else {
                String msg = "Only TreeColumns may be added to CompositeItemHolder";
                throw new IllegalArgumentException(msg);
            }
        }

        @Override
        public void insert(Item item, int index) {
            if (item instanceof TreeColumn) {
                columnHolder.insert((TreeColumn) item, index);
            } else {
                String msg = "Only TreeColumns may be inserted to CompositeItemHolder";
                throw new IllegalArgumentException(msg);
            }
        }

        @Override
        public void remove(Item item) {
            if (item instanceof TreeColumn) {
                columnHolder.remove((TreeColumn) item);
            } else {
                String msg = "Only TreeColumns may be removed from CompositeItemHolder";
                throw new IllegalArgumentException(msg);
            }
        }

        @Override
        public Item[] getItems() {
            TreeItem[] items = getCreatedItems();
            Item[] columns = columnHolder.getItems();
            Item[] result = new Item[columns.length + items.length];
            System.arraycopy(columns, 0, result, 0, columns.length);
            System.arraycopy(items, 0, result, columns.length, items.length);
            return result;
        }
    }

    private final class InternalTreeAdapter
            implements ITreeAdapter, ICellToolTipAdapter, SerializableCompatibility {
        private String toolTipText;
        private ICellToolTipProvider provider;

        @Override
        public void checkData() {
            updateAllItems();
        }

        @Override
        public void setScrollLeft(int left) {
            scrollLeft = left;
        }

        @Override
        public int getScrollLeft() {
            return scrollLeft;
        }

        @Override
        public boolean isCached(TreeItem item) {
            return item.isCached();
        }

        @Override
        public Point getItemImageSize(int index) {
            return Tree.this.getItemImageSize(index);
        }

        @Override
        public int getCellLeft(int index) {
            return Tree.this.getCellLeft(index);
        }

        @Override
        public int getCellWidth(int index) {
            return Tree.this.getCellWidth(index);
        }

        @Override
        public int getTextOffset(int index) {
            return Tree.this.getTextOffset(index);
        }

        @Override
        public int getTextMaxWidth(int index) {
            return getTextWidth(index);
        }

        @Override
        public int getCheckWidth() {
            return getCheckImageSize().width;
        }

        @Override
        public int getImageOffset(int index) {
            return Tree.this.getImageOffset(index);
        }

        @Override
        public int getIndentionWidth() {
            return Tree.this.getIndentionWidth();
        }

        @Override
        public int getCheckLeft() {
            return getCheckBoxMargin().left;
        }

        @Override
        public Rectangle getTextMargin() {
            return TEXT_MARGIN;
        }

        @Override
        public int getTopItemIndex() {
            return Tree.this.getTopItemIndex();
        }

        @Override
        public void setTopItemIndex(int index) {
            Tree.this.setTopItemIndex(index);
        }

        @Override
        public int getColumnLeft(TreeColumn column) {
            int index = Tree.this.indexOf(column);
            return getColumn(index).getLeft();
        }

        @Override
        public ICellToolTipProvider getCellToolTipProvider() {
            return provider;
        }

        @Override
        public void setCellToolTipProvider(ICellToolTipProvider provider) {
            this.provider = provider;
        }

        @Override
        public String getCellToolTipText() {
            return toolTipText;
        }

        @Override
        public void setCellToolTipText(String toolTipText) {
            if (toolTipText != null && isToolTipMarkupEnabledFor(Tree.this)
                    && !isValidationDisabledFor(Tree.this)) {
                MarkupValidator.getInstance().validate(toolTipText);
            }
            this.toolTipText = toolTipText;
        }

        @Override
        public int getFixedColumns() {
            return Tree.this.getFixedColumns();
        }

        @Override
        public boolean isFixedColumn(TreeColumn column) {
            return Tree.this.isFixedColumn(Tree.this.indexOf(column));
        }

    }

    static final class LayoutCache implements SerializableCompatibility {
        private static final int UNKNOWN = -1;

        int headerHeight = UNKNOWN;
        int itemHeight = UNKNOWN;
        int cellSpacing = UNKNOWN;
        BoxDimensions cellPadding;
        BoxDimensions checkBoxMargin;

        public boolean hasHeaderHeight() {
            return headerHeight != UNKNOWN;
        }

        public void invalidateHeaderHeight() {
            headerHeight = UNKNOWN;
        }

        public boolean hasItemHeight() {
            return itemHeight != UNKNOWN;
        }

        public void invalidateItemHeight() {
            itemHeight = UNKNOWN;
        }

        public boolean hasCellSpacing() {
            return cellSpacing != UNKNOWN;
        }

        public void invalidateCellSpacing() {
            cellSpacing = UNKNOWN;
        }

        public boolean hasCellPadding() {
            return cellPadding != null;
        }

        public void invalidateCellPadding() {
            cellPadding = null;
        }

        public boolean hasCheckBoxMargin() {
            return checkBoxMargin != null;
        }

        public void invalidateCheckBoxMargin() {
            checkBoxMargin = null;
        }

        public void invalidateAll() {
            invalidateHeaderHeight();
            invalidateItemHeight();
            invalidateCellSpacing();
            invalidateCellPadding();
            invalidateCheckBoxMargin();
        }

    }

}