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