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