blob: 47c8f08add50e3c194783d029bdfcae5e692e967 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM - Initial API and implementation
* Remy Chi Jian Suen (Versant Corporation) - bug 255005
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654
*******************************************************************************/
package org.eclipse.ui.internal.progress;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.e4.ui.internal.workbench.swt.CSSConstants;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.PartSite;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.part.WorkbenchPart;
import org.eclipse.ui.progress.IProgressService;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* The WorkbenchSiteProgressService is the concrete implementation of the
* WorkbenchSiteProgressService used by the workbench components.
*/
public class WorkbenchSiteProgressService implements IWorkbenchSiteProgressService, IJobBusyListener {
PartSite site;
/**
* Map from Job to Boolean (non-null). Interpretation of the value:
* <ul>
* <li>{@link Boolean#TRUE}: The half busy cursor has been requested.</li>
* <li>{@link Boolean#FALSE}: The half busy cursor has not (yet) been
* requested.</li>
* </ul>
*/
private Map<Job, Boolean> busyJobs = new HashMap<>();
private Object busyLock = new Object();
IPropertyChangeListener[] changeListeners = new IPropertyChangeListener[0];
private int waitCursorJobCount;
private Object waitCursorLock = new Object();
private SiteUpdateJob updateJob;
/**
* Flag that keeps state from calls to {@link #incrementBusy()} and
* {@link #decrementBusy()}.
*/
private int busyCount = 0;
/**
* Job to run UI updates.
*/
public class SiteUpdateJob extends WorkbenchJob {
private boolean busy;
Object lock = new Object();
/**
* Set whether we are updating with the wait or busy cursor.
*
* @param cursorState
*/
void setBusy(boolean cursorState) {
synchronized (lock) {
busy = cursorState;
}
}
private SiteUpdateJob() {
super(ProgressMessages.WorkbenchSiteProgressService_CursorJob);
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
Control control = (Control) site.getModel().getWidget();
if (control == null || control.isDisposed()) {
return Status.CANCEL_STATUS;
}
synchronized (lock) {
// Update cursors if we are doing that
Cursor cursor = null;
if (waitCursorJobCount != 0) {
// at least one job which is running has requested for wait cursor
cursor = control.getDisplay().getSystemCursor(SWT.CURSOR_APPSTARTING);
}
control.setCursor(cursor);
showBusy(busy);
IWorkbenchPart part = site.getPart();
if (part instanceof WorkbenchPart) {
((WorkbenchPart) part).showBusy(busy);
}
}
return Status.OK_STATUS;
}
}
/**
* Create a new instance of the receiver with a site of partSite
*
* @param partSite PartSite.
*/
public WorkbenchSiteProgressService(final PartSite partSite) {
site = partSite;
updateJob = new SiteUpdateJob();
updateJob.setSystem(true);
}
/**
* Dispose the resources allocated by the receiver.
*
*/
public void dispose() {
if (updateJob != null) {
updateJob.cancel();
}
ProgressManager.getInstance().removeListener(this);
showBusy(false);
}
@Override
public void busyCursorWhile(IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException {
getWorkbenchProgressService().busyCursorWhile(runnable);
}
@Override
public void schedule(Job job, long delay, boolean useHalfBusyCursor) {
job.addJobChangeListener(getJobChangeListener(useHalfBusyCursor));
job.schedule(delay);
}
@Override
public void schedule(Job job, long delay) {
schedule(job, delay, false);
}
@Override
public void schedule(Job job) {
schedule(job, 0L, false);
}
@Override
public void showBusyForFamily(Object family) {
ProgressManager.getInstance().addListenerToFamily(family, this);
}
/**
* Get the job change listener for this site.
*
* @param useHalfBusyCursor <code>true</code> to use half busy cursor
* @return IJobChangeListener
*/
public IJobChangeListener getJobChangeListener(final boolean useHalfBusyCursor) {
return new JobChangeAdapter() {
@Override
public void aboutToRun(IJobChangeEvent event) {
incrementBusy(event.getJob(), useHalfBusyCursor);
}
@Override
public void done(IJobChangeEvent event) {
Job job = event.getJob();
decrementBusy(job);
job.removeJobChangeListener(this);
}
};
}
@Override
public void decrementBusy(Job job) {
Object halfBusyCursorState;
synchronized (busyLock) {
halfBusyCursorState = busyJobs.remove(job);
if (halfBusyCursorState == null) {
return;
}
}
if (halfBusyCursorState == Boolean.TRUE) {
synchronized (waitCursorLock) {
waitCursorJobCount--;
}
}
try {
decrementBusy();
} catch (Exception ex) {
// protecting against assertion failures
WorkbenchPlugin.log(ex);
}
}
@Override
public void incrementBusy(Job job) {
incrementBusy(job, false);
}
private void incrementBusy(Job job, boolean useHalfBusyCursor) {
Object halfBusyCursorState;
synchronized (busyLock) {
halfBusyCursorState = busyJobs.get(job);
if (useHalfBusyCursor || halfBusyCursorState != Boolean.TRUE)
busyJobs.put(job, Boolean.valueOf(useHalfBusyCursor));
}
if (useHalfBusyCursor && halfBusyCursorState != Boolean.TRUE) {
// want to set busy cursor and it has not been set before
synchronized (waitCursorLock) {
waitCursorJobCount++;
}
}
if (halfBusyCursorState != null) {
// incrementBusy(Job, boolean) has been called before
if (useHalfBusyCursor && halfBusyCursorState == Boolean.FALSE) {
// need to update cursor without changing busy count:
synchronized (busyLock) {
updateJob.setBusy(true);
}
if (PlatformUI.isWorkbenchRunning()) {
updateJob.schedule(100);
} else {
updateJob.cancel();
}
}
return;
}
incrementBusy();
}
@Override
public void warnOfContentChange() {
MPart part = site.getModel();
if (!part.getTags().contains(CSSConstants.CSS_CONTENT_CHANGE_CLASS)) {
part.getTags().add(CSSConstants.CSS_CONTENT_CHANGE_CLASS);
}
}
@Override
public void showInDialog(Shell shell, Job job) {
getWorkbenchProgressService().showInDialog(shell, job);
}
/**
* Get the progress service for the workbnech,
*
* @return IProgressService
*/
private IProgressService getWorkbenchProgressService() {
return site.getWorkbenchWindow().getWorkbench().getProgressService();
}
@Override
public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
throws InvocationTargetException, InterruptedException {
getWorkbenchProgressService().run(fork, cancelable, runnable);
}
@Override
public void runInUI(IRunnableContext context, IRunnableWithProgress runnable, ISchedulingRule rule)
throws InvocationTargetException, InterruptedException {
getWorkbenchProgressService().runInUI(context, runnable, rule);
}
@Override
public int getLongOperationTime() {
return getWorkbenchProgressService().getLongOperationTime();
}
@Override
public void registerIconForFamily(ImageDescriptor icon, Object family) {
getWorkbenchProgressService().registerIconForFamily(icon, family);
}
@Override
public Image getIconFor(Job job) {
return getWorkbenchProgressService().getIconFor(job);
}
@Override
public void incrementBusy() {
synchronized (busyLock) {
this.busyCount++;
if (busyCount != 1) {
return;
}
updateJob.setBusy(true);
}
if (PlatformUI.isWorkbenchRunning()) {
updateJob.schedule(100);
} else {
updateJob.cancel();
}
}
@Override
public void decrementBusy() {
synchronized (busyLock) {
Assert.isTrue(busyCount > 0,
"Ignoring unexpected call to IWorkbenchSiteProgressService.decrementBusy(). This might be due to an earlier call to this method."); //$NON-NLS-1$
this.busyCount--;
if (busyCount != 0) {
return;
}
updateJob.setBusy(false);
}
if (PlatformUI.isWorkbenchRunning()) {
updateJob.schedule(100);
} else {
updateJob.cancel();
}
}
/**
* This method is made public only for the tests. Clients should not be using
* this method
*
* @return the updateJob that updates the site
*/
public SiteUpdateJob getUpdateJob() {
return updateJob;
}
/**
* Update UI to show busy state.
*
* @param busy <code>true</code> if we are busy
*/
protected void showBusy(boolean busy) {
MPart part = site.getModel();
if (busy) {
part.getTags().add(CSSConstants.CSS_BUSY_CLASS);
} else {
part.getTags().remove(CSSConstants.CSS_BUSY_CLASS);
}
}
}