JAL-3416 Remove full height separators between new curved tabs
[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.OutputStream;
30 import java.io.OutputStreamWriter;
31 import java.io.PrintStream;
32 import java.io.PrintWriter;
33 import java.net.MalformedURLException;
34 import java.net.URI;
35 import java.net.URISyntaxException;
36 import java.net.URL;
37 import java.security.AllPermission;
38 import java.security.CodeSource;
39 import java.security.PermissionCollection;
40 import java.security.Permissions;
41 import java.security.Policy;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.Properties;
48 import java.util.Vector;
49 import java.util.logging.ConsoleHandler;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52 import java.util.stream.Collectors;
53
54 import javax.swing.JDialog;
55 import javax.swing.JFrame;
56 import javax.swing.JInternalFrame;
57 import javax.swing.JOptionPane;
58 import javax.swing.SwingUtilities;
59 import javax.swing.UIManager;
60 import javax.swing.UIManager.LookAndFeelInfo;
61 import javax.swing.UnsupportedLookAndFeelException;
62
63 import com.formdev.flatlaf.FlatLightLaf;
64 import com.formdev.flatlaf.themes.FlatMacLightLaf;
65 import com.formdev.flatlaf.util.SystemInfo;
66 import com.threerings.getdown.util.LaunchUtil;
67
68 //import edu.stanford.ejalbert.launching.IBrowserLaunching;
69 import groovy.lang.Binding;
70 import groovy.util.GroovyScriptEngine;
71 import jalview.bin.argparser.Arg;
72 import jalview.bin.argparser.Arg.Opt;
73 import jalview.bin.argparser.Arg.Type;
74 import jalview.bin.argparser.ArgParser;
75 import jalview.bin.argparser.BootstrapArgs;
76 import jalview.ext.so.SequenceOntology;
77 import jalview.gui.AlignFrame;
78 import jalview.gui.Desktop;
79 import jalview.gui.PromptUserConfig;
80 import jalview.gui.QuitHandler;
81 import jalview.gui.QuitHandler.QResponse;
82 import jalview.gui.StructureViewerBase;
83 import jalview.io.AppletFormatAdapter;
84 import jalview.io.BioJsHTMLOutput;
85 import jalview.io.DataSourceType;
86 import jalview.io.FileFormat;
87 import jalview.io.FileFormatException;
88 import jalview.io.FileFormatI;
89 import jalview.io.FileFormats;
90 import jalview.io.FileLoader;
91 import jalview.io.HtmlSvgOutput;
92 import jalview.io.IdentifyFile;
93 import jalview.io.NewickFile;
94 import jalview.io.exceptions.ImageOutputException;
95 import jalview.io.gff.SequenceOntologyFactory;
96 import jalview.schemes.ColourSchemeI;
97 import jalview.schemes.ColourSchemeProperty;
98 import jalview.util.ChannelProperties;
99 import jalview.util.HttpUtils;
100 import jalview.util.LaunchUtils;
101 import jalview.util.MessageManager;
102 import jalview.util.Platform;
103 import jalview.ws.jws2.Jws2Discoverer;
104
105 /**
106  * Main class for Jalview Application <br>
107  * <br>
108  * start with: java -classpath "$PATH_TO_LIB$/*:$PATH_TO_CLASSES$" \
109  * jalview.bin.Jalview
110  * 
111  * or on Windows: java -classpath "$PATH_TO_LIB$/*;$PATH_TO_CLASSES$" \
112  * jalview.bin.Jalview jalview.bin.Jalview
113  * 
114  * (ensure -classpath arg is quoted to avoid shell expansion of '*' and do not
115  * embellish '*' to e.g. '*.jar')
116  * 
117  * @author $author$
118  * @version $Revision$
119  */
120 public class Jalview
121 {
122   static
123   {
124     Platform.getURLCommandArguments();
125     Platform.addJ2SDirectDatabaseCall("https://www.jalview.org");
126     Platform.addJ2SDirectDatabaseCall("http://www.jalview.org");
127     Platform.addJ2SDirectDatabaseCall("http://www.compbio.dundee.ac.uk");
128     Platform.addJ2SDirectDatabaseCall("https://www.compbio.dundee.ac.uk");
129   }
130
131   /*
132    * singleton instance of this class
133    */
134   private static Jalview instance;
135
136   private Desktop desktop;
137
138   protected Commands cmds;
139
140   public static AlignFrame currentAlignFrame;
141
142   public ArgParser argparser = null;
143
144   public BootstrapArgs bootstrapArgs = null;
145
146   private boolean QUIET = false;
147
148   public static boolean quiet()
149   {
150     return Jalview.getInstance() != null && Jalview.getInstance().QUIET;
151   }
152
153   static
154   {
155     if (!Platform.isJS())
156     /**
157      * Java only
158      * 
159      * @j2sIgnore
160      */
161     {
162       // grab all the rights we can for the JVM
163       Policy.setPolicy(new Policy()
164       {
165         @Override
166         public PermissionCollection getPermissions(CodeSource codesource)
167         {
168           Permissions perms = new Permissions();
169           perms.add(new AllPermission());
170           return (perms);
171         }
172
173         @Override
174         public void refresh()
175         {
176         }
177       });
178     }
179   }
180
181   /**
182    * keep track of feature fetching tasks.
183    * 
184    * @author JimP
185    * 
186    */
187   class FeatureFetcher
188   {
189     /*
190      * TODO: generalise to track all jalview events to orchestrate batch processing
191      * events.
192      */
193
194     private int queued = 0;
195
196     private int running = 0;
197
198     public FeatureFetcher()
199     {
200
201     }
202
203     public void addFetcher(final AlignFrame af,
204             final Vector<String> dasSources)
205     {
206       final long id = System.currentTimeMillis();
207       queued++;
208       final FeatureFetcher us = this;
209       new Thread(new Runnable()
210       {
211
212         @Override
213         public void run()
214         {
215           synchronized (us)
216           {
217             queued--;
218             running++;
219           }
220
221           af.setProgressBar(MessageManager
222                   .getString("status.das_features_being_retrived"), id);
223           af.featureSettings_actionPerformed(null);
224           af.setProgressBar(null, id);
225           synchronized (us)
226           {
227             running--;
228           }
229         }
230       }).start();
231     }
232
233     public synchronized boolean allFinished()
234     {
235       return queued == 0 && running == 0;
236     }
237
238   }
239
240   public static Jalview getInstance()
241   {
242     return instance;
243   }
244
245   /**
246    * main class for Jalview application
247    * 
248    * @param args
249    *          open <em>filename</em>
250    */
251   public static void main(String[] args)
252   {
253     // setLogging(); // BH - for event debugging in JavaScript
254     instance = new Jalview();
255     instance.doMain(args);
256   }
257
258   private static void logClass(String name)
259   {
260     // BH - for event debugging in JavaScript
261     ConsoleHandler consoleHandler = new ConsoleHandler();
262     consoleHandler.setLevel(Level.ALL);
263     Logger logger = Logger.getLogger(name);
264     logger.setLevel(Level.ALL);
265     logger.addHandler(consoleHandler);
266   }
267
268   @SuppressWarnings("unused")
269   private static void setLogging()
270   {
271
272     /**
273      * @j2sIgnore
274      * 
275      */
276     {
277       System.out.println("not in js");
278     }
279
280     // BH - for event debugging in JavaScript (Java mode only)
281     if (!Platform.isJS())
282     /**
283      * Java only
284      * 
285      * @j2sIgnore
286      */
287     {
288       Logger.getLogger("").setLevel(Level.ALL);
289       logClass("java.awt.EventDispatchThread");
290       logClass("java.awt.EventQueue");
291       logClass("java.awt.Component");
292       logClass("java.awt.focus.Component");
293       logClass("java.awt.focus.DefaultKeyboardFocusManager");
294     }
295
296   }
297
298   /**
299    * @param args
300    */
301   void doMain(String[] args)
302   {
303     if (!Platform.isJS())
304     {
305       System.setSecurityManager(null);
306     }
307
308     if (args == null || args.length == 0 || (args.length == 1
309             && (args[0] == null || args[0].length() == 0)))
310     {
311       args = new String[] {};
312     }
313
314     // get args needed before proper ArgParser
315     bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
316
317     if (!Platform.isJS())
318     {
319       // are we being --quiet ?
320       if (bootstrapArgs.contains(Arg.QUIET))
321       {
322         QUIET = true;
323         OutputStream devNull = new OutputStream()
324         {
325
326           @Override
327           public void write(int b)
328           {
329             // DO NOTHING
330           }
331         };
332         System.setOut(new PrintStream(devNull));
333         // redirecting stderr not working
334         if (bootstrapArgs.getList(Arg.QUIET).size() > 1)
335         {
336           System.setErr(new PrintStream(devNull));
337         }
338       }
339
340       if (bootstrapArgs.contains(Arg.HELP)
341               || bootstrapArgs.contains(Arg.VERSION))
342       {
343         QUIET = true;
344       }
345     }
346
347     // set individual session preferences
348     if (bootstrapArgs.contains(Arg.P))
349     {
350       for (String kev : bootstrapArgs.getValueList(Arg.P))
351       {
352         if (kev == null)
353         {
354           continue;
355         }
356         int equalsIndex = kev.indexOf(ArgParser.EQUALS);
357         if (equalsIndex > -1)
358         {
359           String key = kev.substring(0, equalsIndex);
360           String val = kev.substring(equalsIndex + 1);
361           Cache.setSessionProperty(key, val);
362         }
363       }
364     }
365
366     // Move any new getdown-launcher-new.jar into place over old
367     // getdown-launcher.jar
368     String appdirString = System.getProperty("getdownappdir");
369     if (appdirString != null && appdirString.length() > 0)
370     {
371       final File appdir = new File(appdirString);
372       new Thread()
373       {
374
375         @Override
376         public void run()
377         {
378           LaunchUtil.upgradeGetdown(
379                   new File(appdir, "getdown-launcher-old.jar"),
380                   new File(appdir, "getdown-launcher.jar"),
381                   new File(appdir, "getdown-launcher-new.jar"));
382         }
383       }.start();
384     }
385
386     if (!quiet() || bootstrapArgs.contains(Arg.VERSION))
387     {
388       System.out.println(
389               "Java version: " + System.getProperty("java.version"));
390       System.out.println("Java home: " + System.getProperty("java.home"));
391       System.out.println("Java arch: " + System.getProperty("os.arch") + " "
392               + System.getProperty("os.name") + " "
393               + System.getProperty("os.version"));
394
395       String val = System.getProperty("sys.install4jVersion");
396       if (val != null)
397       {
398         System.out.println("Install4j version: " + val);
399       }
400       val = System.getProperty("installer_template_version");
401       if (val != null)
402       {
403         System.out.println("Install4j template version: " + val);
404       }
405       val = System.getProperty("launcher_version");
406       if (val != null)
407       {
408         System.out.println("Launcher version: " + val);
409       }
410     }
411
412     if (Platform.isLinux() && LaunchUtils.getJavaVersion() < 11)
413     {
414       System.setProperty("flatlaf.uiScale", "1");
415     }
416
417     // get bootstrap properties (mainly for the logger level)
418     Properties bootstrapProperties = Cache
419             .bootstrapProperties(bootstrapArgs.getValue(Arg.PROPS));
420
421     // report Jalview version
422     Cache.loadBuildProperties(
423             !quiet() || bootstrapArgs.contains(Arg.VERSION));
424
425     // stop now if only after --version
426     if (bootstrapArgs.contains(Arg.VERSION))
427     {
428       Jalview.exit(null, 0);
429     }
430
431     // old ArgsParser
432     ArgsParser aparser = new ArgsParser(args);
433
434     // old
435     boolean headless = false;
436     // new
437     boolean headlessArg = false;
438
439     try
440     {
441       String logLevel = null;
442       if (bootstrapArgs.contains(Arg.TRACE))
443       {
444         logLevel = "TRACE";
445       }
446       else if (bootstrapArgs.contains(Arg.DEBUG))
447       {
448         logLevel = "DEBUG";
449       }
450       if (logLevel == null && !(bootstrapProperties == null))
451       {
452         logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
453       }
454       Console.initLogger(logLevel);
455     } catch (NoClassDefFoundError error)
456     {
457       error.printStackTrace();
458       String message = "\nEssential logging libraries not found."
459               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
460       Jalview.exit(message, 0);
461     }
462
463     // register SIGTERM listener
464     Runtime.getRuntime().addShutdownHook(new Thread()
465     {
466       @Override
467       public void run()
468       {
469         Console.debug("Running shutdown hook");
470         QuitHandler.startForceQuit();
471         boolean closeExternal = Cache
472                 .getDefault("DEFAULT_CLOSE_EXTERNAL_VIEWERS", false)
473                 || Cache.getDefault("ALWAYS_CLOSE_EXTERNAL_VIEWERS", false);
474         StructureViewerBase.setQuitClose(closeExternal);
475         if (desktop != null)
476         {
477           for (JInternalFrame frame : Desktop.desktop.getAllFrames())
478           {
479             if (frame instanceof StructureViewerBase)
480             {
481               ((StructureViewerBase) frame).closeViewer(closeExternal);
482             }
483           }
484         }
485
486         if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
487         {
488           // Got to here by a SIGTERM signal.
489           // Note we will not actually cancel the quit from here -- it's too
490           // late -- but we can wait for saving files and close external viewers
491           // if configured.
492           // Close viewers/Leave viewers open
493           Console.debug("Checking for saving files");
494           QuitHandler.getQuitResponse(false);
495         }
496         else
497         {
498           Console.debug("Nothing more to do");
499         }
500         Console.debug("Exiting, bye!");
501         // shutdownHook cannot be cancelled, JVM will now halt
502       }
503     });
504
505     String usrPropsFile = bootstrapArgs.contains(Arg.PROPS)
506             ? bootstrapArgs.getValue(Arg.PROPS)
507             : aparser.getValue("props");
508     // if usrPropsFile == null, loadProperties will use the Channel
509     // preferences.file
510     Cache.loadProperties(usrPropsFile);
511     if (usrPropsFile != null)
512     {
513       System.out.println(
514               "CMD [-props " + usrPropsFile + "] executed successfully!");
515       testoutput(bootstrapArgs, Arg.PROPS,
516               "test/jalview/bin/testProps.jvprops", usrPropsFile);
517     }
518
519     // --argfile=... -- OVERRIDES ALL NON-BOOTSTRAP ARGS
520     if (bootstrapArgs.contains(Arg.ARGFILE))
521     {
522       argparser = ArgParser.parseArgFiles(
523               bootstrapArgs.getValueList(Arg.ARGFILE),
524               bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
525               bootstrapArgs);
526     }
527     else
528     {
529       argparser = new ArgParser(args,
530               bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
531               bootstrapArgs);
532     }
533
534     if (!Platform.isJS())
535     /**
536      * Java only
537      * 
538      * @j2sIgnore
539      */
540     {
541       if (bootstrapArgs.contains(Arg.HELP))
542       {
543         List<Map.Entry<Type, String>> helpArgs = bootstrapArgs
544                 .getList(Arg.HELP);
545         System.out.println(Arg.usage(helpArgs.stream().map(e -> e.getKey())
546                 .collect(Collectors.toList())));
547         Jalview.exit(null, 0);
548       }
549       if (aparser.contains("help") || aparser.contains("h"))
550       {
551         /*
552          * Now using new usage statement.
553         showUsage();
554         */
555         System.out.println(Arg.usage());
556         Jalview.exit(null, 0);
557       }
558
559       // new CLI
560       headlessArg = bootstrapArgs.isHeadless();
561       if (headlessArg)
562       {
563         System.setProperty("java.awt.headless", "true");
564       }
565       // old CLI
566       if (aparser.contains("nodisplay") || aparser.contains("nogui")
567               || aparser.contains("headless"))
568       {
569         System.setProperty("java.awt.headless", "true");
570         headless = true;
571       }
572       // anything else!
573
574       // allow https handshakes to download intermediate certs if necessary
575       System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
576
577       String jabawsUrl = bootstrapArgs.getValue(Arg.JABAWS);
578       if (jabawsUrl == null)
579         jabawsUrl = aparser.getValue("jabaws");
580       if (jabawsUrl != null)
581       {
582         try
583         {
584           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
585           System.out.println(
586                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
587           testoutput(bootstrapArgs, Arg.JABAWS,
588                   "http://www.compbio.dundee.ac.uk/jabaws", jabawsUrl);
589         } catch (MalformedURLException e)
590         {
591           System.err.println(
592                   "Invalid jabaws parameter: " + jabawsUrl + " ignored");
593         }
594       }
595     }
596
597     List<String> setprops = new ArrayList<>();
598     if (bootstrapArgs.contains(Arg.SETPROP))
599     {
600       setprops = bootstrapArgs.getValueList(Arg.SETPROP);
601     }
602     else
603     {
604       String sp = aparser.getValue("setprop");
605       while (sp != null)
606       {
607         setprops.add(sp);
608         sp = aparser.getValue("setprop");
609       }
610     }
611     for (String setprop : setprops)
612     {
613       int p = setprop.indexOf('=');
614       if (p == -1)
615       {
616         System.err
617                 .println("Ignoring invalid setprop argument : " + setprop);
618       }
619       else
620       {
621         System.out.println("Executing setprop argument: " + setprop);
622         if (Platform.isJS())
623         {
624           Cache.setProperty(setprop.substring(0, p),
625                   setprop.substring(p + 1));
626         }
627         // DISABLED FOR SECURITY REASONS
628         // TODO: add a property to allow properties to be overriden by cli args
629         // Cache.setProperty(setprop.substring(0,p), setprop.substring(p+1));
630       }
631     }
632     if (System.getProperty("java.awt.headless") != null
633             && System.getProperty("java.awt.headless").equals("true"))
634     {
635       headless = true;
636     }
637     System.setProperty("http.agent", HttpUtils.getUserAgent());
638
639     try
640     {
641       Console.initLogger();
642     } catch (
643
644     NoClassDefFoundError error)
645     {
646       error.printStackTrace();
647       String message = "\nEssential logging libraries not found."
648               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
649       Jalview.exit(message, 0);
650     }
651     desktop = null;
652
653     if (!(headless || headlessArg))
654       setLookAndFeel();
655
656     /*
657      * configure 'full' SO model if preferences say to, else use the default (full SO)
658      * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
659      */
660     boolean soDefault = !Platform.isJS();
661     if (Cache.getDefault("USE_FULL_SO", soDefault))
662     {
663       SequenceOntologyFactory.setInstance(new SequenceOntology());
664     }
665
666     if (!(headless || headlessArg))
667     {
668       Desktop.nosplash = "false".equals(bootstrapArgs.getValue(Arg.SPLASH))
669               || aparser.contains("nosplash")
670               || Cache.getDefault("SPLASH", "true").equals("false");
671       desktop = new Desktop();
672       desktop.setInBatchMode(true); // indicate we are starting up
673
674       try
675       {
676         JalviewTaskbar.setTaskbar(this);
677       } catch (Exception e)
678       {
679         Console.info("Cannot set Taskbar");
680         Console.error(e.getMessage());
681         // e.printStackTrace();
682       } catch (Throwable t)
683       {
684         Console.info("Cannot set Taskbar");
685         Console.error(t.getMessage());
686         // t.printStackTrace();
687       }
688
689       // set Proxy settings before all the internet calls
690       Cache.setProxyPropertiesFromPreferences();
691
692       desktop.setVisible(true);
693
694       if (!Platform.isJS())
695       /**
696        * Java only
697        * 
698        * @j2sIgnore
699        */
700       {
701
702         /**
703          * Check to see that the JVM version being run is suitable for the Java
704          * version this Jalview was compiled for. Popup a warning if not.
705          */
706         if (!LaunchUtils.checkJavaVersion())
707         {
708           Console.warn("The Java version being used (Java "
709                   + LaunchUtils.getJavaVersion()
710                   + ") may lead to problems. This installation of Jalview should be used with Java "
711                   + LaunchUtils.getJavaCompileVersion() + ".");
712
713           if (!LaunchUtils
714                   .getBooleanUserPreference("IGNORE_JVM_WARNING_POPUP"))
715           {
716             Object[] options = {
717                 MessageManager.getString("label.continue") };
718             JOptionPane.showOptionDialog(null,
719                     MessageManager.formatMessage(
720                             "warning.wrong_jvm_version_message",
721                             LaunchUtils.getJavaVersion(),
722                             LaunchUtils.getJavaCompileVersion()),
723                     MessageManager
724                             .getString("warning.wrong_jvm_version_title"),
725                     JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
726                     null, options, options[0]);
727           }
728         }
729
730         boolean webservicediscovery = bootstrapArgs
731                 .getBoolean(Arg.WEBSERVICEDISCOVERY);
732         if (aparser.contains("nowebservicediscovery"))
733           webservicediscovery = false;
734         if (webservicediscovery)
735         {
736           desktop.startServiceDiscovery();
737         }
738         else
739         {
740           testoutput(argparser, Arg.WEBSERVICEDISCOVERY);
741         }
742
743         boolean usagestats = !bootstrapArgs.getBoolean(Arg.NOUSAGESTATS);
744         if (aparser.contains("nousagestats"))
745           usagestats = false;
746         if (usagestats)
747         {
748           startUsageStats(desktop);
749           testoutput(argparser, Arg.NOUSAGESTATS);
750         }
751         else
752         {
753           System.out.println("CMD [-nousagestats] executed successfully!");
754           testoutput(argparser, Arg.NOUSAGESTATS);
755         }
756
757         boolean questionnaire = bootstrapArgs.getBoolean(Arg.QUESTIONNAIRE);
758         if (aparser.contains("noquestionnaire"))
759           questionnaire = false;
760         if (questionnaire)
761         {
762           String url = aparser.getValue("questionnaire");
763           if (url != null)
764           {
765             // Start the desktop questionnaire prompter with the specified
766             // questionnaire
767             Console.debug("Starting questionnaire url at " + url);
768             desktop.checkForQuestionnaire(url);
769             System.out.println("CMD questionnaire[-" + url
770                     + "] executed successfully!");
771           }
772           else
773           {
774             if (Cache.getProperty("NOQUESTIONNAIRES") == null)
775             {
776               // Start the desktop questionnaire prompter with the specified
777               // questionnaire
778               // String defurl =
779               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
780               // //
781               String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
782               Console.debug(
783                       "Starting questionnaire with default url: " + defurl);
784               desktop.checkForQuestionnaire(defurl);
785             }
786           }
787         }
788         else
789         {
790           System.out
791                   .println("CMD [-noquestionnaire] executed successfully!");
792           testoutput(argparser, Arg.QUESTIONNAIRE);
793         }
794
795         if ((!aparser.contains("nonews")
796                 && Cache.getProperty("NONEWS") == null
797                 && !"false".equals(bootstrapArgs.getValue(Arg.NEWS)))
798                 || "true".equals(bootstrapArgs.getValue(Arg.NEWS)))
799         {
800           desktop.checkForNews();
801         }
802
803         if (!aparser.contains("nohtmltemplates")
804                 && Cache.getProperty("NOHTMLTEMPLATES") == null)
805         {
806           BioJsHTMLOutput.updateBioJS();
807         }
808       }
809     }
810     // Run Commands from cli
811     cmds = new Commands(argparser, headlessArg);
812     boolean commandsSuccess = cmds.argsWereParsed();
813
814     if (commandsSuccess)
815     {
816       if (headlessArg)
817       {
818         if (argparser.getBoolean(Arg.NOQUIT))
819         {
820           Console.warn(
821                   "Completed " + Arg.HEADLESS.getName() + " commands, but "
822                           + Arg.NOQUIT + " is set so not quitting!");
823         }
824         else
825         {
826           Jalview.exit("Successfully completed commands in headless mode",
827                   0);
828         }
829       }
830       Console.info("Successfully completed commands");
831     }
832     else
833     {
834       if (headlessArg)
835       {
836         Jalview.exit("Error when running Commands in headless mode", 1);
837       }
838       Console.warn("Error when running commands");
839     }
840
841     // Check if JVM and compile version might cause problems and log if it
842     // might.
843     if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
844     {
845       Console.warn("The Java version being used (Java "
846               + LaunchUtils.getJavaVersion()
847               + ") may lead to problems. This installation of Jalview should be used with Java "
848               + LaunchUtils.getJavaCompileVersion() + ".");
849     }
850
851     String file = null, data = null;
852
853     FileFormatI format = null;
854
855     DataSourceType protocol = null;
856
857     FileLoader fileLoader = new FileLoader(!headless);
858
859     String groovyscript = null; // script to execute after all loading is
860     // completed one way or another
861     // extract groovy argument and execute if necessary
862     groovyscript = aparser.getValue("groovy", true);
863     file = aparser.getValue("open", true);
864
865     if (file == null && desktop == null && !commandsSuccess)
866     {
867       Jalview.exit("No files to open!", 1);
868     }
869
870     long progress = -1;
871     // Finally, deal with the remaining input data.
872     if (file != null)
873     {
874       if (!headless)
875       {
876         desktop.setProgressBar(
877                 MessageManager
878                         .getString("status.processing_commandline_args"),
879                 progress = System.currentTimeMillis());
880       }
881       System.out.println("CMD [-open " + file + "] executed successfully!");
882
883       if (!Platform.isJS())
884       /**
885        * ignore in JavaScript -- can't just file existence - could load it?
886        * 
887        * @j2sIgnore
888        */
889       {
890         if (!HttpUtils.startsWithHttpOrHttps(file))
891         {
892           if (!(new File(file)).exists())
893           {
894             if (headless)
895             {
896               Jalview.exit(
897                       "Can't find file '" + file + "' in headless mode", 1);
898             }
899             Console.warn("Can't find file'" + file + "'");
900           }
901         }
902       }
903
904       protocol = AppletFormatAdapter.checkProtocol(file);
905
906       try
907       {
908         format = new IdentifyFile().identify(file, protocol);
909       } catch (FileFormatException e1)
910       {
911         // TODO ?
912       }
913
914       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
915               format);
916       if (af == null)
917       {
918         System.out.println("error");
919       }
920       else
921       {
922         setCurrentAlignFrame(af);
923         data = aparser.getValue("colour", true);
924         if (data != null)
925         {
926           data.replaceAll("%20", " ");
927
928           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
929                   af.getViewport(), af.getViewport().getAlignment(), data);
930
931           if (cs != null)
932           {
933             System.out.println(
934                     "CMD [-colour " + data + "] executed successfully!");
935           }
936           af.changeColour(cs);
937         }
938
939         // Must maintain ability to use the groups flag
940         data = aparser.getValue("groups", true);
941         if (data != null)
942         {
943           af.parseFeaturesFile(data,
944                   AppletFormatAdapter.checkProtocol(data));
945           // System.out.println("Added " + data);
946           System.out.println(
947                   "CMD groups[-" + data + "]  executed successfully!");
948         }
949         data = aparser.getValue("features", true);
950         if (data != null)
951         {
952           af.parseFeaturesFile(data,
953                   AppletFormatAdapter.checkProtocol(data));
954           // System.out.println("Added " + data);
955           System.out.println(
956                   "CMD [-features " + data + "]  executed successfully!");
957         }
958
959         data = aparser.getValue("annotations", true);
960         if (data != null)
961         {
962           af.loadJalviewDataFile(data, null, null, null);
963           // System.out.println("Added " + data);
964           System.out.println(
965                   "CMD [-annotations " + data + "] executed successfully!");
966         }
967         // set or clear the sortbytree flag.
968         if (aparser.contains("sortbytree"))
969         {
970           af.getViewport().setSortByTree(true);
971           if (af.getViewport().getSortByTree())
972           {
973             System.out.println("CMD [-sortbytree] executed successfully!");
974           }
975         }
976         if (aparser.contains("no-annotation"))
977         {
978           af.getViewport().setShowAnnotation(false);
979           if (!af.getViewport().isShowAnnotation())
980           {
981             System.out.println("CMD no-annotation executed successfully!");
982           }
983         }
984         if (aparser.contains("nosortbytree"))
985         {
986           af.getViewport().setSortByTree(false);
987           if (!af.getViewport().getSortByTree())
988           {
989             System.out
990                     .println("CMD [-nosortbytree] executed successfully!");
991           }
992         }
993         data = aparser.getValue("tree", true);
994         if (data != null)
995         {
996           try
997           {
998             System.out.println(
999                     "CMD [-tree " + data + "] executed successfully!");
1000             NewickFile nf = new NewickFile(data,
1001                     AppletFormatAdapter.checkProtocol(data));
1002             af.getViewport()
1003                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
1004           } catch (IOException ex)
1005           {
1006             System.err.println("Couldn't add tree " + data);
1007             ex.printStackTrace(System.err);
1008           }
1009         }
1010
1011         if (groovyscript != null)
1012         {
1013           // Execute the groovy script after we've done all the rendering stuff
1014           // and before any images or figures are generated.
1015           System.out.println("Executing script " + groovyscript);
1016           executeGroovyScript(groovyscript, af);
1017           System.out.println("CMD groovy[" + groovyscript
1018                   + "] executed successfully!");
1019           groovyscript = null;
1020         }
1021         String imageName = "unnamed.png";
1022         while (aparser.getSize() > 1)
1023         {
1024           try
1025           {
1026             String outputFormat = aparser.nextValue();
1027             file = aparser.nextValue();
1028
1029             if (outputFormat.equalsIgnoreCase("png"))
1030             {
1031               System.out.println("Creating PNG image: " + file);
1032               af.createPNG(new File(file));
1033               imageName = (new File(file)).getName();
1034               continue;
1035             }
1036             else if (outputFormat.equalsIgnoreCase("svg"))
1037             {
1038               System.out.println("Creating SVG image: " + file);
1039               File imageFile = new File(file);
1040               imageName = imageFile.getName();
1041               af.createSVG(imageFile);
1042               continue;
1043             }
1044             else if (outputFormat.equalsIgnoreCase("html"))
1045             {
1046               File imageFile = new File(file);
1047               imageName = imageFile.getName();
1048               HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
1049
1050               System.out.println("Creating HTML image: " + file);
1051               htmlSVG.exportHTML(file);
1052               continue;
1053             }
1054             else if (outputFormat.equalsIgnoreCase("biojsmsa"))
1055             {
1056               if (file == null)
1057               {
1058                 System.err.println("The output html file must not be null");
1059                 return;
1060               }
1061               try
1062               {
1063                 BioJsHTMLOutput.refreshVersionInfo(
1064                         BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
1065               } catch (URISyntaxException e)
1066               {
1067                 e.printStackTrace();
1068               }
1069               BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
1070               System.out.println(
1071                       "Creating BioJS MSA Viwer HTML file: " + file);
1072               bjs.exportHTML(file);
1073               continue;
1074             }
1075             else if (outputFormat.equalsIgnoreCase("imgMap"))
1076             {
1077               System.out.println("Creating image map: " + file);
1078               af.createImageMap(new File(file), imageName);
1079               continue;
1080             }
1081             else if (outputFormat.equalsIgnoreCase("eps"))
1082             {
1083               File outputFile = new File(file);
1084               System.out.println(
1085                       "Creating EPS file: " + outputFile.getAbsolutePath());
1086               af.createEPS(outputFile);
1087               continue;
1088             }
1089
1090             FileFormatI outFormat = null;
1091             try
1092             {
1093               outFormat = FileFormats.getInstance().forName(outputFormat);
1094             } catch (Exception formatP)
1095             {
1096               System.out.println("Couldn't parse " + outFormat
1097                       + " as a valid Jalview format string.");
1098             }
1099             if (outFormat != null)
1100             {
1101               if (!outFormat.isWritable())
1102               {
1103                 System.out.println(
1104                         "This version of Jalview does not support alignment export as "
1105                                 + outputFormat);
1106               }
1107               else
1108               {
1109                 af.saveAlignment(file, outFormat);
1110                 if (af.isSaveAlignmentSuccessful())
1111                 {
1112                   System.out.println("Written alignment in "
1113                           + outFormat.getName() + " format to " + file);
1114                 }
1115                 else
1116                 {
1117                   System.out.println("Error writing file " + file + " in "
1118                           + outFormat.getName() + " format!!");
1119                 }
1120               }
1121             }
1122           } catch (ImageOutputException ioexc)
1123           {
1124             System.out.println(
1125                     "Unexpected error whilst exporting image to " + file);
1126             ioexc.printStackTrace();
1127           }
1128
1129         }
1130
1131         while (aparser.getSize() > 0)
1132         {
1133           System.out.println("Unknown arg: " + aparser.nextValue());
1134         }
1135       }
1136     }
1137
1138     AlignFrame startUpAlframe = null;
1139     // We'll only open the default file if the desktop is visible.
1140     // And the user
1141     // ////////////////////
1142
1143     if (!Platform.isJS() && !headless && file == null
1144             && Cache.getDefault("SHOW_STARTUP_FILE", true)
1145             && !cmds.commandArgsProvided()
1146             && !bootstrapArgs.getBoolean(Arg.NOSTARTUPFILE))
1147     // don't open the startup file if command line args have been processed
1148     // (&& !Commands.commandArgsProvided())
1149     /**
1150      * Java only
1151      * 
1152      * @j2sIgnore
1153      */
1154     {
1155       file = Cache.getDefault("STARTUP_FILE",
1156               Cache.getDefault("www.jalview.org", "https://www.jalview.org")
1157                       + "/examples/exampleFile_2_7.jvp");
1158       if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
1159               || file.equals(
1160                       "http://www.jalview.org/examples/exampleFile_2_7.jar"))
1161       {
1162         file.replace("http:", "https:");
1163         // hardwire upgrade of the startup file
1164         file.replace("_2_3", "_2_7");
1165         file.replace("2_7.jar", "2_7.jvp");
1166         // and remove the stale setting
1167         Cache.removeProperty("STARTUP_FILE");
1168       }
1169
1170       protocol = AppletFormatAdapter.checkProtocol(file);
1171
1172       if (file.endsWith(".jar"))
1173       {
1174         format = FileFormat.Jalview;
1175       }
1176       else
1177       {
1178         try
1179         {
1180           format = new IdentifyFile().identify(file, protocol);
1181         } catch (FileFormatException e)
1182         {
1183           // TODO what?
1184         }
1185       }
1186
1187       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
1188               format);
1189       // don't ask to save when quitting if only the startup file has been
1190       // opened
1191       Console.debug("Resetting up-to-date flag for startup file");
1192       startUpAlframe.getViewport().setSavedUpToDate(true);
1193       // extract groovy arguments before anything else.
1194     }
1195
1196     // Once all other stuff is done, execute any groovy scripts (in order)
1197     if (groovyscript != null)
1198     {
1199       if (Cache.groovyJarsPresent())
1200       {
1201         System.out.println("Executing script " + groovyscript);
1202         executeGroovyScript(groovyscript, startUpAlframe);
1203       }
1204       else
1205       {
1206         System.err.println(
1207                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
1208                         + groovyscript);
1209       }
1210     }
1211     // and finally, turn off batch mode indicator - if the desktop still exists
1212     if (desktop != null)
1213     {
1214       if (progress != -1)
1215       {
1216         desktop.setProgressBar(null, progress);
1217       }
1218       desktop.setInBatchMode(false);
1219     }
1220   }
1221
1222   private static void setLookAndFeel()
1223   {
1224     if (!Platform.isJS())
1225     /**
1226      * Java only
1227      * 
1228      * @j2sIgnore
1229      */
1230     {
1231       // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
1232       // "mac" or "flat"
1233       // If not set (or chosen laf fails), use the normal SystemLaF and if on
1234       // Mac,
1235       // try Quaqua/Vaqua.
1236       String lafProp = System.getProperty("laf");
1237       String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
1238       String laf = "none";
1239       if (lafProp != null)
1240       {
1241         laf = lafProp;
1242       }
1243       else if (lafSetting != null)
1244       {
1245         laf = lafSetting;
1246       }
1247       boolean lafSet = false;
1248       switch (laf)
1249       {
1250       case "crossplatform":
1251         lafSet = setCrossPlatformLookAndFeel();
1252         if (!lafSet)
1253         {
1254           Console.error("Could not set requested laf=" + laf);
1255         }
1256         break;
1257       case "system":
1258         lafSet = setSystemLookAndFeel();
1259         if (!lafSet)
1260         {
1261           Console.error("Could not set requested laf=" + laf);
1262         }
1263         break;
1264       case "gtk":
1265         lafSet = setGtkLookAndFeel();
1266         if (!lafSet)
1267         {
1268           Console.error("Could not set requested laf=" + laf);
1269         }
1270         break;
1271       case "metal":
1272         lafSet = setMetalLookAndFeel();
1273         if (!lafSet)
1274         {
1275           Console.error("Could not set requested laf=" + laf);
1276         }
1277         break;
1278       case "nimbus":
1279         lafSet = setNimbusLookAndFeel();
1280         if (!lafSet)
1281         {
1282           Console.error("Could not set requested laf=" + laf);
1283         }
1284         break;
1285       case "flat":
1286         lafSet = setFlatLookAndFeel();
1287         if (!lafSet)
1288         {
1289           Console.error("Could not set requested laf=" + laf);
1290         }
1291         break;
1292       case "mac":
1293         lafSet = setMacLookAndFeel();
1294         if (!lafSet)
1295         {
1296           Console.error("Could not set requested laf=" + laf);
1297         }
1298         break;
1299       case "none":
1300         break;
1301       default:
1302         Console.error("Requested laf=" + laf + " not implemented");
1303       }
1304       if (!lafSet)
1305       {
1306         // Flatlaf default for everyone!
1307         lafSet = setFlatLookAndFeel();
1308         if (!lafSet)
1309         {
1310           setSystemLookAndFeel();
1311         }
1312         if (Platform.isLinux())
1313         {
1314           setLinuxLookAndFeel();
1315         }
1316         if (Platform.isMac())
1317         {
1318           setMacLookAndFeel();
1319         }
1320       }
1321     }
1322   }
1323
1324   private static boolean setCrossPlatformLookAndFeel()
1325   {
1326     boolean set = false;
1327     try
1328     {
1329       UIManager.setLookAndFeel(
1330               UIManager.getCrossPlatformLookAndFeelClassName());
1331       set = true;
1332     } catch (Exception ex)
1333     {
1334       Console.error("Unexpected Look and Feel Exception");
1335       Console.error(ex.getMessage());
1336       Console.debug(Cache.getStackTraceString(ex));
1337     }
1338     return set;
1339   }
1340
1341   private static boolean setSystemLookAndFeel()
1342   {
1343     boolean set = false;
1344     try
1345     {
1346       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1347       set = true;
1348     } catch (Exception ex)
1349     {
1350       Console.error("Unexpected Look and Feel Exception");
1351       Console.error(ex.getMessage());
1352       Console.debug(Cache.getStackTraceString(ex));
1353     }
1354     return set;
1355   }
1356
1357   private static boolean setSpecificLookAndFeel(String name,
1358           String className, boolean nameStartsWith)
1359   {
1360     boolean set = false;
1361     try
1362     {
1363       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
1364       {
1365         if (info.getName() != null && nameStartsWith
1366                 ? info.getName().toLowerCase(Locale.ROOT)
1367                         .startsWith(name.toLowerCase(Locale.ROOT))
1368                 : info.getName().toLowerCase(Locale.ROOT)
1369                         .equals(name.toLowerCase(Locale.ROOT)))
1370         {
1371           className = info.getClassName();
1372           break;
1373         }
1374       }
1375       UIManager.setLookAndFeel(className);
1376       set = true;
1377     } catch (Exception ex)
1378     {
1379       Console.error("Unexpected Look and Feel Exception");
1380       Console.error(ex.getMessage());
1381       Console.debug(Cache.getStackTraceString(ex));
1382     }
1383     return set;
1384   }
1385
1386   private static boolean setGtkLookAndFeel()
1387   {
1388     return setSpecificLookAndFeel("gtk",
1389             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
1390   }
1391
1392   private static boolean setMetalLookAndFeel()
1393   {
1394     return setSpecificLookAndFeel("metal",
1395             "javax.swing.plaf.metal.MetalLookAndFeel", false);
1396   }
1397
1398   private static boolean setNimbusLookAndFeel()
1399   {
1400     return setSpecificLookAndFeel("nimbus",
1401             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
1402   }
1403
1404   private static boolean setFlatLookAndFeel()
1405   {
1406     boolean set = false;
1407     if (SystemInfo.isMacOS)
1408     {
1409       try
1410       {
1411         UIManager.setLookAndFeel(
1412                 "com.formdev.flatlaf.themes.FlatMacLightLaf");
1413         set = true;
1414         Console.debug("Using FlatMacLightLaf");
1415       } catch (ClassNotFoundException | InstantiationException
1416               | IllegalAccessException | UnsupportedLookAndFeelException e)
1417       {
1418         Console.debug("Exception loading FlatLightLaf", e);
1419       }
1420       System.setProperty("apple.laf.useScreenMenuBar", "true");
1421       System.setProperty("apple.awt.application.name",
1422               ChannelProperties.getProperty("app_name"));
1423       System.setProperty("apple.awt.application.appearance", "system");
1424       if (SystemInfo.isMacFullWindowContentSupported
1425               && Desktop.desktop != null)
1426       {
1427         Console.debug("Setting transparent title bar");
1428         Desktop.desktop.getRootPane()
1429                 .putClientProperty("apple.awt.fullWindowContent", true);
1430         Desktop.desktop.getRootPane()
1431                 .putClientProperty("apple.awt.transparentTitleBar", true);
1432         Desktop.desktop.getRootPane()
1433                 .putClientProperty("apple.awt.fullscreenable", true);
1434       }
1435       SwingUtilities.invokeLater(() -> {
1436         FlatMacLightLaf.setup();
1437       });
1438       Console.debug("Using FlatMacLightLaf");
1439       set = true;
1440     }
1441     if (!set)
1442     {
1443       try
1444       {
1445         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1446         set = true;
1447         Console.debug("Using FlatLightLaf");
1448       } catch (ClassNotFoundException | InstantiationException
1449               | IllegalAccessException | UnsupportedLookAndFeelException e)
1450       {
1451         Console.debug("Exception loading FlatLightLaf", e);
1452       }
1453       // Windows specific properties here
1454       SwingUtilities.invokeLater(() -> {
1455         FlatLightLaf.setup();
1456       });
1457       Console.debug("Using FlatLightLaf");
1458       set = true;
1459     }
1460     else if (SystemInfo.isLinux)
1461     {
1462       try
1463       {
1464         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1465         set = true;
1466         Console.debug("Using FlatLightLaf");
1467       } catch (ClassNotFoundException | InstantiationException
1468               | IllegalAccessException | UnsupportedLookAndFeelException e)
1469       {
1470         Console.debug("Exception loading FlatLightLaf", e);
1471       }
1472       // enable custom window decorations
1473       JFrame.setDefaultLookAndFeelDecorated(true);
1474       JDialog.setDefaultLookAndFeelDecorated(true);
1475       SwingUtilities.invokeLater(() -> {
1476         FlatLightLaf.setup();
1477       });
1478       Console.debug("Using FlatLightLaf");
1479       set = true;
1480     }
1481
1482     if (!set)
1483     {
1484       try
1485       {
1486         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1487         set = true;
1488         Console.debug("Using FlatLightLaf");
1489       } catch (ClassNotFoundException | InstantiationException
1490               | IllegalAccessException | UnsupportedLookAndFeelException e)
1491       {
1492         Console.debug("Exception loading FlatLightLaf", e);
1493       }
1494     }
1495
1496     if (set)
1497     {
1498       UIManager.put("TabbedPane.tabType", "card");
1499       UIManager.put("TabbedPane.showTabSeparators", true);
1500       UIManager.put("TabbedPane.showContentSeparator", true);
1501       // UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
1502       UIManager.put("TabbedPane.tabsOverlapBorder", true);
1503       UIManager.put("TabbedPane.hasFullBorder", true);
1504       UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
1505       UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
1506       UIManager.put("TabbedPane.smoothScrolling", true);
1507       UIManager.put("TabbedPane.tabWidthMode", "compact");
1508       UIManager.put("TabbedPane.selectedBackground", Color.white);
1509       UIManager.put("TabbedPane.background", new Color(236, 236, 236));
1510       UIManager.put("TabbedPane.hoverColor", Color.lightGray);
1511     }
1512
1513     Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
1514     return set;
1515   }
1516
1517   private static boolean setMacLookAndFeel()
1518   {
1519     boolean set = false;
1520     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
1521             ChannelProperties.getProperty("app_name"));
1522     System.setProperty("apple.laf.useScreenMenuBar", "true");
1523     /*
1524      * broken native LAFs on (ARM?) macbooks
1525     set = setQuaquaLookAndFeel();
1526     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
1527             .toLowerCase(Locale.ROOT).contains("quaqua"))
1528     {
1529       set = setVaquaLookAndFeel();
1530     }
1531      */
1532     set = setFlatLookAndFeel();
1533     return set;
1534   }
1535
1536   private static boolean setLinuxLookAndFeel()
1537   {
1538     boolean set = false;
1539     set = setFlatLookAndFeel();
1540     if (!set)
1541       set = setMetalLookAndFeel();
1542     // avoid GtkLookAndFeel -- not good results especially on HiDPI
1543     if (!set)
1544       set = setNimbusLookAndFeel();
1545     return set;
1546   }
1547
1548   /*
1549   private static void showUsage()
1550   {
1551     System.out.println(
1552             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1553                     + "-nodisplay\tRun Jalview without User Interface.\n"
1554                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1555                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1556                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1557                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1558                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1559                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1560                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1561                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1562                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1563                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1564                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1565                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1566                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1567                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1568                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1569                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1570                     + "-html FILE\tCreate HTML file from alignment.\n"
1571                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1572                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1573                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1574                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1575                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1576                     + "-nonews\tTurn off check for Jalview news.\n"
1577                     + "-nousagestats\tTurn off analytics tracking for this session.\n"
1578                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1579                     // +
1580                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1581                     // after all other properties files have been read\n\t
1582                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1583                     // passed in correctly)"
1584                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1585                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1586                     + "-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"
1587                     + "-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"
1588                     + "-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"
1589                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
1590   }
1591   */
1592
1593   private static void startUsageStats(final Desktop desktop)
1594   {
1595     /**
1596      * start a User Config prompt asking if we can log usage statistics.
1597      */
1598     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1599             "USAGESTATS",
1600             MessageManager.getString("prompt.plausible_analytics_title"),
1601             MessageManager.getString("prompt.plausible_analytics"),
1602             new Runnable()
1603             {
1604               @Override
1605               public void run()
1606               {
1607                 Console.debug("Initialising analytics for usage stats.");
1608                 Cache.initAnalytics();
1609                 Console.debug("Tracking enabled.");
1610               }
1611             }, new Runnable()
1612             {
1613               @Override
1614               public void run()
1615               {
1616                 Console.debug("Not enabling analytics.");
1617               }
1618             }, null, true);
1619     desktop.addDialogThread(prompter);
1620   }
1621
1622   /**
1623    * Locate the given string as a file and pass it to the groovy interpreter.
1624    * 
1625    * @param groovyscript
1626    *          the script to execute
1627    * @param jalviewContext
1628    *          the Jalview Desktop object passed in to the groovy binding as the
1629    *          'Jalview' object.
1630    */
1631   protected void executeGroovyScript(String groovyscript, AlignFrame af)
1632   {
1633     /**
1634      * for scripts contained in files
1635      */
1636     File tfile = null;
1637     /**
1638      * script's URI
1639      */
1640     URL sfile = null;
1641     if (groovyscript.trim().equals("STDIN"))
1642     {
1643       // read from stdin into a tempfile and execute it
1644       try
1645       {
1646         tfile = File.createTempFile("jalview", "groovy");
1647         PrintWriter outfile = new PrintWriter(
1648                 new OutputStreamWriter(new FileOutputStream(tfile)));
1649         BufferedReader br = new BufferedReader(
1650                 new InputStreamReader(System.in));
1651         String line = null;
1652         while ((line = br.readLine()) != null)
1653         {
1654           outfile.write(line + "\n");
1655         }
1656         br.close();
1657         outfile.flush();
1658         outfile.close();
1659
1660       } catch (Exception ex)
1661       {
1662         System.err.println("Failed to read from STDIN into tempfile "
1663                 + ((tfile == null) ? "(tempfile wasn't created)"
1664                         : tfile.toString()));
1665         ex.printStackTrace();
1666         return;
1667       }
1668       try
1669       {
1670         sfile = tfile.toURI().toURL();
1671       } catch (Exception x)
1672       {
1673         System.err.println(
1674                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1675                         + tfile.toURI());
1676         x.printStackTrace();
1677         return;
1678       }
1679     }
1680     else
1681     {
1682       try
1683       {
1684         sfile = new URI(groovyscript).toURL();
1685       } catch (Exception x)
1686       {
1687         tfile = new File(groovyscript);
1688         if (!tfile.exists())
1689         {
1690           System.err.println("File '" + groovyscript + "' does not exist.");
1691           return;
1692         }
1693         if (!tfile.canRead())
1694         {
1695           System.err.println("File '" + groovyscript + "' cannot be read.");
1696           return;
1697         }
1698         if (tfile.length() < 1)
1699         {
1700           System.err.println("File '" + groovyscript + "' is empty.");
1701           return;
1702         }
1703         try
1704         {
1705           sfile = tfile.getAbsoluteFile().toURI().toURL();
1706         } catch (Exception ex)
1707         {
1708           System.err.println("Failed to create a file URL for "
1709                   + tfile.getAbsoluteFile());
1710           return;
1711         }
1712       }
1713     }
1714     try
1715     {
1716       Map<String, java.lang.Object> vbinding = new HashMap<>();
1717       vbinding.put("Jalview", this);
1718       if (af != null)
1719       {
1720         vbinding.put("currentAlFrame", af);
1721       }
1722       Binding gbinding = new Binding(vbinding);
1723       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1724       gse.run(sfile.toString(), gbinding);
1725       if ("STDIN".equals(groovyscript))
1726       {
1727         // delete temp file that we made -
1728         // only if it was successfully executed
1729         tfile.delete();
1730       }
1731     } catch (Exception e)
1732     {
1733       System.err.println("Exception Whilst trying to execute file " + sfile
1734               + " as a groovy script.");
1735       e.printStackTrace(System.err);
1736
1737     }
1738   }
1739
1740   public static boolean isHeadlessMode()
1741   {
1742     String isheadless = System.getProperty("java.awt.headless");
1743     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1744     {
1745       return true;
1746     }
1747     return false;
1748   }
1749
1750   public AlignFrame[] getAlignFrames()
1751   {
1752     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1753             : Desktop.getAlignFrames();
1754
1755   }
1756
1757   /**
1758    * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
1759    */
1760   public void quit()
1761   {
1762     // System.exit will run the shutdownHook first
1763     Jalview.exit("Quitting now. Bye!", 0);
1764   }
1765
1766   public static AlignFrame getCurrentAlignFrame()
1767   {
1768     return Jalview.currentAlignFrame;
1769   }
1770
1771   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
1772   {
1773     Jalview.currentAlignFrame = currentAlignFrame;
1774   }
1775
1776   protected Commands getCommands()
1777   {
1778     return cmds;
1779   }
1780
1781   public static void exit(String message, int exitcode)
1782   {
1783     if (Console.log == null)
1784     {
1785       // Don't start the logger just to exit!
1786       if (message != null)
1787       {
1788         if (exitcode == 0)
1789         {
1790           System.out.println(message);
1791         }
1792         else
1793         {
1794           System.err.println(message);
1795         }
1796       }
1797     }
1798     else
1799     {
1800       Console.debug("Using Jalview.exit");
1801       if (message != null)
1802       {
1803         if (exitcode == 0)
1804         {
1805           Console.info(message);
1806         }
1807         else
1808         {
1809           Console.error(message);
1810         }
1811       }
1812     }
1813     if (exitcode > -1)
1814     {
1815       System.exit(exitcode);
1816     }
1817   }
1818
1819   /******************************
1820    * 
1821    * TEST OUTPUT METHODS
1822    * 
1823    ******************************/
1824   /**
1825    * method for reporting string values parsed/processed during tests
1826    * 
1827    */
1828   protected static void testoutput(ArgParser ap, Arg a, String s1,
1829           String s2)
1830   {
1831     BootstrapArgs bsa = ap.getBootstrapArgs();
1832     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1833       return;
1834     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1835     {
1836       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1837               + s2 + "' for arg " + a.argString());
1838       return;
1839     }
1840     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1841             : ap.isSet(a);
1842     if (!isset)
1843     {
1844       Console.warn("Arg '" + a.getName() + "' not set at all");
1845       return;
1846     }
1847     testoutput(true, a, s1, s2);
1848   }
1849
1850   /**
1851    * method for reporting string values parsed/processed during tests
1852    */
1853
1854   protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
1855           String s2)
1856   {
1857     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1858       return;
1859     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1860     {
1861       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1862               + s2 + "' for arg " + a.argString());
1863       return;
1864     }
1865     if (!a.hasOption(Opt.BOOTSTRAP))
1866     {
1867       Console.error("Non-bootstrap Arg '" + a.getName()
1868               + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
1869     }
1870     if (!bsa.contains(a))
1871     {
1872       Console.warn("Arg '" + a.getName() + "' not set at all");
1873       return;
1874     }
1875     testoutput(true, a, s1, s2);
1876   }
1877
1878   /**
1879    * report value set for string values parsed/processed during tests
1880    */
1881   private static void testoutput(boolean yes, Arg a, String s1, String s2)
1882   {
1883     if (yes && ((s1 == null && s2 == null)
1884             || (s1 != null && s1.equals(s2))))
1885     {
1886       System.out.println("[TESTOUTPUT] arg " + a.argString() + "='" + s1
1887               + "' was set");
1888     }
1889   }
1890
1891   /*
1892    * testoutput for boolean and unary values
1893    */
1894   protected static void testoutput(ArgParser ap, Arg a)
1895   {
1896     if (ap == null)
1897       return;
1898     BootstrapArgs bsa = ap.getBootstrapArgs();
1899     if (bsa == null)
1900       return;
1901     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1902       return;
1903     boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
1904             : ap.getBoolean(a);
1905     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1906             : ap.isSet(a);
1907     if (!isset)
1908     {
1909       Console.warn("Arg '" + a.getName() + "' not set at all");
1910       return;
1911     }
1912     testoutput(val, a);
1913   }
1914
1915   protected static void testoutput(BootstrapArgs bsa, Arg a)
1916   {
1917     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1918       return;
1919     if (!a.hasOption(Opt.BOOTSTRAP))
1920     {
1921       Console.warn("Non-bootstrap Arg '" + a.getName()
1922               + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
1923
1924     }
1925     if (!bsa.contains(a))
1926     {
1927       Console.warn("Arg '" + a.getName() + "' not set at all");
1928       return;
1929     }
1930     testoutput(bsa.getBoolean(a), a);
1931   }
1932
1933   private static void testoutput(boolean yes, Arg a)
1934   {
1935     String message = null;
1936     if (a.hasOption(Opt.BOOLEAN))
1937     {
1938       message = (yes ? a.argString() : a.negateArgString()) + " was set";
1939     }
1940     else if (a.hasOption(Opt.UNARY))
1941     {
1942       message = a.argString() + (yes ? " was set" : " was not set");
1943     }
1944     System.out.println("[TESTOUTPUT] arg " + message);
1945   }
1946 }