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