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