Merge branch 'improvement/JAL-4250_secondary_structure_annotation_antialias' into...
[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.argparser.Arg;
18 import jalview.bin.argparser.ArgParser;
19 import jalview.bin.argparser.ArgParser.Position;
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.imagemaker.BitmapImageSizing;
58
59 public class Commands
60 {
61   Desktop desktop;
62
63   private boolean headless;
64
65   private ArgParser argParser;
66
67   private Map<String, AlignFrame> afMap;
68
69   private boolean commandArgsProvided = false;
70
71   private boolean argsWereParsed = false;
72
73   public Commands(ArgParser argparser, boolean headless)
74   {
75     this(Desktop.instance, argparser, headless);
76   }
77
78   public Commands(Desktop d, ArgParser argparser, boolean h)
79   {
80     argParser = argparser;
81     headless = h;
82     desktop = d;
83     afMap = new HashMap<>();
84     if (argparser != null)
85     {
86       processArgs(argparser, headless);
87     }
88   }
89
90   private boolean processArgs(ArgParser argparser, boolean h)
91   {
92     argParser = argparser;
93     headless = h;
94     boolean theseArgsWereParsed = false;
95
96     if (argParser != null && argParser.getLinkedIds() != null)
97     {
98       for (String id : argParser.getLinkedIds())
99       {
100         ArgValuesMap avm = argParser.getLinkedArgs(id);
101         theseArgsWereParsed = true;
102         theseArgsWereParsed &= processLinked(id);
103         processGroovyScript(id);
104         boolean processLinkedOkay = theseArgsWereParsed;
105
106         // wait around until alignFrame isn't busy
107         AlignFrame af = afMap.get(id);
108         while (af != null && af.getViewport().isCalcInProgress())
109         {
110           try
111           {
112             Thread.sleep(25);
113           } catch (Exception q)
114           {
115           }
116           ;
117         }
118
119         theseArgsWereParsed &= processImages(id);
120         if (processLinkedOkay)
121           theseArgsWereParsed &= processOutput(id);
122
123         // close ap
124         if (avm.getBoolean(Arg.CLOSE))
125         {
126           af = afMap.get(id);
127           if (af != null)
128           {
129             af.closeMenuItem_actionPerformed(true);
130           }
131         }
132
133       }
134
135     }
136     if (argParser.getBoolean(Arg.QUIT))
137     {
138       Jalview.getInstance().quit();
139       return true;
140     }
141     // carry on with jalview.bin.Jalview
142     argsWereParsed = theseArgsWereParsed;
143     return argsWereParsed;
144   }
145
146   public boolean commandArgsProvided()
147   {
148     return commandArgsProvided;
149   }
150
151   public boolean argsWereParsed()
152   {
153     return argsWereParsed;
154   }
155
156   protected boolean processUnlinked(String id)
157   {
158     return processLinked(id);
159   }
160
161   protected boolean processLinked(String id)
162   {
163     boolean theseArgsWereParsed = false;
164     ArgValuesMap avm = argParser.getLinkedArgs(id);
165     if (avm == null)
166       return true;
167
168     // set wrap scope here so it can be applied after structures are opened
169     boolean wrap = false;
170
171     if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
172     {
173       commandArgsProvided = true;
174       long progress = -1;
175
176       boolean first = true;
177       boolean progressBarSet = false;
178       AlignFrame af;
179       // Combine the APPEND and OPEN files into one list, along with whether it
180       // was APPEND or OPEN
181       List<ArgValue> openAvList = new ArrayList<>();
182       openAvList.addAll(avm.getArgValueList(Arg.OPEN));
183       openAvList.addAll(avm.getArgValueList(Arg.APPEND));
184       // sort avlist based on av.getArgIndex()
185       Collections.sort(openAvList);
186       for (ArgValue av : openAvList)
187       {
188         Arg a = av.getArg();
189         SubVals sv = av.getSubVals();
190         String openFile = av.getValue();
191         if (openFile == null)
192           continue;
193
194         theseArgsWereParsed = true;
195         if (first)
196         {
197           first = false;
198           if (!headless && desktop != null)
199           {
200             desktop.setProgressBar(
201                     MessageManager.getString(
202                             "status.processing_commandline_args"),
203                     progress = System.currentTimeMillis());
204             progressBarSet = true;
205           }
206         }
207
208         if (!Platform.isJS())
209         /**
210          * ignore in JavaScript -- can't just file existence - could load it?
211          * 
212          * @j2sIgnore
213          */
214         {
215           if (!HttpUtils.startsWithHttpOrHttps(openFile))
216           {
217             if (!(new File(openFile)).exists())
218             {
219               Console.warn("Can't find file '" + openFile + "'");
220             }
221           }
222         }
223
224         DataSourceType protocol = AppletFormatAdapter
225                 .checkProtocol(openFile);
226
227         FileFormatI format = null;
228         try
229         {
230           format = new IdentifyFile().identify(openFile, protocol);
231         } catch (FileFormatException e1)
232         {
233           Console.error("Unknown file format for '" + openFile + "'");
234         }
235
236         af = afMap.get(id);
237         // When to open a new AlignFrame
238         if (af == null || "true".equals(av.getSubVal("new"))
239                 || a == Arg.OPEN || format == FileFormat.Jalview)
240         {
241           if (a == Arg.OPEN)
242           {
243             Jalview.testoutput(argParser, Arg.OPEN, "examples/uniref50.fa",
244                     openFile);
245           }
246
247           Console.debug(
248                   "Opening '" + openFile + "' in new alignment frame");
249           FileLoader fileLoader = new FileLoader(!headless);
250
251           af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
252                   format);
253
254           // colour alignment?
255           String colour = ArgParser.getFromSubValArgOrPref(avm, av,
256                   Arg.COLOUR, sv, null, "DEFAULT_COLOUR_PROT", "");
257           if ("" != colour)
258           {
259             ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
260                     af.getViewport(), af.getViewport().getAlignment(),
261                     colour);
262
263             if (cs == null && !"None".equals(colour))
264             {
265               Console.warn(
266                       "Couldn't parse '" + colour + "' as a colourscheme.");
267             }
268             else
269             {
270               af.changeColour(cs);
271             }
272             Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
273           }
274
275           // Change alignment frame title
276           String title = ArgParser.getFromSubValArgOrPref(avm, av,
277                   Arg.TITLE, sv, null, null, null);
278           if (title != null)
279           {
280             af.setTitle(title);
281             Jalview.testoutput(argParser, Arg.TITLE, "test title", title);
282           }
283
284           // Add features
285           String featuresfile = ArgParser.getValueFromSubValOrArg(avm, av,
286                   Arg.FEATURES, sv);
287           if (featuresfile != null)
288           {
289             af.parseFeaturesFile(featuresfile,
290                     AppletFormatAdapter.checkProtocol(featuresfile));
291             Jalview.testoutput(argParser, Arg.FEATURES,
292                     "examples/testdata/plantfdx.features", featuresfile);
293           }
294
295           // Add annotations from file
296           String annotationsfile = ArgParser.getValueFromSubValOrArg(avm,
297                   av, Arg.ANNOTATIONS, sv);
298           if (annotationsfile != null)
299           {
300             af.loadJalviewDataFile(annotationsfile, null, null, null);
301             Jalview.testoutput(argParser, Arg.ANNOTATIONS,
302                     "examples/testdata/plantfdx.annotations",
303                     annotationsfile);
304           }
305
306           // Set or clear the sortbytree flag
307           boolean sortbytree = ArgParser.getBoolFromSubValOrArg(avm,
308                   Arg.SORTBYTREE, sv);
309           if (sortbytree)
310           {
311             af.getViewport().setSortByTree(true);
312             Jalview.testoutput(argParser, Arg.SORTBYTREE);
313           }
314
315           // Load tree from file
316           String treefile = ArgParser.getValueFromSubValOrArg(avm, av,
317                   Arg.TREE, sv);
318           if (treefile != null)
319           {
320             try
321             {
322               NewickFile nf = new NewickFile(treefile,
323                       AppletFormatAdapter.checkProtocol(treefile));
324               af.getViewport().setCurrentTree(
325                       af.showNewickTree(nf, treefile).getTree());
326               Jalview.testoutput(argParser, Arg.TREE,
327                       "examples/testdata/uniref50_test_tree", treefile);
328             } catch (IOException e)
329             {
330               Console.warn("Couldn't add tree " + treefile, e);
331             }
332           }
333
334           // Show secondary structure annotations?
335           boolean showSSAnnotations = ArgParser.getFromSubValArgOrPref(avm,
336                   Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
337                   "STRUCT_FROM_PDB", true);
338           af.setAnnotationsVisibility(showSSAnnotations, true, false);
339
340           // Show sequence annotations?
341           boolean showAnnotations = ArgParser.getFromSubValArgOrPref(avm,
342                   Arg.SHOWANNOTATIONS, av.getSubVals(), null,
343                   "SHOW_ANNOTATIONS", true);
344           af.setAnnotationsVisibility(showAnnotations, false, true);
345
346           // show temperature factor annotations?
347           if (avm.getBoolean(Arg.NOTEMPFAC))
348           {
349             // do this better (annotation types?)
350             List<String> hideThese = new ArrayList<>();
351             hideThese.add("Temperature Factor");
352             hideThese.add(AlphaFoldAnnotationRowBuilder.LABEL);
353             AlignmentUtils.showOrHideSequenceAnnotations(
354                     af.getCurrentView().getAlignment(), hideThese, null,
355                     false, false);
356           }
357
358           // wrap alignment? do this last for formatting reasons
359           wrap = ArgParser.getFromSubValArgOrPref(avm, Arg.WRAP, sv, null,
360                   "WRAP_ALIGNMENT", false);
361           // af.setWrapFormat(wrap) is applied after structures are opened for
362           // annotation reasons
363
364           // store the AlignFrame for this id
365           afMap.put(id, af);
366
367           // is it its own structure file?
368           if (format.isStructureFile())
369           {
370             StructureSelectionManager ssm = StructureSelectionManager
371                     .getStructureSelectionManager(Desktop.instance);
372             SequenceI seq = af.alignPanel.getAlignment().getSequenceAt(0);
373             ssm.computeMapping(false, new SequenceI[] { seq }, null,
374                     openFile, DataSourceType.FILE, null, null, null, false);
375           }
376         }
377         else
378         {
379           Console.debug(
380                   "Opening '" + openFile + "' in existing alignment frame");
381           DataSourceType dst = HttpUtils.startsWithHttpOrHttps(openFile)
382                   ? DataSourceType.URL
383                   : DataSourceType.FILE;
384           FileLoader fileLoader = new FileLoader(!headless);
385           fileLoader.LoadFile(af.getCurrentView(), openFile, dst, null,
386                   false);
387         }
388
389         Console.debug("Command " + Arg.APPEND + " executed successfully!");
390
391       }
392       if (first) // first=true means nothing opened
393       {
394         if (headless)
395         {
396           Jalview.exit("Could not open any files in headless mode", 1);
397         }
398         else
399         {
400           Console.warn("No more files to open");
401         }
402       }
403       if (progressBarSet && desktop != null)
404         desktop.setProgressBar(null, progress);
405
406     }
407
408     // open the structure (from same PDB file or given PDBfile)
409     if (!avm.getBoolean(Arg.NOSTRUCTURE))
410     {
411       AlignFrame af = afMap.get(id);
412       if (avm.containsArg(Arg.STRUCTURE))
413       {
414         commandArgsProvided = true;
415         for (ArgValue av : avm.getArgValueList(Arg.STRUCTURE))
416         {
417           String val = av.getValue();
418           SubVals subVals = av.getSubVals();
419           SequenceI seq = getSpecifiedSequence(af, avm, av);
420           if (seq == null)
421           {
422             // Could not find sequence from subId, let's assume the first
423             // sequence in the alignframe
424             AlignmentI al = af.getCurrentView().getAlignment();
425             seq = al.getSequenceAt(0);
426           }
427
428           if (seq == null)
429           {
430             Console.warn("Could not find sequence for argument "
431                     + Arg.STRUCTURE.argString() + "=" + val);
432             // you probably want to continue here, not break
433             // break;
434             continue;
435           }
436           File structureFile = null;
437           if (subVals.getContent() != null
438                   && subVals.getContent().length() != 0)
439           {
440             structureFile = new File(subVals.getContent());
441             Console.debug("Using structure file (from argument) '"
442                     + structureFile.getAbsolutePath() + "'");
443           }
444           // TRY THIS
445           /*
446            * PDBEntry fileEntry = new AssociatePdbFileWithSeq()
447            * .associatePdbWithSeq(selectedPdbFileName, DataSourceType.FILE,
448            * selectedSequence, true, Desktop.instance);
449            * 
450            * sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap, new
451            * SequenceI[] { selectedSequence });
452            * 
453            */
454           /* THIS DOESN'T WORK */
455           else if (seq.getAllPDBEntries() != null
456                   && seq.getAllPDBEntries().size() > 0)
457           {
458             structureFile = new File(
459                     seq.getAllPDBEntries().elementAt(0).getFile());
460             Console.debug("Using structure file (from sequence) '"
461                     + structureFile.getAbsolutePath() + "'");
462           }
463
464           if (structureFile == null)
465           {
466             Console.warn("Not provided structure file with '" + val + "'");
467             continue;
468           }
469
470           if (!structureFile.exists())
471           {
472             Console.warn("Structure file '"
473                     + structureFile.getAbsoluteFile() + "' not found.");
474             continue;
475           }
476
477           Console.debug("Using structure file "
478                   + structureFile.getAbsolutePath());
479
480           // open structure view
481           AlignmentPanel ap = af.alignPanel;
482           if (headless)
483           {
484             Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
485                     StructureViewer.ViewerType.JMOL.toString());
486           }
487
488           String structureFilepath = structureFile.getAbsolutePath();
489
490           // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
491           String paeFilepath = ArgParser
492                   .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
493                           Arg.PAEMATRIX, Position.AFTER, av, subVals, null,
494                           null, null);
495           if (paeFilepath != null)
496           {
497             File paeFile = new File(paeFilepath);
498
499             try
500             {
501               paeFilepath = paeFile.getCanonicalPath();
502             } catch (IOException e)
503             {
504               paeFilepath = paeFile.getAbsolutePath();
505               Console.warn("Problem with the PAE file path: '"
506                       + paeFile.getPath() + "'");
507             }
508           }
509
510           // showing annotations from structure file or not
511           boolean ssFromStructure = ArgParser.getFromSubValArgOrPref(avm,
512                   Arg.SHOWSSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
513                   true);
514
515           // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
516           // reference annotations
517           String tftString = ArgParser
518                   .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
519                           Arg.TEMPFAC, Position.AFTER, av, subVals, null,
520                           null, null);
521           boolean notempfac = ArgParser.getFromSubValArgOrPref(avm,
522                   Arg.NOTEMPFAC, subVals, null, "ADD_TEMPFACT_ANN", false,
523                   true);
524           TFType tft = notempfac ? null : TFType.DEFAULT;
525           if (tftString != null && !notempfac)
526           {
527             // get kind of temperature factor annotation
528             try
529             {
530               tft = TFType.valueOf(tftString.toUpperCase(Locale.ROOT));
531               Console.debug("Obtained Temperature Factor type of '" + tft
532                       + "' for structure '" + structureFilepath + "'");
533             } catch (IllegalArgumentException e)
534             {
535               // Just an error message!
536               StringBuilder sb = new StringBuilder().append("Cannot set ")
537                       .append(Arg.TEMPFAC.argString()).append(" to '")
538                       .append(tft)
539                       .append("', ignoring.  Valid values are: ");
540               Iterator<TFType> it = Arrays.stream(TFType.values())
541                       .iterator();
542               while (it.hasNext())
543               {
544                 sb.append(it.next().toString().toLowerCase(Locale.ROOT));
545                 if (it.hasNext())
546                   sb.append(", ");
547               }
548               Console.warn(sb.toString());
549             }
550           }
551
552           String sViewer = ArgParser.getFromSubValArgOrPref(avm,
553                   Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals, null,
554                   null, "jmol");
555           ViewerType viewerType = ViewerType.getFromString(sViewer);
556
557           // TODO use ssFromStructure
558           StructureViewer sv = StructureChooser
559                   .openStructureFileForSequence(null, null, ap, seq, false,
560                           structureFilepath, tft, paeFilepath, false,
561                           ssFromStructure, false, viewerType);
562
563           if (sv == null)
564           {
565             Console.error("Failed to import and open structure view.");
566             continue;
567           }
568           try
569           {
570             long tries = 1000;
571             while (sv.isBusy() && tries > 0)
572             {
573               Thread.sleep(25);
574               if (sv.isBusy())
575               {
576                 tries--;
577                 Console.debug(
578                         "Waiting for viewer for " + structureFilepath);
579               }
580             }
581             if (tries == 0 && sv.isBusy())
582             {
583               Console.warn(
584                       "Gave up waiting for structure viewer to load. Something may have gone wrong.");
585             }
586           } catch (Exception x)
587           {
588             Console.warn("Exception whilst waiting for structure viewer "
589                     + structureFilepath, x);
590           }
591           Console.debug(
592                   "Successfully opened viewer for " + structureFilepath);
593           String structureImageFilename = ArgParser.getValueFromSubValOrArg(
594                   avm, av, Arg.STRUCTUREIMAGE, subVals);
595           if (sv != null && structureImageFilename != null)
596           {
597             ArgValue siAv = avm.getClosestNextArgValueOfArg(av,
598                     Arg.STRUCTUREIMAGE);
599             SubVals sisv = null;
600             if (structureImageFilename.equals(siAv.getValue()))
601             {
602               sisv = siAv.getSubVals();
603             }
604             File structureImageFile = new File(structureImageFilename);
605             String width = ArgParser.getValueFromSubValOrArg(avm, av,
606                     Arg.STRUCTUREIMAGEWIDTH, sisv);
607             String height = ArgParser.getValueFromSubValOrArg(avm, av,
608                     Arg.STRUCTUREIMAGEHEIGHT, sisv);
609             String scale = ArgParser.getValueFromSubValOrArg(avm, av,
610                     Arg.STRUCTUREIMAGESCALE, sisv);
611             String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
612                     Arg.STRUCTUREIMAGETEXTRENDERER, sisv);
613             String typeS = ArgParser.getValueFromSubValOrArg(avm, av,
614                     Arg.STRUCTUREIMAGETYPE, sisv);
615             if (typeS == null || typeS.length() == 0)
616             {
617               typeS = FileUtils.getExtension(structureImageFile);
618             }
619             TYPE imageType;
620             try
621             {
622               imageType = Enum.valueOf(TYPE.class,
623                       typeS.toUpperCase(Locale.ROOT));
624             } catch (IllegalArgumentException e)
625             {
626               Console.warn("Do not know image format '" + typeS
627                       + "', using PNG");
628               imageType = TYPE.PNG;
629             }
630             BitmapImageSizing userBis = ImageMaker
631                     .parseScaleWidthHeightStrings(scale, width, height);
632             // TODO MAKE THIS VIEWER INDEPENDENT!!
633             switch (StructureViewer.getViewerType())
634             {
635             case JMOL:
636               try
637               {
638                 Thread.sleep(1000); // WHY ???
639               } catch (InterruptedException e)
640               {
641                 // TODO Auto-generated catch block
642                 e.printStackTrace();
643               }
644               JalviewStructureDisplayI sview = sv
645                       .getJalviewStructureDisplay();
646               if (sview instanceof AppJmol)
647               {
648                 AppJmol jmol = (AppJmol) sview;
649                 try
650                 {
651                   Console.debug("Rendering image to " + structureImageFile);
652                   jmol.makePDBImage(structureImageFile, imageType, renderer,
653                           userBis);
654                   Console.debug("Finished Rendering image to "
655                           + structureImageFile);
656
657                 } catch (ImageOutputException ioexc)
658                 {
659                   Console.warn("Unexpected error whilst exporting image to "
660                           + structureImageFile, ioexc);
661                 }
662
663               }
664               break;
665             default:
666               Console.warn("Cannot export image for structure viewer "
667                       + sv.getViewerType() + " yet");
668               break;
669             }
670           }
671         }
672       }
673     }
674
675     if (wrap)
676     {
677       AlignFrame af = afMap.get(id);
678       if (af != null)
679       {
680         af.setWrapFormat(wrap, true);
681       }
682     }
683
684     /*
685     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
686     if (doShading)
687     {
688       AlignFrame af = afMap.get(id);
689       for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
690               .findAnnotation(PDBChain.class.getName().toString()))
691       {
692         AnnotationColourGradient acg = new AnnotationColourGradient(aa,
693                 af.alignPanel.av.getGlobalColourScheme(), 0);
694         acg.setSeqAssociated(true);
695         af.changeColour(acg);
696         Console.info("Changed colour " + acg.toString());
697       }
698     }
699     */
700
701     return theseArgsWereParsed;
702   }
703
704   protected void processGroovyScript(String id)
705   {
706     ArgValuesMap avm = argParser.getLinkedArgs(id);
707     AlignFrame af = afMap.get(id);
708
709     if (af == null)
710     {
711       Console.warn("Did not have an alignment window for id=" + id);
712       return;
713     }
714
715     if (avm.containsArg(Arg.GROOVY))
716     {
717       String groovyscript = avm.getValue(Arg.GROOVY);
718       if (groovyscript != null)
719       {
720         // Execute the groovy script after we've done all the rendering stuff
721         // and before any images or figures are generated.
722         Console.info("Executing script " + groovyscript);
723         Jalview.getInstance().executeGroovyScript(groovyscript, af);
724       }
725     }
726   }
727
728   protected boolean processImages(String id)
729   {
730     ArgValuesMap avm = argParser.getLinkedArgs(id);
731     AlignFrame af = afMap.get(id);
732
733     if (af == null)
734     {
735       Console.warn("Did not have an alignment window for id=" + id);
736       return false;
737     }
738
739     if (avm.containsArg(Arg.IMAGE))
740     {
741       for (ArgValue av : avm.getArgValueList(Arg.IMAGE))
742       {
743         String val = av.getValue();
744         SubVals subVal = av.getSubVals();
745         String fileName = subVal.getContent();
746         File file = new File(fileName);
747         String name = af.getName();
748         String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
749                 Arg.TEXTRENDERER, subVal);
750         if (renderer == null)
751           renderer = "text";
752         String type = "png"; // default
753
754         String scale = ArgParser.getValueFromSubValOrArg(avm, av, Arg.SCALE,
755                 subVal);
756         String width = ArgParser.getValueFromSubValOrArg(avm, av, Arg.WIDTH,
757                 subVal);
758         String height = ArgParser.getValueFromSubValOrArg(avm, av,
759                 Arg.HEIGHT, subVal);
760         BitmapImageSizing userBis = ImageMaker
761                 .parseScaleWidthHeightStrings(scale, width, height);
762
763         type = ArgParser.getValueFromSubValOrArg(avm, av, Arg.TYPE, subVal);
764         if (type == null && fileName != null)
765         {
766           for (String ext : new String[] { "svg", "png", "html", "eps" })
767           {
768             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
769             {
770               type = ext;
771             }
772           }
773         }
774         // for moment we disable JSON export
775         Cache.setPropsAreReadOnly(true);
776         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
777
778         Console.info("Writing " + file);
779         try
780         {
781           switch (type)
782           {
783
784           case "svg":
785             Console.debug("Outputting type '" + type + "' to " + fileName);
786             af.createSVG(file, renderer);
787             break;
788
789           case "png":
790             Console.debug("Outputting type '" + type + "' to " + fileName);
791             af.createPNG(file, null, userBis);
792             break;
793
794           case "html":
795             Console.debug("Outputting type '" + type + "' to " + fileName);
796             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
797             htmlSVG.exportHTML(fileName, renderer);
798             break;
799
800           case "biojs":
801             Console.debug(
802                     "Outputting BioJS MSA Viwer HTML file: " + fileName);
803             try
804             {
805               BioJsHTMLOutput.refreshVersionInfo(
806                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
807             } catch (URISyntaxException e)
808             {
809               e.printStackTrace();
810             }
811             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
812             bjs.exportHTML(fileName);
813             break;
814
815           case "eps":
816             Console.debug("Outputting EPS file: " + fileName);
817             af.createEPS(file, renderer);
818             break;
819
820           case "imagemap":
821             Console.debug("Outputting ImageMap file: " + fileName);
822             af.createImageMap(file, name);
823             break;
824
825           default:
826             Console.warn(Arg.IMAGE.argString() + " type '" + type
827                     + "' not known. Ignoring");
828             break;
829           }
830         } catch (Exception ioex)
831         {
832           Console.warn("Unexpected error during export", ioex);
833         }
834       }
835     }
836     return true;
837   }
838
839   protected boolean processOutput(String id)
840   {
841     ArgValuesMap avm = argParser.getLinkedArgs(id);
842     AlignFrame af = afMap.get(id);
843
844     if (af == null)
845     {
846       Console.warn("Did not have an alignment window for id=" + id);
847       return false;
848     }
849
850     if (avm.containsArg(Arg.OUTPUT))
851     {
852       for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
853       {
854         String val = av.getValue();
855         SubVals subVals = av.getSubVals();
856         String fileName = subVals.getContent();
857         boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
858         File file = new File(fileName);
859         boolean overwrite = ArgParser.getFromSubValArgOrPref(avm,
860                 Arg.OVERWRITE, subVals, null, "OVERWRITE_OUTPUT", false);
861         // backups. Use the Arg.BACKUPS or subval "backups" setting first,
862         // otherwise if headless assume false, if not headless use the user
863         // preference with default true.
864         boolean backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS,
865                 subVals, null,
866                 Platform.isHeadless() ? null : BackupFiles.ENABLED,
867                 !Platform.isHeadless());
868
869         // if backups is not true then --overwrite must be specified
870         if (file.exists() && !(overwrite || backups || stdout))
871         {
872           Console.error("Won't overwrite file '" + fileName + "' without "
873                   + Arg.OVERWRITE.argString() + " or "
874                   + Arg.BACKUPS.argString() + " set");
875           return false;
876         }
877
878         String name = af.getName();
879         String format = ArgParser.getValueFromSubValOrArg(avm, av,
880                 Arg.FORMAT, subVals);
881         FileFormats ffs = FileFormats.getInstance();
882         List<String> validFormats = ffs.getWritableFormats(false);
883
884         FileFormatI ff = null;
885         if (format == null && fileName != null)
886         {
887           FORMAT: for (String fname : validFormats)
888           {
889             FileFormatI tff = ffs.forName(fname);
890             String[] extensions = tff.getExtensions().split(",");
891             for (String ext : extensions)
892             {
893               if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
894               {
895                 ff = tff;
896                 format = ff.getName();
897                 break FORMAT;
898               }
899             }
900           }
901         }
902         if (ff == null && format != null)
903         {
904           ff = ffs.forName(format);
905         }
906         if (ff == null)
907         {
908           if (stdout)
909           {
910             ff = FileFormat.Fasta;
911           }
912           else
913           {
914             StringBuilder validSB = new StringBuilder();
915             for (String f : validFormats)
916             {
917               if (validSB.length() > 0)
918                 validSB.append(", ");
919               validSB.append(f);
920               FileFormatI tff = ffs.forName(f);
921               validSB.append(" (");
922               validSB.append(tff.getExtensions());
923               validSB.append(")");
924             }
925
926             Jalview.exit("No valid format specified for "
927                     + Arg.OUTPUT.argString() + ". Valid formats are "
928                     + validSB.toString() + ".", 1);
929             // this return really shouldn't happen
930             return false;
931           }
932         }
933
934         String savedBackupsPreference = Cache
935                 .getDefault(BackupFiles.ENABLED, null);
936         Console.debug("Setting backups to " + backups);
937         Cache.applicationProperties.put(BackupFiles.ENABLED,
938                 Boolean.toString(backups));
939
940         Console.info("Writing " + fileName);
941
942         af.saveAlignment(fileName, ff, stdout);
943         Console.debug("Returning backups to " + savedBackupsPreference);
944         if (savedBackupsPreference != null)
945           Cache.applicationProperties.put(BackupFiles.ENABLED,
946                   savedBackupsPreference);
947         if (af.isSaveAlignmentSuccessful())
948         {
949           Console.debug("Written alignment '" + name + "' in "
950                   + ff.getName() + " format to " + file);
951         }
952         else
953         {
954           Console.warn("Error writing file " + file + " in " + ff.getName()
955                   + " format!");
956         }
957
958       }
959     }
960     return true;
961   }
962
963   private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
964           ArgValue av)
965   {
966     SubVals subVals = av.getSubVals();
967     ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID);
968     SequenceI seq = null;
969     if (subVals == null && idAv == null)
970       return null;
971     if (af == null || af.getCurrentView() == null)
972     {
973       return null;
974     }
975     AlignmentI al = af.getCurrentView().getAlignment();
976     if (al == null)
977     {
978       return null;
979     }
980     if (subVals != null)
981     {
982       if (subVals.has(Arg.SEQID.getName()))
983       {
984         seq = al.findName(subVals.get(Arg.SEQID.getName()));
985       }
986       else if (-1 < subVals.getIndex()
987               && subVals.getIndex() < al.getSequences().size())
988       {
989         seq = al.getSequenceAt(subVals.getIndex());
990       }
991     }
992     else if (idAv != null)
993     {
994       seq = al.findName(idAv.getValue());
995     }
996     return seq;
997   }
998 }