JAL-3878 Separate gui elements from operations.
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Mon, 8 Nov 2021 15:01:52 +0000 (16:01 +0100)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Mon, 8 Nov 2021 15:01:52 +0000 (16:01 +0100)
Separation went well for alignment operation but not so much
for the annotations. GUI progress bar needs to be moved outside
the operation and be controlled by the worker listener.
Addition of the results to the frame needs to be moved out from
the #done method, but it's strongly wired to the align frame
code and cannot be nicely packed into AnnotationResult object.

15 files changed:
src/jalview/gui/AlignFrame.java
src/jalview/ws2/WebServiceDiscoverer.java
src/jalview/ws2/WebServiceI.java
src/jalview/ws2/WebServiceInfoUpdater.java
src/jalview/ws2/WebServiceWorkerI.java
src/jalview/ws2/WebServiceWorkerListener.java [new file with mode: 0644]
src/jalview/ws2/WebServiceWorkerListenersList.java [new file with mode: 0644]
src/jalview/ws2/gui/AlignmentMenuBuilder.java [new file with mode: 0644]
src/jalview/ws2/gui/AnnotationMenuBuilder.java [new file with mode: 0644]
src/jalview/ws2/operations/AlignmentOperation.java
src/jalview/ws2/operations/AnnotationOperation.java
src/jalview/ws2/operations/AnnotationServiceWorker.java
src/jalview/ws2/operations/Operation.java
src/jalview/ws2/slivka/SlivkaWSDiscoverer.java
src/jalview/ws2/slivka/SlivkaWebService.java

index 3d00450..3336663 100644 (file)
@@ -918,8 +918,8 @@ public class AlignFrame extends GAlignFrame
   }
 
   @Override
-  public void servicesChanged(WebServiceDiscoverer discoverer,
-          Collection<? extends WebServiceI> services)
+  public void operationsChanged(WebServiceDiscoverer discoverer,
+      List<Operation> list)
   {
     buildWebServicesMenu();
   }
@@ -4731,8 +4731,7 @@ public class AlignFrame extends GAlignFrame
     if (discoverer.hasServices())
     {
       var builder = new WebServicesMenuBuilder();
-      for (var service : discoverer.getServices())
-        builder.addAllOperations(service.getOperations());
+      builder.addAllOperations(discoverer.getOperations());
       builder.addSelectedHostChangeListener((name, op) -> {
         menu.removeAll();
         builder.buildMenu(menu, this);
index 635bec9..513610c 100644 (file)
@@ -7,6 +7,8 @@ import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CopyOnWriteArrayList;
 
+import jalview.ws2.operations.Operation;
+
 public interface WebServiceDiscoverer
 {
   public static final int STATUS_OK = 1;
@@ -25,7 +27,7 @@ public interface WebServiceDiscoverer
 
   public int getStatusForUrl(String url);
 
-  public List<WebServiceI> getServices();
+  public List<Operation> getOperations();
 
   public boolean hasServices();
 
@@ -40,8 +42,8 @@ public interface WebServiceDiscoverer
   @FunctionalInterface
   static interface ServiceChangeListener
   {
-    public void servicesChanged(WebServiceDiscoverer discoverer,
-            Collection<? extends WebServiceI> services);
+    public void operationsChanged(WebServiceDiscoverer discoverer,
+            List<Operation> list);
   }
 
   List<ServiceChangeListener> serviceListeners = new CopyOnWriteArrayList<>();
@@ -58,11 +60,11 @@ public interface WebServiceDiscoverer
     serviceListeners.remove(listener);
   }
 
-  default void fireServicesChanged(List<WebServiceI> services)
+  default void fireOperationsChanged(List<Operation> list)
   {
     for (var listener : serviceListeners)
     {
-      listener.servicesChanged(this, services);
+      listener.operationsChanged(this, list);
     }
   }
 }
index 6781668..a09233f 100755 (executable)
@@ -24,13 +24,11 @@ public interface WebServiceI
 
   public String getProviderName();
 
-  public String getName();
+  String getName();
 
-  public String getDescription();
+  String getDescription();
 
-  public List<Operation> getOperations();
-
-  public boolean hasParameters();
+  boolean hasParameters();
 
   public ParamDatastoreI getParamStore();
 
@@ -39,8 +37,6 @@ public interface WebServiceI
 
   public void updateProgress(WSJob job) throws IOException;
 
-  // public <T> ResultSupplier<T> getResultSupplier(Class<T> type);
-
   public void cancel(WSJob job) throws IOException;
 
   /**
index 8152abf..c9557c6 100644 (file)
@@ -8,12 +8,14 @@ import jalview.gui.WebserviceInfo;
 
 public class WebServiceInfoUpdater implements PropertyChangeListener
 {
+  private final WebServiceWorkerI worker;
   private final WebserviceInfo wsInfo;
 
   private String outputHeader = "";
 
-  public WebServiceInfoUpdater(WebserviceInfo wsInfo)
+  public WebServiceInfoUpdater(WebServiceWorkerI worker, WebserviceInfo wsInfo)
   {
+    this.worker = worker;
     this.wsInfo = wsInfo;
   }
 
@@ -76,6 +78,7 @@ public class WebServiceInfoUpdater implements PropertyChangeListener
       break;
     }
     wsInfo.setStatus(job.getJobNum(), wsInfoStatus);
+    updateWSInfoGlobalStatus();
   }
 
   private void logChanged(PropertyChangeEvent evt)
@@ -96,4 +99,33 @@ public class WebServiceInfoUpdater implements PropertyChangeListener
             newLog.substring(oldLog.length()));
   }
 
+
+  private void updateWSInfoGlobalStatus()
+  {
+    var jobs = worker.getJobs();
+    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);
+      }
+    }
+  }
 }
index 22b4c73..38c82a2 100644 (file)
@@ -3,11 +3,13 @@ package jalview.ws2;
 import java.io.IOException;
 import java.util.List;
 
+import jalview.ws2.utils.WSJobList;
+
 public interface WebServiceWorkerI extends PollableTaskI
-{
+{  
   long getUID();
 
-  List<WSJob> getJobs();
+  WSJobList getJobs();
 
   void start() throws IOException;
 
@@ -32,4 +34,6 @@ public interface WebServiceWorkerI extends PollableTaskI
    * either normally or exceptionally.
    */
   void done();
+  
+  public void addListener(WebServiceWorkerListener listener);
 }
diff --git a/src/jalview/ws2/WebServiceWorkerListener.java b/src/jalview/ws2/WebServiceWorkerListener.java
new file mode 100644 (file)
index 0000000..16ee7bd
--- /dev/null
@@ -0,0 +1,16 @@
+package jalview.ws2;
+
+public interface WebServiceWorkerListener
+{
+  void workerStarted(WebServiceWorkerI source);
+  
+  void workerNotStarted(WebServiceWorkerI source);
+  
+  void jobCreated(WebServiceWorkerI source, WSJob job);
+  
+  void pollException(WebServiceWorkerI source, WSJob job, Exception e);
+  
+  void workerCompleting(WebServiceWorkerI source);
+  
+  void workerCompleted(WebServiceWorkerI source);
+}
diff --git a/src/jalview/ws2/WebServiceWorkerListenersList.java b/src/jalview/ws2/WebServiceWorkerListenersList.java
new file mode 100644 (file)
index 0000000..a09c958
--- /dev/null
@@ -0,0 +1,73 @@
+package jalview.ws2;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+
+public class WebServiceWorkerListenersList
+{
+  private WebServiceWorkerI owner;
+  private List<WebServiceWorkerListener> listeners = new CopyOnWriteArrayList<>();
+  
+  public WebServiceWorkerListenersList(WebServiceWorkerI worker)
+  {
+    this.owner = worker;
+  }
+  
+  public void addListener(WebServiceWorkerListener listener)
+  {
+    listeners.add(listener);
+  }
+  
+  public void removeListener(WebServiceWorkerListener listener)
+  {
+    listeners.remove(listener);
+  }
+  
+  public void fireWorkerStarted()
+  {
+    for (var listener : listeners)
+      listener.workerStarted(owner);
+  }
+  
+  public void fireWorkerNotStarted()
+  {
+    for (var listener : listeners)
+      listener.workerNotStarted(owner);
+  }
+  
+  public void fireJobCreated(WSJob job)
+  {
+    for (var listener : listeners)
+      listener.jobCreated(owner, job);
+  }
+  
+  public void firePollException(WSJob job, Exception e)
+  {
+    for (var listener : listeners)
+      listener.pollException(owner, job, e);
+  }
+  
+  public void fireWorkerCompleting()
+  {
+    for (var listener : listeners)
+      listener.workerCompleting(owner);
+  }
+  
+  public void fireWorkerCompleted()
+  {
+    for (var listener : listeners)
+      listener.workerCompleted(owner);
+  }
+  
+  public List<WebServiceWorkerListener> getListeners()
+  {
+    return listeners;
+  }
+  
+  public void forEach(Consumer<WebServiceWorkerListener> consumer)
+  {
+    for (var listener : listeners)
+      consumer.accept(listener);
+  }
+}
diff --git a/src/jalview/ws2/gui/AlignmentMenuBuilder.java b/src/jalview/ws2/gui/AlignmentMenuBuilder.java
new file mode 100644 (file)
index 0000000..f3dfb68
--- /dev/null
@@ -0,0 +1,377 @@
+package jalview.ws2.gui;
+
+import static java.lang.String.format;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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 jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.HiddenColumns;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.JvSwingUtils;
+import jalview.gui.WebserviceInfo;
+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.PollingTaskExecutor;
+import jalview.ws2.WSJob;
+import jalview.ws2.WebServiceInfoUpdater;
+import jalview.ws2.WebServiceWorkerI;
+import jalview.ws2.WebServiceWorkerListener;
+import jalview.ws2.operations.AlignmentOperation;
+import jalview.ws2.operations.AlignmentOperation.AlignmentResult;
+import jalview.ws2.operations.AlignmentOperation.AlignmentWorker;
+
+
+public class AlignmentMenuBuilder implements MenuEntryProviderI
+{
+  AlignmentOperation operation;
+  
+  public AlignmentMenuBuilder(AlignmentOperation operation)
+  {
+    this.operation = operation;
+  }
+  
+  public void buildMenu(JMenu parent, AlignFrame frame)
+  {
+    if (operation.canSubmitGaps())
+    {
+      var alignSubmenu = new JMenu(operation.getName());
+      buildMenu(alignSubmenu, frame, false);
+      parent.add(alignSubmenu);
+      var realignSubmenu = new JMenu(MessageManager.formatMessage(
+              "label.realign_with_params", operation.getName()));
+      realignSubmenu.setToolTipText(MessageManager
+              .getString("label.align_sequences_to_existing_alignment"));
+      buildMenu(realignSubmenu, frame, true);
+      parent.add(realignSubmenu);
+    }
+    else
+    {
+      buildMenu(parent, frame, false);
+    }
+  }
+
+  protected void buildMenu(JMenu parent, AlignFrame frame,
+          boolean submitGaps)
+  {
+    final String action = submitGaps ? "Align" : "Realign";
+    final var calcName = operation.getName();
+
+    {
+      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();
+        if (msa != null)
+        {
+          startWorker(frame, msa, Collections.emptyList(), submitGaps);
+        }
+      });
+      parent.add(item);
+    }
+
+    if (operation.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) -> {
+        AlignmentView msa = frame.gatherSequencesForAlignment();
+        if (msa != null)
+        {
+          openEditParamsDialog(operation.getParamStore(), null, null)
+              .thenAcceptAsync((arguments) -> {
+                if (arguments != null)
+                {
+                  startWorker(frame, msa, arguments, submitGaps);
+                }
+              });
+        }
+      });
+      parent.add(item);
+    }
+
+    var presets = operation.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) -> {
+          AlignmentView msa = frame.gatherSequencesForAlignment();
+          startWorker(frame, msa, preset.getArguments(), submitGaps);
+        });
+        presetList.add(item);
+      }
+      parent.add(presetList);
+    }
+  }
+
+  private CompletionStage<List<ArgumentI>> openEditParamsDialog(
+          ParamDatastoreI paramStore, WsParamSetI preset,
+          List<ArgumentI> arguments)
+  {
+    WsJobParameters jobParams;
+    if (preset == null && arguments != null && arguments.size() > 0)
+      jobParams = new WsJobParameters(paramStore, preset, arguments);
+    else
+      jobParams = new WsJobParameters(paramStore, preset, null);
+    if (preset != null)
+    {
+      jobParams.setName(MessageManager.getString(
+          "label.adjusting_parameters_for_calculation"));
+    }
+    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;
+      }
+    });
+  }
+
+  private void startWorker(AlignFrame frame, AlignmentView msa,
+      List<ArgumentI> arguments, boolean submitGaps)
+  {
+    AlignViewport viewport = frame.getViewport();
+    PollingTaskExecutor executor = frame.getViewport().getWSExecutor();
+    if (msa != null)
+    {
+
+      String panelInfo = String.format("%s using service hosted at %s%n%s",
+              operation.getName(), operation.getHostName(),
+              Objects.requireNonNullElse(operation.getDescription(), ""));
+      var wsInfo = new WebserviceInfo(operation.getName(), panelInfo, false);
+      
+      final String alnTitle = frame.getTitle();
+      AlignmentWorker worker = operation.new AlignmentWorker(msa,
+          arguments, frame.getTitle(), submitGaps, true,
+          viewport);
+      String outputHeader = String.format("%s of %s%nJob details%n",
+          submitGaps ? "Re-alignment" : "Alignment", alnTitle);
+
+      var awl = new AlignmentWorkerListener(worker, wsInfo, frame,
+          outputHeader);
+      worker.setResultConsumer(awl);
+      worker.addListener(awl);
+      
+      executor.submit(worker);
+    }
+    
+  }
+  
+  private class AlignmentWorkerListener
+      implements WebServiceWorkerListener, Consumer<AlignmentResult>
+  {
+
+    final WebServiceWorkerI worker;
+    final WebserviceInfo wsInfo;
+    final AlignFrame frame;
+    WebServiceInfoUpdater updater;
+    String outputHeader;
+    final long progbarId = MathUtils.getUID();
+    
+    private AlignmentWorkerListener(WebServiceWorkerI worker, WebserviceInfo wsInfo,
+        AlignFrame frame, String header)
+    {
+      this.worker = worker;
+      this.wsInfo = wsInfo;
+      this.frame = frame;
+      this.outputHeader = header;
+      this.updater = new WebServiceInfoUpdater(worker, wsInfo);
+      updater.setOutputHeader(outputHeader);
+    }
+    
+    @Override
+    public void workerStarted(WebServiceWorkerI source)
+    {
+      // wsInfo.setThisService() should happen here
+      wsInfo.setVisible(true);
+    }
+    
+    @Override
+    public void workerNotStarted(WebServiceWorkerI source)
+    {
+      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);
+    }
+
+    @Override
+    public void jobCreated(WebServiceWorkerI source, WSJob job)
+    {
+      int tabIndex = wsInfo.addJobPane();
+      wsInfo.setProgressName(String.format("region %d", job.getJobNum()), 
+          tabIndex);
+      wsInfo.setProgressText(tabIndex, outputHeader);
+      job.addPropertyChangeListener(updater);
+    }
+    
+    @Override
+    public void pollException(WebServiceWorkerI source, WSJob job, Exception e)
+    {
+      wsInfo.appendProgressText(job.getJobNum(),
+          MessageManager.formatMessage("info.server_exception",
+              operation.getName(), e.getMessage()));
+    }
+
+    @Override
+    public void workerCompleting(WebServiceWorkerI source)
+    {
+      // TODO Auto-generated method stub
+      wsInfo.setProgressBar(
+              MessageManager.getString("status.collecting_job_results"),
+              progbarId);
+    }
+    
+    @Override
+    public void workerCompleted(WebServiceWorkerI source)
+    {
+      wsInfo.removeProgressBar(progbarId);
+      
+    }
+
+    @Override
+    public void accept(AlignmentResult out)
+    {
+      if (out != null)
+      {
+        wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
+                new Alignment(out.getAln()), out.getAlorders(), out.getHidden()));
+        wsInfo.setResultsReady();
+      }
+      else
+      {
+        wsInfo.setFinishedNoResults();
+      }
+    }
+    
+    private void displayNewFrame(AlignmentI aln,
+        List<AlignmentOrder> alorders, HiddenColumns hidden)
+    {
+      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", operation.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", operation.getName(),
+                  String.join(",", iter));
+          frame.addSortByOrderMenuItem(orderName, alorders.get(i));
+        }
+      }
+    
+      /* TODO
+       * If alignment was requested from one half of a SplitFrame, show in a
+       * SplitFrame with the other pane similarly aligned.
+       */
+    
+      Desktop.addInternalFrame(frame, frame.getTitle(), AlignFrame.DEFAULT_WIDTH,
+              AlignFrame.DEFAULT_HEIGHT);
+    }
+
+
+    private List<List<Integer>> sortOrders(List<?> alorders)
+    {
+      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;
+    }
+    
+    
+  }
+}
+
diff --git a/src/jalview/ws2/gui/AnnotationMenuBuilder.java b/src/jalview/ws2/gui/AnnotationMenuBuilder.java
new file mode 100644 (file)
index 0000000..a1b2316
--- /dev/null
@@ -0,0 +1,190 @@
+package jalview.ws2.gui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+
+import jalview.api.AlignCalcManagerI2;
+import jalview.api.AlignmentViewPanel;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.WsJobParameters;
+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.PollingTaskExecutor;
+import jalview.ws2.operations.AnnotationOperation;
+import jalview.ws2.operations.AnnotationServiceWorker;
+
+public class AnnotationMenuBuilder implements MenuEntryProviderI
+{
+  final AnnotationOperation operation;
+  
+  public AnnotationMenuBuilder(AnnotationOperation operation)
+  {
+    this.operation = operation;
+  }
+
+  @Override
+  public void buildMenu(JMenu parent, AlignFrame frame)
+  {
+    if (operation.isInteractive())
+      buildInteractiveMenu(parent, frame);
+    else
+      buildClassicMenu(parent, frame);
+  }
+
+  protected void buildClassicMenu(JMenu parent, AlignFrame frame)
+  {
+    final var calcName = operation.getName();
+    PollingTaskExecutor wsExecutor = frame.getViewport().getWSExecutor();
+    final var calcManager = frame.getViewport().getCalcManager();
+    {
+      var item = new JMenuItem(MessageManager.formatMessage(
+          "label.calcname_with_default_settings", calcName));
+      item.addActionListener((event) -> {
+        var worker = createWorker(Collections.emptyList(), frame, calcManager);
+        calcManager.startWorker(worker);
+      });
+      parent.add(item);
+    }
+    if (operation.hasParameters())
+    {
+      var item = new JMenuItem(
+          MessageManager.getString("label.edit_settings_and_run"));
+      item.setToolTipText(MessageManager.getString(
+          "label.view_and_change_parameters_before_running_calculation"));
+      item.addActionListener((event) -> {
+        openEditParamsDialog(operation.getParamStore(), null, null)
+            .thenAcceptAsync((arguments) -> {
+              if (arguments != null)
+              {
+                var worker = createWorker(arguments, frame, calcManager);
+                calcManager.startWorker(worker);
+              }
+            });
+      });
+      parent.add(item);
+    }
+  }
+
+  protected void buildInteractiveMenu(JMenu parent, AlignFrame frame)
+  {
+    final var calcName = operation.getName();
+    final var calcManager = frame.getViewport().getCalcManager();
+    final var arguments = new ArrayList<ArgumentI>();
+    final JCheckBoxMenuItem runItem;
+    {
+      // TODO use MessageManager and set tool tip text
+      runItem = new JCheckBoxMenuItem(
+          String.format("%s calculations", calcName));
+      runItem.addActionListener((event) -> {
+        calcManager.removeWorkersForName(calcName);
+        var worker = createWorker(arguments, frame, calcManager);
+        calcManager.registerWorker(worker);
+      });
+      parent.add(runItem);
+    }
+    JMenuItem _editItem = null;
+    if (operation.hasParameters())
+    {
+      // TODO use MessageManager and set tool tip text
+      _editItem = new JMenuItem(
+          String.format("Edit %s settings", calcName));
+      _editItem.addActionListener((event) -> {
+        openEditParamsDialog(operation.getParamStore(), null, null)
+            .thenAcceptAsync((args) -> {
+              if (arguments != null)
+              {
+                arguments.clear();
+                arguments.addAll(args);
+                calcManager.removeWorkersForName(calcName);
+                var worker = createWorker(arguments, frame, calcManager);
+                calcManager.registerWorker(worker);
+              }
+            });
+      });
+      parent.add(_editItem);
+    }
+    final var editItem = _editItem;
+
+    parent.addMenuListener(new MenuListener()
+      {
+        @Override
+        public void menuSelected(MenuEvent e)
+        {
+          var isNuc = frame.getViewport().getAlignment().isNucleotide();
+          var menuEnabled = (isNuc && operation.isNucleotideOperation()) ||
+              (!isNuc && operation.isProteinOperation());
+          runItem.setEnabled(menuEnabled);
+          if (editItem != null)
+            editItem.setEnabled(menuEnabled);
+          boolean currentlyRunning = calcManager.getWorkersForName(calcName).size() > 0;
+          runItem.setSelected(currentlyRunning);
+        }
+
+        @Override
+        public void menuDeselected(MenuEvent e) {}
+
+        @Override
+        public void menuCanceled(MenuEvent e) {}
+      });
+  }
+  
+
+  private CompletionStage<List<ArgumentI>> openEditParamsDialog(
+          ParamDatastoreI paramStore, WsParamSetI preset,
+          List<ArgumentI> arguments)
+  {
+    WsJobParameters jobParams;
+    if (preset == null && arguments != null && arguments.size() > 0)
+      jobParams = new WsJobParameters(paramStore, preset, arguments);
+    else
+      jobParams = new WsJobParameters(paramStore, preset, null);
+    if (preset != null)
+    {
+      jobParams.setName(MessageManager.getString(
+          "label.adjusting_parameters_for_calculation"));
+    }
+    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;
+      }
+    });
+  }
+  
+
+  private AnnotationServiceWorker createWorker(
+      List<ArgumentI> arguments, AlignFrame frame, AlignCalcManagerI2 calcManager)
+  {
+    /* What is the purpose of AlignViewport and AlignmentViewPanel? */
+    AlignViewport viewport = frame.getCurrentView();
+    AlignmentViewPanel alignPanel = frame.alignPanel;
+    return new AnnotationServiceWorker(operation,
+        arguments, viewport, alignPanel, frame, frame,
+        calcManager);
+  }
+
+}
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;
     }
   }
 
index 55ed03b..c1d2c7e 100644 (file)
@@ -21,11 +21,13 @@ import jalview.io.FeaturesFile;
 import jalview.util.Format;
 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;
 import jalview.ws2.PollingTaskExecutor;
 import jalview.ws2.WebServiceI;
+import jalview.ws2.gui.AnnotationMenuBuilder;
 
 /**
  *
@@ -65,6 +67,12 @@ public class AnnotationOperation implements Operation
   {
     return service.getName();
   }
+  
+  @Override
+  public String getDescription()
+  {
+    return service.getDescription();
+  }
 
   @Override
   public String getTypeName()
@@ -77,6 +85,18 @@ public class AnnotationOperation implements Operation
   {
     return service.getHostName();
   }
+  
+  @Override
+  public boolean hasParameters()
+  {
+    return service.hasParameters();
+  }
+  
+  @Override
+  public ParamDatastoreI getParamStore()
+  {
+    return service.getParamStore();
+  }
 
   @Override
   public int getMinSequences()
@@ -145,119 +165,9 @@ public class AnnotationOperation implements Operation
   @Override
   public MenuEntryProviderI getMenuBuilder()
   {
-    if (isInteractive())
-      return this::buildInteractiveMenu;
-    else
-      return this::buildClassicMenu;
-  }
-
-  protected void buildClassicMenu(JMenu parent, AlignFrame frame)
-  {
-    final var calcName = service.getName();
-    PollingTaskExecutor wsExecutor = frame.getViewport().getWSExecutor();
-    final var calcManager = frame.getViewport().getCalcManager();
-    {
-      var item = new JMenuItem(MessageManager.formatMessage(
-          "label.calcname_with_default_settings", calcName));
-      item.addActionListener((event) -> {
-        var worker = createWorker(Collections.emptyList(), frame, calcManager);
-        calcManager.startWorker(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_running_calculation"));
-      item.addActionListener((event) -> {
-        openEditParamsDialog(service, null, null)
-            .thenAcceptAsync((arguments) -> {
-              if (arguments != null)
-              {
-                var worker = createWorker(arguments, frame, calcManager);
-                calcManager.startWorker(worker);
-              }
-            });
-      });
-      parent.add(item);
-    }
-  }
-
-  protected void buildInteractiveMenu(JMenu parent, AlignFrame frame)
-  {
-    final var calcName = service.getName();
-    final var calcManager = frame.getViewport().getCalcManager();
-    final var arguments = new ArrayList<ArgumentI>();
-    final JCheckBoxMenuItem runItem;
-    {
-      // TODO use MessageManager and set tool tip text
-      runItem = new JCheckBoxMenuItem(
-          String.format("%s calculations", calcName));
-      runItem.addActionListener((event) -> {
-        calcManager.removeWorkersForName(calcName);
-        var worker = createWorker(arguments, frame, calcManager);
-        calcManager.registerWorker(worker);
-      });
-      parent.add(runItem);
-    }
-    JMenuItem _editItem = null;
-    if (service.hasParameters())
-    {
-      // TODO use MessageManager and set tool tip text
-      _editItem = new JMenuItem(
-          String.format("Edit %s settings", calcName));
-      _editItem.addActionListener((event) -> {
-        openEditParamsDialog(service, null, null)
-            .thenAcceptAsync((args) -> {
-              if (arguments != null)
-              {
-                arguments.clear();
-                arguments.addAll(args);
-                calcManager.removeWorkersForName(calcName);
-                var worker = createWorker(arguments, frame, calcManager);
-                calcManager.registerWorker(worker);
-              }
-            });
-      });
-      parent.add(_editItem);
-    }
-    final var editItem = _editItem;
-
-    parent.addMenuListener(new MenuListener()
-      {
-        @Override
-        public void menuSelected(MenuEvent e)
-        {
-          var isNuc = frame.getViewport().getAlignment().isNucleotide();
-          var menuEnabled = (isNuc && isNucleotideOperation()) ||
-              (!isNuc && isProteinOperation());
-          runItem.setEnabled(menuEnabled);
-          if (editItem != null)
-            editItem.setEnabled(menuEnabled);
-          boolean currentlyRunning = calcManager.getWorkersForName(calcName).size() > 0;
-          runItem.setSelected(currentlyRunning);
-        }
-
-        @Override
-        public void menuDeselected(MenuEvent e) {}
-
-        @Override
-        public void menuCanceled(MenuEvent e) {}
-      });
+    return new AnnotationMenuBuilder(this);
   }
 
-  private AnnotationServiceWorker createWorker(
-      List<ArgumentI> arguments, AlignFrame frame, AlignCalcManagerI2 calcManager)
-  {
-    /* What is the purpose of AlignViewport and AlignmentViewPanel? */
-    AlignViewport viewport = frame.getCurrentView();
-    AlignmentViewPanel alignPanel = frame.alignPanel;
-    return new AnnotationServiceWorker(this, service,
-        arguments, viewport, alignPanel, frame, frame,
-        calcManager);
-  }
 
   private CompletionStage<List<ArgumentI>> openEditParamsDialog(
       WebServiceI service, WsParamSetI preset, List<ArgumentI> arguments)
index 034b581..97b4199 100644 (file)
@@ -68,12 +68,13 @@ public class AnnotationServiceWorker implements PollableAlignCalcWorkerI
   private int exceptionCount = MAX_RETRY;
   private static final int MAX_RETRY = 5;
 
-  AnnotationServiceWorker(AnnotationOperation operation, WebServiceI service,
+  public AnnotationServiceWorker(AnnotationOperation operation,
       List<ArgumentI> args, AlignViewport viewport, AlignmentViewPanel alignPanel,
-      IProgressIndicator progressIndicator, AlignFrame frame, AlignCalcManagerI2 calcMan)
+      IProgressIndicator progressIndicator, AlignFrame frame, 
+      AlignCalcManagerI2 calcMan)
   {
     this.operation = operation;
-    this.service = service;
+    this.service = operation.service;
     this.args = args;
     this.viewport = viewport;
     this.alignPanel = alignPanel;
index ff559d4..f02bb59 100644 (file)
@@ -1,14 +1,21 @@
 package jalview.ws2.operations;
 
+import jalview.ws.params.ParamDatastoreI;
 import jalview.ws2.MenuEntryProviderI;
 
 public interface Operation
 {
   public String getName();
+  
+  public String getDescription();
 
   public String getTypeName();
 
   public String getHostName();
+  
+  public boolean hasParameters();
+  
+  public ParamDatastoreI getParamStore();
 
   public int getMinSequences();
 
@@ -29,4 +36,5 @@ public interface Operation
   public boolean getFilterNonStandardSymbols();
   
   public boolean getNeedsAlignedSequences();
+
 }
index c0c7502..8534f06 100644 (file)
@@ -22,7 +22,7 @@ public class SlivkaWSDiscoverer implements WebServiceDiscoverer
 
   private static SlivkaWSDiscoverer instance = null;
 
-  private List<WebServiceI> services = List.of();
+  private List<Operation> operations = List.of();
 
   private SlivkaWSDiscoverer()
   {
@@ -96,14 +96,16 @@ public class SlivkaWSDiscoverer implements WebServiceDiscoverer
     }
   }
 
-  public List<WebServiceI> getServices()
+  @Override
+  public List<Operation> getOperations()
   {
-    return Collections.unmodifiableList(services);
+    return Collections.unmodifiableList(operations);
   }
 
+  @Override
   public boolean hasServices()
   {
-    return !isRunning() && services.size() > 0;
+    return !isRunning() && operations.size() > 0;
   }
 
   public boolean isRunning()
@@ -133,16 +135,16 @@ public class SlivkaWSDiscoverer implements WebServiceDiscoverer
               reloadServices();
               return SlivkaWSDiscoverer.this;
             });
-    task.thenRun(() -> fireServicesChanged(getServices()));
+    task.thenRun(() -> fireOperationsChanged(getOperations()));
     discoveryTasks.add(task);
     return task;
   }
 
-  private List<WebServiceI> reloadServices()
+  private List<Operation> reloadServices()
   {
     Cache.log.info("Reloading Slivka services");
-    fireServicesChanged(Collections.emptyList());
-    ArrayList<WebServiceI> allServices = new ArrayList<>();
+    fireOperationsChanged(Collections.emptyList());
+    ArrayList<Operation> allOperations= new ArrayList<>();
     for (String url : getUrls())
     {
       SlivkaClient client = new SlivkaClient(url);
@@ -158,19 +160,20 @@ public class SlivkaWSDiscoverer implements WebServiceDiscoverer
       for (SlivkaService service : services)
       {
         SlivkaWebService instance = new SlivkaWebService(client, service);
+        Operation op = null;
         for (String classifier : service.classifiers)
         {
           String[] path = classifier.split("\\s*::\\s*");
           if (path.length >= 3 && path[0].toLowerCase().equals("operation")
                   && path[1].toLowerCase().equals("analysis"))
           {
-            Operation op = null;
             switch (path[path.length - 1].toLowerCase())
             {
             case "sequence alignment analysis (conservation)":
               AnnotationOperation anop;
               op = anop = new AnnotationOperation(instance,
-                  instance::getAnnotations, instance::getFeaturesFile, "Conservation");
+                  instance::getAnnotations, instance::getFeaturesFile,
+                  "Conservation");
               anop.setAlignmentAnalysis(true);
               anop.setInteractive(true);
               break;
@@ -184,20 +187,18 @@ public class SlivkaWSDiscoverer implements WebServiceDiscoverer
             }
             if (op != null)
             {
-              instance.addOperation(op);
               break;
             }
           }
         }
-        if (instance.operations.size() > 0)
-        {
-          allServices.add(instance);
+        if (op != null) {
+          allOperations.add(op);
         }
       }
     }
-    this.services = allServices;
+    this.operations = allOperations;
     Cache.log.info("Reloading slivka services finished");
-    return allServices;
+    return allOperations;
   }
 
   @Override
index 4c58b61..9f04290 100644 (file)
@@ -46,12 +46,8 @@ public class SlivkaWebService implements WebServiceI
   protected final SlivkaClient client;
 
   protected final SlivkaService service;
-
-  protected SlivkaDatastore store = null;
-
-  protected final ArrayList<Operation> operations = new ArrayList<>();
-
-  protected int typeFlags = 0;
+  
+  protected ParamDatastoreI store;
 
   protected static final EnumMap<Job.Status, WSJobStatus> statusMap = new EnumMap<>(
           Job.Status.class);
@@ -100,22 +96,6 @@ public class SlivkaWebService implements WebServiceI
   }
 
   @Override
-  public List<Operation> getOperations()
-  {
-    return operations;
-  }
-
-  void addOperation(Operation operation)
-  {
-    operations.add(operation);
-  }
-
-  void removeOperation(Operation operation)
-  {
-    operations.remove(operation);
-  }
-
-  @Override
   public boolean hasParameters()
   {
     return getParamStore().getServiceParameters().size() > 0;