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("%s
%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> openEditParamsDialog( ParamDatastoreI paramStore, WsParamSetI preset, List 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 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 { 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 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 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> sortOrders(List alorders) { List> regions = new ArrayList<>(); for (int i = 0; i < alorders.size(); i++) { List 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; } } }