/******************************************************************************* * Copyright (c) 2009, 2015 Atlassian 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: * Atlassian - initial API and implementation ******************************************************************************/ package org.eclipse.mylyn.reviews.ui; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.HashMap; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.dialogs.TitleAreaDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.operation.ModalContext; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.wizard.ProgressMonitorPart; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Cursor; 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.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; /** * Dialog that can display progress * * @author Shawn Minto */ public abstract class ProgressDialog extends TitleAreaDialog { private boolean lockedUI = false; private Composite pageContainer; private Cursor waitCursor; private ProgressMonitorPart progressMonitorPart; private long activeRunningOperations = 0; private final HashMap<Integer, Button> buttons = new HashMap<Integer, Button>(); public ProgressDialog(Shell parentShell) { super(parentShell); setDialogHelpAvailable(false); setHelpAvailable(false); } /* * (non-Javadoc) Method declared on Dialog. */ @Override protected Control createDialogArea(Composite parent) { Composite composite = (Composite) super.createDialogArea(parent); // Build the Page container pageContainer = new Composite(composite, SWT.NONE); pageContainer.setLayout(new FillLayout()); GridData gd = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); pageContainer.setLayoutData(gd); pageContainer.setFont(parent.getFont()); createPageControls(pageContainer); // Insert a progress monitor GridLayout pmlayout = new GridLayout(); pmlayout.numColumns = 1; progressMonitorPart = createProgressMonitorPart(composite, pmlayout); GridData gridData = new GridData(GridData.FILL_HORIZONTAL); progressMonitorPart.setLayoutData(gridData); progressMonitorPart.setVisible(true); // Build the separator line Label separator = new Label(parent, SWT.HORIZONTAL | SWT.SEPARATOR); separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); applyDialogFont(progressMonitorPart); return composite; } protected abstract Control createPageControls(Composite parent); protected Collection<? extends Control> getDisableableControls() { return buttons.values(); } /** * 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 void aboutToStart(boolean enableCancelButton) { 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(); waitCursor = new Cursor(d, SWT.CURSOR_WAIT); setDisplayCursor(waitCursor); for (Control button : getDisableableControls()) { button.setEnabled(false); } progressMonitorPart.setVisible(true); } } /** * 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 */ private void stopped(Object savedState) { if (getShell() != null) { progressMonitorPart.setVisible(false); setDisplayCursor(null); waitCursor.dispose(); waitCursor = null; for (Control button : getDisableableControls()) { button.setEnabled(true); } } } /** * Create the progress monitor part in the receiver. * * @param composite * @param pmlayout * @return ProgressMonitorPart */ protected ProgressMonitorPart createProgressMonitorPart(Composite composite, GridLayout pmlayout) { return new ProgressMonitorPart(composite, pmlayout, SWT.DEFAULT) { private String currentTask = null; @Override public void setBlocked(IStatus reason) { super.setBlocked(reason); if (!lockedUI) { getBlockedHandler().showBlocked(getShell(), this, reason, currentTask); } } @Override public void clearBlocked() { super.clearBlocked(); if (!lockedUI) { getBlockedHandler().clearBlocked(); } } @Override public void beginTask(String name, int totalWork) { super.beginTask(name, totalWork); currentTask = name; } @Override public void setTaskName(String name) { super.setTaskName(name); currentTask = name; } @Override public void subTask(String name) { super.subTask(name); if (currentTask == null) { currentTask = name; } } }; } /** * 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) { 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 (activeRunningOperations <= 0) { stopped(state); } } } /** * Returns the progress monitor for this wizard dialog (if it has one). * * @return the progress monitor, or <code>null</code> if this wizard dialog does not have one */ protected IProgressMonitor getProgressMonitor() { return progressMonitorPart; } /** * 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 element : shells) { element.setCursor(c); } } @Override protected Button createButton(Composite parent, int id, String label, boolean defaultButton) { // increment the number of columns in the button bar ((GridLayout) parent.getLayout()).numColumns++; Button button = new Button(parent, SWT.PUSH); button.setText(label); button.setFont(JFaceResources.getDialogFont()); button.setData(Integer.valueOf(id)); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { buttonPressed(((Integer) event.widget.getData()).intValue()); } }); if (defaultButton) { Shell shell = parent.getShell(); if (shell != null) { shell.setDefaultButton(button); } } buttons.put(Integer.valueOf(id), button); setButtonLayoutData(button); return button; } @Override protected Button getButton(int id) { return buttons.get(id); } }