/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.ui.common.viewsupport;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
/**
* UiBusyIndicator is a copy of {@link org.eclipse.swt.custom.BusyIndicator}
* that runs on the Display thread.
*
* The original BusyIndicator will not correctly display the busy cursor since
* it assumes the runnable being executed is a shell for another worker thread
* that uses Display.syncExec. Please refer to this <a
* href="http://www.eclipse.org/swt/snippets/#busyindicator">snippet</a>.
*
* To make this simpler, the whole thread logic has been inserted into this
* class so that clients can simply create a runnable with their work inside it.
*
* @since 8.0
*/
public abstract class UiBusyIndicator {
/**
* Encloses the done flag in an object, avoiding the need to synchronize
* (potential deadlocks) it and have multiple threads potentially changing
* the same done flag. Each execution of showWhile is responsible for
* setting the cursor back to its correct state and should always exit the
* Display.sleep() loop at the foot of the showWhile method.
*/
private static class RunnableStatus {
private boolean done = false;
/**
* @param done
* Sets done to the specified value.
*/
public void done() {
this.done = true;
}
/**
* @return done
*/
public boolean isDone() {
return done;
}
}
private static int nextBusyId = 1;
private static final String BUSYID_NAME = "UI BusyIndicator"; //$NON-NLS-1$
/**
* Runs the given <code>Runnable</code> on the Display thread while
* providing busy feedback using this busy indicator.
*
* This method is synchronized to make it thread safe, ie. should two
* threads call it in parallel then the second will have to wait for the
* first to finish thereby ensuring that both runnables have completed
* gracefully.
*
* @param display
* the display on which the busy feedback should be displayed. If
* the display is null, the Display for the current thread will
* be used. If there is no Display for the current thread, the
* runnable code will be executed and no busy feedback will be
* displayed.
* @param runnable
* the runnable for which busy feedback is to be shown. Must not
* be null.
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the runnable is null</li>
* </ul>
*/
public static synchronized void showWhile(Display display,
final Runnable runnable) {
// ensure runnable is not null
if (runnable == null)
SWT.error(SWT.ERROR_NULL_ARGUMENT);
// ensure we can get a valid Display object
if (display == null) {
display = Display.getCurrent() != null ? Display.getCurrent()
: Display.getDefault();
if (display == null) {
// This just should not happen ...
runnable.run();
return;
}
}
/*
* ensure this method is called only on the display's event dispatch
* thread
*/
if (display.getThread() != Thread.currentThread()) {
final Display d = display;
display.syncExec(new Runnable() {
@Override
public void run() {
showWhileInternal(d, runnable);
}
});
return;
}
else {
showWhileInternal(display, runnable);
}
}
/**
* Internal method that change the cursor to WAITING, executes the runnable
* and changes the cursor back again. Unlike
* {@link org.eclipse.swt.custom.BusyIndicator}, the
* {@link #executeRunnable(Display, Runnable, RunnableStatus)} will allow
* the WAITING icon to actually display before executing the runnable.
*
* @param display
* @param runnable
*/
private static void showWhileInternal(Display display,
final Runnable runnable) {
if (PlatformUI.getWorkbench().isStarting()) {
/*
* Need to avoid calling executeRunnable on startup since it creates
* a new thread and uses display.syncExec. Since this thread is
* non-priveleged, the syncExec gets blocked until startup has
* completed. However, if showWhile() was called from a privileged
* startup thread that requires this to complete, a deadlock can
* result. Thus, we avoid this by simply running the runnable.
*/
runnable.run();
return;
}
Integer busyId = new Integer(nextBusyId);
nextBusyId++;
Shell[] shells = display.getShells();
for (int i = 0; i < shells.length; i++) {
Integer id = (Integer) shells[i].getData(BUSYID_NAME);
if (id == null) {
shells[i].setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
shells[i].setData(BUSYID_NAME, busyId);
}
}
try {
executeRunnable(display, runnable, new RunnableStatus());
}
finally {
shells = display.getShells();
for (int i = 0; i < shells.length; i++) {
Integer id = (Integer) shells[i].getData(BUSYID_NAME);
if (busyId.equals(id)) {
shells[i].setCursor(null);
shells[i].setData(BUSYID_NAME, null);
}
}
}
}
/**
* Invokes the given {@link Runnable} on its own thread but calls the
* {@link Runnable#run()} method using a {@link Display#syncExec(Runnable)}.
*
* This ensures that the runnable's work is performed on the Display thread
* but that the Display is also given a chance to update itself both before
* and after the work, allowing the cursor icon to properly change to its
* WAITING version.
*
* This method should NOT be allowed to execute while eclipse is starting up
* since the display.syncExec in its child thread can cause deadlocks and
* eclipse will hang. This is guarded against in
* {@link #showWhileInternal(Display, Runnable)}.
*
* @param runnable
*/
private static void executeRunnable(final Display display,
final Runnable runnable, final RunnableStatus runnableStatus) {
String threadName = UiBusyIndicator.class.getName()
+ " runnable parent thread"; //$NON-NLS-1$
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if (display.isDisposed())
return;
display.syncExec(new Runnable() {
@Override
public void run() {
try {
runnable.run();
}
finally {
/*
* The runnable may throw an exception, which is up
* to the developer of the runnable to solve. We
* need to simply ensure that done is always true
* and the display is awakened.
*/
runnableStatus.done();
display.wake();
}
}
});
}
}, threadName);
thread.setDaemon(true);
thread.start();
while (!runnableStatus.isDone() && !shellsDisposed(display)) {
if (!display.readAndDispatch())
display.sleep();
}
}
/**
* Determines if even one of the {@link Shell}s on the {@link Display} are
* disposed.
*
* @param display
* @return
*/
private static boolean shellsDisposed(Display display) {
Shell[] shells = display.getShells();
for (Shell shell : shells) {
if (shell.isDisposed())
return true;
}
return false;
}
}