JAL-4265 JAL-4286 reinstated explicit restoreSession method because Jmol’s LOAD files...
[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.lang.reflect.Field;
7 import java.net.URISyntaxException;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Locale;
15 import java.util.Map;
16
17 import jalview.analysis.AlignmentUtils;
18 import jalview.api.structures.JalviewStructureDisplayI;
19 import jalview.bin.Jalview.ExitCode;
20 import jalview.bin.argparser.Arg;
21 import jalview.bin.argparser.ArgParser;
22 import jalview.bin.argparser.ArgValue;
23 import jalview.bin.argparser.ArgValuesMap;
24 import jalview.bin.argparser.SubVals;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.annotations.AlphaFoldAnnotationRowBuilder;
28 import jalview.ext.jmol.JalviewJmolBinding;
29 import jalview.ext.jmol.JmolCommands;
30 import jalview.gui.AlignFrame;
31 import jalview.gui.AlignmentPanel;
32 import jalview.gui.AppJmol;
33 import jalview.gui.Desktop;
34 import jalview.gui.Preferences;
35 import jalview.gui.StructureChooser;
36 import jalview.gui.StructureViewer;
37 import jalview.gui.StructureViewer.ViewerType;
38 import jalview.io.AppletFormatAdapter;
39 import jalview.io.BackupFiles;
40 import jalview.io.BioJsHTMLOutput;
41 import jalview.io.DataSourceType;
42 import jalview.io.FileFormat;
43 import jalview.io.FileFormatException;
44 import jalview.io.FileFormatI;
45 import jalview.io.FileFormats;
46 import jalview.io.FileLoader;
47 import jalview.io.HtmlSvgOutput;
48 import jalview.io.IdentifyFile;
49 import jalview.io.NewickFile;
50 import jalview.io.exceptions.ImageOutputException;
51 import jalview.schemes.ColourSchemeI;
52 import jalview.schemes.ColourSchemeProperty;
53 import jalview.structure.StructureCommandI;
54 import jalview.structure.StructureCommandsI;
55 import jalview.structure.StructureImportSettings.TFType;
56 import jalview.structure.StructureSelectionManager;
57 import jalview.util.ColorUtils;
58 import jalview.util.FileUtils;
59 import jalview.util.HttpUtils;
60 import jalview.util.ImageMaker;
61 import jalview.util.ImageMaker.TYPE;
62 import jalview.util.MessageManager;
63 import jalview.util.Platform;
64 import jalview.util.StringUtils;
65 import jalview.util.imagemaker.BitmapImageSizing;
66
67 public class Commands
68 {
69   Desktop desktop;
70
71   private boolean headless;
72
73   private ArgParser argParser;
74
75   private Map<String, AlignFrame> afMap;
76
77   private Map<String, List<StructureViewer>> svMap;
78
79   private boolean commandArgsProvided = false;
80
81   private boolean argsWereParsed = false;
82
83   private List<String> errors = new ArrayList<>();
84
85   public Commands(ArgParser argparser, boolean headless)
86   {
87     this(Desktop.instance, argparser, headless);
88   }
89
90   public Commands(Desktop d, ArgParser argparser, boolean h)
91   {
92     argParser = argparser;
93     headless = h;
94     desktop = d;
95     afMap = new HashMap<>();
96   }
97
98   protected boolean processArgs()
99   {
100     if (argParser == null)
101     {
102       return true;
103     }
104
105     boolean theseArgsWereParsed = false;
106
107     if (argParser != null && argParser.getLinkedIds() != null)
108     {
109       for (String id : argParser.getLinkedIds())
110       {
111         ArgValuesMap avm = argParser.getLinkedArgs(id);
112         theseArgsWereParsed = true;
113         boolean processLinkedOkay = processLinked(id);
114         theseArgsWereParsed &= processLinkedOkay;
115
116         processGroovyScript(id);
117
118         // wait around until alignFrame isn't busy
119         AlignFrame af = afMap.get(id);
120         while (af != null && af.getViewport().isCalcInProgress())
121         {
122           try
123           {
124             Thread.sleep(25);
125           } catch (Exception q)
126           {
127           }
128           ;
129         }
130
131         theseArgsWereParsed &= processImages(id);
132
133         if (processLinkedOkay)
134         {
135           theseArgsWereParsed &= processOutput(id);
136         }
137
138         // close ap
139         if (avm.getBoolean(Arg.CLOSE))
140         {
141           af = afMap.get(id);
142           if (af != null)
143           {
144             af.closeMenuItem_actionPerformed(true);
145           }
146         }
147
148       }
149
150     }
151
152     // report errors
153     Console.warn(
154             "The following errors and warnings occurred whilst processing files:\n"
155                     + errorsToString());
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                   addWarn("Cannot export image for structure viewer "
687                           + viewerType.name() + " yet");
688                   continue;
689                 }
690
691                 /////
692                 // Apply the temporary colourscheme to the linked alignment
693                 // TODO: enhance for multiple linked alignments.
694                 
695                 String imageColour = avm.getValueFromSubValOrArg(
696                         structureImageArgValue, Arg.IMAGECOLOUR,
697                         structureImageSubVals);
698                 ColourSchemeI originalColourScheme = this
699                         .getColourScheme(af);
700                 this.colourAlignFrame(af, imageColour);
701
702                 /////
703                 // custom image background colour
704
705                 String bgcolourstring = avm.getValueFromSubValOrArg(
706                         structureImageArgValue, Arg.BGCOLOUR,
707                         structureImageSubVals);
708                 Color bgcolour = null;
709                 if (bgcolourstring != null && bgcolourstring.length() > 0)
710                 {
711                   try
712                   {
713                     // 
714                     // FIXME: Why not use ColorUtils.parseColourString(bgcolourstring) - this is consistent and backwards compatible
715                     //
716                     if (bgcolourstring.charAt(0) == '#')
717                     {
718                       bgcolour = Color.decode(bgcolourstring);
719                     }
720                     else
721                     {
722                       Field field = Color.class.getField(bgcolourstring);
723                       bgcolour = (Color) field.get(null);
724                     }
725                   } catch (IllegalArgumentException | NoSuchFieldException
726                           | SecurityException | IllegalAccessException nfe)
727                   {
728                     Console.warn(
729                             "Background colour string '" + bgcolourstring
730                                     + "' not recognised -- using default");
731                     //bgcolour = Color.black;
732                   }
733                 }
734
735                 JalviewStructureDisplayI sview = structureViewer
736                         .getJalviewStructureDisplay();
737
738                 File sessionToRestore = null;
739                 
740                 List<StructureCommandI> extraCommands=new ArrayList<>();
741                 
742                 if (extraCommands.size() > 0 || bgcolour!=null)
743                 {
744                   try {
745                     sessionToRestore = sview.saveSession();
746                   } catch (Throwable t)
747                   {
748                     Console.warn("Unable to save temporary session file before custom structure view export operation.");
749                   }
750                 }
751                 
752                 ////
753                 // Do temporary ops 
754
755                 sview.getBinding().setBackgroundColour(bgcolour);
756                 
757                 sview.getBinding().executeCommands(extraCommands, false, "Executing Custom Commands");
758                 
759                 // and export the view as an image
760                 boolean success = this.checksBeforeWritingToFile(avm,
761                         subVals, false, structureImageFilename,
762                         "structure image", isError);
763                 
764                 if (!success)
765                 {
766                   continue;
767                 }
768                 Console.debug(
769                         "Rendering image to " + structureImageFile);
770                 // 
771                 // TODO - extend StructureViewer / Binding with makePDBImage so we can do this with every viewer
772                 // 
773                 
774                 try {
775                   // We don't expect class cast exception
776                   AppJmol jmol = (AppJmol) sview;
777                   jmol.makePDBImage(structureImageFile, imageType,
778                               renderer, userBis);
779                   Console.debug("Finished Rendering image to "
780                           + structureImageFile);
781     
782                   // RESTORE SESSION AFTER EXPORT IF NEED BE
783                   if (sessionToRestore != null)
784                   {
785                     Console.debug("Restoring session from "
786                             + sessionToRestore);
787                     
788                     sview.getBinding().restoreSession(sessionToRestore.getAbsolutePath());
789
790                   }
791                 } catch (ImageOutputException ioexc)
792                 {
793                   addError("Unexpected error whilst exporting image to "
794                           + structureImageFile, ioexc);
795                   isError = true;
796                   continue;
797                 }
798                 finally
799                 {
800                   try {
801                     this.colourAlignFrame(af, originalColourScheme);
802                   } catch (Exception t)
803                   {
804                     addError("Unexpected error when restoring colourscheme to alignment after temporary change for export.",t);
805                   }
806                 }
807               }
808             }
809           }
810           argParser.setStructureFilename(null);
811         }
812       }
813     }
814
815     if (wrap)
816     {
817       AlignFrame af = afMap.get(id);
818       if (af != null)
819       {
820         af.setWrapFormat(wrap, true);
821       }
822     }
823
824     /*
825     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
826     if (doShading)
827     {
828       AlignFrame af = afMap.get(id);
829       for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
830               .findAnnotation(PDBChain.class.getName().toString()))
831       {
832         AnnotationColourGradient acg = new AnnotationColourGradient(aa,
833                 af.alignPanel.av.getGlobalColourScheme(), 0);
834         acg.setSeqAssociated(true);
835         af.changeColour(acg);
836         Console.info("Changed colour " + acg.toString());
837       }
838     }
839     */
840
841     return theseArgsWereParsed && !isError;
842   }
843
844   protected void processGroovyScript(String id)
845   {
846     ArgValuesMap avm = argParser.getLinkedArgs(id);
847     AlignFrame af = afMap.get(id);
848
849     if (af == null)
850     {
851       addWarn("Did not have an alignment window for id=" + id);
852       return;
853     }
854
855     if (avm.containsArg(Arg.GROOVY))
856     {
857       String groovyscript = avm.getValue(Arg.GROOVY);
858       if (groovyscript != null)
859       {
860         // Execute the groovy script after we've done all the rendering stuff
861         // and before any images or figures are generated.
862         Console.info("Executing script " + groovyscript);
863         Jalview.getInstance().executeGroovyScript(groovyscript, af);
864       }
865     }
866   }
867
868   protected boolean processImages(String id)
869   {
870     ArgValuesMap avm = argParser.getLinkedArgs(id);
871     AlignFrame af = afMap.get(id);
872
873     if (af == null)
874     {
875       addWarn("Did not have an alignment window for id=" + id);
876       return false;
877     }
878
879     Boolean isError = Boolean.valueOf(false);
880     if (avm.containsArg(Arg.IMAGE))
881     {
882       for (ArgValue imageAv : avm.getArgValueList(Arg.IMAGE))
883       {
884         String val = imageAv.getValue();
885         SubVals imageSubVals = imageAv.getSubVals();
886         String fileName = imageSubVals.getContent();
887         File file = new File(fileName);
888         String name = af.getName();
889         String renderer = avm.getValueFromSubValOrArg(imageAv,
890                 Arg.TEXTRENDERER, imageSubVals);
891         if (renderer == null)
892           renderer = "text";
893         String type = "png"; // default
894
895         String scale = avm.getValueFromSubValOrArg(imageAv, Arg.SCALE,
896                 imageSubVals);
897         String width = avm.getValueFromSubValOrArg(imageAv, Arg.WIDTH,
898                 imageSubVals);
899         String height = avm.getValueFromSubValOrArg(imageAv, Arg.HEIGHT,
900                 imageSubVals);
901         BitmapImageSizing userBis = ImageMaker
902                 .parseScaleWidthHeightStrings(scale, width, height);
903
904         type = avm.getValueFromSubValOrArg(imageAv, Arg.TYPE, imageSubVals);
905         if (type == null && fileName != null)
906         {
907           for (String ext : new String[] { "svg", "png", "html", "eps" })
908           {
909             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
910             {
911               type = ext;
912             }
913           }
914         }
915         // for moment we disable JSON export
916         Cache.setPropsAreReadOnly(true);
917         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
918
919         String imageColour = avm.getValueFromSubValOrArg(imageAv,
920                 Arg.IMAGECOLOUR, imageSubVals);
921         ColourSchemeI originalColourScheme = this.getColourScheme(af);
922         this.colourAlignFrame(af, imageColour);
923
924         Console.info("Writing " + file);
925
926         boolean success = checksBeforeWritingToFile(avm, imageSubVals,
927                 false, fileName, "image", isError);
928         if (!success)
929         {
930           continue;
931         }
932
933         try
934         {
935           switch (type)
936           {
937
938           case "svg":
939             Console.debug("Outputting type '" + type + "' to " + fileName);
940             af.createSVG(file, renderer);
941             break;
942
943           case "png":
944             Console.debug("Outputting type '" + type + "' to " + fileName);
945             af.createPNG(file, null, userBis);
946             break;
947
948           case "html":
949             Console.debug("Outputting type '" + type + "' to " + fileName);
950             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
951             htmlSVG.exportHTML(fileName, renderer);
952             break;
953
954           case "biojs":
955             Console.debug(
956                     "Outputting BioJS MSA Viwer HTML file: " + fileName);
957             try
958             {
959               BioJsHTMLOutput.refreshVersionInfo(
960                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
961             } catch (URISyntaxException e)
962             {
963               e.printStackTrace();
964             }
965             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
966             bjs.exportHTML(fileName);
967             break;
968
969           case "eps":
970             Console.debug("Outputting EPS file: " + fileName);
971             af.createEPS(file, renderer);
972             break;
973
974           case "imagemap":
975             Console.debug("Outputting ImageMap file: " + fileName);
976             af.createImageMap(file, name);
977             break;
978
979           default:
980             addWarn(Arg.IMAGE.argString() + " type '" + type
981                     + "' not known. Ignoring");
982             break;
983           }
984         } catch (Exception ioex)
985         {
986           addError("Unexpected error during export to '" + fileName + "'",
987                   ioex);
988           isError = true;
989         }
990
991         this.colourAlignFrame(af, originalColourScheme);
992       }
993     }
994     return !isError;
995   }
996
997   protected boolean processOutput(String id)
998   {
999     ArgValuesMap avm = argParser.getLinkedArgs(id);
1000     AlignFrame af = afMap.get(id);
1001
1002     if (af == null)
1003     {
1004       addWarn("Did not have an alignment window for id=" + id);
1005       return false;
1006     }
1007
1008     Boolean isError = Boolean.valueOf(false);
1009
1010     if (avm.containsArg(Arg.OUTPUT))
1011     {
1012       for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
1013       {
1014         String val = av.getValue();
1015         SubVals subVals = av.getSubVals();
1016         String fileName = subVals.getContent();
1017         boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
1018         File file = new File(fileName);
1019
1020         String name = af.getName();
1021         String format = avm.getValueFromSubValOrArg(av, Arg.FORMAT,
1022                 subVals);
1023         FileFormats ffs = FileFormats.getInstance();
1024         List<String> validFormats = ffs.getWritableFormats(false);
1025
1026         FileFormatI ff = null;
1027         if (format == null && fileName != null)
1028         {
1029           FORMAT: for (String fname : validFormats)
1030           {
1031             FileFormatI tff = ffs.forName(fname);
1032             String[] extensions = tff.getExtensions().split(",");
1033             for (String ext : extensions)
1034             {
1035               if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
1036               {
1037                 ff = tff;
1038                 format = ff.getName();
1039                 break FORMAT;
1040               }
1041             }
1042           }
1043         }
1044         if (ff == null && format != null)
1045         {
1046           ff = ffs.forName(format);
1047         }
1048         if (ff == null)
1049         {
1050           if (stdout)
1051           {
1052             ff = FileFormat.Fasta;
1053           }
1054           else
1055           {
1056             StringBuilder validSB = new StringBuilder();
1057             for (String f : validFormats)
1058             {
1059               if (validSB.length() > 0)
1060                 validSB.append(", ");
1061               validSB.append(f);
1062               FileFormatI tff = ffs.forName(f);
1063               validSB.append(" (");
1064               validSB.append(tff.getExtensions());
1065               validSB.append(")");
1066             }
1067
1068             addError("No valid format specified for "
1069                     + Arg.OUTPUT.argString() + ". Valid formats are "
1070                     + validSB.toString() + ".");
1071             continue;
1072           }
1073         }
1074
1075         boolean success = checksBeforeWritingToFile(avm, subVals, true,
1076                 fileName, ff.getName(), isError);
1077         if (!success)
1078         {
1079           continue;
1080         }
1081
1082         boolean backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVals,
1083                 null, Platform.isHeadless() ? null : BackupFiles.ENABLED,
1084                 !Platform.isHeadless());
1085
1086         Console.info("Writing " + fileName);
1087
1088         af.saveAlignment(fileName, ff, stdout, backups);
1089         if (af.isSaveAlignmentSuccessful())
1090         {
1091           Console.debug("Written alignment '" + name + "' in "
1092                   + ff.getName() + " format to '" + file + "'");
1093         }
1094         else
1095         {
1096           addError("Error writing file '" + file + "' in " + ff.getName()
1097                   + " format!");
1098           isError = true;
1099           continue;
1100         }
1101
1102       }
1103     }
1104     return !isError;
1105   }
1106
1107   private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
1108           ArgValue av)
1109   {
1110     SubVals subVals = av.getSubVals();
1111     ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID, true);
1112     SequenceI seq = null;
1113     if (subVals == null && idAv == null)
1114       return null;
1115     if (af == null || af.getCurrentView() == null)
1116     {
1117       return null;
1118     }
1119     AlignmentI al = af.getCurrentView().getAlignment();
1120     if (al == null)
1121     {
1122       return null;
1123     }
1124     if (subVals != null)
1125     {
1126       if (subVals.has(Arg.SEQID.getName()))
1127       {
1128         seq = al.findName(subVals.get(Arg.SEQID.getName()));
1129       }
1130       else if (-1 < subVals.getIndex()
1131               && subVals.getIndex() < al.getSequences().size())
1132       {
1133         seq = al.getSequenceAt(subVals.getIndex());
1134       }
1135     }
1136     if (seq == null && idAv != null)
1137     {
1138       seq = al.findName(idAv.getValue());
1139     }
1140     return seq;
1141   }
1142
1143   public AlignFrame[] getAlignFrames()
1144   {
1145     AlignFrame[] afs = null;
1146     if (afMap != null)
1147     {
1148       afs = (AlignFrame[]) afMap.values().toArray();
1149     }
1150
1151     return afs;
1152   }
1153
1154   public List<StructureViewer> getStructureViewers()
1155   {
1156     List<StructureViewer> svs = null;
1157     if (svMap != null)
1158     {
1159       for (List<StructureViewer> svList : svMap.values())
1160       {
1161         if (svs == null)
1162         {
1163           svs = new ArrayList<>();
1164         }
1165         svs.addAll(svList);
1166       }
1167     }
1168     return svs;
1169   }
1170
1171   private void colourAlignFrame(AlignFrame af, String colour)
1172   {
1173     // use string "none" to remove colour scheme
1174     if (colour != null && "" != colour)
1175     {
1176       ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
1177               af.getViewport(), af.getViewport().getAlignment(), colour);
1178       if (cs == null && !StringUtils.equalsIgnoreCase(colour, "none"))
1179       {
1180         addWarn("Couldn't parse '" + colour + "' as a colourscheme.");
1181       }
1182       else
1183       {
1184         Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
1185         colourAlignFrame(af, cs);
1186       }
1187     }
1188   }
1189
1190   private void colourAlignFrame(AlignFrame af, ColourSchemeI cs)
1191   {
1192     // Note that cs == null removes colour scheme from af
1193     af.changeColour(cs);
1194   }
1195
1196   private ColourSchemeI getColourScheme(AlignFrame af)
1197   {
1198     return af.getViewport().getGlobalColourScheme();
1199   }
1200
1201   private void addInfo(String errorMessage)
1202   {
1203     Console.info(errorMessage);
1204     errors.add(errorMessage);
1205   }
1206
1207   private void addWarn(String errorMessage)
1208   {
1209     Console.warn(errorMessage);
1210     errors.add(errorMessage);
1211   }
1212
1213   private void addError(String errorMessage)
1214   {
1215     addError(errorMessage, null);
1216   }
1217
1218   private void addError(String errorMessage, Exception e)
1219   {
1220     Console.error(errorMessage, e);
1221     errors.add(errorMessage);
1222   }
1223
1224   private boolean checksBeforeWritingToFile(ArgValuesMap avm,
1225           SubVals subVal, boolean includeBackups, String filename,
1226           String adjective, Boolean isError)
1227   {
1228     File file = new File(filename);
1229
1230     boolean overwrite = avm.getFromSubValArgOrPref(Arg.OVERWRITE, subVal,
1231             null, "OVERWRITE_OUTPUT", false);
1232     boolean stdout = false;
1233     boolean backups = false;
1234     if (includeBackups)
1235     {
1236       stdout = ArgParser.STDOUTFILENAME.equals(filename);
1237       // backups. Use the Arg.BACKUPS or subval "backups" setting first,
1238       // otherwise if headless assume false, if not headless use the user
1239       // preference with default true.
1240       backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVal, null,
1241               Platform.isHeadless() ? null : BackupFiles.ENABLED,
1242               !Platform.isHeadless());
1243     }
1244
1245     if (file.exists() && !(overwrite || backups || stdout))
1246     {
1247       addWarn("Won't overwrite file '" + filename + "' without "
1248               + Arg.OVERWRITE.argString()
1249               + (includeBackups ? " or " + Arg.BACKUPS.argString() : "")
1250               + " set");
1251       return false;
1252     }
1253
1254     boolean mkdirs = avm.getFromSubValArgOrPref(Arg.MKDIRS, subVal, null,
1255             "MKDIRS_OUTPUT", false);
1256
1257     if (!FileUtils.checkParentDir(file, mkdirs))
1258     {
1259       addError("Directory '"
1260               + FileUtils.getParentDir(file).getAbsolutePath()
1261               + "' does not exist for " + adjective + " file '" + filename
1262               + "'."
1263               + (mkdirs ? "" : "  Try using " + Arg.MKDIRS.argString()));
1264       isError = true;
1265       return false;
1266     }
1267
1268     return true;
1269   }
1270
1271   public List<String> getErrors()
1272   {
1273     return errors;
1274   }
1275
1276   public String errorsToString()
1277   {
1278     StringBuilder sb = new StringBuilder();
1279     for (String error : errors)
1280     {
1281       if (sb.length() > 0)
1282         sb.append("\n");
1283       sb.append("- " + error);
1284     }
1285     return sb.toString();
1286   }
1287 }