From 0eb7e53ef07892231fb5d447a67b6e64d4131415 Mon Sep 17 00:00:00 2001 From: Mateusz Warowny Date: Mon, 11 Apr 2022 19:46:31 +0200 Subject: [PATCH] JAL-3878 Build interactive services menu --- src/jalview/ws2/actions/AbstractPollableTask.java | 7 + .../ws2/actions/annotation/AnnotationTask.java | 10 +- src/jalview/ws2/gui/WebServicesMenuManager.java | 208 +++++++++++++++++++- 3 files changed, 222 insertions(+), 3 deletions(-) diff --git a/src/jalview/ws2/actions/AbstractPollableTask.java b/src/jalview/ws2/actions/AbstractPollableTask.java index bf58b13..fc3c554 100644 --- a/src/jalview/ws2/actions/AbstractPollableTask.java +++ b/src/jalview/ws2/actions/AbstractPollableTask.java @@ -341,4 +341,11 @@ public abstract class AbstractPollableTask implements Task { return result; } + + @Override + public String toString() + { + var status = taskStatus != null ? taskStatus.name() : "UNSET"; + return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, status); + } } diff --git a/src/jalview/ws2/actions/annotation/AnnotationTask.java b/src/jalview/ws2/actions/annotation/AnnotationTask.java index 8f210e0..9d16400 100644 --- a/src/jalview/ws2/actions/annotation/AnnotationTask.java +++ b/src/jalview/ws2/actions/annotation/AnnotationTask.java @@ -209,6 +209,7 @@ public class AnnotationTask implements TaskI void stop() { + calcMan.disableWorker(this); super.abortAndDestroy(); } @@ -228,6 +229,13 @@ public class AnnotationTask implements TaskI // dispose of unfinished jobs just in case cancelJobs(); } + + @Override + public String toString() + { + return AnnotationTask.this.toString() + "$AlignCalcWorker@" + + Integer.toHexString(hashCode()); + } } public AnnotationTask(AnnotationWebServiceClientI client, @@ -576,6 +584,6 @@ public class AnnotationTask implements TaskI public String toString() { var status = taskStatus != null ? taskStatus.name() : "UNSET"; - return String.format("AnnotationTask(%d, %s)", uid, status); + return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, status); } } diff --git a/src/jalview/ws2/gui/WebServicesMenuManager.java b/src/jalview/ws2/gui/WebServicesMenuManager.java index c75c483..3cc2531 100644 --- a/src/jalview/ws2/gui/WebServicesMenuManager.java +++ b/src/jalview/ws2/gui/WebServicesMenuManager.java @@ -3,6 +3,7 @@ package jalview.ws2.gui; import java.awt.Color; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.lang.ref.WeakReference; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -16,9 +17,12 @@ import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.CompletionStage; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.ToolTipManager; +import javax.swing.border.EmptyBorder; import jalview.gui.AlignFrame; import jalview.gui.Desktop; @@ -37,6 +41,9 @@ import jalview.ws2.api.Credentials; import jalview.ws2.api.WebService; import jalview.ws2.client.api.WebServiceProviderI; +import static java.lang.String.format; +import static java.util.Objects.requireNonNullElse; + public class WebServicesMenuManager { private final JMenu menu; @@ -52,6 +59,8 @@ public class WebServicesMenuManager noServicesItem.setEnabled(false); } + private Map>> interactiveTasks = new HashMap<>(); + public WebServicesMenuManager(String name, AlignFrame frame) { this.frame = frame; @@ -102,7 +111,7 @@ public class WebServicesMenuManager { if (oneshot != null) categoryMenu.addSeparator(); - // addInteractiveEntries(interactive, categoryMenu); + addInteractiveEntries(interactive, categoryMenu); } menu.add(categoryMenu); } @@ -178,7 +187,7 @@ public class WebServicesMenuManager { String text = itemName; if (datastore.hasParameters() || datastore.hasPresets()) - text += "with defaults"; + text += " with defaults"; JMenuItem item = new JMenuItem(text); item.addActionListener(e -> { runAction(action, frame.getCurrentView(), Collections.emptyList(), @@ -238,6 +247,201 @@ public class WebServicesMenuManager } } + private void addInteractiveEntries(List> services, JMenu menu) + { + Map>> byServiceName = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (var service : services) + { + byServiceName.computeIfAbsent(service.getName(), k -> new ArrayList<>()) + .add(service); + } + for (var entry : byServiceName.entrySet()) + { + var group = new InteractiveServiceEntryGroup(entry.getKey(), entry.getValue()); + group.appendTo(menu); + } + } + + private class InteractiveServiceEntryGroup + { + JLabel serviceLabel; + + JMenuItem urlItem = new JMenuItem(); + + JCheckBoxMenuItem serviceItem = new JCheckBoxMenuItem(); + + JMenuItem editParamsItem = new JMenuItem("Edit parameters..."); + + JMenu presetsMenu = new JMenu("Change preset"); + + JMenu alternativesMenu = new JMenu("Choose action"); + { + urlItem.setForeground(Color.BLUE); + urlItem.setVisible(false); + serviceItem.setVisible(false); + editParamsItem.setVisible(false); + presetsMenu.setVisible(false); + } + + InteractiveServiceEntryGroup(String name, List> services) + { + serviceLabel = new JLabel(name); + serviceLabel.setBorder(new EmptyBorder(0, 6, 0, 6)); + buildAlternativesMenu(services); + } + + private void buildAlternativesMenu(List> services) + { + var menu = alternativesMenu; + services.sort(Comparator + ., String> comparing(s -> s.getUrl().toString()) + .thenComparing(s -> s.getName())); + URL lastHost = null; + for (var service : services) + { + // Adding url "separator" before each group + URL host = service.getUrl(); + if (!host.equals(lastHost)) + { + if (lastHost != null) + menu.addSeparator(); + var item = new JMenuItem(host.toString()); + item.setForeground(Color.BLUE); + item.addActionListener(e -> Desktop.showUrl(host.toString())); + menu.add(item); + lastHost = host; + } + menu.addSeparator(); + var actionsByCategory = new TreeMap>>(); + for (ActionI action : service.getActions()) + { + actionsByCategory + .computeIfAbsent( + requireNonNullElse(action.getSubcategory(), ""), + k -> new ArrayList<>()) + .add(action); + } + actionsByCategory.forEach((key, actions) -> { + var atMenu = key.isEmpty() ? menu : new JMenu(key + " with " + service.getName()); + boolean topLevel = atMenu == menu; + if (!topLevel) + menu.add(atMenu); + actions.sort(Comparator.comparing( + a -> a.getName(), + Comparator.nullsFirst(Comparator.naturalOrder()))); + for (ActionI action : actions) + { + var item = new JMenuItem(action.getFullName()); + item.addActionListener(e -> setAlternative(action)); + atMenu.add(item); + } + }); + } + } + + private void setAlternative(ActionI action) + { + final var arguments = new ArrayList(); + final WsParamSetI[] lastPreset = { null }; + + // update selected url menu item + String url = action.getWebService().getUrl().toString(); + urlItem.setText(url); + urlItem.setVisible(true); + for (var l : urlItem.getActionListeners()) + urlItem.removeActionListener(l); + urlItem.addActionListener(e -> Desktop.showUrl(url)); + + // update selected service menu item + serviceItem.setText(action.getFullName()); + serviceItem.setVisible(true); + for (var l : serviceItem.getActionListeners()) + serviceItem.removeActionListener(l); + WebService service = action.getWebService(); + serviceItem.addActionListener(e -> { + if (serviceItem.getState()) + { + cancelAndRunInteractive(action, frame.getCurrentView(), arguments, + Credentials.empty()); + } + else + { + cancelInteractive(service.getName()); + } + }); + serviceItem.setSelected(true); + + // update edit parameters menu item + var datastore = service.getParamDatastore(); + editParamsItem.setVisible(datastore.hasParameters()); + for (var l : editParamsItem.getActionListeners()) + editParamsItem.removeActionListener(l); + if (datastore.hasParameters()) + { + editParamsItem.addActionListener(e -> { + openEditParamsDialog(service.getParamDatastore(), lastPreset[0], arguments) + .thenAccept(args -> { + if (args != null) + { + lastPreset[0] = null; + arguments.clear(); + arguments.addAll(args); + cancelAndRunInteractive(action, frame.getCurrentView(), + arguments, Credentials.empty()); + } + }); + }); + } + + // update presets menu + presetsMenu.removeAll(); + presetsMenu.setEnabled(datastore.hasPresets()); + if (datastore.hasPresets()) + { + for (WsParamSetI preset : datastore.getPresets()) + { + var item = new JMenuItem(preset.getName()); + item.addActionListener(e -> { + lastPreset[0] = preset; + cancelAndRunInteractive(action, frame.getCurrentView(), + preset.getArguments(), Credentials.empty()); + }); + presetsMenu.add(item); + } + } + + cancelAndRunInteractive(action, frame.getCurrentView(), arguments, + Credentials.empty()); + } + + void appendTo(JMenu menu) + { + menu.add(serviceLabel); + menu.add(urlItem); + menu.add(serviceItem); + menu.add(editParamsItem); + menu.add(presetsMenu); + menu.add(alternativesMenu); + } + } + + private void cancelInteractive(String wsName) + { + var taskRef = interactiveTasks.get(wsName); + if (taskRef != null && taskRef.get() != null) + taskRef.get().cancel(); + interactiveTasks.put(wsName, null); + } + + private void cancelAndRunInteractive(ActionI action, + AlignmentViewport viewport, List args, Credentials credentials) + { + var wsName = action.getWebService().getName(); + cancelInteractive(wsName); + var task = runAction(action, viewport, args, credentials); + interactiveTasks.put(wsName, new WeakReference<>(task)); + } + private TaskI runAction(ActionI action, AlignmentViewport viewport, List args, Credentials credentials) { -- 1.7.10.2