From 55f06e88cb50b4ea49468c68b06b33a3eb5348a6 Mon Sep 17 00:00:00 2001 From: Alain Magloire Date: Sun, 12 Nov 2006 15:58:39 +0000 Subject: [PATCH] PR# 104605 When exploding a binary in the CView not to do it in the UI thread and use a deferred manager to show "pending" while we finish the parsing. --- .../ui/cview/CViewContentProvider.java | 61 ++- .../ui/util/PendingUpdateAdapter.java | 84 ++++ .../ui/util/RemoteTreeContentManager.java | 357 +++++++++++++++ .../internal/ui/util/RemoteTreeViewer.java | 431 ++++++++++++++++++ 4 files changed, 928 insertions(+), 5 deletions(-) create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/PendingUpdateAdapter.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeContentManager.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeViewer.java diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/cview/CViewContentProvider.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/cview/CViewContentProvider.java index 3c78cf1732c..9ecf1b8ffa3 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/cview/CViewContentProvider.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/cview/CViewContentProvider.java @@ -22,35 +22,63 @@ import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.IIncludeReference; import org.eclipse.cdt.core.model.ILibraryReference; +import org.eclipse.cdt.internal.ui.util.RemoteTreeContentManager; +import org.eclipse.cdt.internal.ui.util.RemoteTreeViewer; import org.eclipse.cdt.ui.CElementContentProvider; import org.eclipse.core.resources.IContainer; import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.IWorkbenchPartSite; /** * CViewContentProvider */ public class CViewContentProvider extends CElementContentProvider { + private RemoteTreeContentManager fManager; + /** * */ - public CViewContentProvider() { + public CViewContentProvider(TreeViewer viewer, IWorkbenchPartSite site) { super(); + fManager = createContentManager(viewer, site); } /** * @param provideMembers * @param provideWorkingCopy */ - public CViewContentProvider(boolean provideMembers, boolean provideWorkingCopy) { + public CViewContentProvider(TreeViewer viewer, IWorkbenchPartSite site, boolean provideMembers, boolean provideWorkingCopy) { super(provideMembers, provideWorkingCopy); + fManager = createContentManager(viewer, site); + } + + protected RemoteTreeContentManager createContentManager(TreeViewer viewer, IWorkbenchPartSite site) { + if (site == null) { + return new RemoteTreeContentManager(this, (RemoteTreeViewer)viewer, null); + } + return new RemoteTreeContentManager(this, (RemoteTreeViewer)viewer, site); } - /* (non-Javadoc) * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object) */ public Object[] getChildren(Object element) { - Object[] objs = super.getChildren(element); + Object[] objs = null; + + // use the the deferred manager for some cases + if (element instanceof IBinary) { + // It takes sometimes to parse binaries deferred it + objs = fManager.getChildren(element); + } else if (element instanceof IArchive) { + // It takes sometimes to parse archives deferred it + objs = fManager.getChildren(element); + } + + if (objs == null) { + objs = super.getChildren(element); + } Object[] extras = null; try { if (element instanceof ICProject) { @@ -170,7 +198,11 @@ public class CViewContentProvider extends CElementContentProvider { * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object) */ public boolean hasChildren(Object element) { - if (element instanceof IBinaryContainer) { + if (element instanceof IBinary) { + return fManager.mayHaveChildren(element); + } else if (element instanceof IArchive) { + return fManager.mayHaveChildren(element); + } else if (element instanceof IBinaryContainer) { try { IBinaryContainer cont = (IBinaryContainer)element; IBinary[] bins = getBinaries(cont); @@ -199,4 +231,23 @@ public class CViewContentProvider extends CElementContentProvider { } return super.hasChildren(element); } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IContentProvider#dispose() + */ + public void dispose() { + if (fManager != null) { + fManager.cancel(); + } + super.dispose(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object) + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + fManager.cancel(); + super.inputChanged(viewer, oldInput, newInput); + } + } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/PendingUpdateAdapter.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/PendingUpdateAdapter.java new file mode 100644 index 00000000000..6a250234cc8 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/PendingUpdateAdapter.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2003, 2005 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.cdt.internal.ui.util; + +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.model.IWorkbenchAdapter; + +/** + * The PendingUpdateAdapter is a convenience object that can be used + * by a BaseWorkbenchContentProvider that wants to show a pending update. + */ +public class PendingUpdateAdapter implements IWorkbenchAdapter, IAdaptable { + + boolean removed = false; + + /** + * Return whether or not this has been removed from the tree. + * @return boolean + */ + public boolean isRemoved() { + return removed; + } + + /** + * Set whether or not this has been removed from the tree. + * @param removedValue boolean + */ + public void setRemoved(boolean removedValue) { + this.removed = removedValue; + } + + /** + * Create a new instance of the receiver. + */ + public PendingUpdateAdapter() { + //No initial behavior + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) + */ + public Object getAdapter(Class adapter) { + if (adapter == IWorkbenchAdapter.class) + return this; + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.model.IWorkbenchAdapter#getChildren(java.lang.Object) + */ + public Object[] getChildren(Object o) { + return new Object[0]; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.model.IWorkbenchAdapter#getImageDescriptor(java.lang.Object) + */ + public ImageDescriptor getImageDescriptor(Object object) { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.model.IWorkbenchAdapter#getLabel(java.lang.Object) + */ + public String getLabel(Object o) { + return "Pending"; //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.eclipse.ui.model.IWorkbenchAdapter#getParent(java.lang.Object) + */ + public Object getParent(Object o) { + return null; + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeContentManager.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeContentManager.java new file mode 100644 index 00000000000..9d35aa314a5 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeContentManager.java @@ -0,0 +1,357 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.cdt.internal.ui.util; + +import java.util.ArrayList; +import java.util.List; + +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.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IDeferredWorkbenchAdapter; +import org.eclipse.ui.progress.IElementCollector; +import org.eclipse.ui.progress.IWorkbenchSiteProgressService; +import org.eclipse.ui.progress.WorkbenchJob; + +/** + * A remote content manager that merges content into a tree rather then replacing + * its children with a "pending" node, and then the real children when they are available. + * This avoids collapsing the viewer when a refresh is performed. This implementation is + * currently tied to the RemoteTreeViewer. + * + * @since 3.1 + */ +public class RemoteTreeContentManager { + private RemoteTreeViewer fViewer; + private IWorkbenchSiteProgressService progressService; + private ITreeContentProvider fProvider; + + /** + * Job to fetch children + */ + private Job fFetchJob = new FetchJob(); + + /** + * Queue of parents to fetch children for, and + * associated element collectors and deferred adapters. + */ + private List fElementQueue = new ArrayList(); + private List fCollectors = new ArrayList(); + private List fAdapaters = new ArrayList(); + + /** + * Fetching children is done in a single background job. + * This makes fetching single threaded/serial per view. + */ + class FetchJob extends Job { + + public FetchJob() { + super("FetchJob"); //$NON-NLS-1$ + setSystem(true); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + protected IStatus run(IProgressMonitor monitor) { + while (!fElementQueue.isEmpty() && !monitor.isCanceled()) { + Object element = null; + IElementCollector collector = null; + IDeferredWorkbenchAdapter adapter = null; + synchronized (fElementQueue) { + // could have been cancelled after entering the while loop + if (fElementQueue.isEmpty()) { + return Status.CANCEL_STATUS; + } + element = fElementQueue.remove(0); + collector = (IElementCollector) fCollectors.remove(0); + adapter = (IDeferredWorkbenchAdapter) fAdapaters.remove(0); + } + adapter.fetchDeferredChildren(element, collector, monitor); + } + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } + return Status.OK_STATUS; + } + + } + + /** + * Element collector + */ + public class Collector implements IElementCollector { + // number of children added to the tree + int offset = 0; + Object fParent; + + public Collector(Object parent) { + fParent = parent; + } + /* + * (non-Javadoc) + * @see org.eclipse.jface.progress.IElementCollector#add(java.lang.Object, org.eclipse.core.runtime.IProgressMonitor) + */ + public void add(Object element, IProgressMonitor monitor) { + add(new Object[] { element }, monitor); + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.progress.IElementCollector#add(java.lang.Object[], org.eclipse.core.runtime.IProgressMonitor) + */ + public void add(Object[] elements, IProgressMonitor monitor) { + Object[] filtered = fViewer.filter(elements); + if (filtered.length > 0) { + replaceChildren(fParent, filtered, offset, monitor); + offset = offset + filtered.length; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.progress.IElementCollector#done() + */ + public void done() { + prune(fParent, offset); + } + } + + /** + * Contructs a new content manager. + * + * @param provider content provider + * @param viewer viewer + * @param site part site + */ + public RemoteTreeContentManager(ITreeContentProvider provider, RemoteTreeViewer viewer, IWorkbenchPartSite site) { + fViewer = viewer; + fProvider = provider; + if (site != null) { + Object siteService = site.getAdapter(IWorkbenchSiteProgressService.class); + if (siteService != null) { + progressService = (IWorkbenchSiteProgressService) siteService; + } + } + } + + /** + * Create the element collector for the receiver. + *@param parent + * The parent object being filled in, + * @param placeholder + * The adapter that will be used to indicate that results are + * pending, possibly null + * @return IElementCollector + */ + protected IElementCollector createElementCollector(Object parent, PendingUpdateAdapter placeholder) { + return new Collector(parent); + } + + /** + * Returns the child elements of the given element, or in the case of a + * deferred element, returns a placeholder. If a deferred element is used, a + * job is created to fetch the children in the background. + * + * @param parent + * The parent object. + * @return Object[] or null if parent is not an instance of + * IDeferredWorkbenchAdapter. + */ + public Object[] getChildren(final Object parent) { + IDeferredWorkbenchAdapter element = getAdapter(parent); + if (element == null) + return null; + Object[] currentChildren = fViewer.getCurrentChildren(parent); + PendingUpdateAdapter placeholder = null; + if (currentChildren == null || currentChildren.length == 0) { + placeholder = new PendingUpdateAdapter(); + } + startFetchingDeferredChildren(parent, element, placeholder); + if (placeholder == null) { + return currentChildren; + } + return new Object[] { placeholder }; + } + + /** + * Create a UIJob to replace the children of the parent in the tree viewer. + * + * @param parent the parent for which children are to be replaced + * @param children the replacement children + * @param offset the offset at which to start replacing children + * @param monitor progress monitor + */ + protected void replaceChildren(final Object parent, final Object[] children, final int offset, IProgressMonitor monitor) { + if (monitor.isCanceled()) { + return; + } + WorkbenchJob updateJob = new WorkbenchJob("IncrementalDeferredTreeContentManager") { //$NON-NLS-1$ + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus runInUIThread(IProgressMonitor updateMonitor) { + //Cancel the job if the tree viewer got closed + if (fViewer.getControl().isDisposed()) + return Status.CANCEL_STATUS; + fViewer.replace(parent, children, offset); + return Status.OK_STATUS; + } + }; + updateJob.setSystem(true); + updateJob.setPriority(Job.INTERACTIVE); + updateJob.schedule(); + } + + /** + * Create a UIJob to prune the children of the parent in the tree viewer, starting + * at the given offset. + * + * @param parent the parent for which children should be pruned + * @param offset the offset at which children should be pruned. All children at and after + * this index will be removed from the tree. + * @param monitor + */ + protected void prune(final Object parent, final int offset) { + WorkbenchJob updateJob = new WorkbenchJob("DeferredTree") { //$NON-NLS-1$ + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus runInUIThread(IProgressMonitor updateMonitor) { + //Cancel the job if the tree viewer got closed + if (fViewer.getControl().isDisposed()) + return Status.CANCEL_STATUS; + fViewer.prune(parent, offset); + return Status.OK_STATUS; + } + }; + updateJob.setSystem(true); + updateJob.setPriority(Job.INTERACTIVE); + updateJob.schedule(); + } + + /** + * Run a job to clear the placeholder. This is used when the update + * for the tree is complete so that the user is aware that no more + * updates are pending. + * + * @param placeholder + */ + protected void runClearPlaceholderJob(final PendingUpdateAdapter placeholder) { + if (placeholder == null || placeholder.isRemoved() || !PlatformUI.isWorkbenchRunning()) + return; + //Clear the placeholder if it is still there + WorkbenchJob clearJob = new WorkbenchJob("DeferredTreeContentManager_ClearJob") { //$NON-NLS-1$ + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus runInUIThread(IProgressMonitor monitor) { + if (!placeholder.isRemoved()) { + Control control = fViewer.getControl(); + if (control.isDisposed()) + return Status.CANCEL_STATUS; + fViewer.remove(placeholder); + placeholder.setRemoved(true); + } + return Status.OK_STATUS; + } + }; + clearJob.setSystem(true); + clearJob.schedule(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.progress.DeferredTreeContentManager#getFetchJobName(java.lang.Object, org.eclipse.ui.progress.IDeferredWorkbenchAdapter) + */ + protected String getFetchJobName(Object parent, IDeferredWorkbenchAdapter adapter) { + return "RemoteTreeContentManager"; //$NON-NLS-1$ + } + + + /** + * Return the IDeferredWorkbenchAdapter for element or the element if it is + * an instance of IDeferredWorkbenchAdapter. If it does not exist return + * null. + * + * @param element + * @return IDeferredWorkbenchAdapter or null + */ + protected IDeferredWorkbenchAdapter getAdapter(Object element) { + if (element instanceof IDeferredWorkbenchAdapter) + return (IDeferredWorkbenchAdapter) element; + if (!(element instanceof IAdaptable)) + return null; + Object adapter = ((IAdaptable) element) + .getAdapter(IDeferredWorkbenchAdapter.class); + if (adapter == null) + return null; + return (IDeferredWorkbenchAdapter) adapter; + } + + protected void startFetchingDeferredChildren(final Object parent, final IDeferredWorkbenchAdapter adapter, PendingUpdateAdapter placeholder) { + final IElementCollector collector = createElementCollector(parent, placeholder); + synchronized (fElementQueue) { + if (!fElementQueue.contains(parent)) { + fElementQueue.add(parent); + fCollectors.add(collector); + fAdapaters.add(adapter); + } + } + if (progressService == null) + fFetchJob.schedule(); + else + progressService.schedule(fFetchJob); + } + + /** + * Provides an optimized lookup for determining if an element has children. + * This is required because elements that are populated lazilly can't + * answer getChildren just to determine the potential for + * children. Throw an AssertionFailedException if element is null. + * + * @param element The Object being tested. This should not be + * null. + * @return boolean true if there are potentially children. + * @throws RuntimeException if the element is null. + */ + public boolean mayHaveChildren(Object element) { + //Assert.isNotNull(element, ProgressMessages.DeferredTreeContentManager_NotDeferred); + IDeferredWorkbenchAdapter adapter = getAdapter(element); + return adapter != null && adapter.isContainer(); + } + + /** + * Cancels any content this provider is currently fetching. + */ + public void cancel() { + synchronized (fElementQueue) { + fFetchJob.cancel(); + fElementQueue.clear(); + fAdapaters.clear(); + fCollectors.clear(); + } + } + +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeViewer.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeViewer.java new file mode 100644 index 00000000000..6ae497d2796 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/RemoteTreeViewer.java @@ -0,0 +1,431 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.cdt.internal.ui.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.cdt.core.model.ITranslationUnit; +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.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.model.IWorkbenchAdapter; +import org.eclipse.ui.progress.UIJob; + +public class RemoteTreeViewer extends ProblemTreeViewer { + private ExpansionJob fExpansionJob = null; + private SelectionJob fSelectionJob = null; + + + class ExpansionJob extends UIJob { + + private Object element; + private List parents = new ArrayList(); // top down + + /** + * Constucts a job to expand the given element. + * + * @param target the element to expand + */ + public ExpansionJob() { + super("Expansion"); //$NON-NLS-1$ + setPriority(Job.INTERACTIVE); + setSystem(true); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus runInUIThread(IProgressMonitor monitor) { + if (getControl().isDisposed() || element == null) { + return Status.OK_STATUS; + } + synchronized (RemoteTreeViewer.this) { + boolean allParentsExpanded = true; + Iterator iterator = parents.iterator(); + while (iterator.hasNext() && !monitor.isCanceled()) { + Object parent = iterator.next(); + Widget item = findItem(parent); + if (item != null) { + expandToLevel(parent, 1); + } else { + allParentsExpanded = false; + break; + } + } + if (allParentsExpanded) { + Widget item = findItem(element); + if (item != null) { + if (isExpandable(element)) { + expandToLevel(element, 1); + } + element = null; + parents.clear(); + return Status.OK_STATUS; + } + } + return Status.OK_STATUS; + } + } + + public void validate(Object object) { + if (element != null) { + if (element.equals(object) || parents.contains(object)) { + cancel(); + element = null; + } + } + } + + public void setDeferredExpansion(Object toExpand) { + element = toExpand; + parents.clear(); + addAllParents(parents, element); + } + + } + + class SelectionJob extends UIJob { + + private IStructuredSelection selection; + private Object first; + private List parents = new ArrayList(); // top down + + /** + * Constucts a job to select the given element. + * + * @param target the element to select + */ + public SelectionJob() { + super("Selection"); //$NON-NLS-1$ + setPriority(Job.INTERACTIVE); + setSystem(true); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus runInUIThread(IProgressMonitor monitor) { + if (getControl().isDisposed() || selection == null) { + return Status.OK_STATUS; + } + synchronized (RemoteTreeViewer.this) { + boolean allParentsExpanded = true; + Iterator iterator = parents.iterator(); + while (iterator.hasNext() && !monitor.isCanceled()) { + Object parent = iterator.next(); + Widget item = findItem(parent); + if (item != null) { + expandToLevel(parent, 1); + } else { + allParentsExpanded = false; + break; + } + } + if (allParentsExpanded) { + if (findItem(first) != null) { + setSelection(selection, true); + selection = null; + first = null; + parents.clear(); + return Status.OK_STATUS; + } + } + + return Status.OK_STATUS; + } + } + + public void setDeferredSelection(IStructuredSelection sel) { + selection = sel; + first = selection.getFirstElement(); + parents.clear(); + addAllParents(parents, first); + } + + public void validate(Object object) { + if (first != null) { + if (first.equals(object) || parents.contains(object)) { + cancel(); + selection = null; + } + } + } + } + + + /** + * Constructs a remote tree viewer parented by the given composite. + * + * @param parent parent composite + */ + public RemoteTreeViewer(Composite parent) { + super(parent); + addDisposeListener(); + fExpansionJob = new ExpansionJob(); + fSelectionJob = new SelectionJob(); + } + + /** + * Constructs a remote tree viewer parented by the given composite + * with the given style. + * + * @param parent parent composite + * @param style style bits + */ + public RemoteTreeViewer(Composite parent, int style) { + super(parent, style); + addDisposeListener(); + fExpansionJob = new ExpansionJob(); + fSelectionJob = new SelectionJob(); + } + + /** + * Constructs a remote tree viewer with the given tree. + * + * @param tree tree widget + */ + public RemoteTreeViewer(Tree tree) { + super(tree); + addDisposeListener(); + fExpansionJob = new ExpansionJob(); + fSelectionJob = new SelectionJob(); + } + + private void addDisposeListener() { + getControl().addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + cancelJobs(); + } + }); + } + + protected void runDeferredUpdates() { + if (fExpansionJob != null) { + fExpansionJob.schedule(); + } + if (fSelectionJob != null) { + fSelectionJob.schedule(); + } + } + + /** + * The given element is being removed from the tree. Cancel + * any deferred updates for the element. + * + * @param element + */ + protected void validateDeferredUpdates(Object element) { + if (element != null) { + if (fExpansionJob != null) { + fExpansionJob.validate(element); + } + if (fSelectionJob != null) { + fSelectionJob.validate(element); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.AbstractTreeViewer#add(java.lang.Object, java.lang.Object) + */ + public synchronized void add(Object parentElement, Object childElement) { + super.add(parentElement, childElement); + runDeferredUpdates(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.AbstractTreeViewer#add(java.lang.Object, java.lang.Object[]) + */ + public synchronized void add(Object parentElement, Object[] childElements) { + super.add(parentElement, childElements); + runDeferredUpdates(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.AbstractTreeViewer#remove(java.lang.Object) + */ + public synchronized void remove(Object element) { + validateDeferredUpdates(element); + super.remove(element); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.AbstractTreeViewer#remove(java.lang.Object[]) + */ + public synchronized void remove(Object[] elements) { + for (int i = 0; i < elements.length; i++) { + validateDeferredUpdates(elements[i]); + } + super.remove(elements); + } + + /** + * Cancels any deferred updates currently scheduled/running. + */ + public void cancelJobs() { + cancel(fSelectionJob); + cancel(fExpansionJob); + } + + public synchronized void deferExpansion(Object element) { + TreeItem treeItem = (TreeItem) findItem(element); + if (treeItem == null) { + fExpansionJob.setDeferredExpansion(element); + fExpansionJob.schedule(); + } else { + if (!getExpanded(treeItem)) { + fExpansionJob.setDeferredExpansion(element); + fExpansionJob.schedule(); + } + } + } + + public synchronized void deferSelection(IStructuredSelection selection) { + if (fSelectionJob == null) { + fSelectionJob = new SelectionJob(); + } + + fSelectionJob.setDeferredSelection(selection); + fSelectionJob.schedule(); + } + + public IStructuredSelection getDeferredSelection() { + if (fSelectionJob != null) { + return fSelectionJob.selection; + } + return null; + } + + private void cancel(Job job) { + if (job != null) { + job.cancel(); + } + } + + private void addAllParents(List list, Object element) { + if (element instanceof IAdaptable) { + IAdaptable adaptable = (IAdaptable) element; + IWorkbenchAdapter adapter = (IWorkbenchAdapter) adaptable.getAdapter(IWorkbenchAdapter.class); + if (adapter != null) { + Object parent = adapter.getParent(element); + if (parent != null) { + list.add(0, parent); + if (!(parent instanceof ITranslationUnit)) + addAllParents(list, parent); + } + } + } + } + + public Object[] filter(Object[] elements) { + return super.filter(elements); + } + + public Object[] getCurrentChildren(Object parent) { + Widget widget = findItem(parent); + if (widget != null) { + Item[] items = getChildren(widget); + Object[] children = new Object[items.length]; + for (int i = 0; i < children.length; i++) { + Object data = items[i].getData(); + if (data == null) { + return null; + } + children[i] = data; + } + return children; + } + return null; + } + + public synchronized void prune(final Object parent, final int offset) { + Widget widget = findItem(parent); + if (widget != null) { + final Item[] currentChildren = getChildren(widget); + if (offset < currentChildren.length) { + preservingSelection(new Runnable() { + public void run() { + for (int i = offset; i < currentChildren.length; i++) { + if (currentChildren[i].getData() != null) { + disassociate(currentChildren[i]); + } + currentChildren[i].dispose(); + } + } + }); + } + } + } + + public synchronized void replace(final Object parent, final Object[] children, final int offset) { + preservingSelection(new Runnable() { + public void run() { + Widget widget = findItem(parent); + if (widget == null) { + add(parent, children); + } else { + Item[] currentChildren = getChildren(widget); + int pos = offset; + if (pos >= currentChildren.length) { + // append + add(parent, children); + } else { + // replace + for (int i = 0; i < children.length; i++) { + Object child = children[i]; + if (pos < currentChildren.length) { + // replace + Item item = currentChildren[pos]; + Object data = item.getData(); + if (!child.equals(data)) { + // no need to cancel pending updates here, the child may have shifted up/down + internalRefresh(item, child, true, true); + } else { + // If it's the same child, the label/content may still have changed + doUpdateItem(item, child); + updatePlus(item, child); + } + } else { + // add + int numLeft = children.length - i; + if (numLeft > 1) { + Object[] others = new Object[numLeft]; + System.arraycopy(children, i, others, 0, numLeft); + add(parent, others); + } else { + add(parent, child); + } + break; + } + pos++; + } + } + } + runDeferredUpdates(); + } + }); + } + + +}