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