package ika.gui;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JDialog;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
/**
*
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public abstract class TerrainSculptorProgressIndicator<T> extends SwingWorker<T, Integer>
implements ProgressIndicator {
/**
* The GUI. Must be accessed by the Swing thread only.
*/
protected TerrainSculptorProgressPanel progressPanel;
/**
* The dialog to display the progressPanel. Must be accessed by the Swing thread only.
*/
protected JDialog dialog;
/**
* The owner frame of the dialog
*/
protected Frame owner;
/**
* aborted is true after abort() is called. Access to aborted must be synchronized.
*/
private boolean aborted = false;
/**
* flag to remember whether the duration of the task is indeterminate.
*/
private boolean indeterminate;
/**
* The number of tasks to excecute. The default is 1.
*/
private int totalTasksCount = 1;
/**
* The ID of the current task.
*/
private int currentTask = 1;
/**
* If an operation takes less time than maxTimeWithoutDialog, no dialog is
* shown. Unit: milliseconds.
*/
private int maxTimeWithoutDialog = 2000;
/**
* Time in milliseconds when the operation started.
*/
private long startTime;
/**
* Must be called in the Event Dispatching Thread.
* @param owner
* @param dialogTitle
* @param message
* @param blockOwner
*/
public TerrainSculptorProgressIndicator(Frame owner,
String dialogTitle,
String message,
boolean blockOwner) {
assert(SwingUtilities.isEventDispatchThread());
this.owner = owner;
// prepare the dialog
dialog = new JDialog(owner);
dialog.setTitle(dialogTitle);
dialog.setModal(blockOwner);
dialog.setResizable(false);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
Action cancelAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
abort();
}
};
progressPanel = new TerrainSculptorProgressPanel();
progressPanel.setMessage(message);
progressPanel.setCancelAction(cancelAction);
dialog.setContentPane(progressPanel);
dialog.pack();
dialog.setLocationRelativeTo(owner);
dialog.setName(ProgressPanel.DIALOG_NAME);
dialog.setAlwaysOnTop(true);
}
/**
* Initialize the dialog. Can be called from any thread.
*/
public void start() {
synchronized (this) {
this.aborted = false;
this.startTime = System.currentTimeMillis();
}
// start a timer task that will show the dialog a little later
// in case the client does not call progress() for a longer period.
new ShowDialogTask().start();
// initialize the GUI in the event dispatching thread
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressPanel.start();
}
});
}
/**
* Stop the operation. The dialog will be hidden and the operation will
* stop as soon as possible. This might not happen synchronously. Can be
* called from any thread.
*/
public void abort() {
synchronized (this) {
// the client has to regularly check the aborted flag.
this.aborted = true;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
dialog.setVisible(false);
}
});
}
/**
* Inform the dialog that the operation has completed and it can be hidden.
*/
public void complete() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressPanel.removeActionListeners();
dialog.setVisible(false);
}
});
}
/**
* Update the progress indicator.
* @param percentage A value between 0 and 100.
* @return True if the operation should continue, false otherwise.
*/
public boolean progress(int percentage) {
this.setProgress(percentage);
this.publish(percentage);
return !this.isAborted();
}
/**
* Returns whether this operation should stop.
* @return
*/
public boolean isAborted() {
synchronized (this) {
return this.aborted;
}
}
public void disableCancel() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressPanel.disableCancel();
}
});
}
public void enableCancel() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressPanel.enableCancel();
}
});
}
public void setMessage(final String msg) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressPanel.setMessage(msg);
}
});
}
/**
* Invoked when the task's publish() method is called. Update the value displayed
* by the progress dialog. This is called from within the event dispatching thread.
*/
@Override
protected void process(List<Integer> progressList) {
if (isAborted()) {
return;
}
int progress = progressList.get(progressList.size() - 1).intValue();
progress = progress / totalTasksCount + (currentTask - 1) * 100 / totalTasksCount;
if (dialog.isVisible()) {
this.progressPanel.updateProgressGUI(progress);
} else {
// make the dialog visible if necessary
// Don't show the dialog for short operations.
// Only show it when half of maxTimeWithoutDialog has passed
// and the operation has not yet completed half of its task.
final long currentTime = System.currentTimeMillis();
if (currentTime - startTime > maxTimeWithoutDialog / 2 && progress < 50) {
// show the dialog
dialog.pack();
dialog.setVisible(true);
}
}
}
public void setDeferredFiltering(boolean deferredFiltering) {
progressPanel.setDeferredFiltering(deferredFiltering);
}
public boolean isDeferredFiltering() {
return progressPanel.isDeferredFiltering();
}
/** ShowDialogTask is a timer that makes sure the progress dialog
* is shown if progress() is not called for a long time. In this case, the
* dialog would never become visible.
*/
private class ShowDialogTask implements ActionListener {
private ShowDialogTask() {
}
/**
* Start a timer that will show the dialog in maxTimeWithoutDialog
* milliseconds if the dialog is not visible until then.
*/
private void start() {
javax.swing.Timer timer = new javax.swing.Timer(getMaxTimeWithoutDialog(), this);
timer.setRepeats(false);
timer.start();
}
/**
* Show the dialog if it is not visible yet and if the operation has not
* yet finished.
*/
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// only make the dialog visible if the operation is not
// yet finished
if (isAborted() || dialog.isVisible()) {
return;
}
// don't know how long the operation will take.
progressPanel.setIndeterminate(true);
// show the dialog
dialog.pack();
dialog.setVisible(true);
}
});
}
}
public int getMaxTimeWithoutDialog() {
return maxTimeWithoutDialog;
}
public void setMaxTimeWithoutDialog(int maxTimeWithoutDialog) {
this.maxTimeWithoutDialog = maxTimeWithoutDialog;
}
/**
* Sets the number of tasks. Each task has a progress between 0 and 100.
* If the number of tasks is larger than 1, progress of task 1 will be
* rescaled to 0..50.
* @param tasksCount The total number of tasks.
*/
public void setTotalTasksCount(int tasksCount) {
synchronized (this) {
this.totalTasksCount = tasksCount;
}
}
/**
* Returns the total numbers of tasks for this progress indicator.
* @return The total numbers of tasks.
*/
public int getTotalTasksCount() {
synchronized (this) {
return this.totalTasksCount;
}
}
/**
* Switch to the next task.
*/
public void nextTask() {
synchronized (this) {
++this.currentTask;
if (this.currentTask > this.totalTasksCount) {
this.totalTasksCount = this.currentTask;
}
// set progress to 0 for the new task, otherwise the progress bar would
// jump to the end of this new task and jump back when setProgress(0)
// is called
this.setProgress(0);
}
}
public void nextTask (String message) {
this.nextTask();
this.setMessage(message);
}
/**
* Returns the ID of the current task. The first task has ID 1 (and not 0).
* @return The ID of the current task.
*/
public int currentTask() {
synchronized (this) {
return this.currentTask;
}
}
public boolean isIndeterminate() {
synchronized (this) {
return this.indeterminate;
}
}
public void setIndeterminate(boolean indet) {
synchronized (this) {
this.indeterminate = indet;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressPanel.setIndeterminate(indeterminate);
}
});
}
}