blob: ca0ed933f97b7ceae0af89c661f1e03f64f5b20e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.views.markers.internal;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
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.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Control;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* Provides a threaded content provider for efficiently handling large tables.
* The owner of this object is responsible for adding and removing the items in the
* table (by invoking the add, remove, set, and change methods, respectively). However,
* the changes are buffered and applied to the actual table incrementally
* using a background thread. This keeps the UI responsive when manipulating very
* large tables.
*
* <p>
* Other objects should set the contents of the table by manipulating
* this object rather than manipulating the table itself. Threading issues
* are encapsulated internally.
* </p>
*
*/
class TableContentProvider implements IStructuredContentProvider {
// NLS strings
private static final String TABLE_SYNCHRONIZATION = Messages.getString("TableContentProvider.TableSynchronization"); //$NON-NLS-1$
private static final String UPDATING_TABLE_WIDGET = Messages.getString("TableContentProvider.Updating"); //$NON-NLS-1$
/**
* Currently running update job.
*/
private String description = ""; //$NON-NLS-1$
/**
* Comparator to use for sorting the view
*/
private TableSorter sortOrder = null;
// Pending changes
private DeferredQueue queues;
/**
* This job disables redraw on the table
*/
private Job disableUpdatesJob = new WorkbenchJob(TABLE_SYNCHRONIZATION) {
public IStatus runInUIThread(IProgressMonitor monitor) {
if (controlExists()) {
getViewer().getTable().setRedraw(false);
}
return Status.OK_STATUS;
}
};
/**
* This job re-enables redraw on the table
*/
private Job enableUpdatesJob = new WorkbenchJob(TABLE_SYNCHRONIZATION) {
public IStatus runInUIThread(IProgressMonitor monitor) {
if (controlExists()) {
getViewer().getTable().setRedraw(true);
}
return Status.OK_STATUS;
}
};
/**
* This job is responsible for performing a single incremental update to the
* viewer. It will either add, remove, or change items in the viewer depending
* on the contents of the pending* sets, above. It is scheduled repeatedly by
* the OverallUpdateJob, below.
*/
private class WidgetRefreshJob extends WorkbenchJob {
/**
* Number of items modified in the last update
*/
int lastWorked = 0;
/**
* Remembers whether the viewer existed the last time this job was run
*/
boolean controlExists = true;
WidgetRefreshJob(String title) {
super(title);
}
/**
* Will be executed each time the update thread wakes up. This makes
* a single incremental update to the viewer (ie: adds or removes a few items)
*/
public IStatus runInUIThread(IProgressMonitor monitor) {
// If we can't get the lock, terminate without blocking the UI thread.
if (lock.getDepth() > 0) {
lastWorked = 0;
return Status.OK_STATUS;
}
lock.acquire();
try {
if (!PlatformUI.isWorkbenchRunning()) {
controlExists = false;
} else {
controlExists = controlExists();
if (controlExists) {
lastWorked = updateViewer();
}
}
} finally {
lock.release();
}
return Status.OK_STATUS;
}
}
/**
* Job that does the real work for individual updates
*/
WidgetRefreshJob uiJob;
/**
* This job incrementally updates the viewer until all pending changes have
* been applied. It repeatedly schedules WidgetRefreshJobs until there are no more
* changes to apply. This job doesn't actually do any real work -- it simply
* schedules updates and updates the progress bar.
*/
RestartableJob updateJob;
private ILock lock;
/**
* Creates a new TableContentProvider that will control the contents of the given
* viewer.
*
* @param viewer
* @param description user-readable string that will be included in progress monitors
* @param service IWorkbenchSiteProgressService or <code>null</null>
* the service that this content provider will inform of
* updates.
*/
public TableContentProvider(TableViewer viewer, String description, IWorkbenchSiteProgressService service) {
this.queues = new DeferredQueue(viewer);
this.description = description;
uiJob = new WidgetRefreshJob(UPDATING_TABLE_WIDGET);
uiJob.setPriority(Job.LONG);
uiJob.setSystem(true);
updateJob = new RestartableJob(TABLE_SYNCHRONIZATION,
new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
doUpdate(monitor);
}
}, service);
lock = Platform.getJobManager().newLock();
}
/**
* Sets the view's sorter (or null if no sorting is to be used)
*
* @param c comparator that controls the view's sort order (or null if no sorting)
*/
public void setSorter(TableSorter c) {
if (sortOrder != c) {
sortOrder = c;
scheduleUpdate();
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
*/
public Object[] getElements(Object inputElement) {
return queues.getVisibleItems();
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
public void dispose() {
//No state to dispose here
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
*/
public void inputChanged(Viewer inputViewer, Object oldInput, Object newInput) {
scheduleUpdate();
}
/**
* Sets the contents of the table. Note that the changes may not become visible
* immediately, as the viewer will actually be updated in a background thread.
*
* @param newVisibleItems
*/
public void set(Collection newVisibleItems, IProgressMonitor mon) {
lock.acquire();
try {
queues.set(newVisibleItems, mon);
scheduleUpdate();
} finally {
lock.release();
}
}
/**
* If the contents of the viewer have somehow become out-of-sync with the "visibleItems"
* set, this method will restore synchronization.
*/
private void resync() {
if (controlExists()) {
int count = queues.getViewer().getTable().getItemCount();
if (count != queues.countVisibleItems()) {
queues.getViewer().refresh();
}
}
}
/**
* Causes the given collection of items to be refreshed
*
* @param changes collection of objects that have changed
*/
void change(Collection changes) {
lock.acquire();
try {
// Ensure that this is never done in the user interface thread.
//Assert.isTrue(Display.getCurrent() == null);
queues.change(changes);
scheduleUpdate();
} finally {
lock.release();
}
}
/**
* Returns the TableViewer being populated by this content provider
*
* @return the TableViewer being populated by this content provider
*/
private TableViewer getViewer() {
return queues.getViewer();
}
/**
* Returns true iff the control exists and has not yet been disposed
*
* @return
*/
private boolean controlExists() {
Control control = getViewer().getControl();
if (control == null || control.isDisposed()) {
return false;
}
return true;
}
/**
* Returns true iff this content provider contains changes that are not yet
* reflected in the viewer.
*
* @return true iff the reciever has unprocessed changes
*/
public boolean hasPendingChanges() {
return queues.hasPendingChanges() || sortOrder != queues.getSorter();
}
/**
* Returns an estimate of the total work remaining (used for progress monitors)
*
* @return
*/
private int totalWork() {
return queues.workRemaining() + 1;
}
/**
* Starts the update thread... this will continue to make incremental changes
* to the viewer until the pending* sets become empty. Does nothing if the
* update thread is already running or if there are no changes to process.
*/
private void scheduleUpdate() {
if (hasPendingChanges()) {
updateJob.schedule();
}
}
/**
* Cancels any pending changes to the viewer. The contents of the viewer
* will be left in whatever state they are in at the time. Any changes that
* have not yet been applied will be lost. It is a good idea to call this
* method before performing a long computation that will ultimately invalidate
* the contents of the viewer.
*/
public void cancelPendingChanges() {
updateJob.cancel();
lock.acquire();
try {
queues.cancelPending();
} finally {
lock.release();
}
}
private void doUpdate(IProgressMonitor monitor) throws InterruptedException {
//Do not update if the workbench is shutdown
if(!PlatformUI.isWorkbenchRunning())
return;
// This counter represents how many work units remain unused in the progress monitor.
int remainingWorkUnits = 100000;
monitor.beginTask(description, remainingWorkUnits);
disableUpdatesJob.schedule();
disableUpdatesJob.join();
try {
// Loop until there are no more changes to apply, the control is destroyed, the monitor is cancelled,
// or another job takes over.
while (hasPendingChanges() && !monitor.isCanceled()) {
// Ensure that we aren't running in the UI thread
//Assert.isTrue(Display.getCurrent() == null);
try {
int totalWork;
lock.acquire();
try {
totalWork = totalWork();
if (sortOrder != queues.getSorter()) {
queues.setComparator(sortOrder);
}
SubProgressMonitor sub = new SubProgressMonitor(monitor, 0);
queues.refreshQueues(sub);
} finally {
lock.release();
}
try {
uiJob.schedule();
// Wait for the current update job to complete before scheduling another
uiJob.join();
} catch (IllegalStateException e) {
// Ignore this exception -- it means that the Job manager was shut down, which is expected
// at the end of the application. Note that we need to check for this by catching the exception
// rather than using something like if (jobManagerIsShutDown())... since the job manager might
// be shut down in another thread between the time we evaluate the if statement and when
// we schedule the job.
}
// Estimate how much of the remaining work we'll be doing in this update,
// and update the progress bar appropriately.
int consumedUnits = uiJob.lastWorked * remainingWorkUnits / totalWork;
monitor.worked(consumedUnits);
remainingWorkUnits -= consumedUnits;
} catch (InterruptedException e) {
monitor.setCanceled(true);
}
if (!uiJob.controlExists) {
break;
}
}
} finally {
//Only send updates if we can send them to the workbench
if(PlatformUI.isWorkbenchRunning()){
enableUpdatesJob.schedule();
enableUpdatesJob.join();
}
monitor.done();
}
}
/**
* Performs a single update to the viewer. Based on the contents of the pending* queues,
* items will either be removed, added, or refreshed in the viewer (in that order). This
* should only be called within a synchronized block, since the various queues shouldn't
* be modified during an update. This method is invoked repeatedly by jobs to gradually
* apply the pending changes.
*/
private int updateViewer() {
int result;
// Note that this method is only called when we have the lock so acquiring it here
// does nothing... but we re-acquire it anyway in case future refactoring causes
// this to be called when we don't own the lock.
lock.acquire();
try {
if (getViewer().getSorter() != null) {
getViewer().setSorter(null);
}
resync();
result = queues.nextUpdate();
} finally {
lock.release();
}
return result;
}
}