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