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