blob: 1e8b8403757452af26159e0d15543343c41080cc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* IBM Corporation - initial API and implementation
* Chris Gross (schtoo@schtoo.com) - patch for bug 16179
* Tasktop Technologies - extracted code for Mylyn
*******************************************************************************/
package org.eclipse.mylyn.commons.ui;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ProgressMonitorWrapper;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
/**
* A helper class for running operations in dialogs. Based on {@link WizardDialog}.
*
* @author Steffen Pingel
* @since 3.7
*/
public class ProgressContainer implements IRunnableContext {
private static final String FOCUS_CONTROL = "focusControl"; //$NON-NLS-1$
// The number of long running operation executed from the dialog.
private long activeRunningOperations = 0;
private Cursor arrowCursor;
private Button cancelButton;
private boolean lockedUI = false;
// The progress monitor
private final ProgressMonitorPart progressMonitorPart;
private final Shell shell;
private Cursor waitCursor;
private boolean useWaitCursor;
public ProgressContainer(Shell shell, ProgressMonitorPart progressMonitorPart) {
Assert.isNotNull(shell);
Assert.isNotNull(progressMonitorPart);
this.shell = shell;
this.progressMonitorPart = progressMonitorPart;
init();
}
private void init() {
this.shell.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
progressMonitorPart.setCanceled(true);
}
});
}
public boolean useWaitCursor() {
return useWaitCursor;
}
public void setUseWaitCursor(boolean useWaitCursor) {
this.useWaitCursor = useWaitCursor;
}
/**
* About to start a long running operation triggered through the wizard. Shows the progress monitor and disables the
* wizard's buttons and controls.
*
* @param enableCancelButton
* <code>true</code> if the Cancel button should be enabled, and <code>false</code> if it should be
* disabled
* @return the saved UI state
*/
private Object aboutToStart(boolean enableCancelButton) {
Map<Object, Object> savedState = null;
if (getShell() != null) {
// Save focus control
Control focusControl = getShell().getDisplay().getFocusControl();
if (focusControl != null && focusControl.getShell() != getShell()) {
focusControl = null;
}
// Set the busy cursor to all shells.
Display d = getShell().getDisplay();
if (useWaitCursor()) {
waitCursor = new Cursor(d, SWT.CURSOR_WAIT);
setDisplayCursor(waitCursor);
// Set the arrow cursor to the cancel component.
arrowCursor = new Cursor(d, SWT.CURSOR_ARROW);
if (cancelButton != null) {
cancelButton.setCursor(arrowCursor);
}
}
// Deactivate shell
savedState = new HashMap<Object, Object>(10);
saveUiState(savedState);
if (focusControl != null) {
savedState.put(FOCUS_CONTROL, focusControl);
}
// Attach the progress monitor part to the cancel button
if (cancelButton != null) {
progressMonitorPart.attachToCancelComponent(cancelButton);
}
progressMonitorPart.setVisible(true);
}
return savedState;
}
public Button getCancelButton() {
return cancelButton;
}
private IProgressMonitor getProgressMonitor() {
return new ProgressMonitorWrapper(progressMonitorPart) {
@Override
public void internalWorked(double work) {
if (progressMonitorPart.isDisposed()) {
this.setCanceled(true);
return;
}
super.internalWorked(work);
}
};
}
public Shell getShell() {
return shell;
}
public boolean isActive() {
return activeRunningOperations > 0;
}
public boolean isLockedUI() {
return lockedUI;
}
protected void restoreUiState(Map<Object, Object> state) {
// ignore
}
/**
* This implementation of IRunnableContext#run(boolean, boolean, IRunnableWithProgress) blocks until the runnable
* has been run, regardless of the value of <code>fork</code>. It is recommended that <code>fork</code> is set to
* true in most cases. If <code>fork</code> is set to <code>false</code>, the runnable will run in the UI thread and
* it is the runnable's responsibility to call <code>Display.readAndDispatch()</code> to ensure UI responsiveness.
* UI state is saved prior to executing the long-running operation and is restored after the long-running operation
* completes executing. Any attempt to change the UI state of the wizard in the long-running operation will be
* nullified when original UI state is restored.
*/
public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) throws InvocationTargetException,
InterruptedException {
// The operation can only be canceled if it is executed in a separate
// thread.
// Otherwise the UI is blocked anyway.
Object state = null;
if (activeRunningOperations == 0) {
state = aboutToStart(fork && cancelable);
}
activeRunningOperations++;
try {
if (!fork) {
lockedUI = true;
}
ModalContext.run(runnable, fork, getProgressMonitor(), getShell().getDisplay());
lockedUI = false;
} finally {
activeRunningOperations--;
// Stop if this is the last one
if (state != null) {
stopped(state);
}
}
}
protected void saveUiState(Map<Object, Object> savedState) {
// ignore
}
public void setCancelButton(Button cancelButton) {
this.cancelButton = cancelButton;
}
/**
* Sets the given cursor for all shells currently active for this window's display.
*
* @param c
* the cursor
*/
private void setDisplayCursor(Cursor c) {
Shell[] shells = getShell().getDisplay().getShells();
for (Shell shell2 : shells) {
shell2.setCursor(c);
}
}
/**
* A long running operation triggered through the wizard was stopped either by user input or by normal end. Hides
* the progress monitor and restores the enable state wizard's buttons and controls.
*
* @param savedState
* the saved UI state as returned by <code>aboutToStart</code>
* @see #aboutToStart
*/
@SuppressWarnings("unchecked")
private void stopped(Object savedState) {
if (getShell() != null && !getShell().isDisposed()) {
progressMonitorPart.setVisible(false);
if (cancelButton != null) {
progressMonitorPart.removeFromCancelComponent(cancelButton);
}
Map<Object, Object> state = (Map<Object, Object>) savedState;
restoreUiState(state);
if (waitCursor != null) {
setDisplayCursor(null);
if (cancelButton != null) {
cancelButton.setCursor(null);
}
waitCursor.dispose();
waitCursor = null;
arrowCursor.dispose();
arrowCursor = null;
}
Control focusControl = (Control) state.get(FOCUS_CONTROL);
if (focusControl != null && !focusControl.isDisposed()) {
focusControl.setFocus();
}
}
}
}