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