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