From: BobHanson Date: Wed, 3 Jun 2020 13:43:52 +0000 (-0500) Subject: AsyncSwingWorker setpaused adjustment X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2FJalview-JS%2Fdevelop.JAL-3446.SequenceFeatureAsync;p=jalview.git AsyncSwingWorker setpaused adjustment --- diff --git a/src/jalview/gui/SequenceFetcher.java b/src/jalview/gui/SequenceFetcher.java index ded241b..70ad188 100755 --- a/src/jalview/gui/SequenceFetcher.java +++ b/src/jalview/gui/SequenceFetcher.java @@ -707,7 +707,6 @@ public class SequenceFetcher extends JPanel return; } super.setPaused(false); - super.stateLoop(); } private int runSubtask(Runnable subtask) diff --git a/src/javajs/async/AsyncSwingWorker.java b/src/javajs/async/AsyncSwingWorker.java index df103cd..f23da48 100644 --- a/src/javajs/async/AsyncSwingWorker.java +++ b/src/javajs/async/AsyncSwingWorker.java @@ -10,6 +10,8 @@ import javajs.async.SwingJSUtils.StateHelper; import javajs.async.SwingJSUtils.StateMachine; /** + * v. 2020.06.03 + * * Executes synchronous or asynchronous tasks using a SwingWorker in Java or * JavaScript, equivalently. * @@ -38,348 +40,392 @@ import javajs.async.SwingJSUtils.StateMachine; * the subclass to update the progress field in both the SwingWorker and the * ProgressMonitor. * - * If it is desired to run the AsyncSwingWorker synchonously, call the + * If it is desired to run the AsyncSwingWorker synchronously, call the * executeSynchronously() method rather than execute(). Never call * SwingWorker.run(). * + * Note that doInBackgroundAsync runs on the Java AWT event queue. This means + * that, unlike a true SwingWorker, it will run in event-queue sequence, after + * anything that that method itself adds to the queue. This is what SwingWorker itself + * does with its done() signal. + * + * If doInBackgroundAsync has tasks that are time intensive, the thing to do is to + * + * (a) pause this worker by setting the value of progress for the NEXT step: + * + * setProgressAsync(n); + * + * (b) pause the timer so that when doInBackgroundAsync returns, the timer is not fired: + * + * setPaused(true); + * + * (c) start your process as new Thread, which bypasses the AWT EventQueue: + * + * new Thread(Runnable).start(); + * + * (d) have your thread, when it is done, return control to this worker: + * + * setPaused(false); + * + * This final call restarts the worker with the currently specified progress value. * * @author hansonr * */ public abstract class AsyncSwingWorker extends SwingWorker implements StateMachine { - public static final String DONE_ASYNC = "DONE_ASYNC"; - public static final String CANCELED_ASYNC = "CANCELED_ASYNC"; - - protected int progressAsync; - - /** - * Override to provide initial tasks. - */ - abstract public void initAsync(); - - /** - * Given the last progress, do some portion of the task that the SwingWorker - * would do in the background, and return the new progress. returning max or - * above will complete the task. - * - * @param progress - * @return new progress - */ - abstract public int doInBackgroundAsync(int progress); - - /** - * Do something when the task is finished or canceled. - * - */ - abstract public void doneAsync(); - - protected ProgressMonitor progressMonitor; - - protected int delayMillis; - protected String note; - protected int min; - protected int max; - protected int progressPercent; - - protected boolean isAsync; - private Exception exception; - - /** - * Construct an asynchronous SwingWorker task that optionally will display a - * ProgressMonitor. Progress also can be monitored by adding a - * PropertyChangeListener to the AsyncSwingWorker and looking for the "progress" - * event, just the same as for a standard SwingWorker. - * - * @param owner optional owner for the ProgressMonitor, typically a JFrame - * or JDialog. - * - * @param title A non-null title indicates we want to use a - * ProgressMonitor with that title line. - * - * @param delayMillis A positive number indicating the delay we want before - * executions, during which progress will be reported. - * - * @param min The first progress value. No range limit. - * - * @param max The last progress value. No range limit; may be greater - * than min. - * - */ - public AsyncSwingWorker(Component owner, String title, int delayMillis, int min, int max) { - if (title != null && delayMillis > 0) { - progressMonitor = new ProgressMonitor(owner, title, "", Math.min(min, max), Math.max(min, max)); - progressMonitor.setProgress(Math.min(min, max)); // displays monitor - } - this.delayMillis = Math.max(0, delayMillis); - this.isAsync = (delayMillis > 0); - - this.min = min; - this.max = max; - } - - public void executeAsync() { - super.execute(); - } - - public void executeSynchronously() { - isAsync = false; - delayMillis = 0; - try { - doInBackground(); - } catch (Exception e) { - exception = e; - e.printStackTrace(); - cancelAsync(); - } - } - - public Exception getException() { - return exception; - } - - public int getMinimum() { - return min; - } - - public void setMinimum(int min) { - this.min = min; - if (progressMonitor != null) { - progressMonitor.setMinimum(min); - } - } - - public int getMaximum() { - return max; - } - - public void setMaximum(int max) { - if (progressMonitor != null) { - progressMonitor.setMaximum(max); - } - this.max = max; - } - - public int getProgressPercent() { - return progressPercent; - } - - public void setNote(String note) { - this.note = note; - if (progressMonitor != null) { - progressMonitor.setNote(note); - } - } - - /** - * Cancel the asynchronous process. - * - */ - public void cancelAsync() { - helper.interrupt(); - } - - /** - * Check to see if the asynchronous process has been canceled. - * - * @return true if StateHelper is not alive anymore - * - */ - public boolean isCanceledAsync() { - return !helper.isAlive(); - } - - /** - * Check to see if the asynchronous process is completely done. - * - * @return true only if the StateMachine is at STATE_DONE - * - */ - public boolean isDoneAsync() { - return helper.getState() == STATE_DONE; - } - - /** - * Override to set a more informed note for the ProcessMonitor. - * - * @param progress - * @return - */ - public String getNote(int progress) { - return String.format("Completed %d%%.\n", progress); - } - - /** - * Retrieve the last note delivered by the ProcessMonitor. - * - * @return - */ - public String getNote() { - return note; - } - - public int getProgressAsync() { - return progressAsync; - } - - /** - * Set the [min,max] progress safely. - * - * SwingWorker only allows progress between 0 and 100. This method safely - * translates [min,max] to [0,100]. - * - * @param n - */ - public void setProgressAsync(int n) { - n = (max > min ? Math.max(min, Math.min(n, max)) : Math.max(max, Math.min(n, min))); - progressAsync = n; - n = (n - min) * 100 / (max - min); - n = (n < 0 ? 0 : n > 100 ? 100 : n); - progressPercent = n; - } - - ///// the StateMachine ///// - - private final static int STATE_INIT = 0; - private final static int STATE_LOOP = 1; - private final static int STATE_WAIT = 2; - private final static int STATE_DONE = 99; - - private StateHelper helper; - - protected StateHelper getHelper() { - return helper; - } - - private boolean isPaused; - - protected void setPaused(boolean tf) { - isPaused = tf; - } - - protected boolean isPaused() { - return isPaused; - } - - /** - * The StateMachine's main loop. - * - * Note that a return from this method will exit doInBackground, trigger the - * isDone() state on the underying worker, and scheduling its done() for - * execution on the AWTEventQueue. - * - * Since this happens essentially immediately, it is unlikely that - * SwingWorker.isCancelled() will ever be true. Thus, the SwingWorker task - * itself won't be cancelable in Java or in JavaScript, since its - * doInBackground() method is officially complete, and isDone() is true well - * before we are "really" done. FutureTask will not set isCancelled() true once - * the task has run. - * - * We are using an asynchronous task specifically because we want to have the - * opportunity for the ProgressMonitor to report in JavaScript. We will have to - * cancel our task and report progress explicitly using our own methods. - * - */ - @Override - public boolean stateLoop() { - while (helper.isAlive() && !isPaused) { - switch (helper.getState()) { - case STATE_INIT: - setProgressAsync(min); - initAsync(); - helper.setState(STATE_WAIT); - continue; - case STATE_LOOP: - if (checkCanceled()) { - helper.setState(STATE_DONE); - firePropertyChange("state", null, CANCELED_ASYNC); - } else { - int ret = doInBackgroundAsync(progressAsync); - if (!helper.isAlive() || isPaused) { - continue; - } - progressAsync = ret; - setProgressAsync(progressAsync); - setNote(getNote(progressAsync)); - setProgress(progressPercent); - if (progressMonitor != null) { - progressMonitor.setProgress(max > min ? progressAsync : max + min - progressAsync); - } - helper.setState(progressAsync == max ? STATE_DONE : STATE_WAIT); - } - continue; - case STATE_WAIT: - helper.setState(STATE_LOOP); - helper.sleep(delayMillis); - return true; - default: - case STATE_DONE: - stopProgressMonitor(); - // Put the doneAsync() method on the AWTEventQueue - // just as for SwingWorker.done(). - if (isAsync) { - SwingUtilities.invokeLater(doneRunnable); - } else { - doneRunnable.run(); - } - - return false; - } - } - if (!helper.isAlive()) { - stopProgressMonitor(); - } - return false; - } - - private void stopProgressMonitor() { - if (progressMonitor != null) { - progressMonitor.close(); - progressMonitor = null; - } - } - - private Runnable doneRunnable = new Runnable() { - @Override - public void run() { - doneAsync(); - firePropertyChange("state", null, DONE_ASYNC); - } - - }; - - private boolean checkCanceled() { - if (isMonitorCanceled() || isCancelled()) { - helper.interrupt(); - return true; - } - return false; - } - - //// final SwingWorker methods not to be used by subclasses //// - - private boolean isMonitorCanceled() { - return (progressMonitor != null && progressMonitor.isCanceled()); - } - - /** - * see SwingWorker, made final here. - * - */ - @Override - final protected Void doInBackground() throws Exception { - helper = new StateHelper(this); - setProgressAsync(min); - helper.next(STATE_INIT); - return null; - } - - /** - * see SwingWorker, made final here. Nothing to do. - * - */ - @Override - final public void done() { - } + + // PropertyChangeEvent getPropertyName() + + private static final String PROPERTY_STATE = "state"; + private static final String PROPERTY_PAUSE = "pause"; + + // PropertyChangeEvent getNewValue() + + public static final String STARTED_ASYNC = "STARTED_ASYNC"; + public static final String STARTED_SYNC = "STARTED_SYNC"; + + public static final String DONE_ASYNC = "DONE_ASYNC"; + public static final String CANCELED_ASYNC = "CANCELED_ASYNC"; + + public static final String PAUSED = "PAUSED"; + public static final String RESUMED = "RESUMED"; + + protected int progressAsync; + + /** + * Override to provide initial tasks. + */ + abstract public void initAsync(); + + /** + * Given the last progress, do some portion of the task that the SwingWorker + * would do in the background, and return the new progress. returning max or + * above will complete the task. + * + * @param progress + * @return new progress + */ + abstract public int doInBackgroundAsync(int progress); + + /** + * Do something when the task is finished or canceled. + * + */ + abstract public void doneAsync(); + + protected ProgressMonitor progressMonitor; + + protected int delayMillis; + protected String note; + protected int min; + protected int max; + protected int progressPercent; + + protected boolean isAsync; + private Exception exception; + + /** + * Construct an asynchronous SwingWorker task that optionally will display a + * ProgressMonitor. Progress also can be monitored by adding a + * PropertyChangeListener to the AsyncSwingWorker and looking for the "progress" + * event, just the same as for a standard SwingWorker. + * + * @param owner optional owner for the ProgressMonitor, typically a JFrame + * or JDialog. + * + * @param title A non-null title indicates we want to use a + * ProgressMonitor with that title line. + * + * @param delayMillis A positive number indicating the delay we want before + * executions, during which progress will be reported. + * + * @param min The first progress value. No range limit. + * + * @param max The last progress value. No range limit; may be greater + * than min. + * + */ + public AsyncSwingWorker(Component owner, String title, int delayMillis, int min, int max) { + if (title != null && delayMillis > 0) { + progressMonitor = new ProgressMonitor(owner, title, "", Math.min(min, max), Math.max(min, max)); + progressMonitor.setProgress(Math.min(min, max)); // displays monitor + } + this.delayMillis = Math.max(0, delayMillis); + this.isAsync = (delayMillis > 0); + + this.min = min; + this.max = max; + } + + public void executeAsync() { + firePropertyChange(PROPERTY_STATE, null, STARTED_ASYNC); + super.execute(); + } + + public void executeSynchronously() { + firePropertyChange(PROPERTY_STATE, null, STARTED_SYNC); + isAsync = false; + delayMillis = 0; + try { + doInBackground(); + } catch (Exception e) { + exception = e; + e.printStackTrace(); + cancelAsync(); + } + } + + public Exception getException() { + return exception; + } + + public int getMinimum() { + return min; + } + + public void setMinimum(int min) { + this.min = min; + if (progressMonitor != null) { + progressMonitor.setMinimum(min); + } + } + + public int getMaximum() { + return max; + } + + public void setMaximum(int max) { + if (progressMonitor != null) { + progressMonitor.setMaximum(max); + } + this.max = max; + } + + public int getProgressPercent() { + return progressPercent; + } + + public void setNote(String note) { + this.note = note; + if (progressMonitor != null) { + progressMonitor.setNote(note); + } + } + + /** + * Cancel the asynchronous process. + * + */ + public void cancelAsync() { + helper.interrupt(); + } + + /** + * Check to see if the asynchronous process has been canceled. + * + * @return true if StateHelper is not alive anymore + * + */ + public boolean isCanceledAsync() { + return !helper.isAlive(); + } + + /** + * Check to see if the asynchronous process is completely done. + * + * @return true only if the StateMachine is at STATE_DONE + * + */ + public boolean isDoneAsync() { + return helper.getState() == STATE_DONE; + } + + /** + * Override to set a more informed note for the ProcessMonitor. + * + * @param progress + * @return + */ + public String getNote(int progress) { + return String.format("Completed %d%%.\n", progress); + } + + /** + * Retrieve the last note delivered by the ProcessMonitor. + * + * @return + */ + public String getNote() { + return note; + } + + public int getProgressAsync() { + return progressAsync; + } + + /** + * Set the [min,max] progress safely. + * + * SwingWorker only allows progress between 0 and 100. This method safely + * translates [min,max] to [0,100]. + * + * @param n + */ + public void setProgressAsync(int n) { + n = (max > min ? Math.max(min, Math.min(n, max)) : Math.max(max, Math.min(n, min))); + progressAsync = n; + n = (n - min) * 100 / (max - min); + n = (n < 0 ? 0 : n > 100 ? 100 : n); + progressPercent = n; + } + + ///// the StateMachine ///// + + private final static int STATE_INIT = 0; + private final static int STATE_LOOP = 1; + private final static int STATE_WAIT = 2; + private final static int STATE_DONE = 99; + + private StateHelper helper; + + protected StateHelper getHelper() { + return helper; + } + + private boolean isPaused; + + protected void setPaused(boolean tf) { + isPaused = tf; + firePropertyChange(PROPERTY_PAUSE, null, (tf ? PAUSED : RESUMED)); + if (!tf) + stateLoop(); + } + + protected boolean isPaused() { + return isPaused; + } + + /** + * The StateMachine's main loop. + * + * Note that a return from this method will exit doInBackground, trigger the + * isDone() state on the underying worker, and scheduling its done() for + * execution on the AWTEventQueue. + * + * Since this happens essentially immediately, it is unlikely that + * SwingWorker.isCancelled() will ever be true. Thus, the SwingWorker task + * itself won't be cancelable in Java or in JavaScript, since its + * doInBackground() method is officially complete, and isDone() is true well + * before we are "really" done. FutureTask will not set isCancelled() true once + * the task has run. + * + * We are using an asynchronous task specifically because we want to have the + * opportunity for the ProgressMonitor to report in JavaScript. We will have to + * cancel our task and report progress explicitly using our own methods. + * + */ + @Override + public boolean stateLoop() { + while (helper.isAlive() && !isPaused) { + switch (helper.getState()) { + case STATE_INIT: + setProgressAsync(min); + initAsync(); + helper.setState(STATE_WAIT); + continue; + case STATE_LOOP: + if (checkCanceled()) { + helper.setState(STATE_DONE); + firePropertyChange(PROPERTY_STATE, null, CANCELED_ASYNC); + } else { + int ret = doInBackgroundAsync(progressAsync); + if (!helper.isAlive() || isPaused) { + continue; + } + progressAsync = ret; + setProgressAsync(progressAsync); + setNote(getNote(progressAsync)); + setProgress(progressPercent); + if (progressMonitor != null) { + progressMonitor.setProgress(max > min ? progressAsync : max + min - progressAsync); + } + helper.setState(progressAsync == max ? STATE_DONE : STATE_WAIT); + } + continue; + case STATE_WAIT: + // meaning "sleep" and then "loop" + helper.setState(STATE_LOOP); + helper.sleep(delayMillis); + return true; + default: + case STATE_DONE: + stopProgressMonitor(); + // Put the doneAsync() method on the AWTEventQueue + // just as for SwingWorker.done(). + if (isAsync) { + SwingUtilities.invokeLater(doneRunnable); + } else { + doneRunnable.run(); + } + + return false; + } + } + if (!helper.isAlive()) { + stopProgressMonitor(); + } + return false; + } + + private void stopProgressMonitor() { + if (progressMonitor != null) { + progressMonitor.close(); + progressMonitor = null; + } + } + + private Runnable doneRunnable = new Runnable() { + @Override + public void run() { + doneAsync(); + firePropertyChange(PROPERTY_STATE, null, DONE_ASYNC); + } + + }; + + private boolean checkCanceled() { + if (isMonitorCanceled() || isCancelled()) { + helper.interrupt(); + return true; + } + return false; + } + + //// final SwingWorker methods not to be used by subclasses //// + + private boolean isMonitorCanceled() { + return (progressMonitor != null && progressMonitor.isCanceled()); + } + + /** + * see SwingWorker, made final here. + * + */ + @Override + final protected Void doInBackground() throws Exception { + helper = new StateHelper(this); + setProgressAsync(min); + helper.next(STATE_INIT); + return null; + } + + /** + * see SwingWorker, made final here. Nothing to do. + * + */ + @Override + final public void done() { + } } diff --git a/swingjs/timestamp b/swingjs/timestamp index 6c92ed6..2f272fc 100644 --- a/swingjs/timestamp +++ b/swingjs/timestamp @@ -1 +1 @@ -20200601093857 +20200603084227 diff --git a/swingjs/ver/3.2.9-j11/SwingJS-site.zip b/swingjs/ver/3.2.9-j11/SwingJS-site.zip index 5563c76..d5e28c1 100644 Binary files a/swingjs/ver/3.2.9-j11/SwingJS-site.zip and b/swingjs/ver/3.2.9-j11/SwingJS-site.zip differ diff --git a/swingjs/ver/3.2.9-j11/timestamp b/swingjs/ver/3.2.9-j11/timestamp index 71722a8..763c0a1 100644 --- a/swingjs/ver/3.2.9-j11/timestamp +++ b/swingjs/ver/3.2.9-j11/timestamp @@ -1 +1 @@ -20200528154316 +20200603075530 diff --git a/swingjs/ver/3.2.9/timestamp b/swingjs/ver/3.2.9/timestamp index 6c92ed6..2f272fc 100644 --- a/swingjs/ver/3.2.9/timestamp +++ b/swingjs/ver/3.2.9/timestamp @@ -1 +1 @@ -20200601093857 +20200603084227