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