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