org.eclipse.jface.viewers.TreeViewer Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2004, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Tom Schindl - concept of ViewerRow,
* refactoring (bug 153993), bug 167323, 191468, 205419
* Matthew Hall - bug 221988
* Pawel Piech, WindRiver - bug 296573
* Lars Vogel - Bug 430873
*******************************************************************************/
package org.eclipse.jface.viewers;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.viewers.internal.ExpandableNode;
import org.eclipse.pde.api.tools.annotations.NoExtend;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
/**
* A concrete viewer based on an SWT Tree
control.
*
* This class is not intended to be subclassed outside the viewer framework. It
* is designed to be instantiated with a pre-existing SWT tree control and
* configured with a domain-specific content provider, label provider, element
* filter (optional), and element sorter (optional).
*
*
* As of 3.2, TreeViewer supports multiple equal elements (each with a
* different parent chain) in the tree. This support requires that clients
* enable the element map by calling setUseHashlookup(true)
.
*
*
* Content providers for tree viewers must implement either the
* {@link ITreeContentProvider} interface, (as of 3.2) the
* {@link ILazyTreeContentProvider} interface, or (as of 3.3) the
* {@link ILazyTreePathContentProvider}. If the content provider is an
* ILazyTreeContentProvider
or an
* ILazyTreePathContentProvider
, the underlying Tree must be
* created using the {@link SWT#VIRTUAL} style bit, the tree viewer will not
* support sorting or filtering, and hash lookup must be enabled by calling
* {@link #setUseHashlookup(boolean)}.
*
*
* Users setting up an editable tree with more than 1 column have to pass the
* SWT.FULL_SELECTION style bit
*
*/
@NoExtend
public class TreeViewer extends AbstractTreeViewer {
private static final String VIRTUAL_DISPOSE_KEY = Policy.JFACE
+ ".DISPOSE_LISTENER"; //$NON-NLS-1$
/**
* This viewer's control.
*/
private Tree tree;
/**
* Flag for whether the tree has been disposed of.
*/
private boolean treeIsDisposed = false;
private boolean contentProviderIsLazy;
private boolean contentProviderIsTreeBased;
/**
* The row object reused
*/
private TreeViewerRow cachedRow;
/**
* true if we are inside a preservingSelection() call
*/
private boolean insidePreservingSelection;
/**
* Creates a tree viewer on a newly-created tree control under the given
* parent. The tree control is created using the SWT style bits
* MULTI, H_SCROLL, V_SCROLL,
and BORDER
. The
* viewer has no input, no content provider, a default label provider, no
* sorter, and no filters.
*
* @param parent
* the parent control
*/
public TreeViewer(Composite parent) {
this(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
}
/**
* Creates a tree viewer on a newly-created tree control under the given
* parent. The tree control is created using the given SWT style bits. The
* viewer has no input, no content provider, a default label provider, no
* sorter, and no filters.
*
* @param parent
* the parent control
* @param style
* the SWT style bits used to create the tree.
*/
public TreeViewer(Composite parent, int style) {
this(new Tree(parent, style));
}
/**
* Creates a tree viewer on the given tree control. The viewer has no input,
* no content provider, a default label provider, no sorter, and no filters.
*
* @param tree
* the tree control
*/
public TreeViewer(Tree tree) {
super();
this.tree = tree;
hookControl(tree);
}
@Override
protected void addTreeListener(Control c, TreeListener listener) {
((Tree) c).addTreeListener(listener);
}
@Override
protected Widget getColumnViewerOwner(int columnIndex) {
if (columnIndex < 0 || ( columnIndex > 0 && columnIndex >= getTree().getColumnCount() ) ) {
return null;
}
if (getTree().getColumnCount() == 0)// Hang it off the table if it
return getTree();
return getTree().getColumn(columnIndex);
}
@Override
protected Item[] getChildren(Widget o) {
if (o instanceof TreeItem) {
return ((TreeItem) o).getItems();
}
if (o instanceof Tree) {
return ((Tree) o).getItems();
}
return null;
}
@Override
public Control getControl() {
return tree;
}
@Override
protected boolean getExpanded(Item item) {
return ((TreeItem) item).getExpanded();
}
@Override
protected Item getItemAt(Point p) {
TreeItem[] selection = tree.getSelection();
if( selection.length == 1 ) {
int columnCount = tree.getColumnCount();
for( int i = 0; i < columnCount; i++ ) {
if( selection[0].getBounds(i).contains(p) ) {
return selection[0];
}
}
}
return getTree().getItem(p);
}
@Override
protected int getItemCount(Control widget) {
return ((Tree) widget).getItemCount();
}
@Override
protected int getItemCount(Item item) {
return ((TreeItem) item).getItemCount();
}
@Override
protected Item[] getItems(Item item) {
return ((TreeItem) item).getItems();
}
/**
* The tree viewer implementation of this Viewer
framework
* method ensures that the given label provider is an instance of either
* ITableLabelProvider
or ILabelProvider
. If
* it is an ITableLabelProvider
, then it provides a separate
* label text and image for each column. If it is an
* ILabelProvider
, then it provides only the label text and
* image for the first column, and any remaining columns are blank.
*/
@Override
public IBaseLabelProvider getLabelProvider() {
return super.getLabelProvider();
}
@Override
protected Item getParentItem(Item item) {
return ((TreeItem) item).getParentItem();
}
@Override
protected Item[] getSelection(Control widget) {
return ((Tree) widget).getSelection();
}
/**
* Returns this tree viewer's tree control.
*
* @return the tree control
*/
public Tree getTree() {
return tree;
}
@Override
protected void hookControl(Control control) {
super.hookControl(control);
Tree treeControl = (Tree) control;
if ((treeControl.getStyle() & SWT.VIRTUAL) != 0) {
treeControl.addDisposeListener(e -> {
treeIsDisposed = true;
unmapAllElements();
});
treeControl.addListener(SWT.SetData, event -> {
if (contentProviderIsLazy) {
TreeItem item = (TreeItem) event.item;
TreeItem parentItem = item.getParentItem();
int index = event.index;
virtualLazyUpdateWidget(
parentItem == null ? (Widget) getTree()
: parentItem, index);
}
});
}
}
@Override
protected ColumnViewerEditor createViewerEditor() {
return new TreeViewerEditor(this,null,new ColumnViewerEditorActivationStrategy(this),ColumnViewerEditor.DEFAULT);
}
@Override
protected Item newItem(Widget parent, int flags, int ix) {
TreeItem item;
if (parent instanceof TreeItem) {
item = (TreeItem) createNewRowPart(getViewerRowFromItem(parent),
flags, ix).getItem();
} else {
item = (TreeItem) createNewRowPart(null, flags, ix).getItem();
}
return item;
}
@Override
protected void removeAll(Control widget) {
((Tree) widget).removeAll();
}
@Override
protected void setExpanded(Item node, boolean expand) {
TreeItem treeItem = (TreeItem) node;
if (!treeItem.isDisposed()) {
treeItem.setExpanded(expand);
}
if (contentProviderIsLazy) {
// force repaints to happen
Control control = getControl();
if (!control.isDisposed()) {
control.update();
}
}
}
@Override
protected void setSelection(List- items) {
Item[] current = getSelection(getTree());
// Don't bother resetting the same selection
if (isSameSelection(items, current)) {
return;
}
TreeItem[] newItems = new TreeItem[items.size()];
items.toArray(newItems);
getTree().setSelection(newItems);
}
@Override
protected void showItem(Item item) {
getTree().showItem((TreeItem) item);
}
@Override
protected Item getChild(Widget widget, int index) {
if (widget instanceof TreeItem) {
return ((TreeItem) widget).getItem(index);
}
if (widget instanceof Tree) {
return ((Tree) widget).getItem(index);
}
return null;
}
@Override
protected void assertContentProviderType(IContentProvider provider) {
if (provider instanceof ILazyTreeContentProvider
|| provider instanceof ILazyTreePathContentProvider) {
return;
}
super.assertContentProviderType(provider);
}
@Override
protected Object[] getRawChildren(Object parent) {
if (contentProviderIsLazy) {
return new Object[0];
}
return super.getRawChildren(parent);
}
@Override
void preservingSelection(Runnable updateCode, boolean reveal) {
if (insidePreservingSelection || !getPreserveSelection()){
// avoid preserving the selection if called reentrantly,
// see bug 172640
updateCode.run();
return;
}
// avoid costly calls to setSelectionToWidget() and getSelection() during
// updateCode.run():
insidePreservingSelection = true;
try {
super.preservingSelection(updateCode, reveal);
} finally {
insidePreservingSelection = false;
}
}
/**
* For a TreeViewer with a tree with the VIRTUAL style bit set, set the
* number of children of the given element or tree path. To set the number
* of children of the invisible root of the tree, you can pass the input
* object or an empty tree path.
*
* @param elementOrTreePath
* the element, or tree path
* @param count new number of children
*
* @since 3.2
*/
public void setChildCount(final Object elementOrTreePath, final int count) {
if (checkBusy())
return;
preservingSelection(() -> {
if (internalIsInputOrEmptyPath(elementOrTreePath)) {
getTree().setItemCount(count);
return;
}
Widget[] items = internalFindItems(elementOrTreePath);
for (Widget item : items) {
TreeItem treeItem = (TreeItem) item;
treeItem.setItemCount(count);
}
});
}
/**
* For a TreeViewer with a tree with the VIRTUAL style bit set, replace the
* given parent's child at index with the given element. If the given parent
* is this viewer's input or an empty tree path, this will replace the root
* element at the given index.
*
* This method should be called by implementers of ILazyTreeContentProvider
* to populate this viewer.
*
*
* @param parentElementOrTreePath
* the parent of the element that should be updated, or the tree
* path to that parent
* @param index
* the index in the parent's children
* @param element
* the new element
*
* @see #setChildCount(Object, int)
* @see ILazyTreeContentProvider
* @see ILazyTreePathContentProvider
*
* @since 3.2
*/
public void replace(final Object parentElementOrTreePath, final int index,
final Object element) {
if (checkBusy())
return;
Item[] selectedItems = insidePreservingSelection ? null : getSelection(getControl());
TreeSelection selection = insidePreservingSelection ? null : (TreeSelection) getSelection();
Widget[] itemsToDisassociate;
if (parentElementOrTreePath instanceof TreePath) {
TreePath elementPath = ((TreePath) parentElementOrTreePath)
.createChildPath(element);
itemsToDisassociate = internalFindItems(elementPath);
} else {
itemsToDisassociate = internalFindItems(element);
}
if (internalIsInputOrEmptyPath(parentElementOrTreePath)) {
if (index < tree.getItemCount()) {
TreeItem item = tree.getItem(index);
if (!insidePreservingSelection) {
selection = adjustSelectionForReplace(selectedItems, selection, item, element);
}
// disassociate any different item that represents the
// same element under the same parent (the tree)
for (Widget widget : itemsToDisassociate) {
if (widget instanceof TreeItem) {
TreeItem itemToDisassociate = (TreeItem) widget;
if (itemToDisassociate != item
&& itemToDisassociate.getParentItem() == null) {
int indexToDisassociate = getTree().indexOf(
itemToDisassociate);
disassociate(itemToDisassociate);
getTree().clear(indexToDisassociate, true);
}
}
}
Object oldData = item.getData();
updateItem(item, element);
if (!TreeViewer.this.equals(oldData, element)) {
item.clearAll(true);
}
}
} else {
Widget[] parentItems = internalFindItems(parentElementOrTreePath);
for (Widget widget : parentItems) {
TreeItem parentItem = (TreeItem) widget;
if (index < parentItem.getItemCount()) {
TreeItem item = parentItem.getItem(index);
if (!insidePreservingSelection) {
selection = adjustSelectionForReplace(selectedItems, selection, item, element);
}
// disassociate any different item that represents the
// same element under the same parent (the tree)
for (Widget widgetToDisassociate : itemsToDisassociate) {
if (widgetToDisassociate instanceof TreeItem) {
TreeItem itemToDisassociate = (TreeItem) widgetToDisassociate;
if (itemToDisassociate != item
&& itemToDisassociate.getParentItem() == parentItem) {
int indexToDisaccociate = parentItem
.indexOf(itemToDisassociate);
disassociate(itemToDisassociate);
parentItem.clear(indexToDisaccociate, true);
}
}
}
Object oldData = item.getData();
updateItem(item, element);
if (!TreeViewer.this.equals(oldData, element)) {
item.clearAll(true);
}
}
}
}
// Restore the selection if we are not already in a nested preservingSelection:
if (!insidePreservingSelection) {
setSelectionToWidget(selection, false);
// send out notification if old and new differ
ISelection newSelection = getSelection();
if (!newSelection.equals(selection)) {
handleInvalidSelection(selection, newSelection);
}
}
}
/**
* Fix for bug 185673: If the currently replaced item was selected, add it
* to the selection that is being restored. Only do this if its getData() is
* currently null
*
* @return the adjusted selection
*/
private TreeSelection adjustSelectionForReplace(Item[] selectedItems,
TreeSelection selection, TreeItem item, Object element) {
if (item.getData() != null) {
// Don't do anything - we are not seeing an instance of bug 185673
return selection;
}
for (Item selectedItem : selectedItems) {
if (item == selectedItem) {
// The current item was selected, but its data is null.
// The data will be replaced by the given element, so to keep
// it selected, we have to add it to the selection.
TreePath[] originalPaths = selection.getPaths();
int length = originalPaths.length;
TreePath[] paths = new TreePath[length + 1];
System.arraycopy(originalPaths, 0, paths, 0, length);
// set the element temporarily so that we can call getTreePathFromItem
item.setData(element);
paths[length] = getTreePathFromItem(item);
item.setData(null);
return new TreeSelection(paths, selection.getElementComparer());
}
}
// The item was not selected, return the given selection
return selection;
}
@Override
public boolean isExpandable(Object element) {
if (contentProviderIsLazy) {
TreeItem treeItem = (TreeItem) internalExpand(element, false);
if (treeItem == null) {
return false;
}
virtualMaterializeItem(treeItem);
return treeItem.getItemCount() > 0;
}
if (element instanceof ExpandableNode) {
return false;
}
return super.isExpandable(element);
}
@Override
protected Object getParentElement(Object element) {
boolean oldBusy = isBusy();
setBusy(true);
try {
if (contentProviderIsLazy && !contentProviderIsTreeBased && !(element instanceof TreePath)) {
ILazyTreeContentProvider lazyTreeContentProvider = (ILazyTreeContentProvider) getContentProvider();
return lazyTreeContentProvider.getParent(element);
}
if (contentProviderIsLazy && contentProviderIsTreeBased && !(element instanceof TreePath)) {
ILazyTreePathContentProvider lazyTreePathContentProvider = (ILazyTreePathContentProvider) getContentProvider();
TreePath[] parents = lazyTreePathContentProvider
.getParents(element);
if (parents != null && parents.length > 0) {
return parents[0];
}
}
return super.getParentElement(element);
} finally {
setBusy(oldBusy);
}
}
@Override
void createChildren(Widget widget, boolean materialize) {
if (contentProviderIsLazy) {
Object element = widget.getData();
if (element == null && widget instanceof TreeItem) {
// parent has not been materialized
virtualMaterializeItem((TreeItem) widget);
// try getting the element now that updateElement was called
element = widget.getData();
}
if (element == null) {
// give up because the parent is still not materialized
return;
}
Item[] children = getChildren(widget);
if (children.length == 1 && children[0].getData() == null) {
// found a dummy node
virtualLazyUpdateChildCount(widget, children.length);
children = getChildren(widget);
}
// touch all children to make sure they are materialized
for (int i = 0; i < children.length; i++) {
if (children[i].getData() == null) {
if (materialize) {
virtualLazyUpdateWidget(widget, i);
} else {
((TreeItem)children[i]).clearAll(true);
}
}
}
return;
}
super.createChildren(widget, materialize);
}
@Override
protected void internalAdd(Widget widget, Object parentElement,
Object[] childElements) {
if (contentProviderIsLazy) {
if (widget instanceof TreeItem) {
TreeItem ti = (TreeItem) widget;
int count = ti.getItemCount() + childElements.length;
ti.setItemCount(count);
ti.clearAll(false);
} else {
Tree t = (Tree) widget;
t.setItemCount(t.getItemCount() + childElements.length);
t.clearAll(false);
}
return;
}
super.internalAdd(widget, parentElement, childElements);
}
private void virtualMaterializeItem(TreeItem treeItem) {
if (treeItem.getData() != null) {
// already materialized
return;
}
if (!contentProviderIsLazy) {
return;
}
int index;
Widget parent = treeItem.getParentItem();
if (parent == null) {
parent = treeItem.getParent();
}
Object parentElement = parent.getData();
if (parentElement != null) {
if (parent instanceof Tree) {
index = ((Tree) parent).indexOf(treeItem);
} else {
index = ((TreeItem) parent).indexOf(treeItem);
}
virtualLazyUpdateWidget(parent, index);
}
}
@Override
protected void internalRefreshStruct(Widget widget, Object element,
boolean updateLabels) {
if (contentProviderIsLazy) {
// clear all starting with the given widget
if (widget instanceof Tree) {
((Tree) widget).clearAll(true);
} else if (widget instanceof TreeItem) {
((TreeItem) widget).clearAll(true);
}
int index = 0;
Widget parent = null;
if (widget instanceof TreeItem) {
TreeItem treeItem = (TreeItem) widget;
parent = treeItem.getParentItem();
if (parent == null) {
parent = treeItem.getParent();
}
if (parent instanceof Tree) {
index = ((Tree) parent).indexOf(treeItem);
} else {
index = ((TreeItem) parent).indexOf(treeItem);
}
}
virtualRefreshExpandedItems(parent, widget, element, index);
return;
}
super.internalRefreshStruct(widget, element, updateLabels);
}
/**
* Traverses the visible (expanded) part of the tree and updates child
* counts.
*
* @param parent the parent of the widget, or null
if the widget is the tree
* @param index the index of the widget in the children array of its parent, or 0 if the widget is the tree
*/
private void virtualRefreshExpandedItems(Widget parent, Widget widget, Object element, int index) {
if (widget instanceof Tree) {
if (element == null) {
((Tree) widget).setItemCount(0);
return;
}
virtualLazyUpdateChildCount(widget, getChildren(widget).length);
} else if (((TreeItem) widget).getExpanded()) {
// prevent SetData callback
((TreeItem)widget).setText(" "); //$NON-NLS-1$
virtualLazyUpdateWidget(parent, index);
} else {
return;
}
Item[] items = getChildren(widget);
for (int i = 0; i < items.length; i++) {
Item item = items[i];
Object data = item.getData();
virtualRefreshExpandedItems(widget, item, data, i);
}
}
/*
* To unmap elements correctly, we need to register a dispose listener with
* the item if the tree is virtual.
*/
@Override
protected void mapElement(Object element, final Widget item) {
super.mapElement(element, item);
// make sure to unmap elements if the tree is virtual
if ((getTree().getStyle() & SWT.VIRTUAL) != 0) {
// only add a dispose listener if item hasn't already on assigned
// because it is reused
if (item.getData(VIRTUAL_DISPOSE_KEY) == null) {
item.setData(VIRTUAL_DISPOSE_KEY, Boolean.TRUE);
item.addDisposeListener(e -> {
if (!treeIsDisposed) {
Object data = item.getData();
if (usingElementMap() && data != null) {
unmapElement(data, item);
}
}
});
}
}
}
@Override
protected ViewerRow getViewerRowFromItem(Widget item) {
if( cachedRow == null ) {
cachedRow = new TreeViewerRow((TreeItem) item);
} else {
cachedRow.setItem((TreeItem) item);
}
return cachedRow;
}
/**
* Create a new ViewerRow at rowIndex
*
* @return ViewerRow
*/
private ViewerRow createNewRowPart(ViewerRow parent, int style, int rowIndex) {
if (parent == null) {
if (rowIndex >= 0) {
return getViewerRowFromItem(new TreeItem(tree, style, rowIndex));
}
return getViewerRowFromItem(new TreeItem(tree, style));
}
if (rowIndex >= 0) {
return getViewerRowFromItem(new TreeItem((TreeItem) parent.getItem(),
SWT.NONE, rowIndex));
}
return getViewerRowFromItem(new TreeItem((TreeItem) parent.getItem(),
SWT.NONE));
}
@Override
protected void internalInitializeTree(Control widget) {
if (contentProviderIsLazy) {
if (widget instanceof Tree && widget.getData() != null) {
virtualLazyUpdateChildCount(widget, 0);
return;
}
}
super.internalInitializeTree(tree);
}
@Override
protected void updatePlus(Item item, Object element) {
if (contentProviderIsLazy) {
Object data = item.getData();
int itemCount = 0;
if (data != null) {
// item is already materialized
itemCount = ((TreeItem) item).getItemCount();
}
virtualLazyUpdateHasChildren(item, itemCount);
} else {
super.updatePlus(item, element);
}
}
/**
* Removes the element at the specified index of the parent. The selection is updated if required.
*
* @param parentOrTreePath the parent element, the input element, or a tree path to the parent element
* @param index child index
* @since 3.3
*/
public void remove(final Object parentOrTreePath, final int index) {
if (checkBusy())
return;
// in case preservingSelection() is nested avoid getSelection():
final List oldSelection = insidePreservingSelection ? null : new LinkedList<>(
Arrays.asList(((TreeSelection) getSelection()).getPaths()));
preservingSelection(() -> {
TreePath removedPath = null;
if (internalIsInputOrEmptyPath(parentOrTreePath)) {
Tree tree = (Tree) getControl();
if (index < tree.getItemCount()) {
if (getItemsLimit() > 0 && hasLimitedChildrenItems(tree)) {
internalRefreshStruct(tree, getInput(), false);
return;
}
TreeItem item1 = tree.getItem(index);
if (item1.getData() != null) {
removedPath = getTreePathFromItem(item1);
disassociate(item1);
}
item1.dispose();
}
} else {
Widget[] parentItems = internalFindItems(parentOrTreePath);
for (Widget parentWidget : parentItems) {
TreeItem parentItem = (TreeItem) parentWidget;
if (parentItem.isDisposed())
continue;
if (getItemsLimit() > 0 && hasLimitedChildrenItems(parentWidget)) {
internalRefreshStruct(parentWidget, parentWidget.getData(), false);
continue;
}
if (index < parentItem.getItemCount()) {
TreeItem item2 = parentItem.getItem(index);
if (item2.getData() == null) {
// If getData()==null and index == 0, and the
// parent item is collapsed, then we are
// being asked to remove the dummy node. We'll
// just ignore the request to remove the dummy
// node (bug 292322 and bug 296573).
if (index > 0 || parentItem.getExpanded()) {
item2.dispose();
}
} else {
removedPath = getTreePathFromItem(item2);
disassociate(item2);
item2.dispose();
}
}
}
}
if (removedPath != null && oldSelection != null) {
boolean removed = false;
for (Iterator it = oldSelection.iterator(); it.hasNext();) {
TreePath path = it.next();
if (path.startsWith(removedPath, getComparer())) {
it.remove();
removed = true;
}
}
if (removed) {
setSelection(new TreeSelection(
oldSelection
.toArray(new TreePath[oldSelection
.size()]), getComparer()),
false);
}
}
});
}
@Override
protected void handleTreeExpand(TreeEvent event) {
// Fix for Bug 271744 because windows expanding doesn't fire a focus lost
if( isCellEditorActive() ) {
applyEditorValue();
}
if (contentProviderIsLazy) {
if (event.item.getData() != null) {
Item[] children = getChildren(event.item);
if (children.length == 1 && children[0].getData()==null) {
// we have a dummy child node, ask for an updated child
// count
virtualLazyUpdateChildCount(event.item, children.length);
}
fireTreeExpanded(new TreeExpansionEvent(this, event.item
.getData()));
}
return;
}
super.handleTreeExpand(event);
}
@Override
protected void handleTreeCollapse(TreeEvent event) {
// Fix for Bug 271744 because windows is firing collapse before
// focus lost event
if( isCellEditorActive() ) {
applyEditorValue();
}
super.handleTreeCollapse(event);
}
/**
* Sets the content provider used by this TreeViewer
.
*
* Content providers for tree viewers must implement either
* {@link ITreeContentProvider}, or {@link ITreePathContentProvider}, or
* {@link ILazyTreeContentProvider}, or
* {@link ILazyTreePathContentProvider}.
*/
@Override
public void setContentProvider(IContentProvider provider) {
contentProviderIsLazy = (provider instanceof ILazyTreeContentProvider)
|| (provider instanceof ILazyTreePathContentProvider);
contentProviderIsTreeBased = provider instanceof ILazyTreePathContentProvider;
super.setContentProvider(provider);
}
/**
* For a TreeViewer with a tree with the VIRTUAL style bit set, inform the
* viewer about whether the given element or tree path has children. Avoid
* calling this method if the number of children has already been set.
*
* @param elementOrTreePath
* the element, or tree path
* @param hasChildren the new state for the element or tree path
*
* @since 3.3
*/
public void setHasChildren(final Object elementOrTreePath, final boolean hasChildren) {
if (checkBusy())
return;
preservingSelection(() -> {
if (internalIsInputOrEmptyPath(elementOrTreePath)) {
if (hasChildren) {
virtualLazyUpdateChildCount(getTree(),
getChildren(getTree()).length);
} else {
setChildCount(elementOrTreePath, 0);
}
return;
}
Widget[] items = internalFindItems(elementOrTreePath);
for (Widget widget : items) {
TreeItem item = (TreeItem) widget;
if (!hasChildren) {
item.setItemCount(0);
} else if (!item.getExpanded()) {
item.setItemCount(1);
TreeItem child = item.getItem(0);
if (child.getData() != null) {
disassociate(child);
}
item.clear(0, true);
} else {
virtualLazyUpdateChildCount(item, item.getItemCount());
}
}
});
}
/**
* Update the widget at index.
*/
private void virtualLazyUpdateWidget(Widget widget, int index) {
boolean oldBusy = isBusy();
setBusy(false);
try {
if (contentProviderIsTreeBased) {
TreePath treePath;
if (widget instanceof Item) {
if (widget.getData() == null) {
// we need to materialize the parent first
// see bug 167668
// however, that would be too risky
// see bug 182782 and bug 182598
// so we just ignore this call altogether
// and don't do this: virtualMaterializeItem((TreeItem) widget);
return;
}
treePath = getTreePathFromItem((Item) widget);
} else {
treePath = TreePath.EMPTY;
}
((ILazyTreePathContentProvider) getContentProvider())
.updateElement(treePath, index);
} else {
((ILazyTreeContentProvider) getContentProvider()).updateElement(
widget.getData(), index);
}
} finally {
setBusy(oldBusy);
}
}
/**
* Update the child count
*/
private void virtualLazyUpdateChildCount(Widget widget, int currentChildCount) {
boolean oldBusy = isBusy();
setBusy(false);
try {
if (contentProviderIsTreeBased) {
TreePath treePath;
if (widget instanceof Item) {
treePath = getTreePathFromItem((Item) widget);
} else {
treePath = TreePath.EMPTY;
}
((ILazyTreePathContentProvider) getContentProvider())
.updateChildCount(treePath, currentChildCount);
} else {
((ILazyTreeContentProvider) getContentProvider()).updateChildCount(widget.getData(), currentChildCount);
}
} finally {
setBusy(oldBusy);
}
}
/**
* Update the item with the current child count.
*/
private void virtualLazyUpdateHasChildren(Item item, int currentChildCount) {
boolean oldBusy = isBusy();
setBusy(false);
try {
if (contentProviderIsTreeBased) {
TreePath treePath;
treePath = getTreePathFromItem(item);
if (currentChildCount == 0 || !((TreeItem)item).getExpanded()) {
// item is not expanded (but may have a plus currently)
((ILazyTreePathContentProvider) getContentProvider())
.updateHasChildren(treePath);
} else {
((ILazyTreePathContentProvider) getContentProvider())
.updateChildCount(treePath, currentChildCount);
}
} else {
((ILazyTreeContentProvider) getContentProvider()).updateChildCount(item.getData(), currentChildCount);
}
} finally {
setBusy(oldBusy);
}
}
@Override
protected void disassociate(Item item) {
if (contentProviderIsLazy) {
// avoid causing a callback:
item.setText(" "); //$NON-NLS-1$
}
super.disassociate(item);
}
@Override
protected int doGetColumnCount() {
return tree.getColumnCount();
}
/**
* Sets a new selection for this viewer and optionally makes it visible.
*
* Currently the reveal
parameter is not honored because
* {@link Tree} does not provide an API to only select an item without
* scrolling it into view
*
*
* @param selection
* the new selection
* @param reveal
* true
if the selection is to be made visible,
* and false
otherwise
*/
@Override
public void setSelection(ISelection selection, boolean reveal) {
super.setSelection(selection, reveal);
}
@Override
public void editElement(Object element, int column) {
if( element instanceof TreePath ) {
try {
getControl().setRedraw(false);
setSelection(new TreeSelection((TreePath) element));
TreeItem[] items = tree.getSelection();
if( items.length == 1 ) {
ViewerRow row = getViewerRowFromItem(items[0]);
if (row != null) {
ViewerCell cell = row.getCell(column);
if (cell != null) {
triggerEditorActivationEvent(new ColumnViewerEditorActivationEvent(cell));
}
}
}
} finally {
getControl().setRedraw(true);
}
} else {
super.editElement(element, column);
}
}
@Override
void handleExpandableNodeClicked(Widget w) {
if (!(w instanceof Item item)) {
return;
}
Object data = item.getData();
if (!(data instanceof ExpandableNode expNode)) {
return;
}
Object[] sortedChildren = expNode.getRemainingElements();
Object[] children = applyItemsLimit(data, sortedChildren);
if (children.length == 0) {
return;
}
boolean oldBusy = isBusy();
Tree tree = getTree();
try {
setBusy(true);
tree.setRedraw(false);
Widget parent = getParentItem(item);
if (parent == null) {
parent = getControl();
}
// destroy widget
disassociate(item);
item.dispose();
// create children on parent
for (Object element : children) {
createTreeItem(parent, element, -1);
}
// reset the selection. client's selection listener should not be triggered.
// there was only one selection on Expandable Node.
Item[] curSel = tree.getSelection();
if (curSel.length == 1) {
tree.deselect((TreeItem) curSel[0]);
}
// Scroll to the last element, so user can see what's expanded
// end continue expanding if needed
Object lastElement = getLastElement(parent);
if (lastElement instanceof ExpandableNode) {
reveal(lastElement);
}
} finally {
tree.setRedraw(true);
setBusy(oldBusy);
}
}
/**
* Returns the data of the last item on the viewer.
*
* @param parent
*
* @return may return null
*/
Object getLastElement(Widget parent) {
Item[] items = getChildren(parent);
int length = items.length;
if (length == 0) {
return null;
}
return items[length - 1].getData();
}
}