AsyncSwingWorker setpaused adjustment Jalview-JS/develop.JAL-3446.SequenceFeatureAsync
authorBobHanson <hansonr@stolaf.edu>
Wed, 3 Jun 2020 13:43:52 +0000 (08:43 -0500)
committerBobHanson <hansonr@stolaf.edu>
Wed, 3 Jun 2020 13:43:52 +0000 (08:43 -0500)
src/jalview/gui/SequenceFetcher.java
src/javajs/async/AsyncSwingWorker.java
swingjs/timestamp
swingjs/ver/3.2.9-j11/SwingJS-site.zip
swingjs/ver/3.2.9-j11/timestamp
swingjs/ver/3.2.9/timestamp

index ded241b..70ad188 100755 (executable)
@@ -707,7 +707,6 @@ public class SequenceFetcher extends JPanel
         return;
       }
       super.setPaused(false);
-      super.stateLoop();
     }
 
     private int runSubtask(Runnable subtask)
index df103cd..f23da48 100644 (file)
@@ -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<Void, Void> 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() {
+  }
 
 }
index 6c92ed6..2f272fc 100644 (file)
@@ -1 +1 @@
-20200601093857 
+20200603084227 
index 5563c76..d5e28c1 100644 (file)
Binary files a/swingjs/ver/3.2.9-j11/SwingJS-site.zip and b/swingjs/ver/3.2.9-j11/SwingJS-site.zip differ
index 6c92ed6..2f272fc 100644 (file)
@@ -1 +1 @@
-20200601093857 
+20200603084227