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