diff --git a/bundles/org.eclipse.e4.ui.progress/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.ui.progress/META-INF/MANIFEST.MF
index 56edde6f84e..5b86ba3891d 100644
--- a/bundles/org.eclipse.e4.ui.progress/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.ui.progress/META-INF/MANIFEST.MF
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.e4.ui.progress;singleton:=true
-Bundle-Version: 0.5.200.qualifier
+Bundle-Version: 0.6.0.qualifier
Bundle-Name: %pluginName
Bundle-Vendor: %providerName
Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/IProgressService.java b/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/IProgressService.java
index 4c855db6d03..97a5a211678 100644
--- a/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/IProgressService.java
+++ b/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/IProgressService.java
@@ -141,6 +141,23 @@ public void runInUI(IRunnableContext context,
public void busyCursorWhile(IRunnableWithProgress runnable)
throws InvocationTargetException, InterruptedException;
+ /**
+ * Same as {@link #busyCursorWhile(IRunnableWithProgress)}, but the Cancel
+ * button of the progress dialog is disabled from the start if
+ * cancelable is false. User input is blocked in the
+ * same way in both cases, so prefer a cancelable operation or a
+ * {@link Job} for operations that may take long.
+ *
+ * @param runnable The runnable to execute and show the progress for.
+ * @param cancelable true if the user may cancel the operation,
+ * false if it can't be canceled
+ * @throws InvocationTargetException if executed operation failed
+ * @throws InterruptedException if operation is interrupted
+ * @since 0.6
+ */
+ public void busyCursorWhile(IRunnableWithProgress runnable, boolean cancelable)
+ throws InvocationTargetException, InterruptedException;
+
/**
* This specialization of IRunnableContext#run(boolean, boolean,
* IRunnableWithProgress) might run the runnable asynchronously
diff --git a/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/internal/ProgressServiceImpl.java b/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/internal/ProgressServiceImpl.java
index ee3e456d967..5a20f35f8cb 100644
--- a/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/internal/ProgressServiceImpl.java
+++ b/bundles/org.eclipse.e4.ui.progress/src/org/eclipse/e4/ui/progress/internal/ProgressServiceImpl.java
@@ -122,6 +122,12 @@ public Image getIconFor(Job job) {
@Override
public void busyCursorWhile(final IRunnableWithProgress runnable)
throws InvocationTargetException, InterruptedException {
+ busyCursorWhile(runnable, true);
+ }
+
+ @Override
+ public void busyCursorWhile(final IRunnableWithProgress runnable, final boolean cancelable)
+ throws InvocationTargetException, InterruptedException {
final ProgressMonitorJobsDialog dialog = new ProgressMonitorJobsDialog(
ProgressManagerUtil.getDefaultParent(), this, progressManager,
contentProviderFactory, finishedJobs);
@@ -133,7 +139,7 @@ public void busyCursorWhile(final IRunnableWithProgress runnable)
try {
dialog.setOpenOnRun(false);
setUserInterfaceActive(false);
- dialog.run(true, true, runnable);
+ dialog.run(true, cancelable, runnable);
} catch (InvocationTargetException e1) {
invokes[0] = e1;
} catch (InterruptedException e2) {
@@ -257,13 +263,19 @@ public IStatus getStatus() {
private void busyCursorWhile(Runnable dialogWaitRunnable,
ProgressMonitorJobsDialog dialog) {
// create the job that will open the dialog after a delay
- scheduleProgressMonitorJob(dialog);
- final Display display = getDisplay();
- if (display == null) {
- return;
+ Job showDialogJob = scheduleProgressMonitorJob(dialog);
+ try {
+ final Display display = getDisplay();
+ if (display == null) {
+ return;
+ }
+ // show a busy cursor until the dialog opens
+ BusyIndicator.showWhile(display, dialogWaitRunnable);
+ } finally {
+ // In case the dialog hasn't popped up yet, cancel it so it doesn't pop up after
+ // the operation finishes or unwinds with an exception.
+ showDialogJob.cancel();
}
- // show a busy cursor until the dialog opens
- BusyIndicator.showWhile(display, dialogWaitRunnable);
}
/**
@@ -271,8 +283,9 @@ private void busyCursorWhile(Runnable dialogWaitRunnable,
*
* @param dialog
* the dialog to open
+ * @return the job that was scheduled to open the dialog
*/
- private void scheduleProgressMonitorJob(
+ private Job scheduleProgressMonitorJob(
final ProgressMonitorJobsDialog dialog) {
Job updateJob = UIJob.create(ProgressMessages.ProgressManager_openJobName, monitor -> {
@@ -283,7 +296,7 @@ private void scheduleProgressMonitorJob(
});
updateJob.setSystem(true);
updateJob.schedule(getLongOperationTime());
-
+ return updateJob;
}
/**
diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/ProgressManager.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/ProgressManager.java
index 0116708ad6b..e5d767fb52a 100644
--- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/ProgressManager.java
+++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/ProgressManager.java
@@ -919,6 +919,12 @@ ImageData[] getImageData(URL fileSystemPath, ImageLoader loader) {
@Override
public void busyCursorWhile(final IRunnableWithProgress runnable)
throws InvocationTargetException, InterruptedException {
+ busyCursorWhile(runnable, true);
+ }
+
+ @Override
+ public void busyCursorWhile(final IRunnableWithProgress runnable, final boolean cancelable)
+ throws InvocationTargetException, InterruptedException {
final ProgressMonitorJobsDialog dialog = new ProgressMonitorJobsDialog(ProgressManagerUtil.getDefaultParent());
dialog.setOpenOnRun(false);
final InvocationTargetException[] invokes = new InvocationTargetException[1];
@@ -928,7 +934,7 @@ public void busyCursorWhile(final IRunnableWithProgress runnable)
try {
dialog.setOpenOnRun(false);
setUserInterfaceActive(false);
- dialog.run(true, true, runnable);
+ dialog.run(true, cancelable, runnable);
} catch (InvocationTargetException e1) {
invokes[0] = e1;
} catch (InterruptedException e2) {
diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/WorkbenchSiteProgressService.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/WorkbenchSiteProgressService.java
index 5b4a263a140..ef8688675d8 100644
--- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/WorkbenchSiteProgressService.java
+++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/WorkbenchSiteProgressService.java
@@ -154,6 +154,12 @@ public void busyCursorWhile(IRunnableWithProgress runnable) throws InvocationTar
getWorkbenchProgressService().busyCursorWhile(runnable);
}
+ @Override
+ public void busyCursorWhile(IRunnableWithProgress runnable, boolean cancelable)
+ throws InvocationTargetException, InterruptedException {
+ getWorkbenchProgressService().busyCursorWhile(runnable, cancelable);
+ }
+
@Override
public void schedule(Job job, long delay, boolean useHalfBusyCursor) {
job.addJobChangeListener(getJobChangeListener(useHalfBusyCursor));
diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/progress/IProgressService.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/progress/IProgressService.java
index 32e12d7c908..30c42860e4d 100644
--- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/progress/IProgressService.java
+++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/progress/IProgressService.java
@@ -139,6 +139,23 @@ void runInUI(IRunnableContext context, IRunnableWithProgress runnable, IScheduli
*/
void busyCursorWhile(IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException;
+ /**
+ * Same as {@link #busyCursorWhile(IRunnableWithProgress)}, but the Cancel
+ * button of the progress dialog is disabled from the start if
+ * cancelable is false. User input is blocked in the
+ * same way in both cases, so prefer a cancelable operation or a
+ * {@link Job} for operations that may take long.
+ *
+ * @param runnable The runnable to execute and show the progress for.
+ * @param cancelable true if the user may cancel the operation,
+ * false if it can't be canceled
+ * @throws InvocationTargetException if executed operation failed
+ * @throws InterruptedException if operation is interrupted
+ * @since 3.141
+ */
+ void busyCursorWhile(IRunnableWithProgress runnable, boolean cancelable)
+ throws InvocationTargetException, InterruptedException;
+
/**
* This specialization of IRunnableContext#run(boolean, boolean,
* IRunnableWithProgress) might run the runnable asynchronously if
diff --git a/examples/org.eclipse.e4.ui.examples.job/META-INF/MANIFEST.MF b/examples/org.eclipse.e4.ui.examples.job/META-INF/MANIFEST.MF
index 63bb21a3ce8..e38132c3e7b 100644
--- a/examples/org.eclipse.e4.ui.examples.job/META-INF/MANIFEST.MF
+++ b/examples/org.eclipse.e4.ui.examples.job/META-INF/MANIFEST.MF
@@ -9,7 +9,7 @@ Export-Package: org.eclipse.e4.ui.examples.jobs,
Require-Bundle: org.eclipse.core.resources,
org.eclipse.core.runtime,
org.eclipse.jface.text,
- org.eclipse.e4.ui.progress;bundle-version="0.1.0",
+ org.eclipse.e4.ui.progress;bundle-version="0.6.0",
org.eclipse.swt;bundle-version="3.133.0",
org.eclipse.e4.ui.di,
org.eclipse.jface;bundle-version="3.10.0"
diff --git a/examples/org.eclipse.e4.ui.examples.job/src/org/eclipse/e4/ui/examples/jobs/views/JobsView.java b/examples/org.eclipse.e4.ui.examples.job/src/org/eclipse/e4/ui/examples/jobs/views/JobsView.java
index e1c77ef57f7..37a8ee2cd73 100644
--- a/examples/org.eclipse.e4.ui.examples.job/src/org/eclipse/e4/ui/examples/jobs/views/JobsView.java
+++ b/examples/org.eclipse.e4.ui.examples.job/src/org/eclipse/e4/ui/examples/jobs/views/JobsView.java
@@ -62,6 +62,7 @@ public class JobsView {
private Text quantityField, delayField, rescheduleDelay;
private Button schedulingRuleField;
private Button noPromptField;
+ private Button cancelableField;
Composite parent;
@@ -79,13 +80,14 @@ protected void busyCursorWhile() {
try {
final long duration = getDuration();
final boolean shouldLock = lockField.getSelection();
+ final boolean cancelable = cancelableField.getSelection();
progressService.busyCursorWhile(
monitor -> {
if (shouldLock)
doRunInWorkspace(duration, monitor);
else
doRun(duration, monitor);
- });
+ }, cancelable);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
@@ -201,7 +203,13 @@ public void widgetSelected(SelectionEvent e) {
});
// busy cursor while
- Button busyWhile = new Button(group, SWT.PUSH);
+ Composite busyWhileRow = new Composite(group, SWT.NONE);
+ GridLayout busyWhileLayout = new GridLayout(2, false);
+ busyWhileLayout.marginWidth = 0;
+ busyWhileLayout.marginHeight = 0;
+ busyWhileRow.setLayout(busyWhileLayout);
+ busyWhileRow.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ Button busyWhile = new Button(busyWhileRow, SWT.PUSH);
busyWhile.setText("busyCursorWhile"); //$NON-NLS-1$
busyWhile.setToolTipText("Uses IProgressService.busyCursorWhile"); //$NON-NLS-1$
busyWhile.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
@@ -211,6 +219,10 @@ public void widgetSelected(SelectionEvent e) {
busyCursorWhile();
}
});
+ cancelableField = new Button(busyWhileRow, SWT.CHECK);
+ cancelableField.setText("cancelable"); //$NON-NLS-1$
+ cancelableField.setToolTipText("Whether the Cancel button of busyCursorWhile is enabled"); //$NON-NLS-1$
+ cancelableField.setSelection(true);
// progress monitor dialog with fork=false
Button noFork = new Button(group, SWT.PUSH);
noFork.setText("runInUI"); //$NON-NLS-1$
diff --git a/examples/org.eclipse.ui.examples.job/src/org/eclipse/ui/examples/jobs/views/JobsView.java b/examples/org.eclipse.ui.examples.job/src/org/eclipse/ui/examples/jobs/views/JobsView.java
index 5be6880b0ad..7d9f536e11b 100644
--- a/examples/org.eclipse.ui.examples.job/src/org/eclipse/ui/examples/jobs/views/JobsView.java
+++ b/examples/org.eclipse.ui.examples.job/src/org/eclipse/ui/examples/jobs/views/JobsView.java
@@ -59,18 +59,20 @@ public class JobsView extends ViewPart {
private Text quantityField, delayField, rescheduleDelay;
private Button schedulingRuleField;
private Button noPromptField;
+ private Button cancelableField;
protected void busyCursorWhile() {
try {
final long duration = getDuration();
final boolean shouldLock = lockField.getSelection();
+ final boolean cancelable = cancelableField.getSelection();
PlatformUI.getWorkbench().getProgressService().busyCursorWhile(
monitor -> {
if (shouldLock)
doRunInWorkspace(duration, monitor);
else
doRun(duration, monitor);
- });
+ }, cancelable);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
@@ -188,11 +190,21 @@ private void createPushButtonGroup(Composite parent) {
touch.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
touch.addSelectionListener(SelectionListener.widgetSelectedAdapter(_ -> touchWorkspace()));
// busy cursor while
- Button busyWhile = new Button(group, SWT.PUSH);
+ Composite busyWhileRow = new Composite(group, SWT.NONE);
+ GridLayout busyWhileLayout = new GridLayout(2, false);
+ busyWhileLayout.marginWidth = 0;
+ busyWhileLayout.marginHeight = 0;
+ busyWhileRow.setLayout(busyWhileLayout);
+ busyWhileRow.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ Button busyWhile = new Button(busyWhileRow, SWT.PUSH);
busyWhile.setText("busyCursorWhile"); //$NON-NLS-1$
busyWhile.setToolTipText("Uses IProgressService.busyCursorWhile"); //$NON-NLS-1$
busyWhile.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
busyWhile.addSelectionListener(SelectionListener.widgetSelectedAdapter(_ -> busyCursorWhile()));
+ cancelableField = new Button(busyWhileRow, SWT.CHECK);
+ cancelableField.setText("cancelable"); //$NON-NLS-1$
+ cancelableField.setToolTipText("Whether the Cancel button of busyCursorWhile is enabled"); //$NON-NLS-1$
+ cancelableField.setSelection(true);
// progress monitor dialog with fork=false
Button noFork = new Button(group, SWT.PUSH);
noFork.setText("runInUI"); //$NON-NLS-1$
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/progress/ProgressServiceTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/progress/ProgressServiceTest.java
index c25e2979ded..f7826ad855d 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/progress/ProgressServiceTest.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/progress/ProgressServiceTest.java
@@ -3,16 +3,23 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
@@ -367,6 +374,123 @@ private void assertDialogShown(boolean fork, boolean cancelable, boolean runInBa
expectDialogShown, fork, cancelable, runInBackgroundPref, pumpUiEventsWhileRunning));
}
+ @Test
+ public void testBusyCursorWhile_cancelButtonIsEnabled() throws Exception {
+ assertTrue(isCancelButtonEnabledInBusyCursorDialog(r -> progressService.busyCursorWhile(r)));
+ }
+
+ @Test
+ public void testBusyCursorWhile_cancelable_cancelButtonIsEnabled() throws Exception {
+ assertTrue(isCancelButtonEnabledInBusyCursorDialog(r -> progressService.busyCursorWhile(r, true)));
+ }
+
+ @Test
+ public void testBusyCursorWhile_notCancelable_cancelButtonIsDisabled() throws Exception {
+ assertFalse(isCancelButtonEnabledInBusyCursorDialog(r -> progressService.busyCursorWhile(r, false)));
+ }
+
+ /**
+ * Runs the given {@code busyCursorWhile} call with a runnable that waits for
+ * the progress dialog to open and returns whether the Cancel button of that
+ * dialog was enabled.
+ */
+ private boolean isCancelButtonEnabledInBusyCursorDialog(BusyCursorCall call) throws Exception {
+ Display display = PlatformUI.getWorkbench().getDisplay();
+ Set preexistingShells = new HashSet<>(Arrays.asList(display.getShells()));
+ AtomicReference cancelEnabled = new AtomicReference<>();
+ call.run(monitor -> {
+ // The dialog opens after getLongOperationTime(), the UI thread is pumping
+ // events
+ long deadline = System.currentTimeMillis() + progressService.getLongOperationTime() * 10L;
+ while (cancelEnabled.get() == null && System.currentTimeMillis() < deadline) {
+ display.syncExec(() -> {
+ for (Shell shell : display.getShells()) {
+ if (!shell.isDisposed() && shell.isVisible() && !preexistingShells.contains(shell)) {
+ Button cancel = findCancelButton(shell);
+ if (cancel != null) {
+ cancelEnabled.set(cancel.isEnabled());
+ }
+ }
+ }
+ });
+ Thread.sleep(20);
+ }
+ });
+ assertNotNull(cancelEnabled.get(), "The progress dialog with a Cancel button was not shown");
+ return cancelEnabled.get();
+ }
+
+ @Test
+ public void testBusyCursorWhile_closingDialogIsIgnored() throws Exception {
+ assertClosingDialogIsIgnored(closeBusyCursorDialog(r -> progressService.busyCursorWhile(r)));
+ }
+
+ @Test
+ public void testBusyCursorWhile_cancelable_closingDialogIsIgnored() throws Exception {
+ assertClosingDialogIsIgnored(closeBusyCursorDialog(r -> progressService.busyCursorWhile(r, true)));
+ }
+
+ @Test
+ public void testBusyCursorWhile_notCancelable_closingDialogIsIgnored() throws Exception {
+ assertClosingDialogIsIgnored(closeBusyCursorDialog(r -> progressService.busyCursorWhile(r, false)));
+ }
+
+ private static void assertClosingDialogIsIgnored(CloseResult result) {
+ assertFalse(result.canceled(), "Closing the dialog must not cancel the operation");
+ assertFalse(result.closed(), "The dialog must stay open while the operation runs");
+ }
+
+ private record CloseResult(boolean canceled, boolean closed) {
+ }
+
+ /**
+ * Waits for the progress dialog of the given {@code busyCursorWhile} call,
+ * closes its shell like the close box does and returns what happened.
+ */
+ private CloseResult closeBusyCursorDialog(BusyCursorCall call) throws Exception {
+ Display display = PlatformUI.getWorkbench().getDisplay();
+ Set preexistingShells = new HashSet<>(Arrays.asList(display.getShells()));
+ AtomicReference result = new AtomicReference<>();
+ call.run(monitor -> {
+ long deadline = System.currentTimeMillis() + progressService.getLongOperationTime() * 10L;
+ while (result.get() == null && System.currentTimeMillis() < deadline) {
+ display.syncExec(() -> {
+ for (Shell shell : display.getShells()) {
+ if (!shell.isDisposed() && shell.isVisible() && !preexistingShells.contains(shell)
+ && findCancelButton(shell) != null) {
+ shell.close();
+ boolean closed = shell.isDisposed() || !shell.isVisible();
+ result.set(new CloseResult(monitor.isCanceled(), closed));
+ }
+ }
+ });
+ Thread.sleep(20);
+ }
+ });
+ assertNotNull(result.get(), "The progress dialog with a Cancel button was not shown");
+ return result.get();
+ }
+
+ private static Button findCancelButton(Composite parent) {
+ for (Control child : parent.getChildren()) {
+ if (child instanceof Button button
+ && Integer.valueOf(IDialogConstants.CANCEL_ID).equals(button.getData())) {
+ return button;
+ }
+ if (child instanceof Composite composite) {
+ Button found = findCancelButton(composite);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ interface BusyCursorCall {
+ void run(IRunnableWithProgress runnable) throws Exception;
+ }
+
private static void runInBackground(boolean value) {
WorkbenchPlugin.getDefault().getPreferenceStore().setValue(IPreferenceConstants.RUN_IN_BACKGROUND, value);
}