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