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