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