/******************************************************************************* * Copyright (c) 2005, 2013 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.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<ImageDescriptor, Image>(); /** * 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<FontData, Font>(); /** * 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<RGB, Color>(); /** * 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); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#hookControl(org.eclipse.swt.widgets.Control) */ @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 (int i = 0; i < nodes.length; i++) { updateLabel(nodes[i]); } } } /** * 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; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#unmapAllElements() */ @Override protected synchronized void unmapAllElements() { super.unmapAllElements(); AsynchronousModel model = getModel(); if (model != null) { model.dispose(); } } /* (non-Javadoc) * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object, java.lang.Object) */ @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; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#doFindItem(java.lang.Object) */ @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; } /* * (non-Javadoc) * * @see org.eclipse.jface.viewers.StructuredViewer#doFindInputItem(java.lang.Object) */ @Override protected Widget doFindInputItem(Object element) { if (element instanceof ModelNode) { ModelNode node = (ModelNode) element; if (node.getElement().equals(getInput())) { return getControl(); } } return null; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#doUpdateItem(org.eclipse.swt.widgets.Widget, java.lang.Object, boolean) */ @Override protected void doUpdateItem(Widget item, Object element, boolean fullMap) { } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#internalRefresh(java.lang.Object) */ @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 (int i = 0; i < nodes.length; i++) { ModelNode node = nodes[i]; // 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); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean) */ @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()); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#getSelection() */ @Override public ISelection getSelection() { Control control = getControl(); if (control == null || control.isDisposed() || fCurrentSelection == null) { return StructuredSelection.EMPTY; } return fCurrentSelection; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#handleSelect(org.eclipse.swt.events.SelectionEvent) */ @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()); } } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#handlePostSelect(org.eclipse.swt.events.SelectionEvent) */ @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; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(org.eclipse.jface.viewers.ISelection, boolean) */ @Override final protected void setSelectionToWidget(ISelection selection, final boolean reveal) { // NOT USED throw new IllegalArgumentException("This method should not be called"); //$NON-NLS-1$ } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(java.util.List, boolean) */ @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 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) { } /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#preservingSelection(java.lang.Runnable) */ @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); /* (non-Javadoc) * @see org.eclipse.jface.viewers.StructuredViewer#updateSelection(org.eclipse.jface.viewers.ISelection) */ @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 (int i = 0; i < childrenNodes.length; i++) { unmapNode(childrenNodes[i]); } } } /** * 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 (int i = 0; i < nodes.length; i++) { ModelNode node = nodes[i]; 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); } /* * (non-Javadoc) * * A virtual item has been exposed in the control, map its data. * * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ @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 (int i = 0; i < nodes.length; i++) { ModelNode parentNode = nodes[i]; 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); } }