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