changeset 6:1a6747be662d

Jface operations
author Frank Benoit <benoit@tionex.de>
date Fri, 28 Mar 2008 19:31:01 +0100
parents 103ab03b77eb
children 8a302fdb4140
files dwtx/jface/contexts/IContextIds.d dwtx/jface/internal/InternalPolicy.d dwtx/jface/operation/AccumulatingProgressMonitor.d dwtx/jface/operation/IRunnableContext.d dwtx/jface/operation/IRunnableWithProgress.d dwtx/jface/operation/IThreadListener.d dwtx/jface/operation/ModalContext.d
diffstat 7 files changed, 929 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/contexts/IContextIds.d	Fri Mar 28 19:31:01 2008 +0100
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.contexts.IContextIds;
+
+import dwt.dwthelper.utils;
+
+/**
+ * <p>
+ * A list of well-known context identifiers. The context identifiers use the
+ * prefix "dwtx.ui" for historical reasons. These contexts exist as part
+ * of JFace.
+ * </p>
+ * <p>
+ * This interface should not be implemented or extended by clients.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IContextIds {
+
+    /**
+     * The identifier for the context that is active when a shell registered as
+     * a dialog.
+     */
+    public static const String CONTEXT_ID_DIALOG = "dwtx.ui.contexts.dialog"; //$NON-NLS-1$
+
+    /**
+     * The identifier for the context that is active when a shell is registered
+     * as either a window or a dialog.
+     */
+    public static const String CONTEXT_ID_DIALOG_AND_WINDOW = "dwtx.ui.contexts.dialogAndWindow"; //$NON-NLS-1$
+
+    /**
+     * The identifier for the context that is active when a shell is registered
+     * as a window.
+     */
+    public static const String CONTEXT_ID_WINDOW = "dwtx.ui.contexts.window"; //$NON-NLS-1$
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/internal/InternalPolicy.d	Fri Mar 28 19:31:01 2008 +0100
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2007 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ ******************************************************************************/
+module dwtx.jface.internal.InternalPolicy;
+
+import tango.util.collection.model.Map;
+
+/**
+ * Internal class used for non-API debug flags.
+ *
+ * @since 3.3
+ */
+public class InternalPolicy {
+
+    /**
+     * (NON-API) A flag to indicate whether reentrant viewer calls should always be
+     * logged. If false, only the first reentrant call will cause a log entry.
+     *
+     * @since 3.3
+     */
+    public static bool DEBUG_LOG_REENTRANT_VIEWER_CALLS = false;
+
+    /**
+     * (NON-API) Instead of logging current conflicts they can be
+     * held here.  If there is a problem, they can be reported then.
+     */
+    public static Map!(Object/+???+/,Object/+???+/) currentConflicts = null;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/operation/AccumulatingProgressMonitor.d	Fri Mar 28 19:31:01 2008 +0100
@@ -0,0 +1,272 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.operation.AccumulatingProgressMonitor;
+
+
+import dwt.widgets.Display;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.IProgressMonitor;
+import dwtx.core.runtime.IProgressMonitorWithBlocking;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.ProgressMonitorWrapper;
+//FIXME
+// import dwtx.jface.dialogs.Dialog;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+
+/**
+ * A progress monitor that accumulates <code>worked</code> and <code>subtask</code>
+ * calls in the following way by wrapping a standard progress monitor:
+ * <ul>
+ * <li> When a <code>worked</code> or <code>subtask</code> call occurs the first time,
+ *      the progress monitor posts a runnable into the asynchronous DWT event queue.
+ * </li>
+ * <li> Subsequent calls to <code>worked</code> or <code>subtask</code> do not post
+ *      a new runnable as long as a previous runnable still exists in the DWT event
+ *      queue. In this case, the progress monitor just updates the internal state of
+ *      the runnable that waits in the DWT event queue for its execution. If no runnable
+ *      exists, a new one is created and posted into the event queue.
+ * </ul>
+ * <p>
+ * This class is internal to the framework; clients outside JFace should not
+ * use this class.
+ * </p>
+ */
+/* package */class AccumulatingProgressMonitor : ProgressMonitorWrapper {
+
+    /**
+     * The display.
+     */
+    private Display display;
+
+    /**
+     * The collector, or <code>null</code> if none.
+     */
+    private Collector collector;
+
+    private String currentTask = ""; //$NON-NLS-1$
+
+    private class Collector : Runnable {
+        private String subTask_;
+
+        private double worked_;
+
+        private IProgressMonitor monitor;
+
+        /**
+         * Create a new collector.
+         * @param subTask
+         * @param work
+         * @param monitor
+         */
+        public this(String subTask_, double work, IProgressMonitor monitor) {
+            this.subTask_ = subTask_;
+            this.worked_ = work;
+            this.monitor = monitor;
+        }
+
+        /**
+         * Add worked to the work.
+         * @param workedIncrement
+         */
+        public void worked(double workedIncrement) {
+            this.worked_ = this.worked_ + workedIncrement;
+        }
+
+        /**
+         * Set the subTask name.
+         * @param subTaskName
+         */
+        public void subTask(String subTaskName) {
+            this.subTask_ = subTaskName;
+        }
+
+        /**
+         * Run the collector.
+         */
+        public void run() {
+            clearCollector(this);
+            if (subTask_ !is null) {
+                monitor.subTask(subTask_);
+            }
+            if (worked_ > 0) {
+                monitor.internalWorked(worked_);
+            }
+        }
+    }
+
+    /**
+     * Creates an accumulating progress monitor wrapping the given one
+     * that uses the given display.
+     *
+     * @param monitor the actual progress monitor to be wrapped
+     * @param display the DWT display used to forward the calls
+     *  to the wrapped progress monitor
+     */
+    public this(IProgressMonitor monitor, Display display) {
+        super(monitor);
+        Assert.isNotNull(display);
+        this.display = display;
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IProgressMonitor.
+     */
+    public void beginTask(String name, int totalWork) {
+        synchronized (this) {
+            collector = null;
+        }
+        display.syncExec(new class Runnable {
+            public void run() {
+                currentTask = name;
+                getWrappedProgressMonitor().beginTask(name, totalWork);
+            }
+        });
+    }
+
+    /**
+     * Clears the collector object used to accumulate work and subtask calls
+     * if it matches the given one.
+     * @param collectorToClear
+     */
+    private synchronized void clearCollector(Collector collectorToClear) {
+        // Check if the accumulator is still using the given collector.
+        // If not, don't clear it.
+        if (this.collector is collectorToClear) {
+            this.collector = null;
+        }
+    }
+
+    /**
+     *  Creates a collector object to accumulate work and subtask calls.
+     * @param subTask
+     * @param work
+     */
+    private void createCollector(String subTask, double work) {
+        collector = new Collector(subTask, work, getWrappedProgressMonitor());
+        display.asyncExec(collector);
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IProgressMonitor.
+     */
+    public void done() {
+        synchronized (this) {
+            collector = null;
+        }
+        display.syncExec(new class Runnable {
+            public void run() {
+                getWrappedProgressMonitor().done();
+            }
+        });
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IProgressMonitor.
+     */
+    public synchronized void internalWorked(double work) {
+        if (collector is null) {
+            createCollector(null, work);
+        } else {
+            collector.worked(work);
+        }
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IProgressMonitor.
+     */
+    public void setTaskName(String name) {
+        synchronized (this) {
+            collector = null;
+        }
+        display.syncExec(new class Runnable {
+            public void run() {
+                currentTask = name;
+                getWrappedProgressMonitor().setTaskName(name);
+            }
+        });
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IProgressMonitor.
+     */
+    public synchronized void subTask(String name) {
+        if (collector is null) {
+            createCollector(name, 0);
+        } else {
+            collector.subTask(name);
+        }
+    }
+
+    /* (non-Javadoc)
+     * Method declared on IProgressMonitor.
+     */
+    public synchronized void worked(int work) {
+        internalWorked(work);
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.core.runtime.ProgressMonitorWrapper#clearBlocked()
+     */
+    public void clearBlocked() {
+
+        //If this is a monitor that can report blocking do so.
+        //Don't bother with a collector as this should only ever
+        //happen once and prevent any more progress.
+        IProgressMonitor pm = getWrappedProgressMonitor();
+        if (!(cast(IProgressMonitorWithBlocking)pm )) {
+            return;
+        }
+
+        display.asyncExec(new class Runnable {
+            IProgressMonitor pm_;
+            this(){ pm_=pm; }
+            /* (non-Javadoc)
+             * @see java.lang.Runnable#run()
+             */
+            public void run() {
+                (cast(IProgressMonitorWithBlocking) pm_).clearBlocked();
+//FIXME
+//                 Dialog.getBlockedHandler().clearBlocked();
+            }
+        });
+    }
+
+    /* (non-Javadoc)
+     * @see dwtx.core.runtime.ProgressMonitorWrapper#setBlocked(dwtx.core.runtime.IStatus)
+     */
+    public void setBlocked(IStatus reason) {
+        //If this is a monitor that can report blocking do so.
+        //Don't bother with a collector as this should only ever
+        //happen once and prevent any more progress.
+        IProgressMonitor pm = getWrappedProgressMonitor();
+        if (!(cast(IProgressMonitorWithBlocking)pm )) {
+            return;
+        }
+
+        display.asyncExec(new class Runnable {
+            IProgressMonitor pm_;
+            this(){ pm_=pm; }
+            /* (non-Javadoc)
+             * @see java.lang.Runnable#run()
+             */
+            public void run() {
+                (cast(IProgressMonitorWithBlocking) pm_).setBlocked(reason);
+                //Do not give a shell as we want it to block until it opens.
+//FIXME
+//                 Dialog.getBlockedHandler().showBlocked(pm, reason, currentTask);
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/operation/IRunnableContext.d	Fri Mar 28 19:31:01 2008 +0100
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.operation.IRunnableContext;
+
+import dwtx.jface.operation.IRunnableWithProgress;
+
+import dwt.dwthelper.utils;
+
+/**
+ * Interface for UI components which can execute a long-running operation
+ * in the form of an <code>IRunnableWithProgress</code>.
+ * The context is responsible for displaying a progress indicator and Cancel
+ * button to the end user while the operation is in progress; the context
+ * supplies a progress monitor to be used from code running inside the operation.
+ * Note that an <code>IRunnableContext</code> is not a runnable itself.
+ * <p>
+ * For examples of UI components which implement this interface,
+ * see <code>ApplicationWindow</code>, <code>ProgressMonitorDialog</code>,
+ * and <code>WizardDialog</code>.
+ * </p>
+ *
+ * @see IRunnableWithProgress
+ * @see dwtx.jface.window.ApplicationWindow
+ * @see dwtx.jface.dialogs.ProgressMonitorDialog
+ * @see dwtx.jface.wizard.WizardDialog
+ */
+public interface IRunnableContext {
+    /**
+     * <p>
+     * Runs the given <code>IRunnableWithProgress</code> in this context.
+     * For example, if this is a <code>ProgressMonitorDialog</code> then the runnable
+     * is run using this dialog's progress monitor.
+     * </p>
+     * <p>
+     * If <code>fork</code> is <code>false</code>, the current thread is used
+     * to run the runnable. Note that if <code>fork</code> is <code>true</code>,
+     * it is unspecified whether or not this method blocks until the runnable
+     * has been run. Implementers should document whether the runnable is run
+     * synchronously (blocking) or asynchronously (non-blocking), or if no
+     * assumption can be made about the blocking behaviour.
+     * </p>
+     *
+     * @param fork <code>true</code> if the runnable should be run in a separate thread,
+     *  and <code>false</code> to run in the same thread
+     * @param cancelable <code>true</code> to enable the cancelation, and
+     *  <code>false</code> to make the operation uncancellable
+     * @param runnable the runnable to run
+     *
+     * @exception InvocationTargetException wraps any exception or error which occurs
+     *  while running the runnable
+     * @exception InterruptedException propagated by the context if the runnable
+     *  acknowledges cancelation by throwing this exception.  This should not be thrown
+     *  if cancelable is <code>false</code>.
+     */
+    public void run(bool fork, bool cancelable,
+            IRunnableWithProgress runnable);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/operation/IRunnableWithProgress.d	Fri Mar 28 19:31:01 2008 +0100
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.operation.IRunnableWithProgress;
+
+import dwtx.core.runtime.IProgressMonitor;
+
+import dwt.dwthelper.utils;
+
+/**
+ * The <code>IRunnableWithProgress</code> interface should be implemented by any
+ * class whose instances are intended to be executed as a long-running operation.
+ * Long-running operations are typically presented at the UI via a modal dialog
+ * showing a progress indicator and a Cancel button.
+ * The class must define a <code>run</code> method that takes a progress monitor.
+ * The <code>run</code> method is usually not invoked directly, but rather by
+ * passing the <code>IRunnableWithProgress</code> to the <code>run</code> method of
+ * an <code>IRunnableContext</code>, which provides the UI for the progress monitor
+ * and Cancel button.
+ *
+ * @see IRunnableContext
+ */
+public interface IRunnableWithProgress {
+    /**
+     * Runs this operation.  Progress should be reported to the given progress monitor.
+     * This method is usually invoked by an <code>IRunnableContext</code>'s <code>run</code> method,
+     * which supplies the progress monitor.
+     * A request to cancel the operation should be honored and acknowledged
+     * by throwing <code>InterruptedException</code>.
+     *
+     * @param monitor the progress monitor to use to display progress and receive
+     *   requests for cancelation
+     * @exception InvocationTargetException if the run method must propagate a checked exception,
+     *  it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
+     *  wrapped in an <code>InvocationTargetException</code> by the calling context
+     * @exception InterruptedException if the operation detects a request to cancel,
+     *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
+     *  <code>InterruptedException</code>
+     *
+     * @see IRunnableContext#run
+     */
+    public void run(IProgressMonitor monitor);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/operation/IThreadListener.d	Fri Mar 28 19:31:01 2008 +0100
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.operation.IThreadListener;
+
+import dwt.dwthelper.utils;
+import tango.core.Thread;
+
+/**
+ * A thread listener is an object that is interested in receiving notifications
+ * of thread changes.  For example, a thread listener can be used to notify a
+ * runnable of the thread that will execute it, allowing the runnable to transfer
+ * thread-local state from the calling thread before control passes to the new thread.
+ *
+ * @since 3.1
+ */
+public interface IThreadListener {
+    /**
+     * Notification that a thread change is occurring.
+     *
+     * @param thread The new thread
+     */
+    public void threadChange(Thread thread);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/operation/ModalContext.d	Fri Mar 28 19:31:01 2008 +0100
@@ -0,0 +1,420 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.operation.ModalContext;
+
+import dwt.widgets.Display;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.IProgressMonitor;
+import dwtx.core.runtime.OperationCanceledException;
+import dwtx.core.runtime.ProgressMonitorWrapper;
+
+import dwtx.jface.operation.IThreadListener;
+import dwtx.jface.operation.IRunnableWithProgress;
+import dwtx.jface.operation.AccumulatingProgressMonitor;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.Runnable;
+import tango.core.Thread;
+import tango.io.Stdout;
+
+/**
+ * Utility class for supporting modal operations.
+ * The runnable passed to the <code>run</code> method is executed in a
+ * separate thread, depending on the value of the passed fork argument.
+ * If the runnable is executed in a separate thread then the current thread
+ * either waits until the new thread ends or, if the current thread is the
+ * UI thread, it polls the DWT event queue and dispatches each event.
+ * <p>
+ * This class is not intended to be subclassed.
+ * </p>
+ */
+public class ModalContext {
+
+    /**
+     * Indicated whether ModalContext is in debug mode;
+     * <code>false</code> by default.
+     */
+    private static bool debug_ = false;
+
+    /**
+     * The number of nested modal runs, or 0 if not inside a modal run.
+     * This is global state.
+     */
+    private static int modalLevel = 0;
+
+    /**
+     * Indicates whether operations should be run in a separate thread.
+     * Defaults to true.
+     * For internal debugging use, set to false to run operations in the calling thread.
+     */
+    private static bool runInSeparateThread = true;
+
+    /**
+     * Thread which runs the modal context.
+     */
+    private static class ModalContextThread : Thread {
+        /**
+         * The operation to be run.
+         */
+        private IRunnableWithProgress runnable;
+
+        /**
+         * The exception thrown by the operation starter.
+         */
+        private Exception throwable;
+
+        /**
+         * The progress monitor used for progress and cancelation.
+         */
+        private IProgressMonitor progressMonitor;
+
+        /**
+         * The display used for event dispatching.
+         */
+        private Display display;
+
+        /**
+         * Indicates whether to continue event queue dispatching.
+         */
+        private /+volatile+/ bool continueEventDispatching = true;
+
+        /**
+         * The thread that forked this modal context thread.
+         *
+         * @since 3.1
+         */
+        private Thread callingThread;
+
+        /**
+         * Creates a new modal context.
+         *
+         * @param operation the runnable to run
+         * @param monitor the progress monitor to use to display progress and receive
+         *   requests for cancelation
+         * @param display the display to be used to read and dispatch events
+         */
+        private this(IRunnableWithProgress operation,
+                IProgressMonitor monitor, Display display) {
+            super(/+"ModalContext"+/); //$NON-NLS-1$
+            Assert.isTrue(monitor !is null && display !is null);
+            runnable = operation;
+            progressMonitor = new AccumulatingProgressMonitor(monitor, display);
+            this.display = display;
+            this.callingThread = Thread.getThis();
+        }
+
+        /* (non-Javadoc)
+         * Method declared on Thread.
+         */
+        public void run() {
+            try {
+                if (runnable !is null) {
+                    runnable.run(progressMonitor);
+                }
+            /+
+            } catch (InvocationTargetException e) {
+                throwable = e;
+            } catch (InterruptedException e) {
+                throwable = e;
+            } catch (RuntimeException e) {
+                throwable = e;
+            } catch (ThreadDeath e) {
+                // Make sure to propagate ThreadDeath, or threads will never fully terminate
+                throw e;
+            +/
+            } catch (/+Error+/Exception e) {
+                throwable = e;
+            } finally {
+                //notify the operation of change of thread of control
+                if ( auto tl = cast(IThreadListener)runnable ) {
+                    tl.threadChange(callingThread);
+                }
+
+                // Make sure that all events in the asynchronous event queue
+                // are dispatched.
+                display.syncExec(new class() Runnable {
+                    public void run() {
+                        // do nothing
+                    }
+                });
+
+                // Stop event dispatching
+                continueEventDispatching = false;
+
+                // Force the event loop to return from sleep () so that
+                // it stops event dispatching.
+                display.asyncExec(null);
+            }
+        }
+
+        /**
+         * Processes events or waits until this modal context thread terminates.
+         */
+        public void block() {
+            if (display is Display.getCurrent()) {
+                while (continueEventDispatching) {
+                    // Run the event loop.  Handle any uncaught exceptions caused
+                    // by UI events.
+                    try {
+                        if (!display.readAndDispatch()) {
+                            display.sleep();
+                        }
+                    }
+                    /+
+                    // ThreadDeath is a normal error when the thread is dying.  We must
+                    // propagate it in order for it to properly terminate.
+                    catch (ThreadDeath e) {
+                        throw (e);
+                    }
+                    +/
+                    // For all other exceptions, log the problem.
+                    catch (Exception e) {
+                        Stderr.formatln("Unhandled event loop exception during blocked modal context."); //$NON-NLS-1$
+                        ExceptionPrintStackTrace(e);
+                    }
+                }
+            } else {
+                try {
+                    join();
+                } catch (Exception e) {
+                    throwable = e;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether the first progress monitor is the same as, or
+     * a wrapper around, the second progress monitor.
+     *
+     * @param monitor1 the first progress monitor
+     * @param monitor2 the second progress monitor
+     * @return <code>true</code> if the first is the same as, or
+     *   a wrapper around, the second
+     * @see ProgressMonitorWrapper
+     */
+    public static bool canProgressMonitorBeUsed(IProgressMonitor monitor1,
+            IProgressMonitor monitor2) {
+        if (monitor1 is monitor2) {
+            return true;
+        }
+
+        while ( cast(ProgressMonitorWrapper)monitor1 ) {
+            monitor1 = (cast(ProgressMonitorWrapper)monitor1)
+                    .getWrappedProgressMonitor();
+            if (monitor1 is monitor2) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks with the given progress monitor and throws
+     * <code>InterruptedException</code> if it has been canceled.
+     * <p>
+     * Code in a long-running operation should call this method
+     * regularly so that a request to cancel will be honored.
+     * </p>
+     * <p>
+     * Convenience for:
+     * <pre>
+     * if (monitor.isCanceled())
+     *    throw new InterruptedException();
+     * </pre>
+     * </p>
+     *
+     * @param monitor the progress monitor
+     * @exception InterruptedException if cancelling the operation has been requested
+     * @see IProgressMonitor#isCanceled()
+     */
+    public static void checkCanceled(IProgressMonitor monitor) {
+        if (monitor.isCanceled()) {
+            throw new InterruptedException();
+        }
+    }
+
+    /**
+     * Returns the currently active modal context thread, or null if no modal context is active.
+     */
+    private static ModalContextThread getCurrentModalContextThread() {
+        Thread t = Thread.getThis();
+        if ( auto r = cast(ModalContextThread)t ) {
+            return r;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the modal nesting level.
+     * <p>
+     * The modal nesting level increases by one each time the
+     * <code>ModalContext.run</code> method is called within the
+     * dynamic scope of another call to <code>ModalContext.run</code>.
+     * </p>
+     *
+     * @return the modal nesting level, or <code>0</code> if
+     *  this method is called outside the dynamic scope of any
+     *  invocation of <code>ModalContext.run</code>
+     */
+    public static int getModalLevel() {
+        return modalLevel;
+    }
+
+    /**
+     * Returns whether the given thread is running a modal context.
+     *
+     * @param thread The thread to be checked
+     * @return <code>true</code> if the given thread is running a modal context, <code>false</code> if not
+     */
+    public static bool isModalContextThread(Thread thread) {
+        return (cast(ModalContextThread)thread) !is null;
+    }
+
+    /**
+     * Runs the given runnable in a modal context, passing it a progress monitor.
+     * <p>
+     * The modal nesting level is increased by one from the perspective
+     * of the given runnable.
+     * </p>
+     *<p>
+     * If the supplied operation implements <code>IThreadListener</code>, it
+     * will be notified of any thread changes required to execute the operation.
+     * Specifically, the operation will be notified of the thread that will call its
+     * <code>run</code> method before it is called, and will be notified of the
+     * change of control back to the thread calling this method when the operation
+     * completes.  These thread change notifications give the operation an
+     * opportunity to transfer any thread-local state to the execution thread before
+     * control is transferred to the new thread.
+     *</p>
+     * @param operation the runnable to run
+     * @param fork <code>true</code> if the runnable should run in a separate thread,
+     *   and <code>false</code> if in the same thread
+     * @param monitor the progress monitor to use to display progress and receive
+     *   requests for cancelation
+     * @param display the display to be used to read and dispatch events
+     * @exception InvocationTargetException if the run method must propagate a checked exception,
+     *  it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions and errors are automatically
+     *  wrapped in an <code>InvocationTargetException</code> by this method
+     * @exception InterruptedException if the operation detects a request to cancel,
+     *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
+     *  <code>InterruptedException</code>; this method propagates the exception
+     */
+    public static void run(IRunnableWithProgress operation, bool fork,
+            IProgressMonitor monitor, Display display) {
+        Assert.isTrue(operation !is null && monitor !is null);
+
+        modalLevel++;
+        try {
+            if (monitor !is null) {
+                monitor.setCanceled(false);
+            }
+            // Is the runnable supposed to be execute in the same thread.
+            if (!fork || !runInSeparateThread) {
+                runInCurrentThread(operation, monitor);
+            } else {
+                ModalContextThread t = getCurrentModalContextThread();
+                if (t !is null) {
+                    Assert.isTrue(canProgressMonitorBeUsed(monitor,
+                            t.progressMonitor));
+                    runInCurrentThread(operation, monitor);
+                } else {
+                    t = new ModalContextThread(operation, monitor, display);
+                    if ( auto tl = cast(IThreadListener)operation ) {
+                        tl.threadChange(t);
+                    }
+                    t.start();
+                    t.block();
+                    Exception throwable = t.throwable;
+                    if (throwable !is null) {
+                        if (debug_
+                                && !(cast(InterruptedException)throwable )
+                                && !(cast(OperationCanceledException)throwable )) {
+                            Stderr.formatln("Exception in modal context operation:"); //$NON-NLS-1$
+                            ExceptionPrintStackTrace(throwable);
+                            Stderr.formatln("Called from:"); //$NON-NLS-1$
+                            // Don't create the InvocationTargetException on the throwable,
+                            // otherwise it will print its stack trace (from the other thread).
+                            ExceptionPrintStackTrace( new InvocationTargetException(null));
+                        }
+                        if (cast(InvocationTargetException)throwable ) {
+                            throw cast(InvocationTargetException) throwable;
+                        } else if (cast(InterruptedException)throwable ) {
+                            throw cast(InterruptedException) throwable;
+                        } else if (cast(OperationCanceledException)throwable ) {
+                            // See 1GAN3L5: ITPUI:WIN2000 - ModalContext converts OperationCancelException into InvocationTargetException
+                            throw new InterruptedException(throwable
+                                    .msg);
+                        } else {
+                            throw new InvocationTargetException(throwable);
+                        }
+                    }
+                }
+            }
+        } finally {
+            modalLevel--;
+        }
+    }
+
+    /**
+     * Run a runnable.  Convert all thrown exceptions to
+     * either InterruptedException or InvocationTargetException
+     */
+    private static void runInCurrentThread(IRunnableWithProgress runnable,
+            IProgressMonitor progressMonitor) {
+        try {
+            if (runnable !is null) {
+                runnable.run(progressMonitor);
+            }
+        } catch (InvocationTargetException e) {
+            throw e;
+        } catch (InterruptedException e) {
+            throw e;
+        } catch (OperationCanceledException e) {
+            throw new InterruptedException();
+        /+
+        } catch (ThreadDeath e) {
+            // Make sure to propagate ThreadDeath, or threads will never fully terminate
+            throw e;
+        +/
+        } catch (RuntimeException e) {
+            throw new InvocationTargetException(e);
+        } catch (/+Error+/Exception e) {
+            throw new InvocationTargetException(e);
+        }
+    }
+
+    /**
+     * Sets whether ModalContext is running in debug mode.
+     *
+     * @param debugMode <code>true</code> for debug mode,
+     *  and <code>false</code> for normal mode (the default)
+     */
+    public static void setDebugMode(bool debugMode) {
+        debug_ = debugMode;
+    }
+
+    /**
+     * Sets whether ModalContext may process events (by calling <code>Display.readAndDispatch()</code>)
+     * while running operations. By default, ModalContext will process events while running operations.
+     * Use this method to disallow event processing temporarily.
+     * @param allowReadAndDispatch <code>true</code> (the default) if events may be processed while
+     * running an operation, <code>false</code> if Display.readAndDispatch() should not be called
+     * from ModalContext.
+     * @since 3.2
+     */
+    public static void setAllowReadAndDispatch(bool allowReadAndDispatch) {
+        // use a separate thread if and only if it is OK to spin the event loop
+        runInSeparateThread = allowReadAndDispatch;
+    }
+}