blob: 10d404caddc92fad65c6a68937c7678161af08ee [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2009 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
* Pawel Piech (Wind River) - added support for a virtual tree model viewer (Bug 242489)
*******************************************************************************/
package org.eclipse.debug.internal.ui.viewers.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
import org.eclipse.debug.internal.core.commands.Request;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentation;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentationFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementContentProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelSelectionPolicy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.IBasicPropertyConstants;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILazyTreePathContentProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerLabel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
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.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
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.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IMemento;
/**
* A tree viewer that displays a model.
*
* @since 3.3
*/
public class InternalTreeModelViewer extends TreeViewer
implements ITreeModelViewer, ITreeModelContentProviderTarget, ITreeModelLabelProviderTarget
{
private IPresentationContext fContext;
/**
* Current column presentation or <code>null</code>
*/
private IColumnPresentation fColumnPresentation = null;
/**
* Map of columns presentation id to its visible columns id's (String[])
* When a columns presentation is not in the map, default settings are used.
*/
private Map fVisibleColumns = new HashMap();
/**
* Map of column id's to persisted sizes
*/
private Map fColumnSizes = new HashMap();
/**
* Map of column presentation id's to an array of integers representing the column order
* for that presentation, or <code>null</code> if default.
*/
private Map fColumnOrder = 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();
/**
* Item's tree path cache
*/
private static final String TREE_PATH_KEY = "TREE_PATH_KEY"; //$NON-NLS-1$
/**
* Memento type for column sizes. Sizes are keyed by column presentation id
*/
private static final String COLUMN_SIZES = "COLUMN_SIZES"; //$NON-NLS-1$
/**
* Memento type for the column order for a presentation context.
* A memento is created for each column presentation
*/
private static final String COLUMN_ORDER = "COLUMN_ORDER"; //$NON-NLS-1$
/**
* Memento type for the visible columns for a presentation context.
* A memento is created for each column 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$
/**
* True while performing an insert... we allow insert with filters
*/
private boolean fInserting = false;
/**
* Whether to notify the content provider when an element is unmapped
*/
private boolean fNotifyUnmap = true;
/**
* Flag indicating whether the viewer is a pop-up viewer. A pop-up viewer
* is transient and does not automatically expand and select elements up
* when requested by the model. It also does not dispose the presentation
* context when its control is disposed.
*
* @since 3.5
*/
private boolean fIsPopup;
/**
* 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) {
persistColumnOrder();
}
/* (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();
/**
* Proxy to cell modifier/editor support
*/
class CellModifierProxy implements ICellModifier {
private ICellModifier fModifier;
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ICellModifier#canModify(java.lang.Object, java.lang.String)
*/
public boolean canModify(Object element, String property) {
IElementEditor editor = ViewerAdapterService.getElementEditor(element);
if (editor != null) {
fModifier = editor.getCellModifier(getPresentationContext(), element);
if (fModifier != null) {
if (fModifier.canModify(element, property)) {
// install cell editor
CellEditor cellEditor = editor.getCellEditor(getPresentationContext(), property, element, (Composite)getControl());
if (cellEditor != null) {
disposeCellEditors();
CellEditor[] newEditors = new CellEditor[getVisibleColumns().length];
for (int i = 0; i < newEditors.length; i++) {
newEditors[i] = cellEditor;
}
setCellEditors(newEditors);
return true;
}
}
}
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ICellModifier#getValue(java.lang.Object, java.lang.String)
*/
public Object getValue(Object element, String property) {
if (fModifier != null) {
return fModifier.getValue(element, property);
}
return null;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ICellModifier#modify(java.lang.Object, java.lang.String, java.lang.Object)
*/
public void modify(Object element, String property, Object value) {
if (fModifier != null) {
if (element instanceof Item) {
element = ((Item)element).getData();
}
fModifier.modify(element, property, value);
}
}
/**
* Disposes client's column editor and cell editors
*/
protected void dispose() {
fModifier = null;
disposeCellEditors();
setCellEditors(null);
}
/**
* Disposes current 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();
}
}
}
}
}
private CellModifierProxy fCellModifier;
/**
* A handle to an element in a model.
*/
class VirtualElement {
/**
* Tree item associated with the element, or <code>null</code> for the root element
*/
private TreeItem fItem;
/**
* Model element (can be <code>null</code> until retrieved)
*/
private Object fElement;
/**
* Associated label update or <code>null</code> if the element was already
* present in the tree.
*/
private VirtualLabelUpdate fLabel;
/**
* Children elements or <code>null</code> if none
*/
private VirtualElement[] fChildren = null;
/**
* Whether this element would be filtered from the viewer
*/
private boolean fFiltered = false;
/**
* Listens for update when populating this virtual element in the tree viewer
*/
class UpdateListener implements IViewerUpdateListener {
/**
* The parent pending update
*/
private TreePath fParentPath;
/**
* The child index pending update
*/
private int fIndex;
/**
* Whether the update has completed
*/
private boolean fDone = false;
/**
* Constructs a new listener waiting
* @param parentPath
* @param childIndex
*/
UpdateListener(TreePath parentPath, int childIndex) {
fParentPath = parentPath;
fIndex = childIndex;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener#updateComplete(org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate)
*/
public void updateComplete(IViewerUpdate update) {
if (update instanceof IChildrenUpdate) {
IChildrenUpdate cu = (IChildrenUpdate) update;
if (cu.getElementPath().equals(fParentPath)) {
if (fIndex >= cu.getOffset() && fIndex <= (cu.getOffset() + cu.getLength())) {
fDone = true;
}
}
}
}
/**
* Returns whether the update has completed.
*
* @return whether the update has completed
*/
boolean isComplete() {
return fDone;
}
public void updateStarted(IViewerUpdate update) {
}
public void viewerUpdatesBegin() {
}
public void viewerUpdatesComplete() {
}
}
/**
* Constructs a new virtual element for the given tree item and all
* of its expanded children. Has to be called in the UI thread.
* The model is used to count unrealized elements.
*
* Note only never re-use objects already in the tree as they may be out
* of synch.
*
* @param item tree item
* @param model virtual model
* @param root subtree to consider or <code>null</code> if all
* @param indexes children to consider or <code>null</code>
*/
VirtualElement(TreeItem item, VirtualModel model, TreePath root, int[] indexes) {
fItem = item;
model.incVirtual();
if (item.getExpanded()) {
TreeItem[] items = item.getItems();
fChildren = createChildren(items, model, root, indexes);
}
}
/**
* Constructs a new virtual element for the given tree and all expanded
* children. The model is passed in to count unrealized elements.
*
* @param tree tree
* @param model virtual model
* @param root subtree scope or <code>null</code> for all
* @param indexes child indexes to consider or <code>null</code>
*/
VirtualElement(Tree tree, VirtualModel model, TreePath root, int[] indexes) {
fElement = tree.getData();
TreeItem[] items = tree.getItems();
if (items.length > 0) {
fChildren = createChildren(items, model, root, indexes);
}
}
/**
* Creates and returns children elements.
*
* @param items tree items
* @param model model
* @param root subtree to consider or all if <code>null</code>
* @param indexes children of the root to consider or <code>null</code>
* @return children
*/
private VirtualElement[] createChildren(TreeItem[] items, VirtualModel model, TreePath root, int[] indexes) {
VirtualElement[] kids = new VirtualElement[items.length];
if (root == null || root.getSegmentCount() == 0) {
if (indexes == null) {
for (int i = 0; i < items.length; i++) {
kids[i] = new VirtualElement(items[i], model, null, null);
}
} else {
for (int i = 0; i < indexes.length; i++) {
int index = indexes[i];
kids[index] = new VirtualElement(items[index], model, null, null);
}
}
} else {
for (int i = 0; i < items.length; i++) {
TreeItem treeItem = items[i];
if (treeItem.getData() != null) {
TreePath path = getTreePathFromItem(treeItem);
if (root.startsWith(path, null)) {
if (root.getSegmentCount() > path.getSegmentCount()) {
kids[i] = new VirtualElement(treeItem, model, root, indexes);
} else {
kids[i] = new VirtualElement(treeItem, model, null, indexes);
}
break;
}
}
}
}
return kids;
}
/**
* Returns whether this element would be filtered from the viewer.
*
* @return whether filtered
*/
public boolean isFiltered() {
return fFiltered;
}
/**
* Causes the tree item associated with this model element to be realized.
* Must be called in the UI thread.
*
* @return tree path to associated element, or <code>null</code> if the operation
* fails
*/
public TreePath realize() {
if (fItem.getData() != null) {
return getTreePathFromItem(fItem);
}
int index = -1;
TreePath parentPath = null;
if (fItem.getParentItem() == null) {
index = getTree().indexOf(fItem);
parentPath = TreePath.EMPTY;
} else {
index = fItem.getParentItem().indexOf(fItem);
parentPath = getTreePathFromItem(fItem.getParentItem());
}
UpdateListener listener = new UpdateListener(parentPath, index);
addViewerUpdateListener(listener);
((ILazyTreePathContentProvider)getContentProvider()).updateElement(parentPath, index);
Display display = getTree().getDisplay();
while (!listener.isComplete()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
removeViewerUpdateListener(listener);
if (fItem.getData() != null) {
return getTreePathFromItem(fItem);
}
return null;
}
/**
* Schedules updates to retrieve unrealized children of this node.
*
* @param parentPath path to this element
* @param model model
*/
void retrieveChildren(TreePath parentPath, VirtualModel model) {
VirtualChildrenUpdate update = null;
if (fChildren != null) {
int prevModelIndex = Integer.MAX_VALUE;
for (int i = 0; i < fChildren.length; i++) {
VirtualElement element = fChildren[i];
if (element == null) {
if (update != null) {
// non-consecutive updates, schedule and keep going
model.scheduleUpdate(update);
update = null;
}
} else {
int modelIndex = ((ITreeModelContentProvider)getContentProvider()).viewToModelIndex(parentPath, i);
if (update == null) {
update = new VirtualChildrenUpdate(parentPath, this, model);
} else if ((modelIndex - prevModelIndex) > 1) {
// non-consecutive updates, schedule and keep going
model.scheduleUpdate(update);
update = new VirtualChildrenUpdate(parentPath, this, model);
}
update.add(modelIndex);
prevModelIndex = modelIndex;
}
}
}
if (update != null) {
model.scheduleUpdate(update);
}
}
/**
* Sets the underlying model object.
*
* @param path path to the element
*/
void setElement(Object data) {
fElement = data;
}
/**
* Sets the label update associated with this element
*
* @param update
*/
void setLabelUpdate(VirtualLabelUpdate update) {
fLabel = update;
}
/**
* Returns the image for this element or <code>null</code> if none
*
* @return image or <code>null</code> if none
*/
public Image getImage() {
if (fLabel == null) {
return fItem.getImage();
} else {
return ((ITreeModelLabelProvider)getLabelProvider()).getImage(fLabel.fImage);
}
}
/**
* Returns the labels for the element - one for each column requested.
*
* @return column labels
*/
public String[] getLabel() {
if (fLabel == null) {
String[] visibleColumns = getVisibleColumns();
String[] label = new String[visibleColumns == null ? 1 : visibleColumns.length];
for (int i = 0; i < label.length; i++) {
label[i] = fItem.getText(i);
}
return label;
}
return fLabel.fText;
}
/**
* Returns the children of this element or <code>null</code> if none.
*
* @return children or <code>null</code> if none
*/
public VirtualElement[] getChildren() {
return fChildren;
}
}
/**
* Common function for virtual updates.
*/
class VirtualUpdate extends Request implements IViewerUpdate {
/**
* Path to the element being updated, or EMPTY for viewer input.
*/
private TreePath fPath;
/**
* Element being updated.
*/
VirtualElement fVirtualElement;
/**
* Associated virtual model
*/
VirtualModel fModel = null;
/**
* Creates a new update based for the given element and model
*/
public VirtualUpdate(VirtualModel model, VirtualElement element, TreePath elementPath) {
fPath = elementPath;
fModel = model;
fVirtualElement = element;
}
/**
* Returns the associated virtual element.
*
* @return virtual element
*/
protected VirtualElement getVirtualElement() {
return fVirtualElement;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate#getElement()
*/
public Object getElement() {
if (fPath.getSegmentCount() == 0) {
return getViewerInput();
}
return fPath.getLastSegment();
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate#getElementPath()
*/
public TreePath getElementPath() {
return fPath;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate#getPresentationContext()
*/
public IPresentationContext getPresentationContext() {
return InternalTreeModelViewer.this.getPresentationContext();
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate#getViewerInput()
*/
public Object getViewerInput() {
return InternalTreeModelViewer.this.getInput();
}
}
/**
* Represents all expanded items in this viewer's tree. The model is virtual - i.e. not
* all items have their associated model elements/labels retrieved from the model. The
* model can be populated in a non-UI thread.
*/
class VirtualModel {
/**
* Update requests in progress (children updates and label updates)
*/
private List fPendingUpdates = new ArrayList();
/**
* Whether population has been canceled.
*/
private boolean fCanceled = false;
/**
* Root element in the model.
*/
private VirtualElement fRoot = null;
/**
* Progress monitor to use during retrieval of virtual elements
*/
private IProgressMonitor fMonitor = null;
/**
* Column IDs to generate labels for or <code>null</code> if no columns.
*/
private String[] fColumnIds;
/**
* Count of the number of virtual (unrealized) elements in this model
* when it was created.
*/
private int fVirtualCount = 0;
/**
* Creates a virtual model for this tree viewer limited to the given
* subtrees. Includes the entire model if root is <code>null</code>.
*
* @param root limits model to given subtree scope, or <code>null</code>
* @param childIndexes children of the root to consider or <code>null</code>
*/
VirtualModel(TreePath root, int[] childIndexes) {
fRoot = new VirtualElement(getTree(), this, root, childIndexes);
}
/**
* Increments the counter of virtual elements in the model.
*/
void incVirtual() {
fVirtualCount++;
}
/**
* update progress monitor
*
* @param work
*/
void worked(int work) {
fMonitor.worked(work);
}
/**
* Schedules a children update.
*/
private synchronized void scheduleUpdate(IChildrenUpdate update) {
Object element = update.getElement();
if (element == null) {
element = getInput();
}
IElementContentProvider provider = ViewerAdapterService.getContentProvider(element);
if (provider != null) {
fPendingUpdates.add(update);
provider.update(new IChildrenUpdate[]{update});
}
}
/**
* Populates this models elements that have not yet been retrieved from the model,
* returning all elements in the model, or <code>null</code> if canceled.
*
* @param monitor progress monitor for progress reporting and for checking if canceled
* @param taskName used in progress monitor, for example "Find..."
* @param columnIds labels to generate or <code>null</code> if no columns are to be used
* @return model elements or <code>null</code> if canceled
*/
public VirtualElement populate(IProgressMonitor monitor, String taskName, String[] columnIds) {
fMonitor = monitor;
fColumnIds = columnIds;
monitor.beginTask(taskName, fVirtualCount * 2);
boolean done = false;
fRoot.retrieveChildren(TreePath.EMPTY, this);
synchronized (this) {
done = fPendingUpdates.isEmpty();
}
while (!done) {
synchronized (this) {
try {
wait(500);
} catch (InterruptedException e) {
}
}
if (monitor.isCanceled()) {
cancel();
return null;
}
synchronized (this) {
done = fPendingUpdates.isEmpty();
}
}
monitor.done();
return fRoot;
}
/**
* Cancels all pending updates.
*/
void cancel() {
synchronized (this) {
fCanceled = true;
Iterator iterator = fPendingUpdates.iterator();
while (iterator.hasNext()) {
IViewerUpdate update = (IViewerUpdate) iterator.next();
update.cancel();
}
fPendingUpdates.clear();
}
}
private synchronized boolean isCanceled() {
return fCanceled;
}
/**
* Notification the given children update is complete. Schedules associated label
* updates if the request or the population of the model has not been canceled.
*
* @param update completed children update request
*/
synchronized void done(VirtualChildrenUpdate update) {
if (!isCanceled()) {
fPendingUpdates.remove(update);
if (!update.isCanceled()) {
VirtualElement[] children = update.fVirtualElement.fChildren;
TreePath parent = update.getElementPath();
IElementLabelProvider provider = null;
List requests = new ArrayList();
int start = update.getOffset();
int end = start + update.getLength();
for (int i = start; i < end; i++) {
int viewIndex = ((ITreeModelContentProvider)getContentProvider()).modelToViewIndex(parent, i);
VirtualElement proxy = children[viewIndex];
if (proxy.fFiltered) {
fMonitor.worked(1); // don't need the label, this one is already done
} else {
Object element = proxy.fElement;
if (element != null) { // null indicates other updates coming later
VirtualLabelUpdate labelUpdate = new VirtualLabelUpdate(update.fModel, proxy, parent.createChildPath(element));
proxy.setLabelUpdate(labelUpdate);
IElementLabelProvider next = ViewerAdapterService.getLabelProvider(element);
if (next != null) {
fPendingUpdates.add(labelUpdate);
}
if (provider == null) {
provider = next;
requests.add(labelUpdate);
} else if (next != null) {
if (provider.equals(next)) {
requests.add(labelUpdate);
} else {
// schedule queued requests, label provider has changed
provider.update((ILabelUpdate[])requests.toArray(new ILabelUpdate[requests.size()]));
requests.clear();
requests.add(labelUpdate);
provider = next;
}
}
}
}
}
if (provider != null && !requests.isEmpty()) {
provider.update((ILabelUpdate[])requests.toArray(new ILabelUpdate[requests.size()]));
}
}
notifyAll();
}
}
/**
* Notification the given label update is complete. Updates progress reporting
* and updates pending updates.
*
* @param update label update that was completed
*/
synchronized void done(VirtualLabelUpdate update) {
if (!isCanceled()) {
fPendingUpdates.remove(update);
fMonitor.worked(1);
}
if (fPendingUpdates.isEmpty()) {
notifyAll();
}
}
}
/**
* Request to update a range of children.
*/
class VirtualChildrenUpdate extends VirtualUpdate implements IChildrenUpdate {
private int fOffset = -1;
private int fLength = 0;
/**
* @param elementPath
*/
public VirtualChildrenUpdate(TreePath parentPath, VirtualElement parent, VirtualModel model) {
super(model, parent, parentPath);
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate#getLength()
*/
public int getLength() {
return fLength;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate#getOffset()
*/
public int getOffset() {
return fOffset;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate#setChild(java.lang.Object, int)
*/
public void setChild(Object child, int offset) {
int viewIndex = ((ITreeModelContentProvider)getContentProvider()).modelToViewIndex(getElementPath(), offset);
VirtualElement virtualChild = getVirtualElement().fChildren[viewIndex];
virtualChild.setElement(child);
ITreeModelContentProvider provider = (ITreeModelContentProvider) getContentProvider();
virtualChild.fFiltered = provider.shouldFilter(getElementPath(), child);
if (!virtualChild.fFiltered) {
virtualChild.retrieveChildren(getElementPath().createChildPath(child), fModel);
}
fModel.worked(1);
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.core.commands.Request#done()
*/
public void done() {
fModel.done(this);
}
/**
* @param treeItem
* @param i
*/
void add(int i) {
if (fOffset == -1) {
fOffset = i;
}
fLength++;
}
}
class VirtualLabelUpdate extends VirtualUpdate implements ILabelUpdate {
/**
* Constructs a label request for the given element;
*
* @param elementPath
*/
public VirtualLabelUpdate(VirtualModel coordinator, VirtualElement element, TreePath elementPath) {
super(coordinator, element, elementPath);
}
private String[] fText;
private ImageDescriptor fImage;
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate#getColumnIds()
*/
public String[] getColumnIds() {
return fModel.fColumnIds;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate#setBackground(org.eclipse.swt.graphics.RGB, int)
*/
public void setBackground(RGB background, int columnIndex) {
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate#setFontData(org.eclipse.swt.graphics.FontData, int)
*/
public void setFontData(FontData fontData, int columnIndex) {
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate#setForeground(org.eclipse.swt.graphics.RGB, int)
*/
public void setForeground(RGB foreground, int columnIndex) {
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate#setImageDescriptor(org.eclipse.jface.resource.ImageDescriptor, int)
*/
public void setImageDescriptor(ImageDescriptor image, int columnIndex) {
fImage = image;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate#setLabel(java.lang.String, int)
*/
public void setLabel(String text, int columnIndex) {
if (fText == null) {
if (getColumnIds() == null) {
fText = new String[1];
} else {
fText = new String[getColumnIds().length];
}
}
fText[columnIndex] = text;
}
/* (non-Javadoc)
* @see org.eclipse.debug.internal.core.commands.Request#done()
*/
public void done() {
fModel.done(this);
}
}
/**
* @param parent
* @param style
*/
public InternalTreeModelViewer(Composite parent, int style, IPresentationContext context) {
super(parent, style);
if ((style & SWT.VIRTUAL) == 0) {
throw new IllegalArgumentException("style must include SWT.VIRTUAL"); //$NON-NLS-1$
}
setUseHashlookup(true);
fCellModifier = new CellModifierProxy();
fContext = context;
setContentProvider(createContentProvider());
setLabelProvider(createLabelProvider());
fIsPopup = (style & SWT.POP_UP) != 0;
if (fIsPopup) {
((ITreeModelContentProvider)getContentProvider()).setSuppressModelControlDeltas(true);
}
}
/**
* @return content provider for this tree viewer
*/
protected ITreeModelContentProvider createContentProvider()
{
return new TreeModelContentProvider();
}
/**
* @return label provider for this tree viewer
*/
protected ITreeModelLabelProvider createLabelProvider()
{
return new TreeModelLabelProvider(this);
}
/* (non-Javadoc)
*
* Workaround for bug 159461: when an item is cleared it's label is cleared. To avoid
* flashing, restore its label to its previous value.
*
* @see org.eclipse.jface.viewers.TreeViewer#hookControl(org.eclipse.swt.widgets.Control)
*/
protected void hookControl(Control control) {
Tree treeControl = (Tree) control;
treeControl.addListener(SWT.SetData, new Listener() {
public void handleEvent(Event event) {
// to avoid flash, reset previous label data
TreeItem item = (TreeItem) event.item;
preserveItem(item);
}
});
super.hookControl(control);
}
/**
* @param item
*/
private void preserveItem(TreeItem item) {
Object[] labels = (Object[]) item.getData(PREV_LABEL_KEY);
if (labels != null) {
for (int i = 0; i < labels.length; i++) {
if (labels[i] != null) {
item.setText(i, (String)labels[i]);
}
}
}
Object[] images = (Object[]) item.getData(PREV_IMAGE_KEY);
if (images != null) {
for (int i = 0; i < images.length; i++) {
item.setImage(i, (Image) images[i]);
}
}
Object[] fonts = (Object[]) item.getData(PREV_FONT_KEY);
if (fonts != null) {
for (int i = 0; i < fonts.length; i++) {
item.setFont(i, (Font) fonts[i]);
}
}
Object[] foregrounds = (Object[]) item.getData(PREV_FOREGROUND_KEY);
if (foregrounds != null) {
for (int i = 0; i < foregrounds.length; i++) {
item.setForeground(i, (Color) foregrounds[i]);
}
}
Object[] backgrounds = (Object[]) item.getData(PREV_BACKGROUND_KEY);
if (backgrounds != null) {
for (int i = 0; i < backgrounds.length; i++) {
item.setBackground(i, (Color) backgrounds[i]);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.StructuredViewer#handleInvalidSelection
*
* Override the default handler for invalid selection to allow model
* selection policy to select the new selection.
*/
protected void handleInvalidSelection(ISelection selection, ISelection newSelection) {
IModelSelectionPolicy selectionPolicy = ViewerAdapterService.getSelectionPolicy(selection, getPresentationContext());
if (selectionPolicy != null) {
while (!selection.equals(newSelection)) {
ISelection temp = newSelection;
selection = selectionPolicy.replaceInvalidSelection(selection, newSelection);
if (selection == null) {
selection = TreeSelection.EMPTY;
}
if (!temp.equals(selection)) {
setSelectionToWidget(selection, false);
newSelection = getSelection();
} else {
break;
}
}
}
super.handleInvalidSelection(selection, newSelection);
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ContentViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
*/
protected void handleDispose(DisposeEvent event) {
if (fColumnPresentation != null) {
fColumnPresentation.dispose();
}
fCellModifier.dispose();
if (!fIsPopup) {
fContext.dispose();
}
super.handleDispose(event);
}
/**
* Returns this viewer's presentation context.
*
* @return presentation context
*/
public IPresentationContext getPresentationContext() {
return fContext;
}
protected void unmapElement(Object element, Widget widget) {
if (fNotifyUnmap) {
// TODO: should we update the filter with the "new non-identical element"?
((ITreeModelContentProvider) getContentProvider()).unmapPath((TreePath) widget.getData(TREE_PATH_KEY));
}
super.unmapElement(element, widget);
}
protected void associate(Object element, Item item) {
// see AbstractTreeViewer.associate(...)
Object data = item.getData();
if (data != null && data != element && equals(data, element)) {
// elements are equal but not identical
// -> being removed from map, but should not change filters
try {
fNotifyUnmap = false;
super.associate(element, item);
} finally {
fNotifyUnmap = true;
}
} else {
super.associate(element, item);
}
}
/* (non-Javadoc)
*
* We need tree paths when disposed/unmapped in any order so cache the tree path.
*
* @see org.eclipse.jface.viewers.TreeViewer#mapElement(java.lang.Object, org.eclipse.swt.widgets.Widget)
*/
protected void mapElement(Object element, Widget widget) {
super.mapElement(element, widget);
if (widget instanceof Item) {
widget.setData(TREE_PATH_KEY, getTreePathFromItem((Item)widget));
} else {
widget.setData(TREE_PATH_KEY, ModelContentProvider.EMPTY_TREE_PATH);
}
}
/* (non-Javadoc)
*
* Override because we allow inserting with filters present.
*
* @see org.eclipse.jface.viewers.AbstractTreeViewer#insert(java.lang.Object, java.lang.Object, int)
*/
public void insert(Object parentElementOrTreePath, Object element, int position) {
try {
fInserting = true;
super.insert(parentElementOrTreePath, element, position);
} finally {
fInserting = false;
}
}
/* (non-Javadoc)
*
* Override because we allow inserting with filters present.
*
* @see org.eclipse.jface.viewers.StructuredViewer#hasFilters()
*/
protected boolean hasFilters() {
if (fInserting) {
return false;
}
return super.hasFilters();
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.AbstractTreeViewer#inputChanged(java.lang.Object, java.lang.Object)
*/
protected void inputChanged(Object input, Object oldInput) {
super.inputChanged(input, oldInput);
resetColumns(input);
}
/**
* 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)
IColumnPresentationFactory factory = ViewerAdapterService.getColumnPresentationFactory(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);
}
}
/**
* Toggles columns on/off for the current column presentation, if any.
*
* @param show whether to show columns if the current input supports
* columns
*/
public void setShowColumns(boolean show) {
if (show) {
if (!isShowColumns()) {
fShowColumns.remove(fColumnPresentation.getId());
}
} else {
if (isShowColumns()){
fShowColumns.put(fColumnPresentation.getId(), Boolean.FALSE);
}
}
refreshColumns();
}
/**
* 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]);
}
}
/**
* Sets the id's 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) {
if (ids != null && ids.length == 0) {
ids = null;
}
IColumnPresentation presentation = getColumnPresentation();
if (presentation != null) {
fColumnOrder.remove(presentation.getId());
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();
}
}
/**
* Refreshes the columns in the view, based on the viewer input.
*/
protected void refreshColumns() {
configureColumns();
refresh();
}
/**
* 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();
}
/**
* Creates new columns for the given presentation.
*
* TODO: does this need to be asynchronous?
*
* @param presentation
*/
protected void buildColumns(IColumnPresentation presentation) {
// dispose current columns, persisting their weights
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(((ITreeModelLabelProvider)getLabelProvider()).getImage(image));
}
column.setData(id);
}
int[] order = (int[]) fColumnOrder.get(presentation.getId());
if (order != null) {
tree.setColumnOrder(order);
}
tree.setHeaderVisible(true);
tree.setLinesVisible(true);
presentationContext.setColumns(getVisibleColumns());
setColumnProperties(getVisibleColumns());
setCellModifier(fCellModifier);
} else {
tree.setHeaderVisible(false);
tree.setLinesVisible(false);
presentationContext.setColumns(null);
setCellModifier(null);
setColumnProperties(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);
}
}
/**
* 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() {
if (isShowColumns()) {
IColumnPresentation presentation = getColumnPresentation();
if (presentation != null) {
String[] columns = (String[]) fVisibleColumns.get(presentation.getId());
if (columns == null) {
return presentation.getInitialColumns();
}
return columns;
}
}
return null;
}
/**
* 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()));
}
}
/**
* Persists column ordering
*/
protected void persistColumnOrder() {
IColumnPresentation presentation = getColumnPresentation();
if (presentation != null) {
Tree tree = getTree();
int[] order = tree.getColumnOrder();
if (order.length > 0) {
for (int i = 0; i < order.length; i++) {
if (i != order[i]) {
// non default order
fColumnOrder.put(presentation.getId(), order);
return;
}
}
}
// default order
fColumnOrder.remove(presentation.getId());
}
}
/**
* 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]);
}
}
}
if (!fColumnOrder.isEmpty()) {
Iterator iterator = fColumnOrder.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Entry) iterator.next();
String id = (String) entry.getKey();
IMemento orderMemento = memento.createChild(COLUMN_ORDER, id);
int[] order = (int[]) entry.getValue();
orderMemento.putInteger(SIZE, order.length);
for (int i = 0; i < order.length; i++) {
orderMemento.putInteger(COLUMN+Integer.toString(i), order[i]);
}
}
}
// save presentation context properties
IPresentationContext context = getPresentationContext();
if (context instanceof PresentationContext) {
PresentationContext pc = (PresentationContext) context;
pc.saveProperites(memento);
}
}
/**
* Initializes viewer 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);
}
}
mementos = memento.getChildren(COLUMN_ORDER);
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();
int[] order = new int[length];
for (int j = 0; j < length; j++) {
order[j] = child.getInteger(COLUMN+Integer.toString(j)).intValue();
}
fColumnOrder.put(id, order);
}
}
// restore presentation context properties
// save presentation context properties
IPresentationContext context = getPresentationContext();
if (context instanceof PresentationContext) {
PresentationContext pc = (PresentationContext) context;
pc.initProperties(memento);
}
}
/**
* Returns whether the candidate selection should override the current
* selection.
*
* @param current
* @param curr
* @return
*/
public boolean overrideSelection(ISelection current, ISelection candidate) {
IModelSelectionPolicy selectionPolicy = ViewerAdapterService.getSelectionPolicy(current, getPresentationContext());
if (selectionPolicy == null) {
return true;
}
if (selectionPolicy.contains(candidate, getPresentationContext())) {
return selectionPolicy.overrides(current, candidate, getPresentationContext());
}
return !selectionPolicy.isSticky(current, getPresentationContext());
}
/* (non-Javadoc)
*
* Consider selection policy
*
* @see org.eclipse.jface.viewers.StructuredViewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
*/
public void setSelection(ISelection selection, boolean reveal) {
if (!overrideSelection(getSelection(), selection)) {
return;
}
super.setSelection(selection, reveal);
}
/**
* Sets the selection in the viewer to the specified selection.
*
* @param selection the selection
* @param reveal whether to reveal the selection
* @param force whether to force the selection (i.e. <code>true</code> to
* override the model selection policy)
*/
public void setSelection(ISelection selection, boolean reveal, boolean force) {
if (force) {
super.setSelection(selection, reveal);
} else {
setSelection(selection, reveal);
}
}
/**
* Registers the specified listener for view update notifications.
*
* @param listener listener
*/
public void addViewerUpdateListener(IViewerUpdateListener listener) {
((ITreeModelContentProvider)getContentProvider()).addViewerUpdateListener(listener);
}
/**
* Removes the specified listener from update notifications.
*
* @param listener listener
*/
public void removeViewerUpdateListener(IViewerUpdateListener listener) {
ITreeModelContentProvider cp = (ITreeModelContentProvider)getContentProvider();
if (cp != null) {
cp.removeViewerUpdateListener(listener);
}
}
/**
* Registers the given listener for model delta notification.
*
* @param listener model delta listener
*/
public void addModelChangedListener(IModelChangedListener listener) {
((ITreeModelContentProvider)getContentProvider()).addModelChangedListener(listener);
}
/**
* Unregisters the given listener from model delta notification.
*
* @param listener model delta listener
*/
public void removeModelChangedListener(IModelChangedListener listener) {
ITreeModelContentProvider cp = (ITreeModelContentProvider)getContentProvider();
if (cp != null) {
cp.removeModelChangedListener(listener);
}
}
/*
* (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;
}
if ( !((ITreeModelLabelProvider)getLabelProvider()).update(getTreePathFromItem(item)) ) {
if (element instanceof String) {
item.setData(PREV_LABEL_KEY, new String[] { (String)element } );
}
}
// As it is possible for user code to run the event
// loop check here.
if (item.isDisposed()) {
unmapElement(element, item);
}
}
/**
* Collects all expanded items from this tree viewer and returns them as part of
* a virtual model. This must be called in a UI thread to traverse the tree items.
* The model can the be populated in a non-UI thread.
*
* Alternatively a root element can be specified with a set of child indexes to
* consider. All children of the specified children are added to the model.
*
* @param root subtree to consider or <code>null</code> if all
* @param childIndexes indexes of root element to consider, or <code>null</code> if all
* @return virtual model
*/
VirtualModel buildVirtualModel(TreePath root, int[] childIndexes) {
return new VirtualModel(root, childIndexes);
}
public void addLabelUpdateListener(ILabelUpdateListener listener) {
((ITreeModelLabelProvider)getLabelProvider()).addLabelUpdateListener(listener);
}
public void removeLabelUpdateListener(ILabelUpdateListener listener) {
if (!getControl().isDisposed()) {
((ITreeModelLabelProvider)getLabelProvider()).removeLabelUpdateListener(listener);
}
}
/**
* Returns the item for the element at the given tree path or <code>null</code>
* if none.
*
* @param path tree path
* @return item or <code>null</code>
*/
public Widget findItem(TreePath path) {
if (path.getSegmentCount() == 0) {
return getTree();
}
Widget[] items = super.findItems(path.getLastSegment());
if (items.length == 1) {
return items[0];
}
for (int i = 0; i < items.length; i++) {
if (getTreePathFromItem((Item)items[i]).equals(path)) {
return items[i];
}
}
return null;
}
public Item[] getChildren(Widget widget) {
return super.getChildren(widget);
}
/**
* Returns the tree path for the given item.
* @param item
* @return {@link TreePath}
*/
protected TreePath getTreePathFromItem(Item item) {
return super.getTreePathFromItem(item);
}
//**************************************************************************
// These methods were copied from TreeViewer as a workaround for bug 183463:
// Expanded nodes in tree viewer flash on refresh
/*
* (non-Javadoc)
*
* workaround for bug 183463
*
* @see org.eclipse.jface.viewers.AbstractTreeViewer#internalRefreshStruct(org.eclipse.swt.widgets.Widget,
* java.lang.Object, boolean)
*/
protected void internalRefreshStruct(Widget widget, Object element,
boolean updateLabels) {
// 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);
}
/**
* Traverses the visible (expanded) part of the tree and updates child
* counts.
* <p>
* workaround for bug 183463
* </p>
* @param parent the parent of the widget, or <code>null</code> if the widget is the tree
* @param widget
* @param element
* @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
preserveItem((TreeItem)widget);
//((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);
}
}
/**
* workaround for bug 183463
*
* Update the child count
* @param widget
* @param currentChildCount
*/
private void virtualLazyUpdateChildCount(Widget widget, int currentChildCount) {
TreePath treePath;
if (widget instanceof Item) {
treePath = getTreePathFromItem((Item) widget);
} else {
treePath = TreePath.EMPTY;
}
((ILazyTreePathContentProvider) getContentProvider())
.updateChildCount(treePath, currentChildCount);
}
/**
* Update the widget at index.
* <p>
* workaround for bug 183463
* </p>
* @param widget
* @param index
*/
private void virtualLazyUpdateWidget(Widget widget, int index) {
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);
}
/**
* Performs auto expand on an element at the specified path if the auto expand
* level dictates the element should be expanded.
*
* @param elementPath tree path to element to consider for expansion
*/
public void autoExpand(TreePath elementPath) {
int level = getAutoExpandLevel();
if (level > 0 || level == ITreeModelViewer.ALL_LEVELS) {
if (level == ITreeModelViewer.ALL_LEVELS || level >= elementPath.getSegmentCount()) {
expandToLevel(elementPath, 1);
}
}
}
public int findElementIndex(TreePath parentPath, Object element) {
Widget parentItem = findItem(parentPath);
if (parentItem != null) {
Item[] children = getChildren(parentItem);
for (int i = 0; i < children.length; i++) {
Item item = children[i];
Object data = item.getData();
if ( (element != null && element.equals(data)) || (element == null && data == null) ) {
return i;
}
}
}
return -1;
}
public Display getDisplay() {
Control control = getControl();
if (control != null) {
return control.getDisplay();
}
return null;
}
protected static final String[] STATE_PROPERTIES = new String[]{ IBasicPropertyConstants.P_TEXT, IBasicPropertyConstants.P_IMAGE };
public void update(Object element) {
update(element, STATE_PROPERTIES);
}
/**
* Label data cache keys
* TODO: workaround for bug 159461
*/
static String PREV_LABEL_KEY = "PREV_LABEL_KEY"; //$NON-NLS-1$
static String PREV_IMAGE_KEY = "PREV_IMAGE_KEY"; //$NON-NLS-1$
static String PREV_FONT_KEY = "PREV_FONT_KEY"; //$NON-NLS-1$
static String PREV_FOREGROUND_KEY = "PREV_FOREGROUND_KEY"; //$NON-NLS-1$
static String PREV_BACKGROUND_KEY = "PREV_BACKGROUND_KEY"; //$NON-NLS-1$
public void setElementData(TreePath path, int numColumns, String[] labels, ImageDescriptor[] imageDescriptors,
FontData[] fontDatas, RGB[] _foregrounds, RGB[] _backgrounds)
{
Widget widget = findItem(path);
String[] columnIds = getVisibleColumns();
if (widget != null && widget instanceof TreeItem && !widget.isDisposed()) {
TreeItem item = (TreeItem)widget;
/*Object data = item.getData();
int itemCount = item.getItemCount();
item.clearAll(false);
item.setData(data);
item.setItemCount(itemCount);*/
for (int i=0; i<numColumns; i++){
// text might be null if the launch has been terminated
item.setText(i,(labels[i] == null ? IInternalDebugCoreConstants.EMPTY_STRING : labels[i]));
}
item.setData(PREV_LABEL_KEY, labels);
if (imageDescriptors == null) {
for (int i=0; i<numColumns; i++){
item.setImage(i,null);
}
item.setData(PREV_IMAGE_KEY, null);
} else {
Image[] images = new Image[imageDescriptors.length];
for (int i = 0; i < imageDescriptors.length; i++) {
images[i] = ((ITreeModelLabelProvider)getLabelProvider()).getImage(imageDescriptors[i]);
}
if (columnIds == null) {
item.setImage(images[0]);
} else {
item.setImage(images);
}
item.setData(PREV_IMAGE_KEY, images);
}
if (_foregrounds == null) {
for (int i=0; i<numColumns; i++){
item.setForeground(i,null);
}
item.setData(PREV_FOREGROUND_KEY, null);
} else {
Color[] foregrounds = new Color[_foregrounds.length];
for (int i = 0; i< foregrounds.length; i++) {
foregrounds[i] = ((ITreeModelLabelProvider)getLabelProvider()).getColor(_foregrounds[i]);
}
if (columnIds == null) {
item.setForeground(0,foregrounds[0]);
} else {
for (int i = 0; i< foregrounds.length; i++) {
item.setForeground(i, foregrounds[i]);
}
}
item.setData(PREV_FOREGROUND_KEY, foregrounds);
}
if (_backgrounds == null) {
for (int i=0; i<numColumns; i++){
item.setBackground(i,null);
}
item.setData(PREV_BACKGROUND_KEY, null);
} else {
Color[] backgrounds = new Color[_backgrounds.length];
for (int i = 0; i< backgrounds.length; i++) {
backgrounds[i] = ((ITreeModelLabelProvider)getLabelProvider()).getColor(_backgrounds[i]);
}
if (columnIds == null) {
item.setBackground(0,backgrounds[0]);
} else {
for (int i = 0; i< backgrounds.length; i++) {
item.setBackground(i, backgrounds[i]);
}
}
item.setData(PREV_BACKGROUND_KEY, backgrounds);
}
if (fontDatas == null) {
for (int i=0; i<numColumns; i++){
item.setFont(i,null);
}
item.setData(PREV_FONT_KEY, null);
} else {
Font[] fonts = new Font[fontDatas.length];
for (int i = 0; i < fontDatas.length; i++) {
fonts[i] = ((ITreeModelLabelProvider)getLabelProvider()).getFont(fontDatas[i]);
}
if (columnIds == null) {
item.setFont(0,fonts[0]);
} else {
for (int i = 0; i < fonts.length; i++) {
item.setFont(i, fonts[i]);
}
}
item.setData(PREV_FONT_KEY, fonts);
}
}
}
public ViewerLabel getElementLabel(TreePath path, String columnId) {
if (path.getSegmentCount() == 0) {
return null;
}
int columnIdx = -1;
String[] visibleColumns = getVisibleColumns();
if (columnId != null && visibleColumns != null) {
int i = 0;
for (i = 0; i < visibleColumns.length; i++) {
if (columnId.equals(getVisibleColumns()[i])) {
columnIdx = i;
break;
}
}
if (i == visibleColumns.length) {
return null;
}
} else {
columnIdx = 0;
}
TreeItem item = (TreeItem)findItem(path);
if (item != null) {
ViewerLabel label = new ViewerLabel(item.getText(columnIdx), item.getImage(columnIdx));
label.setFont(item.getFont(columnIdx));
label.setBackground(item.getBackground(columnIdx));
label.setForeground(item.getForeground(columnIdx));
return label;
}
return null;
}
public void reveal(TreePath path, int index) {
Widget item = findItem(path);
TreeItem[] children = null;
if (item instanceof TreeItem) {
children = ((TreeItem)item).getItems();
} else if (item instanceof Tree) {
children = ((Tree)item).getItems();
}
if (children != null && index < children.length) {
getTree().setTopItem(children[index]);
}
}
public int getChildCount(TreePath path) {
if (path.getSegmentCount() == 0) {
return ((Tree)getControl()).getItemCount();
} else {
Widget[] items = internalFindItems(path);
if (items.length > 0) {
if (items[0] instanceof TreeItem) {
return ((TreeItem)items[0]).getItemCount();
}
}
}
return -1;
}
public Object getChildElement(TreePath path, int index) {
TreeItem childItem = null;
if (path.getSegmentCount() == 0) {
Tree tree = (Tree)getControl();
try {
childItem = tree.getItem(index);
} catch (IllegalArgumentException e) {}
} else {
try {
Widget[] items = internalFindItems(path);
if (items.length > 0) {
if (items[0] instanceof TreeItem) {
childItem = ((TreeItem)items[0]).getItem(index);
}
}
} catch (IllegalArgumentException e) {}
}
if (childItem != null) {
return childItem.getData();
}
return null;
}
public TreePath getTopElementPath() {
TreeItem topItem = ((Tree)getControl()).getTopItem();
if (topItem != null && topItem.getData() != null) {
return getTreePathFromItem(topItem);
}
return null;
}
public void saveElementState(TreePath path, ModelDelta delta) {
Tree tree = (Tree) getControl();
TreeItem[] selection = tree.getSelection();
Set set = new HashSet();
for (int i = 0; i < selection.length; i++) {
set.add(selection[i]);
}
TreeItem[] items = null;
Widget w = internalGetWidgetToSelect(path);
if (w instanceof Tree) {
delta.setChildCount(
((ITreeModelContentProvider)getContentProvider()).viewToModelCount(path, tree.getItemCount()));
delta.setFlags(delta.getFlags() | IModelDelta.EXPAND);
items = tree.getItems();
} else if (w instanceof TreeItem) {
TreeItem item = (TreeItem)w;
delta.setChildCount(
((ITreeModelContentProvider)getContentProvider()).viewToModelCount(path, item.getItemCount()));
if (item.getExpanded()) {
delta.setFlags(delta.getFlags() | IModelDelta.EXPAND);
}
if (set.contains(item)) {
delta.setFlags(delta.getFlags() | IModelDelta.SELECT);
}
items = ((TreeItem)w).getItems();
}
if (items != null) {
for (int i = 0; i < items.length; i++) {
doSaveElementState(path, delta, items[i], set, i);
}
}
}
private void doSaveElementState(TreePath parentPath, ModelDelta delta, TreeItem item, Collection set, int index) {
Object element = item.getData();
if (element != null) {
boolean expanded = item.getExpanded();
boolean selected = set.contains(item);
if (expanded || selected) {
int flags = IModelDelta.NO_CHANGE;
if (expanded) {
flags = flags | IModelDelta.EXPAND;
}
if (selected) {
flags = flags | IModelDelta.SELECT;
}
int modelIndex = ((ITreeModelContentProvider)getContentProvider()).viewToModelIndex(parentPath, index);
TreePath elementPath = parentPath.createChildPath(element);
int numChildren = ((ITreeModelContentProvider)getContentProvider()).
viewToModelCount(elementPath, item.getItemCount());
ModelDelta childDelta = delta.addNode(element, modelIndex, flags, numChildren);
if (expanded) {
TreeItem[] items = item.getItems();
for (int i = 0; i < items.length; i++) {
doSaveElementState(elementPath, childDelta, items[i], set, i);
}
}
}
}
}
public void updateViewer(IModelDelta delta) {
((ITreeModelContentProvider)getContentProvider()).updateModel(delta);
}
}