Merge branch 'JAL-3878_web_services_overhaul' into try-to-update-slivka-jar
[jalview.git] / src / jalview / bin / Jalview.java
index 1c7e4ea..7a8c786 100755 (executable)
  */
 package jalview.bin;
 
+import java.util.Locale;
+
 import java.awt.GraphicsEnvironment;
+import java.awt.Color;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -38,14 +42,23 @@ import java.security.PermissionCollection;
 import java.security.Permissions;
 import java.security.Policy;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Vector;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
-import javax.swing.LookAndFeel;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
 
+import com.formdev.flatlaf.FlatLightLaf;
+import com.formdev.flatlaf.util.SystemInfo;
 import com.threerings.getdown.util.LaunchUtil;
 
+//import edu.stanford.ejalbert.launching.IBrowserLaunching;
 import groovy.lang.Binding;
 import groovy.util.GroovyScriptEngine;
 import jalview.api.AlignCalcWorkerI;
@@ -70,6 +83,9 @@ import jalview.io.NewickFile;
 import jalview.io.gff.SequenceOntologyFactory;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
+import jalview.util.ChannelProperties;
+import jalview.util.HttpUtils;
+import jalview.util.LaunchUtils;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.ws.jws2.Jws2Discoverer;
@@ -91,7 +107,6 @@ import jalview.ws.jws2.Jws2Discoverer;
  */
 public class Jalview implements ApplicationSingletonI
 {
-
   // for testing those nasty messages you cannot ever find.
   // static
   // {
@@ -116,8 +131,14 @@ public class Jalview implements ApplicationSingletonI
 
   private Jalview()
   {
+    Platform.getURLCommandArguments();
+    Platform.addJ2SDirectDatabaseCall("https://www.jalview.org");
+    Platform.addJ2SDirectDatabaseCall("http://www.jalview.org");
+    Platform.addJ2SDirectDatabaseCall("http://www.compbio.dundee.ac.uk");
+    Platform.addJ2SDirectDatabaseCall("https://www.compbio.dundee.ac.uk");
   }
 
+
   private boolean headless;
 
   private Desktop desktop;
@@ -154,9 +175,15 @@ public class Jalview implements ApplicationSingletonI
 
   static
   {
-    if (Platform.isJS()) {
-        Platform.getURLCommandArguments();
-    } else /** @j2sIgnore */
+    if (Platform.isJS())
+    {
+       Platform.getURLCommandArguments();
+    } else
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
     {
       // grab all the rights we can for the JVM
       Policy.setPolicy(new Policy()
@@ -186,8 +213,8 @@ public class Jalview implements ApplicationSingletonI
   class FeatureFetcher
   {
     /*
-     * TODO: generalise to track all jalview events to orchestrate batch
-     * processing events.
+     * TODO: generalise to track all jalview events to orchestrate batch processing
+     * events.
      */
 
     private int queued = 0;
@@ -250,8 +277,13 @@ public class Jalview implements ApplicationSingletonI
     {
       Platform.startJavaLogging();
     }
+
     getInstance().doMain(args);
+
   }
+  
+
+  
 
   /**
    * @param args
@@ -299,9 +331,7 @@ public class Jalview implements ApplicationSingletonI
     headless = false;
 
     String usrPropsFile = aparser.getValue("props");
-
     Cache.loadProperties(usrPropsFile); // must do this before
-
     boolean allowServices = true;
     
     if (isJS)
@@ -313,6 +343,7 @@ public class Jalview implements ApplicationSingletonI
       appletResourcePath = (String) aparser.getAppletValue("resourcepath",
               null, true);
     }
+
     else
     /**
      * Java only
@@ -320,7 +351,6 @@ public class Jalview implements ApplicationSingletonI
      * @j2sIgnore
      */
     {
-
       if (usrPropsFile != null)
       {
         System.out.println(
@@ -343,7 +373,10 @@ public class Jalview implements ApplicationSingletonI
         }
         headless = true;
       }
+      // anything else!
 
+      // allow https handshakes to download intermediate certs if necessary
+      System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
       final String jabawsUrl = aparser.getValue(ArgsParser.JABAWS);
       allowServices = !("none".equals(jabawsUrl));
       if (allowServices && jabawsUrl != null)
@@ -359,8 +392,8 @@ public class Jalview implements ApplicationSingletonI
                   "Invalid jabaws parameter: " + jabawsUrl + " ignored");
         }
       }
-
     }
+
     String defs = aparser.getValue(ArgsParser.SETPROP);
     while (defs != null)
     {
@@ -379,11 +412,16 @@ public class Jalview implements ApplicationSingletonI
       }
       defs = aparser.getValue("setprop");
     }
+    if (System.getProperty("java.awt.headless") != null
+            && System.getProperty("java.awt.headless").equals("true"))
+    {
+      headless = true;
+    }
     System.setProperty("http.agent",
             "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
     try
     {
-      Cache.initLogger();
+      Console.initLogger();
     } catch (NoClassDefFoundError error)
     {
       error.printStackTrace();
@@ -392,56 +430,10 @@ public class Jalview implements ApplicationSingletonI
       System.exit(0);
     }
 
-    if (!isJS)
-    /** @j2sIgnore */
-    {
-      try
-      {
-        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-      } catch (Exception ex)
-      {
-        System.err.println("Unexpected Look and Feel Exception");
-        ex.printStackTrace();
-      }
-      if (Platform.isMac())
-      {
+    desktop = null;
+
+    setLookAndFeel();
 
-        LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager
-                .getLookAndFeel();
-        System.setProperty(
-                "com.apple.mrj.application.apple.menu.about.name",
-                "Jalview");
-        System.setProperty("apple.laf.useScreenMenuBar", "true");
-        if (lookAndFeel != null)
-        {
-          try
-          {
-            UIManager.setLookAndFeel(lookAndFeel);
-          } catch (Throwable e)
-          {
-            System.err.println(
-                    "Failed to set QuaQua look and feel: " + e.toString());
-          }
-        }
-        if (lookAndFeel == null
-                || !(lookAndFeel.getClass().isAssignableFrom(
-                        UIManager.getLookAndFeel().getClass()))
-                || !UIManager.getLookAndFeel().getClass().toString()
-                        .toLowerCase().contains("quaqua"))
-        {
-          try
-          {
-            System.err.println(
-                    "Quaqua LaF not available on this plaform. Using VAqua(4).\nSee https://issues.jalview.org/browse/JAL-2976");
-            UIManager.setLookAndFeel("org.violetlib.aqua.AquaLookAndFeel");
-          } catch (Throwable e)
-          {
-            System.err.println(
-                    "Failed to reset look and feel: " + e.toString());
-          }
-        }
-      }
-    }
     /*
      * configure 'full' SO model if preferences say to, else use the default (full SO)
      * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
@@ -452,26 +444,41 @@ public class Jalview implements ApplicationSingletonI
       SequenceOntologyFactory.setSequenceOntology(new SequenceOntology());
     }
 
-
-    desktop = null;
     if (!headless)
     {
+      Desktop.nosplash = aparser.contains("nosplash");
       desktop = Desktop.getInstance();
       desktop.setInBatchMode(true); // indicate we are starting up
+
       try
       {
         JalviewTaskbar.setTaskbar(this);
+      } catch (Exception e)
+      {
+        Console.info("Cannot set Taskbar");
+        Console.error(e.getMessage());
+        // e.printStackTrace();
       } catch (Throwable t)
       {
-        System.out.println("Error setting Taskbar: " + t.getMessage());
+        Console.info("Cannot set Taskbar");
+        Console.error(t.getMessage());
+        // t.printStackTrace();
       }
+
+      // set Proxy settings before all the internet calls
+      Cache.setProxyPropertiesFromPreferences();
+
       desktop.setVisible(true);
-      if (Platform.isJS())
+
+      if (isJS)
+      {
         Cache.setProperty("SHOW_JWS2_SERVICES", "false");
-      if (allowServices)
+      }
+      if (allowServices && !aparser.contains("nowebservicediscovery"))
       {
         desktop.startServiceDiscovery();
       }
+
       if (!isJS)
       /**
        * Java only
@@ -479,6 +486,33 @@ public class Jalview implements ApplicationSingletonI
        * @j2sIgnore
        */
       {
+        /**
+         * Check to see that the JVM version being run is suitable for the Java
+         * version this Jalview was compiled for. Popup a warning if not.
+         */
+        if (!LaunchUtils.checkJavaVersion())
+        {
+          Console.warn("The Java version being used (Java "
+                  + LaunchUtils.getJavaVersion()
+                  + ") may lead to problems. This installation of Jalview should be used with Java "
+                  + LaunchUtils.getJavaCompileVersion() + ".");
+
+          if (!LaunchUtils
+                  .getBooleanUserPreference("IGNORE_JVM_WARNING_POPUP"))
+          {
+            Object[] options = {
+                MessageManager.getString("label.continue") };
+            JOptionPane.showOptionDialog(null,
+                    MessageManager.formatMessage(
+                            "warning.wrong_jvm_version_message",
+                            LaunchUtils.getJavaVersion(),
+                            LaunchUtils.getJavaCompileVersion()),
+                    MessageManager
+                            .getString("warning.wrong_jvm_version_title"),
+                    JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
+                    null, options, options[0]);
+          }
+        }
         if (!aparser.contains("nousagestats"))
         {
           startUsageStats(desktop);
@@ -495,7 +529,7 @@ public class Jalview implements ApplicationSingletonI
           {
             // Start the desktop questionnaire prompter with the specified
             // questionnaire
-            Cache.log.debug("Starting questionnaire url at " + url);
+            Console.debug("Starting questionnaire url at " + url);
             desktop.checkForQuestionnaire(url);
             System.out.println("CMD questionnaire[-" + url
                     + "] executed successfully!");
@@ -509,8 +543,8 @@ public class Jalview implements ApplicationSingletonI
               // String defurl =
               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
               // //
-              String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
-              Cache.log.debug(
+              String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
+              Console.debug(
                       "Starting questionnaire with default url: " + defurl);
               desktop.checkForQuestionnaire(defurl);
             }
@@ -522,14 +556,29 @@ public class Jalview implements ApplicationSingletonI
                   .println("CMD [-noquestionnaire] executed successfully!");
         }
 
-        if (!aparser.contains("nonews"))
+        if (!aparser.contains("nonews")
+                || Cache.getProperty("NONEWS") == null)
         {
           desktop.checkForNews();
         }
 
-        BioJsHTMLOutput.updateBioJS();
+        if (!aparser.contains("nohtmltemplates")
+                || Cache.getProperty("NOHTMLTEMPLATES") == null)
+        {
+          BioJsHTMLOutput.updateBioJS();
+        }
       }
     }
+
+    // Check if JVM and compile version might cause problems and log if it
+    // might.
+    if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
+    {
+      Console.warn("The Java version being used (Java "
+              + LaunchUtils.getJavaVersion()
+              + ") may lead to problems. This installation of Jalview should be used with Java "
+              + LaunchUtils.getJavaCompileVersion() + ".");
+    }
     parseArguments(aparser, true);
   }
 
@@ -573,7 +622,6 @@ public class Jalview implements ApplicationSingletonI
       // completed one way or another
       // extract groovy argument and execute if necessary
       groovyscript = aparser.getValue("groovy", true);
-
     }
 
     String file = aparser.getValue("open", true);
@@ -583,11 +631,9 @@ public class Jalview implements ApplicationSingletonI
       System.out.println("No files to open!");
       System.exit(1);
     }
-
     setDisplayParameters(aparser);
     
     // time to open a file.
-
     long progress = -1;
     DataSourceType protocol = null;
     FileLoader fileLoader = new FileLoader(!headless);
@@ -605,53 +651,46 @@ public class Jalview implements ApplicationSingletonI
         
       }
       else if (!headless && Cache.getDefault("SHOW_STARTUP_FILE", true))
-      /**
-       * Java only
-       * 
-       * @j2sIgnore
-       */
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
+    {
+      file = Cache.getDefault("STARTUP_FILE",
+              Cache.getDefault("www.jalview.org", "https://www.jalview.org")
+                      + "/examples/exampleFile_2_7.jvp");
+      if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
+              || file.equals(
+                      "http://www.jalview.org/examples/exampleFile_2_7.jar"))
       {
+        file.replace("http:", "https:");
+        // hardwire upgrade of the startup file
+        file.replace("_2_3", "_2_7");
+        file.replace("2_7.jar", "2_7.jvp");
+        // and remove the stale setting
+        Cache.removeProperty("STARTUP_FILE");
+      }
 
-        // We'll only open the default file if the desktop is visible.
-        // And the user
-        // ////////////////////
-
-        file = Cache.getDefault("STARTUP_FILE",
-                Cache.getDefault("www.jalview.org",
-                        "http://www.jalview.org")
-                        + "/examples/exampleFile_2_7.jar");
-        if (file.equals(
-                "http://www.jalview.org/examples/exampleFile_2_3.jar"))
-        {
-          // hardwire upgrade of the startup file
-          file.replace("_2_3.jar", "_2_7.jar");
-          // and remove the stale setting
-          Cache.removeProperty("STARTUP_FILE");
-        }
-
-        protocol = DataSourceType.FILE;
+      protocol = AppletFormatAdapter.checkProtocol(file);
 
-        if (file.indexOf("http:") > -1)
+      if (file.endsWith(".jar"))
+      {
+        format = FileFormat.Jalview;
+      }
+      else
+      {
+        try
         {
-          protocol = DataSourceType.URL;
-        }
-
-        if (file.endsWith(".jar"))
+          format = new IdentifyFile().identify(file, protocol);
+        } catch (FileFormatException e)
         {
-          format = FileFormat.Jalview;
+          // TODO what?
         }
-        else
-        {
-          try
-          {
-            format = new IdentifyFile().identify(file, protocol);
-          } catch (FileFormatException e)
-          {
-            // TODO what?
-          }
-        }
-        af = fileLoader.LoadFileWaitTillLoaded(file, protocol, format);
       }
+
+      af = fileLoader.LoadFileWaitTillLoaded(file, protocol, format);
+       }
     }
     else
     {
@@ -671,8 +710,7 @@ public class Jalview implements ApplicationSingletonI
        * @j2sIgnore
        */
       {
-        if (!file.startsWith("http://") && !file.startsWith("https://"))
-        // BH 2019 added https check for Java
+        if (!HttpUtils.startsWithHttpOrHttps(file))
         {
           if (!(new File(file)).exists())
           {
@@ -684,11 +722,13 @@ public class Jalview implements ApplicationSingletonI
           }
         }
       }
-      
+
+      // JS Only argument to provide a format parameter to specify what format to use
       String fileFormat = (isJS
               ? (String) aparser.getAppletValue("format", null, true)
               : null);
       protocol = AppletFormatAdapter.checkProtocol(file);
+
       try
       {
         format = (fileFormat != null
@@ -711,7 +751,7 @@ public class Jalview implements ApplicationSingletonI
       }
       else
       {
-
+        
         // JalviewLite interface for JavaScript allows second file open
         String file2 = aparser.getValue(ArgsParser.OPEN2, true);
         if (file2 != null)
@@ -740,6 +780,7 @@ public class Jalview implements ApplicationSingletonI
                     "CMD [-open2 " + file2 + "] executed successfully!");
           }
         }
+        // af is loaded - so set it as current frame
         setCurrentAlignFrame(af);
 
         setFrameDependentProperties(aparser, af);
@@ -782,8 +823,12 @@ public class Jalview implements ApplicationSingletonI
     {
       if (Cache.groovyJarsPresent())
       {
+        // TODO: DECIDE IF THIS SECOND PASS AT GROOVY EXECUTION IS STILL REQUIRED !!
         System.out.println("Executing script " + groovyscript);
         executeGroovyScript(groovyscript, af);
+        System.out.println("CMD groovy[" + groovyscript
+                    + "] executed successfully!");
+
       }
       else
       {
@@ -840,7 +885,6 @@ public class Jalview implements ApplicationSingletonI
     }
   }
 
-
   private void setFrameDependentProperties(ArgsParser aparser,
           AlignFrame af)
   {
@@ -926,6 +970,7 @@ public class Jalview implements ApplicationSingletonI
           doUpdateAnnotation = true;
         }
       }
+
     }
 
     if (aparser.contains(ArgsParser.NOSORTBYTREE))
@@ -987,13 +1032,14 @@ public class Jalview implements ApplicationSingletonI
    */
   private void createOutputFiles(ArgsParser aparser, FileFormatI format)
   {
+    // logic essentially the same as 2.11.2/2.11.3 but uses a switch instead
     AlignFrame af = currentAlignFrame;
     while (aparser.getSize() >= 2)
     {
       String outputFormat = aparser.nextValue();
       File imageFile;
       String fname;
-      switch (outputFormat.toLowerCase())
+      switch (outputFormat.toLowerCase(Locale.ROOT))
       {
       case "png":
         imageFile = new File(aparser.nextValue());
@@ -1088,6 +1134,259 @@ public class Jalview implements ApplicationSingletonI
     }
   }
 
+  private static void setLookAndFeel()
+  {
+    // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
+    // "mac" or "flat"
+    // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
+    // try Quaqua/Vaqua.
+    String lafProp = System.getProperty("laf");
+    String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
+    String laf = "none";
+    if (lafProp != null)
+    {
+      laf = lafProp;
+    }
+    else if (lafSetting != null)
+    {
+      laf = lafSetting;
+    }
+    boolean lafSet = false;
+    switch (laf)
+    {
+    case "crossplatform":
+      lafSet = setCrossPlatformLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "system":
+      lafSet = setSystemLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "gtk":
+      lafSet = setGtkLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "metal":
+      lafSet = setMetalLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "nimbus":
+      lafSet = setNimbusLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "flat":
+      lafSet = setFlatLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "quaqua":
+      lafSet = setQuaquaLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "vaqua":
+      lafSet = setVaquaLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "mac":
+      lafSet = setMacLookAndFeel();
+      if (!lafSet)
+      {
+        Console.error("Could not set requested laf=" + laf);
+      }
+      break;
+    case "none":
+      break;
+    default:
+      Console.error("Requested laf=" + laf + " not implemented");
+    }
+    if (!lafSet)
+    {
+      setSystemLookAndFeel();
+      if (Platform.isLinux())
+      {
+        setMetalLookAndFeel();
+      }
+      if (Platform.isMac())
+      {
+        setMacLookAndFeel();
+      }
+    }
+  }
+
+  private static boolean setCrossPlatformLookAndFeel()
+  {
+    boolean set = false;
+    try
+    {
+      UIManager.setLookAndFeel(
+              UIManager.getCrossPlatformLookAndFeelClassName());
+      set = true;
+    } catch (Exception ex)
+    {
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
+    }
+    return set;
+  }
+
+  private static boolean setSystemLookAndFeel()
+  {
+    boolean set = false;
+    try
+    {
+      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+      set = true;
+    } catch (Exception ex)
+    {
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
+    }
+    return set;
+  }
+
+  private static boolean setSpecificLookAndFeel(String name,
+          String className, boolean nameStartsWith)
+  {
+    boolean set = false;
+    try
+    {
+      for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
+      {
+        if (info.getName() != null && nameStartsWith
+                ? info.getName().toLowerCase(Locale.ROOT)
+                        .startsWith(name.toLowerCase(Locale.ROOT))
+                : info.getName().toLowerCase(Locale.ROOT)
+                        .equals(name.toLowerCase(Locale.ROOT)))
+        {
+          className = info.getClassName();
+          break;
+        }
+      }
+      UIManager.setLookAndFeel(className);
+      set = true;
+    } catch (Exception ex)
+    {
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
+    }
+    return set;
+  }
+
+  private static boolean setGtkLookAndFeel()
+  {
+    return setSpecificLookAndFeel("gtk",
+            "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
+  }
+
+  private static boolean setMetalLookAndFeel()
+  {
+    return setSpecificLookAndFeel("metal",
+            "javax.swing.plaf.metal.MetalLookAndFeel", false);
+  }
+
+  private static boolean setNimbusLookAndFeel()
+  {
+    return setSpecificLookAndFeel("nimbus",
+            "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
+  }
+
+  private static boolean setFlatLookAndFeel()
+  {
+    boolean set = setSpecificLookAndFeel("flatlaf light",
+            "com.formdev.flatlaf.FlatLightLaf", false);
+    if (set)
+    {
+      if (Platform.isMac())
+      {
+        System.setProperty("apple.laf.useScreenMenuBar", "true");
+        System.setProperty("apple.awt.application.name",
+                ChannelProperties.getProperty("app_name"));
+        System.setProperty("apple.awt.application.appearance", "system");
+        if (SystemInfo.isMacFullWindowContentSupported
+            && Desktop.getInstance() != null)
+        {
+         Desktop.getInstance().getRootPane()
+                  .putClientProperty("apple.awt.fullWindowContent", true);
+          Desktop.getInstance().getRootPane()
+                  .putClientProperty("apple.awt.transparentTitleBar", true);
+        }
+
+        SwingUtilities.invokeLater(() -> {
+          FlatLightLaf.setup();
+        });
+      }
+
+      UIManager.put("TabbedPane.showTabSeparators", true);
+      UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
+      UIManager.put("TabbedPane.tabsOverlapBorder", true);
+      // UIManager.put("TabbedPane.hasFullBorder", true);
+      UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
+      UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
+      UIManager.put("TabbedPane.smoothScrolling", true);
+      UIManager.put("TabbedPane.tabWidthMode", "compact");
+      UIManager.put("TabbedPane.selectedBackground", Color.white);
+    }
+    return set;
+  }
+  private static boolean setQuaquaLookAndFeel()
+  {
+    return setSpecificLookAndFeel("quaqua",
+            ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel().getClass()
+                    .getName(),
+            false);
+  }
+
+  private static boolean setVaquaLookAndFeel()
+  {
+    return setSpecificLookAndFeel("vaqua",
+            "org.violetlib.aqua.AquaLookAndFeel", false);
+  }
+
+  private static boolean setMacLookAndFeel()
+  {
+    boolean set = false;
+    System.setProperty("com.apple.mrj.application.apple.menu.about.name",
+            ChannelProperties.getProperty("app_name"));
+    System.setProperty("apple.laf.useScreenMenuBar", "true");
+    /*
+     * broken native LAFs on (ARM?) macbooks
+    set = setQuaquaLookAndFeel();
+    if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
+            .toLowerCase(Locale.ROOT).contains("quaqua"))
+    {
+      set = setVaquaLookAndFeel();
+    }
+     */
+    set = setFlatLookAndFeel();
+    return set;
+  }
+
   private static void showUsage()
   {
     System.out.println(
@@ -1126,7 +1425,9 @@ public class Jalview implements ApplicationSingletonI
                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
                     + "-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"
-                    + "\n~Read documentation in Application or visit http://www.jalview.org for description of Features and Annotations file~\n\n");
+                    + "-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"
+                    + "-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"
+                    + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
   }
 
   private static void startUsageStats(final Desktop desktop)
@@ -1144,17 +1445,17 @@ public class Jalview implements ApplicationSingletonI
               @Override
               public void run()
               {
-                Cache.log.debug(
+                Console.debug(
                         "Initialising googletracker for usage stats.");
                 Cache.initGoogleTracker();
-                Cache.log.debug("Tracking enabled.");
+                Console.debug("Tracking enabled.");
               }
             }, new Runnable()
             {
               @Override
               public void run()
               {
-                Cache.log.debug("Not enabling Google Tracking.");
+                Console.debug("Not enabling Google Tracking.");
               }
             }, null, true);
     desktop.addDialogThread(prompter);
@@ -1296,8 +1597,8 @@ public class Jalview implements ApplicationSingletonI
   }
 
   /**
-   * Quit method delegates to Desktop.quit - unless running in headless mode
-   * when it just ends the JVM
+   * Quit method delegates to Desktop.quit - unless running in headless mode when
+   * it just ends the JVM
    */
   public void quit()
   {
@@ -1320,7 +1621,6 @@ public class Jalview implements ApplicationSingletonI
   {
     Jalview.getInstance().currentAlignFrame = currentAlignFrame;
   }
-
   
   public void notifyWorker(AlignCalcWorkerI worker, String status)
   {
@@ -1340,5 +1640,4 @@ public class Jalview implements ApplicationSingletonI
   {
     isInteractive = tf;
   }
-
 }