/*******************************************************************************
* Copyright (c) 2010 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.ui;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IWorkbenchWizard;
import org.eclipse.ui.PlatformUI;
import org.jboss.tools.foundation.core.jobs.DelegatingProgressMonitor;
/**
* @author André Dietisheim
*/
public class WizardUtils {
private static final long THREAD_SLEEP = 1 * 1000;
private static final int NO_TIMEOUT = -1;
private WizardUtils() {
// inhibit instantiation
}
/**
* Runs the given job in the given wizard container. This method will return
* immediately, it will not wait for the job completion.
* <p>
* In order to have the wizard displaying a progress bar, you need to set
* Wizard#setNeedsProgressMonitor to <code>true</code>.
*
* @param job
* the job to run
* @param container
* the wizard container to run the job in
* @return a future that allows you to wait for the job result.
* @throws InvocationTargetException
* the invocation target exception
* @throws InterruptedException
* the interrupted exception
* @author André Dietisheim
* @see IWizardContainer#run(boolean, boolean, IRunnableWithProgress)
* @see Job
*/
public static IStatus runInWizard(final Job job, IWizardContainer container) throws InvocationTargetException,
InterruptedException {
return runInWizard(job, null, container);
}
/**
* Runs the given job in the given wizard container.
* <p>
* In order to have the wizard displaying a progress bar, you need to set
* Wizard#setNeedsProgressMonitor to <code>true</code>.
*
* @param job
* the job to run in the wizard
* @param delegatingMonitor
* the delegating monitor that the wizard monitor shall be added
* to.
* @param container
* the wizard container to run the job in
* @return a future that allows you to wait for the job result
* @throws InvocationTargetException
* @throws InterruptedException
*/
public static IStatus runInWizard(final Job job, final DelegatingProgressMonitor delegatingMonitor,
final IWizardContainer container) throws InvocationTargetException, InterruptedException {
return runInWizard(job, delegatingMonitor, container, NO_TIMEOUT);
}
/**
* Runs the given job in the given wizard container.
* <p>
* In order to have the wizard displaying a progress bar, you need to set
* Wizard#setNeedsProgressMonitor to <code>true</code>.
* <p>
* In order to be able to report updates in the job to the wizard progress,
* you'd have to use the {@link DelegatingProgressMonitor}. Add your job
* monitor to that aggregating monitor and call #subTask #done etc. on that
* one (@link https://bugs.eclipse.org/bugs/show_bug.cgi?id=293098)
*
* <code>
* DelegatingProgressMonitor delegate = new DelegatingProgressMonitor();
* delegate.add(myJobMonitor)
* delegate.add(wizardMonitor)
* ...
* delegate.subTask("now reporting to the delegate so that progress is shown in workbench and wizard");
* </code>
*
* @param job
* the job to run in the wizard
* @param delegatingMonitor
* the delegating monitor that the wizard monitor shall be added
* to.
* @param container
* the wizard container to run the job in
* @return a future that allows you to wait for the job result
* @throws InvocationTargetException
* @throws InterruptedException
*/
public static IStatus runInWizard(final Job job, final DelegatingProgressMonitor delegatingMonitor,
IWizardContainer container, final long timeout) throws InvocationTargetException, InterruptedException {
final JobResultFuture future = new JobResultFuture(job);
container.run(true, true, new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
if (delegatingMonitor != null) {
delegatingMonitor.add(monitor);
}
monitor.beginTask(job.getName(), IProgressMonitor.UNKNOWN);
job.schedule();
try {
waitForFuture(timeout, future, monitor);
} catch (ExecutionException e) {
} catch (TimeoutException e) {
} finally {
monitor.done();
}
}
});
return getStatus(job, future);
}
private static void waitForFuture(long timeout, JobResultFuture future, IProgressMonitor monitor)
throws InterruptedException, ExecutionException, TimeoutException {
long startTime = System.currentTimeMillis();
while (!future.isDone()) {
if ((timeout > 0 && isTimeouted(startTime, timeout))
|| monitor.isCanceled()) {
future.cancel(true);
break;
}
Thread.sleep(THREAD_SLEEP);
}
}
private static boolean isTimeouted(long startTime, long timeout) {
return (System.currentTimeMillis() - startTime) > timeout;
}
private static IStatus getStatus(final Job job, final JobResultFuture future) {
if (future.isCancelled()) {
String message = NLS.bind("The operation ''{0}'' was cancelled", job.getName());
CommonUIPlugin.getDefault().logError(message);
return new Status(IStatus.CANCEL, CommonUIPlugin.PLUGIN_ID, message);
}
if (future.isDone()) {
return job.getResult();
}
String message =
NLS.bind("The operation ''{0}'' did not complete in a reasonnable amount of time", job.getName());
CommonUIPlugin.getDefault().logError(message);
return new Status(IStatus.ERROR, CommonUIPlugin.PLUGIN_ID, message);
}
/**
* Runs the given job in the given wizard container.
* <p>
* Furhtermore it updates the models and targets of the given data binding
* context. This might be necessary if the given job will change widget
* enablements in the calling wizard page. The reason for is that the runner
* saves the widget enablement states when it's up to execute the runnable.
* It then restores those states once he finished executing the runnable. It
* may therefore restore incorrect states since the job changed the
* enablements when it was run.
*
* @param job
* the job
* @param container
* the container
* @param dbc
* the dbc
* @throws InvocationTargetException
* the invocation target exception
* @throws InterruptedException
* the interrupted exception
*/
public static IStatus runInWizard(final Job job, IWizardContainer container, DataBindingContext dbc)
throws InvocationTargetException, InterruptedException {
return runInWizard(job, null, container, dbc);
}
public static IStatus runInWizard(Job job, DelegatingProgressMonitor monitor, IWizardContainer container,
DataBindingContext dbc) throws InvocationTargetException, InterruptedException {
IStatus status = runInWizard(job, monitor, container);
// re-propagate model states to widgets states after those were disabled by wizard runnable
dbc.updateTargets();
// dont update models, they're already in correct state and this would
// possibly override correct model state with erroneous widget state
// dbc.updateModels();
return status;
}
/**
* Flips to the next wizard page or finishes the current wizard.
*
* @param wizardPage
* the wizard page this call is executed in
*/
public static void nextPageOrFinish(IWizardPage wizardPage) {
IWizard wizard = wizardPage.getWizard();
if (wizardPage.canFlipToNextPage()) {
IWizardPage nextPage = wizard.getNextPage(wizardPage);
wizard.getContainer().showPage(nextPage);
} else if (wizard.canFinish()) {
if (wizard.performFinish()) {
wizard.getContainer().getShell().close();
}
}
}
public static int openWizardDialog(IWizard wizard, Shell shell) {
WizardDialog dialog = new WizardDialog(shell, wizard);
dialog.create();
return dialog.open();
}
public static void close(IWizard wizard) {
IWizardContainer container = wizard.getContainer();
if (container instanceof WizardDialog) {
((WizardDialog) container).close();
}
}
public static boolean openWizardDialog(int width, int height, IWizard wizard, Shell shell) {
WizardDialog dialog = createWizardDialog(wizard, shell);
dialog.setMinimumPageSize(width, height);
return dialog.open() == Dialog.OK;
}
private static WizardDialog createWizardDialog(IWizard wizard, Shell shell) {
WizardDialog dialog = new WizardDialog(shell, wizard);
dialog.create();
return dialog;
}
public static boolean openWizardDialog(IWorkbenchWizard wizard, Shell shell) {
ISelectionService selectionService = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService();
ISelection selection = selectionService.getSelection();
if (selection instanceof IStructuredSelection) {
return openWizardDialog(wizard, shell, (IStructuredSelection) selection);
} else {
return openWizardDialog(wizard, shell, null);
}
}
public static boolean openWizardDialog(IWorkbenchWizard wizard, Shell shell, IStructuredSelection selection) {
WizardDialog wizardDialog = createWizardDialog(wizard, shell);
//1. At this moment the wizard dialog must have a shell.
//2. Initialization may open sub-dialog to prompt for more data needed for wizard
// or even just to warn that there is a reason not to open the wizard.
wizard.init(PlatformUI.getWorkbench(), selection);
//3. If sub-dialog was cancelled (or warning accepted),
// wizard may decide to be auto-cancelled, and request closing the wizard dialog.
if(wizardDialog.getShell() == null || wizardDialog.getShell().isDisposed()) {
return false;
}
return wizardDialog.open() == Dialog.OK;
}
}