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