JAL-3878 Separate gui elements from operations.
[jalview.git] / src / jalview / ws2 / gui / AlignmentMenuBuilder.java
1 package jalview.ws2.gui;
2
3 import static java.lang.String.format;
4
5 import java.awt.event.MouseAdapter;
6 import java.awt.event.MouseEvent;
7 import java.util.ArrayList;
8 import java.util.Collections;
9 import java.util.List;
10 import java.util.Objects;
11 import java.util.concurrent.CompletionStage;
12 import java.util.function.Consumer;
13
14 import javax.swing.JMenu;
15 import javax.swing.JMenuItem;
16 import javax.swing.ToolTipManager;
17
18 import jalview.datamodel.Alignment;
19 import jalview.datamodel.AlignmentI;
20 import jalview.datamodel.AlignmentOrder;
21 import jalview.datamodel.AlignmentView;
22 import jalview.datamodel.HiddenColumns;
23 import jalview.gui.AlignFrame;
24 import jalview.gui.AlignViewport;
25 import jalview.gui.Desktop;
26 import jalview.gui.JvOptionPane;
27 import jalview.gui.JvSwingUtils;
28 import jalview.gui.WebserviceInfo;
29 import jalview.gui.WsJobParameters;
30 import jalview.util.MathUtils;
31 import jalview.util.MessageManager;
32 import jalview.ws.params.ArgumentI;
33 import jalview.ws.params.ParamDatastoreI;
34 import jalview.ws.params.WsParamSetI;
35 import jalview.ws2.MenuEntryProviderI;
36 import jalview.ws2.PollingTaskExecutor;
37 import jalview.ws2.WSJob;
38 import jalview.ws2.WebServiceInfoUpdater;
39 import jalview.ws2.WebServiceWorkerI;
40 import jalview.ws2.WebServiceWorkerListener;
41 import jalview.ws2.operations.AlignmentOperation;
42 import jalview.ws2.operations.AlignmentOperation.AlignmentResult;
43 import jalview.ws2.operations.AlignmentOperation.AlignmentWorker;
44
45
46 public class AlignmentMenuBuilder implements MenuEntryProviderI
47 {
48   AlignmentOperation operation;
49   
50   public AlignmentMenuBuilder(AlignmentOperation operation)
51   {
52     this.operation = operation;
53   }
54   
55   public void buildMenu(JMenu parent, AlignFrame frame)
56   {
57     if (operation.canSubmitGaps())
58     {
59       var alignSubmenu = new JMenu(operation.getName());
60       buildMenu(alignSubmenu, frame, false);
61       parent.add(alignSubmenu);
62       var realignSubmenu = new JMenu(MessageManager.formatMessage(
63               "label.realign_with_params", operation.getName()));
64       realignSubmenu.setToolTipText(MessageManager
65               .getString("label.align_sequences_to_existing_alignment"));
66       buildMenu(realignSubmenu, frame, true);
67       parent.add(realignSubmenu);
68     }
69     else
70     {
71       buildMenu(parent, frame, false);
72     }
73   }
74
75   protected void buildMenu(JMenu parent, AlignFrame frame,
76           boolean submitGaps)
77   {
78     final String action = submitGaps ? "Align" : "Realign";
79     final var calcName = operation.getName();
80
81     {
82       var item = new JMenuItem(MessageManager.formatMessage(
83               "label.calcname_with_default_settings", calcName));
84       item.setToolTipText(MessageManager
85               .formatMessage("label.action_with_default_settings", action));
86       item.addActionListener((event) -> {
87         final AlignmentView msa = frame.gatherSequencesForAlignment();
88         if (msa != null)
89         {
90           startWorker(frame, msa, Collections.emptyList(), submitGaps);
91         }
92       });
93       parent.add(item);
94     }
95
96     if (operation.hasParameters())
97     {
98       var item = new JMenuItem(
99               MessageManager.getString("label.edit_settings_and_run"));
100       item.setToolTipText(MessageManager.getString(
101               "label.view_and_change_parameters_before_alignment"));
102       item.addActionListener((event) -> {
103         AlignmentView msa = frame.gatherSequencesForAlignment();
104         if (msa != null)
105         {
106           openEditParamsDialog(operation.getParamStore(), null, null)
107               .thenAcceptAsync((arguments) -> {
108                 if (arguments != null)
109                 {
110                   startWorker(frame, msa, arguments, submitGaps);
111                 }
112               });
113         }
114       });
115       parent.add(item);
116     }
117
118     var presets = operation.getParamStore().getPresets();
119     if (presets != null && presets.size() > 0)
120     {
121       final var presetList = new JMenu(MessageManager
122               .formatMessage("label.run_with_preset_params", calcName));
123       final var showToolTipFor = ToolTipManager.sharedInstance()
124               .getDismissDelay();
125       for (final var preset : presets)
126       {
127         var item = new JMenuItem(preset.getName());
128         final int QUICK_TOOLTIP = 1500;
129         item.addMouseListener(new MouseAdapter()
130         {
131           @Override
132           public void mouseEntered(MouseEvent e)
133           {
134             ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
135           }
136
137           @Override
138           public void mouseExited(MouseEvent e)
139           {
140             ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
141           }
142         });
143         String tooltip = JvSwingUtils.wrapTooltip(true,
144                 format("<strong>%s</strong><br/>%s",
145                         MessageManager.getString(
146                                 preset.isModifiable() ? "label.user_preset"
147                                         : "label.service_preset"),
148                         preset.getDescription()));
149         item.setToolTipText(tooltip);
150         item.addActionListener((event) -> {
151           AlignmentView msa = frame.gatherSequencesForAlignment();
152           startWorker(frame, msa, preset.getArguments(), submitGaps);
153         });
154         presetList.add(item);
155       }
156       parent.add(presetList);
157     }
158   }
159
160   private CompletionStage<List<ArgumentI>> openEditParamsDialog(
161           ParamDatastoreI paramStore, WsParamSetI preset,
162           List<ArgumentI> arguments)
163   {
164     WsJobParameters jobParams;
165     if (preset == null && arguments != null && arguments.size() > 0)
166       jobParams = new WsJobParameters(paramStore, preset, arguments);
167     else
168       jobParams = new WsJobParameters(paramStore, preset, null);
169     if (preset != null)
170     {
171       jobParams.setName(MessageManager.getString(
172           "label.adjusting_parameters_for_calculation"));
173     }
174     var stage = jobParams.showRunDialog();
175     return stage.thenApply((startJob) -> {
176       if (startJob)
177       {
178         if (jobParams.getPreset() == null)
179         {
180           return jobParams.getJobParams();
181         }
182         else
183         {
184           return jobParams.getPreset().getArguments();
185         }
186       }
187       else
188       {
189         return null;
190       }
191     });
192   }
193
194   private void startWorker(AlignFrame frame, AlignmentView msa,
195       List<ArgumentI> arguments, boolean submitGaps)
196   {
197     AlignViewport viewport = frame.getViewport();
198     PollingTaskExecutor executor = frame.getViewport().getWSExecutor();
199     if (msa != null)
200     {
201
202       String panelInfo = String.format("%s using service hosted at %s%n%s",
203               operation.getName(), operation.getHostName(),
204               Objects.requireNonNullElse(operation.getDescription(), ""));
205       var wsInfo = new WebserviceInfo(operation.getName(), panelInfo, false);
206       
207       final String alnTitle = frame.getTitle();
208       AlignmentWorker worker = operation.new AlignmentWorker(msa,
209           arguments, frame.getTitle(), submitGaps, true,
210           viewport);
211       String outputHeader = String.format("%s of %s%nJob details%n",
212           submitGaps ? "Re-alignment" : "Alignment", alnTitle);
213
214       var awl = new AlignmentWorkerListener(worker, wsInfo, frame,
215           outputHeader);
216       worker.setResultConsumer(awl);
217       worker.addListener(awl);
218       
219       executor.submit(worker);
220     }
221     
222   }
223   
224   private class AlignmentWorkerListener
225       implements WebServiceWorkerListener, Consumer<AlignmentResult>
226   {
227
228     final WebServiceWorkerI worker;
229     final WebserviceInfo wsInfo;
230     final AlignFrame frame;
231     WebServiceInfoUpdater updater;
232     String outputHeader;
233     final long progbarId = MathUtils.getUID();
234     
235     private AlignmentWorkerListener(WebServiceWorkerI worker, WebserviceInfo wsInfo,
236         AlignFrame frame, String header)
237     {
238       this.worker = worker;
239       this.wsInfo = wsInfo;
240       this.frame = frame;
241       this.outputHeader = header;
242       this.updater = new WebServiceInfoUpdater(worker, wsInfo);
243       updater.setOutputHeader(outputHeader);
244     }
245     
246     @Override
247     public void workerStarted(WebServiceWorkerI source)
248     {
249       // wsInfo.setThisService() should happen here
250       wsInfo.setVisible(true);
251     }
252     
253     @Override
254     public void workerNotStarted(WebServiceWorkerI source)
255     {
256       wsInfo.setVisible(false);
257       // TODO show notification dialog.
258        JvOptionPane.showMessageDialog(frame,
259            MessageManager.getString("info.invalid_msa_input_mininfo"),
260            MessageManager.getString("info.invalid_msa_notenough"),
261            JvOptionPane.INFORMATION_MESSAGE);
262     }
263
264     @Override
265     public void jobCreated(WebServiceWorkerI source, WSJob job)
266     {
267       int tabIndex = wsInfo.addJobPane();
268       wsInfo.setProgressName(String.format("region %d", job.getJobNum()), 
269           tabIndex);
270       wsInfo.setProgressText(tabIndex, outputHeader);
271       job.addPropertyChangeListener(updater);
272     }
273     
274     @Override
275     public void pollException(WebServiceWorkerI source, WSJob job, Exception e)
276     {
277       wsInfo.appendProgressText(job.getJobNum(),
278           MessageManager.formatMessage("info.server_exception",
279               operation.getName(), e.getMessage()));
280     }
281
282     @Override
283     public void workerCompleting(WebServiceWorkerI source)
284     {
285       // TODO Auto-generated method stub
286       wsInfo.setProgressBar(
287               MessageManager.getString("status.collecting_job_results"),
288               progbarId);
289     }
290     
291     @Override
292     public void workerCompleted(WebServiceWorkerI source)
293     {
294       wsInfo.removeProgressBar(progbarId);
295       
296     }
297
298     @Override
299     public void accept(AlignmentResult out)
300     {
301       if (out != null)
302       {
303         wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
304                 new Alignment(out.getAln()), out.getAlorders(), out.getHidden()));
305         wsInfo.setResultsReady();
306       }
307       else
308       {
309         wsInfo.setFinishedNoResults();
310       }
311     }
312     
313     private void displayNewFrame(AlignmentI aln,
314         List<AlignmentOrder> alorders, HiddenColumns hidden)
315     {
316       AlignFrame frame = new AlignFrame(aln, hidden,
317               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
318       // TODO store feature renderer settings in worker object
319       // frame.getFeatureRenderer().transferSettings(featureSettings);
320       var regions = sortOrders(alorders);
321       if (alorders.size() == 1)
322       {
323         frame.addSortByOrderMenuItem(
324                 format("%s Ordering", operation.getName()), alorders.get(0));
325       }
326       else
327       {
328         for (int i = 0; i < alorders.size(); i++)
329         {
330           final int j = i;
331           Iterable<String> iter = () -> regions.get(j).stream()
332                   .map(it -> Integer.toString(it)).iterator();
333           var orderName = format("%s Region %s Ordering", operation.getName(),
334                   String.join(",", iter));
335           frame.addSortByOrderMenuItem(orderName, alorders.get(i));
336         }
337       }
338     
339       /* TODO
340        * If alignment was requested from one half of a SplitFrame, show in a
341        * SplitFrame with the other pane similarly aligned.
342        */
343     
344       Desktop.addInternalFrame(frame, frame.getTitle(), AlignFrame.DEFAULT_WIDTH,
345               AlignFrame.DEFAULT_HEIGHT);
346     }
347
348
349     private List<List<Integer>> sortOrders(List<?> alorders)
350     {
351       List<List<Integer>> regions = new ArrayList<>();
352       for (int i = 0; i < alorders.size(); i++)
353       {
354         List<Integer> regs = new ArrayList<>();
355         regs.add(i);
356         int j = i + 1;
357         while (j < alorders.size())
358         {
359           if (alorders.get(i).equals(alorders.get(j)))
360           {
361             alorders.remove(j);
362             regs.add(j);
363           }
364           else
365           {
366             j++;
367           }
368         }
369         regions.add(regs);
370       }
371       return regions;
372     }
373     
374     
375   }
376 }
377