Bug 495007 - Do not block UI in Importer when scanning folder

This basically allows to report operation from a Job inside the usual
progress monitor of the WizardDialog.

Change-Id: I5221484c6eda5731bd39c1dd5bdb0c3a4546b3da
Signed-off-by: Mickael Istria <mistria@redhat.com>
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/wizard/ProgressMonitorPart.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/wizard/ProgressMonitorPart.java
index 79567e0..278b9d3 100644
--- a/bundles/org.eclipse.jface/src/org/eclipse/jface/wizard/ProgressMonitorPart.java
+++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/wizard/ProgressMonitorPart.java
@@ -157,10 +157,12 @@
         fTaskName = name;
         fSubTaskName = ""; //$NON-NLS-1$
         updateLabel();
-        if (totalWork == IProgressMonitor.UNKNOWN || totalWork == 0) {
-            fProgressIndicator.beginAnimatedTask();
-        } else {
-            fProgressIndicator.beginTask(totalWork);
+		if (!fProgressIndicator.isDisposed()) {
+			if (totalWork == IProgressMonitor.UNKNOWN || totalWork == 0) {
+				fProgressIndicator.beginAnimatedTask();
+			} else {
+				fProgressIndicator.beginTask(totalWork);
+			}
         }
         if (fToolBar != null && !fToolBar.isDisposed()) {
         	fToolBar.setVisible(true);
@@ -279,7 +281,9 @@
 
     @Override
 	public void internalWorked(double work) {
-        fProgressIndicator.worked(work);
+		if (!fProgressIndicator.isDisposed()) {
+			fProgressIndicator.worked(work);
+		}
     }
 
     @Override
@@ -333,6 +337,9 @@
      * Updates the label with the current task and subtask names.
      */
     protected void updateLabel() {
+		if (fLabel.isDisposed() || fLabel.isAutoDirection()) {
+			return;
+		}
         if (blockedStatus == null) {
             String text = taskLabel();
             fLabel.setText(text);
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java
index e3f632a..e38056f 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java
@@ -185,6 +185,8 @@
 	public static String SmartImportProposals_folder;
 	public static String SmartImportProposals_importAs;
 	public static String SmartImportProposals_hideExistingProjects;
+	public static String SmartImportProposals_inspecitionCanceled;
+	public static String SmartImportProposals_errorWhileInspecting;
 
 	public static String SmartImportReport_importedProjects;
 	public static String SmartImportReport_importedProjectsWithCount;
@@ -202,8 +204,6 @@
 	public static String SmartImportJob_inspecting;
 	public static String SmartImportJob_importingProjectIntoWorkspace;
 
-
-
 	static {
 		// load message values from bundle file
 		NLS.initializeMessages(BUNDLE_NAME, DataTransferMessages.class);
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DelegateProgressMonitorInUIThreadAndPreservingFocus.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DelegateProgressMonitorInUIThreadAndPreservingFocus.java
new file mode 100644
index 0000000..afe8930
--- /dev/null
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DelegateProgressMonitorInUIThreadAndPreservingFocus.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Red Hat Inc., and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Mickael Istria (Red Hat Inc.) - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.ui.internal.wizards.datatransfer;
+
+import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.wizard.ProgressMonitorPart;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * A progress monitor that delegates report to another one wrapping invocations
+ * of delegate methods in Dislay.asyncExec() and ensuring focus is preserved
+ * during the beginTask operation.
+ *
+ * @since 3.12
+ */
+class DelegateProgressMonitorInUIThreadAndPreservingFocus implements IProgressMonitorWithBlocking {
+	private ProgressMonitorPart delegate;
+	private Display display;
+
+	/**
+	 * @param delegate
+	 */
+	public DelegateProgressMonitorInUIThreadAndPreservingFocus(ProgressMonitorPart delegate) {
+		this.delegate = delegate;
+		this.display = delegate.getDisplay();
+	}
+
+	private void inUIThread(Runnable r) {
+		if (display == Display.getCurrent()) {
+			r.run();
+		} else {
+			PlatformUI.getWorkbench().getDisplay().asyncExec(r);
+		}
+	}
+
+	@Override
+	public void worked(int work) {
+		inUIThread(() -> delegate.worked(work));
+	}
+
+	@Override
+	public void subTask(String name) {
+		inUIThread(() -> delegate.subTask(name));
+	}
+
+	@Override
+	public void setTaskName(String name) {
+		inUIThread(() -> delegate.setTaskName(name));
+	}
+
+	@Override
+	public void setCanceled(boolean value) {
+		inUIThread(() -> delegate.setCanceled(value));
+	}
+
+	@Override
+	public boolean isCanceled() {
+		return delegate.isCanceled();
+	}
+
+	@Override
+	public void internalWorked(double work) {
+		inUIThread(() -> delegate.internalWorked(work));
+	}
+
+	@Override
+	public void done() {
+		inUIThread(() -> delegate.done());
+	}
+
+	@Override
+	public void beginTask(String name, int totalWork) {
+		inUIThread(() -> {
+			Point initialSelection = null;
+			Control focusControl = Display.getCurrent().getFocusControl();
+			if (focusControl != null && focusControl instanceof Combo) {
+				initialSelection = ((Combo) focusControl).getSelection();
+			}
+			delegate.beginTask(name, totalWork);
+			// this is necessary because ProgressMonitorPart
+			// sets focus on Stop button
+			if (focusControl != null) {
+				focusControl.setFocus();
+				if (focusControl instanceof Combo && initialSelection != null) {
+					((Combo) focusControl).setSelection(initialSelection);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void setBlocked(IStatus reason) {
+		inUIThread(() -> delegate.setBlocked(reason));
+	}
+
+	@Override
+	public void clearBlocked() {
+		inUIThread(() -> delegate.clearBlocked());
+	}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java
index 4749074..04f632f 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java
@@ -24,6 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.ResourcesPlugin;
@@ -32,11 +33,15 @@
 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.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.fieldassist.ControlDecoration;
 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
 import org.eclipse.jface.layout.GridLayoutFactory;
 import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.viewers.CellLabelProvider;
 import org.eclipse.jface.viewers.CheckStateChangedEvent;
 import org.eclipse.jface.viewers.CheckboxTreeViewer;
@@ -51,6 +56,7 @@
 import org.eclipse.jface.viewers.ViewerComparator;
 import org.eclipse.jface.viewers.ViewerFilter;
 import org.eclipse.jface.wizard.IWizard;
+import org.eclipse.jface.wizard.ProgressMonitorPart;
 import org.eclipse.jface.wizard.WizardPage;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
@@ -58,26 +64,32 @@
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.DirectoryDialog;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.FileDialog;
 import org.eclipse.swt.widgets.Group;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.swt.widgets.TreeItem;
 import org.eclipse.ui.IWorkingSet;
 import org.eclipse.ui.dialogs.FilteredTree;
 import org.eclipse.ui.dialogs.PatternFilter;
 import org.eclipse.ui.dialogs.WorkingSetConfigurationBlock;
 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
+import org.eclipse.ui.internal.progress.ProgressManager;
+import org.eclipse.ui.internal.progress.ProgressManager.JobMonitor;
 import org.eclipse.ui.statushandlers.StatusManager;
 import org.eclipse.ui.wizards.datatransfer.ProjectConfigurator;
 
@@ -92,14 +104,10 @@
 
 	static final String IMPORTED_SOURCES = SmartImportRootWizardPage.class.getName() + ".knownSources"; //$NON-NLS-1$
 
+	// Root
 	private File selection;
-	private boolean detectNestedProjects = true;
-	private boolean configureProjects = true;
-	private Set<IWorkingSet> workingSets;
-	private ControlDecoration rootDirectoryTextDecorator;
-	private WorkingSetConfigurationBlock workingSetsBlock;
-
 	private Combo rootDirectoryText;
+	private ControlDecoration rootDirectoryTextDecorator;
 	// Proposal part
 	private CheckboxTreeViewer tree;
 	private ControlDecoration proposalSelectionDecorator;
@@ -107,6 +115,45 @@
 	private Set<File> notAlreadyExistingProjects;
 	private Label selectionSummary;
 	protected Map<File, List<ProjectConfigurator>> potentialProjects = Collections.emptyMap();
+	// Configuration part
+	private boolean detectNestedProjects = true;
+	private boolean configureProjects = true;
+	// Working sets
+	private Set<IWorkingSet> workingSets;
+	private WorkingSetConfigurationBlock workingSetsBlock;
+	// Progress monitor
+	protected Supplier<ProgressMonitorPart> wizardProgressMonitor = new Supplier<ProgressMonitorPart>() {
+		private ProgressMonitorPart progressMonitorPart;
+		@Override
+		public ProgressMonitorPart get() {
+			if (progressMonitorPart == null) {
+				try {
+					getWizard().getContainer().run(false, true, monitor -> {
+						if (monitor instanceof ProgressMonitorPart) {
+							progressMonitorPart = (ProgressMonitorPart) monitor;
+						}
+					});
+				} catch (InvocationTargetException ite) {
+					IStatus status = new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH,
+							DataTransferMessages.SmartImportWizardPage_scanProjectsFailed, ite.getCause());
+					StatusManager.getManager().handle(status, StatusManager.LOG | StatusManager.SHOW);
+				} catch (InterruptedException operationCanceled) {
+					Thread.interrupted();
+				}
+			}
+			return progressMonitorPart;
+		}
+	};
+
+	private Job refreshProposalsJob;
+	private JobMonitor jobMonitor;
+	private DelegateProgressMonitorInUIThreadAndPreservingFocus delegateMonitor;
+	private SelectionListener cancelWorkListener = new SelectionAdapter() {
+		@Override
+		public void widgetSelected(SelectionEvent e) {
+			stopAndDisconnectCurrentWork();
+		}
+	};
 
 	private class FolderForProjectsLabelProvider extends CellLabelProvider implements IColorProvider {
 		public String getText(Object o) {
@@ -225,11 +272,9 @@
 
 		createInputSelectionOptions(res);
 
-
-		Composite proposalParent = new Composite(res, SWT.NONE);
-		proposalParent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
-		proposalParent.setLayout(new FillLayout());
-		createProposalsGroup(proposalParent);
+		GridData proposalsGroupLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1);
+		proposalsGroupLayoutData.verticalIndent = 12;
+		createProposalsGroup(res).setLayoutData(proposalsGroupLayoutData);
 
 		createConfigurationOptions(res);
 
@@ -283,6 +328,7 @@
 						expandSelectedArchive();
 					}
 				}
+				validatePage();
 				refreshProposals();
 			}
 
@@ -423,7 +469,7 @@
 	/**
 	 * @param res
 	 */
-	private void createProposalsGroup(Composite parent) {
+	private Composite createProposalsGroup(Composite parent) {
 		Composite res = new Composite(parent, SWT.NONE);
 		GridLayoutFactory.fillDefaults().numColumns(2).applyTo(res);
 		PatternFilter patternFilter = new PatternFilter();
@@ -568,6 +614,8 @@
 			}
 		});
 		tree.setInput(Collections.emptyMap());
+
+		return res;
 	}
 
 	protected void validatePage() {
@@ -587,7 +635,6 @@
 			setErrorMessage(this.rootDirectoryTextDecorator.getDescriptionText());
 		} else {
 			this.rootDirectoryTextDecorator.hide();
-			setErrorMessage(null);
 		}
 		setPageComplete(isPageComplete());
 	}
@@ -619,6 +666,7 @@
 	public void setInitialImportRoot(File directoryOrArchive) {
 		this.selection = directoryOrArchive;
 		this.rootDirectoryText.setText(directoryOrArchive.getAbsolutePath());
+		refreshProposals();
 	}
 
 	/**
@@ -686,49 +734,135 @@
 	}
 
 	private void refreshProposals() {
-		try {
-			if (sourceIsValid()) {
-				Point initialSelection = rootDirectoryText.getSelection();
-				getContainer().run(true, true, new IRunnableWithProgress() {
-					@Override
-					public void run(IProgressMonitor monitor) {
-						SmartImportRootWizardPage.this.potentialProjects = getWizard().getImportJob()
-								.getImportProposals(monitor);
-						if (!potentialProjects.containsKey(getWizard().getImportJob().getRoot())) {
-							potentialProjects.put(getWizard().getImportJob().getRoot(), Collections.emptyList());
-						}
-
-						SmartImportRootWizardPage.this.notAlreadyExistingProjects = new HashSet<>(
-								potentialProjects.keySet());
-						SmartImportRootWizardPage.this.alreadyExistingProjects = new HashSet<>();
-						for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
-							IPath location = project.getLocation();
-							if (location == null) {
-								continue;
-							}
-							SmartImportRootWizardPage.this.notAlreadyExistingProjects.remove(location.toFile());
-							SmartImportRootWizardPage.this.alreadyExistingProjects.add(location.toFile());
-						}
+		stopAndDisconnectCurrentWork();
+		SmartImportRootWizardPage.this.potentialProjects = Collections.emptyMap();
+		SmartImportRootWizardPage.this.notAlreadyExistingProjects = Collections.emptySet();
+		SmartImportRootWizardPage.this.alreadyExistingProjects = Collections.emptySet();
+		proposalsUpdated();
+		// compute new state
+		if (sourceIsValid()) {
+			tree.getControl().setEnabled(false);
+			TreeItem computingItem = new TreeItem(tree.getTree(), SWT.DEFAULT);
+			computingItem
+					.setText(NLS.bind(DataTransferMessages.SmartImportJob_inspecting, selection.getAbsolutePath()));
+			final SmartImportJob importJob = getWizard().getImportJob();
+			refreshProposalsJob = new Job(
+					NLS.bind(DataTransferMessages.SmartImportJob_inspecting, selection.getAbsolutePath())) {
+				@Override
+				public IStatus run(IProgressMonitor monitor) {
+					SmartImportRootWizardPage.this.potentialProjects = importJob.getImportProposals(monitor);
+					if (monitor.isCanceled()) {
+						return Status.CANCEL_STATUS;
 					}
-				});
-				// restore selection as getContainer().run(...) looses it
-				rootDirectoryText.setSelection(initialSelection);
-			} else {
-				SmartImportRootWizardPage.this.potentialProjects = Collections.emptyMap();
-				SmartImportRootWizardPage.this.notAlreadyExistingProjects = Collections.emptySet();
-				SmartImportRootWizardPage.this.alreadyExistingProjects = Collections.emptySet();
+					if (!potentialProjects.containsKey(importJob.getRoot())) {
+						potentialProjects.put(importJob.getRoot(), Collections.emptyList());
+					}
+
+					SmartImportRootWizardPage.this.notAlreadyExistingProjects = new HashSet<>(
+							potentialProjects.keySet());
+					SmartImportRootWizardPage.this.alreadyExistingProjects = new HashSet<>();
+					for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
+						IPath location = project.getLocation();
+						if (location == null) {
+							continue;
+						}
+						SmartImportRootWizardPage.this.notAlreadyExistingProjects.remove(location.toFile());
+						SmartImportRootWizardPage.this.alreadyExistingProjects.add(location.toFile());
+					}
+					return Status.OK_STATUS;
+				}
+			};
+			Control previousFocusControl = tree.getControl().getDisplay().getFocusControl();
+			if (previousFocusControl == null) {
+				previousFocusControl = rootDirectoryText;
 			}
-			tree.setInput(potentialProjects);
-			tree.setCheckedElements(this.notAlreadyExistingProjects.toArray());
-		} catch (InvocationTargetException ite) {
-			this.selection = null;
-			IStatus status = new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, DataTransferMessages.SmartImportWizardPage_scanProjectsFailed,
-					ite.getCause());
-			StatusManager.getManager().handle(status, StatusManager.LOG | StatusManager.SHOW);
-		} catch (InterruptedException operationCanceled) {
+			Point initialSelection = rootDirectoryText.getSelection();
+			wizardProgressMonitor.get().attachToCancelComponent(null);
+			wizardProgressMonitor.get().setVisible(true);
+			// restore focus and selection because IWizardDialog.run(...) and
+			// attachToCancelComponent take them
+			previousFocusControl.setFocus();
+			rootDirectoryText.setSelection(initialSelection);
+			ToolItem stopButton = getStopButton(wizardProgressMonitor.get());
+			stopButton.addSelectionListener(this.cancelWorkListener);
+			jobMonitor = ProgressManager.getInstance().progressFor(refreshProposalsJob);
+			delegateMonitor = new DelegateProgressMonitorInUIThreadAndPreservingFocus(wizardProgressMonitor.get());
+			jobMonitor.addProgressListener(delegateMonitor);
+			refreshProposalsJob.setPriority(Job.INTERACTIVE);
+			refreshProposalsJob.setUser(true);
+			refreshProposalsJob.addJobChangeListener(new JobChangeAdapter() {
+				@Override
+				public void done(IJobChangeEvent event) {
+					Control control = tree.getControl();
+					if (!control.isDisposed()) {
+						control.getDisplay().asyncExec(() -> {
+							IStatus result = event.getResult();
+							if (!control.isDisposed() && result.isOK()) {
+								computingItem.dispose();
+								if (sourceIsValid() && getWizard().getImportJob() == importJob) {
+									proposalsUpdated();
+								}
+								tree.getTree().setEnabled(true);
+							} else if (result.getCode() == IStatus.CANCEL) {
+								computingItem.setText(DataTransferMessages.SmartImportProposals_inspecitionCanceled);
+							} else if (result.getCode() == IStatus.ERROR) {
+								computingItem.setText(
+										NLS.bind(DataTransferMessages.SmartImportProposals_errorWhileInspecting,
+												result.getMessage()));
+							}
+							if (!wizardProgressMonitor.get().isDisposed()
+									&& refreshProposalsJob.getState() == Job.NONE) {
+								wizardProgressMonitor.get().setVisible(false);
+							}
+						});
+					}
+				}
+			});
+			refreshProposalsJob.schedule(0);
 		}
+	}
+
+	private static ToolItem getStopButton(ProgressMonitorPart part) {
+		for (Control control : part.getChildren()) {
+			if (control instanceof ToolBar) {
+				for (ToolItem item : ((ToolBar) control).getItems()) {
+					if (item.getToolTipText().equals(JFaceResources.getString("ProgressMonitorPart.cancelToolTip"))) { //$NON-NLS-1$ ))
+						return item;
+					}
+				}
+			}
+		}
+		return null;
+	}
+
+	private void stopAndDisconnectCurrentWork() {
+		if (refreshProposalsJob != null) {
+			refreshProposalsJob.cancel();
+		}
+	}
+
+	private void proposalsUpdated() {
+		tree.setInput(potentialProjects);
+		tree.setCheckedElements(this.notAlreadyExistingProjects.toArray());
 		proposalsSelectionChanged();
-		SmartImportRootWizardPage.this.validatePage();
+		validatePage();
+	}
+
+	@Override
+	public void dispose() {
+		stopAndDisconnectCurrentWork();
+		getStopButton(wizardProgressMonitor.get()).removeSelectionListener(this.cancelWorkListener);
+		super.dispose();
+	}
+
+	/**
+	 * Only made public for testing purpose
+	 *
+	 * @return the Wizard progress monitor
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public ProgressMonitorPart getWizardProgressMonitor() {
+		return this.wizardProgressMonitor.get();
 	}
 
 }
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java
index 905ac94..31a2b0b 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java
@@ -33,7 +33,6 @@
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.wizard.IWizardPage;
 import org.eclipse.jface.wizard.Wizard;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.ui.IImportWizard;
@@ -329,12 +328,4 @@
 				&& job.isConfigureProjects() == page.isConfigureProjects();
 	}
 
-	@Override
-	public IWizardPage getNextPage(IWizardPage page) {
-		if (page == this.projectRootPage && !this.projectRootPage.isDetectNestedProject()) {
-			return null;
-		}
-		return super.getNextPage(page);
-	}
-
 }
\ No newline at end of file
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties
index 7d41e33..6984119 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties
@@ -191,6 +191,8 @@
 SmartImportProposals_folder=Folder
 SmartImportProposals_importAs=Import as
 SmartImportProposals_hideExistingProjects=&Hide already open projects
+SmartImportProposals_inspecitionCanceled=Inspeciton canceled
+SmartImportProposals_errorWhileInspecting=Error while inspecting: {0}
 SmartImportJob_detectAndConfigureProjects=Detecting and configuring nested projects
 SmartImportJob_configuringSelectedDirectories=Configuring selected directories
 SmartImportJob_configuring=Configuring project {0}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/progress/ProgressManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/progress/ProgressManager.java
index be27427..94d6e18 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/progress/ProgressManager.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/progress/ProgressManager.java
@@ -26,7 +26,9 @@
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.RejectedExecutionException;
@@ -35,6 +37,7 @@
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
+import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
 import org.eclipse.core.runtime.IStatus;
@@ -206,10 +209,10 @@
 	 * The JobMonitor is the inner class that handles the IProgressMonitor
 	 * integration with the ProgressMonitor.
 	 */
-	class JobMonitor implements IProgressMonitorWithBlocking {
+	public class JobMonitor implements IProgressMonitorWithBlocking {
 		Job job;
 		String currentTaskName;
-		IProgressMonitorWithBlocking listener;
+		Set<IProgressMonitorWithBlocking> monitors = Collections.emptySet();
 
 		/**
 		 * Creates a monitor on the supplied job.
@@ -225,25 +228,32 @@
 		 *
 		 * @param monitor
 		 */
-		void addProgressListener(IProgressMonitorWithBlocking monitor) {
-			listener = monitor;
+		public void addProgressListener(IProgressMonitorWithBlocking monitor) {
+			Assert.isNotNull(monitor);
+			Set<IProgressMonitorWithBlocking> newSet = new LinkedHashSet<>(monitors);
+			newSet.add(monitor);
+			this.monitors = Collections.unmodifiableSet(newSet);
 			JobInfo info = getJobInfo(job);
 			TaskInfo currentTask = info.getTaskInfo();
 			if (currentTask != null) {
-				listener.beginTask(currentTaskName, currentTask.totalWork);
-				listener.internalWorked(currentTask.preWork);
+				monitor.beginTask(currentTaskName, currentTask.totalWork);
+				monitor.internalWorked(currentTask.preWork);
 			}
 		}
 
+		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) {
 			JobInfo info = getJobInfo(job);
 			info.beginTask(taskName, totalWork);
 			refreshJobInfo(info);
 			currentTaskName = taskName;
-			if (listener != null) {
-				listener.beginTask(taskName, totalWork);
-			}
+			monitors.stream().forEach(listener -> listener.beginTask(taskName, totalWork));
 		}
 
 		@Override
@@ -252,9 +262,7 @@
 			info.clearTaskInfo();
 			info.clearChildren();
 			runnableMonitors.remove(job);
-			if (listener != null) {
-				listener.done();
-			}
+			monitors.stream().forEach(IProgressMonitorWithBlocking::done);
 		}
 
 		@Override
@@ -264,9 +272,7 @@
 				info.addWork(work);
 				refreshJobInfo(info);
 			}
-			if (listener != null) {
-				listener.internalWorked(work);
-			}
+			monitors.stream().forEach(listener -> listener.internalWorked(work));
 		}
 
 		@Override
@@ -285,10 +291,8 @@
 			// Don't bother canceling twice.
 			if (value && !info.isCanceled()) {
 				info.cancel();
-				// Only inform the first time.
-				if (listener != null) {
-					listener.setCanceled(value);
-				}
+				// Only inform the first time
+				monitors.stream().forEach(listener -> listener.setCanceled(value));
 			}
 		}
 
@@ -304,9 +308,7 @@
 			info.clearChildren();
 			refreshJobInfo(info);
 			currentTaskName = taskName;
-			if (listener != null) {
-				listener.setTaskName(taskName);
-			}
+			monitors.stream().forEach(listener -> listener.setTaskName(taskName));
 		}
 
 		@Override
@@ -318,9 +320,7 @@
 			info.clearChildren();
 			info.addSubTask(name);
 			refreshJobInfo(info);
-			if (listener != null) {
-				listener.subTask(name);
-			}
+			monitors.stream().forEach(listener -> listener.subTask(name));
 		}
 
 		@Override
@@ -333,9 +333,7 @@
 			JobInfo info = getJobInfo(job);
 			info.setBlockedStatus(null);
 			refreshJobInfo(info);
-			if (listener != null) {
-				listener.clearBlocked();
-			}
+			monitors.stream().forEach(IProgressMonitorWithBlocking::clearBlocked);
 		}
 
 		@Override
@@ -343,9 +341,7 @@
 			JobInfo info = getJobInfo(job);
 			info.setBlockedStatus(reason);
 			refreshJobInfo(info);
-			if (listener != null) {
-				listener.setBlocked(reason);
-			}
+			monitors.stream().forEach(listener -> listener.setBlocked(reason));
 		}
 	}
 
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java
index 9b1dff3..a8b1eaf 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java
@@ -23,12 +23,20 @@
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.wizard.ProgressMonitorPart;
 import org.eclipse.jface.wizard.WizardDialog;
+import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.internal.wizards.datatransfer.SmartImportRootWizardPage;
 import org.eclipse.ui.internal.wizards.datatransfer.SmartImportWizard;
 import org.eclipse.ui.tests.harness.util.UITestCase;
+import org.junit.Test;
 
 /**
  * @since 3.12
@@ -116,6 +124,7 @@
 		return null;
 	}
 
+	@Test
 	public void testImport6Projects() throws IOException, OperationCanceledException, InterruptedException {
 		URL url = FileLocator
 				.toFileURL(getClass().getResource("/data/org.eclipse.datatransferArchives/ProjectsArchive.zip"));
@@ -124,6 +133,7 @@
 		assertEquals(6, ResourcesPlugin.getWorkspace().getRoot().getProjects().length);
 	}
 
+	@Test
 	public void testImportModularProjectsWithSameName()
 			throws IOException, OperationCanceledException, InterruptedException {
 		URL url = FileLocator
@@ -143,4 +153,52 @@
 		assertTrue(implProjectNames.contains("module2_impl"));
 		assertTrue(implProjectNames.contains("module3_impl"));
 	}
+
+	@Test
+	public void testCancelWizardCancelsJob() {
+		SmartImportWizard wizard = new SmartImportWizard();
+		wizard.setInitialImportSource(File.listRoots()[0]);
+		this.dialog = new WizardDialog(getWorkbench().getActiveWorkbenchWindow().getShell(), wizard);
+		dialog.setBlockOnOpen(false);
+		dialog.open();
+		SmartImportRootWizardPage page = (SmartImportRootWizardPage) dialog.getCurrentPage();
+		ProgressMonitorPart wizardProgressMonitor = page.getWizardProgressMonitor();
+		assertNotNull("Wizard should have a progress monitor", wizardProgressMonitor);
+		ToolItem stopButton = getStopButton(wizardProgressMonitor);
+		processEventsUntil(new Condition() {
+			@Override
+			public boolean compute() {
+				return stopButton.isEnabled();
+			}
+		}, 10000);
+		assertTrue("Wizard should show progress monitor", wizardProgressMonitor.isVisible());
+		assertTrue("Stop button should be enabled", stopButton.isEnabled());
+		Event clickButtonEvent = new Event();
+		clickButtonEvent.widget = stopButton;
+		clickButtonEvent.item = stopButton;
+		clickButtonEvent.type = SWT.Selection;
+		clickButtonEvent.doit = true;
+		clickButtonEvent.stateMask = SWT.BUTTON1;
+		stopButton.notifyListeners(SWT.Selection, clickButtonEvent);
+		processEventsUntil(new Condition() {
+			@Override
+			public boolean compute() {
+				return !wizardProgressMonitor.isVisible();
+			}
+		}, 10000);
+		assertFalse("Progress monitor should be hidden within 10 seconds", wizardProgressMonitor.isVisible());
+	}
+
+	private static ToolItem getStopButton(ProgressMonitorPart part) {
+		for (Control control : part.getChildren()) {
+			if (control instanceof ToolBar) {
+				for (ToolItem item : ((ToolBar) control).getItems()) {
+					if (item.getToolTipText().equals(JFaceResources.getString("ProgressMonitorPart.cancelToolTip"))) { //$NON-NLS-1$ ))
+						return item;
+					}
+				}
+			}
+		}
+		return null;
+	}
 }