blob: afea0fe48013ca413fdca2eaa85148c4ac813ca5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.viewers;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jface.util.Assert;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
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 <code>Tree</code> control.
* <p>
* 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).
* </p>
* <p>
* Content providers for tree viewers must implement the
* <code>ITreeContentProvider</code> interface.
* </p>
*/
public class TreeViewer extends AbstractTreeViewer {
/**
* TreeColorAndFontCollector is an helper class for color and font support
* for trees that support the ITableFontProvider and the
* ITableColorProvider.
*
* @see ITableColorProvider
* @see ITableFontProvider
*/
private class TreeColorAndFontCollector {
ITableFontProvider fontProvider = null;
ITableColorProvider colorProvider = null;
/**
* Create an instance of the receiver. Set the color and font providers
* if provider can be cast to the correct type.
*
* @param provider
* IBaseLabelProvider
*/
public TreeColorAndFontCollector(IBaseLabelProvider provider) {
if (provider instanceof ITableFontProvider)
fontProvider = (ITableFontProvider) provider;
if (provider instanceof ITableColorProvider)
colorProvider = (ITableColorProvider) provider;
}
/**
* Create an instance of the receiver with no color and font providers.
*/
public TreeColorAndFontCollector() {
}
/**
* Set the fonts and colors for the treeItem if there is a color and
* font provider available.
*
* @param treeItem
* The item to update.
* @param element
* The element being represented
* @param column
* The column index
*/
public void setFontsAndColors(TreeItem treeItem, Object element,
int column) {
if (colorProvider != null) {
treeItem.setBackground(column, colorProvider.getBackground(
element, column));
treeItem.setForeground(column, colorProvider.getForeground(
element, column));
}
if (fontProvider != null)
treeItem.setFont(column, fontProvider.getFont(element, column));
}
}
/**
* Internal tree viewer implementation.
*/
private TreeEditorImpl treeViewerImpl;
/**
* This viewer's control.
*/
private Tree tree;
/**
* This viewer's tree editor.
*/
private TreeEditor treeEditor;
/**
* The color and font collector for the cells.
*/
private TreeColorAndFontCollector treeColorAndFont = new TreeColorAndFontCollector();
/**
* Creates a tree viewer on a newly-created tree control under the given
* parent. The tree control is created using the SWT style bits
* <code>MULTI, H_SCROLL, V_SCROLL,</code> and <code>BORDER</code>. 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);
treeEditor = new TreeEditor(tree);
initTreeViewerImpl();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected void addTreeListener(Control c, TreeListener listener) {
((Tree) c).addTreeListener(listener);
}
/**
* Cancels a currently active cell editor. All changes already done in the
* cell editor are lost.
*
* @since 3.1
*/
public void cancelEditing() {
treeViewerImpl.cancelEditing();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected void doUpdateItem(final Item item, Object element) {
if (!(item instanceof TreeItem))
return;
TreeItem treeItem = (TreeItem) item;
if (treeItem.isDisposed()) {
unmapElement(element, treeItem);
return;
}
getColorAndFontCollector().setFontsAndColors(element);
IBaseLabelProvider prov = getLabelProvider();
ITableLabelProvider tprov = null;
ILabelProvider lprov = null;
IViewerLabelProvider vprov = null;
if(prov instanceof ILabelProvider)
lprov = (ILabelProvider) prov;
if (prov instanceof IViewerLabelProvider) {
vprov = (IViewerLabelProvider) prov;
}
if (prov instanceof ITableLabelProvider) {
tprov = (ITableLabelProvider) prov;
}
int columnCount = tree.getColumnCount();
if (columnCount == 0) {// If no columns were created use the label
// provider
ViewerLabel updateLabel = new ViewerLabel(treeItem.getText(),
treeItem.getImage());
if(vprov != null)
buildLabel(updateLabel,element,vprov);
else{
if(lprov != null)
buildLabel(updateLabel,element,lprov);
}
// As it is possible for user code to run the event
// loop check here.
if (treeItem.isDisposed()) {
unmapElement(element, treeItem);
return;
}
if (updateLabel.hasNewText())
treeItem.setText(updateLabel.getText());
if (updateLabel.hasNewImage())
treeItem.setImage(updateLabel.getImage());
} else {// Use the table based support
for (int column = 0; column < columnCount; column++) {
// Similar code in TableViewer.doUpdateItem()
String text = "";//$NON-NLS-1$
Image image = null;
treeColorAndFont.setFontsAndColors(treeItem, element, column);
if (tprov == null) {
if (column == 0) {
ViewerLabel updateLabel = new ViewerLabel(treeItem
.getText(), treeItem.getImage());
if(vprov != null)
buildLabel(updateLabel,element,vprov);
else{
if(lprov != null)
buildLabel(updateLabel,element,lprov);
}
// As it is possible for user code to run the event
// loop check here.
if (treeItem.isDisposed()) {
unmapElement(element, treeItem);
return;
}
text = updateLabel.getText();
image = updateLabel.getImage();
}
} else {
text = tprov.getColumnText(element, column);
image = tprov.getColumnImage(element, column);
}
// Avoid setting text to null
if (text == null)
text = ""; //$NON-NLS-1$
treeItem.setText(column, text);
if (treeItem.getImage(column) != image) {
treeItem.setImage(column, image);
}
}
}
getColorAndFontCollector().applyFontsAndColors(treeItem);
}
/**
* Starts editing the given element.
*
* @param element
* the element
* @param column
* the column number
* @since 3.1
*/
public void editElement(Object element, int column) {
treeViewerImpl.editElement(element, column);
}
/**
* Returns the cell editors of this tree viewer.
*
* @return the list of cell editors
* @since 3.1
*/
public CellEditor[] getCellEditors() {
return treeViewerImpl.getCellEditors();
}
/**
* Returns the cell modifier of this tree viewer.
*
* @return the cell modifier
* @since 3.1
*/
public ICellModifier getCellModifier() {
return treeViewerImpl.getCellModifier();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected Item[] getChildren(Widget o) {
if (o instanceof TreeItem)
return ((TreeItem) o).getItems();
if (o instanceof Tree)
return ((Tree) o).getItems();
return null;
}
/**
* Returns the column properties of this tree viewer. The properties must
* correspond with the columns of the tree control. They are used to
* identify the column in a cell modifier.
*
* @return the list of column properties
* @since 3.1
*/
public Object[] getColumnProperties() {
return treeViewerImpl.getColumnProperties();
}
/*
* (non-Javadoc) Method declared in Viewer.
*/
public Control getControl() {
return tree;
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected boolean getExpanded(Item item) {
return ((TreeItem) item).getExpanded();
}
/*
* (non-Javadoc) Method declared in StructuredViewer.
*/
protected Item getItem(int x, int y) {
return getTree().getItem(getTree().toControl(new Point(x, y)));
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected int getItemCount(Control widget) {
return ((Tree) widget).getItemCount();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected int getItemCount(Item item) {
return ((TreeItem) item).getItemCount();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected Item[] getItems(Item item) {
return ((TreeItem) item).getItems();
}
/**
* The tree viewer implementation of this <code>Viewer</code> framework
* method ensures that the given label provider is an instance of either
* <code>ITableLabelProvider</code> or <code>ILabelProvider</code>. If
* it is an <code>ITableLabelProvider</code>, then it provides a separate
* label text and image for each column. If it is an
* <code>ILabelProvider</code>, then it provides only the label text and
* image for the first column, and any remaining columns are blank.
*/
public IBaseLabelProvider getLabelProvider() {
return super.getLabelProvider();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected Item getParentItem(Item item) {
return ((TreeItem) item).getParentItem();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
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;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.AbstractTreeViewer#hookControl(org.eclipse.swt.widgets.Control)
*/
protected void hookControl(Control control) {
super.hookControl(control);
Tree treeControl = (Tree) control;
treeControl.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
treeViewerImpl.handleMouseDown(e);
}
});
if ((treeControl.getStyle() & SWT.VIRTUAL) != 0) {
treeControl.addListener(SWT.SetData, new Listener(){
public void handleEvent(Event event) {
if (getContentProvider() instanceof ILazyTreeContentProvider) {
ILazyTreeContentProvider lazyContentProvider = (ILazyTreeContentProvider) getContentProvider();
TreeItem item = (TreeItem) event.item;
TreeItem parentItem = item.getParentItem();
Object parent;
int index;
if (parentItem != null) {
parent = parentItem.getData();
index = parentItem.indexOf(item);
} else {
parent = getInput();
index = getTree().indexOf(item);
}
lazyContentProvider.updateElement(parent, index);
}
}
});
}
}
/**
* Initializes the tree viewer implementation.
*/
private void initTreeViewerImpl() {
treeViewerImpl = new TreeEditorImpl(this) {
Rectangle getBounds(Item item, int columnNumber) {
return ((TreeItem) item).getBounds(columnNumber);
}
int getColumnCount() {
return getTree().getColumnCount();
}
Item[] getSelection() {
return getTree().getSelection();
}
void setEditor(Control w, Item item, int columnNumber) {
treeEditor.setEditor(w, (TreeItem) item, columnNumber);
}
void setSelection(IStructuredSelection selection, boolean b) {
TreeViewer.this.setSelection(selection, b);
}
void showSelection() {
getTree().showSelection();
}
void setLayoutData(CellEditor.LayoutData layoutData) {
treeEditor.grabHorizontal = layoutData.grabHorizontal;
treeEditor.horizontalAlignment = layoutData.horizontalAlignment;
treeEditor.minimumWidth = layoutData.minimumWidth;
}
void handleDoubleClickEvent() {
Viewer viewer = getViewer();
fireDoubleClick(new DoubleClickEvent(viewer, viewer
.getSelection()));
fireOpen(new OpenEvent(viewer, viewer.getSelection()));
}
};
}
/**
* Returns whether there is an active cell editor.
*
* @return <code>true</code> if there is an active cell editor, and
* <code>false</code> otherwise
* @since 3.1
*/
public boolean isCellEditorActive() {
return treeViewerImpl.isCellEditorActive();
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected Item newItem(Widget parent, int flags, int ix) {
TreeItem item;
if (ix >= 0) {
if (parent instanceof TreeItem)
item = new TreeItem((TreeItem) parent, flags, ix);
else
item = new TreeItem((Tree) parent, flags, ix);
} else {
if (parent instanceof TreeItem)
item = new TreeItem((TreeItem) parent, flags);
else
item = new TreeItem((Tree) parent, flags);
}
return item;
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected void removeAll(Control widget) {
((Tree) widget).removeAll();
}
/**
* Sets the cell editors of this tree viewer.
*
* @param editors
* the list of cell editors
* @since 3.1
*/
public void setCellEditors(CellEditor[] editors) {
treeViewerImpl.setCellEditors(editors);
}
/**
* Sets the cell modifier of this tree viewer.
*
* @param modifier
* the cell modifier
* @since 3.1
*/
public void setCellModifier(ICellModifier modifier) {
treeViewerImpl.setCellModifier(modifier);
}
/**
* Sets the column properties of this tree viewer. The properties must
* correspond with the columns of the tree control. They are used to
* identify the column in a cell modifier.
*
* @param columnProperties
* the list of column properties
* @since 3.1
*/
public void setColumnProperties(String[] columnProperties) {
treeViewerImpl.setColumnProperties(columnProperties);
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected void setExpanded(Item node, boolean expand) {
((TreeItem) node).setExpanded(expand);
if(getContentProvider() instanceof ILazyTreeContentProvider) {
// force repaints to happen
getControl().update();
}
}
/**
* The tree viewer implementation of this <code>Viewer</code> framework
* method ensures that the given label provider is an instance of
* <code>ILabelProvider</code>.
*/
public void setLabelProvider(IBaseLabelProvider labelProvider) {
Assert.isTrue(labelProvider instanceof ITableLabelProvider
|| labelProvider instanceof ILabelProvider);
super.setLabelProvider(labelProvider);
treeColorAndFont = new TreeColorAndFontCollector(labelProvider);
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
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);
}
/**
* Returns <code>true</code> if the given list and array of items refer to
* the same model elements. Order is unimportant.
*
* @param items
* the list of items
* @param current
* the array of items
* @return <code>true</code> if the refer to the same elements,
* <code>false</code> otherwise
*
* @since 3.1
*/
protected boolean isSameSelection(List items, Item[] current) {
// If they are not the same size then they are not equivalent
int n = items.size();
if (n != current.length)
return false;
CustomHashtable itemSet = newHashtable(n * 2 + 1);
for (Iterator i = items.iterator(); i.hasNext();) {
Item item = (Item) i.next();
Object element = item.getData();
itemSet.put(element, element);
}
// Go through the items of the current collection
// If there is a mismatch return false
for (int i = 0; i < current.length; i++) {
if (!itemSet.containsKey(current[i].getData()))
return false;
}
return true;
}
/*
* (non-Javadoc) Method declared in AbstractTreeViewer.
*/
protected void showItem(Item item) {
getTree().showItem((TreeItem) item);
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.AbstractTreeViewer#getChild(org.eclipse.swt.widgets.Widget, int)
*/
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;
}
protected void assertContentProviderType(IContentProvider provider) {
if(provider instanceof ILazyTreeContentProvider)
return;
super.assertContentProviderType(provider);
}
protected Object[] getRawChildren(Object parent) {
if(getContentProvider() instanceof ILazyTreeContentProvider) {
return new Object[0];
}
return super.getRawChildren(parent);
}
/**
* For a TreeViewer with a tree with the VIRTUAL style bit set, set the
* number of children of the given element. To set the number of children
* of the invisible root of the tree, the input object is passed as the
* element.
*
* @param element
* @param count
*
* <strong>EXPERIMENTAL</strong>. This class or interface has been added as
* part of a work in progress. There is no guarantee that this API will remain
* unchanged during the 3.2 release cycle. Please do not use this API without
* consulting with the Platform/UI team.
* </p>
*
* @since 3.2
*/
public void setChildCount(Object element, int count) {
Tree tree = (Tree) doFindInputItem(element);
if (tree != null) {
tree.setItemCount(count);
return;
}
Widget[] items = findItems(element);
for (int i = 0; i < items.length; i++) {
((TreeItem)items[i]).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, this will replace the root element at the given
* index.
* <p>
* This method should be called by implementers of ILazyTreeContentProvider
* to populate this viewer.
* </p>
* @param parent the parent of the element that should be updated
* @param index the index in the parent's children
* @param element the new element
*
* @see #setChildCount(Object, int)
* @see ILazyTreeContentProvider
*
* <strong>EXPERIMENTAL</strong>. This class or interface has been added as
* part of a work in progress. There is no guarantee that this API will remain
* unchanged during the 3.2 release cycle. Please do not use this API without
* consulting with the Platform/UI team.
* </p>
*
* @since 3.2
*/
public void replace(Object parent, int index, Object element) {
if(parent.equals(getInput())) {
updateItem(tree.getItem(index), element);
} else {
Widget[] parentItems = findItems(parent);
for (int i = 0; i < parentItems.length; i++) {
TreeItem item = ((TreeItem) parentItems[i]).getItem(index);
updateItem(item, element);
}
}
}
public boolean isExpandable(Object element) {
if (getContentProvider() instanceof ILazyTreeContentProvider) {
TreeItem treeItem = (TreeItem) internalExpand(element, false);
if (treeItem == null) {
return false;
}
virtualMaterializeItem(treeItem);
return treeItem.getItemCount() > 0;
}
return super.isExpandable(element);
}
protected Object getParentElement(Object element) {
if(!(element instanceof TreePath) && (getContentProvider() instanceof ILazyTreeContentProvider)) {
ILazyTreeContentProvider lazyTreeContentProvider = (ILazyTreeContentProvider) getContentProvider();
return lazyTreeContentProvider.getParent(element);
}
return super.getParentElement(element);
}
protected void createChildren(Widget widget) {
if (getContentProvider() instanceof ILazyTreeContentProvider) {
ILazyTreeContentProvider lazyTreeContentProvider = (ILazyTreeContentProvider) getContentProvider();
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();
}
int childCount;
if (widget instanceof Tree) {
childCount = ((Tree) widget).getItemCount();
} else {
childCount = ((TreeItem) widget).getItemCount();
}
if (element != null && childCount > 0) {
for (int i = 0; i < childCount; i++) {
lazyTreeContentProvider.updateElement(element, i);
}
}
return;
}
super.createChildren(widget);
}
protected void internalAdd(Widget widget, Object parentElement,
Object[] childElements) {
if (getContentProvider() instanceof ILazyTreeContentProvider) {
if (widget instanceof TreeItem) {
TreeItem ti = (TreeItem) widget;
ti.setItemCount(ti.getItemCount() + 1);
ti.clearAll(false);
} else {
Tree t = (Tree) widget;
t.setItemCount(t.getItemCount() + 1);
t.clearAll(false);
}
return;
}
super.internalAdd(widget, parentElement, childElements);
}
protected void virtualMaterializeItem(TreeItem treeItem) {
if (treeItem.getData() != null) {
// already materialized
return;
}
if (!(getContentProvider() instanceof ILazyTreeContentProvider)) {
return;
}
ILazyTreeContentProvider lazyTreeContentProvider = (ILazyTreeContentProvider) getContentProvider();
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);
}
lazyTreeContentProvider.updateElement(parentElement, index);
}
}
}