JAL-3563 adds Jalview.setSynchronous()
[jalview.git] / src / jalview / bin / Jalview.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import jalview.api.AlignCalcWorkerI;
24 import jalview.api.AlignFrameI;
25 import jalview.api.AlignViewportI;
26 import jalview.api.JalviewApp;
27 import jalview.api.StructureSelectionManagerProvider;
28 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
29 import jalview.datamodel.ColumnSelection;
30 import jalview.datamodel.HiddenColumns;
31 import jalview.datamodel.PDBEntry;
32 import jalview.datamodel.SequenceGroup;
33 import jalview.datamodel.SequenceI;
34 import jalview.ext.so.SequenceOntology;
35 import jalview.gui.AlignFrame;
36 import jalview.gui.AlignViewport;
37 import jalview.gui.AlignmentPanel;
38 import jalview.gui.CalculationChooser;
39 import jalview.gui.Desktop;
40 import jalview.gui.Preferences;
41 import jalview.gui.PromptUserConfig;
42 import jalview.gui.StructureViewer;
43 import jalview.io.AppletFormatAdapter;
44 import jalview.io.BioJsHTMLOutput;
45 import jalview.io.DataSourceType;
46 import jalview.io.FileFormat;
47 import jalview.io.FileFormatException;
48 import jalview.io.FileFormatI;
49 import jalview.io.FileFormats;
50 import jalview.io.FileLoader;
51 import jalview.io.HtmlSvgOutput;
52 import jalview.io.IdentifyFile;
53 import jalview.io.NewickFile;
54 import jalview.io.gff.SequenceOntologyFactory;
55 import jalview.javascript.JSFunctionExec;
56 import jalview.javascript.MouseOverStructureListener;
57 import jalview.renderer.seqfeatures.FeatureRenderer;
58 import jalview.schemes.ColourSchemeI;
59 import jalview.schemes.ColourSchemeProperty;
60 import jalview.structure.SelectionSource;
61 import jalview.structure.VamsasSource;
62 import jalview.util.MessageManager;
63 import jalview.util.Platform;
64 import jalview.ws.jws2.Jws2Discoverer;
65
66 import java.applet.AppletContext;
67 import java.io.BufferedReader;
68 import java.io.File;
69 import java.io.FileOutputStream;
70 import java.io.IOException;
71 import java.io.InputStreamReader;
72 import java.io.OutputStreamWriter;
73 import java.io.PrintWriter;
74 import java.net.MalformedURLException;
75 import java.net.URI;
76 import java.net.URISyntaxException;
77 import java.net.URL;
78 import java.security.AllPermission;
79 import java.security.CodeSource;
80 import java.security.PermissionCollection;
81 import java.security.Permissions;
82 import java.security.Policy;
83 import java.util.HashMap;
84 import java.util.Hashtable;
85 import java.util.Map;
86 import java.util.Vector;
87
88 import javax.swing.LookAndFeel;
89 import javax.swing.SwingUtilities;
90 import javax.swing.UIManager;
91
92 import groovy.lang.Binding;
93 import groovy.util.GroovyScriptEngine;
94 import netscape.javascript.JSObject;
95
96 /**
97  * Main class for Jalview Application <br>
98  * <br>
99  * start with: java -classpath "$PATH_TO_LIB$/*:$PATH_TO_CLASSES$" \
100  * jalview.bin.Jalview
101  * 
102  * or on Windows: java -classpath "$PATH_TO_LIB$/*;$PATH_TO_CLASSES$" \
103  * jalview.bin.Jalview jalview.bin.Jalview
104  * 
105  * (ensure -classpath arg is quoted to avoid shell expansion of '*' and do not
106  * embellish '*' to e.g. '*.jar')
107  * 
108  * @author $author$
109  * @version $Revision$
110  */
111 public class Jalview implements ApplicationSingletonI, JalviewJSApi
112 {
113
114   public static Jalview getInstance()
115   {
116     return (Jalview) ApplicationSingletonProvider
117             .getInstance(Jalview.class);
118   }
119
120   private Jalview()
121   {
122   }
123
124   static
125   {
126     Platform.getURLCommandArguments();
127   }
128
129   private boolean headless;
130
131   public static boolean isHeadlessMode()
132   {
133     return getInstance().headless;
134   }
135
136   private Desktop desktop;
137
138   private AlignFrame currentAlignFrame;
139
140   public boolean isJavaAppletTag;
141
142   public String appletResourcePath;
143
144   JalviewAppLoader appLoader;
145
146   protected JSFunctionExec jsFunctionExec;
147
148   private boolean noCalculation, noMenuBar, noStatus;
149
150   private boolean noAnnotation;
151
152   public static final String TERMINATOR_LINE = "Jalview argument parsing complete.";
153
154   public boolean getStartCalculations()
155   {
156     return !noCalculation;
157   }
158
159   public boolean getAllowMenuBar()
160   {
161     return !noMenuBar;
162   }
163
164   public boolean getShowStatus()
165   {
166     return !noStatus;
167   }
168
169   public boolean getShowAnnotation()
170   {
171     return !noAnnotation;
172   }
173
174   public static AlignFrame getCurrentAlignFrame()
175   {
176     return getInstance().currentAlignFrame;
177   }
178
179   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
180   {
181     getInstance().currentAlignFrame = currentAlignFrame;
182   }
183
184   static
185   {
186     if (!Platform.isJS())
187     /**
188      * Java only
189      * 
190      * @j2sIgnore
191      */
192     {
193       // grab all the rights we can for the JVM
194       Policy.setPolicy(new Policy()
195       {
196         @Override
197         public PermissionCollection getPermissions(CodeSource codesource)
198         {
199           Permissions perms = new Permissions();
200           perms.add(new AllPermission());
201           return (perms);
202         }
203
204         @Override
205         public void refresh()
206         {
207         }
208       });
209     }
210   }
211
212   /**
213    * keep track of feature fetching tasks.
214    * 
215    * @author JimP
216    * 
217    */
218   class FeatureFetcher
219   {
220     /*
221      * TODO: generalise to track all jalview events to orchestrate batch
222      * processing events.
223      */
224
225     private int queued = 0;
226
227     private int running = 0;
228
229     public FeatureFetcher()
230     {
231
232     }
233
234     public void addFetcher(final AlignFrame af,
235             final Vector<String> dasSources)
236     {
237       final long id = System.currentTimeMillis();
238       queued++;
239       final FeatureFetcher us = this;
240       new Thread(new Runnable()
241       {
242
243         @Override
244         public void run()
245         {
246           synchronized (us)
247           {
248             queued--;
249             running++;
250           }
251
252           af.setProgressBar(MessageManager
253                   .getString("status.das_features_being_retrived"), id);
254           af.featureSettings_actionPerformed(null);
255           af.setProgressBar(null, id);
256           synchronized (us)
257           {
258             running--;
259           }
260         }
261       }).start();
262     }
263
264     public synchronized boolean allFinished()
265     {
266       return queued == 0 && running == 0;
267     }
268
269   }
270
271   /**
272    * main class for Jalview application
273    * 
274    * @param args
275    *          open <em>filename</em>
276    */
277   public static void main(String[] args)
278   {
279     // Platform.startJavaLogging();
280     getInstance().doMain(args);
281   }
282
283
284   @SuppressWarnings("unused")
285   /**
286    * @param args
287    */
288   void doMain(String[] args)
289   {
290
291     boolean isJS = Platform.isJS();
292     if (isJS)
293     {
294       Platform.setAppClass(this);
295     }
296     else
297     {
298       System.setSecurityManager(null);
299     }
300
301     System.out
302             .println("Java version: " + System.getProperty("java.version"));
303     System.out.println(System.getProperty("os.arch") + " "
304             + System.getProperty("os.name") + " "
305             + System.getProperty("os.version"));
306
307     ArgsParser aparser = new ArgsParser(args);
308
309     String usrPropsFile = aparser.getValue(ArgsParser.PROPS);
310     Cache.loadProperties(usrPropsFile);
311
312     if (aparser.contains(ArgsParser.NODISPLAY)
313             || aparser.contains(ArgsParser.NOGUI)
314             || aparser.contains(ArgsParser.HEADLESS)
315             || "true".equals(System.getProperty("java.awt.headless")))
316     {
317       headless = true;
318       setSynchronous(true);
319     }
320
321     if (isJS)
322     {
323       isJavaAppletTag = aparser.isApplet();
324       if (isJavaAppletTag)
325       {
326         Preferences.setAppletDefaults();
327         Cache.loadProperties(usrPropsFile); // again, because we
328         // might be changing defaults here?
329       }
330       System.out.println(
331               "<Applet> found: " + aparser.getValue("Info.j2sAppletID"));
332       appletResourcePath = aparser.getValue("Info.resourcePath");
333     }
334     else
335     /**
336      * Java only
337      * 
338      * @j2sIgnore
339      */
340     {
341       if (usrPropsFile != null)
342       {
343         System.out.println(
344                 "CMD [-props " + usrPropsFile + "] executed successfully!");
345       }
346
347       if (aparser.contains("help") || aparser.contains("h"))
348       {
349         showUsage();
350         System.exit(0);
351       }
352
353       // anything else!
354
355       final String jabawsUrl = aparser.getValue(ArgsParser.JABAWS);
356       if (jabawsUrl != null)
357       {
358         try
359         {
360           Jws2Discoverer.getInstance().setPreferredUrl(jabawsUrl);
361           System.out.println(
362                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
363         } catch (MalformedURLException e)
364         {
365           System.err.println(
366                   "Invalid jabaws parameter: " + jabawsUrl + " ignored");
367         }
368       }
369
370     }
371     // check for property setting
372     String defs = aparser.getValue(ArgsParser.SETPROP);
373     while (defs != null)
374     {
375       int p = defs.indexOf('=');
376       if (p == -1)
377       {
378         System.err.println("Ignoring invalid setprop argument : " + defs);
379       }
380       else
381       {
382         System.out.println("Executing setprop argument: " + defs);
383         if (isJS)
384         {
385           Cache.setProperty(defs.substring(0, p), defs.substring(p + 1));
386         }
387       }
388       defs = aparser.getValue(ArgsParser.SETPROP);
389     }
390     System.setProperty("http.agent",
391             "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
392     try
393     {
394       Cache.initLogger();
395     } catch (NoClassDefFoundError error)
396     {
397       error.printStackTrace();
398       System.out.println("\nEssential logging libraries not found."
399               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview");
400       System.exit(0);
401     }
402
403     desktop = null;
404
405     try
406     {
407       if (!isJS && Platform.isWin())
408       {
409         UIManager.setLookAndFeel(
410                 headless ? "javax.swing.plaf.metal.MetalLookAndFeel"
411                         : UIManager.getSystemLookAndFeelClassName());
412         // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
413       }
414     } catch (Exception ex)
415     {
416       System.err.println("Unexpected Look and Feel Exception");
417       ex.printStackTrace();
418     }
419     if (Platform.isAMacAndNotJS())
420     {
421
422       LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager
423               .getLookAndFeel();
424       System.setProperty("com.apple.mrj.application.apple.menu.about.name",
425               "Jalview");
426       System.setProperty("apple.laf.useScreenMenuBar", "true");
427       if (lookAndFeel != null)
428       {
429         try
430         {
431           UIManager.setLookAndFeel(lookAndFeel);
432         } catch (Throwable e)
433         {
434           System.err.println(
435                   "Failed to set QuaQua look and feel: " + e.toString());
436         }
437       }
438       if (lookAndFeel == null
439               || !(lookAndFeel.getClass().isAssignableFrom(
440                       UIManager.getLookAndFeel().getClass()))
441               || !UIManager.getLookAndFeel().getClass().toString()
442                       .toLowerCase().contains("quaqua"))
443       {
444         try
445         {
446           System.err.println(
447                   "Quaqua LaF not available on this plaform. Using VAqua(4).\nSee https://issues.jalview.org/browse/JAL-2976");
448           UIManager.setLookAndFeel("org.violetlib.aqua.AquaLookAndFeel");
449         } catch (Throwable e)
450         {
451           System.err.println(
452                   "Failed to reset look and feel: " + e.toString());
453         }
454       }
455     }
456
457     /*
458      * configure 'full' SO model if preferences say to, 
459      * else use the default (SO Lite)
460      */
461     if (Cache.getDefault(Preferences.USE_FULL_SO, false))
462     {
463       SequenceOntologyFactory.setSequenceOntology(new SequenceOntology());
464     }
465
466     if (headless)
467     {
468       // If this is not tested, then
469
470       if (aparser.contains(ArgsParser.NOUSAGESTATS))
471       {
472         System.err.println("CMD [-nousagestats] executed successfully!");
473       }
474       if (aparser.contains(ArgsParser.NOQUESTIONNAIRE))
475       {
476         System.err.println("CMD [-noquestionnaire] executed successfully!");
477       }
478
479     }
480     else
481     {
482       desktop = Desktop.getInstance();
483       desktop.setInBatchMode(true); // indicate we are starting up
484       desktop.setVisible(true);
485
486       if (!isJS)
487       /**
488        * Java only
489        * 
490        * @j2sIgnore
491        */
492       {
493         desktop.startServiceDiscovery();
494         if (!aparser.contains(ArgsParser.NOUSAGESTATS))
495         {
496           startUsageStats(desktop);
497         }
498         else
499         {
500           System.err.println("CMD [-nousagestats] executed successfully!");
501         }
502
503         if (!aparser.contains(ArgsParser.NOQUESTIONNAIRE))
504         {
505           String url = aparser.getValue(ArgsParser.QUESTIONNAIRE);
506           if (url != null)
507           {
508             // Start the desktop questionnaire prompter with the specified
509             // questionnaire
510             Cache.log.debug("Starting questionnaire url at " + url);
511             desktop.checkForQuestionnaire(url);
512             System.out.println("CMD questionnaire[-" + url
513                     + "] executed successfully!");
514           }
515           else
516           {
517             if (Cache.getProperty(Preferences.NOQUESTIONNAIRES) == null)
518             {
519               // Start the desktop questionnaire prompter with the specified
520               // questionnaire
521               // String defurl =
522               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
523               // //
524               String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
525               Cache.log.debug(
526                       "Starting questionnaire with default url: " + defurl);
527               desktop.checkForQuestionnaire(defurl);
528             }
529           }
530         }
531         else
532         {
533           System.err
534                   .println("CMD [-noquestionnaire] executed successfully!");
535         }
536
537         if (!aparser.contains(ArgsParser.NONEWS))
538         {
539           desktop.checkForNews();
540         }
541
542         BioJsHTMLOutput.updateBioJS();
543       }
544     }
545
546     parseArguments(aparser, true);
547     System.err.println(TERMINATOR_LINE);
548   }
549
550   /**
551    * Allow an outside entity to initiate the second half of argument parsing
552    * (only).
553    * 
554    * @param args
555    * @return null is good
556    */
557   @Override
558   public Object parseArguments(String[] args)
559   {
560
561     try
562     {
563       ArgsParser aparser = new ArgsParser(args);
564       return parseArguments(aparser, false);
565     } catch (Throwable t)
566     {
567       return t;
568     }
569   }
570
571   /**
572    * 
573    * @param aparser
574    * @param isStartup
575    * @return
576    */
577   private Object parseArguments(ArgsParser aparser, boolean isStartup)
578   {
579     boolean isJS = Platform.isJS();
580
581     Desktop desktop = (headless ? null : Desktop.getInstance());
582     // script to execute after all loading is
583     // completed one way or another
584     // extract groovy argument and execute if necessary
585     String groovyscript = (isJS ? null
586             : aparser.getValue(ArgsParser.GROOVY, true));
587     String file = aparser.getValue(ArgsParser.OPEN, true);
588     // BH this here to allow split frame; not working as of 5/17/2019
589     String file2 = aparser.getValue(ArgsParser.OPEN2, true);
590     String fileFormat = (isJavaAppletTag
591             ? aparser.getAppletValue("format", null)
592             : null);
593     FileFormatI format = null;
594     DataSourceType protocol = null;
595
596     if (file == null && desktop == null)
597     {
598       System.out.println("No files to open!");
599       System.exit(1);
600     }
601     boolean haveImport = checkStartVamas(aparser);
602     // Finally, deal with the remaining input data.
603     long progress = -1;
604     if (file == null && isJavaAppletTag)
605     {
606       // Maybe the sequences are added as parameters
607       StringBuffer data = new StringBuffer("PASTE");
608       int i = 1;
609       while ((file = aparser.getAppletValue("sequence" + i, null)) != null)
610       {
611         data.append(file.toString() + "\n");
612         i++;
613       }
614       if (data.length() > 5)
615       {
616         file = data.toString();
617       }
618     }
619
620     String data;
621
622     if (file != null)
623     {
624
625       if (!headless)
626       {
627         desktop.setProgressBar(
628                 MessageManager
629                         .getString("status.processing_commandline_args"),
630                 progress = System.currentTimeMillis());
631       }
632
633       if (!isJS)
634       /**
635        * ignore in JavaScript -- can't just check file existence - could load
636        * it?
637        * 
638        * @j2sIgnore
639        */
640       {
641         if (!file.startsWith("http://") && !file.startsWith("https://"))
642         // BH 2019 added https check for Java
643         {
644           if (!(new File(file)).exists())
645           {
646             System.out.println("Can't find " + file);
647             if (headless)
648             {
649               System.exit(1);
650             }
651           }
652         }
653       }
654
655       protocol = AppletFormatAdapter.checkProtocol(file);
656
657       try
658       {
659         format = (isJavaAppletTag && fileFormat != null
660                 ? FileFormats.getInstance().forName(fileFormat)
661                 : null);
662         if (format == null)
663         {
664           format = new IdentifyFile().identify(file, protocol);
665         }
666       } catch (FileFormatException e1)
667       {
668         // TODO ?
669       }
670
671       if (aparser.contains(ArgsParser.NOMENUBAR))
672       {
673         noMenuBar = true;
674         System.out.println("CMD [nomenu] executed successfully!");
675       }
676
677       if (aparser.contains(ArgsParser.NOSTATUS))
678       {
679         noStatus = true;
680         System.out.println("CMD [nostatus] executed successfully!");
681       }
682
683       if (aparser.contains(ArgsParser.NOANNOTATION)
684               || aparser.contains(ArgsParser.NOANNOTATION2))
685       {
686         noAnnotation = true;
687         System.out.println("CMD no-annotation executed successfully!");
688       }
689       if (aparser.contains(ArgsParser.NOCALCULATION))
690       {
691         noCalculation = true;
692         System.out.println("CMD [nocalculation] executed successfully!");
693       }
694
695       AlignFrame af = new FileLoader(!headless).loadFileWaitTillLoaded(file,
696               protocol, format);
697       if (af == null)
698       {
699         System.out.println("error");
700       }
701       else
702       {
703         System.out
704                 .println("CMD [-open " + file + "] executed successfully!");
705         if (file2 != null)
706         {
707           protocol = AppletFormatAdapter.checkProtocol(file2);
708           try
709           {
710             format = new IdentifyFile().identify(file2, protocol);
711           } catch (FileFormatException e1)
712           {
713             // TODO ?
714           }
715           AlignFrame af2 = new FileLoader(!headless)
716                   .loadFileWaitTillLoaded(file2, protocol, format);
717           if (af2 == null)
718           {
719             System.out.println("error");
720           }
721           else
722           {
723             AlignViewport.openLinkedAlignmentAs(af,
724                     af.getViewport().getAlignment(),
725                     af2.getViewport().getAlignment(), "",
726                     AlignViewport.SPLIT_FRAME);
727             System.out.println(
728                     "CMD [-open2 " + file2 + "] executed successfully!");
729           }
730         }
731
732         setCurrentAlignFrame(af);
733
734         // TODO: file2 How to implement file2 for the applet spit screen?
735
736         data = aparser.getValue(ArgsParser.COLOUR, true);
737         if (data != null)
738         {
739           data.replaceAll("%20", " ");
740
741           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
742                   af.getViewport(), af.getViewport().getAlignment(), data);
743
744           if (cs != null)
745           {
746             System.out.println(
747                     "CMD [-color " + data + "] executed successfully!");
748           }
749           af.changeColour(cs);
750         }
751
752         // Must maintain ability to use the groups flag
753         data = aparser.getValue(ArgsParser.GROUPS, true);
754         if (data != null)
755         {
756           af.parseFeaturesFile(data,
757                   AppletFormatAdapter.checkProtocol(data));
758           // System.out.println("Added " + data);
759           System.out.println(
760                   "CMD groups[-" + data + "]  executed successfully!");
761         }
762         data = aparser.getValue(ArgsParser.FEATURES, true);
763         if (data != null)
764         {
765           af.parseFeaturesFile(data,
766                   AppletFormatAdapter.checkProtocol(data));
767           // System.out.println("Added " + data);
768           System.out.println(
769                   "CMD [-features " + data + "]  executed successfully!");
770         }
771
772         data = aparser.getValue(ArgsParser.ANNOTATIONS, true);
773         if (data != null)
774         {
775           af.loadJalviewDataFile(data, null, null, null);
776           // System.out.println("Added " + data);
777           System.out.println(
778                   "CMD [-annotations " + data + "] executed successfully!");
779         }
780
781         if (aparser.contains(ArgsParser.SHOWOVERVIEW))
782         {
783           af.overviewMenuItem_actionPerformed(null);
784           System.out.println("CMD [showoverview] executed successfully!");
785         }
786
787         // set or clear the sortbytree flag.
788         if (aparser.contains(ArgsParser.SORTBYTREE))
789         {
790           af.getViewport().setSortByTree(true);
791           if (af.getViewport().getSortByTree())
792           {
793             System.out.println("CMD [-sortbytree] executed successfully!");
794           }
795         }
796
797         boolean doUpdateAnnotation = false;
798
799         /**
800          * we do this earlier in JalviewJS because of a complication with
801          * SHOWOVERVIEW
802          * 
803          * For now, just fixing this in JalviewJS.
804          *
805          * 
806          * @j2sIgnore
807          * 
808          */
809         {
810           if (aparser.contains(ArgsParser.NOANNOTATION)
811                   || aparser.contains(ArgsParser.NOANNOTATION2))
812           {
813             af.getViewport().setShowAnnotation(false);
814             if (!af.getViewport().isShowAnnotation())
815             {
816               doUpdateAnnotation = true;
817               System.out
818                       .println("CMD no-annotation executed successfully!");
819             }
820           }
821         }
822         if (aparser.contains(ArgsParser.NOSORTBYTREE))
823         {
824           af.getViewport().setSortByTree(false);
825           if (!af.getViewport().getSortByTree())
826           {
827             doUpdateAnnotation = true;
828             System.out
829                     .println("CMD [-nosortbytree] executed successfully!");
830           }
831         }
832         if (doUpdateAnnotation)
833         { // BH 2019.07.24
834           af.setMenusForViewport();
835           af.alignPanel.updateLayout();
836         }
837         data = aparser.getValue(ArgsParser.TREE, true);
838         if (data != null)
839         {
840           try
841           {
842             System.out.println(
843                     "CMD [-tree " + data + "] executed successfully!");
844             NewickFile nf = new NewickFile(data,
845                     AppletFormatAdapter.checkProtocol(data));
846             af.getViewport()
847                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
848           } catch (IOException ex)
849           {
850             System.err.println("Couldn't add tree " + data);
851             ex.printStackTrace(System.err);
852           }
853         }
854         // TODO - load PDB structure(s) to alignment JAL-629
855         // (associate with identical sequence in alignment, or a specified
856         // sequence)
857         if (isJavaAppletTag)
858         {
859           loadAppletParams(aparser, af);
860         }
861         else if (!isJS)
862         /**
863          * Java only
864          * 
865          * @j2sIgnore
866          */
867         {
868           if (groovyscript != null)
869           {
870             // Execute the groovy script after we've done all the rendering
871             // stuff
872             // and before any images or figures are generated.
873             System.out.println("Executing script " + groovyscript);
874             executeGroovyScript(groovyscript, af);
875             System.out.println("CMD groovy[" + groovyscript
876                     + "] executed successfully!");
877             groovyscript = null;
878           }
879         }
880         createOutputFiles(aparser, af, format);
881         while (aparser.getSize() > 0)
882         {
883           System.out.println("Unknown arg: " + aparser.nextValue());
884         }
885       }
886     }
887     AlignFrame startUpAlframe = null;
888     // We'll only open the default file if the desktop is visible.
889     // And the user
890     // ////////////////////
891
892     if (!isJS && !headless && file == null && !haveImport
893             && jalview.bin.Cache.getDefault("SHOW_STARTUP_FILE", true))
894     /**
895      * Java only
896      * 
897      * @j2sIgnore
898      */
899     {
900       file = jalview.bin.Cache.getDefault("STARTUP_FILE",
901               jalview.bin.Cache.getDefault("www.jalview.org",
902                       "http://www.jalview.org")
903                       + "/examples/exampleFile_2_7.jar");
904       if (file.equals(
905               "http://www.jalview.org/examples/exampleFile_2_3.jar"))
906       {
907         // hardwire upgrade of the startup file
908         file.replace("_2_3.jar", "_2_7.jar");
909         // and remove the stale setting
910         jalview.bin.Cache.removeProperty("STARTUP_FILE");
911       }
912
913       protocol = DataSourceType.FILE;
914
915       if (file.indexOf("http:") > -1)
916       {
917         protocol = DataSourceType.URL;
918       }
919
920       if (file.endsWith(".jar"))
921       {
922         format = FileFormat.Jalview;
923       }
924       else
925       {
926         try
927         {
928           format = new IdentifyFile().identify(file, protocol);
929         } catch (FileFormatException e)
930         {
931           // TODO what?
932         }
933       }
934
935       startUpAlframe = new FileLoader(!headless)
936               .loadFileWaitTillLoaded(file, protocol, format);
937       // extract groovy arguments before anything else.
938     }
939
940     // Once all other stuff is done, execute any groovy scripts (in order)
941     if (groovyscript != null)
942     {
943       if (Cache.groovyJarsPresent())
944       {
945         System.out.println("Executing script " + groovyscript);
946         executeGroovyScript(groovyscript, startUpAlframe);
947       }
948       else
949       {
950         System.err.println(
951                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
952                         + groovyscript);
953       }
954     }
955     // and finally, turn off batch mode indicator - if the desktop still exists
956     if (desktop != null)
957     {
958       if (progress != -1)
959       {
960         desktop.setProgressBar(null, progress);
961       }
962       desktop.setInBatchMode(false);
963     }
964
965     return null;
966   }
967
968   private boolean checkStartVamas(ArgsParser aparser)
969   {
970     String vamsasImport = aparser.getValue(ArgsParser.VDOC);
971     String vamsasSession = aparser.getValue(ArgsParser.VSESS);
972     if (vamsasImport == null && vamsasSession == null)
973     {
974       return false;
975     }
976     if (desktop == null || headless)
977     {
978       System.out.println(
979               "Headless vamsas sessions not yet supported. Sorry.");
980       System.exit(1);
981     }
982     boolean haveImport = (vamsasImport != null);
983     if (haveImport)
984     {
985       // if we have a file, start a new session and import it.
986       boolean inSession = false;
987       try
988       {
989         DataSourceType viprotocol = AppletFormatAdapter
990                 .checkProtocol(vamsasImport);
991         if (viprotocol == DataSourceType.FILE)
992         {
993           inSession = desktop.vamsasImport(new File(vamsasImport));
994         }
995         else if (viprotocol == DataSourceType.URL)
996         {
997           inSession = desktop.vamsasImport(new URL(vamsasImport));
998         }
999
1000       } catch (Exception e)
1001       {
1002         System.err.println("Exeption when importing " + vamsasImport
1003                 + " as a vamsas document.");
1004         e.printStackTrace();
1005       }
1006       if (!inSession)
1007       {
1008         System.err.println("Failed to import " + vamsasImport
1009                 + " as a vamsas document.");
1010       }
1011       else
1012       {
1013         System.out.println("Imported Successfully into new session "
1014                 + desktop.getVamsasApplication().getCurrentSession());
1015       }
1016     }
1017     if (vamsasSession != null)
1018     {
1019       if (vamsasImport != null)
1020       {
1021         // close the newly imported session and import the Jalview specific
1022         // remnants into the new session later on.
1023         desktop.vamsasStop_actionPerformed(null);
1024       }
1025       // now join the new session
1026       try
1027       {
1028         if (desktop.joinVamsasSession(vamsasSession))
1029         {
1030           System.out.println(
1031                   "Successfully joined vamsas session " + vamsasSession);
1032         }
1033         else
1034         {
1035           System.err.println("WARNING: Failed to join vamsas session "
1036                   + vamsasSession);
1037         }
1038       } catch (Exception e)
1039       {
1040         System.err.println(
1041                 "ERROR: Failed to join vamsas session " + vamsasSession);
1042         e.printStackTrace();
1043       }
1044       if (vamsasImport != null)
1045       {
1046         // the Jalview specific remnants can now be imported into the new
1047         // session at the user's leisure.
1048         Cache.log.info(
1049                 "Skipping Push for import of data into existing vamsas session.");
1050         // TODO:
1051         // enable
1052         // this
1053         // when
1054         // debugged
1055         // desktop.getVamsasApplication().push_update();
1056       }
1057     }
1058     return haveImport;
1059   }
1060
1061   /**
1062    * Writes an output file for each format (if any) specified in the
1063    * command-line arguments. Supported formats are currently
1064    * <ul>
1065    * <li>png</li>
1066    * <li>svg</li>
1067    * <li>html</li>
1068    * <li>biojsmsa</li>
1069    * <li>imgMap</li>
1070    * <li>eps</li>
1071    * </ul>
1072    * A format parameter should be followed by a parameter specifying the output
1073    * file name. {@code imgMap} parameters should follow those for the
1074    * corresponding alignment image output.
1075    * 
1076    * @param aparser
1077    * @param af
1078    * @param format
1079    */
1080   private void createOutputFiles(ArgsParser aparser, AlignFrame af,
1081           FileFormatI format)
1082   {
1083     String imageName = "unnamed.png";
1084     while (aparser.getSize() > 1)
1085     {
1086       String outputFormat = aparser.nextValue();
1087       String file = aparser.nextValue();
1088       // System.out.println("format " + outputFormat);
1089
1090       if (outputFormat.equalsIgnoreCase("png"))
1091       {
1092         af.createPNG(new File(file));
1093         imageName = (new File(file)).getName();
1094         System.out.println("Creating PNG image: " + file);
1095         continue;
1096       }
1097       else if (outputFormat.equalsIgnoreCase("svg"))
1098       {
1099         File imageFile = new File(file);
1100         imageName = imageFile.getName();
1101         af.createSVG(imageFile);
1102         System.out.println("Creating SVG image: " + file);
1103         continue;
1104       }
1105       else if (outputFormat.equalsIgnoreCase("html"))
1106       {
1107         File imageFile = new File(file);
1108         imageName = imageFile.getName();
1109         HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
1110         htmlSVG.exportHTML(file);
1111
1112         System.out.println("Creating HTML image: " + file);
1113         continue;
1114       }
1115       else if (outputFormat.equalsIgnoreCase("biojsmsa"))
1116       {
1117         if (file == null)
1118         {
1119           System.err.println("The output html file must not be null");
1120           return;
1121         }
1122         try
1123         {
1124           BioJsHTMLOutput.refreshVersionInfo(
1125                   BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
1126         } catch (URISyntaxException e)
1127         {
1128           e.printStackTrace();
1129         }
1130         BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
1131         bjs.exportHTML(file);
1132         System.out.println("Creating BioJS MSA Viwer HTML file: " + file);
1133         continue;
1134       }
1135       else if (outputFormat.equalsIgnoreCase("imgMap"))
1136       {
1137         af.createImageMap(new File(file), imageName);
1138         System.out.println("Creating image map: " + file);
1139         continue;
1140       }
1141       else if (outputFormat.equalsIgnoreCase("eps"))
1142       {
1143         File outputFile = new File(file);
1144         System.out.println(
1145                 "Creating EPS file: " + outputFile.getAbsolutePath());
1146         af.createEPS(outputFile);
1147         continue;
1148       }
1149
1150       af.saveAlignment(file, format);
1151       if (af.isSaveAlignmentSuccessful())
1152       {
1153         System.out.println(
1154                 "Written alignment in " + format + " format to " + file);
1155       }
1156       else
1157       {
1158         System.out.println("Error writing file " + file + " in " + format
1159                 + " format!!");
1160       }
1161
1162     }
1163   }
1164
1165   private static void showUsage()
1166   {
1167     System.out.println(
1168             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1169                     + "-nodisplay\tRun Jalview without User Interface.\n"
1170                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1171                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1172                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1173                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1174                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1175                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1176                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1177                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1178                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1179                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1180                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1181                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1182                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1183                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1184                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1185                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1186                     + "-html FILE\tCreate HTML file from alignment.\n"
1187                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1188                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1189                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1190                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1191                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1192                     + "-nonews\tTurn off check for Jalview news.\n"
1193                     + "-nousagestats\tTurn off google analytics tracking for this session.\n"
1194                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1195                     // +
1196                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1197                     // after all other properties files have been read\n\t
1198                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1199                     // passed in correctly)"
1200                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1201                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1202                     // +
1203                     // "-vdoc vamsas-document\tImport vamsas document into new
1204                     // session or join existing session with same URN\n"
1205                     // + "-vses vamsas-session\tJoin session with given URN\n"
1206                     + "-groovy FILE\tExecute groovy script in FILE, after all other arguments have been processed (if FILE is the text 'STDIN' then the file will be read from STDIN)\n"
1207                     + "\n~Read documentation in Application or visit http://www.jalview.org for description of Features and Annotations file~\n\n");
1208   }
1209
1210   private static void startUsageStats(final Desktop desktop)
1211   {
1212     /**
1213      * start a User Config prompt asking if we can log usage statistics.
1214      */
1215     PromptUserConfig prompter = new PromptUserConfig(
1216             Desktop.getDesktopPane(), "USAGESTATS",
1217             "Jalview Usage Statistics",
1218             "Do you want to help make Jalview better by enabling "
1219                     + "the collection of usage statistics with Google Analytics ?"
1220                     + "\n\n(you can enable or disable usage tracking in the preferences)",
1221             new Runnable()
1222             {
1223               @Override
1224               public void run()
1225               {
1226                 Cache.log.debug(
1227                         "Initialising googletracker for usage stats.");
1228                 Cache.initGoogleTracker();
1229                 Cache.log.debug("Tracking enabled.");
1230               }
1231             }, new Runnable()
1232             {
1233               @Override
1234               public void run()
1235               {
1236                 Cache.log.debug("Not enabling Google Tracking.");
1237               }
1238             }, null, true);
1239     desktop.addDialogThread(prompter);
1240   }
1241
1242   /**
1243    * Locate the given string as a file and pass it to the groovy interpreter.
1244    * 
1245    * @param groovyscript
1246    *          the script to execute
1247    * @param jalviewContext
1248    *          the Jalview Desktop object passed in to the groovy binding as the
1249    *          'Jalview' object.
1250    */
1251   private void executeGroovyScript(String groovyscript, AlignFrame af)
1252   {
1253     /**
1254      * for scripts contained in files
1255      */
1256     File tfile = null;
1257     /**
1258      * script's URI
1259      */
1260     URL sfile = null;
1261     if (groovyscript.trim().equals("STDIN"))
1262     {
1263       // read from stdin into a tempfile and execute it
1264       try
1265       {
1266         tfile = File.createTempFile("jalview", "groovy");
1267         PrintWriter outfile = new PrintWriter(
1268                 new OutputStreamWriter(new FileOutputStream(tfile)));
1269         BufferedReader br = new BufferedReader(
1270                 new InputStreamReader(System.in));
1271         String line = null;
1272         while ((line = br.readLine()) != null)
1273         {
1274           outfile.write(line + "\n");
1275         }
1276         br.close();
1277         outfile.flush();
1278         outfile.close();
1279
1280       } catch (Exception ex)
1281       {
1282         System.err.println("Failed to read from STDIN into tempfile "
1283                 + ((tfile == null) ? "(tempfile wasn't created)"
1284                         : tfile.toString()));
1285         ex.printStackTrace();
1286         return;
1287       }
1288       try
1289       {
1290         sfile = tfile.toURI().toURL();
1291       } catch (Exception x)
1292       {
1293         System.err.println(
1294                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1295                         + tfile.toURI());
1296         x.printStackTrace();
1297         return;
1298       }
1299     }
1300     else
1301     {
1302       try
1303       {
1304         sfile = new URI(groovyscript).toURL();
1305       } catch (Exception x)
1306       {
1307         tfile = new File(groovyscript);
1308         if (!tfile.exists())
1309         {
1310           System.err.println("File '" + groovyscript + "' does not exist.");
1311           return;
1312         }
1313         if (!tfile.canRead())
1314         {
1315           System.err.println("File '" + groovyscript + "' cannot be read.");
1316           return;
1317         }
1318         if (tfile.length() < 1)
1319         {
1320           System.err.println("File '" + groovyscript + "' is empty.");
1321           return;
1322         }
1323         try
1324         {
1325           sfile = tfile.getAbsoluteFile().toURI().toURL();
1326         } catch (Exception ex)
1327         {
1328           System.err.println("Failed to create a file URL for "
1329                   + tfile.getAbsoluteFile());
1330           return;
1331         }
1332       }
1333     }
1334     try
1335     {
1336       Map<String, Object> vbinding = new HashMap<>();
1337       vbinding.put("Jalview", this);
1338       if (af != null)
1339       {
1340         vbinding.put("currentAlFrame", af);
1341       }
1342       Binding gbinding = new Binding(vbinding);
1343       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1344       gse.run(sfile.toString(), gbinding);
1345       if ("STDIN".equals(groovyscript))
1346       {
1347         // delete temp file that we made -
1348         // only if it was successfully executed
1349         tfile.delete();
1350       }
1351     } catch (Exception e)
1352     {
1353       System.err.println("Exception Whilst trying to execute file " + sfile
1354               + " as a groovy script.");
1355       e.printStackTrace(System.err);
1356
1357     }
1358   }
1359
1360   public AlignFrame[] getAlignFrames()
1361   {
1362     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1363             : Desktop.getAlignFrames();
1364
1365   }
1366
1367   /**
1368    * Quit method delegates to Desktop.quit - unless running in headless mode
1369    * when it just ends the JVM
1370    */
1371   public void quit()
1372   {
1373     if (jsFunctionExec != null)
1374     {
1375       jsFunctionExec.tidyUp();
1376       jsFunctionExec = null;
1377     }
1378
1379     if (desktop != null)
1380     {
1381       desktop.quit();
1382     }
1383     else
1384     {
1385       System.exit(0);
1386     }
1387   }
1388
1389   /**
1390    * Get the SwingJS applet ID and combine that with the frameType
1391    * 
1392    * @param frameType
1393    *          "alignment", "desktop", etc., or null
1394    * @return
1395    */
1396   public static String getAppID(String frameType)
1397   {
1398     String id = Cache.getProperty("Info.j2sAppletID");
1399     if (id == null)
1400     {
1401       id = "jalview";
1402     }
1403     return id + (frameType == null ? "" : "-" + frameType);
1404   }
1405
1406   /**
1407    * Handle all JalviewLite applet parameters
1408    * 
1409    * @param aparser
1410    * @param af
1411    */
1412   private void loadAppletParams(ArgsParser aparser, AlignFrame af)
1413   {
1414     JalviewApp app = new JalviewApp()
1415     {
1416
1417       // TODO BH 2019
1418       //
1419       // These are methods that are in JalviewLite that various classes call
1420       // but are not in JalviewLiteJsApi. Or, even if they are, other classes
1421       // call
1422       // them to JalviewLite directly. Some may not be necessary, but they have
1423       // to
1424       // be at least mentioned here, or the classes calling them should
1425       // reference
1426       // JalviewLite itself.
1427
1428       private boolean alignPDBStructures; // From JalviewLite; not implemented
1429
1430       private Hashtable<String, Hashtable<String, String[]>> jsmessages;
1431
1432       private Hashtable<String, int[]> jshashes;
1433
1434       @Override
1435       public String getParameter(String name)
1436       {
1437         return aparser.getAppletValue(name, null);
1438       }
1439
1440       @Override
1441       public boolean getDefaultParameter(String name, boolean def)
1442       {
1443         String stn;
1444         return ((stn = getParameter(name)) == null ? def
1445                 : "true".equalsIgnoreCase(stn));
1446       }
1447
1448       /**
1449        * Get the applet-like document base even though this is an application.
1450        */
1451       @Override
1452       public URL getDocumentBase()
1453       {
1454         return Platform.getDocumentBase();
1455       }
1456
1457       /**
1458        * Get the applet-like code base even though this is an application.
1459        */
1460       @Override
1461       public URL getCodeBase()
1462       {
1463         return Platform.getCodeBase();
1464       }
1465
1466       @Override
1467       public AlignViewportI getViewport()
1468       {
1469         return af.getViewport();
1470       }
1471
1472       /**
1473        * features
1474        * 
1475        */
1476       @Override
1477       public boolean parseFeaturesFile(String filename,
1478               DataSourceType protocol)
1479       {
1480         return af.parseFeaturesFile(filename, protocol);
1481       }
1482
1483       /**
1484        * scorefile
1485        * 
1486        */
1487       @Override
1488       public boolean loadScoreFile(String sScoreFile) throws IOException
1489       {
1490         af.loadJalviewDataFile(sScoreFile, null, null, null);
1491         return true;
1492       }
1493
1494       /**
1495        * annotations, jpredfile, jnetfile
1496        * 
1497        */
1498       @Override
1499       public void updateForAnnotations()
1500       {
1501         af.updateForAnnotations();
1502       }
1503
1504       @Override
1505       public void loadTree(NewickFile fin, String treeFile)
1506               throws IOException
1507       {
1508         // n/a -- already done by standard Jalview command line processing
1509       }
1510
1511       @Override
1512       public void setAlignPdbStructures(boolean defaultParameter)
1513       {
1514         alignPDBStructures = true;
1515       }
1516
1517       @Override
1518       public void newStructureView(PDBEntry pdb, SequenceI[] seqs,
1519               String[] chains, DataSourceType protocol)
1520       {
1521         StructureViewer.launchStructureViewer(af.alignPanel, pdb, seqs);
1522       }
1523
1524       @Override
1525       public void setFeatureGroupState(String[] groups, boolean state)
1526       {
1527         af.setFeatureGroupState(groups, state);
1528       }
1529
1530       @Override
1531       public void alignedStructureView(PDBEntry[] pdb, SequenceI[][] seqs,
1532               String[][] chains, String[] protocols)
1533       {
1534         System.err.println(
1535                 "Jalview applet interface alignedStructureView not implemented");
1536       }
1537
1538       @Override
1539       public void newFeatureSettings()
1540       {
1541         System.err.println(
1542                 "Jalview applet interface newFeatureSettings not implemented");
1543       }
1544
1545       private Vector<Runnable> jsExecQueue;
1546
1547       @Override
1548       public Vector<Runnable> getJsExecQueue(JSFunctionExec exec)
1549       {
1550         jsFunctionExec = exec;
1551         return (jsExecQueue == null ? (jsExecQueue = new Vector<>())
1552                 : jsExecQueue);
1553       }
1554
1555       @Override
1556       public AppletContext getAppletContext()
1557       {
1558         // TODO Auto-generated method stub
1559         return null;
1560       }
1561
1562       @Override
1563       public boolean isJsfallbackEnabled()
1564       {
1565         // TODO Auto-generated method stub
1566         return false;
1567       }
1568
1569       @Override
1570       public JSObject getJSObject()
1571       {
1572         // TODO Auto-generated method stub
1573         return null;
1574       }
1575
1576       @Override
1577       public StructureSelectionManagerProvider getStructureSelectionManagerProvider()
1578       {
1579         // TODO Q: what exactly is this? BH
1580         return null;
1581       }
1582
1583       @Override
1584       public void updateColoursFromMouseOver(Object source,
1585               MouseOverStructureListener mouseOverStructureListener)
1586       {
1587         // TODO Auto-generated method stub
1588
1589       }
1590
1591       @Override
1592       public Object[] getSelectionForListener(SequenceGroup seqsel,
1593               ColumnSelection colsel, HiddenColumns hidden,
1594               SelectionSource source, Object alignFrame)
1595       {
1596         return appLoader.getSelectionForListener(getCurrentAlignFrame(),
1597                 seqsel, colsel, hidden, source, alignFrame);
1598       }
1599
1600       @Override
1601       public String arrayToSeparatorList(String[] array)
1602       {
1603         return appLoader.arrayToSeparatorList(array);
1604       }
1605
1606       @Override
1607       public Hashtable<String, int[]> getJSHashes()
1608       {
1609         return (jshashes == null ? (jshashes = new Hashtable<>())
1610                 : jshashes);
1611       }
1612
1613       @Override
1614       public Hashtable<String, Hashtable<String, String[]>> getJSMessages()
1615       {
1616         return (jsmessages == null ? (jsmessages = new Hashtable<>())
1617                 : jsmessages);
1618       }
1619
1620       @Override
1621       public Object getFrameForSource(VamsasSource source)
1622       {
1623         if (source != null)
1624         {
1625           AlignFrame af;
1626           if (source instanceof jalview.gui.AlignViewport
1627                   && source == (af = getCurrentAlignFrame()).getViewport())
1628           {
1629             // should be valid if it just generated an event!
1630             return af;
1631           }
1632           // TODO: ensure that if '_af' is specified along with a handler
1633           // function, then only events from that alignFrame are sent to that
1634           // function
1635         }
1636         return null;
1637       }
1638
1639       @Override
1640       public FeatureRenderer getNewFeatureRenderer(AlignViewportI vp)
1641       {
1642         return new jalview.gui.FeatureRenderer((AlignmentPanel) vp);
1643       }
1644
1645     };
1646
1647     appLoader = new JalviewAppLoader(true);
1648     appLoader.load(app);
1649   }
1650
1651   /**
1652    * 
1653    * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences()
1654    */
1655   @Override
1656   public String getSelectedSequences()
1657   {
1658     return getSelectedSequencesFrom(getCurrentAlignFrame());
1659   }
1660
1661   /**
1662    * 
1663    * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences(java.lang.String)
1664    */
1665   @Override
1666   public String getSelectedSequences(String sep)
1667   {
1668     return getSelectedSequencesFrom(getCurrentAlignFrame(), sep);
1669   }
1670
1671   /**
1672    * 
1673    * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
1674    *      .AlignFrame)
1675    */
1676   @Override
1677   public String getSelectedSequencesFrom(AlignFrameI alf)
1678   {
1679     if (alf == null)
1680     {
1681       alf = getCurrentAlignFrame();
1682     }
1683     return getSelectedSequencesFrom(alf, null);
1684   }
1685
1686   /**
1687    * 
1688    * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
1689    *      .AlignFrame, java.lang.String)
1690    */
1691   @Override
1692   public String getSelectedSequencesFrom(AlignFrameI alf, String sep)
1693   {
1694     if (alf == null)
1695     {
1696       alf = getCurrentAlignFrame();
1697     }
1698     return appLoader.getSelectedSequencesFrom(alf, sep);
1699   }
1700
1701   /**
1702    * 
1703    * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
1704    *      .AlignFrame, java.lang.String)
1705    */
1706   @Override
1707   public void highlight(String sequenceId, String position,
1708           String alignedPosition)
1709   {
1710     highlightIn(null, sequenceId, position,
1711             alignedPosition);
1712   }
1713
1714   @Override
1715   public void highlightIn(AlignFrameI alf, String sequenceId,
1716           String position, String alignedPosition)
1717   {
1718     if (alf == null)
1719     {
1720       alf = getCurrentAlignFrame();
1721     }
1722     appLoader.highlightIn(alf, sequenceId, position, alignedPosition);
1723   }
1724
1725   @Override
1726   public void select(String sequenceIds, String columns)
1727   {
1728     selectIn(getCurrentAlignFrame(), sequenceIds, columns, null);
1729   }
1730
1731   @Override
1732   public void select(String sequenceIds, String columns, String sep)
1733   {
1734     selectIn(null, sequenceIds, columns, sep);
1735   }
1736
1737   @Override
1738   public void selectIn(AlignFrameI alf, String sequenceIds, String columns)
1739   {
1740     selectIn(alf, sequenceIds, columns, null);
1741   }
1742
1743   @Override
1744   public void selectIn(AlignFrameI alf, String sequenceIds, String columns,
1745           String sep)
1746   {
1747     if (alf == null)
1748     {
1749       alf = getCurrentAlignFrame();
1750     }
1751     appLoader.selectIn(alf, sequenceIds, columns, sep);
1752   }
1753
1754   @Override
1755   public String getSelectedSequencesAsAlignment(String format,
1756           String suffix)
1757   {
1758     return getSelectedSequencesAsAlignmentFrom(null,
1759             format, suffix);
1760   }
1761
1762   @Override
1763   public String getSelectedSequencesAsAlignmentFrom(AlignFrameI alf,
1764           String format, String sep)
1765   {
1766     if (alf == null)
1767     {
1768       alf = getCurrentAlignFrame();
1769     }
1770     return appLoader.getSelectedSequencesAsAlignmentFrom(alf, format, sep);
1771   }
1772
1773   @Override
1774   public String getAlignmentOrder()
1775   {
1776     return getAlignmentFrom(getCurrentAlignFrame(), null);
1777   }
1778
1779   @Override
1780   public String getAlignmentOrderFrom(AlignFrameI alf)
1781   {
1782     return getAlignmentFrom(alf, null);
1783   }
1784
1785   @Override
1786   public String getAlignmentOrderFrom(AlignFrameI alf, String sep)
1787   {
1788     if (alf == null)
1789     {
1790       alf = getCurrentAlignFrame();
1791     }
1792     return appLoader.getAlignmentOrderFrom(alf, sep);
1793   }
1794
1795   @Override
1796   public String orderBy(String order, String undoName)
1797   {
1798     return orderBy(order, undoName, null);
1799   }
1800
1801   @Override
1802   public String orderBy(String order, String undoName, String sep)
1803   {
1804     return orderAlignmentBy(getCurrentAlignFrame(), order, undoName, sep);
1805   }
1806
1807   @Override
1808   public String orderAlignmentBy(AlignFrameI alf, String order,
1809           String undoName, String sep)
1810   {
1811     if (alf == null)
1812     {
1813       alf = getCurrentAlignFrame();
1814     }
1815     return appLoader.orderAlignmentBy(alf, order, undoName, sep);
1816   }
1817
1818   @Override
1819   public String getAlignment(String format)
1820   {
1821     return getAlignmentFrom(null, format, null);
1822   }
1823
1824   @Override
1825   public String getAlignmentFrom(AlignFrameI alf, String format)
1826   {
1827     return getAlignmentFrom(alf, format, null);
1828   }
1829
1830   @Override
1831   public String getAlignment(String format, String suffix)
1832   {
1833     return getAlignmentFrom(getCurrentAlignFrame(), format, suffix);
1834   }
1835
1836   @Override
1837   public String getAlignmentFrom(AlignFrameI alf, String format,
1838           String suffix)
1839   {
1840     return appLoader.getAlignmentFrom(alf, format, suffix);
1841   }
1842
1843   @Override
1844   public void loadAnnotation(String annotation)
1845   {
1846     loadAnnotationFrom(getCurrentAlignFrame(), annotation);
1847   }
1848
1849   @Override
1850   public void loadAnnotationFrom(AlignFrameI alf, String annotation)
1851   {
1852     if (alf == null)
1853     {
1854       alf = getCurrentAlignFrame();
1855     }
1856     appLoader.loadAnnotationFrom(alf, annotation);
1857   }
1858
1859   @Override
1860   public void loadFeatures(String features, boolean autoenabledisplay)
1861   {
1862     loadFeaturesFrom(currentAlignFrame, features, autoenabledisplay);
1863   }
1864
1865   @Override
1866   public boolean loadFeaturesFrom(AlignFrameI alf, String features,
1867           boolean autoenabledisplay)
1868   {
1869     if (alf == null)
1870     {
1871       alf = getCurrentAlignFrame();
1872     }
1873     return appLoader.loadFeaturesFrom(alf, features, autoenabledisplay);
1874   }
1875
1876   @Override
1877   public String getFeatures(String format)
1878   {
1879     return getFeaturesFrom(null, format);
1880   }
1881
1882   @Override
1883   public String getFeaturesFrom(AlignFrameI alf, String format)
1884   {
1885     if (alf == null)
1886     {
1887       alf = getCurrentAlignFrame();
1888     }
1889     return appLoader.getFeaturesFrom(alf, format);
1890   }
1891
1892   @Override
1893   public String getAnnotation()
1894   {
1895     return getAnnotationFrom(null);
1896   }
1897
1898   @Override
1899   public String getAnnotationFrom(AlignFrameI alf)
1900   {
1901     if (alf == null)
1902     {
1903       alf = getCurrentAlignFrame();
1904     }
1905     return appLoader.getAnnotationFrom(alf);
1906   }
1907
1908   @Override
1909   public AlignFrameI newView()
1910   {
1911     return newViewFrom(null, null);
1912   }
1913
1914   @Override
1915   public AlignFrameI newView(String name)
1916   {
1917     return newViewFrom(null, name);
1918   }
1919
1920   @Override
1921   public AlignFrameI newViewFrom(AlignFrameI alf)
1922   {
1923     return newViewFrom(alf, null);
1924   }
1925
1926   @Override
1927   public AlignFrameI newViewFrom(AlignFrameI alf, String name)
1928   {
1929     if (alf == null)
1930     {
1931       alf = getCurrentAlignFrame();
1932     }
1933     return appLoader.newViewFrom(alf, name);
1934   }
1935
1936   @Override
1937   public AlignFrameI loadAlignment(String text, String title)
1938   {
1939     return appLoader.loadAlignment(text, AlignFrame.DEFAULT_WIDTH,
1940             AlignFrame.DEFAULT_HEIGHT, title);
1941   }
1942
1943   @Override
1944   public boolean addPdbFile(AlignFrameI alFrame, String sequenceId,
1945           String pdbEntryString, String pdbFile)
1946   {
1947     if (alFrame == null)
1948     {
1949       alFrame = getCurrentAlignFrame();
1950     }
1951     return appLoader.addPdbFile(alFrame, sequenceId, pdbEntryString,
1952             pdbFile);
1953   }
1954
1955   @Override
1956   public void scrollViewToIn(AlignFrameI alf, String topRow,
1957           String leftHandColumn)
1958   {
1959     if (alf == null)
1960     {
1961       alf = getCurrentAlignFrame();
1962     }
1963     appLoader.scrollViewToIn(alf, topRow, leftHandColumn);
1964   }
1965
1966   @Override
1967   public void scrollViewToRowIn(AlignFrameI alf, String topRow)
1968   {
1969     if (alf == null)
1970     {
1971       alf = getCurrentAlignFrame();
1972     }
1973     appLoader.scrollViewToRowIn(alf, topRow);
1974   }
1975
1976   @Override
1977   public void scrollViewToColumnIn(AlignFrameI alf, String leftHandColumn)
1978   {
1979     if (alf == null)
1980     {
1981       alf = getCurrentAlignFrame();
1982     }
1983     appLoader.scrollViewToColumnIn(alf, leftHandColumn);
1984   }
1985
1986   @Override
1987   public String getFeatureGroups()
1988   {
1989     return getFeatureGroupsOn(null);
1990   }
1991
1992   @Override
1993   public String getFeatureGroupsOn(AlignFrameI alf)
1994   {
1995     if (alf == null)
1996     {
1997       alf = getCurrentAlignFrame();
1998     }
1999     return appLoader.getFeatureGroupsOn(alf);
2000   }
2001
2002   @Override
2003   public String getFeatureGroupsOfState(boolean visible)
2004   {
2005     return getFeatureGroupsOfStateOn(null, visible);
2006   }
2007
2008   @Override
2009   public String getFeatureGroupsOfStateOn(AlignFrameI alf, boolean visible)
2010   {
2011     if (alf == null)
2012     {
2013       alf = getCurrentAlignFrame();
2014     }
2015     return appLoader.getFeatureGroupsOfStateOn(alf, visible);
2016   }
2017
2018   @Override
2019   public void setFeatureGroupState(String groups, boolean state)
2020   { // JalviewLite API
2021     setFeatureGroupStateOn(null, groups, state);
2022   }
2023
2024   @Override
2025   public void setFeatureGroupStateOn(AlignFrameI alf, String groups,
2026           boolean state)
2027   {
2028     if (alf == null)
2029     {
2030       alf = getCurrentAlignFrame();
2031     }
2032     appLoader.setFeatureGroupStateOn(alf, groups, state);
2033   }
2034
2035   @Override
2036   public String getSeparator()
2037   {
2038     return appLoader.getSeparator();
2039   }
2040
2041   @Override
2042   public void setSeparator(String separator)
2043   {
2044     appLoader.setSeparator(separator);
2045   }
2046
2047   @Override
2048   public String getJsMessage(String messageclass, String viewId)
2049   {
2050     // see http://www.jalview.org/examples/jalviewLiteJs.html
2051     return null;
2052   }
2053
2054   /**
2055    * Open a new Tree panel on the desktop statically. Params are standard (not
2056    * set by Groovy). No dialog is opened.
2057    * 
2058    * @param af
2059    * @param treeType
2060    * @param modelName
2061    * @return null, or the string "label.you_need_at_least_n_sequences" if number
2062    *         of sequences selected is inappropriate
2063    */
2064   @Override
2065   public Object openTreePanel(AlignFrame af, String treeType,
2066           String modelName)
2067   { // JalviewJS api
2068     if (af == null)
2069     {
2070       af = getCurrentAlignFrame();
2071     }
2072     return CalculationChooser.openTreePanel(af, treeType, modelName, null);
2073   }
2074
2075   /**
2076    * public static method for JalviewJS API to open a PCAPanel without
2077    * necessarily using a dialog.
2078    * 
2079    * @param af
2080    * @param modelName
2081    * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
2082    *         if number of sequences selected is inappropriate
2083    */
2084   @Override
2085   public Object openPcaPanel(AlignFrame af, String modelName)
2086   {
2087     if (af == null)
2088     {
2089       af = getCurrentAlignFrame();
2090     }
2091     return CalculationChooser.openPcaPanel(af, modelName, null);
2092   }
2093
2094   @Override
2095   public String getSelectedSequencesAsAlignment(String format,
2096           boolean suffix)
2097   {
2098     return getSelectedSequencesAsAlignmentFrom(null,
2099             format, suffix);
2100   }
2101
2102   @Override
2103   public String getSelectedSequencesAsAlignmentFrom(AlignFrameI alf,
2104           String format, boolean suffix)
2105   {
2106     if (alf == null)
2107     {
2108       alf = getCurrentAlignFrame();
2109     }
2110     return appLoader.getSelectedSequencesAsAlignmentFrom(alf, format,
2111             "" + suffix);
2112   }
2113
2114   @Override
2115   public String arrayToSeparatorList(String[] array)
2116   {
2117     return appLoader.arrayToSeparatorList(array);
2118   }
2119
2120   @Override
2121   public String[] separatorListToArray(String list)
2122   {
2123     return appLoader.separatorListToArray(list);
2124   }
2125
2126   //// probably not needed in JalviewJS -- From when Jmol and Jalview did not
2127   //// have a direct connection?
2128
2129   @Override
2130   public void setMouseoverListener(String listener)
2131   {
2132     // TODO Auto-generated method stub
2133
2134   }
2135
2136   @Override
2137   public void setMouseoverListener(AlignFrameI af, String listener)
2138   {
2139     // TODO Auto-generated method stub
2140
2141   }
2142
2143   @Override
2144   public void setSelectionListener(String listener)
2145   {
2146     // TODO Auto-generated method stub
2147
2148   }
2149
2150   @Override
2151   public void setSelectionListener(AlignFrameI af, String listener)
2152   {
2153     // TODO Auto-generated method stub
2154
2155   }
2156
2157   @Override
2158   public void setStructureListener(String listener, String modelSet)
2159   {
2160     // TODO Auto-generated method stub
2161
2162   }
2163
2164   @Override
2165   public void removeJavascriptListener(AlignFrameI af, String listener)
2166   {
2167     // TODO Auto-generated method stub
2168
2169   }
2170
2171   @Override
2172   public void mouseOverStructure(String pdbResNum, String chain,
2173           String pdbfile)
2174   {
2175     // TODO Auto-generated method stub
2176
2177   }
2178
2179   @Override
2180   public void showOverview()
2181   {
2182     currentAlignFrame.overviewMenuItem_actionPerformed(null);
2183   }
2184
2185   public void notifyWorker(AlignCalcWorkerI worker, String status)
2186   {
2187     // System.out.println("Jalview worker " + worker.getClass().getSimpleName()
2188     // + " " + status);
2189   }
2190
2191   /**
2192    * flag to allow selected Runnable and Thread processes to run synchronously
2193    * 
2194    * JAL-3563
2195    * 
2196    */
2197   private static boolean isSynchronous = false;
2198
2199   /**
2200    * Set Jalview to run selected processes synchronously in test and headless
2201    * environments.
2202    * 
2203    * JAL-3563
2204    * 
2205    * @param b
2206    * @author Bob Hanson
2207    */
2208   public static void setSynchronous(boolean b)
2209   {
2210
2211     isSynchronous = b;
2212
2213   }
2214
2215   /**
2216    * Allows optional synchronous running of a Runnable that would otherwise use
2217    * SwingUtilities.invokeLater.
2218    * 
2219    * JAL-3563
2220    * 
2221    * @param t
2222    * @author Bob Hanson
2223    */
2224   public static boolean isSynchronous()
2225   {
2226     return isSynchronous;
2227   }
2228
2229   /**
2230    * Allows optional synchronous running of a Runnable that would otherwise use
2231    * SwingUtilities.invokeLater.
2232    * 
2233    * JAL-3563
2234    * 
2235    * @param t
2236    * @author Bob Hanson
2237    */
2238   public static void execRunnable(Runnable r)
2239   {
2240     if (isSynchronous())
2241     {
2242       r.run();
2243     }
2244     else
2245     {
2246       SwingUtilities.invokeLater(r);
2247     }
2248   }
2249
2250   /**
2251    * Allows optional synchronous running of a thread that would otherwise be run
2252    * using start().
2253    * 
2254    * JAL-3563
2255    * 
2256    * @param t
2257    * @author Bob Hanson
2258    */
2259   public static void execThread(Thread t)
2260   {
2261     if (isSynchronous())
2262     {
2263       t.run();
2264     }
2265     else
2266     {
2267       t.start();
2268     }
2269   }
2270
2271 }