JAL-4059 fix that boolean for non-value query string args
[jalview.git] / src / jalview / bin / Jalview.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import java.awt.Color;
24 import java.io.BufferedReader;
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.io.OutputStream;
30 import java.io.OutputStreamWriter;
31 import java.io.PrintStream;
32 import java.io.PrintWriter;
33 import java.net.MalformedURLException;
34 import java.net.URI;
35 import java.net.URISyntaxException;
36 import java.net.URL;
37 import java.security.AllPermission;
38 import java.security.CodeSource;
39 import java.security.PermissionCollection;
40 import java.security.Permissions;
41 import java.security.Policy;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.Properties;
48 import java.util.Vector;
49 import java.util.logging.ConsoleHandler;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52 import java.util.stream.Collectors;
53
54 import javax.swing.JDialog;
55 import javax.swing.JFrame;
56 import javax.swing.JInternalFrame;
57 import javax.swing.JOptionPane;
58 import javax.swing.SwingUtilities;
59 import javax.swing.UIManager;
60 import javax.swing.UIManager.LookAndFeelInfo;
61 import javax.swing.UnsupportedLookAndFeelException;
62
63 import com.formdev.flatlaf.FlatLightLaf;
64 import com.formdev.flatlaf.themes.FlatMacLightLaf;
65 import com.formdev.flatlaf.util.SystemInfo;
66 import com.threerings.getdown.util.LaunchUtil;
67
68 //import edu.stanford.ejalbert.launching.IBrowserLaunching;
69 import groovy.lang.Binding;
70 import groovy.util.GroovyScriptEngine;
71 import jalview.bin.argparser.Arg;
72 import jalview.bin.argparser.Arg.Opt;
73 import jalview.bin.argparser.Arg.Type;
74 import jalview.bin.argparser.ArgParser;
75 import jalview.bin.argparser.BootstrapArgs;
76 import jalview.bin.groovy.JalviewObject;
77 import jalview.bin.groovy.JalviewObjectI;
78 import jalview.ext.so.SequenceOntology;
79 import jalview.gui.AlignFrame;
80 import jalview.gui.Desktop;
81 import jalview.gui.JvOptionPane;
82 import jalview.gui.PromptUserConfig;
83 import jalview.gui.QuitHandler;
84 import jalview.gui.QuitHandler.QResponse;
85 import jalview.gui.StructureViewerBase;
86 import jalview.io.AppletFormatAdapter;
87 import jalview.io.BioJsHTMLOutput;
88 import jalview.io.DataSourceType;
89 import jalview.io.FileFormat;
90 import jalview.io.FileFormatException;
91 import jalview.io.FileFormatI;
92 import jalview.io.FileFormats;
93 import jalview.io.FileLoader;
94 import jalview.io.HtmlSvgOutput;
95 import jalview.io.IdentifyFile;
96 import jalview.io.NewickFile;
97 import jalview.io.exceptions.ImageOutputException;
98 import jalview.io.gff.SequenceOntologyFactory;
99 import jalview.schemes.ColourSchemeI;
100 import jalview.schemes.ColourSchemeProperty;
101 import jalview.util.ChannelProperties;
102 import jalview.util.HttpUtils;
103 import jalview.util.IdUtils;
104 import jalview.util.IdUtils.IdType;
105 import jalview.util.LaunchUtils;
106 import jalview.util.MessageManager;
107 import jalview.util.Platform;
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 = IdUtils.newId(IdType.PROGRESS);
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", HttpUtils.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     boolean commandsSuccess = false;
843     if (!(argparser.isMixedStyle() || argparser.isOldStyle()))
844     {
845       cmds = new Commands(argparser, headlessArg);
846       cmds.processArgs();
847       commandsSuccess = cmds.argsWereParsed();
848
849       if (commandsSuccess)
850       {
851         if (headlessArg)
852         {
853           if (argparser.getBoolean(Arg.NOQUIT))
854           {
855             Console.warn("Completed " + Arg.HEADLESS.getName()
856                     + " commands, but " + Arg.NOQUIT
857                     + " is set so not quitting!");
858           }
859           else
860           {
861             Jalview.exit("Successfully completed commands in headless mode",
862                     ExitCode.OK);
863           }
864         }
865         Console.info("Successfully completed commands");
866       }
867       else
868       {
869         if (headlessArg)
870         {
871           Jalview.exit("Error when running Commands in headless mode",
872                   ExitCode.ERROR_RUNNING_COMMANDS);
873         }
874         Console.warn("Error when running commands");
875       }
876     }
877
878     // Check if JVM and compile version might cause problems and log if it
879     // might.
880     if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
881     {
882       Console.warn("The Java version being used (Java "
883               + LaunchUtils.getJavaVersion()
884               + ") may lead to problems. This installation of Jalview should be used with Java "
885               + LaunchUtils.getJavaCompileVersion() + ".");
886     }
887
888     String file = null, data = null;
889
890     FileFormatI format = null;
891
892     DataSourceType protocol = null;
893
894     FileLoader fileLoader = new FileLoader(!headless);
895
896     String groovyscript = null; // script to execute after all loading is
897     // completed one way or another
898     // extract groovy argument and execute if necessary
899     groovyscript = aparser.getValue("groovy", true);
900     file = aparser.getValue("open", true);
901
902     if (file == null && desktop == null && !commandsSuccess)
903     {
904       Jalview.exit("No files to open!", ExitCode.NO_FILES);
905     }
906
907     long progress = -1;
908     // Finally, deal with the remaining input data.
909     if (file != null)
910     {
911       if (!headless)
912       {
913         desktop.setProgressBar(
914                 MessageManager
915                         .getString("status.processing_commandline_args"),
916                 progress = IdUtils.newId(IdType.PROGRESS));
917       }
918       Console.outPrintln("CMD [-open " + file + "] executed successfully!");
919
920       if (!Platform.isJS())
921       /**
922        * ignore in JavaScript -- can't just file existence - could load it?
923        * 
924        * @j2sIgnore
925        */
926       {
927         if (!HttpUtils.startsWithHttpOrHttps(file))
928         {
929           if (!(new File(file)).exists())
930           {
931             if (headless)
932             {
933               Jalview.exit(
934                       "Can't find file '" + file + "' in headless mode",
935                       ExitCode.FILE_NOT_FOUND);
936             }
937             Console.warn("Can't find file'" + file + "'");
938           }
939         }
940       }
941
942       protocol = AppletFormatAdapter.checkProtocol(file);
943
944       try
945       {
946         format = new IdentifyFile().identify(file, protocol);
947       } catch (FileFormatException e1)
948       {
949         // TODO ?
950       }
951
952       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
953               format);
954       if (af == null)
955       {
956         Console.outPrintln("error");
957       }
958       else
959       {
960         setCurrentAlignFrame(af);
961         data = aparser.getValue("colour", true);
962         if (data != null)
963         {
964           data.replaceAll("%20", " ");
965
966           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
967                   af.getViewport(), af.getViewport().getAlignment(), data);
968
969           if (cs != null)
970           {
971             Console.outPrintln(
972                     "CMD [-colour " + data + "] executed successfully!");
973           }
974           af.changeColour(cs);
975         }
976
977         // Must maintain ability to use the groups flag
978         data = aparser.getValue("groups", true);
979         if (data != null)
980         {
981           af.parseFeaturesFile(data,
982                   AppletFormatAdapter.checkProtocol(data));
983           // Console.outPrintln("Added " + data);
984           Console.outPrintln(
985                   "CMD groups[-" + data + "]  executed successfully!");
986         }
987         data = aparser.getValue("features", true);
988         if (data != null)
989         {
990           af.parseFeaturesFile(data,
991                   AppletFormatAdapter.checkProtocol(data));
992           // Console.outPrintln("Added " + data);
993           Console.outPrintln(
994                   "CMD [-features " + data + "]  executed successfully!");
995         }
996
997         data = aparser.getValue("annotations", true);
998         if (data != null)
999         {
1000           af.loadJalviewDataFile(data, null, null, null);
1001           // Console.outPrintln("Added " + data);
1002           Console.outPrintln(
1003                   "CMD [-annotations " + data + "] executed successfully!");
1004         }
1005         // set or clear the sortbytree flag.
1006         if (aparser.contains("sortbytree"))
1007         {
1008           af.getViewport().setSortByTree(true);
1009           if (af.getViewport().getSortByTree())
1010           {
1011             Console.outPrintln("CMD [-sortbytree] executed successfully!");
1012           }
1013         }
1014         if (aparser.contains("no-annotation"))
1015         {
1016           af.getViewport().setShowAnnotation(false);
1017           if (!af.getViewport().isShowAnnotation())
1018           {
1019             Console.outPrintln("CMD no-annotation executed successfully!");
1020           }
1021         }
1022         if (aparser.contains("nosortbytree"))
1023         {
1024           af.getViewport().setSortByTree(false);
1025           if (!af.getViewport().getSortByTree())
1026           {
1027             Console.outPrintln(
1028                     "CMD [-nosortbytree] executed successfully!");
1029           }
1030         }
1031         data = aparser.getValue("tree", true);
1032         if (data != null)
1033         {
1034           try
1035           {
1036             Console.outPrintln(
1037                     "CMD [-tree " + data + "] executed successfully!");
1038             NewickFile nf = new NewickFile(data,
1039                     AppletFormatAdapter.checkProtocol(data));
1040             af.getViewport()
1041                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
1042           } catch (IOException ex)
1043           {
1044             jalview.bin.Console.errPrintln("Couldn't add tree " + data);
1045             ex.printStackTrace(System.err);
1046           }
1047         }
1048
1049         if (groovyscript != null)
1050         {
1051           // Execute the groovy script after we've done all the rendering stuff
1052           // and before any images or figures are generated.
1053           Console.outPrintln("Executing script " + groovyscript);
1054           executeGroovyScript(groovyscript, af);
1055           Console.outPrintln("CMD groovy[" + groovyscript
1056                   + "] executed successfully!");
1057           groovyscript = null;
1058         }
1059         String imageName = "unnamed.png";
1060         while (aparser.getSize() > 1)
1061         {
1062           try
1063           {
1064             String outputFormat = aparser.nextValue();
1065             file = aparser.nextValue();
1066
1067             if (outputFormat.equalsIgnoreCase("png"))
1068             {
1069               Console.outPrintln("Creating PNG image: " + file);
1070               af.createPNG(new File(file));
1071               imageName = (new File(file)).getName();
1072               continue;
1073             }
1074             else if (outputFormat.equalsIgnoreCase("svg"))
1075             {
1076               Console.outPrintln("Creating SVG image: " + file);
1077               File imageFile = new File(file);
1078               imageName = imageFile.getName();
1079               af.createSVG(imageFile);
1080               continue;
1081             }
1082             else if (outputFormat.equalsIgnoreCase("html"))
1083             {
1084               File imageFile = new File(file);
1085               imageName = imageFile.getName();
1086               HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
1087
1088               Console.outPrintln("Creating HTML image: " + file);
1089               htmlSVG.exportHTML(file);
1090               continue;
1091             }
1092             else if (outputFormat.equalsIgnoreCase("biojsmsa"))
1093             {
1094               if (file == null)
1095               {
1096                 jalview.bin.Console.errPrintln(
1097                         "The output html file must not be null");
1098                 return;
1099               }
1100               try
1101               {
1102                 BioJsHTMLOutput.refreshVersionInfo(
1103                         BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
1104               } catch (URISyntaxException e)
1105               {
1106                 e.printStackTrace();
1107               }
1108               BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
1109               Console.outPrintln(
1110                       "Creating BioJS MSA Viwer HTML file: " + file);
1111               bjs.exportHTML(file);
1112               continue;
1113             }
1114             else if (outputFormat.equalsIgnoreCase("imgMap"))
1115             {
1116               Console.outPrintln("Creating image map: " + file);
1117               af.createImageMap(new File(file), imageName);
1118               continue;
1119             }
1120             else if (outputFormat.equalsIgnoreCase("eps"))
1121             {
1122               File outputFile = new File(file);
1123               Console.outPrintln(
1124                       "Creating EPS file: " + outputFile.getAbsolutePath());
1125               af.createEPS(outputFile);
1126               continue;
1127             }
1128
1129             FileFormatI outFormat = null;
1130             try
1131             {
1132               outFormat = FileFormats.getInstance().forName(outputFormat);
1133             } catch (Exception formatP)
1134             {
1135               Console.outPrintln("Couldn't parse " + outFormat
1136                       + " as a valid Jalview format string.");
1137             }
1138             if (outFormat != null)
1139             {
1140               if (!outFormat.isWritable())
1141               {
1142                 Console.outPrintln(
1143                         "This version of Jalview does not support alignment export as "
1144                                 + outputFormat);
1145               }
1146               else
1147               {
1148                 af.saveAlignment(file, outFormat);
1149                 if (af.isSaveAlignmentSuccessful())
1150                 {
1151                   Console.outPrintln("Written alignment in "
1152                           + outFormat.getName() + " format to " + file);
1153                 }
1154                 else
1155                 {
1156                   Console.outPrintln("Error writing file " + file + " in "
1157                           + outFormat.getName() + " format!!");
1158                 }
1159               }
1160             }
1161           } catch (ImageOutputException ioexc)
1162           {
1163             Console.outPrintln(
1164                     "Unexpected error whilst exporting image to " + file);
1165             ioexc.printStackTrace();
1166           }
1167
1168         }
1169
1170         while (aparser.getSize() > 0)
1171         {
1172           Console.outPrintln("Unknown arg: " + aparser.nextValue());
1173         }
1174       }
1175     }
1176
1177     AlignFrame startUpAlframe = null;
1178     // We'll only open the default file if the desktop is visible.
1179     // And the user
1180     // ////////////////////
1181
1182     if (!Platform.isJS() && !headless && file == null
1183             && Cache.getDefault("SHOW_STARTUP_FILE", true)
1184             && !cmds.commandArgsProvided()
1185             && !bootstrapArgs.getBoolean(Arg.NOSTARTUPFILE))
1186     // don't open the startup file if command line args have been processed
1187     // (&& !Commands.commandArgsProvided())
1188     /**
1189      * Java only
1190      * 
1191      * @j2sIgnore
1192      */
1193     {
1194       file = Cache.getDefault("STARTUP_FILE",
1195               Cache.getDefault("www.jalview.org", "https://www.jalview.org")
1196                       + "/examples/exampleFile_2_7.jvp");
1197       if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
1198               || file.equals(
1199                       "http://www.jalview.org/examples/exampleFile_2_7.jar"))
1200       {
1201         file.replace("http:", "https:");
1202         // hardwire upgrade of the startup file
1203         file.replace("_2_3", "_2_7");
1204         file.replace("2_7.jar", "2_7.jvp");
1205         // and remove the stale setting
1206         Cache.removeProperty("STARTUP_FILE");
1207       }
1208
1209       protocol = AppletFormatAdapter.checkProtocol(file);
1210
1211       if (file.endsWith(".jar"))
1212       {
1213         format = FileFormat.Jalview;
1214       }
1215       else
1216       {
1217         try
1218         {
1219           format = new IdentifyFile().identify(file, protocol);
1220         } catch (FileFormatException e)
1221         {
1222           // TODO what?
1223         }
1224       }
1225
1226       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
1227               format);
1228       // don't ask to save when quitting if only the startup file has been
1229       // opened
1230       Console.debug("Resetting up-to-date flag for startup file");
1231       startUpAlframe.getViewport().setSavedUpToDate(true);
1232       // extract groovy arguments before anything else.
1233     }
1234
1235     // Once all other stuff is done, execute any groovy scripts (in order)
1236     if (groovyscript != null)
1237     {
1238       if (Cache.groovyJarsPresent())
1239       {
1240         Console.outPrintln("Executing script " + groovyscript);
1241         executeGroovyScript(groovyscript, startUpAlframe);
1242       }
1243       else
1244       {
1245         jalview.bin.Console.errPrintln(
1246                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
1247                         + groovyscript);
1248       }
1249     }
1250     // and finally, turn off batch mode indicator - if the desktop still exists
1251     if (desktop != null)
1252     {
1253       if (progress != -1)
1254       {
1255         desktop.setProgressBar(null, progress);
1256       }
1257       desktop.setInBatchMode(false);
1258     }
1259
1260     cliWarning();
1261   }
1262
1263   private static void setLookAndFeel()
1264   {
1265     if (!Platform.isJS())
1266     /**
1267      * Java only
1268      * 
1269      * @j2sIgnore
1270      */
1271     {
1272       // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
1273       // "mac" or "flat"
1274       // If not set (or chosen laf fails), use the normal SystemLaF and if on
1275       // Mac,
1276       // try Quaqua/Vaqua.
1277       String lafProp = System.getProperty("laf");
1278       String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
1279       String laf = "none";
1280       if (lafProp != null)
1281       {
1282         laf = lafProp;
1283       }
1284       else if (lafSetting != null)
1285       {
1286         laf = lafSetting;
1287       }
1288       boolean lafSet = false;
1289       switch (laf)
1290       {
1291       case "crossplatform":
1292         lafSet = setCrossPlatformLookAndFeel();
1293         if (!lafSet)
1294         {
1295           Console.error("Could not set requested laf=" + laf);
1296         }
1297         break;
1298       case "system":
1299         lafSet = setSystemLookAndFeel();
1300         if (!lafSet)
1301         {
1302           Console.error("Could not set requested laf=" + laf);
1303         }
1304         break;
1305       case "gtk":
1306         lafSet = setGtkLookAndFeel();
1307         if (!lafSet)
1308         {
1309           Console.error("Could not set requested laf=" + laf);
1310         }
1311         break;
1312       case "metal":
1313         lafSet = setMetalLookAndFeel();
1314         if (!lafSet)
1315         {
1316           Console.error("Could not set requested laf=" + laf);
1317         }
1318         break;
1319       case "nimbus":
1320         lafSet = setNimbusLookAndFeel();
1321         if (!lafSet)
1322         {
1323           Console.error("Could not set requested laf=" + laf);
1324         }
1325         break;
1326       case "flat":
1327         lafSet = setFlatLookAndFeel();
1328         if (!lafSet)
1329         {
1330           Console.error("Could not set requested laf=" + laf);
1331         }
1332         break;
1333       case "mac":
1334         lafSet = setMacLookAndFeel();
1335         if (!lafSet)
1336         {
1337           Console.error("Could not set requested laf=" + laf);
1338         }
1339         break;
1340       case "none":
1341         break;
1342       default:
1343         Console.error("Requested laf=" + laf + " not implemented");
1344       }
1345       if (!lafSet)
1346       {
1347         // Flatlaf default for everyone!
1348         lafSet = setFlatLookAndFeel();
1349         if (!lafSet)
1350         {
1351           setSystemLookAndFeel();
1352         }
1353         if (Platform.isLinux())
1354         {
1355           setLinuxLookAndFeel();
1356         }
1357         if (Platform.isMac())
1358         {
1359           setMacLookAndFeel();
1360         }
1361       }
1362     }
1363   }
1364
1365   private static boolean setCrossPlatformLookAndFeel()
1366   {
1367     boolean set = false;
1368     try
1369     {
1370       UIManager.setLookAndFeel(
1371               UIManager.getCrossPlatformLookAndFeelClassName());
1372       set = true;
1373     } catch (Exception ex)
1374     {
1375       Console.error("Unexpected Look and Feel Exception");
1376       Console.error(ex.getMessage());
1377       Console.debug(Cache.getStackTraceString(ex));
1378     }
1379     return set;
1380   }
1381
1382   private static boolean setSystemLookAndFeel()
1383   {
1384     boolean set = false;
1385     try
1386     {
1387       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1388       set = true;
1389     } catch (Exception ex)
1390     {
1391       Console.error("Unexpected Look and Feel Exception");
1392       Console.error(ex.getMessage());
1393       Console.debug(Cache.getStackTraceString(ex));
1394     }
1395     return set;
1396   }
1397
1398   private static boolean setSpecificLookAndFeel(String name,
1399           String className, boolean nameStartsWith)
1400   {
1401     boolean set = false;
1402     try
1403     {
1404       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
1405       {
1406         if (info.getName() != null && nameStartsWith
1407                 ? info.getName().toLowerCase(Locale.ROOT)
1408                         .startsWith(name.toLowerCase(Locale.ROOT))
1409                 : info.getName().toLowerCase(Locale.ROOT)
1410                         .equals(name.toLowerCase(Locale.ROOT)))
1411         {
1412           className = info.getClassName();
1413           break;
1414         }
1415       }
1416       UIManager.setLookAndFeel(className);
1417       set = true;
1418     } catch (Exception ex)
1419     {
1420       Console.error("Unexpected Look and Feel Exception");
1421       Console.error(ex.getMessage());
1422       Console.debug(Cache.getStackTraceString(ex));
1423     }
1424     return set;
1425   }
1426
1427   private static boolean setGtkLookAndFeel()
1428   {
1429     return setSpecificLookAndFeel("gtk",
1430             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
1431   }
1432
1433   private static boolean setMetalLookAndFeel()
1434   {
1435     return setSpecificLookAndFeel("metal",
1436             "javax.swing.plaf.metal.MetalLookAndFeel", false);
1437   }
1438
1439   private static boolean setNimbusLookAndFeel()
1440   {
1441     return setSpecificLookAndFeel("nimbus",
1442             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
1443   }
1444
1445   private static boolean setFlatLookAndFeel()
1446   {
1447     boolean set = false;
1448     if (SystemInfo.isMacOS)
1449     {
1450       try
1451       {
1452         UIManager.setLookAndFeel(
1453                 "com.formdev.flatlaf.themes.FlatMacLightLaf");
1454         set = true;
1455         Console.debug("Using FlatMacLightLaf");
1456       } catch (ClassNotFoundException | InstantiationException
1457               | IllegalAccessException | UnsupportedLookAndFeelException e)
1458       {
1459         Console.debug("Exception loading FlatLightLaf", e);
1460       }
1461       System.setProperty("apple.laf.useScreenMenuBar", "true");
1462       System.setProperty("apple.awt.application.name",
1463               ChannelProperties.getProperty("app_name"));
1464       System.setProperty("apple.awt.application.appearance", "system");
1465       if (SystemInfo.isMacFullWindowContentSupported
1466               && Desktop.desktop != null)
1467       {
1468         Console.debug("Setting transparent title bar");
1469         Desktop.desktop.getRootPane()
1470                 .putClientProperty("apple.awt.fullWindowContent", true);
1471         Desktop.desktop.getRootPane()
1472                 .putClientProperty("apple.awt.transparentTitleBar", true);
1473         Desktop.desktop.getRootPane()
1474                 .putClientProperty("apple.awt.fullscreenable", true);
1475       }
1476       SwingUtilities.invokeLater(() -> {
1477         FlatMacLightLaf.setup();
1478       });
1479       Console.debug("Using FlatMacLightLaf");
1480       set = true;
1481     }
1482     if (!set)
1483     {
1484       try
1485       {
1486         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1487         set = true;
1488         Console.debug("Using FlatLightLaf");
1489       } catch (ClassNotFoundException | InstantiationException
1490               | IllegalAccessException | UnsupportedLookAndFeelException e)
1491       {
1492         Console.debug("Exception loading FlatLightLaf", e);
1493       }
1494       // Windows specific properties here
1495       SwingUtilities.invokeLater(() -> {
1496         FlatLightLaf.setup();
1497       });
1498       Console.debug("Using FlatLightLaf");
1499       set = true;
1500     }
1501     else if (SystemInfo.isLinux)
1502     {
1503       try
1504       {
1505         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1506         set = true;
1507         Console.debug("Using FlatLightLaf");
1508       } catch (ClassNotFoundException | InstantiationException
1509               | IllegalAccessException | UnsupportedLookAndFeelException e)
1510       {
1511         Console.debug("Exception loading FlatLightLaf", e);
1512       }
1513       // enable custom window decorations
1514       JFrame.setDefaultLookAndFeelDecorated(true);
1515       JDialog.setDefaultLookAndFeelDecorated(true);
1516       SwingUtilities.invokeLater(() -> {
1517         FlatLightLaf.setup();
1518       });
1519       Console.debug("Using FlatLightLaf");
1520       set = true;
1521     }
1522
1523     if (!set)
1524     {
1525       try
1526       {
1527         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1528         set = true;
1529         Console.debug("Using FlatLightLaf");
1530       } catch (ClassNotFoundException | InstantiationException
1531               | IllegalAccessException | UnsupportedLookAndFeelException e)
1532       {
1533         Console.debug("Exception loading FlatLightLaf", e);
1534       }
1535     }
1536
1537     if (set)
1538     {
1539       UIManager.put("TabbedPane.tabType", "card");
1540       UIManager.put("TabbedPane.showTabSeparators", true);
1541       UIManager.put("TabbedPane.showContentSeparator", true);
1542       // UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
1543       UIManager.put("TabbedPane.tabsOverlapBorder", true);
1544       UIManager.put("TabbedPane.hasFullBorder", true);
1545       UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
1546       UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
1547       UIManager.put("TabbedPane.smoothScrolling", true);
1548       UIManager.put("TabbedPane.tabWidthMode", "compact");
1549       UIManager.put("TabbedPane.selectedBackground", Color.white);
1550       UIManager.put("TabbedPane.background", new Color(236, 236, 236));
1551       UIManager.put("TabbedPane.hoverColor", Color.lightGray);
1552     }
1553
1554     Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
1555     return set;
1556   }
1557
1558   private static boolean setMacLookAndFeel()
1559   {
1560     boolean set = false;
1561     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
1562             ChannelProperties.getProperty("app_name"));
1563     System.setProperty("apple.laf.useScreenMenuBar", "true");
1564     /*
1565      * broken native LAFs on (ARM?) macbooks
1566     set = setQuaquaLookAndFeel();
1567     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
1568             .toLowerCase(Locale.ROOT).contains("quaqua"))
1569     {
1570       set = setVaquaLookAndFeel();
1571     }
1572      */
1573     set = setFlatLookAndFeel();
1574     return set;
1575   }
1576
1577   private static boolean setLinuxLookAndFeel()
1578   {
1579     boolean set = false;
1580     set = setFlatLookAndFeel();
1581     if (!set)
1582       set = setMetalLookAndFeel();
1583     // avoid GtkLookAndFeel -- not good results especially on HiDPI
1584     if (!set)
1585       set = setNimbusLookAndFeel();
1586     return set;
1587   }
1588
1589   /*
1590   private static void showUsage()
1591   {
1592     jalview.bin.Console.outPrintln(
1593             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1594                     + "-nodisplay\tRun Jalview without User Interface.\n"
1595                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1596                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1597                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1598                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1599                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1600                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1601                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1602                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1603                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1604                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1605                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1606                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1607                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1608                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1609                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1610                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1611                     + "-html FILE\tCreate HTML file from alignment.\n"
1612                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1613                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1614                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1615                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1616                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1617                     + "-nonews\tTurn off check for Jalview news.\n"
1618                     + "-nousagestats\tTurn off analytics tracking for this session.\n"
1619                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1620                     // +
1621                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1622                     // after all other properties files have been read\n\t
1623                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1624                     // passed in correctly)"
1625                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1626                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1627                     + "-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"
1628                     + "-jvmmempc=PERCENT\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
1629                     + "-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"
1630                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
1631   }
1632   */
1633
1634   private static void startUsageStats(final Desktop desktop)
1635   {
1636     /**
1637      * start a User Config prompt asking if we can log usage statistics.
1638      */
1639     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1640             "USAGESTATS",
1641             MessageManager.getString("prompt.analytics_title"),
1642             MessageManager.getString("prompt.analytics"), new Runnable()
1643             {
1644               @Override
1645               public void run()
1646               {
1647                 Console.debug("Initialising analytics for usage stats.");
1648                 Cache.initAnalytics();
1649                 Console.debug("Tracking enabled.");
1650               }
1651             }, new Runnable()
1652             {
1653               @Override
1654               public void run()
1655               {
1656                 Console.debug("Not enabling analytics.");
1657               }
1658             }, null, true);
1659     desktop.addDialogThread(prompter);
1660   }
1661
1662   /**
1663    * Locate the given string as a file and pass it to the groovy interpreter.
1664    * 
1665    * @param groovyscript
1666    *          the script to execute
1667    * @param jalviewContext
1668    *          the Jalview Desktop object passed in to the groovy binding as the
1669    *          'Jalview' object.
1670    */
1671   protected void executeGroovyScript(String groovyscript, AlignFrame af)
1672   {
1673     /**
1674      * for scripts contained in files
1675      */
1676     File tfile = null;
1677     /**
1678      * script's URI
1679      */
1680     URL sfile = null;
1681     if (groovyscript.trim().equals("STDIN"))
1682     {
1683       // read from stdin into a tempfile and execute it
1684       try
1685       {
1686         tfile = File.createTempFile("jalview", "groovy");
1687         PrintWriter outfile = new PrintWriter(
1688                 new OutputStreamWriter(new FileOutputStream(tfile)));
1689         BufferedReader br = new BufferedReader(
1690                 new InputStreamReader(System.in));
1691         String line = null;
1692         while ((line = br.readLine()) != null)
1693         {
1694           outfile.write(line + "\n");
1695         }
1696         br.close();
1697         outfile.flush();
1698         outfile.close();
1699
1700       } catch (Exception ex)
1701       {
1702         jalview.bin.Console
1703                 .errPrintln("Failed to read from STDIN into tempfile "
1704                         + ((tfile == null) ? "(tempfile wasn't created)"
1705                                 : tfile.toString()));
1706         ex.printStackTrace();
1707         return;
1708       }
1709       try
1710       {
1711         sfile = tfile.toURI().toURL();
1712       } catch (Exception x)
1713       {
1714         jalview.bin.Console.errPrintln(
1715                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1716                         + tfile.toURI());
1717         x.printStackTrace();
1718         return;
1719       }
1720     }
1721     else
1722     {
1723       try
1724       {
1725         sfile = new URI(groovyscript).toURL();
1726       } catch (Exception x)
1727       {
1728         tfile = new File(groovyscript);
1729         if (!tfile.exists())
1730         {
1731           jalview.bin.Console.errPrintln(
1732                   "File '" + groovyscript + "' does not exist.");
1733           return;
1734         }
1735         if (!tfile.canRead())
1736         {
1737           jalview.bin.Console.errPrintln(
1738                   "File '" + groovyscript + "' cannot be read.");
1739           return;
1740         }
1741         if (tfile.length() < 1)
1742         {
1743           jalview.bin.Console
1744                   .errPrintln("File '" + groovyscript + "' is empty.");
1745           return;
1746         }
1747         try
1748         {
1749           sfile = tfile.getAbsoluteFile().toURI().toURL();
1750         } catch (Exception ex)
1751         {
1752           jalview.bin.Console.errPrintln("Failed to create a file URL for "
1753                   + tfile.getAbsoluteFile());
1754           return;
1755         }
1756       }
1757     }
1758     try
1759     {
1760       JalviewObjectI j = new JalviewObject(this);
1761       Map<String, java.lang.Object> vbinding = new HashMap<>();
1762       vbinding.put(JalviewObjectI.jalviewObjectName, j);
1763       vbinding.put(JalviewObjectI.currentAlFrameName,
1764               af != null ? af : getCurrentAlignFrame());
1765       Binding gbinding = new Binding(vbinding);
1766       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1767       gse.run(sfile.toString(), gbinding);
1768       if ("STDIN".equals(groovyscript))
1769       {
1770         // delete temp file that we made -
1771         // only if it was successfully executed
1772         tfile.delete();
1773       }
1774     } catch (Exception e)
1775     {
1776       jalview.bin.Console
1777               .errPrintln("Exception Whilst trying to execute file " + sfile
1778                       + " as a groovy script.");
1779       e.printStackTrace(System.err);
1780     }
1781   }
1782
1783   public static boolean isHeadlessMode()
1784   {
1785     String isheadless = System.getProperty("java.awt.headless");
1786     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1787     {
1788       return true;
1789     }
1790     return false;
1791   }
1792
1793   @Override
1794   public AlignFrame[] getAlignFrames()
1795   {
1796     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1797             : Desktop.getDesktopAlignFrames();
1798   }
1799
1800   /**
1801    * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
1802    */
1803   @Override
1804   public void quit()
1805   {
1806     // System.exit will run the shutdownHook first
1807     Jalview.exit("Quitting now. Bye!", ExitCode.OK);
1808   }
1809
1810   @Override
1811   public AlignFrame getCurrentAlignFrame()
1812   {
1813     return currentAlignFrame;
1814   }
1815
1816   public void setCurrentAlignFrame(AlignFrame af)
1817   {
1818     this.currentAlignFrame = af;
1819   }
1820
1821   public Commands getCommands()
1822   {
1823     return cmds;
1824   }
1825
1826   public static void exit(String message, ExitCode ec)
1827   {
1828     int exitcode = ec == ExitCode.OK ? 0 : ec.ordinal() + 1;
1829     if (Console.log == null)
1830     {
1831       // Don't start the logger just to exit!
1832       if (message != null)
1833       {
1834         if (exitcode == 0)
1835         {
1836           Console.outPrintln(message);
1837         }
1838         else
1839         {
1840           jalview.bin.Console.errPrintln(message);
1841         }
1842       }
1843     }
1844     else
1845     {
1846       Console.debug("Using Jalview.exit");
1847       if (message != null)
1848       {
1849         if (exitcode == 0)
1850         {
1851           Console.info(message);
1852         }
1853         else
1854         {
1855           Console.error(message);
1856         }
1857       }
1858     }
1859     if (exitcode > -1)
1860     {
1861       System.exit(exitcode);
1862     }
1863   }
1864
1865   public enum ExitCode
1866   {
1867     // only add new ones to the end of the list (to preserve ordinal values)
1868     OK, FILE_NOT_FOUND, FILE_NOT_READABLE, NO_FILES, INVALID_FORMAT,
1869     INVALID_ARGUMENT, INVALID_VALUE, MIXED_CLI_ARGUMENTS,
1870     ERROR_RUNNING_COMMANDS, NO_LOGGING, GROOVY_ERROR;
1871   }
1872
1873   /******************************
1874    * 
1875    * TEST OUTPUT METHODS
1876    * 
1877    * these operate only when Arg.TESTOUTPUT has been passed, and variously check
1878    * if an expected value / arg was set and report it to the test framework.
1879    * 
1880    ******************************/
1881   /**
1882    * report string values parsed/processed during tests When the Bootstrap
1883    * argument Arg.TESTOUTPUT is present - reports on debug if given s1 is not
1884    * null and not equals s2, warns if given argument is not set, and calls
1885    * testoutput(true,a,s1,s2) to report processing progress.
1886    * 
1887    * @param ap
1888    *          - ArgParser handling parsing
1889    * @param a
1890    *          - Arg currently being processed
1891    * @param s1
1892    *          - expected
1893    * @param s2
1894    */
1895   protected static void testoutput(ArgParser ap, Arg a, String s1,
1896           String s2)
1897   {
1898     BootstrapArgs bsa = ap.getBootstrapArgs();
1899     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1900       return;
1901     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1902     {
1903       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1904               + s2 + "' for arg " + a.argString());
1905       return;
1906     }
1907     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1908             : ap.isSet(a);
1909     if (!isset)
1910     {
1911       Console.warn("Arg '" + a.getName() + "' not set at all");
1912       return;
1913     }
1914     testoutput(true, a, s1, s2);
1915   }
1916
1917   /**
1918    * report values passed via bootstrap arguments
1919    * 
1920    * TODO: significant code duplication with testouput(Argparser...) - move it
1921    */
1922
1923   protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
1924           String s2)
1925   {
1926     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1927       return;
1928     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1929     {
1930       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1931               + s2 + "' for arg " + a.argString());
1932       return;
1933     }
1934     if (!a.hasOption(Opt.BOOTSTRAP))
1935     {
1936       Console.error("Non-bootstrap Arg '" + a.getName()
1937               + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
1938     }
1939     if (!bsa.contains(a))
1940     {
1941       Console.warn("Arg '" + a.getName() + "' not set at all");
1942       return;
1943     }
1944     testoutput(true, a, s1, s2);
1945   }
1946
1947   /**
1948    * conditionally (on @param yes) report that expected value s1 was set during
1949    * CommandsTest tests
1950    */
1951   private static void testoutput(boolean yes, Arg a, String s1, String s2)
1952   {
1953     if (yes && ((s1 == null && s2 == null)
1954             || (s1 != null && s1.equals(s2))))
1955     {
1956       Console.outPrintln("[TESTOUTPUT] arg " + a.argString() + "='" + s1
1957               + "' was set");
1958     }
1959   }
1960
1961   /*
1962    * testoutput for boolean and unary values
1963    */
1964   protected static void testoutput(ArgParser ap, Arg a)
1965   {
1966     if (ap == null)
1967       return;
1968     BootstrapArgs bsa = ap.getBootstrapArgs();
1969     if (bsa == null)
1970       return;
1971     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1972       return;
1973     boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
1974             : ap.getBoolean(a);
1975     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1976             : ap.isSet(a);
1977     if (!isset)
1978     {
1979       Console.warn("Arg '" + a.getName() + "' not set at all");
1980       return;
1981     }
1982     testoutput(val, a);
1983   }
1984
1985   protected static void testoutput(BootstrapArgs bsa, Arg a)
1986   {
1987     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1988       return;
1989     if (!a.hasOption(Opt.BOOTSTRAP))
1990     {
1991       Console.warn("Non-bootstrap Arg '" + a.getName()
1992               + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
1993
1994     }
1995     if (!bsa.contains(a))
1996     {
1997       Console.warn("Arg '" + a.getName() + "' not set at all");
1998       return;
1999     }
2000     testoutput(bsa.getBoolean(a), a);
2001   }
2002
2003   private static void testoutput(boolean yes, Arg a)
2004   {
2005     String message = null;
2006     if (a.hasOption(Opt.BOOLEAN))
2007     {
2008       message = (yes ? a.argString() : a.negateArgString()) + " was set";
2009     }
2010     else if (a.hasOption(Opt.UNARY))
2011     {
2012       message = a.argString() + (yes ? " was set" : " was not set");
2013     }
2014     Console.outPrintln("[TESTOUTPUT] arg " + message);
2015   }
2016
2017   public ArgParser getArgParser()
2018   {
2019     return argparser;
2020   }
2021
2022   public BootstrapArgs getBootstrapArgs()
2023   {
2024     return bootstrapArgs;
2025   }
2026
2027   public static boolean isBatchMode()
2028   {
2029     return getInstance() != null && (getInstance().desktop == null
2030             || getInstance().desktop.isInBatchMode());
2031   }
2032
2033   /**
2034    * Warning about old or mixed command line arguments
2035    */
2036   private void mixedCliWarning()
2037   {
2038     Jalview j = Jalview.getInstance();
2039     boolean mixedStyle = j.getArgParser() != null
2040             && j.getArgParser().isMixedStyle();
2041     String title = MessageManager.getString("label.command_line_arguments");
2042     if (mixedStyle)
2043     {
2044       String warning = MessageManager.formatMessage(
2045               "warning.using_mixed_command_line_arguments",
2046               j.getArgParser().getMixedExamples());
2047       String quit = MessageManager.getString("action.quit");
2048
2049       Desktop.instance.nonBlockingDialog(title, warning, null, quit,
2050               JvOptionPane.WARNING_MESSAGE, false, false, true, 30000);
2051
2052       Jalview.exit(
2053               "Exiting due to mixed old and new command line arguments.",
2054               ExitCode.MIXED_CLI_ARGUMENTS);
2055     }
2056   }
2057
2058   private void cliWarning()
2059   {
2060     Jalview j = Jalview.getInstance();
2061     Commands c = j.getCommands();
2062     boolean oldStyle = j.getArgParser() != null
2063             && j.getArgParser().isOldStyle();
2064     String title = MessageManager.getString("label.command_line_arguments");
2065     if (oldStyle)
2066     {
2067       String warning = MessageManager
2068               .getString("warning.using_old_command_line_arguments");
2069       String url = "<a href=\"https://www.jalview.org/help/html/features/commandline.html\">https://www.jalview.org/help/html/features/commandline.html</a>";
2070       if (Desktop.instance != null)
2071       {
2072         String cont = MessageManager.getString("label.continue");
2073
2074         Desktop.instance.nonBlockingDialog(title, warning, url, cont,
2075                 JvOptionPane.WARNING_MESSAGE, false, true, true, 30000);
2076       }
2077     }
2078     if (j.getCommands() != null && j.getCommands().getErrors().size() > 0)
2079     {
2080       if (Desktop.instance != null)
2081       {
2082         String message = MessageManager
2083                 .getString("warning.the_following_errors");
2084         String ok = MessageManager.getString("action.ok");
2085         int shortest = 60;
2086         List<String> errors = j.getCommands().getErrors();
2087         for (int i = 0; i < errors.size(); i++)
2088         {
2089           shortest = Math.min(shortest, errors.get(i).length());
2090         }
2091         Desktop.instance.nonBlockingDialog(
2092                 Math.max(message.length(), Math.min(60, shortest)),
2093                 Math.min(errors.size(), 20), title, message,
2094                 j.getCommands().errorsToString(), ok,
2095                 JvOptionPane.WARNING_MESSAGE, true, false, true, -1);
2096       }
2097     }
2098   }
2099
2100 }