| /******************************************************************************* |
| * Copyright (c) 2005, 2007 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.Platform; |
| 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 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 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 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$ |
| |
| // debug flags |
| public static boolean DEBUG_VIEWER = false; |
| |
| static { |
| DEBUG_VIEWER = DebugUIPlugin.DEBUG && "true".equals( //$NON-NLS-1$ |
| Platform.getDebugOption("org.eclipse.debug.ui/debug/viewers/viewer")); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates a new viewer |
| */ |
| protected AsynchronousViewer() { |
| setContentProvider(new NullContentProvider()); |
| setUseHashlookup(true); |
| } |
| |
| /** |
| * Hash lookup is required, don't let subclasses change behavior. |
| */ |
| 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) |
| */ |
| 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() { |
| Iterator images = fImageCache.values().iterator(); |
| while (images.hasNext()) { |
| Image image = (Image) images.next(); |
| image.dispose(); |
| } |
| fImageCache.clear(); |
| |
| Iterator fonts = fFontCache.values().iterator(); |
| while (fonts.hasNext()) { |
| Font font = (Font) fonts.next(); |
| font.dispose(); |
| } |
| fFontCache.clear(); |
| |
| Iterator colors = fColorCache.values().iterator(); |
| while (colors.hasNext()) { |
| Color color = (Color) colors.next(); |
| 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 |
| * @param item its associated item |
| */ |
| 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() |
| */ |
| 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) |
| */ |
| 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 = (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 = (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 = (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 |
| */ |
| public void setContext(IPresentationContext context) { |
| fContext = context; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.viewers.StructuredViewer#doFindItem(java.lang.Object) |
| */ |
| 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) |
| */ |
| 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) |
| */ |
| protected void doUpdateItem(Widget item, Object element, boolean fullMap) { |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.viewers.StructuredViewer#internalRefresh(java.lang.Object) |
| */ |
| 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) |
| */ |
| 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$ |
| 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 |
| * @param curr |
| * @return |
| */ |
| 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() |
| */ |
| 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) |
| */ |
| 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) |
| */ |
| 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 = (IModelSelectionPolicyFactory) 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) |
| */ |
| 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) |
| */ |
| 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 { |
| public void dispose() { |
| } |
| |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| } |
| |
| 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) |
| */ |
| 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$ |
| 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 |
| */ |
| 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) |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| protected void updateComplete(IStatusMonitor monitor) { |
| } |
| |
| /** |
| * Clears the given widget |
| * |
| * @param item |
| */ |
| protected abstract void clear(Widget item); |
| |
| /** |
| * Clears the children of the widget. |
| * |
| * @param item |
| */ |
| protected abstract void clearChildren(Widget item); |
| |
| /** |
| * Clears the child at the given index. |
| * |
| * @param parent |
| * @param childIndex |
| */ |
| 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 |
| * @param index |
| * @return |
| */ |
| protected abstract Widget getChildWidget(Widget parent, int index); |
| |
| /** |
| * Sets the item count for a parent widget |
| * |
| * @param parent |
| * @param itemCount |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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 |
| * @return |
| */ |
| 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) |
| */ |
| 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 (DEBUG_VIEWER) { |
| DebugUIPlugin.debug("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 (DEBUG_VIEWER) { |
| DebugUIPlugin.debug("\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 (DEBUG_VIEWER) { |
| DebugUIPlugin.debug("\tFAILED - no children nodes for " + node); //$NON-NLS-1$ |
| } |
| return; |
| } |
| int pindex = getNodeIndex(i); |
| if (pindex < childrenNodes.length) { |
| node = childrenNodes[pindex]; |
| } else { |
| if (DEBUG_VIEWER) { |
| DebugUIPlugin.debug("\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 (DEBUG_VIEWER) { |
| DebugUIPlugin.debug("\titem mapped: " + node); //$NON-NLS-1$ |
| } |
| internalRefresh(node); |
| } else { |
| if (DEBUG_VIEWER) { |
| DebugUIPlugin.debug("\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 |
| * @param 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 |
| * @return |
| */ |
| 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 |
| * @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); |
| } |
| } |