blob: 06cf563418653248bae39096e90b158e8580ac0c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2016 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 Corporation - initial API and implementation
* Teddy Walker <teddy.walker@googlemail.com>
* - Fix for Bug 151204 [Progress] Blocked status of jobs are not applied/reported
* Lars Vogel <Lars.Vogel@gmail.com> - Bug 422040
* Terry Parker - Bug 454633, Report the cumulative error status of job groups in the ProgressManager
*******************************************************************************/
package org.eclipse.ui.internal.progress;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.QualifiedName;
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.IJobManager;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.core.runtime.jobs.ProgressProvider;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.Throttler;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.IPreferenceConstants;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
import org.eclipse.ui.internal.dialogs.WorkbenchDialogBlockedHandler;
import org.eclipse.ui.progress.IProgressConstants;
import org.eclipse.ui.progress.IProgressService;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.statushandlers.StatusAdapter;
import org.eclipse.ui.statushandlers.StatusManager;
/**
* JobProgressManager provides the progress monitor to the job manager and
* informs any ProgressContentProviders of changes.
*/
public class ProgressManager extends ProgressProvider implements IProgressService {
/**
* A property to determine if the job was run in the dialog. Kept for backwards
* compatibility.
*
* @deprecated use IProgressConstants#PROPERTY_IN_DIALOG instead
*/
@Deprecated
public static final QualifiedName PROPERTY_IN_DIALOG = IProgressConstants.PROPERTY_IN_DIALOG;
private static final String ERROR_JOB = "errorstate.png"; //$NON-NLS-1$
static final String ERROR_JOB_KEY = "ERROR_JOB"; //$NON-NLS-1$
private static ProgressManager singleton;
final private Set<Job> managedJobs = ConcurrentHashMap.newKeySet();
final private Map<Object, Collection<IJobBusyListener>> familyListeners = Collections
.synchronizedMap(new LinkedHashMap<>());
// list of IJobProgressManagerListener
private ListenerList<IJobProgressManagerListener> listeners = new ListenerList<>();
final IJobChangeListener changeListener;
static final String PROGRESS_VIEW_NAME = "org.eclipse.ui.views.ProgressView"; //$NON-NLS-1$
static final String PROGRESS_FOLDER = "$nl$/icons/full/progress/"; //$NON-NLS-1$
private static final String SLEEPING_JOB = "sleeping.png"; //$NON-NLS-1$
private static final String WAITING_JOB = "waiting.png"; //$NON-NLS-1$
private static final String BLOCKED_JOB = "lockedstate.png"; //$NON-NLS-1$
/**
* The key for the sleeping job icon.
*/
public static final String SLEEPING_JOB_KEY = "SLEEPING_JOB"; //$NON-NLS-1$
/**
* The key for the waiting job icon.
*/
public static final String WAITING_JOB_KEY = "WAITING_JOB"; //$NON-NLS-1$
/**
* The key for the locked job icon.
*/
public static final String BLOCKED_JOB_KEY = "LOCKED_JOB"; //$NON-NLS-1$
final Map<Job, JobMonitor> runnableMonitors = new HashMap<>();
// A table that maps families to keys in the Jface image table
private Hashtable<Object, String> imageKeyTable = new Hashtable<>();
/**
* Lock object for synchronizing updates of {@code pendingJobUpdates},
* {@code pendingGroupUpdates}, {@code pendingJobRemoval},
* {@code pendingGroupRemoval} and {@code pendingJobAddition}.
*/
private final Object pendingUpdatesMutex = new Object();
/**
* Modification guarded by {@link #pendingUpdatesMutex}.
*/
private Map<JobInfo, Set<IJobProgressManagerListener>> pendingJobUpdates = new LinkedHashMap<>();
/**
* Modification guarded by {@link #pendingUpdatesMutex}.
*/
private Set<GroupInfo> pendingGroupUpdates = new LinkedHashSet<>();
/**
* Modification guarded by {@link #pendingUpdatesMutex}.
*/
private Map<JobInfo, Set<IJobProgressManagerListener>> pendingJobRemoval = new LinkedHashMap<>();
/**
* Modification guarded by {@link #pendingUpdatesMutex}.
*/
private Set<GroupInfo> pendingGroupRemoval = new LinkedHashSet<>();
/**
* Modification guarded by {@link #pendingUpdatesMutex}.
*/
private Map<JobInfo, Set<IJobProgressManagerListener>> pendingJobAddition = new LinkedHashMap<>();
private static final String IMAGE_KEY = "org.eclipse.ui.progress.images"; //$NON-NLS-1$
private final Throttler uiRefreshThrottler;
/**
* Returns the progress manager currently in use.
*
* @return JobProgressManager
*/
public static ProgressManager getInstance() {
if (singleton == null) {
singleton = new ProgressManager();
}
return singleton;
}
/**
* Shuts down the singleton if there is one.
*/
public static void shutdownProgressManager() {
if (singleton == null) {
return;
}
singleton.shutdown();
}
/**
* The JobMonitor is the inner class that handles the IProgressMonitor
* integration with the ProgressMonitor.
*/
public class JobMonitor implements IProgressMonitorWithBlocking {
Job job;
JobInfo info;
String currentTaskName;
Set<IProgressMonitorWithBlocking> monitors = Collections.emptySet();
/**
* Creates a monitor on the supplied job.
*
* @param newJob the job this monitor is created for
* @param jobInfo the info object for the job
*/
JobMonitor(Job newJob, JobInfo jobInfo) {
job = newJob;
info = jobInfo;
}
/**
* Get the monitored job's information.
*
* @return job info
*/
public JobInfo getJobInfo() {
return info;
}
/**
* Adds monitor as another monitor that
*
* @param monitor the listening monitor to add
*/
public void addProgressListener(IProgressMonitorWithBlocking monitor) {
Assert.isNotNull(monitor);
Set<IProgressMonitorWithBlocking> newSet = new LinkedHashSet<>(monitors);
newSet.add(monitor);
this.monitors = Collections.unmodifiableSet(newSet);
Optional<TaskInfo> optionalInfo = info.getTaskInfo();
if (optionalInfo.isPresent()) {
TaskInfo currentTask = optionalInfo.get();
monitor.beginTask(currentTaskName, currentTask.totalWork);
monitor.internalWorked(currentTask.preWork);
}
}
/**
* Removes progress listener.
*
* @param monitor the listening monitor to remove
*/
public void removeProgresListener(IProgressMonitorWithBlocking monitor) {
Set<IProgressMonitorWithBlocking> newSet = new LinkedHashSet<>(monitors);
newSet.remove(monitor);
this.monitors = Collections.unmodifiableSet(newSet);
}
@Override
public void beginTask(String taskName, int totalWork) {
info.beginTask(taskName, totalWork);
refreshJobInfo(info);
currentTaskName = taskName;
monitors.forEach(listener -> listener.beginTask(taskName, totalWork));
}
@Override
public void done() {
info.clearTaskInfo();
info.clearChildren();
monitors.forEach(IProgressMonitorWithBlocking::done);
}
@Override
public void internalWorked(double work) {
if (info.getTaskInfo().isPresent()) {
info.addWork(work);
refreshJobInfo(info);
}
monitors.forEach(listener -> listener.internalWorked(work));
}
@Override
public boolean isCanceled() {
return info.isCanceled();
}
@Override
public void setCanceled(boolean value) {
// Don't bother canceling twice.
if (value && !info.isCanceled()) {
info.cancel();
// Only inform the first time
monitors.forEach(listener -> listener.setCanceled(value));
}
}
@Override
public void setTaskName(String taskName) {
if (info.getTaskInfo().isPresent()) {
info.setTaskName(taskName);
} else {
beginTask(taskName, 100);
return;
}
info.clearChildren();
refreshJobInfo(info);
currentTaskName = taskName;
monitors.forEach(listener -> listener.setTaskName(taskName));
}
@Override
public void subTask(String name) {
if (name == null) {
return;
}
info.clearChildren();
info.addSubTask(name);
refreshJobInfo(info);
monitors.forEach(listener -> listener.subTask(name));
}
@Override
public void worked(int work) {
internalWorked(work);
}
@Override
public void clearBlocked() {
info.setBlockedStatus(null);
refreshJobInfo(info);
monitors.forEach(IProgressMonitorWithBlocking::clearBlocked);
}
@Override
public void setBlocked(IStatus reason) {
info.setBlockedStatus(reason);
refreshJobInfo(info);
monitors.forEach(listener -> listener.setBlocked(reason));
}
}
/**
* Creates a new instance of the receiver.
*/
ProgressManager() {
Dialog.setBlockedHandler(new WorkbenchDialogBlockedHandler());
setUpImages();
uiRefreshThrottler = new Throttler(Display.getDefault(), Duration.ofMillis(100), this::notifyListeners);
changeListener = createChangeListener();
Job.getJobManager().setProgressProvider(this);
Job.getJobManager().addJobChangeListener(this.changeListener);
}
/**
* Send pending notifications to listeners.
*/
/* Visible for testing */ public void notifyListeners() {
Set<GroupInfo> localPendingGroupUpdates, localPendingGroupRemoval;
Map<JobInfo, Set<IJobProgressManagerListener>> localPendingJobUpdates, localPendingJobAddition,
localPendingJobRemoval;
synchronized (pendingUpdatesMutex) {
localPendingJobUpdates = pendingJobUpdates;
pendingJobUpdates = new LinkedHashMap<>();
localPendingGroupUpdates = pendingGroupUpdates;
pendingGroupUpdates = new LinkedHashSet<>();
localPendingJobRemoval = pendingJobRemoval;
pendingJobRemoval = new LinkedHashMap<>();
localPendingGroupRemoval = pendingGroupRemoval;
pendingGroupRemoval = new LinkedHashSet<>();
localPendingJobAddition = pendingJobAddition;
pendingJobAddition = new LinkedHashMap<>();
}
localPendingJobAddition.entrySet().forEach(e -> e.getValue().forEach(listener -> listener.addJob(e.getKey())));
// Adds all non null JobInfo#getGroupInfo to the list of groups to
// be refreshed
localPendingJobUpdates.entrySet().stream().map(e -> e.getKey().getGroupInfo()).filter(Objects::nonNull)
.forEach(localPendingGroupUpdates::add);
localPendingJobUpdates.entrySet()
.forEach(e -> e.getValue().forEach(listener -> listener.refreshJobInfo(e.getKey())));
// refresh groups
localPendingGroupUpdates.forEach(groupInfo -> listeners.forEach(listener -> listener.refreshGroup(groupInfo)));
localPendingJobRemoval.entrySet()
.forEach(e -> e.getValue().forEach(listener -> listener.removeJob(e.getKey())));
localPendingGroupRemoval.forEach(group -> {
listeners.forEach(listener -> listener.removeGroup(group));
});
}
private void setUpImages() {
URL iconsRoot = ProgressManagerUtil.getIconsRoot();
try {
setUpImage(iconsRoot, SLEEPING_JOB, SLEEPING_JOB_KEY);
setUpImage(iconsRoot, WAITING_JOB, WAITING_JOB_KEY);
setUpImage(iconsRoot, BLOCKED_JOB, BLOCKED_JOB_KEY);
ImageDescriptor errorImage = ImageDescriptor.createFromURL(new URL(iconsRoot, ERROR_JOB));
JFaceResources.getImageRegistry().put(ERROR_JOB_KEY, errorImage);
} catch (MalformedURLException e) {
ProgressManagerUtil.logException(e);
}
}
/**
* Create and return the IJobChangeListener registered with the Job manager.
*
* @return the created IJobChangeListener
*/
private IJobChangeListener createChangeListener() {
return new JobChangeAdapter() {
@Override
public void aboutToRun(IJobChangeEvent event) {
JobInfo info = progressFor(event.getJob()).getJobInfo();
refreshJobInfo(info);
Iterator<IJobBusyListener> startListeners = busyListenersForJob(event.getJob()).iterator();
while (startListeners.hasNext()) {
IJobBusyListener next = startListeners.next();
next.incrementBusy(event.getJob());
}
}
@Override
public void done(IJobChangeEvent event) {
if (!PlatformUI.isWorkbenchRunning()) {
return;
}
Iterator<IJobBusyListener> startListeners = busyListenersForJob(event.getJob()).iterator();
while (startListeners.hasNext()) {
IJobBusyListener next = startListeners.next();
next.decrementBusy(event.getJob());
}
final JobInfo info = removeJob(event.getJob());
/*
* Only report severe errors to the StatusManager if the error is not part of a
* job group, or if the job is the last job in a job group. For job groups, the
* JobManager accumulates the status of jobs belonging to the group, suppresses
* the status reporting of the individual jobs and reports a single MultiStatus
* for the group, so mirror that behavior here.
*/
StatusAdapter statusAdapter = null;
if (event.getJobGroupResult() != null && event.getJobGroupResult().getSeverity() == IStatus.ERROR) {
statusAdapter = new StatusAdapter(event.getJobGroupResult());
} else if (event.getResult() != null && event.getResult().getSeverity() == IStatus.ERROR
&& (event.getJob() == null || event.getJob().getJobGroup() == null)) {
statusAdapter = new StatusAdapter(event.getResult());
statusAdapter.addAdapter(Job.class, event.getJob());
}
if (statusAdapter != null) {
if (event.getJob()
.getProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY) == Boolean.TRUE) {
statusAdapter.setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
StatusAdapterHelper.getInstance().putStatusAdapter(info, statusAdapter);
}
StatusManager.getManager().handle(statusAdapter, StatusManager.SHOW);
}
}
@Override
public void scheduled(IJobChangeEvent event) {
updateFor(event);
if (event.getJob().isUser()) {
boolean noDialog = shouldRunInBackground();
if (!noDialog) {
final IJobChangeEvent finalEvent = event;
WorkbenchJob showJob = new WorkbenchJob(ProgressMessages.ProgressManager_showInDialogName) {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
showInDialog(null, finalEvent.getJob());
return Status.OK_STATUS;
}
};
showJob.setSystem(true);
showJob.schedule();
return;
}
}
}
/**
* Updates the listeners for the receiver for the event.
*
* @param event
*/
private void updateFor(IJobChangeEvent event) {
if (managedJobs.contains(event.getJob())) {
refreshJobInfo(progressFor(event.getJob()).getJobInfo());
} else {
addJobInfo(progressFor(event.getJob()).getJobInfo());
}
}
@Override
public void awake(IJobChangeEvent event) {
updateFor(event);
}
@Override
public void sleeping(IJobChangeEvent event) {
if (managedJobs.contains(event.getJob()))// Are we showing this?
sleepJobInfo(progressFor(event.getJob()).getJobInfo());
}
};
}
/**
* The job in JobInfo is now sleeping. Refreshes it if we are showing it,
* removes it if not.
*
* @param info the job going to sleep
*/
protected void sleepJobInfo(JobInfo info) {
GroupInfo group = info.getGroupInfo();
if (group != null) {
sleepGroup(group, info);
}
for (IJobProgressManagerListener listener : listeners) {
// Is this one the user never sees?
if (isNeverDisplaying(info.getJob(), listener.showsDebug()))
continue;
if (listener.showsDebug()) {
listener.refreshJobInfo(info);
} else {
listener.removeJob(info);
}
}
}
/**
* Refreshes the group when info is sleeping.
*
* @param group
*/
private void sleepGroup(GroupInfo group, JobInfo info) {
for (IJobProgressManagerListener listener : listeners) {
if (isNeverDisplaying(info.getJob(), listener.showsDebug()))
continue;
if (listener.showsDebug() || group.isActive()) {
listener.refreshGroup(group);
} else {
listener.removeGroup(group);
}
}
}
/**
* Sets up the image in the image registry.
*
* @param iconsRoot
* @param fileName
* @param key
* @throws MalformedURLException
*/
private void setUpImage(URL iconsRoot, String fileName, String key) throws MalformedURLException {
JFaceResources.getImageRegistry().put(key, ImageDescriptor.createFromURL(new URL(iconsRoot, fileName)));
}
@Override
public IProgressMonitor createMonitor(Job job) {
return progressFor(job);
}
@Override
public IProgressMonitor getDefaultMonitor() {
// only need a default monitor for operations the UI thread
// and only if there is a display
Display display;
if (PlatformUI.isWorkbenchRunning() && !PlatformUI.getWorkbench().isStarting()) {
display = PlatformUI.getWorkbench().getDisplay();
if (!display.isDisposed() && (display.getThread() == Thread.currentThread())) {
return new EventLoopProgressMonitor(new NullProgressMonitor());
}
}
return super.getDefaultMonitor();
}
/**
* Returns a monitor for the job. Checks if we cached a monitor for this job
* previously for a long operation timeout check.
*
* @param job the job to (progress) monitor
* @return a monitor for the job. Might be an existing monitor for this job.
*/
public JobMonitor progressFor(Job job) {
synchronized (runnableMonitors) {
return runnableMonitors.computeIfAbsent(job, j -> new JobMonitor(j, new JobInfo(j)));
}
}
/**
* Adds an IJobProgressManagerListener to listen to the changes.
*
* @param listener
*/
void addListener(IJobProgressManagerListener listener) {
listeners.add(listener);
}
/**
* Removes the supplied IJobProgressManagerListener from the list of listeners.
*
* @param listener
*/
void removeListener(IJobProgressManagerListener listener) {
listeners.remove(listener);
}
/**
* Refreshes the IJobProgressManagerListeners as a result of a change in info.
*
* @param info the updated job info
*/
public void refreshJobInfo(JobInfo info) {
synchronized (pendingUpdatesMutex) {
Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug());
rememberListenersForJob(info, pendingJobUpdates, predicate);
}
uiRefreshThrottler.throttledExec();
}
/**
* Refreshes the IJobProgressManagerListeners as a result of a change in info.
*
* @param info the updated job group
*/
public void refreshGroup(GroupInfo info) {
synchronized (pendingUpdatesMutex) {
pendingGroupUpdates.add(info);
}
uiRefreshThrottler.throttledExec();
}
/**
* Refreshes the content providers as a result of a deletion of job.
*
* @param job the job to remove information about
* @return the removed job info
*/
public JobInfo removeJob(Job job) {
JobInfo info;
synchronized (runnableMonitors) {
info = progressFor(job).getJobInfo();
managedJobs.remove(job);
synchronized (pendingUpdatesMutex) {
Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug());
rememberListenersForJob(info, pendingJobRemoval, predicate);
}
runnableMonitors.remove(job);
}
uiRefreshThrottler.throttledExec();
return info;
}
/**
* Refreshes the content providers as a result of a deletion of info.
*
* @param info the info to remove
* @deprecated use the more thread safe {@link #removeJob(Job)} instead. See bug
* 558655.
*/
@Deprecated
public void removeJobInfo(JobInfo info) {
removeJob(info.getJob());
}
/**
* Removes the group from the roots and inform the listeners.
*
* @param group GroupInfo
*/
public void removeGroup(GroupInfo group) {
synchronized (pendingUpdatesMutex) {
pendingGroupRemoval.add(group);
}
uiRefreshThrottler.throttledExec();
}
/**
* Refreshes the content providers as a result of an addition of info.
*
* @param info the added job info
*/
public void addJobInfo(JobInfo info) {
GroupInfo group = info.getGroupInfo();
if (group != null) {
refreshGroup(group);
}
managedJobs.add(info.getJob());
synchronized (pendingUpdatesMutex) {
Predicate<IJobProgressManagerListener> predicate = listener -> !isCurrentDisplaying(info.getJob(), listener.showsDebug());
rememberListenersForJob(info, pendingJobAddition, predicate);
}
uiRefreshThrottler.throttledExec();
}
private void rememberListenersForJob(JobInfo info, Map<JobInfo, Set<IJobProgressManagerListener>> listenersMap, Predicate<IJobProgressManagerListener> predicate) {
Set<IJobProgressManagerListener> localListeners = listenersMap.computeIfAbsent(info,
k -> new LinkedHashSet<>());
listeners.stream().filter(predicate).forEach(localListeners::add);
}
/**
* Returns whether or not this job is currently displayable.
*
* @param job
* @param debug if the listener is in debug mode
* @return boolean <code>true</code> if the job is not displayed
*/
boolean isCurrentDisplaying(Job job, boolean debug) {
return isNeverDisplaying(job, debug) || job.getState() == Job.SLEEPING;
}
/**
* Returns whether or not we even display this job with debug mode set to debug.
*
* @param job
* @param debug
* @return boolean
*/
boolean isNeverDisplaying(Job job, boolean debug) {
if (debug)
return false;
return job.isSystem();
}
/**
* Returns the current job infos filtered on debug mode.
*
* @param debug if the listener is in debug mode
* @return JobInfo[]
*/
public JobInfo[] getJobInfos(boolean debug) {
return managedJobs.stream().filter(job -> !isCurrentDisplaying(job, debug))
.map(job -> progressFor(job).getJobInfo()).toArray(JobInfo[]::new);
}
/**
* Returns the current root elements filtered on the debug mode.
*
* @param debug if the listener is in debug mode
* @return JobTreeElement[]
*/
public JobTreeElement[] getRootElements(boolean debug) {
return managedJobs.stream().filter(job -> !isCurrentDisplaying(job, debug)).map(job -> {
JobInfo jobInfo = progressFor(job).getJobInfo();
GroupInfo group = jobInfo.getGroupInfo();
if (group == null) {
return jobInfo;
}
return group;
}).distinct().toArray(JobTreeElement[]::new);
}
/**
* Returns whether or not there are any jobs being displayed.
*
* @return boolean
*/
public boolean hasJobInfos() {
return !managedJobs.isEmpty();
}
/**
* Returns the image descriptor with the given relative path.
*
* @param source
* @return Image
*/
Image getImage(ImageData source) {
ImageData mask = source.getTransparencyMask();
return new Image(null, source, mask);
}
/**
* Returns the image descriptor with the given relative path.
*
* @param fileSystemPath The URL for the file system to the image.
* @param loader the loader used to get this data
* @return ImageData[]
*/
ImageData[] getImageData(URL fileSystemPath, ImageLoader loader) {
try {
ImageData[] result;
try (InputStream stream = fileSystemPath.openStream()) {
result = loader.load(stream);
}
return result;
} catch (IOException exception) {
ProgressManagerUtil.logException(exception);
return null;
}
}
@Override
public void busyCursorWhile(final IRunnableWithProgress runnable)
throws InvocationTargetException, InterruptedException {
final ProgressMonitorJobsDialog dialog = new ProgressMonitorJobsDialog(ProgressManagerUtil.getDefaultParent());
dialog.setOpenOnRun(false);
final InvocationTargetException[] invokes = new InvocationTargetException[1];
final InterruptedException[] interrupt = new InterruptedException[1];
// Show a busy cursor until the dialog opens.
Runnable dialogWaitRunnable = () -> {
try {
dialog.setOpenOnRun(false);
setUserInterfaceActive(false);
dialog.run(true, true, runnable);
} catch (InvocationTargetException e1) {
invokes[0] = e1;
} catch (InterruptedException e2) {
interrupt[0] = e2;
} finally {
setUserInterfaceActive(true);
}
};
busyCursorWhile(dialogWaitRunnable, dialog);
if (invokes[0] != null) {
throw invokes[0];
}
if (interrupt[0] != null) {
throw interrupt[0];
}
}
/**
* Shows the busy cursor while the runnable is running. Schedule a job to
* replace it with a progress dialog.
*
* @param dialogWaitRunnable
* @param dialog
*/
private void busyCursorWhile(Runnable dialogWaitRunnable, ProgressMonitorJobsDialog dialog) {
// Create the job that will open the dialog after a delay.
scheduleProgressMonitorJob(dialog);
final Display display = PlatformUI.getWorkbench().getDisplay();
if (display == null) {
return;
}
// Show a busy cursor until the dialog opens.
BusyIndicator.showWhile(display, dialogWaitRunnable);
}
/**
* Schedules the job that will open the progress monitor dialog.
*
* @param dialog the dialog to open
*/
private void scheduleProgressMonitorJob(final ProgressMonitorJobsDialog dialog) {
final WorkbenchJob updateJob = new WorkbenchJob(ProgressMessages.ProgressManager_openJobName) {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
setUserInterfaceActive(true);
if (ProgressManagerUtil.safeToOpen(dialog, null)) {
dialog.open();
}
return Status.OK_STATUS;
}
};
updateJob.setSystem(true);
updateJob.schedule(getLongOperationTime());
}
/**
* Shuts down the receiver.
*/
private void shutdown() {
listeners.clear();
Job.getJobManager().setProgressProvider(null);
Job.getJobManager().removeJobChangeListener(this.changeListener);
}
@Override
public IProgressMonitor createProgressGroup() {
return new GroupInfo();
}
@Override
public IProgressMonitor createMonitor(Job job, IProgressMonitor group, int ticks) {
JobMonitor monitor = progressFor(job);
if (group instanceof GroupInfo) {
GroupInfo groupInfo = (GroupInfo) group;
JobInfo jobInfo = monitor.getJobInfo();
jobInfo.setGroupInfo(groupInfo);
jobInfo.setTicks(ticks);
groupInfo.addJobInfo(jobInfo);
}
return monitor;
}
/**
* Adds the listener to the family.
*
* @param family
* @param listener
*/
void addListenerToFamily(Object family, IJobBusyListener listener) {
synchronized (familyListeners) {
Collection<IJobBusyListener> currentListeners = familyListeners.get(family);
if (currentListeners == null) {
currentListeners = new LinkedHashSet<>();
familyListeners.put(family, currentListeners);
}
currentListeners.add(listener);
}
}
/**
* Removes the listener from all families.
*
* @param listener
*/
void removeListener(IJobBusyListener listener) {
synchronized (familyListeners) {
Iterator<Collection<IJobBusyListener>> familyListeners = this.familyListeners.values().iterator();
while (familyListeners.hasNext()) {
Collection<IJobBusyListener> currentListeners = familyListeners.next();
currentListeners.remove(listener);
// Remove any empty listeners.
if (currentListeners.isEmpty()) {
familyListeners.remove();
}
}
}
}
/**
* Returns the listeners for the job.
*
* @param job
* @return Collection of IJobBusyListener
*/
private Collection<IJobBusyListener> busyListenersForJob(Job job) {
if (job.isSystem()) {
return Collections.emptyList();
}
synchronized (familyListeners) {
if (familyListeners.isEmpty()) {
return Collections.emptyList();
}
Collection<IJobBusyListener> returnValue = new LinkedHashSet<>();
for (Entry<Object, Collection<IJobBusyListener>> entry : familyListeners.entrySet()) {
if (job.belongsTo(entry.getKey())) {
Collection<IJobBusyListener> currentListeners = entry.getValue();
returnValue.addAll(currentListeners);
}
}
return returnValue;
}
}
@Override
public void showInDialog(Shell shell, Job job) {
if (shouldRunInBackground()) {
return;
}
final ProgressMonitorFocusJobDialog dialog = new ProgressMonitorFocusJobDialog(shell);
dialog.show(job, shell);
}
@Override
public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
throws InvocationTargetException, InterruptedException {
if (!fork || !cancelable) {
// Backward compatible code.
final ProgressMonitorJobsDialog dialog = new ProgressMonitorJobsDialog(null);
dialog.run(fork, cancelable, runnable);
return;
}
busyCursorWhile(runnable);
}
@Override
public void runInUI(final IRunnableContext context, final IRunnableWithProgress runnable,
final ISchedulingRule rule) throws InvocationTargetException, InterruptedException {
final RunnableWithStatus runnableWithStatus = new RunnableWithStatus(context, runnable, rule);
final Display display = Display.getDefault();
display.syncExec(() -> BusyIndicator.showWhile(display, runnableWithStatus));
IStatus status = runnableWithStatus.getStatus();
if (!status.isOK()) {
Throwable exception = status.getException();
if (exception instanceof InvocationTargetException) {
throw (InvocationTargetException) exception;
} else if (exception instanceof InterruptedException) {
throw (InterruptedException) exception;
} else { // should be OperationCanceledException
throw new InterruptedException(exception.getMessage());
}
}
}
@Override
public int getLongOperationTime() {
return 800;
}
@Override
public void registerIconForFamily(ImageDescriptor icon, Object family) {
String key = IMAGE_KEY + imageKeyTable.size();
imageKeyTable.put(family, key);
ImageRegistry registry = JFaceResources.getImageRegistry();
// Avoid registering twice.
if (registry.getDescriptor(key) == null) {
registry.put(key, icon);
}
}
@Override
public Image getIconFor(Job job) {
Enumeration<Object> families = imageKeyTable.keys();
while (families.hasMoreElements()) {
Object next = families.nextElement();
if (job.belongsTo(next)) {
return JFaceResources.getImageRegistry().get(imageKeyTable.get(next));
}
}
return null;
}
/**
* Iterates through all of the windows and set them to be disabled or enabled as
* appropriate.
*
* @param active the state the windows will be set to
*/
private void setUserInterfaceActive(boolean active) {
IWorkbench workbench = PlatformUI.getWorkbench();
Shell[] shells = workbench.getDisplay().getShells();
if (active) {
for (Shell shell : shells) {
if (!shell.isDisposed()) {
shell.setEnabled(active);
}
}
} else {
// Deactivate shells in reverse order.
for (int i = shells.length - 1; i >= 0; i--) {
if (!shells[i].isDisposed()) {
shells[i].setEnabled(active);
}
}
}
}
/**
* Checks the if the job should be removed from the list as it may be stale.
*
* @param job
* @return boolean
*/
boolean checkForStaleness(Job job) {
if (job.getState() == Job.NONE) {
removeJob(job);
return true;
}
return false;
}
/**
* Returns whether or not dialogs should be run in the background
*
* @return <code>true</code> if the dialog should not be shown
*/
private boolean shouldRunInBackground() {
return WorkbenchPlugin.getDefault().getPreferenceStore().getBoolean(IPreferenceConstants.RUN_IN_BACKGROUND);
}
/**
* Sets whether or not the ProgressViewUpdater should show system jobs.
*
* @param showSystem <code>true</code> to show system jobs
*/
public void setShowSystemJobs(boolean showSystem) {
ProgressViewUpdater updater = ProgressViewUpdater.getSingleton();
updater.debug = showSystem;
updater.refreshAll();
}
private static class RunnableWithStatus implements Runnable {
IStatus status = Status.OK_STATUS;
private final IRunnableContext context;
private final IRunnableWithProgress runnable;
private final ISchedulingRule rule;
public RunnableWithStatus(IRunnableContext context, IRunnableWithProgress runnable, ISchedulingRule rule) {
this.context = context;
this.runnable = runnable;
this.rule = rule;
}
@Override
public void run() {
IJobManager manager = Job.getJobManager();
try {
manager.beginRule(rule, getEventLoopMonitor());
context.run(false, false, runnable);
} catch (InvocationTargetException | InterruptedException | OperationCanceledException e) {
status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, e.getMessage(), e);
} finally {
manager.endRule(rule);
}
}
/**
* Returns a progress monitor that forwards to an event loop monitor. Overrides
* #setBlocked() so that we always open the blocked dialog.
*
* @return the monitor on the event loop
*/
private IProgressMonitor getEventLoopMonitor() {
if (PlatformUI.getWorkbench().isStarting())
return new NullProgressMonitor();
return new EventLoopProgressMonitor(new NullProgressMonitor()) {
@Override
public void setBlocked(IStatus reason) {
// Set a shell to open with as we want to create this even
// if there is a modal shell.
Dialog.getBlockedHandler().showBlocked(ProgressManagerUtil.getDefaultParent(), this, reason,
getTaskName());
}
};
}
public IStatus getStatus() {
return status;
}
}
}