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