# HG changeset patch
# User Frank Benoit
+ * This concrete dialog class can be instantiated as is, or further subclassed
+ * as required.
+ *
+ * Typical usage is:
+ *
+ *
+ *
+ *
+ * try {
+ * IRunnableWithProgress op = ...;
+ * new ProgressMonitorDialog(activeShell).run(true, true, op);
+ * } catch (InvocationTargetException e) {
+ * // handle exception
+ * } catch (InterruptedException e) {
+ * // handle cancelation
+ * }
+ *
+ *
+ *
+ *
+ *
+ * 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. + *
+ *+ * 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. + *
+ */ +public class ProgressMonitorDialog : IconAndMessageDialog, + IRunnableContext { + /** + * Name to use for task when normal task name is empty string. + */ + private static String DEFAULT_TASKNAME; + + static this() { + 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 bool operationCancelableState = false; + + /** + * Indicates whether the Cancel button is to be enabled. + */ + protected bool enableCancelButton; + + /** + * The progress monitor. + */ + private ProgressMonitor 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 bool openOnRun = true; + + /** + * Internal progress monitor implementation. + */ + private class ProgressMonitor : IProgressMonitorWithBlocking { + private String fSubTask = "";//$NON-NLS-1$ + + private bool fIsCanceled; + + /** + * is the process forked + */ + protected bool forked = false; + + /** + * is locked + */ + protected bool locked = false; + + public void beginTask(String name, int totalWork) { + if (progressIndicator.isDisposed()) { + return; + } + if (name is 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 is UNKNOWN) { + progressIndicator.beginAnimatedTask(); + } else { + progressIndicator.beginTask(totalWork); + } + } + + public void done() { + if (!progressIndicator.isDisposed()) { + progressIndicator.sendRemainingWork(); + progressIndicator.done(); + } + } + + public void setTaskName(String name) { + if (name is null) { + task = "";//$NON-NLS-1$ + } else { + task = name; + } + String s = task; + if (s.length <= 0) { + s = DEFAULT_TASKNAME; + } + setMessage(s, false); + if (!forked) { + update(); + } + } + + public bool isCanceled() { + return fIsCanceled; + } + + public void setCanceled(bool b) { + fIsCanceled = b; + if (locked) { + clearBlocked(); + } + } + + public void subTask(String name) { + if (subTaskLabel.isDisposed()) { + return; + } + if (name is null) { + fSubTask = "";//$NON-NLS-1$ + } else { + fSubTask = name; + } + subTaskLabel.setText(shortenText(fSubTask, subTaskLabel)); + if (!forked) { + subTaskLabel.update(); + } + } + + public void worked(int work) { + internalWorked(work); + } + + public void internalWorked(double work) { + if (!progressIndicator.isDisposed()) { + progressIndicator.worked(work); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.runtime.IProgressMonitorWithBlocking#clearBlocked() + */ + public void clearBlocked() { + locked = false; + updateForClearBlocked(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.core.runtime.IProgressMonitorWithBlocking#setBlocked(dwtx.core.runtime.IStatus) + */ + public void setBlocked(IStatus reason) { + locked = true; + updateForSetBlocked(reason); + } + } + + /** + * Clear blocked state from the receiver. + */ + protected void updateForClearBlocked() { + setMessage(task, true); + imageLabel.setImage(getImage()); + } + + /** + * Set blocked state from the receiver. + * + * @param reason + * IStatus that gives the details + */ + protected void updateForSetBlocked(IStatus reason) { + 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.open
is non-blocking.
+ *
+ * @param parent
+ * the parent shell, or null
to create a top-level
+ * shell
+ */
+ public this(Shell parent) {
+ progressMonitor = new ProgressMonitor();
+ super(parent);
+ setShellStyle(getDefaultOrientation() | DWT.BORDER | DWT.TITLE
+ | DWT.APPLICATION_MODAL); // no
+ // close
+ // button
+ setBlockOnOpen(false);
+ }
+
+ /**
+ * Enables the cancel button (asynchronously).
+ *
+ * @param b
+ * The state to set the button to.
+ */
+ private void asyncSetOperationCancelButtonEnabled(bool b) {
+ if (getShell() !is null) {
+ getShell().getDisplay().asyncExec(new class Runnable {
+ bool b_;
+ this(){ b_=b; }
+ public void run() {
+ setOperationCancelButtonEnabled(b);
+ }
+ });
+ }
+ }
+
+ /**
+ * The cancel button has been pressed.
+ *
+ * @since 3.0
+ */
+ 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();
+ }
+
+ /*
+ * (non-Javadoc) Method declared on Window.
+ */
+ /**
+ * The ProgressMonitorDialog
implementation of this method
+ * only closes the dialog if there are no currently running runnables.
+ */
+ public bool close() {
+ if (getNestingDepth() <= 0) {
+ clearCursors();
+ return super.close();
+ }
+ return false;
+ }
+
+ /**
+ * Clear the cursors in the dialog.
+ *
+ * @since 3.0
+ */
+ protected void clearCursors() {
+ if (cancel !is null && !cancel.isDisposed()) {
+ cancel.setCursor(null);
+ }
+ Shell shell = getShell();
+ if (shell !is null && !shell.isDisposed()) {
+ shell.setCursor(null);
+ }
+ if (arrowCursor !is null) {
+ arrowCursor.dispose();
+ }
+ if (waitCursor !is null) {
+ waitCursor.dispose();
+ }
+ arrowCursor = null;
+ waitCursor = null;
+ }
+
+ /*
+ * (non-Javadoc) Method declared in Window.
+ */
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText(JFaceResources.getString("ProgressMonitorDialog.title")); //$NON-NLS-1$
+ if (waitCursor is null) {
+ waitCursor = new Cursor(shell.getDisplay(), DWT.CURSOR_WAIT);
+ }
+ shell.setCursor(waitCursor);
+ // Add a listener to set the message properly when the dialog becomes
+ // visible
+ shell.addListener(DWT.Show, new class Listener {
+ Shell shell_;
+ this(){ shell_=shell; }
+ public void handleEvent(Event event) {
+ // We need to async the message update since the Show precedes
+ // visibility
+ shell_.getDisplay().asyncExec(new class Runnable {
+ public void run() {
+ setMessage(message, true);
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc) Method declared on Dialog.
+ */
+ 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 is null) {
+ arrowCursor = new Cursor(cancel.getDisplay(), DWT.CURSOR_ARROW);
+ }
+ cancel.setCursor(arrowCursor);
+ setOperationCancelButtonEnabled(enableCancelButton);
+ }
+
+ /*
+ * (non-Javadoc) Method declared on Dialog.
+ */
+ 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, DWT.LEFT | DWT.WRAP);
+ gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.heightHint = convertVerticalDLUsToPixels(LABEL_DLUS);
+ gd.horizontalSpan = 2;
+ subTaskLabel.setLayoutData(gd);
+ subTaskLabel.setFont(parent.getFont());
+ return parent;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see dwtx.jface.window.Window#getInitialSize()
+ */
+ 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(bool, bool,
+ * IRunnableWithProgress) runs the given IRunnableWithProgress
+ * using the progress monitor for this progress dialog and blocks until the
+ * runnable has been run, regardless of the value of fork
.
+ * The dialog is opened before the runnable is run, and closed after it
+ * completes. It is recommended that fork
is set to true in
+ * most cases. If fork
is set to false
, the
+ * runnable will run in the UI thread and it is the runnable's
+ * responsibility to call Display.readAndDispatch()
to ensure
+ * UI responsiveness.
+ */
+ public void run(bool fork, bool cancelable,
+ IRunnableWithProgress runnable) {
+ 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 true
+ *
+ * @return true
to open the dialog before run,
+ * false
to only create the dialog, but not open it
+ * @since 3.0
+ */
+ public bool 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
+ * true
to open the dialog before run,
+ * false
to only create the dialog, but not open
+ * it
+ * @since 3.0
+ */
+ public void setOpenOnRun(bool 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 getOpenOnRun
,
+ * 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
+ * true
if the end user can cancel this progress
+ * dialog, and false
if it cannot be canceled
+ */
+ public void setCancelable(bool cancelable) {
+ if (cancel is null) {
+ enableCancelButton = cancelable;
+ } else {
+ asyncSetOperationCancelButtonEnabled(cancelable);
+ }
+ }
+
+ /**
+ * Helper to enable/disable Cancel button for this dialog.
+ *
+ * @param b
+ * true
to enable the cancel button, and
+ * false
to disable it
+ * @since 3.0
+ */
+ protected void setOperationCancelButtonEnabled(bool b) {
+ operationCancelableState = b;
+ cancel.setEnabled(b);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see dwtx.jface.dialogs.IconAndMessageDialog#getImage()
+ */
+ 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, bool force) {
+ // must not set null text in a label
+ message = messageString is null ? "" : messageString; //$NON-NLS-1$
+ if (messageLabel is 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 is null || messageLabel.isDisposed()) {
+ return;
+ }
+ messageLabel.update();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see dwtx.jface.window.Window#open()
+ */
+ public int open() {
+ // Check to be sure it is not already done. If it is just return OK.
+ if (!getOpenOnRun()) {
+ if (getNestingDepth() is 0) {
+ return OK;
+ }
+ }
+ int result = super.open();
+ // update message label just in case beginTask() has been invoked already
+ if (task is null || task.length is 0)
+ setMessage(DEFAULT_TASKNAME, true);
+ else
+ setMessage(task, true);
+ return result;
+ }
+}