+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;
+ }
+
+
+ }
+}
+