475eba26165d3da7f88e8e51bb554665c10d8d77
[jalview.git] / src / jalview / bin / Commands.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import java.awt.Color;
24 import java.io.File;
25 import java.io.IOException;
26 import java.net.URISyntaxException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35
36 import javax.swing.SwingUtilities;
37
38 import jalview.analysis.AlignmentUtils;
39 import jalview.api.structures.JalviewStructureDisplayI;
40 import jalview.bin.Jalview.ExitCode;
41 import jalview.bin.argparser.Arg;
42 import jalview.bin.argparser.ArgParser;
43 import jalview.bin.argparser.ArgValue;
44 import jalview.bin.argparser.ArgValuesMap;
45 import jalview.bin.argparser.SubVals;
46 import jalview.datamodel.AlignmentI;
47 import jalview.datamodel.SequenceI;
48 import jalview.datamodel.annotations.AlphaFoldAnnotationRowBuilder;
49 import jalview.gui.AlignFrame;
50 import jalview.gui.AlignmentPanel;
51 import jalview.gui.AppJmol;
52 import jalview.gui.Desktop;
53 import jalview.gui.Preferences;
54 import jalview.gui.StructureChooser;
55 import jalview.gui.StructureViewer;
56 import jalview.gui.StructureViewer.ViewerType;
57 import jalview.io.AppletFormatAdapter;
58 import jalview.io.BackupFiles;
59 import jalview.io.BioJsHTMLOutput;
60 import jalview.io.DataSourceType;
61 import jalview.io.FileFormat;
62 import jalview.io.FileFormatException;
63 import jalview.io.FileFormatI;
64 import jalview.io.FileFormats;
65 import jalview.io.FileLoader;
66 import jalview.io.HtmlSvgOutput;
67 import jalview.io.IdentifyFile;
68 import jalview.io.NewickFile;
69 import jalview.io.exceptions.ImageOutputException;
70 import jalview.schemes.ColourSchemeI;
71 import jalview.schemes.ColourSchemeProperty;
72 import jalview.structure.StructureCommandI;
73 import jalview.structure.StructureImportSettings.TFType;
74 import jalview.structure.StructureSelectionManager;
75 import jalview.util.ColorUtils;
76 import jalview.util.FileUtils;
77 import jalview.util.HttpUtils;
78 import jalview.util.ImageMaker;
79 import jalview.util.ImageMaker.TYPE;
80 import jalview.util.MessageManager;
81 import jalview.util.Platform;
82 import jalview.util.StringUtils;
83 import jalview.util.imagemaker.BitmapImageSizing;
84
85 public class Commands
86 {
87   Desktop desktop;
88
89   private boolean headless;
90
91   private ArgParser argParser;
92
93   private Map<String, AlignFrame> afMap;
94
95   private Map<String, List<StructureViewer>> svMap;
96
97   private boolean commandArgsProvided = false;
98
99   private boolean argsWereParsed = false;
100
101   private List<String> errors = new ArrayList<>();
102
103   public Commands(ArgParser argparser, boolean headless)
104   {
105     this(Desktop.instance, argparser, headless);
106   }
107
108   public Commands(Desktop d, ArgParser argparser, boolean h)
109   {
110     argParser = argparser;
111     headless = h;
112     desktop = d;
113     afMap = new HashMap<>();
114   }
115
116   protected boolean processArgs()
117   {
118     if (argParser == null)
119     {
120       return true;
121     }
122
123     boolean theseArgsWereParsed = false;
124
125     if (argParser != null && argParser.getLinkedIds() != null)
126     {
127       for (String id : argParser.getLinkedIds())
128       {
129         ArgValuesMap avm = argParser.getLinkedArgs(id);
130         theseArgsWereParsed = true;
131         boolean processLinkedOkay = processLinked(id);
132         theseArgsWereParsed &= processLinkedOkay;
133
134         processGroovyScript(id);
135
136         // wait around until alignFrame isn't busy
137         AlignFrame af = afMap.get(id);
138         while (af != null && af.getViewport().isCalcInProgress())
139         {
140           try
141           {
142             Thread.sleep(25);
143           } catch (Exception q)
144           {
145           }
146           ;
147         }
148
149         theseArgsWereParsed &= processImages(id);
150
151         if (processLinkedOkay)
152         {
153           theseArgsWereParsed &= processOutput(id);
154         }
155
156         // close ap
157         if (avm.getBoolean(Arg.CLOSE))
158         {
159           af = afMap.get(id);
160           if (af != null)
161           {
162             af.closeMenuItem_actionPerformed(true);
163           }
164         }
165
166       }
167
168     }
169
170     // report errors - if any
171     String errorsRaised = errorsToString();
172     if (errorsRaised.trim().length() > 0)
173     {
174       Console.warn(
175               "The following errors and warnings occurred whilst processing files:\n"
176                       + errorsRaised);
177     }
178     // gui errors reported in Jalview
179
180     if (argParser.getBoolean(Arg.QUIT))
181     {
182       Jalview.getInstance().exit(
183               "Exiting due to " + Arg.QUIT.argString() + " argument.",
184               ExitCode.OK);
185       return true;
186     }
187     // carry on with jalview.bin.Jalview
188     argsWereParsed = theseArgsWereParsed;
189     return argsWereParsed;
190   }
191
192   public boolean commandArgsProvided()
193   {
194     return commandArgsProvided;
195   }
196
197   public boolean argsWereParsed()
198   {
199     return argsWereParsed;
200   }
201
202   protected boolean processLinked(String id)
203   {
204     boolean theseArgsWereParsed = false;
205     ArgValuesMap avm = argParser.getLinkedArgs(id);
206     if (avm == null)
207     {
208       return true;
209     }
210
211     Boolean isError = Boolean.valueOf(false);
212
213     // set wrap scope here so it can be applied after structures are opened
214     boolean wrap = false;
215
216     if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
217     {
218       commandArgsProvided = true;
219       final long progress = System.currentTimeMillis();
220
221       boolean first = true;
222       boolean progressBarSet = false;
223       AlignFrame af;
224       // Combine the APPEND and OPEN files into one list, along with whether it
225       // was APPEND or OPEN
226       List<ArgValue> openAvList = new ArrayList<>();
227       openAvList.addAll(avm.getArgValueList(Arg.OPEN));
228       openAvList.addAll(avm.getArgValueList(Arg.APPEND));
229       // sort avlist based on av.getArgIndex()
230       Collections.sort(openAvList);
231       for (ArgValue av : openAvList)
232       {
233         Arg a = av.getArg();
234         SubVals sv = av.getSubVals();
235         String openFile = av.getValue();
236         if (openFile == null)
237           continue;
238
239         theseArgsWereParsed = true;
240         if (first)
241         {
242           first = false;
243           if (!headless && desktop != null)
244           {
245             SwingUtilities.invokeLater(new Runnable()
246             {
247               @Override
248               public void run()
249               {
250                 desktop.setProgressBar(
251                         MessageManager.getString(
252                                 "status.processing_commandline_args"),
253                         progress);
254
255               }
256             });
257             progressBarSet = true;
258           }
259         }
260
261         if (!Platform.isJS())
262         /**
263          * ignore in JavaScript -- can't just file existence - could load it?
264          * 
265          * @j2sIgnore
266          */
267         {
268           if (!HttpUtils.startsWithHttpOrHttps(openFile))
269           {
270             if (!(new File(openFile)).exists())
271             {
272               addError("Can't find file '" + openFile + "'");
273               isError = true;
274               continue;
275             }
276           }
277         }
278
279         DataSourceType protocol = AppletFormatAdapter
280                 .checkProtocol(openFile);
281
282         FileFormatI format = null;
283         try
284         {
285           format = new IdentifyFile().identify(openFile, protocol);
286         } catch (FileFormatException e1)
287         {
288           addError("Unknown file format for '" + openFile + "'");
289           isError = true;
290           continue;
291         }
292
293         af = afMap.get(id);
294         // When to open a new AlignFrame
295         if (af == null || "true".equals(av.getSubVal("new"))
296                 || a == Arg.OPEN || format == FileFormat.Jalview)
297         {
298           if (a == Arg.OPEN)
299           {
300             Jalview.testoutput(argParser, Arg.OPEN, "examples/uniref50.fa",
301                     openFile);
302           }
303
304           Console.debug(
305                   "Opening '" + openFile + "' in new alignment frame");
306           FileLoader fileLoader = new FileLoader(!headless);
307           boolean xception = false;
308           try
309           {
310             af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
311                     format);
312           } catch (Throwable thr)
313           {
314             xception = true;
315             addError("Couldn't open '" + openFile + "' as " + format + " "
316                     + thr.getLocalizedMessage()
317                     + " (Enable debug for full stack trace)");
318             isError = true;
319             Console.debug("Exception when opening '" + openFile + "'", thr);
320           } finally
321           {
322             if (af == null && !xception)
323             {
324               addInfo("Ignoring '" + openFile
325                       + "' - no alignment data found.");
326               continue;
327             }
328           }
329
330           // colour alignment
331           String colour = avm.getFromSubValArgOrPref(av, Arg.COLOUR, sv,
332                   null, "DEFAULT_COLOUR_PROT", "");
333           this.colourAlignFrame(af, colour);
334
335           // Change alignment frame title
336           String title = avm.getFromSubValArgOrPref(av, Arg.TITLE, sv, null,
337                   null, null);
338           if (title != null)
339           {
340             af.setTitle(title);
341             Jalview.testoutput(argParser, Arg.TITLE, "test title", title);
342           }
343
344           // Add features
345           String featuresfile = avm.getValueFromSubValOrArg(av,
346                   Arg.FEATURES, sv);
347           if (featuresfile != null)
348           {
349             af.parseFeaturesFile(featuresfile,
350                     AppletFormatAdapter.checkProtocol(featuresfile));
351             Jalview.testoutput(argParser, Arg.FEATURES,
352                     "examples/testdata/plantfdx.features", featuresfile);
353           }
354
355           // Add annotations from file
356           String annotationsfile = avm.getValueFromSubValOrArg(av,
357                   Arg.ANNOTATIONS, sv);
358           if (annotationsfile != null)
359           {
360             af.loadJalviewDataFile(annotationsfile, null, null, null);
361             Jalview.testoutput(argParser, Arg.ANNOTATIONS,
362                     "examples/testdata/plantfdx.annotations",
363                     annotationsfile);
364           }
365
366           // Set or clear the sortbytree flag
367           boolean sortbytree = avm.getBoolFromSubValOrArg(Arg.SORTBYTREE,
368                   sv);
369           if (sortbytree)
370           {
371             af.getViewport().setSortByTree(true);
372             Jalview.testoutput(argParser, Arg.SORTBYTREE);
373           }
374
375           // Load tree from file
376           String treefile = avm.getValueFromSubValOrArg(av, Arg.TREE, sv);
377           if (treefile != null)
378           {
379             try
380             {
381               NewickFile nf = new NewickFile(treefile,
382                       AppletFormatAdapter.checkProtocol(treefile));
383               af.getViewport().setCurrentTree(
384                       af.showNewickTree(nf, treefile).getTree());
385               Jalview.testoutput(argParser, Arg.TREE,
386                       "examples/testdata/uniref50_test_tree", treefile);
387             } catch (IOException e)
388             {
389               addError("Couldn't add tree " + treefile, e);
390               isError = true;
391             }
392           }
393
394           // Show secondary structure annotations?
395           boolean showSSAnnotations = avm.getFromSubValArgOrPref(
396                   Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
397                   "STRUCT_FROM_PDB", true);
398
399           // Show sequence annotations?
400           boolean showAnnotations = avm.getFromSubValArgOrPref(
401                   Arg.SHOWANNOTATIONS, av.getSubVals(), null,
402                   "SHOW_ANNOTATIONS", true);
403
404           boolean hideTFrows = (avm.getBoolean(Arg.NOTEMPFAC));
405           final AlignFrame _af = af;
406           // many of jalview's format/layout methods are only thread safe on the
407           // swingworker thread.
408           // all these methods should be on the alignViewController so it can
409           // coordinate such details
410           try
411           {
412             SwingUtilities.invokeAndWait(new Runnable()
413             {
414
415               @Override
416               public void run()
417               {
418                 _af.setAnnotationsVisibility(showSSAnnotations, true,
419                         false);
420
421                 _af.setAnnotationsVisibility(showAnnotations, false, true);
422
423                 // show temperature factor annotations?
424                 if (hideTFrows)
425                 {
426                   // do this better (annotation types?)
427                   List<String> hideThese = new ArrayList<>();
428                   hideThese.add("Temperature Factor");
429                   hideThese.add(AlphaFoldAnnotationRowBuilder.LABEL);
430                   AlignmentUtils.showOrHideSequenceAnnotations(
431                           _af.getCurrentView().getAlignment(), hideThese,
432                           null, false, false);
433                 }
434               }
435             });
436           } catch (Exception x)
437           {
438             Console.warn(
439                     "Unexpected exception adjusting annotation row visibility.",
440                     x);
441           }
442
443           // wrap alignment? do this last for formatting reasons
444           wrap = avm.getFromSubValArgOrPref(Arg.WRAP, sv, null,
445                   "WRAP_ALIGNMENT", false);
446           // af.setWrapFormat(wrap) is applied after structures are opened for
447           // annotation reasons
448
449           // store the AlignFrame for this id
450           afMap.put(id, af);
451
452           // is it its own structure file?
453           if (format.isStructureFile())
454           {
455             StructureSelectionManager ssm = StructureSelectionManager
456                     .getStructureSelectionManager(Desktop.instance);
457             SequenceI seq = af.alignPanel.getAlignment().getSequenceAt(0);
458             ssm.computeMapping(false, new SequenceI[] { seq }, null,
459                     openFile, DataSourceType.FILE, null, null, null, false);
460           }
461         }
462         else
463         {
464           Console.debug(
465                   "Opening '" + openFile + "' in existing alignment frame");
466
467           DataSourceType dst = HttpUtils.startsWithHttpOrHttps(openFile)
468                   ? DataSourceType.URL
469                   : DataSourceType.FILE;
470
471           FileLoader fileLoader = new FileLoader(!headless);
472           fileLoader.LoadFile(af.getCurrentView(), openFile, dst, null,
473                   false);
474         }
475
476         Console.debug("Command " + Arg.APPEND + " executed successfully!");
477
478       }
479       if (first) // first=true means nothing opened
480       {
481         if (headless)
482         {
483           Jalview.exit("Could not open any files in headless mode",
484                   ExitCode.NO_FILES);
485         }
486         else
487         {
488           Console.info("No more files to open");
489         }
490       }
491       if (progressBarSet && desktop != null)
492         desktop.setProgressBar(null, progress);
493
494     }
495
496     // open the structure (from same PDB file or given PDBfile)
497     if (!avm.getBoolean(Arg.NOSTRUCTURE))
498     {
499
500       AlignFrame af = afMap.get(id);
501       if (avm.containsArg(Arg.STRUCTURE))
502       {
503         commandArgsProvided = true;
504         for (
505
506         ArgValue av : avm.getArgValueList(Arg.STRUCTURE))
507         {
508           argParser.setStructureFilename(null);
509           String val = av.getValue();
510           SubVals subVals = av.getSubVals();
511           int argIndex = av.getArgIndex();
512           SequenceI seq = getSpecifiedSequence(af, avm, av);
513           if (seq == null)
514           {
515             // Could not find sequence from subId, let's assume the first
516             // sequence in the alignframe
517             AlignmentI al = af.getCurrentView().getAlignment();
518             seq = al.getSequenceAt(0);
519           }
520
521           if (seq == null)
522           {
523             addWarn("Could not find sequence for argument "
524                     + Arg.STRUCTURE.argString() + "=" + val);
525             continue;
526           }
527           String structureFilename = null;
528           File structureFile = null;
529           if (subVals.getContent() != null
530                   && subVals.getContent().length() != 0)
531           {
532             structureFilename = subVals.getContent();
533             Console.debug("Using structure file (from argument) '"
534                     + structureFilename + "'");
535             structureFile = new File(structureFilename);
536           }
537           /* THIS DOESN'T WORK */
538           else if (seq.getAllPDBEntries() != null
539                   && seq.getAllPDBEntries().size() > 0)
540           {
541             structureFile = new File(
542                     seq.getAllPDBEntries().elementAt(0).getFile());
543             if (structureFile != null)
544             {
545               Console.debug("Using structure file (from sequence) '"
546                       + structureFile.getAbsolutePath() + "'");
547             }
548             structureFilename = structureFile.getAbsolutePath();
549           }
550
551           if (structureFilename == null || structureFile == null)
552           {
553             addWarn("Not provided structure file with '" + val + "'");
554             continue;
555           }
556
557           if (!structureFile.exists())
558           {
559             addWarn("Structure file '" + structureFile.getAbsoluteFile()
560                     + "' not found.");
561             continue;
562           }
563
564           Console.debug("Using structure file "
565                   + structureFile.getAbsolutePath());
566
567           argParser.setStructureFilename(structureFilename);
568
569           // open structure view
570           AlignmentPanel ap = af.alignPanel;
571           if (headless)
572           {
573             Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
574                     StructureViewer.ViewerType.JMOL.toString());
575           }
576
577           String structureFilepath = structureFile.getAbsolutePath();
578
579           // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
580           String paeFilepath = avm.getFromSubValArgOrPrefWithSubstitutions(
581                   argParser, Arg.PAEMATRIX, ArgValuesMap.Position.AFTER, av,
582                   subVals, null, null, null);
583           if (paeFilepath != null)
584           {
585             File paeFile = new File(paeFilepath);
586
587             try
588             {
589               paeFilepath = paeFile.getCanonicalPath();
590             } catch (IOException e)
591             {
592               paeFilepath = paeFile.getAbsolutePath();
593               addWarn("Problem with the PAE file path: '"
594                       + paeFile.getPath() + "'");
595             }
596           }
597
598           // showing annotations from structure file or not
599           boolean ssFromStructure = avm.getFromSubValArgOrPref(
600                   Arg.SHOWSSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
601                   true);
602
603           // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
604           // reference annotations
605           String tftString = avm.getFromSubValArgOrPrefWithSubstitutions(
606                   argParser, Arg.TEMPFAC, ArgValuesMap.Position.AFTER, av,
607                   subVals, null, null, null);
608           boolean notempfac = avm.getFromSubValArgOrPref(Arg.NOTEMPFAC,
609                   subVals, null, "ADD_TEMPFACT_ANN", false, true);
610           TFType tft = notempfac ? null : TFType.DEFAULT;
611           if (tftString != null && !notempfac)
612           {
613             // get kind of temperature factor annotation
614             try
615             {
616               tft = TFType.valueOf(tftString.toUpperCase(Locale.ROOT));
617               Console.debug("Obtained Temperature Factor type of '" + tft
618                       + "' for structure '" + structureFilepath + "'");
619             } catch (IllegalArgumentException e)
620             {
621               // Just an error message!
622               StringBuilder sb = new StringBuilder().append("Cannot set ")
623                       .append(Arg.TEMPFAC.argString()).append(" to '")
624                       .append(tft)
625                       .append("', ignoring.  Valid values are: ");
626               Iterator<TFType> it = Arrays.stream(TFType.values())
627                       .iterator();
628               while (it.hasNext())
629               {
630                 sb.append(it.next().toString().toLowerCase(Locale.ROOT));
631                 if (it.hasNext())
632                   sb.append(", ");
633               }
634               addWarn(sb.toString());
635             }
636           }
637
638           String sViewerName = avm.getFromSubValArgOrPref(
639                   Arg.STRUCTUREVIEWER, ArgValuesMap.Position.AFTER, av,
640                   subVals, null, null, "jmol");
641           ViewerType viewerType = ViewerType.getFromString(sViewerName);
642
643           // TODO use ssFromStructure
644           StructureViewer structureViewer = StructureChooser
645                   .openStructureFileForSequence(null, null, ap, seq, false,
646                           structureFilepath, tft, paeFilepath, false,
647                           ssFromStructure, false, viewerType);
648
649           if (structureViewer == null)
650           {
651             if (!StringUtils.equalsIgnoreCase(sViewerName, "none"))
652             {
653               addError("Failed to import and open structure view for file '"
654                       + structureFile + "'.");
655             }
656             continue;
657           }
658           try
659           {
660             long tries = 1000;
661             while (structureViewer.isBusy() && tries > 0)
662             {
663               Thread.sleep(25);
664               if (structureViewer.isBusy())
665               {
666                 tries--;
667                 Console.debug(
668                         "Waiting for viewer for " + structureFilepath);
669               }
670             }
671             if (tries == 0 && structureViewer.isBusy())
672             {
673               addWarn("Gave up waiting for structure viewer to load file '"
674                       + structureFile
675                       + "'. Something may have gone wrong.");
676             }
677           } catch (Exception x)
678           {
679             addError("Exception whilst waiting for structure viewer "
680                     + structureFilepath, x);
681             isError = true;
682           }
683
684           // add StructureViewer to svMap list
685           if (svMap == null)
686           {
687             svMap = new HashMap<>();
688           }
689           if (svMap.get(id) == null)
690           {
691             svMap.put(id, new ArrayList<>());
692           }
693           svMap.get(id).add(structureViewer);
694
695           Console.debug(
696                   "Successfully opened viewer for " + structureFilepath);
697
698           if (avm.containsArg(Arg.STRUCTUREIMAGE))
699           {
700             for (ArgValue structureImageArgValue : avm
701                     .getArgValueList(Arg.STRUCTUREIMAGE))
702             {
703               String structureImageFilename = argParser.makeSubstitutions(
704                       structureImageArgValue.getValue(), id, true);
705               if (structureViewer != null && structureImageFilename != null)
706               {
707                 SubVals structureImageSubVals = null;
708                 structureImageSubVals = structureImageArgValue.getSubVals();
709                 File structureImageFile = new File(structureImageFilename);
710                 String width = avm.getValueFromSubValOrArg(
711                         structureImageArgValue, Arg.WIDTH,
712                         structureImageSubVals);
713                 String height = avm.getValueFromSubValOrArg(
714                         structureImageArgValue, Arg.HEIGHT,
715                         structureImageSubVals);
716                 String scale = avm.getValueFromSubValOrArg(
717                         structureImageArgValue, Arg.SCALE,
718                         structureImageSubVals);
719                 String renderer = avm.getValueFromSubValOrArg(
720                         structureImageArgValue, Arg.TEXTRENDERER,
721                         structureImageSubVals);
722                 String typeS = avm.getValueFromSubValOrArg(
723                         structureImageArgValue, Arg.TYPE,
724                         structureImageSubVals);
725                 if (typeS == null || typeS.length() == 0)
726                 {
727                   typeS = FileUtils.getExtension(structureImageFile);
728                 }
729                 TYPE imageType;
730                 try
731                 {
732                   imageType = Enum.valueOf(TYPE.class,
733                           typeS.toUpperCase(Locale.ROOT));
734                 } catch (IllegalArgumentException e)
735                 {
736                   addWarn("Do not know image format '" + typeS
737                           + "', using PNG");
738                   imageType = TYPE.PNG;
739                 }
740                 BitmapImageSizing userBis = ImageMaker
741                         .parseScaleWidthHeightStrings(scale, width, height);
742
743                 /////
744                 // DON'T TRY TO EXPORT IF VIEWER IS UNSUPPORTED
745                 if (viewerType != ViewerType.JMOL)
746                 {
747                   addWarn("Cannot export image for structure viewer "
748                           + viewerType.name() + " yet");
749                   continue;
750                 }
751
752                 /////
753                 // Apply the temporary colourscheme to the linked alignment
754                 // TODO: enhance for multiple linked alignments.
755
756                 String imageColour = avm.getValueFromSubValOrArg(
757                         structureImageArgValue, Arg.IMAGECOLOUR,
758                         structureImageSubVals);
759                 ColourSchemeI originalColourScheme = this
760                         .getColourScheme(af);
761                 this.colourAlignFrame(af, imageColour);
762
763                 /////
764                 // custom image background colour
765
766                 String bgcolourstring = avm.getValueFromSubValOrArg(
767                         structureImageArgValue, Arg.BGCOLOUR,
768                         structureImageSubVals);
769                 Color bgcolour = null;
770                 if (bgcolourstring != null && bgcolourstring.length() > 0)
771                 {
772                   bgcolour = ColorUtils.parseColourString(bgcolourstring);
773                   if (bgcolour == null)
774                   {
775                     Console.warn(
776                             "Background colour string '" + bgcolourstring
777                                     + "' not recognised -- using default");
778                   }
779                 }
780
781                 JalviewStructureDisplayI sview = structureViewer
782                         .getJalviewStructureDisplay();
783
784                 File sessionToRestore = null;
785
786                 List<StructureCommandI> extraCommands = new ArrayList<>();
787
788                 if (extraCommands.size() > 0 || bgcolour != null)
789                 {
790                   try
791                   {
792                     sessionToRestore = sview.saveSession();
793                   } catch (Throwable t)
794                   {
795                     Console.warn(
796                             "Unable to save temporary session file before custom structure view export operation.");
797                   }
798                 }
799
800                 ////
801                 // Do temporary ops
802
803                 if (bgcolour != null)
804                 {
805                   sview.getBinding().setBackgroundColour(bgcolour);
806                 }
807
808                 sview.getBinding().executeCommands(extraCommands, false,
809                         "Executing Custom Commands");
810
811                 // and export the view as an image
812                 boolean success = this.checksBeforeWritingToFile(avm,
813                         subVals, false, structureImageFilename,
814                         "structure image", isError);
815
816                 if (!success)
817                 {
818                   continue;
819                 }
820                 Console.debug("Rendering image to " + structureImageFile);
821                 //
822                 // TODO - extend StructureViewer / Binding with makePDBImage so
823                 // we can do this with every viewer
824                 //
825
826                 try
827                 {
828                   // We don't expect class cast exception
829                   AppJmol jmol = (AppJmol) sview;
830                   jmol.makePDBImage(structureImageFile, imageType, renderer,
831                           userBis);
832                   Console.info("Exported structure image to "
833                           + structureImageFile);
834
835                   // RESTORE SESSION AFTER EXPORT IF NEED BE
836                   if (sessionToRestore != null)
837                   {
838                     Console.debug(
839                             "Restoring session from " + sessionToRestore);
840
841                     sview.getBinding().restoreSession(
842                             sessionToRestore.getAbsolutePath());
843
844                   }
845                 } catch (ImageOutputException ioexec)
846                 {
847                   addError(
848                           "Unexpected error when restoring structure viewer session after custom view operations.");
849                   isError = true;
850                   continue;
851                 } finally
852                 {
853                   try
854                   {
855                     this.colourAlignFrame(af, originalColourScheme);
856                   } catch (Exception t)
857                   {
858                     addError(
859                             "Unexpected error when restoring colourscheme to alignment after temporary change for export.",
860                             t);
861                   }
862                 }
863               }
864             }
865           }
866           argParser.setStructureFilename(null);
867         }
868       }
869     }
870
871     if (wrap)
872     {
873
874       AlignFrame af = afMap.get(id);
875       if (af != null)
876       {
877         af.setWrapFormat(wrap, true);
878       }
879     }
880
881     /*
882     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
883     if (doShading)
884     {
885     AlignFrame af = afMap.get(id);
886     for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
887             .findAnnotation(PDBChain.class.getName().toString()))
888     {
889       AnnotationColourGradient acg = new AnnotationColourGradient(aa,
890               af.alignPanel.av.getGlobalColourScheme(), 0);
891       acg.setSeqAssociated(true);
892       af.changeColour(acg);
893       Console.info("Changed colour " + acg.toString());
894     }
895     }
896     */
897
898     return theseArgsWereParsed && !isError;
899   }
900
901   protected void processGroovyScript(String id)
902   {
903     ArgValuesMap avm = argParser.getLinkedArgs(id);
904     AlignFrame af = afMap.get(id);
905
906     if (avm != null && !avm.containsArg(Arg.GROOVY))
907     {
908       // nothing to do
909       return;
910     }
911
912     if (af == null)
913     {
914       addWarn("Groovy script does not have an alignment window.  Proceeding with caution!");
915     }
916
917     if (avm.containsArg(Arg.GROOVY))
918     {
919       for (ArgValue groovyAv : avm.getArgValueList(Arg.GROOVY))
920       {
921         String groovyscript = groovyAv.getValue();
922         if (groovyscript != null)
923         {
924           // Execute the groovy script after we've done all the rendering stuff
925           // and before any images or figures are generated.
926           Console.info("Executing script " + groovyscript);
927           Jalview.getInstance().executeGroovyScript(groovyscript, af);
928         }
929       }
930     }
931   }
932
933   protected boolean processImages(String id)
934   {
935     ArgValuesMap avm = argParser.getLinkedArgs(id);
936     AlignFrame af = afMap.get(id);
937
938     if (avm != null && !avm.containsArg(Arg.IMAGE))
939     {
940       // nothing to do
941       return true;
942     }
943
944     if (af == null)
945     {
946       addWarn("Do not have an alignment window to create image from (id="
947               + id + ").  Not proceeding.");
948       return false;
949     }
950
951     Boolean isError = Boolean.valueOf(false);
952     if (avm.containsArg(Arg.IMAGE))
953     {
954       for (ArgValue imageAv : avm.getArgValueList(Arg.IMAGE))
955       {
956         String val = imageAv.getValue();
957         SubVals imageSubVals = imageAv.getSubVals();
958         String fileName = imageSubVals.getContent();
959         File file = new File(fileName);
960         String name = af.getName();
961         String renderer = avm.getValueFromSubValOrArg(imageAv,
962                 Arg.TEXTRENDERER, imageSubVals);
963         if (renderer == null)
964           renderer = "text";
965         String type = "png"; // default
966
967         String scale = avm.getValueFromSubValOrArg(imageAv, Arg.SCALE,
968                 imageSubVals);
969         String width = avm.getValueFromSubValOrArg(imageAv, Arg.WIDTH,
970                 imageSubVals);
971         String height = avm.getValueFromSubValOrArg(imageAv, Arg.HEIGHT,
972                 imageSubVals);
973         BitmapImageSizing userBis = ImageMaker
974                 .parseScaleWidthHeightStrings(scale, width, height);
975
976         type = avm.getValueFromSubValOrArg(imageAv, Arg.TYPE, imageSubVals);
977         if (type == null && fileName != null)
978         {
979           for (String ext : new String[] { "svg", "png", "html", "eps" })
980           {
981             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
982             {
983               type = ext;
984             }
985           }
986         }
987         // for moment we disable JSON export
988         Cache.setPropsAreReadOnly(true);
989         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
990
991         String imageColour = avm.getValueFromSubValOrArg(imageAv,
992                 Arg.IMAGECOLOUR, imageSubVals);
993         ColourSchemeI originalColourScheme = this.getColourScheme(af);
994         this.colourAlignFrame(af, imageColour);
995
996         Console.info("Writing " + file);
997
998         boolean success = checksBeforeWritingToFile(avm, imageSubVals,
999                 false, fileName, "image", isError);
1000         if (!success)
1001         {
1002           continue;
1003         }
1004
1005         try
1006         {
1007           switch (type)
1008           {
1009
1010           case "svg":
1011             Console.debug("Outputting type '" + type + "' to " + fileName);
1012             af.createSVG(file, renderer);
1013             break;
1014
1015           case "png":
1016             Console.debug("Outputting type '" + type + "' to " + fileName);
1017             af.createPNG(file, null, userBis);
1018             break;
1019
1020           case "html":
1021             Console.debug("Outputting type '" + type + "' to " + fileName);
1022             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
1023             htmlSVG.exportHTML(fileName, renderer);
1024             break;
1025
1026           case "biojs":
1027             Console.debug(
1028                     "Outputting BioJS MSA Viwer HTML file: " + fileName);
1029             try
1030             {
1031               BioJsHTMLOutput.refreshVersionInfo(
1032                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
1033             } catch (URISyntaxException e)
1034             {
1035               e.printStackTrace();
1036             }
1037             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
1038             bjs.exportHTML(fileName);
1039             break;
1040
1041           case "eps":
1042             Console.debug("Outputting EPS file: " + fileName);
1043             af.createEPS(file, renderer);
1044             break;
1045
1046           case "imagemap":
1047             Console.debug("Outputting ImageMap file: " + fileName);
1048             af.createImageMap(file, name);
1049             break;
1050
1051           default:
1052             addWarn(Arg.IMAGE.argString() + " type '" + type
1053                     + "' not known. Ignoring");
1054             break;
1055           }
1056         } catch (Exception ioex)
1057         {
1058           addError("Unexpected error during export to '" + fileName + "'",
1059                   ioex);
1060           isError = true;
1061         }
1062
1063         this.colourAlignFrame(af, originalColourScheme);
1064       }
1065     }
1066     return !isError;
1067   }
1068
1069   protected boolean processOutput(String id)
1070   {
1071     ArgValuesMap avm = argParser.getLinkedArgs(id);
1072     AlignFrame af = afMap.get(id);
1073
1074     if (avm != null && !avm.containsArg(Arg.OUTPUT))
1075     {
1076       // nothing to do
1077       return true;
1078     }
1079
1080     if (af == null)
1081     {
1082       addWarn("Do not have an alignment window (id=" + id
1083               + ").  Not proceeding.");
1084       return false;
1085     }
1086
1087     Boolean isError = Boolean.valueOf(false);
1088
1089     if (avm.containsArg(Arg.OUTPUT))
1090     {
1091       for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
1092       {
1093         String val = av.getValue();
1094         SubVals subVals = av.getSubVals();
1095         String fileName = subVals.getContent();
1096         boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
1097         File file = new File(fileName);
1098
1099         String name = af.getName();
1100         String format = avm.getValueFromSubValOrArg(av, Arg.FORMAT,
1101                 subVals);
1102         FileFormats ffs = FileFormats.getInstance();
1103         List<String> validFormats = ffs.getWritableFormats(false);
1104
1105         FileFormatI ff = null;
1106         if (format == null && fileName != null)
1107         {
1108           FORMAT: for (String fname : validFormats)
1109           {
1110             FileFormatI tff = ffs.forName(fname);
1111             String[] extensions = tff.getExtensions().split(",");
1112             for (String ext : extensions)
1113             {
1114               if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
1115               {
1116                 ff = tff;
1117                 format = ff.getName();
1118                 break FORMAT;
1119               }
1120             }
1121           }
1122         }
1123         if (ff == null && format != null)
1124         {
1125           ff = ffs.forName(format);
1126         }
1127         if (ff == null)
1128         {
1129           if (stdout)
1130           {
1131             ff = FileFormat.Fasta;
1132           }
1133           else
1134           {
1135             StringBuilder validSB = new StringBuilder();
1136             for (String f : validFormats)
1137             {
1138               if (validSB.length() > 0)
1139                 validSB.append(", ");
1140               validSB.append(f);
1141               FileFormatI tff = ffs.forName(f);
1142               validSB.append(" (");
1143               validSB.append(tff.getExtensions());
1144               validSB.append(")");
1145             }
1146
1147             addError("No valid format specified for "
1148                     + Arg.OUTPUT.argString() + ". Valid formats are "
1149                     + validSB.toString() + ".");
1150             continue;
1151           }
1152         }
1153
1154         boolean success = checksBeforeWritingToFile(avm, subVals, true,
1155                 fileName, ff.getName(), isError);
1156         if (!success)
1157         {
1158           continue;
1159         }
1160
1161         boolean backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVals,
1162                 null, Platform.isHeadless() ? null : BackupFiles.ENABLED,
1163                 !Platform.isHeadless());
1164
1165         Console.info("Writing " + fileName);
1166
1167         af.saveAlignment(fileName, ff, stdout, backups);
1168         if (af.isSaveAlignmentSuccessful())
1169         {
1170           Console.debug("Written alignment '" + name + "' in "
1171                   + ff.getName() + " format to '" + file + "'");
1172         }
1173         else
1174         {
1175           addError("Error writing file '" + file + "' in " + ff.getName()
1176                   + " format!");
1177           isError = true;
1178           continue;
1179         }
1180
1181       }
1182     }
1183     return !isError;
1184   }
1185
1186   private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
1187           ArgValue av)
1188   {
1189     SubVals subVals = av.getSubVals();
1190     ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID, true);
1191     SequenceI seq = null;
1192     if (subVals == null && idAv == null)
1193       return null;
1194     if (af == null || af.getCurrentView() == null)
1195     {
1196       return null;
1197     }
1198     AlignmentI al = af.getCurrentView().getAlignment();
1199     if (al == null)
1200     {
1201       return null;
1202     }
1203     if (subVals != null)
1204     {
1205       if (subVals.has(Arg.SEQID.getName()))
1206       {
1207         seq = al.findName(subVals.get(Arg.SEQID.getName()));
1208       }
1209       else if (-1 < subVals.getIndex()
1210               && subVals.getIndex() < al.getSequences().size())
1211       {
1212         seq = al.getSequenceAt(subVals.getIndex());
1213       }
1214     }
1215     if (seq == null && idAv != null)
1216     {
1217       seq = al.findName(idAv.getValue());
1218     }
1219     return seq;
1220   }
1221
1222   public AlignFrame[] getAlignFrames()
1223   {
1224     AlignFrame[] afs = null;
1225     if (afMap != null)
1226     {
1227       afs = (AlignFrame[]) afMap.values().toArray();
1228     }
1229
1230     return afs;
1231   }
1232
1233   public List<StructureViewer> getStructureViewers()
1234   {
1235     List<StructureViewer> svs = null;
1236     if (svMap != null)
1237     {
1238       for (List<StructureViewer> svList : svMap.values())
1239       {
1240         if (svs == null)
1241         {
1242           svs = new ArrayList<>();
1243         }
1244         svs.addAll(svList);
1245       }
1246     }
1247     return svs;
1248   }
1249
1250   private void colourAlignFrame(AlignFrame af, String colour)
1251   {
1252     // use string "none" to remove colour scheme
1253     if (colour != null && "" != colour)
1254     {
1255       ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
1256               af.getViewport(), af.getViewport().getAlignment(), colour);
1257       if (cs == null && !StringUtils.equalsIgnoreCase(colour, "none"))
1258       {
1259         addWarn("Couldn't parse '" + colour + "' as a colourscheme.");
1260       }
1261       else
1262       {
1263         Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
1264         colourAlignFrame(af, cs);
1265       }
1266     }
1267   }
1268
1269   private void colourAlignFrame(AlignFrame af, ColourSchemeI cs)
1270   {
1271     // Note that cs == null removes colour scheme from af
1272     af.changeColour(cs);
1273   }
1274
1275   private ColourSchemeI getColourScheme(AlignFrame af)
1276   {
1277     return af.getViewport().getGlobalColourScheme();
1278   }
1279
1280   private void addInfo(String errorMessage)
1281   {
1282     Console.info(errorMessage);
1283     errors.add(errorMessage);
1284   }
1285
1286   private void addWarn(String errorMessage)
1287   {
1288     Console.warn(errorMessage);
1289     errors.add(errorMessage);
1290   }
1291
1292   private void addError(String errorMessage)
1293   {
1294     addError(errorMessage, null);
1295   }
1296
1297   private void addError(String errorMessage, Exception e)
1298   {
1299     Console.error(errorMessage, e);
1300     errors.add(errorMessage);
1301   }
1302
1303   private boolean checksBeforeWritingToFile(ArgValuesMap avm,
1304           SubVals subVal, boolean includeBackups, String filename,
1305           String adjective, Boolean isError)
1306   {
1307     File file = new File(filename);
1308
1309     boolean overwrite = avm.getFromSubValArgOrPref(Arg.OVERWRITE, subVal,
1310             null, "OVERWRITE_OUTPUT", false);
1311     boolean stdout = false;
1312     boolean backups = false;
1313     if (includeBackups)
1314     {
1315       stdout = ArgParser.STDOUTFILENAME.equals(filename);
1316       // backups. Use the Arg.BACKUPS or subval "backups" setting first,
1317       // otherwise if headless assume false, if not headless use the user
1318       // preference with default true.
1319       backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVal, null,
1320               Platform.isHeadless() ? null : BackupFiles.ENABLED,
1321               !Platform.isHeadless());
1322     }
1323
1324     if (file.exists() && !(overwrite || backups || stdout))
1325     {
1326       addWarn("Won't overwrite file '" + filename + "' without "
1327               + Arg.OVERWRITE.argString()
1328               + (includeBackups ? " or " + Arg.BACKUPS.argString() : "")
1329               + " set");
1330       return false;
1331     }
1332
1333     boolean mkdirs = avm.getFromSubValArgOrPref(Arg.MKDIRS, subVal, null,
1334             "MKDIRS_OUTPUT", false);
1335
1336     if (!FileUtils.checkParentDir(file, mkdirs))
1337     {
1338       addError("Directory '"
1339               + FileUtils.getParentDir(file).getAbsolutePath()
1340               + "' does not exist for " + adjective + " file '" + filename
1341               + "'."
1342               + (mkdirs ? "" : "  Try using " + Arg.MKDIRS.argString()));
1343       isError = true;
1344       return false;
1345     }
1346
1347     return true;
1348   }
1349
1350   public List<String> getErrors()
1351   {
1352     return errors;
1353   }
1354
1355   public String errorsToString()
1356   {
1357     StringBuilder sb = new StringBuilder();
1358     for (String error : errors)
1359     {
1360       if (sb.length() > 0)
1361         sb.append("\n");
1362       sb.append("- " + error);
1363     }
1364     return sb.toString();
1365   }
1366 }