blob: 151706d30be7f743847f9150059dcef8a29640c6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 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.debug.internal.ui.viewers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.viewers.provisional.IAsynchronousLabelAdapter;
import org.eclipse.debug.internal.ui.viewers.provisional.IColumnEditor;
import org.eclipse.debug.internal.ui.viewers.provisional.IColumnEditorFactoryAdapter;
import org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation;
import org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentationFactoryAdapter;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.Assert;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
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.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* A tree viewer that retrieves children and labels asynchronously via adapters
* and supports duplicate elements in the tree with different parents.
* Retrieving children and labels asynchrnously allows for arbitrary latency
* without blocking the UI thread.
* <p>
* Clients may instantiate and subclass this class.
* </p>
*
* @since 3.2
*/
public class AsynchronousTreeViewer extends AsynchronousViewer {
/**
* The tree
*/
private Tree fTree;
/**
* Cell editing
*/
private TreeEditorImpl fTreeEditorImpl;
private TreeEditor fTreeEditor;
/**
* Collection of tree paths to be expanded. A path is removed from the
* collection when it is expanded. The entire list is cleared when the input
* to the viewer changes.
*/
private List fPendingExpansion = new ArrayList();
/**
* Current column presentation or <code>null</code>
*/
private IColumnPresentation fColumnPresentation = null;
/**
* Current column editor or <code>null</code>
*/
private IColumnEditor fColumnEditor = null;
/**
* Map of columns presentation id to its visible colums ids (String[])
* When a columns presentation is not in the map, default settings are used.
*/
private Map fVisibleColumns = new HashMap();
/**
* Map of column ids to persisted sizes
*/
private Map fColumnSizes = new HashMap();
/**
* Map of column presentation id to whether columns should be displayed
* for that presentation (the user can toggle columns on/off when a
* presentation is optional.
*/
private Map fShowColumns = new HashMap();
/**
* Memento type for column sizes. Sizes are keyed by colunm presentation id
*/
private static final String COLUMN_SIZES = "COLUMN_SIZES"; //$NON-NLS-1$
/**
* Memento type for the visible columns for a presentation context.
* A memento is created for each colunm presentation keyed by column number
*/
private static final String VISIBLE_COLUMNS = "VISIBLE_COLUMNS"; //$NON-NLS-1$
/**
* Memento type for whether columns are visible for a presentation context.
* Booleans are keyed by column presentation id
*/
private static final String SHOW_COLUMNS = "SHOW_COLUMNS"; //$NON-NLS-1$
/**
* Memento key for the number of visible columns in a VISIBLE_COLUMNS memento
* or for the width of a column
*/
private static final String SIZE = "SIZE"; //$NON-NLS-1$
/**
* Memento key prefix a visible column
*/
private static final String COLUMN = "COLUMN"; //$NON-NLS-1$
/**
* Persist column sizes when they change.
*
* @since 3.2
*/
class ColumnListener implements ControlListener {
/* (non-Javadoc)
* @see org.eclipse.swt.events.ControlListener#controlMoved(org.eclipse.swt.events.ControlEvent)
*/
public void controlMoved(ControlEvent e) {
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.ControlListener#controlResized(org.eclipse.swt.events.ControlEvent)
*/
public void controlResized(ControlEvent e) {
persistColumnSizes();
}
}
private ColumnListener fListener = new ColumnListener();
/**
* Creates an asynchronous 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 AsynchronousTreeViewer(Composite parent) {
this(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.VIRTUAL);
}
/**
* Creates an asynchronous 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.
*
* @param parent
* the parent control
* @param style
* the SWT style bits used to create the tree.
*/
public AsynchronousTreeViewer(Composite parent, int style) {
this(new Tree(parent, style));
}
/**
* Creates an asynchronous 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 AsynchronousTreeViewer(Tree tree) {
super();
Assert.isTrue((tree.getStyle() & SWT.VIRTUAL) != 0);
fTree = tree;
hookControl(fTree);
fTreeEditor = new TreeEditor(tree);
initTreeViewerImpl();
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.StructuredViewer#hookControl(org.eclipse.swt.widgets.Control)
*/
protected void hookControl(Control control) {
super.hookControl(control);
Tree tree = (Tree)control;
tree.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
if (isShowColumns()) {
Item[] items = fTreeEditorImpl.getSelection();
if (items.length > 0) {
TreeItem treeItem = (TreeItem) items[0];
if (treeItem != null) {
Object element = treeItem.getData();
updateColumnEditor(element);
if (fColumnEditor != null) {
int columnToEdit = -1;
int columns = fTree.getColumnCount();
if (columns == 0) {
// If no TreeColumn, Tree acts as if it has a single column
// which takes the whole width.
columnToEdit = 0;
} else {
columnToEdit = -1;
for (int i = 0; i < columns; i++) {
Rectangle bounds = fTreeEditorImpl.getBounds(treeItem, i);
if (bounds.contains(e.x, e.y)) {
columnToEdit = i;
break;
}
}
if (columnToEdit == -1) {
return;
}
}
CellEditor cellEditor = fColumnEditor.getCellEditor(getVisibleColumns()[columnToEdit], element, fTree);
if (cellEditor == null) {
return;
}
disposeCellEditors();
CellEditor[] newEditors = new CellEditor[columns];
newEditors[columnToEdit] = cellEditor;
setCellEditors(newEditors);
setCellModifier(fColumnEditor.getCellModifier());
setColumnProperties(getVisibleColumns());
}
}
}
}
fTreeEditorImpl.handleMouseDown(e);
}
});
tree.addTreeListener(new TreeListener() {
public void treeExpanded(TreeEvent e) {
((TreeItem) e.item).setExpanded(true);
ModelNode node = findNode(e.item);
if (node != null) {
internalRefresh(node);
}
}
public void treeCollapsed(TreeEvent e) {
}
});
tree.addMouseListener(new MouseListener() {
public void mouseUp(MouseEvent e) {
}
public void mouseDown(MouseEvent e) {
}
public void mouseDoubleClick(MouseEvent e) {
TreeItem item = ((Tree) e.widget).getItem(new Point(e.x, e.y));
if (item != null) {
if (item.getExpanded()) {
item.setExpanded(false);
} else {
item.setExpanded(true);
ModelNode node = findNode(item);
if (node != null) {
internalRefresh(node);
}
}
}
}
});
}
/**
* Sets the column editor for the given element
*
* @param element
*/
protected void updateColumnEditor(Object element) {
IColumnEditorFactoryAdapter factoryAdapter = getColumnEditorFactoryAdapter(element);
if (factoryAdapter != null) {
if (fColumnEditor != null) {
if (fColumnEditor.getId().equals(factoryAdapter.getColumnEditorId(getPresentationContext(), element))) {
// no change
return;
} else {
// dipose current
fColumnEditor.dispose();
}
}
// create new one
fColumnEditor = factoryAdapter.createColumnEditor(getPresentationContext(), element);
if (fColumnEditor != null) {
fColumnEditor.init(getPresentationContext());
}
} else {
// no editor - dispose current
if (fColumnEditor != null) {
fColumnEditor.dispose();
fColumnEditor = null;
}
}
}
/**
* Returns the tree control for this viewer.
*
* @return the tree control for this viewer
*/
public Tree getTree() {
return fTree;
}
/**
* Updates whether the given node has children.
*
* @param node node to update
*/
protected void updateHasChildren(ModelNode node) {
((AsynchronousTreeModel)getModel()).updateHasChildren(node);
}
/**
* Expands all elements in the given tree selection.
*
* @param selection
*/
public synchronized void expand(ISelection selection) {
if (selection instanceof TreeSelection) {
TreePath[] paths = ((TreeSelection) selection).getPaths();
for (int i = 0; i < paths.length; i++) {
fPendingExpansion.add(paths[i]);
}
if (getControl().getDisplay().getThread() == Thread.currentThread()) {
attemptExpansion();
} else {
WorkbenchJob job = new WorkbenchJob("attemptExpansion") { //$NON-NLS-1$
public IStatus runInUIThread(IProgressMonitor monitor) {
attemptExpansion();
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
}
}
/**
* Attempts to expand all pending expansions.
*/
synchronized void attemptExpansion() {
if (fPendingExpansion != null) {
for (Iterator i = fPendingExpansion.iterator(); i.hasNext();) {
TreePath path = (TreePath) i.next();
if (attemptExpansion(path)) {
i.remove();
}
}
}
}
/**
* Attempts to expand the given tree path and returns whether the expansion
* was completed.
*
* @param path path to expand
* @return whether the expansion was completed
*/
synchronized boolean attemptExpansion(TreePath path) {
int segmentCount = path.getSegmentCount();
for (int j = segmentCount - 1; j >= 0; j--) {
Object element = path.getSegment(j);
ModelNode[] nodes = getModel().getNodes(element);
if (nodes != null) {
for (int k = 0; k < nodes.length; k++) {
ModelNode node = nodes[k];
TreePath treePath = node.getTreePath();
if (path.startsWith(treePath, null)) {
if (!node.isDisposed()) {
Widget widget = findItem(node);
if (widget == null) {
// force the widgets to be mapped
ModelNode parent = node.getParentNode();
ModelNode child = node;
widget = findItem(parent);
while (widget == null && parent != null) {
child = parent;
parent = parent.getParentNode();
if (parent != null) {
widget = findItem(parent);
treePath = parent.getTreePath();
}
}
int childIndex = parent.getChildIndex(child);
if (childIndex < 0) {
return false;
}
TreeItem[] items = getItems(widget);
if (childIndex < items.length) {
widget = items[childIndex];
mapElement(child, widget);
widget.setData(child.getElement());
treePath = child.getTreePath();
node = child;
} else {
return false;
}
}
if (widget instanceof TreeItem && !widget.isDisposed()) {
TreeItem treeItem = (TreeItem) widget;
if (treeItem.getExpanded()) {
return path.getSegmentCount() == treePath.getSegmentCount();
}
if (treeItem.getItemCount() > 0) {
updateChildren(node);
expand(treeItem);
if (path.getSegmentCount() == treePath.getSegmentCount()) {
return true;
}
return false;
}
}
}
}
}
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.Viewer#getControl()
*/
public Control getControl() {
return fTree;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.AsynchronousViewer#dispose()
*/
public synchronized void dispose() {
if (fColumnPresentation != null) {
fColumnPresentation.dispose();
}
disposeCellEditors();
if (fColumnEditor != null) {
fColumnEditor.dispose();
}
super.dispose();
}
/**
* Disposes cell editors
*/
protected void disposeCellEditors() {
CellEditor[] cellEditors = getCellEditors();
if (cellEditors != null) {
for (int i = 0; i < cellEditors.length; i++) {
CellEditor editor = cellEditors[i];
if (editor != null) {
editor.dispose();
}
}
}
setCellEditors(null);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object,
* java.lang.Object)
*/
synchronized protected void inputChanged(Object input, Object oldInput) {
fPendingExpansion.clear();
super.inputChanged(input, oldInput);
resetColumns(input);
}
/**
* Refreshes the columns in the view, based on the viewer input.
*/
public void refreshColumns() {
configureColumns();
refresh();
}
/**
* Configures the columns for the given viewer input.
*
* @param input
*/
protected void resetColumns(Object input) {
if (input != null) {
// only change columns if the input is non-null (persist when empty)
IColumnPresentationFactoryAdapter factory = getColumnPresenetationFactoryAdapter(input);
PresentationContext context = (PresentationContext) getPresentationContext();
String type = null;
if (factory != null) {
type = factory.getColumnPresentationId(context, input);
}
if (type != null) {
if (fColumnPresentation != null) {
if (!fColumnPresentation.getId().equals(type)) {
// dispose old, create new
fColumnPresentation.dispose();
fColumnPresentation = null;
}
}
if (fColumnPresentation == null) {
fColumnPresentation = factory.createColumnPresentation(context, input);
if (fColumnPresentation != null) {
fColumnPresentation.init(context);
configureColumns();
}
}
} else {
if (fColumnPresentation != null) {
fColumnPresentation.dispose();
fColumnPresentation = null;
configureColumns();
}
}
}
}
/**
* Configures the columns based on the current settings.
*
* @param input
*/
protected void configureColumns() {
if (fColumnPresentation != null) {
IColumnPresentation build = null;
if (isShowColumns(fColumnPresentation.getId())) {
build = fColumnPresentation;
}
buildColumns(build);
} else {
// get rid of columns
buildColumns(null);
}
}
/**
* Creates new columns for the given presentation.
*
* TODO: does this need to be async?
*
* @param presentation
*/
protected void buildColumns(IColumnPresentation presentation) {
// dispose current columns, persisting their weigts
Tree tree = getTree();
final TreeColumn[] columns = tree.getColumns();
String[] visibleColumnIds = getVisibleColumns();
for (int i = 0; i < columns.length; i++) {
TreeColumn treeColumn = columns[i];
treeColumn.removeControlListener(fListener);
treeColumn.dispose();
}
PresentationContext presentationContext = (PresentationContext) getPresentationContext();
if (presentation != null) {
for (int i = 0; i < visibleColumnIds.length; i++) {
String id = visibleColumnIds[i];
String header = presentation.getHeader(id);
// TODO: allow client to specify style
TreeColumn column = new TreeColumn(tree, SWT.LEFT, i);
column.setMoveable(true);
column.setText(header);
column.setWidth(1);
ImageDescriptor image = presentation.getImageDescriptor(id);
if (image != null) {
column.setImage(getImage(image));
}
column.setData(id);
}
tree.setHeaderVisible(true);
tree.setLinesVisible(true);
presentationContext.setColumns(getVisibleColumns());
} else {
tree.setHeaderVisible(false);
tree.setLinesVisible(false);
presentationContext.setColumns(null);
}
int avg = tree.getSize().x;
if (visibleColumnIds != null)
avg /= visibleColumnIds.length;
if (avg == 0) {
tree.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
Tree tree2 = getTree();
String[] visibleColumns = getVisibleColumns();
if (visibleColumns != null) {
int avg1 = tree2.getSize().x / visibleColumns.length;
initColumns(avg1);
}
tree2.removePaintListener(this);
}
});
} else {
initColumns(avg);
}
}
private void initColumns(int widthHint) {
TreeColumn[] columns = getTree().getColumns();
for (int i = 0; i < columns.length; i++) {
TreeColumn treeColumn = columns[i];
Integer width = (Integer) fColumnSizes.get(treeColumn.getData());
if (width == null) {
treeColumn.setWidth(widthHint);
} else {
treeColumn.setWidth(width.intValue());
}
treeColumn.addControlListener(fListener);
}
}
/**
* Persists column sizes in cache
*/
protected void persistColumnSizes() {
Tree tree = getTree();
TreeColumn[] columns = tree.getColumns();
for (int i = 0; i < columns.length; i++) {
TreeColumn treeColumn = columns[i];
Object id = treeColumn.getData();
fColumnSizes.put(id, new Integer(treeColumn.getWidth()));
}
}
/**
* Returns the column presentation factory for the given element or <code>null</code>.
*
* @param input
* @return column presentation factory of <code>null</code>
*/
protected IColumnPresentationFactoryAdapter getColumnPresenetationFactoryAdapter(Object input) {
if (input instanceof IColumnPresentationFactoryAdapter) {
return (IColumnPresentationFactoryAdapter) input;
}
if (input instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) input;
return (IColumnPresentationFactoryAdapter) adaptable.getAdapter(IColumnPresentationFactoryAdapter.class);
}
return null;
}
/**
* Returns the column editor factory for the given element or <code>null</code>.
*
* @param input
* @return column editor factory of <code>null</code>
*/
protected IColumnEditorFactoryAdapter getColumnEditorFactoryAdapter(Object input) {
if (input instanceof IColumnEditorFactoryAdapter) {
return (IColumnEditorFactoryAdapter) input;
}
if (input instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) input;
return (IColumnEditorFactoryAdapter) adaptable.getAdapter(IColumnEditorFactoryAdapter.class);
}
return null;
}
/**
* Constructs and returns a tree path for the given item
* or <code>null</code>. Must be called
* from the UI thread.
*
* @param item
* item to constuct a path for
* @return tree path for the item or <code>null</code> if none
*/
protected synchronized TreePath getTreePath(TreeItem item) {
TreeItem parent = item;
List path = new ArrayList();
while (parent != null && !parent.isDisposed()) {
Object parentElement = parent.getData();
if (parentElement == null) {
// this is a fix for bug 139859:
// on Mac, an item gets a 'selection' event before 'set data' when
// scrolling with arrow keys. so this forces the item to get a
// 'set data' callback
parent.getItemCount();
parentElement = parent.getData();
if (parentElement == null)
return null;
}
path.add(0, parentElement);
parent = parent.getParentItem();
}
Object data = fTree.getData();
if (data == null) {
return null;
}
path.add(0, data);
return new TreePath(path.toArray());
}
/**
* Returns the tree paths to the given element in this viewer, possibly
* empty.
*
* @param element model element
* @return the paths to the element in this viewer
*/
public TreePath[] getTreePaths(Object element) {
ModelNode[] nodes = getModel().getNodes(element);
if (nodes == null) {
return new TreePath[]{};
}
TreePath[] paths = new TreePath[nodes.length];
for (int i = 0; i < nodes.length; i++) {
paths[i] = nodes[i].getTreePath();
}
return paths;
}
protected int getItemCount(Widget widget) {
if (widget instanceof TreeItem) {
return ((TreeItem) widget).getItemCount();
}
return ((Tree) widget).getItemCount();
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.model.viewers.AsynchronousViewer#setItemCount(org.eclipse.swt.widgets.Widget, int)
*/
protected void setItemCount(Widget widget, int itemCount) {
if (widget == fTree) {
fTree.setItemCount(itemCount);
} else {
((TreeItem) widget).setItemCount(itemCount);
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.model.viewers.AsynchronousViewer#getChildWidget(org.eclipse.swt.widgets.Widget, int)
*/
protected Widget getChildWidget(Widget parent, int index) {
if (parent instanceof Tree) {
Tree tree = (Tree) parent;
if (index < tree.getItemCount()) {
return tree.getItem(index);
}
} else if (parent instanceof TreeItem){
TreeItem item = (TreeItem) parent;
if (index < item.getItemCount()) {
return item.getItem(index);
}
}
return null;
}
private TreeItem[] getItems(Widget widget) {
if (widget instanceof TreeItem) {
return ((TreeItem) widget).getItems();
} else {
return fTree.getItems();
}
}
/**
* Returns the parent widget for the given widget or <code>null</code>
*
* @param widget
* @return parent widget or <code>null</code>
*/
protected Widget getParentWidget(Widget widget) {
if (widget instanceof TreeItem) {
TreeItem parentItem = ((TreeItem)widget).getParentItem();
if (parentItem == null) {
return getControl();
}
return parentItem;
}
return null;
}
/**
* Expands the given tree item and all of its parents. Does *not* update
* elements or retrieve children.
*
* @param child
* item to expand
*/
private void expand(TreeItem child) {
if (!child.getExpanded()) {
child.setExpanded(true);
TreeItem parent = child.getParentItem();
if (parent != null) {
expand(parent);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.model.viewers.AsynchronousViewer#clear(org.eclipse.swt.widgets.Widget)
*/
protected void clear(Widget widget) {
if (DEBUG_VIEWER) {
DebugUIPlugin.debug("CLEAR [" + widget + "]"); //$NON-NLS-1$//$NON-NLS-2$
}
if (widget instanceof TreeItem && !widget.isDisposed()) {
TreeItem item = (TreeItem) widget;
TreeItem parentItem = item.getParentItem();
if (parentItem == null) {
int index = fTree.indexOf(item);
if (index >= 0)
fTree.clear(index, true);
} else {
int index = parentItem.indexOf(item);
if (index >= 0)
parentItem.clear(index, true);
}
item.clearAll(true);
} else {
fTree.clearAll(true);
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.AsynchronousViewer#clearChildren(org.eclipse.swt.widgets.Widget)
*/
protected void clearChildren(Widget widget) {
if (DEBUG_VIEWER) {
DebugUIPlugin.debug("CLEAR_CHILDREN [" + widget + "]"); //$NON-NLS-1$//$NON-NLS-2$
}
if (widget instanceof TreeItem && !widget.isDisposed()) {
TreeItem item = (TreeItem) widget;
item.clearAll(true);
} else {
fTree.clearAll(true);
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.AsynchronousViewer#clearChild(org.eclipse.swt.widgets.Widget, int)
*/
protected void clearChild(Widget parent, int childIndex) {
if (DEBUG_VIEWER) {
DebugUIPlugin.debug("CLEAR_CHILD [" + parent + "]: " + childIndex); //$NON-NLS-1$//$NON-NLS-2$
}
if (parent instanceof TreeItem && !parent.isDisposed()) {
TreeItem item = (TreeItem) parent;
item.clear(childIndex, true);
} else {
fTree.clear(childIndex, true);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.internal.ui.viewers.AsynchronousModelViewer#newSelectionFromWidget()
*/
protected ISelection newSelectionFromWidget() {
Control control = getControl();
if (control == null || control.isDisposed()) {
return StructuredSelection.EMPTY;
}
List list = getSelectionFromWidget();
return new TreeSelection((TreePath[]) list.toArray(new TreePath[list.size()]));
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget()
*/
protected synchronized List getSelectionFromWidget() {
TreeItem[] selection = fTree.getSelection();
List paths = new ArrayList(selection.length);
for (int i = 0; i < selection.length; i++) {
TreePath treePath = getTreePath(selection[i]);
if (treePath != null) {
paths.add(treePath);
}
}
return paths;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.model.viewers.AsynchronousViewer#internalRefresh(org.eclipse.debug.internal.ui.model.viewers.ModelNode)
*/
protected void internalRefresh(ModelNode node) {
super.internalRefresh(node);
updateHasChildren(node);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.StructuredViewer#reveal(java.lang.Object)
*/
public void reveal(Object element) {
// TODO: in virtual case, we should attempt expansion
ModelNode[] nodes = getModel().getNodes(element);
if (nodes != null) {
for (int i = 0; i < nodes.length; i++) {
ModelNode node = nodes[i];
Widget widget = findItem(node);
if (widget instanceof TreeItem) {
// TODO: only reveals the first occurrence - should we reveal all?
TreeItem item = (TreeItem) widget;
Tree tree = (Tree) getControl();
tree.showItem(item);
return;
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.ui.viewers.AsynchronousViewer#doAttemptSelectionToWidget(org.eclipse.jface.viewers.ISelection,
* boolean)
*/
protected synchronized ISelection doAttemptSelectionToWidget(ISelection selection, boolean reveal) {
List remaining = new ArrayList();
if (!selection.isEmpty()) {
List toSelect = new ArrayList();
List theNodes = new ArrayList();
List theElements = new ArrayList();
TreeSelection treeSelection = (TreeSelection) selection;
TreePath[] paths = treeSelection.getPaths();
for (int i = 0; i < paths.length; i++) {
TreePath path = paths[i];
if (path == null) {
continue;
}
ModelNode[] nodes = getModel().getNodes(path.getLastSegment());
boolean selected = false;
if (nodes != null) {
for (int j = 0; j < nodes.length; j++) {
ModelNode node = nodes[j];
if (node.correspondsTo(path)) {
Widget widget = findItem(node);
if (widget != null && !widget.isDisposed()) {
toSelect.add(widget);
theNodes.add(node);
theElements.add(path.getLastSegment());
selected = true;
break;
}
}
// attempt to map widget
ModelNode parent = node.getParentNode();
ModelNode child = node;
if (parent != null) {
Widget widget = findItem(parent);
if (widget != null && !widget.isDisposed()) {
int childIndex = parent.getChildIndex(child);
if (childIndex < 0) {
break;
}
TreeItem[] items = getItems(widget);
if (childIndex < items.length) {
widget = items[childIndex];
mapElement(child, widget);
widget.setData(child.getElement());
toSelect.add(widget);
theNodes.add(child);
theElements.add(child.getElement());
selected = true;
} else {
break;
}
}
}
}
}
if (!selected) {
remaining.add(path);
}
}
if (!toSelect.isEmpty()) {
final TreeItem[] items = (TreeItem[]) toSelect.toArray(new TreeItem[toSelect.size()]);
// TODO: hack to ensure selection contains 'selected' element
// instead of 'equivalent' element. Handles synch problems
// between set selection & refresh
for (int i = 0; i < items.length; i++) {
TreeItem item = items[i];
Object element = theElements.get(i);
if (!item.isDisposed() && item.getData() != element) {
ModelNode theNode = (ModelNode) theNodes.get(i);
Widget mapped = findItem(theNode);
if (mapped == null) {
// the node has been unmapped from the widget (pushed down perhaps)
return selection;
}
theNode.remap(element);
item.setData(element);
}
}
fTree.setSelection(items);
if (reveal) {
fTree.showItem(items[0]);
}
}
} else {
fTree.setSelection(new TreeItem[0]);
}
return new TreeSelection((TreePath[]) remaining.toArray(new TreePath[remaining.size()]));
}
/**
* Collapses all items in the tree.
*/
public void collapseAll() {
TreeItem[] items = fTree.getItems();
for (int i = 0; i < items.length; i++) {
TreeItem item = items[i];
if (item.getExpanded())
collapse(item);
}
}
/**
* Collaspes the given item and all of its children items.
*
* @param item
* item to collapose recursively
*/
protected void collapse(TreeItem item) {
TreeItem[] items = item.getItems();
for (int i = 0; i < items.length; i++) {
TreeItem child = items[i];
if (child.getExpanded()) {
collapse(child);
}
}
item.setExpanded(false);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.ui.viewers.AsynchronousViewer#setColor(org.eclipse.swt.widgets.Widget,
* org.eclipse.swt.graphics.RGB, org.eclipse.swt.graphics.RGB)
*/
protected void setColors(Widget widget, RGB[] foregrounds, RGB[] backgrounds) {
if (widget instanceof TreeItem) {
TreeItem item = (TreeItem) widget;
Color[] fgs = getColors(foregrounds);
for (int i = 0; i < fgs.length; i++) {
item.setForeground(i, fgs[i]);
}
Color[] bgs = getColors(backgrounds);
for (int i = 0; i < bgs.length; i++) {
item.setBackground(i, bgs[i]);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.ui.viewers.AsynchronousViewer#setFont(org.eclipse.swt.widgets.Widget,
* org.eclipse.swt.graphics.FontData)
*/
protected void setFonts(Widget widget, FontData[] fontData) {
if (widget instanceof TreeItem) {
TreeItem item = (TreeItem) widget;
Font[] fonts = getFonts(fontData);
for (int i = 0; i < fonts.length; i++) {
item.setFont(i, fonts[i]);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.ui.viewers.AsynchronousViewer#acceptsSelection(org.eclipse.jface.viewers.ISelection)
*/
protected boolean acceptsSelection(ISelection selection) {
return selection instanceof TreeSelection;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.ui.viewers.AsynchronousViewer#getEmptySelection()
*/
protected ISelection getEmptySelection() {
return new TreeSelection(new TreePath[0]);
}
/**
* Adds the item specified by the given tree path to this tree. Can be
* called in a non-UI thread.
*
* @param treePath
*/
public void add(TreePath treePath) {
((AsynchronousTreeModel)getModel()).add(treePath);
}
/**
* Removes the item specified in the given tree path from this tree. Can be
* called in a non-UI thread.
*
* @param treePath
*/
public void remove(TreePath treePath) {
synchronized (this) {
for (Iterator i = fPendingExpansion.iterator(); i.hasNext();) {
TreePath expansionPath = (TreePath) i.next();
if (expansionPath.startsWith(treePath, null)) {
i.remove();
}
}
}
((AsynchronousTreeModel)getModel()).remove(treePath);
}
protected void restoreLabels(Item item) {
TreeItem treeItem = (TreeItem) item;
String[] values = (String[]) treeItem.getData(OLD_LABEL);
Image[] images = (Image[])treeItem.getData(OLD_IMAGE);
if (values != null) {
treeItem.setText(values);
treeItem.setImage(images);
}
}
protected void setLabels(Widget widget, String[] text, ImageDescriptor[] image) {
if (widget instanceof TreeItem) {
TreeItem item = (TreeItem) widget;
if (!item.isDisposed()) {
item.setText(text);
item.setData(OLD_LABEL, text);
Image[] images = getImages(image);
item.setImage(images);
item.setData(OLD_IMAGE, images);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.AsynchronousModelViewer#createModel()
*/
protected AsynchronousModel createModel() {
return new AsynchronousTreeModel(this);
}
/**
* Attempt pending udpates. Subclasses may override but should call super.
*/
protected void attemptPendingUpdates() {
attemptExpansion();
super.attemptPendingUpdates();
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.model.viewers.AsynchronousViewer#createUpdatePolicy()
*/
public AbstractUpdatePolicy createUpdatePolicy() {
return new TreeUpdatePolicy();
}
protected synchronized void unmapAllElements() {
super.unmapAllElements();
Tree tree = getTree();
if (!tree.isDisposed()) {
TreeItem[] items = tree.getItems();
for (int i = 0; i < items.length; i++) {
items[i].dispose();
}
clear(tree);
}
}
/**
* Returns the current column presentation for this viewer, or <code>null</code>
* if none.
*
* @return column presentation or <code>null</code>
*/
public IColumnPresentation getColumnPresentation() {
return fColumnPresentation;
}
/**
* Returns identifiers of the visible columns in this viewer, or <code>null</code>
* if there is currently no column presentation.
*
* @return visible columns or <code>null</code>
*/
public String[] getVisibleColumns() {
IColumnPresentation presentation = getColumnPresentation();
if (presentation != null) {
String[] columns = (String[]) fVisibleColumns.get(presentation.getId());
if (columns == null) {
return presentation.getInitialColumns();
}
return columns;
}
return null;
}
/**
* Sets the ids of visible columns, or <code>null</code> to set default columns.
* Only effects the current column presentation.
*
* @param ids visible columns
*/
public void setVisibleColumns(String[] ids) {
IColumnPresentation presentation = getColumnPresentation();
if (presentation != null) {
fVisibleColumns.remove(presentation.getId());
if (ids != null) {
// put back in table if not default
String[] columns = presentation.getInitialColumns();
if (columns.length == ids.length) {
for (int i = 0; i < columns.length; i++) {
if (!ids[i].equals(columns[i])) {
fVisibleColumns.put(presentation.getId(), ids);
break;
}
}
} else {
fVisibleColumns.put(presentation.getId(), ids);
}
}
PresentationContext presentationContext = (PresentationContext) getPresentationContext();
presentationContext.setColumns(getVisibleColumns());
refreshColumns();
}
}
/**
* Save viewer state into the given memento.
*
* @param memento
*/
public void saveState(IMemento memento) {
if (!fColumnSizes.isEmpty()) {
Iterator iterator = fColumnSizes.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Entry) iterator.next();
IMemento sizes = memento.createChild(COLUMN_SIZES, (String)entry.getKey());
sizes.putInteger(SIZE, ((Integer)entry.getValue()).intValue());
}
}
if (!fShowColumns.isEmpty()) {
Iterator iterator = fShowColumns.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Entry) iterator.next();
IMemento sizes = memento.createChild(SHOW_COLUMNS, (String)entry.getKey());
sizes.putString(SHOW_COLUMNS, ((Boolean)entry.getValue()).toString());
}
}
if (!fVisibleColumns.isEmpty()) {
Iterator iterator = fVisibleColumns.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Entry) iterator.next();
String id = (String) entry.getKey();
IMemento visible = memento.createChild(VISIBLE_COLUMNS, id);
String[] columns = (String[]) entry.getValue();
visible.putInteger(SIZE, columns.length);
for (int i = 0; i < columns.length; i++) {
visible.putString(COLUMN+Integer.toString(i), columns[i]);
}
}
}
}
/**
* Resets any persisted column size for the given columns
*/
public void resetColumnSizes(String[] columnIds) {
for (int i = 0; i < columnIds.length; i++) {
fColumnSizes.remove(columnIds[i]);
}
}
/**
* Initializes veiwer state from the memento
*
* @param memento
*/
public void initState(IMemento memento) {
IMemento[] mementos = memento.getChildren(COLUMN_SIZES);
for (int i = 0; i < mementos.length; i++) {
IMemento child = mementos[i];
String id = child.getID();
Integer size = child.getInteger(SIZE);
if (size != null) {
fColumnSizes.put(id, size);
}
}
mementos = memento.getChildren(SHOW_COLUMNS);
for (int i = 0; i < mementos.length; i++) {
IMemento child = mementos[i];
String id = child.getID();
Boolean bool = Boolean.valueOf(child.getString(SHOW_COLUMNS));
if (!bool.booleanValue()) {
fShowColumns.put(id, bool);
}
}
mementos = memento.getChildren(VISIBLE_COLUMNS);
for (int i = 0; i < mementos.length; i++) {
IMemento child = mementos[i];
String id = child.getID();
Integer integer = child.getInteger(SIZE);
if (integer != null) {
int length = integer.intValue();
String[] columns = new String[length];
for (int j = 0; j < length; j++) {
columns[j] = child.getString(COLUMN+Integer.toString(j));
}
fVisibleColumns.put(id, columns);
}
}
}
/**
* Initializes the tree viewer implementation.
*/
private void initTreeViewerImpl() {
fTreeEditorImpl = 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) {
fTreeEditor.setEditor(w, (TreeItem) item, columnNumber);
}
void setSelection(IStructuredSelection selection, boolean b) {
AsynchronousTreeViewer.this.setSelection(selection, b);
}
void showSelection() {
getTree().showSelection();
}
void setLayoutData(CellEditor.LayoutData layoutData) {
fTreeEditor.grabHorizontal = layoutData.grabHorizontal;
fTreeEditor.horizontalAlignment = layoutData.horizontalAlignment;
fTreeEditor.minimumWidth = layoutData.minimumWidth;
}
void handleDoubleClickEvent() {
Viewer viewer = getViewer();
fireDoubleClick(new DoubleClickEvent(viewer, viewer
.getSelection()));
fireOpen(new OpenEvent(viewer, viewer.getSelection()));
}
};
}
/**
* Starts editing the given element.
*
* @param element
* the element
* @param column
* the column number
*/
public void editElement(Object element, int column) {
fTreeEditorImpl.editElement(element, column);
}
/**
* Returns the cell editors of this tree viewer.
*
* @return the list of cell editors
*/
public CellEditor[] getCellEditors() {
return fTreeEditorImpl.getCellEditors();
}
/**
* Returns the cell modifier of this tree viewer.
*
* @return the cell modifier
*/
public ICellModifier getCellModifier() {
return fTreeEditorImpl.getCellModifier();
}
/**
* Cancels a currently active cell editor. All changes already done in the
* cell editor are lost.
*
*/
public void cancelEditing() {
fTreeEditorImpl.cancelEditing();
}
/**
* Returns whether there is an active cell editor.
*
* @return <code>true</code> if there is an active cell editor, and
* <code>false</code> otherwise
*/
public boolean isCellEditorActive() {
return fTreeEditorImpl.isCellEditorActive();
}
/**
* Sets the cell editors of this tree viewer.
*
* @param editors
* the list of cell editors
*/
protected void setCellEditors(CellEditor[] editors) {
fTreeEditorImpl.setCellEditors(editors);
}
/**
* Sets the cell modifier of this tree viewer.
*
* @param modifier
* the cell modifier
*/
protected void setCellModifier(ICellModifier modifier) {
fTreeEditorImpl.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
*/
protected void setColumnProperties(String[] columnProperties) {
fTreeEditorImpl.setColumnProperties(columnProperties);
}
/**
* Toggles columns on/off for the current column presentation, if any.
*
* @param show whether to show columns if the current input suppports
* columns
*/
public void setShowColumns(boolean show) {
if (show) {
if (!isShowColumns()) {
fShowColumns.remove(fColumnPresentation.getId());
}
} else {
if (isShowColumns()){
fShowColumns.put(fColumnPresentation.getId(), Boolean.FALSE);
}
}
refreshColumns();
}
/**
* Returns whether columns are being displayed currently.
*
* @return
*/
public boolean isShowColumns() {
if (fColumnPresentation != null) {
return isShowColumns(fColumnPresentation.getId());
}
return false;
}
/**
* Returns whether columns can be toggled on/off for the current input.
*
* @return whether columns can be toggled on/off for the current input
*/
public boolean canToggleColumns() {
return fColumnPresentation != null && fColumnPresentation.isOptional();
}
protected boolean isShowColumns(String columnPresentationId) {
Boolean bool = (Boolean) fShowColumns.get(columnPresentationId);
if (bool == null) {
return true;
}
return bool.booleanValue();
}
/**
* Notification the container status of a node has changed/been computed.
*
* @param node
*/
protected void nodeContainerChanged(ModelNode node) {
Widget widget = findItem(node);
if (widget != null && !widget.isDisposed()) {
if (node.isContainer()) {
if (widget instanceof TreeItem) {
if (((TreeItem)widget).getExpanded()) {
updateChildren(node);
}
} else {
updateChildren(node);
}
attemptPendingUpdates();
}
}
}
/**
* Collects label results.
*
* @param monitor progress monitor
* @param element element to start collecting at, including all children
* @param taskName label for prorgress monitor main task
*
* @return results or <code>null</code> if cancelled
*/
public List buildLabels(IProgressMonitor monitor, Object element, String taskName) {
ModelNode[] theNodes = getModel().getNodes(element);
List results = new ArrayList();
if (theNodes != null && theNodes.length > 0) {
ModelNode root = theNodes[0];
List nodes = new ArrayList();
collectNodes(nodes, root);
monitor.beginTask(taskName, nodes.size());
Iterator iterator = nodes.iterator();
while (!monitor.isCanceled() && iterator.hasNext()) {
ModelNode node = (ModelNode) iterator.next();
IAsynchronousLabelAdapter labelAdapter = getModel().getLabelAdapter(node.getElement());
if (labelAdapter != null) {
LabelResult result = new LabelResult(node, getModel());
labelAdapter.retrieveLabel(node.getElement(), getPresentationContext(), result);
synchronized (result) {
if (!result.isDone()) {
try {
result.wait();
} catch (InterruptedException e) {
monitor.setCanceled(true);
return null;
}
}
}
IStatus status = result.getStatus();
if (status == null || status.isOK()) {
results.add(result);
}
}
monitor.worked(1);
}
}
monitor.done();
return results;
}
private void collectNodes(List nodes, ModelNode node) {
if (node.getParentNode() != null) {
nodes.add(node);
}
ModelNode[] childrenNodes = node.getChildrenNodes();
if (childrenNodes != null) {
for (int i = 0; i < childrenNodes.length; i++) {
collectNodes(nodes, childrenNodes[i]);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.AsynchronousViewer#indexOf(org.eclipse.swt.widgets.Widget, org.eclipse.swt.widgets.Widget)
*/
protected int indexOf(Widget parent, Widget child) {
if (parent instanceof Tree) {
return ((Tree)parent).indexOf((TreeItem)child);
} else {
return ((TreeItem)parent).indexOf((TreeItem)child);
}
}
}