Merge branch 'improvement/JAL-1988+JAL-3416_Java8_macOS_APQHandlers_and_FlatLaF_optio...
[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 java.awt.Color;
24 import java.io.BufferedReader;
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.io.OutputStreamWriter;
30 import java.io.PrintWriter;
31 import java.net.MalformedURLException;
32 import java.net.URI;
33 import java.net.URISyntaxException;
34 import java.net.URL;
35 import java.security.AllPermission;
36 import java.security.CodeSource;
37 import java.security.PermissionCollection;
38 import java.security.Permissions;
39 import java.security.Policy;
40 import java.util.HashMap;
41 import java.util.Locale;
42 import java.util.Map;
43 import java.util.Vector;
44 import java.util.logging.ConsoleHandler;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47
48 import javax.swing.SwingUtilities;
49 import javax.swing.UIManager;
50 import javax.swing.UIManager.LookAndFeelInfo;
51
52 import com.formdev.flatlaf.FlatLightLaf;
53 import com.formdev.flatlaf.util.SystemInfo;
54 import com.threerings.getdown.util.LaunchUtil;
55
56 //import edu.stanford.ejalbert.launching.IBrowserLaunching;
57 import groovy.lang.Binding;
58 import groovy.util.GroovyScriptEngine;
59 import jalview.ext.so.SequenceOntology;
60 import jalview.gui.AlignFrame;
61 import jalview.gui.Desktop;
62 import jalview.gui.PromptUserConfig;
63 import jalview.io.AppletFormatAdapter;
64 import jalview.io.BioJsHTMLOutput;
65 import jalview.io.DataSourceType;
66 import jalview.io.FileFormat;
67 import jalview.io.FileFormatException;
68 import jalview.io.FileFormatI;
69 import jalview.io.FileFormats;
70 import jalview.io.FileLoader;
71 import jalview.io.HtmlSvgOutput;
72 import jalview.io.IdentifyFile;
73 import jalview.io.NewickFile;
74 import jalview.io.gff.SequenceOntologyFactory;
75 import jalview.schemes.ColourSchemeI;
76 import jalview.schemes.ColourSchemeProperty;
77 import jalview.util.ChannelProperties;
78 import jalview.util.HttpUtils;
79 import jalview.util.MessageManager;
80 import jalview.util.Platform;
81 import jalview.ws.jws2.Jws2Discoverer;
82
83 /**
84  * Main class for Jalview Application <br>
85  * <br>
86  * start with: java -classpath "$PATH_TO_LIB$/*:$PATH_TO_CLASSES$" \
87  * jalview.bin.Jalview
88  * 
89  * or on Windows: java -classpath "$PATH_TO_LIB$/*;$PATH_TO_CLASSES$" \
90  * jalview.bin.Jalview jalview.bin.Jalview
91  * 
92  * (ensure -classpath arg is quoted to avoid shell expansion of '*' and do not
93  * embellish '*' to e.g. '*.jar')
94  * 
95  * @author $author$
96  * @version $Revision$
97  */
98 public class Jalview
99 {
100   static
101   {
102     Platform.getURLCommandArguments();
103     Platform.addJ2SDirectDatabaseCall("https://www.jalview.org");
104     Platform.addJ2SDirectDatabaseCall("http://www.jalview.org");
105     Platform.addJ2SDirectDatabaseCall("http://www.compbio.dundee.ac.uk");
106     Platform.addJ2SDirectDatabaseCall("https://www.compbio.dundee.ac.uk");
107   }
108
109   /*
110    * singleton instance of this class
111    */
112   private static Jalview instance;
113
114   private Desktop desktop;
115
116   public static AlignFrame currentAlignFrame;
117
118   static
119   {
120     if (!Platform.isJS())
121     /**
122      * Java only
123      * 
124      * @j2sIgnore
125      */
126     {
127       // grab all the rights we can for the JVM
128       Policy.setPolicy(new Policy()
129       {
130         @Override
131         public PermissionCollection getPermissions(CodeSource codesource)
132         {
133           Permissions perms = new Permissions();
134           perms.add(new AllPermission());
135           return (perms);
136         }
137
138         @Override
139         public void refresh()
140         {
141         }
142       });
143     }
144   }
145
146   /**
147    * keep track of feature fetching tasks.
148    * 
149    * @author JimP
150    * 
151    */
152   class FeatureFetcher
153   {
154     /*
155      * TODO: generalise to track all jalview events to orchestrate batch processing
156      * events.
157      */
158
159     private int queued = 0;
160
161     private int running = 0;
162
163     public FeatureFetcher()
164     {
165
166     }
167
168     public void addFetcher(final AlignFrame af,
169             final Vector<String> dasSources)
170     {
171       final long id = System.currentTimeMillis();
172       queued++;
173       final FeatureFetcher us = this;
174       new Thread(new Runnable()
175       {
176
177         @Override
178         public void run()
179         {
180           synchronized (us)
181           {
182             queued--;
183             running++;
184           }
185
186           af.setProgressBar(MessageManager
187                   .getString("status.das_features_being_retrived"), id);
188           af.featureSettings_actionPerformed(null);
189           af.setProgressBar(null, id);
190           synchronized (us)
191           {
192             running--;
193           }
194         }
195       }).start();
196     }
197
198     public synchronized boolean allFinished()
199     {
200       return queued == 0 && running == 0;
201     }
202
203   }
204
205   public static Jalview getInstance()
206   {
207     return instance;
208   }
209
210   /**
211    * main class for Jalview application
212    * 
213    * @param args
214    *          open <em>filename</em>
215    */
216   public static void main(String[] args)
217   {
218     // setLogging(); // BH - for event debugging in JavaScript
219     instance = new Jalview();
220     instance.doMain(args);
221   }
222
223   private static void logClass(String name)
224   {
225     // BH - for event debugging in JavaScript
226     ConsoleHandler consoleHandler = new ConsoleHandler();
227     consoleHandler.setLevel(Level.ALL);
228     Logger logger = Logger.getLogger(name);
229     logger.setLevel(Level.ALL);
230     logger.addHandler(consoleHandler);
231   }
232
233   @SuppressWarnings("unused")
234   private static void setLogging()
235   {
236
237     /**
238      * @j2sIgnore
239      * 
240      */
241     {
242       System.out.println("not in js");
243     }
244
245     // BH - for event debugging in JavaScript (Java mode only)
246     if (!Platform.isJS())
247     /**
248      * Java only
249      * 
250      * @j2sIgnore
251      */
252     {
253       Logger.getLogger("").setLevel(Level.ALL);
254       logClass("java.awt.EventDispatchThread");
255       logClass("java.awt.EventQueue");
256       logClass("java.awt.Component");
257       logClass("java.awt.focus.Component");
258       logClass("java.awt.focus.DefaultKeyboardFocusManager");
259     }
260
261   }
262
263   /**
264    * @param args
265    */
266   void doMain(String[] args)
267   {
268
269     if (!Platform.isJS())
270     {
271       System.setSecurityManager(null);
272     }
273
274     System.out
275             .println("Java version: " + System.getProperty("java.version"));
276     System.out.println("Java Home: " + System.getProperty("java.home"));
277     System.out.println(System.getProperty("os.arch") + " "
278             + System.getProperty("os.name") + " "
279             + System.getProperty("os.version"));
280     String val = System.getProperty("sys.install4jVersion");
281     if (val != null)
282     {
283       System.out.println("Install4j version: " + val);
284     }
285     val = System.getProperty("installer_template_version");
286     if (val != null)
287     {
288       System.out.println("Install4j template version: " + val);
289     }
290     val = System.getProperty("launcher_version");
291     if (val != null)
292     {
293       System.out.println("Launcher version: " + val);
294     }
295
296     // report Jalview version
297     Cache.loadBuildProperties(true);
298
299     ArgsParser aparser = new ArgsParser(args);
300     boolean headless = false;
301
302     String usrPropsFile = aparser.getValue("props");
303     Cache.loadProperties(usrPropsFile); // must do this before
304     if (usrPropsFile != null)
305     {
306       System.out.println(
307               "CMD [-props " + usrPropsFile + "] executed successfully!");
308     }
309
310     if (!Platform.isJS())
311     /**
312      * Java only
313      * 
314      * @j2sIgnore
315      */
316     {
317       if (aparser.contains("help") || aparser.contains("h"))
318       {
319         showUsage();
320         System.exit(0);
321       }
322       if (aparser.contains("nodisplay") || aparser.contains("nogui")
323               || aparser.contains("headless"))
324       {
325         System.setProperty("java.awt.headless", "true");
326         headless = true;
327       }
328       // anything else!
329
330       // allow https handshakes to download intermediate certs if necessary
331       System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
332
333       final String jabawsUrl = aparser.getValue("jabaws");
334       if (jabawsUrl != null)
335       {
336         try
337         {
338           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
339           System.out.println(
340                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
341         } catch (MalformedURLException e)
342         {
343           System.err.println(
344                   "Invalid jabaws parameter: " + jabawsUrl + " ignored");
345         }
346       }
347     }
348
349     String defs = aparser.getValue("setprop");
350     while (defs != null)
351     {
352       int p = defs.indexOf('=');
353       if (p == -1)
354       {
355         System.err.println("Ignoring invalid setprop argument : " + defs);
356       }
357       else
358       {
359         System.out.println("Executing setprop argument: " + defs);
360         if (Platform.isJS())
361         {
362           Cache.setProperty(defs.substring(0, p), defs.substring(p + 1));
363         }
364         // DISABLED FOR SECURITY REASONS
365         // TODO: add a property to allow properties to be overriden by cli args
366         // Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
367       }
368       defs = aparser.getValue("setprop");
369     }
370     if (System.getProperty("java.awt.headless") != null
371             && System.getProperty("java.awt.headless").equals("true"))
372     {
373       headless = true;
374     }
375     System.setProperty("http.agent",
376             "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
377
378     try
379     {
380       Console.initLogger();
381     } catch (NoClassDefFoundError error)
382     {
383       error.printStackTrace();
384       System.out.println("\nEssential logging libraries not found."
385               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview");
386       System.exit(0);
387     }
388
389     desktop = null;
390
391     setLookAndFeel();
392
393     /*
394      * configure 'full' SO model if preferences say to, else use the default (full SO)
395      * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
396      */
397     boolean soDefault = !Platform.isJS();
398     if (Cache.getDefault("USE_FULL_SO", soDefault))
399     {
400       SequenceOntologyFactory.setInstance(new SequenceOntology());
401     }
402
403     if (!headless)
404     {
405       Desktop.nosplash = aparser.contains("nosplash");
406       desktop = new Desktop();
407       desktop.setInBatchMode(true); // indicate we are starting up
408
409       try
410       {
411         JalviewTaskbar.setTaskbar(this);
412       } catch (Exception e)
413       {
414         Console.info("Cannot set Taskbar");
415         Console.error(e.getMessage());
416         // e.printStackTrace();
417       } catch (Throwable t)
418       {
419         Console.info("Cannot set Taskbar");
420         Console.error(t.getMessage());
421         // t.printStackTrace();
422       }
423
424       // set Proxy settings before all the internet calls
425       Cache.setProxyPropertiesFromPreferences();
426
427       desktop.setVisible(true);
428
429       if (!Platform.isJS())
430       /**
431        * Java only
432        * 
433        * @j2sIgnore
434        */
435       {
436         if (!aparser.contains("nowebservicediscovery"))
437         {
438           desktop.startServiceDiscovery();
439         }
440         if (!aparser.contains("nousagestats"))
441         {
442           startUsageStats(desktop);
443         }
444         else
445         {
446           System.err.println("CMD [-nousagestats] executed successfully!");
447         }
448
449         if (!aparser.contains("noquestionnaire"))
450         {
451           String url = aparser.getValue("questionnaire");
452           if (url != null)
453           {
454             // Start the desktop questionnaire prompter with the specified
455             // questionnaire
456             Console.debug("Starting questionnaire url at " + url);
457             desktop.checkForQuestionnaire(url);
458             System.out.println("CMD questionnaire[-" + url
459                     + "] executed successfully!");
460           }
461           else
462           {
463             if (Cache.getProperty("NOQUESTIONNAIRES") == null)
464             {
465               // Start the desktop questionnaire prompter with the specified
466               // questionnaire
467               // String defurl =
468               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
469               // //
470               String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
471               Console.debug(
472                       "Starting questionnaire with default url: " + defurl);
473               desktop.checkForQuestionnaire(defurl);
474             }
475           }
476         }
477         else
478         {
479           System.err
480                   .println("CMD [-noquestionnaire] executed successfully!");
481         }
482
483         if (!aparser.contains("nonews")
484                 || Cache.getProperty("NONEWS") == null)
485         {
486           desktop.checkForNews();
487         }
488
489         if (!aparser.contains("nohtmltemplates")
490                 || Cache.getProperty("NOHTMLTEMPLATES") == null)
491         {
492           BioJsHTMLOutput.updateBioJS();
493         }
494       }
495     }
496
497     // Move any new getdown-launcher-new.jar into place over old
498     // getdown-launcher.jar
499     String appdirString = System.getProperty("getdownappdir");
500     if (appdirString != null && appdirString.length() > 0)
501     {
502       final File appdir = new File(appdirString);
503       new Thread()
504       {
505         @Override
506         public void run()
507         {
508           LaunchUtil.upgradeGetdown(
509                   new File(appdir, "getdown-launcher-old.jar"),
510                   new File(appdir, "getdown-launcher.jar"),
511                   new File(appdir, "getdown-launcher-new.jar"));
512         }
513       }.start();
514     }
515
516     String file = null, data = null;
517     FileFormatI format = null;
518     DataSourceType protocol = null;
519     FileLoader fileLoader = new FileLoader(!headless);
520
521     String groovyscript = null; // script to execute after all loading is
522     // completed one way or another
523     // extract groovy argument and execute if necessary
524     groovyscript = aparser.getValue("groovy", true);
525     file = aparser.getValue("open", true);
526
527     if (file == null && desktop == null)
528     {
529       System.out.println("No files to open!");
530       System.exit(1);
531     }
532     long progress = -1;
533     // Finally, deal with the remaining input data.
534     if (file != null)
535     {
536       if (!headless)
537       {
538         desktop.setProgressBar(
539                 MessageManager
540                         .getString("status.processing_commandline_args"),
541                 progress = System.currentTimeMillis());
542       }
543       System.out.println("CMD [-open " + file + "] executed successfully!");
544
545       if (!Platform.isJS())
546       /**
547        * ignore in JavaScript -- can't just file existence - could load it?
548        * 
549        * @j2sIgnore
550        */
551       {
552         if (!HttpUtils.startsWithHttpOrHttps(file))
553         {
554           if (!(new File(file)).exists())
555           {
556             System.out.println("Can't find " + file);
557             if (headless)
558             {
559               System.exit(1);
560             }
561           }
562         }
563       }
564
565       protocol = AppletFormatAdapter.checkProtocol(file);
566
567       try
568       {
569         format = new IdentifyFile().identify(file, protocol);
570       } catch (FileFormatException e1)
571       {
572         // TODO ?
573       }
574
575       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
576               format);
577       if (af == null)
578       {
579         System.out.println("error");
580       }
581       else
582       {
583         setCurrentAlignFrame(af);
584         data = aparser.getValue("colour", true);
585         if (data != null)
586         {
587           data.replaceAll("%20", " ");
588
589           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
590                   af.getViewport(), af.getViewport().getAlignment(), data);
591
592           if (cs != null)
593           {
594             System.out.println(
595                     "CMD [-color " + data + "] executed successfully!");
596           }
597           af.changeColour(cs);
598         }
599
600         // Must maintain ability to use the groups flag
601         data = aparser.getValue("groups", true);
602         if (data != null)
603         {
604           af.parseFeaturesFile(data,
605                   AppletFormatAdapter.checkProtocol(data));
606           // System.out.println("Added " + data);
607           System.out.println(
608                   "CMD groups[-" + data + "]  executed successfully!");
609         }
610         data = aparser.getValue("features", true);
611         if (data != null)
612         {
613           af.parseFeaturesFile(data,
614                   AppletFormatAdapter.checkProtocol(data));
615           // System.out.println("Added " + data);
616           System.out.println(
617                   "CMD [-features " + data + "]  executed successfully!");
618         }
619
620         data = aparser.getValue("annotations", true);
621         if (data != null)
622         {
623           af.loadJalviewDataFile(data, null, null, null);
624           // System.out.println("Added " + data);
625           System.out.println(
626                   "CMD [-annotations " + data + "] executed successfully!");
627         }
628         // set or clear the sortbytree flag.
629         if (aparser.contains("sortbytree"))
630         {
631           af.getViewport().setSortByTree(true);
632           if (af.getViewport().getSortByTree())
633           {
634             System.out.println("CMD [-sortbytree] executed successfully!");
635           }
636         }
637         if (aparser.contains("no-annotation"))
638         {
639           af.getViewport().setShowAnnotation(false);
640           if (!af.getViewport().isShowAnnotation())
641           {
642             System.out.println("CMD no-annotation executed successfully!");
643           }
644         }
645         if (aparser.contains("nosortbytree"))
646         {
647           af.getViewport().setSortByTree(false);
648           if (!af.getViewport().getSortByTree())
649           {
650             System.out
651                     .println("CMD [-nosortbytree] executed successfully!");
652           }
653         }
654         data = aparser.getValue("tree", true);
655         if (data != null)
656         {
657           try
658           {
659             System.out.println(
660                     "CMD [-tree " + data + "] executed successfully!");
661             NewickFile nf = new NewickFile(data,
662                     AppletFormatAdapter.checkProtocol(data));
663             af.getViewport()
664                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
665           } catch (IOException ex)
666           {
667             System.err.println("Couldn't add tree " + data);
668             ex.printStackTrace(System.err);
669           }
670         }
671         // TODO - load PDB structure(s) to alignment JAL-629
672         // (associate with identical sequence in alignment, or a specified
673         // sequence)
674         if (groovyscript != null)
675         {
676           // Execute the groovy script after we've done all the rendering stuff
677           // and before any images or figures are generated.
678           System.out.println("Executing script " + groovyscript);
679           executeGroovyScript(groovyscript, af);
680           System.out.println("CMD groovy[" + groovyscript
681                   + "] executed successfully!");
682           groovyscript = null;
683         }
684         String imageName = "unnamed.png";
685         while (aparser.getSize() > 1)
686         {
687           String outputFormat = aparser.nextValue();
688           file = aparser.nextValue();
689
690           if (outputFormat.equalsIgnoreCase("png"))
691           {
692             af.createPNG(new File(file));
693             imageName = (new File(file)).getName();
694             System.out.println("Creating PNG image: " + file);
695             continue;
696           }
697           else if (outputFormat.equalsIgnoreCase("svg"))
698           {
699             File imageFile = new File(file);
700             imageName = imageFile.getName();
701             af.createSVG(imageFile);
702             System.out.println("Creating SVG image: " + file);
703             continue;
704           }
705           else if (outputFormat.equalsIgnoreCase("html"))
706           {
707             File imageFile = new File(file);
708             imageName = imageFile.getName();
709             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
710             htmlSVG.exportHTML(file);
711
712             System.out.println("Creating HTML image: " + file);
713             continue;
714           }
715           else if (outputFormat.equalsIgnoreCase("biojsmsa"))
716           {
717             if (file == null)
718             {
719               System.err.println("The output html file must not be null");
720               return;
721             }
722             try
723             {
724               BioJsHTMLOutput.refreshVersionInfo(
725                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
726             } catch (URISyntaxException e)
727             {
728               e.printStackTrace();
729             }
730             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
731             bjs.exportHTML(file);
732             System.out
733                     .println("Creating BioJS MSA Viwer HTML file: " + file);
734             continue;
735           }
736           else if (outputFormat.equalsIgnoreCase("imgMap"))
737           {
738             af.createImageMap(new File(file), imageName);
739             System.out.println("Creating image map: " + file);
740             continue;
741           }
742           else if (outputFormat.equalsIgnoreCase("eps"))
743           {
744             File outputFile = new File(file);
745             System.out.println(
746                     "Creating EPS file: " + outputFile.getAbsolutePath());
747             af.createEPS(outputFile);
748             continue;
749           }
750           FileFormatI outFormat = null;
751           try
752           {
753             outFormat = FileFormats.getInstance().forName(outputFormat);
754           } catch (Exception formatP)
755           {
756             System.out.println("Couldn't parse " + outFormat
757                     + " as a valid Jalview format string.");
758           }
759           if (outFormat != null)
760           {
761             if (!outFormat.isWritable())
762             {
763               System.out.println(
764                       "This version of Jalview does not support alignment export as "
765                               + outputFormat);
766             }
767             else
768             {
769               af.saveAlignment(file, outFormat);
770               if (af.isSaveAlignmentSuccessful())
771               {
772                 System.out.println("Written alignment in "
773                         + outFormat.getName() + " format to " + file);
774               }
775               else
776               {
777                 System.out.println("Error writing file " + file + " in "
778                         + outFormat.getName() + " format!!");
779               }
780             }
781           }
782
783         }
784
785         while (aparser.getSize() > 0)
786         {
787           System.out.println("Unknown arg: " + aparser.nextValue());
788         }
789       }
790     }
791     AlignFrame startUpAlframe = null;
792     // We'll only open the default file if the desktop is visible.
793     // And the user
794     // ////////////////////
795
796     if (!Platform.isJS() && !headless && file == null
797             && Cache.getDefault("SHOW_STARTUP_FILE", true))
798     /**
799      * Java only
800      * 
801      * @j2sIgnore
802      */
803     {
804       file = Cache.getDefault("STARTUP_FILE",
805               Cache.getDefault("www.jalview.org", "https://www.jalview.org")
806                       + "/examples/exampleFile_2_7.jvp");
807       if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
808               || file.equals(
809                       "http://www.jalview.org/examples/exampleFile_2_7.jar"))
810       {
811         file.replace("http:", "https:");
812         // hardwire upgrade of the startup file
813         file.replace("_2_3", "_2_7");
814         file.replace("2_7.jar", "2_7.jvp");
815         // and remove the stale setting
816         Cache.removeProperty("STARTUP_FILE");
817       }
818
819       protocol = AppletFormatAdapter.checkProtocol(file);
820
821       if (file.endsWith(".jar"))
822       {
823         format = FileFormat.Jalview;
824       }
825       else
826       {
827         try
828         {
829           format = new IdentifyFile().identify(file, protocol);
830         } catch (FileFormatException e)
831         {
832           // TODO what?
833         }
834       }
835
836       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
837               format);
838       // extract groovy arguments before anything else.
839     }
840
841     // Once all other stuff is done, execute any groovy scripts (in order)
842     if (groovyscript != null)
843     {
844       if (Cache.groovyJarsPresent())
845       {
846         System.out.println("Executing script " + groovyscript);
847         executeGroovyScript(groovyscript, startUpAlframe);
848       }
849       else
850       {
851         System.err.println(
852                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
853                         + groovyscript);
854       }
855     }
856     // and finally, turn off batch mode indicator - if the desktop still exists
857     if (desktop != null)
858     {
859       if (progress != -1)
860       {
861         desktop.setProgressBar(null, progress);
862       }
863       desktop.setInBatchMode(false);
864     }
865   }
866
867   private static void setLookAndFeel()
868   {
869     // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
870     // "mac" or "flat"
871     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
872     // try Quaqua/Vaqua.
873     String lafProp = System.getProperty("laf");
874     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
875     String laf = "none";
876     if (lafProp != null)
877     {
878       laf = lafProp;
879     }
880     else if (lafSetting != null)
881     {
882       laf = lafSetting;
883     }
884     boolean lafSet = false;
885     switch (laf)
886     {
887     case "crossplatform":
888       lafSet = setCrossPlatformLookAndFeel();
889       if (!lafSet)
890       {
891         Console.error("Could not set requested laf=" + laf);
892       }
893       break;
894     case "system":
895       lafSet = setSystemLookAndFeel();
896       if (!lafSet)
897       {
898         Console.error("Could not set requested laf=" + laf);
899       }
900       break;
901     case "gtk":
902       lafSet = setGtkLookAndFeel();
903       if (!lafSet)
904       {
905         Console.error("Could not set requested laf=" + laf);
906       }
907       break;
908     case "metal":
909       lafSet = setMetalLookAndFeel();
910       if (!lafSet)
911       {
912         Console.error("Could not set requested laf=" + laf);
913       }
914       break;
915     case "nimbus":
916       lafSet = setNimbusLookAndFeel();
917       if (!lafSet)
918       {
919         Console.error("Could not set requested laf=" + laf);
920       }
921       break;
922     case "flat":
923       lafSet = setFlatLookAndFeel();
924       if (!lafSet)
925       {
926         Console.error("Could not set requested laf=" + laf);
927       }
928       break;
929     case "quaqua":
930       lafSet = setQuaquaLookAndFeel();
931       if (!lafSet)
932       {
933         Console.error("Could not set requested laf=" + laf);
934       }
935       break;
936     case "vaqua":
937       lafSet = setVaquaLookAndFeel();
938       if (!lafSet)
939       {
940         Console.error("Could not set requested laf=" + laf);
941       }
942       break;
943     case "mac":
944       lafSet = setMacLookAndFeel();
945       if (!lafSet)
946       {
947         Console.error("Could not set requested laf=" + laf);
948       }
949       break;
950     case "none":
951       break;
952     default:
953       Console.error("Requested laf=" + laf + " not implemented");
954     }
955     if (!lafSet)
956     {
957       setSystemLookAndFeel();
958       if (Platform.isLinux())
959       {
960         setMetalLookAndFeel();
961       }
962       if (Platform.isMac())
963       {
964         setMacLookAndFeel();
965       }
966     }
967   }
968
969   private static boolean setCrossPlatformLookAndFeel()
970   {
971     boolean set = false;
972     try
973     {
974       UIManager.setLookAndFeel(
975               UIManager.getCrossPlatformLookAndFeelClassName());
976       set = true;
977     } catch (Exception ex)
978     {
979       Console.error("Unexpected Look and Feel Exception");
980       Console.error(ex.getMessage());
981       Console.debug(Cache.getStackTraceString(ex));
982     }
983     return set;
984   }
985
986   private static boolean setSystemLookAndFeel()
987   {
988     boolean set = false;
989     try
990     {
991       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
992       set = true;
993     } catch (Exception ex)
994     {
995       Console.error("Unexpected Look and Feel Exception");
996       Console.error(ex.getMessage());
997       Console.debug(Cache.getStackTraceString(ex));
998     }
999     return set;
1000   }
1001
1002   private static boolean setSpecificLookAndFeel(String name,
1003           String className, boolean nameStartsWith)
1004   {
1005     boolean set = false;
1006     try
1007     {
1008       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
1009       {
1010         if (info.getName() != null && nameStartsWith
1011                 ? info.getName().toLowerCase(Locale.ROOT)
1012                         .startsWith(name.toLowerCase(Locale.ROOT))
1013                 : info.getName().toLowerCase(Locale.ROOT)
1014                         .equals(name.toLowerCase(Locale.ROOT)))
1015         {
1016           className = info.getClassName();
1017           break;
1018         }
1019       }
1020       UIManager.setLookAndFeel(className);
1021       set = true;
1022     } catch (Exception ex)
1023     {
1024       Console.error("Unexpected Look and Feel Exception");
1025       Console.error(ex.getMessage());
1026       Console.debug(Cache.getStackTraceString(ex));
1027     }
1028     return set;
1029   }
1030
1031   private static boolean setGtkLookAndFeel()
1032   {
1033     return setSpecificLookAndFeel("gtk",
1034             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
1035   }
1036
1037   private static boolean setMetalLookAndFeel()
1038   {
1039     return setSpecificLookAndFeel("metal",
1040             "javax.swing.plaf.metal.MetalLookAndFeel", false);
1041   }
1042
1043   private static boolean setNimbusLookAndFeel()
1044   {
1045     return setSpecificLookAndFeel("nimbus",
1046             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
1047   }
1048
1049   private static boolean setFlatLookAndFeel()
1050   {
1051     boolean set = setSpecificLookAndFeel("flatlaf light",
1052             "com.formdev.flatlaf.FlatLightLaf", false);
1053     if (set)
1054     {
1055       if (Platform.isMac())
1056       {
1057         System.setProperty("apple.laf.useScreenMenuBar", "true");
1058         System.setProperty("apple.awt.application.name",
1059                 ChannelProperties.getProperty("app_name"));
1060         System.setProperty("apple.awt.application.appearance", "system");
1061         if (SystemInfo.isMacFullWindowContentSupported
1062                 && Desktop.desktop != null)
1063         {
1064           Desktop.desktop.getRootPane()
1065                   .putClientProperty("apple.awt.fullWindowContent", true);
1066           Desktop.desktop.getRootPane()
1067                   .putClientProperty("apple.awt.transparentTitleBar", true);
1068         }
1069
1070         SwingUtilities.invokeLater(() -> {
1071           FlatLightLaf.setup();
1072         });
1073       }
1074
1075       UIManager.put("TabbedPane.showTabSeparators", true);
1076       UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
1077       UIManager.put("TabbedPane.tabsOverlapBorder", true);
1078       // UIManager.put("TabbedPane.hasFullBorder", true);
1079       UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
1080       UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
1081       UIManager.put("TabbedPane.smoothScrolling", true);
1082       UIManager.put("TabbedPane.tabWidthMode", "compact");
1083       UIManager.put("TabbedPane.selectedBackground", Color.white);
1084     }
1085     return set;
1086   }
1087
1088   private static boolean setQuaquaLookAndFeel()
1089   {
1090     return setSpecificLookAndFeel("quaqua",
1091             ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel().getClass()
1092                     .getName(),
1093             false);
1094   }
1095
1096   private static boolean setVaquaLookAndFeel()
1097   {
1098     return setSpecificLookAndFeel("vaqua",
1099             "org.violetlib.aqua.AquaLookAndFeel", false);
1100   }
1101
1102   private static boolean setMacLookAndFeel()
1103   {
1104     boolean set = false;
1105     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
1106             ChannelProperties.getProperty("app_name"));
1107     System.setProperty("apple.laf.useScreenMenuBar", "true");
1108     set = setQuaquaLookAndFeel();
1109     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
1110             .toLowerCase(Locale.ROOT).contains("quaqua"))
1111     {
1112       set = setVaquaLookAndFeel();
1113     }
1114     return set;
1115   }
1116
1117   private static void showUsage()
1118   {
1119     System.out.println(
1120             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1121                     + "-nodisplay\tRun Jalview without User Interface.\n"
1122                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1123                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1124                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1125                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1126                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1127                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1128                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1129                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1130                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1131                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1132                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1133                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1134                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1135                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1136                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1137                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1138                     + "-html FILE\tCreate HTML file from alignment.\n"
1139                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1140                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1141                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1142                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1143                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1144                     + "-nonews\tTurn off check for Jalview news.\n"
1145                     + "-nousagestats\tTurn off google analytics tracking for this session.\n"
1146                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1147                     // +
1148                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1149                     // after all other properties files have been read\n\t
1150                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1151                     // passed in correctly)"
1152                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1153                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1154                     + "-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"
1155                     + "-jvmmempc=PERCENT\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
1156                     + "-jvmmemmax=MAXMEMORY\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m), gigabytes(g) or if you're lucky enough, terabytes(t). This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
1157                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
1158   }
1159
1160   private static void startUsageStats(final Desktop desktop)
1161   {
1162     /**
1163      * start a User Config prompt asking if we can log usage statistics.
1164      */
1165     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1166             "USAGESTATS", "Jalview Usage Statistics",
1167             "Do you want to help make Jalview better by enabling "
1168                     + "the collection of usage statistics with Google Analytics ?"
1169                     + "\n\n(you can enable or disable usage tracking in the preferences)",
1170             new Runnable()
1171             {
1172               @Override
1173               public void run()
1174               {
1175                 Console.debug(
1176                         "Initialising googletracker for usage stats.");
1177                 Cache.initGoogleTracker();
1178                 Console.debug("Tracking enabled.");
1179               }
1180             }, new Runnable()
1181             {
1182               @Override
1183               public void run()
1184               {
1185                 Console.debug("Not enabling Google Tracking.");
1186               }
1187             }, null, true);
1188     desktop.addDialogThread(prompter);
1189   }
1190
1191   /**
1192    * Locate the given string as a file and pass it to the groovy interpreter.
1193    * 
1194    * @param groovyscript
1195    *          the script to execute
1196    * @param jalviewContext
1197    *          the Jalview Desktop object passed in to the groovy binding as the
1198    *          'Jalview' object.
1199    */
1200   private void executeGroovyScript(String groovyscript, AlignFrame af)
1201   {
1202     /**
1203      * for scripts contained in files
1204      */
1205     File tfile = null;
1206     /**
1207      * script's URI
1208      */
1209     URL sfile = null;
1210     if (groovyscript.trim().equals("STDIN"))
1211     {
1212       // read from stdin into a tempfile and execute it
1213       try
1214       {
1215         tfile = File.createTempFile("jalview", "groovy");
1216         PrintWriter outfile = new PrintWriter(
1217                 new OutputStreamWriter(new FileOutputStream(tfile)));
1218         BufferedReader br = new BufferedReader(
1219                 new InputStreamReader(System.in));
1220         String line = null;
1221         while ((line = br.readLine()) != null)
1222         {
1223           outfile.write(line + "\n");
1224         }
1225         br.close();
1226         outfile.flush();
1227         outfile.close();
1228
1229       } catch (Exception ex)
1230       {
1231         System.err.println("Failed to read from STDIN into tempfile "
1232                 + ((tfile == null) ? "(tempfile wasn't created)"
1233                         : tfile.toString()));
1234         ex.printStackTrace();
1235         return;
1236       }
1237       try
1238       {
1239         sfile = tfile.toURI().toURL();
1240       } catch (Exception x)
1241       {
1242         System.err.println(
1243                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1244                         + tfile.toURI());
1245         x.printStackTrace();
1246         return;
1247       }
1248     }
1249     else
1250     {
1251       try
1252       {
1253         sfile = new URI(groovyscript).toURL();
1254       } catch (Exception x)
1255       {
1256         tfile = new File(groovyscript);
1257         if (!tfile.exists())
1258         {
1259           System.err.println("File '" + groovyscript + "' does not exist.");
1260           return;
1261         }
1262         if (!tfile.canRead())
1263         {
1264           System.err.println("File '" + groovyscript + "' cannot be read.");
1265           return;
1266         }
1267         if (tfile.length() < 1)
1268         {
1269           System.err.println("File '" + groovyscript + "' is empty.");
1270           return;
1271         }
1272         try
1273         {
1274           sfile = tfile.getAbsoluteFile().toURI().toURL();
1275         } catch (Exception ex)
1276         {
1277           System.err.println("Failed to create a file URL for "
1278                   + tfile.getAbsoluteFile());
1279           return;
1280         }
1281       }
1282     }
1283     try
1284     {
1285       Map<String, java.lang.Object> vbinding = new HashMap<>();
1286       vbinding.put("Jalview", this);
1287       if (af != null)
1288       {
1289         vbinding.put("currentAlFrame", af);
1290       }
1291       Binding gbinding = new Binding(vbinding);
1292       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1293       gse.run(sfile.toString(), gbinding);
1294       if ("STDIN".equals(groovyscript))
1295       {
1296         // delete temp file that we made -
1297         // only if it was successfully executed
1298         tfile.delete();
1299       }
1300     } catch (Exception e)
1301     {
1302       System.err.println("Exception Whilst trying to execute file " + sfile
1303               + " as a groovy script.");
1304       e.printStackTrace(System.err);
1305
1306     }
1307   }
1308
1309   public static boolean isHeadlessMode()
1310   {
1311     String isheadless = System.getProperty("java.awt.headless");
1312     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1313     {
1314       return true;
1315     }
1316     return false;
1317   }
1318
1319   public AlignFrame[] getAlignFrames()
1320   {
1321     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1322             : Desktop.getAlignFrames();
1323
1324   }
1325
1326   /**
1327    * Quit method delegates to Desktop.quit - unless running in headless mode
1328    * when it just ends the JVM
1329    */
1330   public void quit()
1331   {
1332     if (desktop != null)
1333     {
1334       desktop.quit();
1335     }
1336     else
1337     {
1338       System.exit(0);
1339     }
1340   }
1341
1342   public static AlignFrame getCurrentAlignFrame()
1343   {
1344     return Jalview.currentAlignFrame;
1345   }
1346
1347   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
1348   {
1349     Jalview.currentAlignFrame = currentAlignFrame;
1350   }
1351 }