org.eclipse.jface.dialogs.ProgressMonitorDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jface.dialogs.ProgressMonitorDialog.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.dialogs;

import java.lang.reflect.InvocationTargetException;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

/**
 * A modal dialog that displays progress during a long running operation.
 * <p>
 * This concrete dialog class can be instantiated as is, or further subclassed
 * as required.
 * </p>
 * <p>
 * Typical usage is:
 *
 * <pre>
 *
 *
 *    try {
 *       IRunnableWithProgress op = ...;
 *       new ProgressMonitorDialog(activeShell).run(true, true, op);
 *    } catch (InvocationTargetException e) {
 *       // handle exception
 *    } catch (InterruptedException e) {
 *       // handle cancelation
 *    }
 *
 *
 * </pre>
 *
 * </p>
 * <p>
 * Note that the ProgressMonitorDialog is not intended to be used with multiple
 * runnables - this dialog should be discarded after completion of one
 * IRunnableWithProgress and a new one instantiated for use by a second or
 * sebsequent IRunnableWithProgress to ensure proper initialization.
 * </p>
 * <p>
 * Note that not forking the process will result in it running in the UI which
 * may starve the UI. The most obvious symptom of this problem is non
 * responsiveness of the cancel button. If you are running within the UI Thread
 * you should do the bulk of your work in another Thread to prevent starvation.
 * It is recommended that fork is set to true in most cases.
 * </p>
 */
public class ProgressMonitorDialog extends IconAndMessageDialog implements IRunnableContext {
    /**
     * Name to use for task when normal task name is empty string.
     */
    private static String DEFAULT_TASKNAME = JFaceResources.getString("ProgressMonitorDialog.message"); //$NON-NLS-1$

    /**
     * Constants for label and monitor size
     */
    private static int LABEL_DLUS = 21;

    private static int BAR_DLUS = 9;

    /**
     * The progress indicator control.
     */
    protected ProgressIndicator progressIndicator;

    /**
     * The label control for the task. Kept for backwards compatibility.
     */
    protected Label taskLabel;

    /**
     * The label control for the subtask.
     */
    protected Label subTaskLabel;

    /**
     * The Cancel button control.
     */
    protected Button cancel;

    /**
     * Indicates whether the Cancel button is to be shown.
     */
    protected boolean operationCancelableState = false;

    /**
     * Indicates whether the Cancel button is to be enabled.
     */
    protected boolean enableCancelButton;

    /**
     * The progress monitor.
     */
    private ProgressMonitor progressMonitor = new ProgressMonitor();

    /**
     * The name of the current task (used by ProgressMonitor).
     */
    private String task;

    /**
     * The nesting depth of currently running runnables.
     */
    private int nestingDepth;

    /**
     * The cursor used in the cancel button;
     */
    protected Cursor arrowCursor;

    /**
     * The cursor used in the shell;
     */
    private Cursor waitCursor;

    /**
     * Flag indicating whether to open or merely create the dialog before run.
     */
    private boolean openOnRun = true;

    /**
     * Internal progress monitor implementation.
     */
    private class ProgressMonitor implements IProgressMonitorWithBlocking {
        private String fSubTask = "";//$NON-NLS-1$

        private volatile boolean fIsCanceled;

        /**
         * is the process forked
         */
        protected boolean forked = false;

        /**
         * is locked
         */
        protected boolean locked = false;

        @Override
        public void beginTask(String name, int totalWork) {
            if (progressIndicator.isDisposed()) {
                return;
            }
            if (name == null) {
                task = "";//$NON-NLS-1$
            } else {
                task = name;
            }
            String s = task;
            if (s.length() <= 0) {
                s = DEFAULT_TASKNAME;
            }
            setMessage(s, false);
            if (!forked) {
                update();
            }
            if (totalWork == UNKNOWN) {
                progressIndicator.beginAnimatedTask();
            } else {
                progressIndicator.beginTask(totalWork);
            }
        }

        @Override
        public void done() {
            if (!progressIndicator.isDisposed()) {
                progressIndicator.sendRemainingWork();
                progressIndicator.done();
            }
        }

        @Override
        public void setTaskName(String name) {
            if (name == null) {
                task = "";//$NON-NLS-1$
            } else {
                task = name;
            }
            String s = task;
            if (s.length() <= 0) {
                s = DEFAULT_TASKNAME;
            }
            setMessage(s, false);
            if (!forked) {
                update();
            }
        }

        @Override
        public boolean isCanceled() {
            return fIsCanceled;
        }

        @Override
        public void setCanceled(boolean b) {
            fIsCanceled = b;
            if (locked) {
                clearBlocked();
            }
        }

        @Override
        public void subTask(String name) {
            if (subTaskLabel.isDisposed()) {
                return;
            }
            if (name == null) {
                fSubTask = "";//$NON-NLS-1$
            } else {
                fSubTask = name;
            }
            subTaskLabel.setText(shortenText(fSubTask, subTaskLabel));
            if (!forked) {
                subTaskLabel.update();
            }
        }

        @Override
        public void worked(int work) {
            internalWorked(work);
        }

        @Override
        public void internalWorked(double work) {
            if (!progressIndicator.isDisposed()) {
                progressIndicator.worked(work);
            }
        }

        @Override
        public void clearBlocked() {
            if (getShell() == null || getShell().isDisposed())
                return;
            locked = false;
            updateForClearBlocked();
        }

        @Override
        public void setBlocked(IStatus reason) {
            if (getShell() == null || getShell().isDisposed())
                return;
            locked = true;
            updateForSetBlocked(reason);
        }
    }

    /**
     * Clear blocked state from the receiver.
     */
    protected void updateForClearBlocked() {
        progressIndicator.showNormal();
        setMessage(task, true);
        imageLabel.setImage(getImage());

    }

    /**
     * Set blocked state from the receiver.
     *
     * @param reason
     *            IStatus that gives the details
     */
    protected void updateForSetBlocked(IStatus reason) {
        progressIndicator.showPaused();
        setMessage(reason.getMessage(), true);
        imageLabel.setImage(getImage());

    }

    /**
     * Creates a progress monitor dialog under the given shell. The dialog has a
     * standard title and no image. <code>open</code> is non-blocking.
     *
     * @param parent
     *            the parent shell, or <code>null</code> to create a top-level
     *            shell
     */
    public ProgressMonitorDialog(Shell parent) {
        super(parent);
        // no close button on the shell style
        if (isResizable()) {
            setShellStyle(getDefaultOrientation() | SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.RESIZE
                    | SWT.MAX);
        } else {
            setShellStyle(getDefaultOrientation() | SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL);
        }
        setBlockOnOpen(false);
    }

    /**
     * Enables the cancel button (asynchronously).
     *
     * @param b
     *            The state to set the button to.
     */
    private void asyncSetOperationCancelButtonEnabled(final boolean b) {
        if (getShell() != null) {
            getShell().getDisplay().asyncExec(() -> setOperationCancelButtonEnabled(b));
        }
    }

    /**
     * The cancel button has been pressed.
     *
     * @since 3.0
     */
    @Override
    protected void cancelPressed() {
        // NOTE: this was previously done from a listener installed on the
        // cancel button. On GTK, the listener installed by
        // Dialog.createButton is called first and this was throwing an
        // exception because the cancel button was already disposed
        cancel.setEnabled(false);
        progressMonitor.setCanceled(true);
        super.cancelPressed();
    }

    /**
     * The <code>ProgressMonitorDialog</code> implementation of this method
     * only closes the dialog if there are no currently running runnables.
     */
    @Override
    public boolean close() {
        if (getNestingDepth() <= 0) {
            clearCursors();
            return super.close();
        }
        return false;
    }

    /**
     * Clear the cursors in the dialog.
     *
     * @since 3.0
     */
    protected void clearCursors() {
        if (cancel != null && !cancel.isDisposed()) {
            cancel.setCursor(null);
        }
        Shell shell = getShell();
        if (shell != null && !shell.isDisposed()) {
            shell.setCursor(null);
        }
        if (arrowCursor != null) {
            arrowCursor.dispose();
        }
        if (waitCursor != null) {
            waitCursor.dispose();
        }
        arrowCursor = null;
        waitCursor = null;
    }

    @Override
    protected void configureShell(final Shell shell) {
        super.configureShell(shell);
        shell.setText(JFaceResources.getString("ProgressMonitorDialog.title")); //$NON-NLS-1$
        if (waitCursor == null) {
            waitCursor = new Cursor(shell.getDisplay(), SWT.CURSOR_WAIT);
        }
        shell.setCursor(waitCursor);
        // Add a listener to set the message properly when the dialog becomes
        // visible
        shell.addListener(SWT.Show, event -> shell.getDisplay().asyncExec(() -> setMessage(message, true)));
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        // cancel button
        createCancelButton(parent);
    }

    /**
     * Creates the cancel button.
     *
     * @param parent
     *            the parent composite
     * @since 3.0
     */
    protected void createCancelButton(Composite parent) {
        cancel = createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, true);
        if (arrowCursor == null) {
            arrowCursor = new Cursor(cancel.getDisplay(), SWT.CURSOR_ARROW);
        }
        cancel.setCursor(arrowCursor);
        setOperationCancelButtonEnabled(enableCancelButton);
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        setMessage(DEFAULT_TASKNAME, false);
        createMessageArea(parent);
        // Only set for backwards compatibility
        taskLabel = messageLabel;
        // progress indicator
        progressIndicator = new ProgressIndicator(parent);
        GridData gd = new GridData();
        gd.heightHint = convertVerticalDLUsToPixels(BAR_DLUS);
        gd.horizontalAlignment = GridData.FILL;
        gd.grabExcessHorizontalSpace = true;
        gd.horizontalSpan = 2;
        progressIndicator.setLayoutData(gd);
        // label showing current task
        subTaskLabel = new Label(parent, SWT.LEFT | SWT.WRAP);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.heightHint = convertVerticalDLUsToPixels(LABEL_DLUS);
        gd.horizontalSpan = 2;
        subTaskLabel.setLayoutData(gd);
        subTaskLabel.setFont(parent.getFont());
        return parent;
    }

    @Override
    protected Point getInitialSize() {
        Point calculatedSize = super.getInitialSize();
        if (calculatedSize.x < 450) {
            calculatedSize.x = 450;
        }
        return calculatedSize;
    }

    /**
     * Returns the progress monitor to use for operations run in this progress
     * dialog.
     *
     * @return the progress monitor
     */
    public IProgressMonitor getProgressMonitor() {
        return progressMonitor;
    }

    /**
     * This implementation of IRunnableContext#run(boolean, boolean,
     * IRunnableWithProgress) runs the given <code>IRunnableWithProgress</code>
     * using the progress monitor for this progress dialog and blocks until the
     * runnable has been run, regardless of the value of <code>fork</code>.
     * The dialog is opened before the runnable is run, and closed after it
     * completes. 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.
     */
    @Override
    public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
            throws InvocationTargetException, InterruptedException {
        setCancelable(cancelable);
        try {
            aboutToRun();
            // Let the progress monitor know if they need to update in UI Thread
            progressMonitor.forked = fork;
            ModalContext.run(runnable, fork, getProgressMonitor(), getShell().getDisplay());
        } finally {
            finishedRun();
        }
    }

    /**
     * Returns whether the dialog should be opened before the operation is run.
     * Defaults to <code>true</code>
     *
     * @return <code>true</code> to open the dialog before run,
     *         <code>false</code> to only create the dialog, but not open it
     * @since 3.0
     */
    public boolean getOpenOnRun() {
        return openOnRun;
    }

    /**
     * Sets whether the dialog should be opened before the operation is run.
     * NOTE: Setting this to false and not forking a process may starve any
     * asyncExec that tries to open the dialog later.
     *
     * @param openOnRun
     *            <code>true</code> to open the dialog before run,
     *            <code>false</code> to only create the dialog, but not open
     *            it
     * @since 3.0
     */
    public void setOpenOnRun(boolean openOnRun) {
        this.openOnRun = openOnRun;
    }

    /**
     * Returns the nesting depth of running operations.
     *
     * @return the nesting depth of running operations
     * @since 3.0
     */
    protected int getNestingDepth() {
        return nestingDepth;
    }

    /**
     * Increments the nesting depth of running operations.
     *
     * @since 3.0
     */
    protected void incrementNestingDepth() {
        nestingDepth++;
    }

    /**
     * Decrements the nesting depth of running operations.
     *
     * @since 3.0
     *
     */
    protected void decrementNestingDepth() {
        nestingDepth--;
    }

    /**
     * Called just before the operation is run. Default behaviour is to open or
     * create the dialog, based on the setting of <code>getOpenOnRun</code>,
     * and increment the nesting depth.
     *
     * @since 3.0
     */
    protected void aboutToRun() {
        if (getOpenOnRun()) {
            open();
        } else {
            create();
        }
        incrementNestingDepth();
    }

    /**
     * Called just after the operation is run. Default behaviour is to decrement
     * the nesting depth, and close the dialog.
     *
     * @since 3.0
     */
    protected void finishedRun() {
        decrementNestingDepth();
        close();
    }

    /**
     * Sets whether the progress dialog is cancelable or not.
     *
     * @param cancelable
     *            <code>true</code> if the end user can cancel this progress
     *            dialog, and <code>false</code> if it cannot be canceled
     */
    public void setCancelable(boolean cancelable) {
        if (cancel == null) {
            enableCancelButton = cancelable;
        } else {
            asyncSetOperationCancelButtonEnabled(cancelable);
        }
    }

    /**
     * Helper to enable/disable Cancel button for this dialog.
     *
     * @param b
     *            <code>true</code> to enable the cancel button, and
     *            <code>false</code> to disable it
     * @since 3.0
     */
    protected void setOperationCancelButtonEnabled(boolean b) {
        operationCancelableState = b;
        if (cancel != null && !cancel.isDisposed()) {
            cancel.setEnabled(b);
        }
    }

    @Override
    protected Image getImage() {
        return getInfoImage();
    }

    /**
     * Set the message in the message label.
     *
     * @param messageString
     *            The string for the new message.
     * @param force
     *            If force is true then always set the message text.
     */
    private void setMessage(String messageString, boolean force) {
        // must not set null text in a label
        message = messageString == null ? "" : messageString; //$NON-NLS-1$
        if (messageLabel == null || messageLabel.isDisposed()) {
            return;
        }
        if (force || messageLabel.isVisible()) {
            messageLabel.setToolTipText(message);
            messageLabel.setText(shortenText(message, messageLabel));
        }
    }

    /**
     * Update the message label. Required if the monitor is forked.
     */
    private void update() {
        if (messageLabel == null || messageLabel.isDisposed()) {
            return;
        }
        messageLabel.update();
    }

    @Override
    public int open() {
        // Check to be sure it is not already done. If it is just return OK.
        if (!getOpenOnRun()) {
            if (getNestingDepth() == 0) {
                return OK;
            }
        }
        int result = super.open();
        // update message label just in case beginTask() has been invoked
        // already
        if (task == null || task.length() == 0)
            setMessage(DEFAULT_TASKNAME, true);
        else
            setMessage(task, true);
        return result;
    }
}