| /******************************************************************************* |
| * 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); |
| } |
| } |
| } |