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