--- /dev/null
+package jalview.ws2.gui;
+
+import static java.util.Objects.requireNonNullElse;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JInternalFrame;
+
+import jalview.bin.Cache;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.HiddenColumns;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.SplitFrame;
+import jalview.gui.WebserviceInfo;
+import jalview.util.ArrayUtils;
+import jalview.util.MessageManager;
+import jalview.util.Pair;
+import jalview.ws2.actions.alignment.AlignmentAction;
+import jalview.ws2.actions.alignment.AlignmentResult;
+import jalview.ws2.actions.api.JobI;
+import jalview.ws2.actions.api.TaskEventListener;
+import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.api.JobStatus;
+import jalview.ws2.api.WebService;
+import jalview.ws2.helpers.WSClientTaskWrapper;
+
+class AlignmentServiceGuiHandler
+ implements TaskEventListener<AlignmentResult>
+{
+ private final WebService<?> service;
+
+ private final AlignFrame frame;
+
+ private WebserviceInfo infoPanel;
+
+ private String alnTitle; // title of the alignment used in new window
+
+ private JobI[] jobs = new JobI[0];
+
+ private int[] tabs = new int[0];
+
+ private int[] logOffset = new int[0];
+
+ private int[] errLogOffset = new int[0];
+
+ public AlignmentServiceGuiHandler(AlignmentAction action, AlignFrame frame)
+ {
+ this.service = action.getWebService();
+ this.frame = frame;
+ String panelInfo = String.format("%s using service hosted at %s%n%s",
+ service.getName(), service.getUrl(), service.getDescription());
+ infoPanel = new WebserviceInfo(service.getName(), panelInfo, false);
+ String actionName = requireNonNullElse(action.getName(), "Alignment");
+ alnTitle = String.format("%s %s of %s", service.getName(), actionName,
+ frame.getTitle());
+ }
+
+ @Override
+ public void taskStatusChanged(TaskI<AlignmentResult> source, JobStatus status)
+ {
+ switch (status)
+ {
+ case INVALID:
+ infoPanel.setVisible(false);
+ JvOptionPane.showMessageDialog(frame,
+ MessageManager.getString("info.invalid_msa_input_mininfo"),
+ MessageManager.getString("info.invalid_msa_notenough"),
+ JvOptionPane.INFORMATION_MESSAGE);
+ break;
+ case READY:
+ infoPanel.setthisService(new WSClientTaskWrapper(source));
+ infoPanel.setVisible(true);
+ // intentional no break
+ case SUBMITTED:
+ case QUEUED:
+ infoPanel.setStatus(WebserviceInfo.STATE_QUEUING);
+ break;
+ case RUNNING:
+ case UNKNOWN: // unsure what to do with unknown
+ infoPanel.setStatus(WebserviceInfo.STATE_RUNNING);
+ break;
+ case COMPLETED:
+ infoPanel.setProgressBar(
+ MessageManager.getString("status.collecting_job_results"),
+ jobs[0].getInternalId());
+ infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_OK);
+ break;
+ case FAILED:
+ infoPanel.removeProgressBar(jobs[0].getInternalId());
+ infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
+ break;
+ case CANCELLED:
+ infoPanel.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
+ break;
+ case SERVER_ERROR:
+ infoPanel.removeProgressBar(jobs[0].getInternalId());
+ infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
+ break;
+ }
+ }
+
+ @Override
+ public void taskStarted(TaskI<AlignmentResult> source, List<? extends JobI> subJobs)
+ {
+ jobs = subJobs.toArray(new JobI[0]);
+ tabs = new int[subJobs.size()];
+ logOffset = new int[subJobs.size()];
+ errLogOffset = new int[subJobs.size()];
+ for (int i = 0; i < subJobs.size(); i++)
+ {
+ JobI job = jobs[i];
+ int tabIndex = infoPanel.addJobPane();
+ tabs[i] = tabIndex;
+ infoPanel.setProgressName(String.format("region %d", i), tabIndex);
+ infoPanel.setProgressText(tabIndex, alnTitle + "\nJob details\n");
+ // jobs should not have states other than invalid or ready at this point
+ if (job.getStatus() == JobStatus.INVALID)
+ infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_STOPPED_OK);
+ else if (job.getStatus() == JobStatus.READY)
+ infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_QUEUING);
+ }
+ }
+
+ @Override
+ public void taskCompleted(TaskI<AlignmentResult> source, AlignmentResult result)
+ {
+ if (result == null)
+ {
+ infoPanel.setFinishedNoResults();
+ return;
+ }
+ infoPanel.showResultsNewFrame.addActionListener(evt -> {
+ var aln = result.getAlignment();
+ // copy alignment for each frame to have its own isntance
+ var alnCpy = new Alignment(aln);
+ alnCpy.setGapCharacter(aln.getGapCharacter());
+ alnCpy.setDataset(aln.getDataset());
+ displayResultsNewFrame(alnCpy, result.getAlignmentOrders(),
+ result.getHiddenColumns());
+ });
+ infoPanel.setResultsReady();
+ }
+
+ private void displayResultsNewFrame(Alignment aln,
+ List<AlignmentOrder> alorders, HiddenColumns hidden)
+ {
+ AlignFrame newFrame = new AlignFrame(aln, hidden, AlignFrame.DEFAULT_WIDTH,
+ AlignFrame.DEFAULT_HEIGHT);
+ newFrame.getFeatureRenderer().transferSettings(
+ frame.getFeatureRenderer().getSettings());
+ if (alorders.size() > 0)
+ {
+ addSortByMenuItems(newFrame, alorders);
+ }
+
+ var requestingFrame = frame;
+ var splitContainer = requestingFrame.getSplitViewContainer();
+ if (splitContainer != null && splitContainer.getComplement(requestingFrame) != null)
+ {
+ AlignmentI complement = splitContainer.getComplement(requestingFrame);
+ String complementTitle = splitContainer.getComplementTitle(requestingFrame);
+ Alignment copyComplement = new Alignment(complement);
+ copyComplement.setGapCharacter(complement.getGapCharacter());
+ copyComplement.setDataset(complement.getDataset());
+ copyComplement.alignAs(aln);
+ if (copyComplement.getHeight() > 0)
+ {
+ newFrame.setTitle(alnTitle);
+ AlignFrame newFrame2 = new AlignFrame(copyComplement,
+ AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+ newFrame2.setTitle(complementTitle);
+ String linkedTitle = MessageManager.getString("label.linked_view_title");
+ JInternalFrame splitFrame = new SplitFrame(
+ aln.isNucleotide() ? newFrame : newFrame2,
+ aln.isNucleotide() ? newFrame2 : newFrame);
+ Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
+ return;
+ }
+ }
+ // no split frame or failed to create complementary alignment
+ Desktop.addInternalFrame(frame, alnTitle, AlignFrame.DEFAULT_WIDTH,
+ AlignFrame.DEFAULT_HEIGHT);
+ }
+
+ private void addSortByMenuItems(AlignFrame frame, List<AlignmentOrder> alorders)
+ {
+ if (alorders.size() == 1)
+ {
+ frame.addSortByOrderMenuItem(service.getName() + " Ordering",
+ alorders.get(0));
+ return;
+ }
+ BitSet collected = new BitSet(alorders.size());
+ for (int i = 0, N = alorders.size(); i < N; i++)
+ {
+ if (collected.get(i))
+ continue;
+ var regions = new ArrayList<String>();
+ var order = alorders.get(i);
+ for (int j = i; j < N; j++)
+ {
+ if (!collected.get(j) && alorders.get(j).equals(order))
+ {
+ regions.add(Integer.toString(j + 1));
+ collected.set(j);
+ }
+ }
+ var orderName = String.format("%s Region %s Ordering",
+ service.getName(), String.join(",", regions));
+ frame.addSortByOrderMenuItem(orderName, order);
+ }
+ }
+
+ @Override
+ public void taskException(TaskI<AlignmentResult> source, Exception e)
+ {
+ Cache.log.error(String.format("Service %s raised an exception.", service.getName()), e);
+ infoPanel.appendProgressText(e.getMessage());
+ }
+
+ @Override
+ public void taskRestarted(TaskI<AlignmentResult> source)
+ {
+ // alignment services are not restartable
+ }
+
+ @Override
+ public void subJobStatusChanged(TaskI<AlignmentResult> source, JobI job, JobStatus status)
+ {
+ int i = ArrayUtils.indexOf(jobs, job);
+ assert i >= 0 : "job does not exist";
+ if (i < 0)
+ // safeguard that should not happen irl
+ return;
+ int wsStatus;
+ switch (status)
+ {
+ case INVALID:
+ case COMPLETED:
+ wsStatus = WebserviceInfo.STATE_STOPPED_OK;
+ break;
+ case READY:
+ case SUBMITTED:
+ case QUEUED:
+ wsStatus = WebserviceInfo.STATE_QUEUING;
+ break;
+ case RUNNING:
+ case UNKNOWN:
+ wsStatus = WebserviceInfo.STATE_RUNNING;
+ break;
+ case FAILED:
+ wsStatus = WebserviceInfo.STATE_STOPPED_ERROR;
+ break;
+ case CANCELLED:
+ wsStatus = WebserviceInfo.STATE_CANCELLED_OK;
+ break;
+ case SERVER_ERROR:
+ wsStatus = WebserviceInfo.STATE_STOPPED_SERVERERROR;
+ break;
+ default:
+ throw new AssertionError("Non-exhaustive switch statement");
+ }
+ infoPanel.setStatus(tabs[i], wsStatus);
+ }
+
+ @Override
+ public void subJobLogChanged(TaskI<AlignmentResult> source, JobI job, String log)
+ {
+ int i = ArrayUtils.indexOf(jobs, job);
+ assert i >= 0 : "job does not exist";
+ if (i < 0)
+ // safeguard that should never happen
+ return;
+ infoPanel.appendProgressText(tabs[i], log.substring(logOffset[i]));
+ }
+
+ @Override
+ public void subJobErrorLogChanged(TaskI<AlignmentResult> source, JobI job, String log)
+ {
+ int i = ArrayUtils.indexOf(jobs, job);
+ assert i >= 0 : "job does not exist";
+ if (i < 0)
+ // safeguard that should never happen
+ return;
+ infoPanel.appendProgressText(tabs[i], log.substring(errLogOffset[i]));
+ }
+
+}