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