blob: b287456c9e583e4aabee0463ebe2c8b4f48d4200 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2013 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.debug.internal.ui.viewers;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
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.model.provisional.IModelChangedListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelSelectionPolicy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelSelectionPolicyFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IStatusMonitor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
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.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* A viewer that retrieves labels and content asynchronously via adapters and supports
* duplicate elements in the viewer. Retrieving content and labels asynchronously allows
* for arbitrary latency without blocking the UI thread.
* <p>
* This viewer uses adapters to retrieve labels and content rather than
* a label provider and content provider. As such, the label provider for this viewer
* is <code>null</code> by default. The content provider returned by this viewer is
* non-<code>null</code> to conform to the viewer specification, but performs no
* useful function.
* </p>
* <p>
* The selection in this viewer is also set asynchronously. When the selection is set,
* the viewer attempts to perform the selection. If the elements in the specified selection
* are not yet in the viewer, the portion of the selection that could not be honored
* becomes a pending selection. As more elements are added to viewer, the pending selection
* is attempted to be set.
* </p>
* @since 3.2
*/
public abstract class AsynchronousViewer extends StructuredViewer implements Listener {
/**
* Model of elements for this viewer
*/
private AsynchronousModel fModel;
/**
* Cache of images used for elements in this tree viewer. Label updates
* use the method <code>getImage(...)</code> to cache images for
* image descriptors. The images are disposed when this viewer is disposed.
*/
private Map<ImageDescriptor, Image> fImageCache = new HashMap<>();
/**
* Cache of the fonts used for elements in this tree viewer. Label updates
* use the method <code>getFont(...)</code> to cache fonts for
* FontData objects. The fonts are disposed with the viewer.
*/
private Map<FontData, Font> fFontCache = new HashMap<>();
/**
* Cache of the colors used for elements in this tree viewer. Label updates
* use the method <code>getColor(...)</code> to cache colors for
* RGB values. The colors are disposed with the viewer.
*/
private Map<RGB, Color> fColorCache = new HashMap<>();
/**
* The context in which this viewer is being used - i.e. what part it is contained
* in any any preference settings associated with it.
*/
private IPresentationContext fContext;
private ISelection fPendingSelection;
private ISelection fCurrentSelection;
/**
* Array used to store indices of the path to an item in the viewer being mapped
* by a 'set data' callback. Indices are bottom up. For example when 'set data' for
* the 3rd child of the 4th child of the 2nd root element were being asked for,
* the first 3 indices would look like: [3, 4, 2, ....]. We re-use an array to avoid
* creating a new one all the time. The array grows as needed to accommodate deep
* elements.
*/
private int[] fSetDataIndicies = new int[5];
/**
* The update policy for this viewer.
*/
private AbstractUpdatePolicy fUpdatePolicy;
protected static final String OLD_LABEL = "old_label"; //$NON-NLS-1$
protected static final String OLD_IMAGE = "old_image"; //$NON-NLS-1$
/**
* Creates a new viewer
*/
protected AsynchronousViewer() {
setContentProvider(new NullContentProvider());
setUseHashlookup(true);
}
/**
* Hash lookup is required, don't let subclasses change behavior.
* @param enable if hash lookup should be used in the viewer
*/
@Override
public final void setUseHashlookup(boolean enable) {
Assert.isTrue(enable);
super.setUseHashlookup(enable);
}
@Override
protected void hookControl(Control control) {
super.hookControl(control);
control.addListener(SWT.SetData, this);
}
/**
* Clients must call this methods when this viewer is no longer needed
* so it can perform cleanup.
*/
public synchronized void dispose() {
for (Image image : fImageCache.values()) {
image.dispose();
}
fImageCache.clear();
for (Font font : fFontCache.values()) {
font.dispose();
}
fFontCache.clear();
for (Color color : fColorCache.values()) {
color.dispose();
}
fColorCache.clear();
if (fModel != null) {
fModel.dispose();
}
if (fUpdatePolicy != null) {
fUpdatePolicy.dispose();
}
if (fContext != null) {
((PresentationContext)fContext).dispose();
}
}
/**
* Updates all occurrences of the given element in this viewer.
*
* @param element element to update
*/
public void update(Object element) {
ModelNode[] nodes = getModel().getNodes(element);
if (nodes != null) {
for (ModelNode node : nodes) {
updateLabel(node);
}
}
}
/**
* Updates the label for a specific element (node) in the model.
*
* @param node node to update
*/
protected void updateLabel(ModelNode node) {
// the input is not displayed
if (!node.getElement().equals(getInput())) {
getModel().updateLabel(node);
}
}
/**
* Returns the presentation context to be used in update requests.
* Clients may override this method if required to provide special
* implementations of contexts.
*
* @return presentation context
*/
public IPresentationContext getPresentationContext() {
return fContext;
}
@Override
protected synchronized void unmapAllElements() {
super.unmapAllElements();
AsynchronousModel model = getModel();
if (model != null) {
model.dispose();
}
}
@Override
protected synchronized void inputChanged(Object input, Object oldInput) {
fPendingSelection = null;
if (fCurrentSelection != null) {
updateSelection(new StructuredSelection());
fCurrentSelection = null;
}
if (fUpdatePolicy == null) {
fUpdatePolicy = createUpdatePolicy();
fUpdatePolicy.init(this);
}
if (fModel != null) {
fModel.dispose();
}
fModel = createModel();
fModel.init(input);
if (input != null) {
mapElement(fModel.getRootNode(), getControl());
getControl().setData(fModel.getRootNode().getElement());
} else {
unmapAllElements();
getControl().setData(null);
}
refresh();
}
/**
* Creates a new empty model for this viewer that
* is *not* initialized.
*
* @return a new model
*/
protected abstract AsynchronousModel createModel();
/**
* Creates and returns this viewers update policy.
* @return update policy
*/
public abstract AbstractUpdatePolicy createUpdatePolicy();
Image[] getImages(ImageDescriptor[] descriptors) {
if (descriptors == null || descriptors.length == 0) {
String[] columns = getPresentationContext().getColumns();
if (columns == null) {
return new Image[1];
} else {
return new Image[columns.length];
}
}
Image[] images = new Image[descriptors.length];
for (int i = 0; i < images.length; i++) {
images[i] = getImage(descriptors[i]);
}
return images;
}
/**
* Returns an image for the given image descriptor or <code>null</code>. Adds the image
* to a cache of images if it does not already exist. The cache is cleared when this viewer
* is disposed.
*
* @param descriptor image descriptor or <code>null</code>
* @return image or <code>null</code>
*/
protected Image getImage(ImageDescriptor descriptor) {
if (descriptor == null) {
return null;
}
Image image = fImageCache.get(descriptor);
if (image == null) {
image = new Image(getControl().getDisplay(), descriptor.getImageData());
fImageCache.put(descriptor, image);
}
return image;
}
protected Font[] getFonts(FontData[] fontDatas) {
if (fontDatas == null || fontDatas.length == 0) {
String[] columns = getPresentationContext().getColumns();
if (columns == null) {
return new Font[1];
} else {
return new Font[columns.length];
}
}
Font[] fonts = new Font[fontDatas.length];
for (int i = 0; i < fonts.length; i++) {
fonts[i] = getFont(fontDatas[i]);
}
return fonts;
}
/**
* Returns a font for the given font data or <code>null</code>. Adds the font to this viewer's font
* cache which is disposed when this viewer is disposed.
*
* @param fontData font data or <code>null</code>
* @return font font or <code>null</code>
*/
protected Font getFont(FontData fontData) {
if (fontData == null) {
return null;
}
Font font = fFontCache.get(fontData);
if (font == null) {
font = new Font(getControl().getDisplay(), fontData);
fFontCache.put(fontData, font);
}
return font;
}
protected Color[] getColors(RGB[] rgb) {
if (rgb == null || rgb.length == 0) {
String[] columns = getPresentationContext().getColumns();
if (columns == null) {
return new Color[1];
} else {
return new Color[columns.length];
}
}
Color[] colors = new Color[rgb.length];
for (int i = 0; i < colors.length; i++) {
colors[i] = getColor(rgb[i]);
}
return colors;
}
/**
* Returns a color for the given RGB or <code>null</code>. Adds the color to this viewer's color
* cache which is disposed when this viewer is disposed.
*
* @param rgb RGB or <code>null</code>
* @return color or <code>null</code>
*/
protected Color getColor(RGB rgb) {
if (rgb == null) {
return null;
}
Color color = fColorCache.get(rgb);
if (color == null) {
color = new Color(getControl().getDisplay(), rgb);
fColorCache.put(rgb, color);
}
return color;
}
/**
* Sets the context for this viewer.
*
* @param context the presentation context
*/
public void setContext(IPresentationContext context) {
fContext = context;
}
@Override
protected Widget doFindItem(Object element) {
// this viewer maps model nodes to widgets, so the element is a ModelNode
AsynchronousModel model = getModel();
if (model != null) {
if (element.equals(model.getRootNode())) {
return doFindInputItem(element);
}
Widget[] widgets = findItems(element);
if (widgets.length > 0) {
return widgets[0];
}
}
return null;
}
@Override
protected Widget doFindInputItem(Object element) {
if (element instanceof ModelNode) {
ModelNode node = (ModelNode) element;
if (node.getElement().equals(getInput())) {
return getControl();
}
}
return null;
}
@Override
protected void doUpdateItem(Widget item, Object element, boolean fullMap) {
}
@Override
protected void internalRefresh(Object element) {
// get the nodes in the model
AsynchronousModel model = getModel();
if (model != null) {
ModelNode[] nodes = model.getNodes(element);
if (nodes != null) {
for (ModelNode node : nodes) {
// get the widget for the node
Widget item = findItem(node);
if (item != null) {
internalRefresh(node);
}
}
}
}
}
/**
* Refreshes a specific occurrence of an element (a node).
*
* @param node node to update
*
* Subclasses should override and call super
*/
protected void internalRefresh(ModelNode node) {
updateLabel(node);
}
@Override
public synchronized void setSelection(ISelection selection, boolean reveal) {
setSelection(selection, reveal, false);
}
/**
* Sets the selection in this viewer.
*
* @param selection new selection
* @param reveal whether to reveal the selection
* @param force whether to force the selection change without consulting the model
* selection policy
*/
public synchronized void setSelection(ISelection selection, final boolean reveal, boolean force) {
Control control = getControl();
if (control == null || control.isDisposed()) {
return;
}
if (!acceptsSelection(selection)) {
selection = getEmptySelection();
}
if (!force && !overrideSelection(fCurrentSelection, selection)) {
return;
}
fPendingSelection = selection;
if (getControl().getDisplay().getThread() == Thread.currentThread()) {
attemptSelection(reveal);
} else {
WorkbenchJob job = new WorkbenchJob("attemptSelection") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
attemptSelection(reveal);
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
}
/**
* Returns whether the candidate selection should override the current
* selection.
*
* @param current the current selection
* @param candidate the new selection
* @return if the selection should be overridden
*/
protected boolean overrideSelection(ISelection current, ISelection candidate) {
IModelSelectionPolicy selectionPolicy = getSelectionPolicy(current);
if (selectionPolicy == null) {
return true;
}
if (selectionPolicy.contains(candidate, getPresentationContext())) {
return selectionPolicy.overrides(current, candidate, getPresentationContext());
}
return !selectionPolicy.isSticky(current, getPresentationContext());
}
@Override
public ISelection getSelection() {
Control control = getControl();
if (control == null || control.isDisposed() || fCurrentSelection == null) {
return StructuredSelection.EMPTY;
}
return fCurrentSelection;
}
@Override
protected void handleSelect(SelectionEvent event) {
// handle case where an earlier selection listener disposed the control.
Control control = getControl();
if (control != null && !control.isDisposed()) {
updateSelection(newSelectionFromWidget());
}
}
@Override
protected void handlePostSelect(SelectionEvent e) {
SelectionChangedEvent event = new SelectionChangedEvent(this, newSelectionFromWidget());
firePostSelectionChanged(event);
}
/**
* Creates and returns a new selection from this viewer, based on the selected
* elements in the widget.
*
* @return a new selection
*/
protected abstract ISelection newSelectionFromWidget();
/**
* Returns the selection policy associated with the given selection
* or <code>null</code> if none.
*
* @param selection or <code>null</code>
* @return selection policy or <code>null</code>
*/
protected IModelSelectionPolicy getSelectionPolicy(ISelection selection) {
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
Object element = ss.getFirstElement();
if (element instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) element;
IModelSelectionPolicyFactory factory = adaptable.getAdapter(IModelSelectionPolicyFactory.class);
if (factory != null) {
return factory.createModelSelectionPolicyAdapter(adaptable, getPresentationContext());
}
}
}
return null;
}
@Override
final protected void setSelectionToWidget(ISelection selection, final boolean reveal) {
// NOT USED
throw new IllegalArgumentException("This method should not be called"); //$NON-NLS-1$
}
@Override
final protected void setSelectionToWidget(List l, boolean reveal) {
// NOT USED
throw new IllegalArgumentException("This method should not be called"); //$NON-NLS-1$
}
/**
* Attempts to update any pending selection.
*
* @param reveal whether to reveal the selection
*/
protected void attemptSelection(boolean reveal) {
ISelection currentSelection = null;
synchronized (this) {
if (fPendingSelection != null) {
ISelection remaining = doAttemptSelectionToWidget(fPendingSelection, reveal);
if (remaining.isEmpty()) {
remaining = null;
}
if (!fPendingSelection.equals(remaining)) {
fPendingSelection = remaining;
currentSelection = newSelectionFromWidget();
if (isSuppressEqualSelections() && currentSelection.equals(fCurrentSelection)) {
return;
}
}
}
}
if (currentSelection != null) {
updateSelection(currentSelection);
firePostSelectionChanged(new SelectionChangedEvent(this, currentSelection));
}
}
/**
* Controls whether selection change notification is sent even when
* successive selections are equal.
*
* TODO: what we really want is to fire selection change on ACTIVATE model
* change, even when selection is the same.
*
* @return whether to suppress change notification for equal successive
* selections
*/
protected boolean isSuppressEqualSelections() {
return true;
}
/**
* Attempts to selection the specified selection and returns a selection
* representing the portion of the selection that could not be honored
* and still needs to be selected.
*
* @param selection selection to attempt
* @param reveal whether to reveal the selection
* @return remaining selection
*/
protected abstract ISelection doAttemptSelectionToWidget(ISelection selection, boolean reveal);
/**
* Returns whether this viewer supports the given selection.
*
* @param selection a selection
* @return whether this viewer supports the given selection
*/
protected abstract boolean acceptsSelection(ISelection selection);
/**
* Returns an empty selection supported by this viewer.
*
* @return an empty selection supported by this viewer
*/
protected abstract ISelection getEmptySelection();
/**
* A content provider that does nothing.
*/
private static class NullContentProvider implements IStructuredContentProvider {
@Override
public void dispose() {
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
@Override
public Object[] getElements(Object inputElement) {
return null;
}
}
/**
* Notification that a presentation update has failed.
* Subclasses may override as required. The default implementation
* does nothing.
*
* @param monitor monitor for the presentation request that failed
* @param status status of update
*/
protected void handlePresentationFailure(IStatusMonitor monitor, IStatus status) {
}
@Override
protected synchronized void preservingSelection(Runnable updateCode) {
if (fPendingSelection == null || fPendingSelection.isEmpty()) {
ISelection oldSelection = null;
try {
// preserve selection
oldSelection = fCurrentSelection;
// perform the update
updateCode.run();
} finally {
// restore selection
if (oldSelection == null) {
oldSelection = new StructuredSelection();
}
if (getControl().getDisplay().getThread() == Thread.currentThread()) {
if (!oldSelection.equals(newSelectionFromWidget())) {
restoreSelection(oldSelection);
}
} else {
WorkbenchJob job = new WorkbenchJob("attemptSelection") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
synchronized (AsynchronousViewer.this) {
if (!getControl().isDisposed()) {
if (fPendingSelection == null || fPendingSelection.isEmpty()) {
ISelection tempSelection = fCurrentSelection;
if (tempSelection == null) {
tempSelection = new StructuredSelection();
}
if (!tempSelection.equals(newSelectionFromWidget())) {
restoreSelection(tempSelection);
}
}
}
}
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
}
} else {
updateCode.run();
}
}
protected synchronized void restoreSelection(ISelection oldSelection) {
ISelection remaining = doAttemptSelectionToWidget(oldSelection, false);
// send out notification if old and new differ
fCurrentSelection = newSelectionFromWidget();
if (!selectionExists(fCurrentSelection)) {
if (selectionExists(oldSelection)) {
// old selection exists in the model, but not widget
fCurrentSelection = oldSelection;
} else {
fCurrentSelection = getEmptySelection();
}
}
if (!fCurrentSelection.equals(oldSelection)) {
handleInvalidSelection(oldSelection, fCurrentSelection);
// if the remaining selection still exists in the model, make it pending
if (selectionExists(remaining)) {
setSelection(remaining);
}
}
}
/**
* Returns whether the selection exists in the model
* @param selection the selection context
* @return <code>true</code> if the selecton exists in the model <code>false</code> otherwise
*/
protected boolean selectionExists(ISelection selection) {
if (selection.isEmpty()) {
return false;
}
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
Iterator<?> iterator = ss.iterator();
while (iterator.hasNext()) {
Object element = iterator.next();
if (getModel().getNodes(element) == null) {
return false;
}
}
}
return true;
}
/**
* Sets the color attributes of the given widget.
*
* @param widget the widget to update
* @param foreground foreground color of the widget or <code>null</code> if default
* @param background background color of the widget or <code>null</code> if default
*/
protected abstract void setColors(Widget widget, RGB foreground[], RGB background[]);
/**
* Sets the label attributes of the given widget.
*
* @param widget the widget to update
* @param text label text
* @param image label image or <code>null</code>
*/
protected abstract void setLabels(Widget widget, String[] text, ImageDescriptor[] image);
/**
* Sets the font attributes of the given widget.
*
* @param widget widget to update
* @param font font of the widget or <code>null</code> if default.
*/
protected abstract void setFonts(Widget widget, FontData[] font);
@Override
protected synchronized void updateSelection(ISelection selection) {
fCurrentSelection = selection;
super.updateSelection(selection);
}
/**
* Notification the given model proxy has been added to this viewer's model.
*
* @param proxy the model proxy that has been added
*/
protected void modelProxyAdded(IModelProxy proxy) {
if (fUpdatePolicy instanceof IModelChangedListener) {
proxy.addModelChangedListener((IModelChangedListener)fUpdatePolicy);
}
}
/**
* Notification the given model proxy has been removed from this viewer's model.
*
* @param proxy the model proxy that has been removed
*/
protected void modelProxyRemoved(IModelProxy proxy) {
if (fUpdatePolicy instanceof IModelChangedListener) {
proxy.removeModelChangedListener((IModelChangedListener)fUpdatePolicy);
}
}
/**
* Returns this viewer's model
*
* @return model
*/
protected AsynchronousModel getModel() {
return fModel;
}
/**
* A node in the model has been updated
*
* @param node the model node that has been changed
*/
protected void nodeChanged(ModelNode node) {
Widget widget = findItem(node);
if (widget != null) {
clear(widget);
attemptPendingUpdates();
}
}
/**
* @return if there are any more pending updates in the viewer
*/
public synchronized boolean hasPendingUpdates() {
return getModel().hasPendingUpdates();
}
/**
* Notification from the model that the update for the given request
* has completed.
*
* @param monitor the monitor
*/
protected void updateComplete(IStatusMonitor monitor) {
}
/**
* Clears the given widget
*
* @param item the widget
*/
protected abstract void clear(Widget item);
/**
* Clears the children of the widget.
*
* @param item the widget to clear children from
*/
protected abstract void clearChildren(Widget item);
/**
* Clears the child at the given index.
*
* @param parent the parent widget
* @param childIndex the index of the child widget to clear
*/
protected abstract void clearChild(Widget parent, int childIndex);
/**
* Returns the child widget at the given index for the given parent or
* <code>null</code>
*
* @param parent the parent widget
* @param index the index of the child in the parent widget
* @return the widget at the given index in the parent or <code>null</code>
*/
protected abstract Widget getChildWidget(Widget parent, int index);
/**
* Sets the item count for a parent widget
*
* @param parent the parent widget
* @param itemCount the new item count to set
*/
protected abstract void setItemCount(Widget parent, int itemCount);
/**
* Attempt pending updates. Subclasses may override but should call super.
*/
protected void attemptPendingUpdates() {
attemptSelection(false);
}
/**
* Notification a node's children have changed.
* Updates the child count for the parent's widget
* and clears children to be updated.
*
* @param parentNode the parent model node
*/
protected void nodeChildrenChanged(ModelNode parentNode) {
Widget widget = findItem(parentNode);
if (widget != null && !widget.isDisposed()) {
int childCount = parentNode.getChildCount();
setItemCount(widget, childCount);
clearChildren(widget);
attemptPendingUpdates();
}
}
/**
* Notification children have been added to the end
* of the given parent.
*
* @param parentNode the parent model node
*/
protected void nodeChildrenAdded(ModelNode parentNode) {
Widget widget = findItem(parentNode);
if (widget != null && !widget.isDisposed()) {
int childCount = parentNode.getChildCount();
setItemCount(widget, childCount);
attemptPendingUpdates();
}
}
/**
* Notification children have been added to the end
* of the given parent.
*
* @param parentNode the parent model node
* @param index the index of the child that was removed
*/
protected void nodeChildRemoved(ModelNode parentNode, int index) {
Widget widget = findItem(parentNode);
if (widget != null && !widget.isDisposed()) {
Widget childWidget = getChildWidget(widget, index);
int childCount = parentNode.getChildCount();
// if the child widget exists, dispose it so item state remains, otherwise update child count
if (childWidget == null) {
setItemCount(widget, childCount);
} else {
childWidget.dispose();
}
for (int i = index; i < childCount; i ++) {
clearChild(widget, i);
}
attemptPendingUpdates();
}
}
/**
* Unmaps the node from its widget and all of its children nodes from
* their widgets.
*
* @param node the model node
*/
protected void unmapNode(ModelNode node) {
unmapElement(node);
ModelNode[] childrenNodes = node.getChildrenNodes();
if (childrenNodes != null) {
for (ModelNode childNode : childrenNodes) {
unmapNode(childNode);
}
}
}
/**
* Returns the node corresponding to the given widget or <code>null</code>
* @param widget widget for which a node is requested
* @return node or <code>null</code>
*/
protected ModelNode findNode(Widget widget) {
ModelNode[] nodes = getModel().getNodes(widget.getData());
if (nodes != null) {
for (ModelNode node : nodes) {
Widget item = findItem(node);
if (widget == item) {
return node;
}
}
}
return null;
}
/**
* Returns the item for the node or <code>null</code>
* @param node the model node
* @return the widget or <code>null</code>
*/
protected Widget findItem(ModelNode node) {
return findItem((Object)node);
}
/*
* A virtual item has been exposed in the control, map its data.
*/
@Override
public void handleEvent(final Event event) {
update((Item)event.item, event.index);
}
/**
* Update the given item.
*
* @param item item to update
* @param index index of item in parent's children
*/
protected void update(Item item, int index) {
restoreLabels(item);
int level = 0;
Widget parentItem = getParentWidget(item);
if (DebugUIPlugin.DEBUG_VIEWER) {
DebugUIPlugin.trace("SET DATA [" + index + "]: " + parentItem); //$NON-NLS-1$//$NON-NLS-2$
}
ModelNode node = null;
// first, see if the parent element is in the model
// and look directly for the child
if (parentItem != null) {
ModelNode[] nodes = getModel().getNodes(parentItem.getData());
if (nodes != null) {
for (ModelNode parentNode : nodes) {
Widget parentWidget = findItem(parentNode);
if (parentWidget == parentItem) {
ModelNode[] childrenNodes = parentNode.getChildrenNodes();
if (childrenNodes != null && index < childrenNodes.length) {
node = childrenNodes[index];
}
}
}
}
}
// otherwise, build a path to the model node
if (node == null) {
setNodeIndex(index, level);
while (parentItem instanceof Item) {
level++;
Widget parent = getParentWidget(parentItem);
int pindex = indexOf(parent, parentItem);
if (pindex < 0) {
return;
}
setNodeIndex(pindex, level);
parentItem = parent;
}
node = getModel().getRootNode();
if (node == null) {
if (DebugUIPlugin.DEBUG_VIEWER) {
DebugUIPlugin.trace("\tFAILED - root model node is null"); //$NON-NLS-1$
}
return;
}
for (int i = level; i >= 0; i--) {
ModelNode[] childrenNodes = node.getChildrenNodes();
if (childrenNodes == null) {
if (DebugUIPlugin.DEBUG_VIEWER) {
DebugUIPlugin.trace("\tFAILED - no children nodes for " + node); //$NON-NLS-1$
}
return;
}
int pindex = getNodeIndex(i);
if (pindex < childrenNodes.length) {
node = childrenNodes[pindex];
} else {
if (DebugUIPlugin.DEBUG_VIEWER) {
DebugUIPlugin.trace("\tFAILED - no children nodes for " + node); //$NON-NLS-1$
}
return;
}
}
}
// map the node to the element and refresh it
if (node != null) {
mapElement(node, item);
item.setData(node.getElement());
if (DebugUIPlugin.DEBUG_VIEWER) {
DebugUIPlugin.trace("\titem mapped: " + node); //$NON-NLS-1$
}
internalRefresh(node);
} else {
if (DebugUIPlugin.DEBUG_VIEWER) {
DebugUIPlugin.trace("\tFAILED - unable to find corresponding node"); //$NON-NLS-1$
}
}
}
/**
* Sets the index of a child node being mapped at the given expansion level
* in the tree.
*
* @param nodeIndex the index of the node
* @param level the expansion level
*/
private void setNodeIndex(int nodeIndex, int level) {
if (level > (fSetDataIndicies.length - 1)) {
// grow the array
int[] next = new int[level+5];
System.arraycopy(fSetDataIndicies, 0, next, 0, fSetDataIndicies.length);
fSetDataIndicies = next;
}
fSetDataIndicies[level] = nodeIndex;
}
/**
* Returns the index of a child node being mapped at the given expansion level in
* the tree.
*
* @param level the expansion level
* @return the child index
*/
private int getNodeIndex(int level) {
return fSetDataIndicies[level];
}
protected abstract int indexOf(Widget parent, Widget child);
protected abstract void restoreLabels(Item item);
/**
* Returns the parent widget for the given widget or <code>null</code>
*
* @param widget the widget to get the parent from
* @return parent widget or <code>null</code>
*/
protected abstract Widget getParentWidget(Widget widget);
/**
* Updates the children of the given node.
*
* @param parent
* node of which to update children
*/
protected void updateChildren(ModelNode parent) {
getModel().updateChildren(parent);
}
}