Mercurial > projects > dwt-addons
diff dwtx/jface/operation/ModalContext.d @ 6:1a6747be662d
Jface operations
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Fri, 28 Mar 2008 19:31:01 +0100 |
parents | |
children | ea8ff534f622 |
line wrap: on
line diff
--- /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; + } +}