JAL-4265 bail early when attempting to export a view from other viewers than Jmol...
[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                     sview.getBinding().openSession(sessionToRestore.getCanonicalPath());
786                   }
787                 } catch (ImageOutputException ioexc)
788                 {
789                   addError("Unexpected error whilst exporting image to "
790                           + structureImageFile, ioexc);
791                   isError = true;
792                   continue;
793                 }
794                 catch (IOException ioexec)
795                 {
796                   addError("Unexpected error when restoring structure viewer session after custom view operations.");
797                   isError = true;
798                   continue;
799                 }
800                 finally
801                 {
802                   this.colourAlignFrame(af, originalColourScheme);
803                 }
804               }
805             }
806           }
807           argParser.setStructureFilename(null);
808         }
809       }
810     }
811
812     if (wrap)
813     {
814       AlignFrame af = afMap.get(id);
815       if (af != null)
816       {
817         af.setWrapFormat(wrap, true);
818       }
819     }
820
821     /*
822     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
823     if (doShading)
824     {
825       AlignFrame af = afMap.get(id);
826       for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
827               .findAnnotation(PDBChain.class.getName().toString()))
828       {
829         AnnotationColourGradient acg = new AnnotationColourGradient(aa,
830                 af.alignPanel.av.getGlobalColourScheme(), 0);
831         acg.setSeqAssociated(true);
832         af.changeColour(acg);
833         Console.info("Changed colour " + acg.toString());
834       }
835     }
836     */
837
838     return theseArgsWereParsed && !isError;
839   }
840
841   protected void processGroovyScript(String id)
842   {
843     ArgValuesMap avm = argParser.getLinkedArgs(id);
844     AlignFrame af = afMap.get(id);
845
846     if (af == null)
847     {
848       addWarn("Did not have an alignment window for id=" + id);
849       return;
850     }
851
852     if (avm.containsArg(Arg.GROOVY))
853     {
854       String groovyscript = avm.getValue(Arg.GROOVY);
855       if (groovyscript != null)
856       {
857         // Execute the groovy script after we've done all the rendering stuff
858         // and before any images or figures are generated.
859         Console.info("Executing script " + groovyscript);
860         Jalview.getInstance().executeGroovyScript(groovyscript, af);
861       }
862     }
863   }
864
865   protected boolean processImages(String id)
866   {
867     ArgValuesMap avm = argParser.getLinkedArgs(id);
868     AlignFrame af = afMap.get(id);
869
870     if (af == null)
871     {
872       addWarn("Did not have an alignment window for id=" + id);
873       return false;
874     }
875
876     Boolean isError = Boolean.valueOf(false);
877     if (avm.containsArg(Arg.IMAGE))
878     {
879       for (ArgValue imageAv : avm.getArgValueList(Arg.IMAGE))
880       {
881         String val = imageAv.getValue();
882         SubVals imageSubVals = imageAv.getSubVals();
883         String fileName = imageSubVals.getContent();
884         File file = new File(fileName);
885         String name = af.getName();
886         String renderer = avm.getValueFromSubValOrArg(imageAv,
887                 Arg.TEXTRENDERER, imageSubVals);
888         if (renderer == null)
889           renderer = "text";
890         String type = "png"; // default
891
892         String scale = avm.getValueFromSubValOrArg(imageAv, Arg.SCALE,
893                 imageSubVals);
894         String width = avm.getValueFromSubValOrArg(imageAv, Arg.WIDTH,
895                 imageSubVals);
896         String height = avm.getValueFromSubValOrArg(imageAv, Arg.HEIGHT,
897                 imageSubVals);
898         BitmapImageSizing userBis = ImageMaker
899                 .parseScaleWidthHeightStrings(scale, width, height);
900
901         type = avm.getValueFromSubValOrArg(imageAv, Arg.TYPE, imageSubVals);
902         if (type == null && fileName != null)
903         {
904           for (String ext : new String[] { "svg", "png", "html", "eps" })
905           {
906             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
907             {
908               type = ext;
909             }
910           }
911         }
912         // for moment we disable JSON export
913         Cache.setPropsAreReadOnly(true);
914         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
915
916         String imageColour = avm.getValueFromSubValOrArg(imageAv,
917                 Arg.IMAGECOLOUR, imageSubVals);
918         ColourSchemeI originalColourScheme = this.getColourScheme(af);
919         this.colourAlignFrame(af, imageColour);
920
921         Console.info("Writing " + file);
922
923         boolean success = checksBeforeWritingToFile(avm, imageSubVals,
924                 false, fileName, "image", isError);
925         if (!success)
926         {
927           continue;
928         }
929
930         try
931         {
932           switch (type)
933           {
934
935           case "svg":
936             Console.debug("Outputting type '" + type + "' to " + fileName);
937             af.createSVG(file, renderer);
938             break;
939
940           case "png":
941             Console.debug("Outputting type '" + type + "' to " + fileName);
942             af.createPNG(file, null, userBis);
943             break;
944
945           case "html":
946             Console.debug("Outputting type '" + type + "' to " + fileName);
947             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
948             htmlSVG.exportHTML(fileName, renderer);
949             break;
950
951           case "biojs":
952             Console.debug(
953                     "Outputting BioJS MSA Viwer HTML file: " + fileName);
954             try
955             {
956               BioJsHTMLOutput.refreshVersionInfo(
957                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
958             } catch (URISyntaxException e)
959             {
960               e.printStackTrace();
961             }
962             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
963             bjs.exportHTML(fileName);
964             break;
965
966           case "eps":
967             Console.debug("Outputting EPS file: " + fileName);
968             af.createEPS(file, renderer);
969             break;
970
971           case "imagemap":
972             Console.debug("Outputting ImageMap file: " + fileName);
973             af.createImageMap(file, name);
974             break;
975
976           default:
977             addWarn(Arg.IMAGE.argString() + " type '" + type
978                     + "' not known. Ignoring");
979             break;
980           }
981         } catch (Exception ioex)
982         {
983           addError("Unexpected error during export to '" + fileName + "'",
984                   ioex);
985           isError = true;
986         }
987
988         this.colourAlignFrame(af, originalColourScheme);
989       }
990     }
991     return !isError;
992   }
993
994   protected boolean processOutput(String id)
995   {
996     ArgValuesMap avm = argParser.getLinkedArgs(id);
997     AlignFrame af = afMap.get(id);
998
999     if (af == null)
1000     {
1001       addWarn("Did not have an alignment window for id=" + id);
1002       return false;
1003     }
1004
1005     Boolean isError = Boolean.valueOf(false);
1006
1007     if (avm.containsArg(Arg.OUTPUT))
1008     {
1009       for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
1010       {
1011         String val = av.getValue();
1012         SubVals subVals = av.getSubVals();
1013         String fileName = subVals.getContent();
1014         boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
1015         File file = new File(fileName);
1016
1017         String name = af.getName();
1018         String format = avm.getValueFromSubValOrArg(av, Arg.FORMAT,
1019                 subVals);
1020         FileFormats ffs = FileFormats.getInstance();
1021         List<String> validFormats = ffs.getWritableFormats(false);
1022
1023         FileFormatI ff = null;
1024         if (format == null && fileName != null)
1025         {
1026           FORMAT: for (String fname : validFormats)
1027           {
1028             FileFormatI tff = ffs.forName(fname);
1029             String[] extensions = tff.getExtensions().split(",");
1030             for (String ext : extensions)
1031             {
1032               if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
1033               {
1034                 ff = tff;
1035                 format = ff.getName();
1036                 break FORMAT;
1037               }
1038             }
1039           }
1040         }
1041         if (ff == null && format != null)
1042         {
1043           ff = ffs.forName(format);
1044         }
1045         if (ff == null)
1046         {
1047           if (stdout)
1048           {
1049             ff = FileFormat.Fasta;
1050           }
1051           else
1052           {
1053             StringBuilder validSB = new StringBuilder();
1054             for (String f : validFormats)
1055             {
1056               if (validSB.length() > 0)
1057                 validSB.append(", ");
1058               validSB.append(f);
1059               FileFormatI tff = ffs.forName(f);
1060               validSB.append(" (");
1061               validSB.append(tff.getExtensions());
1062               validSB.append(")");
1063             }
1064
1065             addError("No valid format specified for "
1066                     + Arg.OUTPUT.argString() + ". Valid formats are "
1067                     + validSB.toString() + ".");
1068             continue;
1069           }
1070         }
1071
1072         boolean success = checksBeforeWritingToFile(avm, subVals, true,
1073                 fileName, ff.getName(), isError);
1074         if (!success)
1075         {
1076           continue;
1077         }
1078
1079         boolean backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVals,
1080                 null, Platform.isHeadless() ? null : BackupFiles.ENABLED,
1081                 !Platform.isHeadless());
1082
1083         Console.info("Writing " + fileName);
1084
1085         af.saveAlignment(fileName, ff, stdout, backups);
1086         if (af.isSaveAlignmentSuccessful())
1087         {
1088           Console.debug("Written alignment '" + name + "' in "
1089                   + ff.getName() + " format to '" + file + "'");
1090         }
1091         else
1092         {
1093           addError("Error writing file '" + file + "' in " + ff.getName()
1094                   + " format!");
1095           isError = true;
1096           continue;
1097         }
1098
1099       }
1100     }
1101     return !isError;
1102   }
1103
1104   private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
1105           ArgValue av)
1106   {
1107     SubVals subVals = av.getSubVals();
1108     ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID, true);
1109     SequenceI seq = null;
1110     if (subVals == null && idAv == null)
1111       return null;
1112     if (af == null || af.getCurrentView() == null)
1113     {
1114       return null;
1115     }
1116     AlignmentI al = af.getCurrentView().getAlignment();
1117     if (al == null)
1118     {
1119       return null;
1120     }
1121     if (subVals != null)
1122     {
1123       if (subVals.has(Arg.SEQID.getName()))
1124       {
1125         seq = al.findName(subVals.get(Arg.SEQID.getName()));
1126       }
1127       else if (-1 < subVals.getIndex()
1128               && subVals.getIndex() < al.getSequences().size())
1129       {
1130         seq = al.getSequenceAt(subVals.getIndex());
1131       }
1132     }
1133     if (seq == null && idAv != null)
1134     {
1135       seq = al.findName(idAv.getValue());
1136     }
1137     return seq;
1138   }
1139
1140   public AlignFrame[] getAlignFrames()
1141   {
1142     AlignFrame[] afs = null;
1143     if (afMap != null)
1144     {
1145       afs = (AlignFrame[]) afMap.values().toArray();
1146     }
1147
1148     return afs;
1149   }
1150
1151   public List<StructureViewer> getStructureViewers()
1152   {
1153     List<StructureViewer> svs = null;
1154     if (svMap != null)
1155     {
1156       for (List<StructureViewer> svList : svMap.values())
1157       {
1158         if (svs == null)
1159         {
1160           svs = new ArrayList<>();
1161         }
1162         svs.addAll(svList);
1163       }
1164     }
1165     return svs;
1166   }
1167
1168   private void colourAlignFrame(AlignFrame af, String colour)
1169   {
1170     // use string "none" to remove colour scheme
1171     if (colour != null && "" != colour)
1172     {
1173       ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
1174               af.getViewport(), af.getViewport().getAlignment(), colour);
1175       if (cs == null && !StringUtils.equalsIgnoreCase(colour, "none"))
1176       {
1177         addWarn("Couldn't parse '" + colour + "' as a colourscheme.");
1178       }
1179       else
1180       {
1181         Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
1182         colourAlignFrame(af, cs);
1183       }
1184     }
1185   }
1186
1187   private void colourAlignFrame(AlignFrame af, ColourSchemeI cs)
1188   {
1189     // Note that cs == null removes colour scheme from af
1190     af.changeColour(cs);
1191   }
1192
1193   private ColourSchemeI getColourScheme(AlignFrame af)
1194   {
1195     return af.getViewport().getGlobalColourScheme();
1196   }
1197
1198   private void addInfo(String errorMessage)
1199   {
1200     Console.info(errorMessage);
1201     errors.add(errorMessage);
1202   }
1203
1204   private void addWarn(String errorMessage)
1205   {
1206     Console.warn(errorMessage);
1207     errors.add(errorMessage);
1208   }
1209
1210   private void addError(String errorMessage)
1211   {
1212     addError(errorMessage, null);
1213   }
1214
1215   private void addError(String errorMessage, Exception e)
1216   {
1217     Console.error(errorMessage, e);
1218     errors.add(errorMessage);
1219   }
1220
1221   private boolean checksBeforeWritingToFile(ArgValuesMap avm,
1222           SubVals subVal, boolean includeBackups, String filename,
1223           String adjective, Boolean isError)
1224   {
1225     File file = new File(filename);
1226
1227     boolean overwrite = avm.getFromSubValArgOrPref(Arg.OVERWRITE, subVal,
1228             null, "OVERWRITE_OUTPUT", false);
1229     boolean stdout = false;
1230     boolean backups = false;
1231     if (includeBackups)
1232     {
1233       stdout = ArgParser.STDOUTFILENAME.equals(filename);
1234       // backups. Use the Arg.BACKUPS or subval "backups" setting first,
1235       // otherwise if headless assume false, if not headless use the user
1236       // preference with default true.
1237       backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVal, null,
1238               Platform.isHeadless() ? null : BackupFiles.ENABLED,
1239               !Platform.isHeadless());
1240     }
1241
1242     if (file.exists() && !(overwrite || backups || stdout))
1243     {
1244       addWarn("Won't overwrite file '" + filename + "' without "
1245               + Arg.OVERWRITE.argString()
1246               + (includeBackups ? " or " + Arg.BACKUPS.argString() : "")
1247               + " set");
1248       return false;
1249     }
1250
1251     boolean mkdirs = avm.getFromSubValArgOrPref(Arg.MKDIRS, subVal, null,
1252             "MKDIRS_OUTPUT", false);
1253
1254     if (!FileUtils.checkParentDir(file, mkdirs))
1255     {
1256       addError("Directory '"
1257               + FileUtils.getParentDir(file).getAbsolutePath()
1258               + "' does not exist for " + adjective + " file '" + filename
1259               + "'."
1260               + (mkdirs ? "" : "  Try using " + Arg.MKDIRS.argString()));
1261       isError = true;
1262       return false;
1263     }
1264
1265     return true;
1266   }
1267
1268   public List<String> getErrors()
1269   {
1270     return errors;
1271   }
1272
1273   public String errorsToString()
1274   {
1275     StringBuilder sb = new StringBuilder();
1276     for (String error : errors)
1277     {
1278       if (sb.length() > 0)
1279         sb.append("\n");
1280       sb.append("- " + error);
1281     }
1282     return sb.toString();
1283   }
1284 }