JAL-3878 Separate gui elements from operations.
[jalview.git] / src / jalview / ws2 / operations / AlignmentOperation.java
index 89aeb79..1f0a602 100644 (file)
@@ -14,11 +14,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
 
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.ToolTipManager;
 
+import org.eclipse.jetty.http.HttpGenerator.Result;
+
 import jalview.analysis.AlignSeq;
 import jalview.analysis.AlignmentSorter;
 import jalview.analysis.SeqsetUtils;
@@ -40,6 +43,7 @@ import jalview.gui.WsJobParameters;
 import jalview.util.MathUtils;
 import jalview.util.MessageManager;
 import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
 import jalview.ws.params.WsParamSetI;
 import jalview.ws2.MenuEntryProviderI;
 import jalview.ws2.ResultSupplier;
@@ -49,6 +53,9 @@ import jalview.ws2.PollingTaskExecutor;
 import jalview.ws2.WebServiceI;
 import jalview.ws2.WebServiceInfoUpdater;
 import jalview.ws2.WebServiceWorkerI;
+import jalview.ws2.WebServiceWorkerListener;
+import jalview.ws2.WebServiceWorkerListenersList;
+import jalview.ws2.gui.AlignmentMenuBuilder;
 import jalview.ws2.utils.WSJobList;
 
 /**
@@ -58,12 +65,14 @@ import jalview.ws2.utils.WSJobList;
  */
 public class AlignmentOperation implements Operation
 {
-  final WebServiceI service;
+  private final WebServiceI service;
 
-  final ResultSupplier<AlignmentI> supplier;
+  private final ResultSupplier<AlignmentI> supplier;
+  
 
-  public AlignmentOperation(WebServiceI service,
-          ResultSupplier<AlignmentI> supplier)
+  public AlignmentOperation(
+      WebServiceI service,
+      ResultSupplier<AlignmentI> supplier)
   {
     this.service = service;
     this.supplier = supplier;
@@ -74,6 +83,12 @@ public class AlignmentOperation implements Operation
   {
     return service.getName();
   }
+  
+  @Override
+  public String getDescription()
+  {
+    return service.getDescription();
+  }
 
   @Override
   public String getTypeName()
@@ -86,6 +101,18 @@ public class AlignmentOperation implements Operation
   {
     return service.getHostName();
   }
+  
+  @Override
+  public boolean hasParameters()
+  {
+    return service.hasParameters();
+  }
+  
+  @Override
+  public ParamDatastoreI getParamStore()
+  {
+    return service.getParamStore();
+  }
 
   @Override
   public int getMinSequences()
@@ -121,7 +148,7 @@ public class AlignmentOperation implements Operation
   public boolean canSubmitGaps()
   {
     // hack copied from original jabaws code, don't blame me
-    return service.getName().contains("lustal");
+    return getName().contains("lustal");
   }
 
   @Override
@@ -145,164 +172,9 @@ public class AlignmentOperation implements Operation
   @Override
   public MenuEntryProviderI getMenuBuilder()
   {
-    return this::buildMenu;
-  }
-
-  protected void buildMenu(JMenu parent, AlignFrame frame)
-  {
-    if (canSubmitGaps())
-    {
-      var alignSubmenu = new JMenu(service.getName());
-      buildMenu(alignSubmenu, frame, false);
-      parent.add(alignSubmenu);
-      var realignSubmenu = new JMenu(MessageManager.formatMessage(
-              "label.realign_with_params", service.getName()));
-      realignSubmenu.setToolTipText(MessageManager
-              .getString("label.align_sequences_to_existing_alignment"));
-      buildMenu(realignSubmenu, frame, true);
-      parent.add(realignSubmenu);
-    }
-    else
-    {
-      buildMenu(parent, frame, false);
-    }
+    return new AlignmentMenuBuilder(this);
   }
 
-  protected void buildMenu(JMenu parent, AlignFrame frame,
-          boolean submitGaps)
-  {
-    final String action = submitGaps ? "Align" : "Realign";
-    final var calcName = service.getName();
-
-    String title = frame.getTitle();
-    PollingTaskExecutor executor = frame.getViewport().getWSExecutor();
-    {
-      var item = new JMenuItem(MessageManager.formatMessage(
-              "label.calcname_with_default_settings", calcName));
-      item.setToolTipText(MessageManager
-              .formatMessage("label.action_with_default_settings", action));
-      item.addActionListener((event) -> {
-        final AlignmentView msa = frame.gatherSequencesForAlignment();
-        final AlignViewport viewport = frame.getViewport();
-        final AlignmentI alignment = frame.getViewport().getAlignment();
-        if (msa != null)
-        {
-          WebServiceWorkerI worker = new AlignmentWorker(msa,
-              Collections.emptyList(), title, submitGaps, true,
-              alignment, viewport);
-          executor.submit(worker);
-        }
-      });
-      parent.add(item);
-    }
-
-    if (service.hasParameters())
-    {
-      var item = new JMenuItem(
-              MessageManager.getString("label.edit_settings_and_run"));
-      item.setToolTipText(MessageManager.getString(
-              "label.view_and_change_parameters_before_alignment"));
-      item.addActionListener((event) -> {
-        final AlignmentView msa = frame.gatherSequencesForAlignment();
-        final AlignViewport viewport = frame.getViewport();
-        final AlignmentI alignment = frame.getViewport().getAlignment();
-        if (msa != null)
-        {
-          openEditParamsDialog(service, null, null)
-              .thenAcceptAsync((arguments) -> {
-                if (arguments != null)
-                {
-                  WebServiceWorkerI worker = new AlignmentWorker(msa,
-                      arguments, title, submitGaps, true, alignment,
-                      viewport);
-                  executor.submit(worker);
-                }
-              });
-        }
-      });
-      parent.add(item);
-    }
-
-    var presets = service.getParamStore().getPresets();
-    if (presets != null && presets.size() > 0)
-    {
-      final var presetList = new JMenu(MessageManager
-              .formatMessage("label.run_with_preset_params", calcName));
-      final var showToolTipFor = ToolTipManager.sharedInstance()
-              .getDismissDelay();
-      for (final var preset : presets)
-      {
-        var item = new JMenuItem(preset.getName());
-        final int QUICK_TOOLTIP = 1500;
-        item.addMouseListener(new MouseAdapter()
-        {
-          @Override
-          public void mouseEntered(MouseEvent e)
-          {
-            ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
-          }
-
-          @Override
-          public void mouseExited(MouseEvent e)
-          {
-            ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
-          }
-        });
-        String tooltip = JvSwingUtils.wrapTooltip(true,
-                format("<strong>%s</strong><br/>%s",
-                        MessageManager.getString(
-                                preset.isModifiable() ? "label.user_preset"
-                                        : "label.service_preset"),
-                        preset.getDescription()));
-        item.setToolTipText(tooltip);
-        item.addActionListener((event) -> {
-          final AlignmentView msa = frame.gatherSequencesForAlignment();
-          final AlignViewport viewport = frame.getViewport();
-          final AlignmentI alignment = frame.getViewport().getAlignment();
-          if (msa != null)
-          {
-            WebServiceWorkerI worker = new AlignmentWorker(msa,
-                preset.getArguments(), title, submitGaps, true,
-                alignment, viewport);
-            executor.submit(worker);
-          }
-        });
-        presetList.add(item);
-      }
-      parent.add(presetList);
-    }
-  }
-
-  private CompletionStage<List<ArgumentI>> openEditParamsDialog(
-          WebServiceI service, WsParamSetI preset,
-          List<ArgumentI> arguments)
-  {
-    WsJobParameters jobParams;
-    if (preset == null && arguments != null && arguments.size() > 0)
-      jobParams = new WsJobParameters(service.getParamStore(), preset,
-              arguments);
-    else
-      jobParams = new WsJobParameters(service.getParamStore(), preset,
-              null);
-    var stage = jobParams.showRunDialog();
-    return stage.thenApply((startJob) -> {
-      if (startJob)
-      {
-        if (jobParams.getPreset() == null)
-        {
-          return jobParams.getJobParams();
-        }
-        else
-        {
-          return jobParams.getPreset().getArguments();
-        }
-      }
-      else
-      {
-        return null;
-      }
-    });
-  }
 
   /**
    * Implementation of the web service worker performing multiple sequence
@@ -311,7 +183,7 @@ public class AlignmentOperation implements Operation
    * @author mmwarowny
    *
    */
-  private class AlignmentWorker implements WebServiceWorkerI
+  public class AlignmentWorker implements WebServiceWorkerI
   {
 
     private long uid = MathUtils.getUID();
@@ -338,20 +210,18 @@ public class AlignmentOperation implements Operation
 
     private Map<Long, JobInput> inputs = new LinkedHashMap<>();
 
-    private WebserviceInfo wsInfo;
-
     private Map<Long, Integer> exceptionCount = new HashMap<>();
-
+    
     private final int MAX_RETRY = 5;
 
-    AlignmentWorker(AlignmentView msa, List<ArgumentI> args,
+    public AlignmentWorker(AlignmentView msa, List<ArgumentI> args,
             String alnTitle, boolean submitGaps, boolean preserveOrder,
-            AlignmentI alignment, AlignViewport viewport)
+            AlignViewport viewport)
     {
       this.msa = msa;
-      this.dataset = alignment.getDataset();
+      this.dataset = viewport.getAlignment().getDataset();
       List<AlignedCodonFrame> cf = Objects.requireNonNullElse(
-              alignment.getCodonFrames(), Collections.emptyList());
+          viewport.getAlignment().getCodonFrames(), Collections.emptyList());
       this.codonFrame.addAll(cf);
       this.args = args;
       this.alnTitle = alnTitle;
@@ -359,11 +229,6 @@ public class AlignmentOperation implements Operation
       this.preserveOrder = preserveOrder;
       this.viewport = viewport;
       this.gapCharacter = viewport.getGapCharacter();
-
-      String panelInfo = String.format("%s using service hosted at %s%n%s",
-              service.getName(), service.getHostName(),
-              Objects.requireNonNullElse(service.getDescription(), ""));
-      wsInfo = new WebserviceInfo(service.getName(), panelInfo, false);
     }
 
     @Override
@@ -379,40 +244,30 @@ public class AlignmentOperation implements Operation
     }
 
     @Override
-    public List<WSJob> getJobs()
+    public WSJobList getJobs()
     {
-      return Collections.unmodifiableList(jobs);
+      return jobs;
     }
 
     @Override
     public void start() throws IOException
     {
       Cache.log.info(format("Starting new %s job.", service.getName()));
-      String outputHeader = String.format("%s of %s%nJob details%n",
-              submitGaps ? "Re-alignment" : "Alignment", alnTitle);
       SequenceI[][] conmsa = msa.getVisibleContigs('-');
       if (conmsa == null)
       {
         return;
       }
-      WebServiceInfoUpdater updater = new WebServiceInfoUpdater(wsInfo);
-      updater.setOutputHeader(outputHeader);
       int numValid = 0;
       for (int i = 0; i < conmsa.length; i++)
       {
         JobInput input = JobInput.create(conmsa[i], 2, submitGaps);
         WSJob job = new WSJob(service.getProviderName(), service.getName(),
                 service.getHostName());
-        job.setJobNum(wsInfo.addJobPane());
-        if (conmsa.length > 1)
-        {
-          wsInfo.setProgressName(String.format("region %d", i),
-                  job.getJobNum());
-        }
-        wsInfo.setProgressText(job.getJobNum(), outputHeader);
-        job.addPropertyChangeListener(updater);
+        job.setJobNum(i);
         inputs.put(job.getUid(), input);
         jobs.add(job);
+        listeners.fireJobCreated(job);
         if (input.isInputValid())
         {
           int count;
@@ -450,17 +305,11 @@ public class AlignmentOperation implements Operation
       }
       if (numValid > 0)
       {
-        // wsInfo.setThisService() should happen here
-        wsInfo.setVisible(true);
+        listeners.fireWorkerStarted();
       }
       else
       {
-        wsInfo.setVisible(false);
-        // TODO show notification dialog.
-        // JvOptionPane.showMessageDialog(frame,
-        // MessageManager.getString("info.invalid_msa_input_mininfo"),
-        // MessageManager.getString("info.invalid_msa_notenough"),
-        // JvOptionPane.INFORMATION_MESSAGE);
+        listeners.fireWorkerNotStarted();
       }
     }
 
@@ -480,9 +329,7 @@ public class AlignmentOperation implements Operation
           } catch (IOException e)
           {
             Cache.log.error(format("Polling job %s failed.", job), e);
-            wsInfo.appendProgressText(job.getJobNum(),
-                    MessageManager.formatMessage("info.server_exception",
-                            service.getName(), e.getMessage()));
+            listeners.firePollException(job, e);
             int count = exceptionCount.getOrDefault(job.getUid(),
                     MAX_RETRY);
             if (--count <= 0)
@@ -503,45 +350,14 @@ public class AlignmentOperation implements Operation
         }
         done &= job.getStatus().isDone() || job.getStatus().isFailed();
       }
-      updateWSInfoGlobalStatus();
       return done;
     }
 
-    private void updateWSInfoGlobalStatus()
-    {
-      if (jobs.countRunning() > 0)
-      {
-        wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
-      }
-      else if (jobs.countQueuing() > 0
-              || jobs.countSubmitted() < jobs.size())
-      {
-        wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
-      }
-      else
-      {
-        if (jobs.countSuccessful() > 0)
-        {
-          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
-        }
-        else if (jobs.countCancelled() > 0)
-        {
-          wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
-        }
-        else if (jobs.countFailed() > 0)
-        {
-          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
-        }
-      }
-    }
 
     @Override
     public void done()
     {
-      long progbarId = MathUtils.getUID();
-      wsInfo.setProgressBar(
-              MessageManager.getString("status.collecting_job_results"),
-              progbarId);
+      listeners.fireWorkerCompleting();
       Map<Long, AlignmentI> results = new LinkedHashMap<>();
       for (WSJob job : getJobs())
       {
@@ -549,7 +365,8 @@ public class AlignmentOperation implements Operation
           continue;
         try
         {
-          AlignmentI alignment = supplier.getResult(job, dataset.getSequences(), viewport);
+          AlignmentI alignment = supplier.getResult(job, 
+              dataset.getSequences(), viewport);
           if (alignment != null)
           {
             results.put(job.getUid(), alignment);
@@ -564,39 +381,19 @@ public class AlignmentOperation implements Operation
           }
         }
       }
-      updateWSInfoGlobalStatus();
       if (results.size() > 0)
       {
-        OutputWrapper out = prepareOutput(results);
-        wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
-                new Alignment(out.aln), out.alorders, out.hidden));
-        wsInfo.setResultsReady();
+        AlignmentResult out = prepareResult(results);
+        resultConsumer.accept(out);
       }
       else
       {
-        wsInfo.setFinishedNoResults();
+        resultConsumer.accept(null);
       }
-      wsInfo.removeProgressBar(progbarId);
+      listeners.fireWorkerCompleted();
     }
 
-    private class OutputWrapper
-    {
-      AlignmentI aln;
-
-      List<AlignmentOrder> alorders;
-
-      HiddenColumns hidden;
-
-      OutputWrapper(AlignmentI aln, List<AlignmentOrder> alorders,
-              HiddenColumns hidden)
-      {
-        this.aln = aln;
-        this.alorders = alorders;
-        this.hidden = hidden;
-      }
-    }
-
-    private OutputWrapper prepareOutput(Map<Long, AlignmentI> alignments)
+    private AlignmentResult prepareResult(Map<Long, AlignmentI> alignments)
     {
       List<AlignmentOrder> alorders = new ArrayList<>();
       SequenceI[][] results = new SequenceI[jobs.size()][];
@@ -665,7 +462,7 @@ public class AlignmentOperation implements Operation
         aln.setDataset(dataset);
 
       propagateDatasetMappings(aln);
-      return new OutputWrapper(aln, alorders, hidden);
+      return new AlignmentResult(aln, alorders, hidden);
       // displayNewFrame(aln, alorders, hidden);
     }
 
@@ -691,65 +488,53 @@ public class AlignmentOperation implements Operation
         }
       }
     }
+    
+    private Consumer<AlignmentResult> resultConsumer;
+    
+    public void setResultConsumer(Consumer<AlignmentResult> consumer)
+    {
+      this.resultConsumer = consumer;
+    }
 
-    private void displayNewFrame(AlignmentI aln,
-            List<AlignmentOrder> alorders, HiddenColumns hidden)
+    private WebServiceWorkerListenersList listeners = 
+        new WebServiceWorkerListenersList(this);
+    
+    @Override
+    public void addListener(WebServiceWorkerListener listener)
     {
-      AlignFrame frame = new AlignFrame(aln, hidden,
-              AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
-      // TODO store feature renderer settings in worker object
-      // frame.getFeatureRenderer().transferSettings(featureSettings);
-      var regions = sortOrders(alorders);
-      if (alorders.size() == 1)
-      {
-        frame.addSortByOrderMenuItem(
-                format("%s Ordering", service.getName()), alorders.get(0));
-      }
-      else
-      {
-        for (int i = 0; i < alorders.size(); i++)
-        {
-          final int j = i;
-          Iterable<String> iter = () -> regions.get(j).stream()
-                  .map(it -> Integer.toString(it)).iterator();
-          var orderName = format("%s Region %s Ordering", service.getName(),
-                  String.join(",", iter));
-          frame.addSortByOrderMenuItem(orderName, alorders.get(i));
-        }
-      }
+      listeners.addListener(listener);
+    }
+  }
+  
+  public class AlignmentResult
+  {
+    AlignmentI aln;
 
-      /* TODO
-       * If alignment was requested from one half of a SplitFrame, show in a
-       * SplitFrame with the other pane similarly aligned.
-       */
+    List<AlignmentOrder> alorders;
 
-      Desktop.addInternalFrame(frame, alnTitle, AlignFrame.DEFAULT_WIDTH,
-              AlignFrame.DEFAULT_HEIGHT);
+    HiddenColumns hidden;
+
+    AlignmentResult(AlignmentI aln, List<AlignmentOrder> alorders,
+            HiddenColumns hidden)
+    {
+      this.aln = aln;
+      this.alorders = alorders;
+      this.hidden = hidden;
     }
 
-    private List<List<Integer>> sortOrders(List<?> alorders)
+    public AlignmentI getAln()
     {
-      List<List<Integer>> regions = new ArrayList<>();
-      for (int i = 0; i < alorders.size(); i++)
-      {
-        List<Integer> regs = new ArrayList<>();
-        regs.add(i);
-        int j = i + 1;
-        while (j < alorders.size())
-        {
-          if (alorders.get(i).equals(alorders.get(j)))
-          {
-            alorders.remove(j);
-            regs.add(j);
-          }
-          else
-          {
-            j++;
-          }
-        }
-        regions.add(regs);
-      }
-      return regions;
+      return aln;
+    }
+
+    public List<AlignmentOrder> getAlorders()
+    {
+      return alorders;
+    }
+
+    public HiddenColumns getHidden()
+    {
+      return hidden;
     }
   }