package com.cedarsoft.spring.rcp.async;
import com.cedarsoft.CanceledException;
import com.cedarsoft.SwingHelper;
import com.cedarsoft.spring.SpringSupport;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.springframework.richclient.application.ApplicationWindow;
import org.springframework.richclient.application.splash.InfiniteProgressPanel;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Supports running tasks in background - but blocks the UI
*/
public final class BlockingBackgroundActionRunner implements Runnable {
@NotNull
private final InfiniteProgressPanel progressPanel = new InfiniteProgressPanel();
@NotNull
private final ApplicationWindow applicationWindow;
@NotNull
private final BackgroundAction action;
/**
* The delay before the progress is shown
*/
private final int delay;
/**
* Creates a new blocking background runner
*
* @param applicationWindow the applicationn window that is blocked
* @param action the background action that is executed
*/
public BlockingBackgroundActionRunner( @NotNull ApplicationWindow applicationWindow, @NotNull BackgroundAction action ) {
this( applicationWindow, action, true );
}
/**
* Creates a new blocking background runner
*
* @param applicationWindow the application window
* @param action the background action that is executed
* @param delay whether there should be used a delay
*/
public BlockingBackgroundActionRunner( @NotNull ApplicationWindow applicationWindow, @NotNull BackgroundAction action, boolean delay ) {
this.action = action;
this.applicationWindow = applicationWindow;
if ( delay ) {
this.delay = 1000;
} else {
this.delay = 0;
}
}
@NotNull
private final Lock lock = new ReentrantLock();
@NotNull
private final ReadWriteLock activeLock = new ReentrantReadWriteLock();
private boolean active;
/**
* Starts the blocking background runner.
* This method must be called from the EDT
*/
@Override
public void run() {
if ( !SwingHelper.isEventDispatchThread() ) {
SwingUtilities.invokeLater( this );
return;
}
SwingHelper.assertEventThread();
if ( !action.confirm() ) {
return;
}
action.prepare();
//Show the progress after the given delay (if set)
if ( delay > 0 ) {
Timer timer = new Timer( delay, new ActionListener() {
@Override
public void actionPerformed( ActionEvent e ) {
if ( isActive() ) {
showProgress();
}
}
} );
timer.setRepeats( false );
timer.start();
} else {
showProgress();
}
new Thread( new Runnable() {
@Override
public void run() {
try {
try {
setActive( true );
action.executeInBackground();
setActive( false );
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
action.finished();
}
} );
} catch ( CanceledException ignore ) {
} catch ( Exception e ) {
throw new RuntimeException( e );
}
} finally {
setActive( false );
hideProgress();
}
}
} ).start();
}
private void setActive( final boolean active ) {
activeLock.writeLock().lock();
try {
this.active = active;
} finally {
activeLock.writeLock().unlock();
}
}
@SuppressWarnings( {"MethodOnlyUsedFromInnerClass"} )
private boolean isActive() {
activeLock.readLock().lock();
try {
return active;
} finally {
activeLock.readLock().unlock();
}
}
private Component oldGlassPane;
void hideProgress() {
setActive( false );
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
lock.lock();
try {
if ( oldGlassPane == null ) {
return;
}
getApplicationWindow().getControl().setGlassPane( oldGlassPane );
} finally {
progressPanel.stop();
progressPanel.interrupt();
lock.unlock();
}
}
} );
}
@SuppressWarnings( {"MethodOnlyUsedFromInnerClass"} )
private void showProgress() {
lock.lock();
try {
setProgressMessage( action.getInitialProgressMessageKey() );
//Now store the old glass pane
oldGlassPane = getApplicationWindow().getControl().getGlassPane();
getApplicationWindow().getControl().setGlassPane( progressPanel );
getApplicationWindow().getControl().validate();
progressPanel.start();
} finally {
lock.unlock();
}
}
@NotNull
JFrame getParentWindowControl() {
return getApplicationWindow().getControl();
}
/**
* Updates the progress panel with the given message
*
* @param key the key for the message
* @param objects the objects
*/
public void setProgressMessage( @NotNull @NonNls String key, @NotNull Object... objects ) {
progressPanel.setText( SpringSupport.INSTANCE.getMessage( key, objects ) );
}
@NotNull
public ApplicationWindow getApplicationWindow() {
return applicationWindow;
}
}