JAL-4134 hold ctrl to select groups containing rows and columns under mouse over...
[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     desktop = null;
481
482     setLookAndFeel();
483
484     /*
485      * configure 'full' SO model if preferences say to, else use the default (full SO)
486      * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
487      */
488     boolean soDefault = !Platform.isJS();
489     if (Cache.getDefault("USE_FULL_SO", soDefault))
490     {
491       SequenceOntologyFactory.setInstance(new SequenceOntology());
492     }
493
494     if (!headless)
495     {
496       Desktop.nosplash = aparser.contains("nosplash");
497       desktop = new Desktop();
498       desktop.setInBatchMode(true); // indicate we are starting up
499
500       try
501       {
502         JalviewTaskbar.setTaskbar(this);
503       } catch (Exception e)
504       {
505         Console.info("Cannot set Taskbar");
506         Console.error(e.getMessage());
507         // e.printStackTrace();
508       } catch (Throwable t)
509       {
510         Console.info("Cannot set Taskbar");
511         Console.error(t.getMessage());
512         // t.printStackTrace();
513       }
514
515       // set Proxy settings before all the internet calls
516       Cache.setProxyPropertiesFromPreferences();
517
518       desktop.setVisible(true);
519
520       if (!Platform.isJS())
521       /**
522        * Java only
523        * 
524        * @j2sIgnore
525        */
526       {
527
528         /**
529          * Check to see that the JVM version being run is suitable for the Java
530          * version this Jalview was compiled for. Popup a warning if not.
531          */
532         if (!LaunchUtils.checkJavaVersion())
533         {
534           Console.warn("The Java version being used (Java "
535                   + LaunchUtils.getJavaVersion()
536                   + ") may lead to problems. This installation of Jalview should be used with Java "
537                   + LaunchUtils.getJavaCompileVersion() + ".");
538
539           if (!LaunchUtils
540                   .getBooleanUserPreference("IGNORE_JVM_WARNING_POPUP"))
541           {
542             Object[] options = {
543                 MessageManager.getString("label.continue") };
544             JOptionPane.showOptionDialog(null,
545                     MessageManager.formatMessage(
546                             "warning.wrong_jvm_version_message",
547                             LaunchUtils.getJavaVersion(),
548                             LaunchUtils.getJavaCompileVersion()),
549                     MessageManager
550                             .getString("warning.wrong_jvm_version_title"),
551                     JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
552                     null, options, options[0]);
553           }
554         }
555
556         if (!aparser.contains("nowebservicediscovery"))
557         {
558           desktop.startServiceDiscovery();
559         }
560         if (!aparser.contains("nousagestats"))
561         {
562           startUsageStats(desktop);
563         }
564         else
565         {
566           System.err.println("CMD [-nousagestats] executed successfully!");
567         }
568
569         if (!aparser.contains("noquestionnaire"))
570         {
571           String url = aparser.getValue("questionnaire");
572           if (url != null)
573           {
574             // Start the desktop questionnaire prompter with the specified
575             // questionnaire
576             Console.debug("Starting questionnaire url at " + url);
577             desktop.checkForQuestionnaire(url);
578             System.out.println("CMD questionnaire[-" + url
579                     + "] executed successfully!");
580           }
581           else
582           {
583             if (Cache.getProperty("NOQUESTIONNAIRES") == null)
584             {
585               // Start the desktop questionnaire prompter with the specified
586               // questionnaire
587               // String defurl =
588               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
589               // //
590               String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
591               Console.debug(
592                       "Starting questionnaire with default url: " + defurl);
593               desktop.checkForQuestionnaire(defurl);
594             }
595           }
596         }
597         else
598         {
599           System.err
600                   .println("CMD [-noquestionnaire] executed successfully!");
601         }
602
603         if (!aparser.contains("nonews")
604                 || Cache.getProperty("NONEWS") == null)
605         {
606           desktop.checkForNews();
607         }
608
609         if (!aparser.contains("nohtmltemplates")
610                 || Cache.getProperty("NOHTMLTEMPLATES") == null)
611         {
612           BioJsHTMLOutput.updateBioJS();
613         }
614       }
615     }
616
617     // Check if JVM and compile version might cause problems and log if it
618     // might.
619     if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
620     {
621       Console.warn("The Java version being used (Java "
622               + LaunchUtils.getJavaVersion()
623               + ") may lead to problems. This installation of Jalview should be used with Java "
624               + LaunchUtils.getJavaCompileVersion() + ".");
625     }
626
627     // Move any new getdown-launcher-new.jar into place over old
628     // getdown-launcher.jar
629     String appdirString = System.getProperty("getdownappdir");
630     if (appdirString != null && appdirString.length() > 0)
631     {
632       final File appdir = new File(appdirString);
633       new Thread()
634       {
635         @Override
636         public void run()
637         {
638           LaunchUtil.upgradeGetdown(
639                   new File(appdir, "getdown-launcher-old.jar"),
640                   new File(appdir, "getdown-launcher.jar"),
641                   new File(appdir, "getdown-launcher-new.jar"));
642         }
643       }.start();
644     }
645
646     String file = null, data = null;
647
648     FileFormatI format = null;
649
650     DataSourceType protocol = null;
651
652     FileLoader fileLoader = new FileLoader(!headless);
653
654     String groovyscript = null; // script to execute after all loading is
655     // completed one way or another
656     // extract groovy argument and execute if necessary
657     groovyscript = aparser.getValue("groovy", true);
658     file = aparser.getValue("open", true);
659
660     if (file == null && desktop == null)
661     {
662       System.out.println("No files to open!");
663       System.exit(1);
664     }
665
666     long progress = -1;
667     // Finally, deal with the remaining input data.
668     if (file != null)
669     {
670       if (!headless)
671       {
672         desktop.setProgressBar(
673                 MessageManager
674                         .getString("status.processing_commandline_args"),
675                 progress = System.currentTimeMillis());
676       }
677       System.out.println("CMD [-open " + file + "] executed successfully!");
678
679       if (!Platform.isJS())
680       /**
681        * ignore in JavaScript -- can't just file existence - could load it?
682        * 
683        * @j2sIgnore
684        */
685       {
686         if (!HttpUtils.startsWithHttpOrHttps(file))
687         {
688           if (!(new File(file)).exists())
689           {
690             System.out.println("Can't find " + file);
691             if (headless)
692             {
693               System.exit(1);
694             }
695           }
696         }
697       }
698
699       protocol = AppletFormatAdapter.checkProtocol(file);
700
701       try
702       {
703         format = new IdentifyFile().identify(file, protocol);
704       } catch (FileFormatException e1)
705       {
706         // TODO ?
707       }
708
709       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
710               format);
711       if (af == null)
712       {
713         System.out.println("error");
714       }
715       else
716       {
717         setCurrentAlignFrame(af);
718         data = aparser.getValue("colour", true);
719         if (data != null)
720         {
721           data.replaceAll("%20", " ");
722
723           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
724                   af.getViewport(), af.getViewport().getAlignment(), data);
725
726           if (cs != null)
727           {
728             System.out.println(
729                     "CMD [-color " + data + "] executed successfully!");
730           }
731           af.changeColour(cs);
732         }
733
734         // Must maintain ability to use the groups flag
735         data = aparser.getValue("groups", true);
736         if (data != null)
737         {
738           af.parseFeaturesFile(data,
739                   AppletFormatAdapter.checkProtocol(data));
740           // System.out.println("Added " + data);
741           System.out.println(
742                   "CMD groups[-" + data + "]  executed successfully!");
743         }
744         data = aparser.getValue("features", true);
745         if (data != null)
746         {
747           af.parseFeaturesFile(data,
748                   AppletFormatAdapter.checkProtocol(data));
749           // System.out.println("Added " + data);
750           System.out.println(
751                   "CMD [-features " + data + "]  executed successfully!");
752         }
753
754         data = aparser.getValue("annotations", true);
755         if (data != null)
756         {
757           af.loadJalviewDataFile(data, null, null, null);
758           // System.out.println("Added " + data);
759           System.out.println(
760                   "CMD [-annotations " + data + "] executed successfully!");
761         }
762         // set or clear the sortbytree flag.
763         if (aparser.contains("sortbytree"))
764         {
765           af.getViewport().setSortByTree(true);
766           if (af.getViewport().getSortByTree())
767           {
768             System.out.println("CMD [-sortbytree] executed successfully!");
769           }
770         }
771         if (aparser.contains("no-annotation"))
772         {
773           af.getViewport().setShowAnnotation(false);
774           if (!af.getViewport().isShowAnnotation())
775           {
776             System.out.println("CMD no-annotation executed successfully!");
777           }
778         }
779         if (aparser.contains("nosortbytree"))
780         {
781           af.getViewport().setSortByTree(false);
782           if (!af.getViewport().getSortByTree())
783           {
784             System.out
785                     .println("CMD [-nosortbytree] executed successfully!");
786           }
787         }
788         data = aparser.getValue("tree", true);
789         if (data != null)
790         {
791           try
792           {
793             System.out.println(
794                     "CMD [-tree " + data + "] executed successfully!");
795             NewickFile nf = new NewickFile(data,
796                     AppletFormatAdapter.checkProtocol(data));
797             af.getViewport()
798                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
799           } catch (IOException ex)
800           {
801             System.err.println("Couldn't add tree " + data);
802             ex.printStackTrace(System.err);
803           }
804         }
805         // TODO - load PDB structure(s) to alignment JAL-629
806         // (associate with identical sequence in alignment, or a specified
807         // sequence)
808         if (groovyscript != null)
809         {
810           // Execute the groovy script after we've done all the rendering stuff
811           // and before any images or figures are generated.
812           System.out.println("Executing script " + groovyscript);
813           executeGroovyScript(groovyscript, af);
814           System.out.println("CMD groovy[" + groovyscript
815                   + "] executed successfully!");
816           groovyscript = null;
817         }
818         String imageName = "unnamed.png";
819         while (aparser.getSize() > 1)
820         {
821           String outputFormat = aparser.nextValue();
822           file = aparser.nextValue();
823
824           if (outputFormat.equalsIgnoreCase("png"))
825           {
826             af.createPNG(new File(file));
827             imageName = (new File(file)).getName();
828             System.out.println("Creating PNG image: " + file);
829             continue;
830           }
831           else if (outputFormat.equalsIgnoreCase("svg"))
832           {
833             File imageFile = new File(file);
834             imageName = imageFile.getName();
835             af.createSVG(imageFile);
836             System.out.println("Creating SVG image: " + file);
837             continue;
838           }
839           else if (outputFormat.equalsIgnoreCase("html"))
840           {
841             File imageFile = new File(file);
842             imageName = imageFile.getName();
843             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
844             htmlSVG.exportHTML(file);
845
846             System.out.println("Creating HTML image: " + file);
847             continue;
848           }
849           else if (outputFormat.equalsIgnoreCase("biojsmsa"))
850           {
851             if (file == null)
852             {
853               System.err.println("The output html file must not be null");
854               return;
855             }
856             try
857             {
858               BioJsHTMLOutput.refreshVersionInfo(
859                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
860             } catch (URISyntaxException e)
861             {
862               e.printStackTrace();
863             }
864             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
865             bjs.exportHTML(file);
866             System.out
867                     .println("Creating BioJS MSA Viwer HTML file: " + file);
868             continue;
869           }
870           else if (outputFormat.equalsIgnoreCase("imgMap"))
871           {
872             af.createImageMap(new File(file), imageName);
873             System.out.println("Creating image map: " + file);
874             continue;
875           }
876           else if (outputFormat.equalsIgnoreCase("eps"))
877           {
878             File outputFile = new File(file);
879             System.out.println(
880                     "Creating EPS file: " + outputFile.getAbsolutePath());
881             af.createEPS(outputFile);
882             continue;
883           }
884           FileFormatI outFormat = null;
885           try
886           {
887             outFormat = FileFormats.getInstance().forName(outputFormat);
888           } catch (Exception formatP)
889           {
890             System.out.println("Couldn't parse " + outFormat
891                     + " as a valid Jalview format string.");
892           }
893           if (outFormat != null)
894           {
895             if (!outFormat.isWritable())
896             {
897               System.out.println(
898                       "This version of Jalview does not support alignment export as "
899                               + outputFormat);
900             }
901             else
902             {
903               af.saveAlignment(file, outFormat);
904               if (af.isSaveAlignmentSuccessful())
905               {
906                 System.out.println("Written alignment in "
907                         + outFormat.getName() + " format to " + file);
908               }
909               else
910               {
911                 System.out.println("Error writing file " + file + " in "
912                         + outFormat.getName() + " format!!");
913               }
914             }
915           }
916
917         }
918
919         while (aparser.getSize() > 0)
920         {
921           System.out.println("Unknown arg: " + aparser.nextValue());
922         }
923       }
924     }
925
926     AlignFrame startUpAlframe = null;
927     // We'll only open the default file if the desktop is visible.
928     // And the user
929     // ////////////////////
930
931     if (!Platform.isJS() && !headless && file == null
932             && Cache.getDefault("SHOW_STARTUP_FILE", true))
933     /**
934      * Java only
935      * 
936      * @j2sIgnore
937      */
938     {
939       file = Cache.getDefault("STARTUP_FILE",
940               Cache.getDefault("www.jalview.org", "https://www.jalview.org")
941                       + "/examples/exampleFile_2_7.jvp");
942       if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
943               || file.equals(
944                       "http://www.jalview.org/examples/exampleFile_2_7.jar"))
945       {
946         file.replace("http:", "https:");
947         // hardwire upgrade of the startup file
948         file.replace("_2_3", "_2_7");
949         file.replace("2_7.jar", "2_7.jvp");
950         // and remove the stale setting
951         Cache.removeProperty("STARTUP_FILE");
952       }
953
954       protocol = AppletFormatAdapter.checkProtocol(file);
955
956       if (file.endsWith(".jar"))
957       {
958         format = FileFormat.Jalview;
959       }
960       else
961       {
962         try
963         {
964           format = new IdentifyFile().identify(file, protocol);
965         } catch (FileFormatException e)
966         {
967           // TODO what?
968         }
969       }
970
971       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
972               format);
973       // don't ask to save when quitting if only the startup file has been
974       // opened
975       Console.debug("Resetting up-to-date flag for startup file");
976       startUpAlframe.getViewport().setSavedUpToDate(true);
977       // extract groovy arguments before anything else.
978     }
979
980     // Once all other stuff is done, execute any groovy scripts (in order)
981     if (groovyscript != null)
982     {
983       if (Cache.groovyJarsPresent())
984       {
985         System.out.println("Executing script " + groovyscript);
986         executeGroovyScript(groovyscript, startUpAlframe);
987       }
988       else
989       {
990         System.err.println(
991                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
992                         + groovyscript);
993       }
994     }
995     // and finally, turn off batch mode indicator - if the desktop still exists
996     if (desktop != null)
997     {
998       if (progress != -1)
999       {
1000         desktop.setProgressBar(null, progress);
1001       }
1002       desktop.setInBatchMode(false);
1003     }
1004   }
1005
1006   private static void setLookAndFeel()
1007   {
1008     // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
1009     // "mac" or "flat"
1010     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
1011     // try Quaqua/Vaqua.
1012     String lafProp = System.getProperty("laf");
1013     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
1014     String laf = "none";
1015     if (lafProp != null)
1016     {
1017       laf = lafProp;
1018     }
1019     else if (lafSetting != null)
1020     {
1021       laf = lafSetting;
1022     }
1023     boolean lafSet = false;
1024     switch (laf)
1025     {
1026     case "crossplatform":
1027       lafSet = setCrossPlatformLookAndFeel();
1028       if (!lafSet)
1029       {
1030         Console.error("Could not set requested laf=" + laf);
1031       }
1032       break;
1033     case "system":
1034       lafSet = setSystemLookAndFeel();
1035       if (!lafSet)
1036       {
1037         Console.error("Could not set requested laf=" + laf);
1038       }
1039       break;
1040     case "gtk":
1041       lafSet = setGtkLookAndFeel();
1042       if (!lafSet)
1043       {
1044         Console.error("Could not set requested laf=" + laf);
1045       }
1046       break;
1047     case "metal":
1048       lafSet = setMetalLookAndFeel();
1049       if (!lafSet)
1050       {
1051         Console.error("Could not set requested laf=" + laf);
1052       }
1053       break;
1054     case "nimbus":
1055       lafSet = setNimbusLookAndFeel();
1056       if (!lafSet)
1057       {
1058         Console.error("Could not set requested laf=" + laf);
1059       }
1060       break;
1061     case "flat":
1062       lafSet = setFlatLookAndFeel();
1063       if (!lafSet)
1064       {
1065         Console.error("Could not set requested laf=" + laf);
1066       }
1067       break;
1068     case "mac":
1069       lafSet = setMacLookAndFeel();
1070       if (!lafSet)
1071       {
1072         Console.error("Could not set requested laf=" + laf);
1073       }
1074       break;
1075     case "none":
1076       break;
1077     default:
1078       Console.error("Requested laf=" + laf + " not implemented");
1079     }
1080     if (!lafSet)
1081     {
1082       setSystemLookAndFeel();
1083       if (Platform.isLinux())
1084       {
1085         setLinuxLookAndFeel();
1086       }
1087       if (Platform.isMac())
1088       {
1089         setMacLookAndFeel();
1090       }
1091     }
1092   }
1093
1094   private static boolean setCrossPlatformLookAndFeel()
1095   {
1096     boolean set = false;
1097     try
1098     {
1099       UIManager.setLookAndFeel(
1100               UIManager.getCrossPlatformLookAndFeelClassName());
1101       set = true;
1102     } catch (Exception ex)
1103     {
1104       Console.error("Unexpected Look and Feel Exception");
1105       Console.error(ex.getMessage());
1106       Console.debug(Cache.getStackTraceString(ex));
1107     }
1108     return set;
1109   }
1110
1111   private static boolean setSystemLookAndFeel()
1112   {
1113     boolean set = false;
1114     try
1115     {
1116       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1117       set = true;
1118     } catch (Exception ex)
1119     {
1120       Console.error("Unexpected Look and Feel Exception");
1121       Console.error(ex.getMessage());
1122       Console.debug(Cache.getStackTraceString(ex));
1123     }
1124     return set;
1125   }
1126
1127   private static boolean setSpecificLookAndFeel(String name,
1128           String className, boolean nameStartsWith)
1129   {
1130     boolean set = false;
1131     try
1132     {
1133       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
1134       {
1135         if (info.getName() != null && nameStartsWith
1136                 ? info.getName().toLowerCase(Locale.ROOT)
1137                         .startsWith(name.toLowerCase(Locale.ROOT))
1138                 : info.getName().toLowerCase(Locale.ROOT)
1139                         .equals(name.toLowerCase(Locale.ROOT)))
1140         {
1141           className = info.getClassName();
1142           break;
1143         }
1144       }
1145       UIManager.setLookAndFeel(className);
1146       set = true;
1147     } catch (Exception ex)
1148     {
1149       Console.error("Unexpected Look and Feel Exception");
1150       Console.error(ex.getMessage());
1151       Console.debug(Cache.getStackTraceString(ex));
1152     }
1153     return set;
1154   }
1155
1156   private static boolean setGtkLookAndFeel()
1157   {
1158     return setSpecificLookAndFeel("gtk",
1159             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
1160   }
1161
1162   private static boolean setMetalLookAndFeel()
1163   {
1164     return setSpecificLookAndFeel("metal",
1165             "javax.swing.plaf.metal.MetalLookAndFeel", false);
1166   }
1167
1168   private static boolean setNimbusLookAndFeel()
1169   {
1170     return setSpecificLookAndFeel("nimbus",
1171             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
1172   }
1173
1174   private static boolean setFlatLookAndFeel()
1175   {
1176     boolean set = false;
1177     if (SystemInfo.isMacOS)
1178     {
1179       try
1180       {
1181         UIManager.setLookAndFeel(
1182                 "com.formdev.flatlaf.themes.FlatMacLightLaf");
1183         set = true;
1184         Console.debug("Using FlatMacLightLaf");
1185       } catch (ClassNotFoundException | InstantiationException
1186               | IllegalAccessException | UnsupportedLookAndFeelException e)
1187       {
1188         Console.debug("Exception loading FlatLightLaf", e);
1189       }
1190       System.setProperty("apple.laf.useScreenMenuBar", "true");
1191       System.setProperty("apple.awt.application.name",
1192               ChannelProperties.getProperty("app_name"));
1193       System.setProperty("apple.awt.application.appearance", "system");
1194       if (SystemInfo.isMacFullWindowContentSupported
1195               && Desktop.desktop != null)
1196       {
1197         Console.debug("Setting transparent title bar");
1198         Desktop.desktop.getRootPane()
1199                 .putClientProperty("apple.awt.fullWindowContent", true);
1200         Desktop.desktop.getRootPane()
1201                 .putClientProperty("apple.awt.transparentTitleBar", true);
1202         Desktop.desktop.getRootPane()
1203                 .putClientProperty("apple.awt.fullscreenable", true);
1204       }
1205       SwingUtilities.invokeLater(() -> {
1206         FlatMacLightLaf.setup();
1207       });
1208       Console.debug("Using FlatMacLightLaf");
1209       set = true;
1210     }
1211     if (!set)
1212     {
1213       try
1214       {
1215         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1216         set = true;
1217         Console.debug("Using FlatLightLaf");
1218       } catch (ClassNotFoundException | InstantiationException
1219               | IllegalAccessException | UnsupportedLookAndFeelException e)
1220       {
1221         Console.debug("Exception loading FlatLightLaf", e);
1222       }
1223       // Windows specific properties here
1224       SwingUtilities.invokeLater(() -> {
1225         FlatLightLaf.setup();
1226       });
1227       Console.debug("Using FlatLightLaf");
1228       set = true;
1229     }
1230     else if (SystemInfo.isLinux)
1231     {
1232       try
1233       {
1234         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1235         set = true;
1236         Console.debug("Using FlatLightLaf");
1237       } catch (ClassNotFoundException | InstantiationException
1238               | IllegalAccessException | UnsupportedLookAndFeelException e)
1239       {
1240         Console.debug("Exception loading FlatLightLaf", e);
1241       }
1242       // enable custom window decorations
1243       JFrame.setDefaultLookAndFeelDecorated(true);
1244       JDialog.setDefaultLookAndFeelDecorated(true);
1245       SwingUtilities.invokeLater(() -> {
1246         FlatLightLaf.setup();
1247       });
1248       Console.debug("Using FlatLightLaf");
1249       set = true;
1250     }
1251
1252     if (!set)
1253     {
1254       try
1255       {
1256         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1257         set = true;
1258         Console.debug("Using FlatLightLaf");
1259       } catch (ClassNotFoundException | InstantiationException
1260               | IllegalAccessException | UnsupportedLookAndFeelException e)
1261       {
1262         Console.debug("Exception loading FlatLightLaf", e);
1263       }
1264     }
1265
1266     if (set)
1267     {
1268       UIManager.put("TabbedPane.tabType", "card");
1269       UIManager.put("TabbedPane.showTabSeparators", true);
1270       UIManager.put("TabbedPane.showContentSeparator", true);
1271       UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
1272       UIManager.put("TabbedPane.tabsOverlapBorder", true);
1273       UIManager.put("TabbedPane.hasFullBorder", true);
1274       UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
1275       UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
1276       UIManager.put("TabbedPane.smoothScrolling", true);
1277       UIManager.put("TabbedPane.tabWidthMode", "compact");
1278       UIManager.put("TabbedPane.selectedBackground", Color.white);
1279     }
1280
1281     Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
1282     return set;
1283   }
1284
1285   private static boolean setMacLookAndFeel()
1286   {
1287     boolean set = false;
1288     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
1289             ChannelProperties.getProperty("app_name"));
1290     System.setProperty("apple.laf.useScreenMenuBar", "true");
1291     /*
1292      * broken native LAFs on (ARM?) macbooks
1293     set = setQuaquaLookAndFeel();
1294     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
1295             .toLowerCase(Locale.ROOT).contains("quaqua"))
1296     {
1297       set = setVaquaLookAndFeel();
1298     }
1299      */
1300     set = setFlatLookAndFeel();
1301     return set;
1302   }
1303
1304   private static boolean setLinuxLookAndFeel()
1305   {
1306     boolean set = false;
1307     set = setFlatLookAndFeel();
1308     if (!set)
1309       set = setMetalLookAndFeel();
1310     // avoid GtkLookAndFeel -- not good results especially on HiDPI
1311     if (!set)
1312       set = setNimbusLookAndFeel();
1313     return set;
1314   }
1315
1316   private static void showUsage()
1317   {
1318     System.out.println(
1319             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1320                     + "-nodisplay\tRun Jalview without User Interface.\n"
1321                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1322                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1323                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1324                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1325                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1326                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1327                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1328                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1329                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1330                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1331                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1332                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1333                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1334                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1335                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1336                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1337                     + "-html FILE\tCreate HTML file from alignment.\n"
1338                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1339                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1340                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1341                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1342                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1343                     + "-nonews\tTurn off check for Jalview news.\n"
1344                     + "-nousagestats\tTurn off google analytics tracking for this session.\n"
1345                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1346                     // +
1347                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1348                     // after all other properties files have been read\n\t
1349                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1350                     // passed in correctly)"
1351                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1352                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1353                     + "-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"
1354                     + "-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"
1355                     + "-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"
1356                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
1357   }
1358
1359   private static void startUsageStats(final Desktop desktop)
1360   {
1361     /**
1362      * start a User Config prompt asking if we can log usage statistics.
1363      */
1364     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1365             "USAGESTATS", "Jalview Usage Statistics",
1366             "Do you want to help make Jalview better by enabling "
1367                     + "the collection of usage statistics with Google Analytics ?"
1368                     + "\n\n(you can enable or disable usage tracking in the preferences)",
1369             new Runnable()
1370             {
1371               @Override
1372               public void run()
1373               {
1374                 Console.debug(
1375                         "Initialising googletracker for usage stats.");
1376                 Cache.initGoogleTracker();
1377                 Console.debug("Tracking enabled.");
1378               }
1379             }, new Runnable()
1380             {
1381               @Override
1382               public void run()
1383               {
1384                 Console.debug("Not enabling Google Tracking.");
1385               }
1386             }, null, true);
1387     desktop.addDialogThread(prompter);
1388   }
1389
1390   /**
1391    * Locate the given string as a file and pass it to the groovy interpreter.
1392    * 
1393    * @param groovyscript
1394    *          the script to execute
1395    * @param jalviewContext
1396    *          the Jalview Desktop object passed in to the groovy binding as the
1397    *          'Jalview' object.
1398    */
1399   private void executeGroovyScript(String groovyscript, AlignFrame af)
1400   {
1401     /**
1402      * for scripts contained in files
1403      */
1404     File tfile = null;
1405     /**
1406      * script's URI
1407      */
1408     URL sfile = null;
1409     if (groovyscript.trim().equals("STDIN"))
1410     {
1411       // read from stdin into a tempfile and execute it
1412       try
1413       {
1414         tfile = File.createTempFile("jalview", "groovy");
1415         PrintWriter outfile = new PrintWriter(
1416                 new OutputStreamWriter(new FileOutputStream(tfile)));
1417         BufferedReader br = new BufferedReader(
1418                 new InputStreamReader(System.in));
1419         String line = null;
1420         while ((line = br.readLine()) != null)
1421         {
1422           outfile.write(line + "\n");
1423         }
1424         br.close();
1425         outfile.flush();
1426         outfile.close();
1427
1428       } catch (Exception ex)
1429       {
1430         System.err.println("Failed to read from STDIN into tempfile "
1431                 + ((tfile == null) ? "(tempfile wasn't created)"
1432                         : tfile.toString()));
1433         ex.printStackTrace();
1434         return;
1435       }
1436       try
1437       {
1438         sfile = tfile.toURI().toURL();
1439       } catch (Exception x)
1440       {
1441         System.err.println(
1442                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1443                         + tfile.toURI());
1444         x.printStackTrace();
1445         return;
1446       }
1447     }
1448     else
1449     {
1450       try
1451       {
1452         sfile = new URI(groovyscript).toURL();
1453       } catch (Exception x)
1454       {
1455         tfile = new File(groovyscript);
1456         if (!tfile.exists())
1457         {
1458           System.err.println("File '" + groovyscript + "' does not exist.");
1459           return;
1460         }
1461         if (!tfile.canRead())
1462         {
1463           System.err.println("File '" + groovyscript + "' cannot be read.");
1464           return;
1465         }
1466         if (tfile.length() < 1)
1467         {
1468           System.err.println("File '" + groovyscript + "' is empty.");
1469           return;
1470         }
1471         try
1472         {
1473           sfile = tfile.getAbsoluteFile().toURI().toURL();
1474         } catch (Exception ex)
1475         {
1476           System.err.println("Failed to create a file URL for "
1477                   + tfile.getAbsoluteFile());
1478           return;
1479         }
1480       }
1481     }
1482     try
1483     {
1484       Map<String, java.lang.Object> vbinding = new HashMap<>();
1485       vbinding.put("Jalview", this);
1486       if (af != null)
1487       {
1488         vbinding.put("currentAlFrame", af);
1489       }
1490       Binding gbinding = new Binding(vbinding);
1491       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1492       gse.run(sfile.toString(), gbinding);
1493       if ("STDIN".equals(groovyscript))
1494       {
1495         // delete temp file that we made -
1496         // only if it was successfully executed
1497         tfile.delete();
1498       }
1499     } catch (Exception e)
1500     {
1501       System.err.println("Exception Whilst trying to execute file " + sfile
1502               + " as a groovy script.");
1503       e.printStackTrace(System.err);
1504
1505     }
1506   }
1507
1508   public static boolean isHeadlessMode()
1509   {
1510     String isheadless = System.getProperty("java.awt.headless");
1511     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1512     {
1513       return true;
1514     }
1515     return false;
1516   }
1517
1518   public AlignFrame[] getAlignFrames()
1519   {
1520     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1521             : Desktop.getAlignFrames();
1522
1523   }
1524
1525   /**
1526    * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
1527    */
1528   public void quit()
1529   {
1530     // System.exit will run the shutdownHook first
1531     System.exit(0);
1532   }
1533
1534   public static AlignFrame getCurrentAlignFrame()
1535   {
1536     return Jalview.currentAlignFrame;
1537   }
1538
1539   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
1540   {
1541     Jalview.currentAlignFrame = currentAlignFrame;
1542   }
1543 }