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