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