JAL-1988 Make sensible choices if only the opening file has been opened, and alter...
[jalview.git] / src / jalview / bin / Jalview.java
index a1a6846..cb078b8 100755 (executable)
@@ -38,23 +38,35 @@ 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.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
 import javax.swing.UIManager.LookAndFeelInfo;
+import javax.swing.UnsupportedLookAndFeelException;
 
+import com.formdev.flatlaf.FlatLightLaf;
+import com.formdev.flatlaf.themes.FlatMacLightLaf;
+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.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
 import jalview.gui.PromptUserConfig;
+import jalview.gui.QuitHandler;
+import jalview.gui.QuitHandler.QResponse;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
@@ -71,6 +83,7 @@ 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;
@@ -95,6 +108,10 @@ public class Jalview
   static
   {
     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");
   }
 
   /*
@@ -260,6 +277,28 @@ public class Jalview
     if (!Platform.isJS())
     {
       System.setSecurityManager(null);
+
+      Runtime.getRuntime().addShutdownHook(new Thread()
+      {
+        public void run()
+        {
+          Console.debug("Running shutdown hook");
+          if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
+          {
+            // Got to here by a SIGTERM signal.
+            // Note we will not actually cancel the quit from here -- it's too
+            // late -- but we can wait for saving files.
+            Console.debug("Checking for saving files");
+            QuitHandler.getQuitResponse(false);
+          }
+          else
+          {
+            Console.debug("Nothing more to do");
+          }
+          Console.debug("Exiting, bye!");
+          // shutdownHook cannot be cancelled, JVM will now halt
+        }
+      });
     }
 
     System.out
@@ -268,6 +307,7 @@ public class Jalview
     System.out.println(System.getProperty("os.arch") + " "
             + System.getProperty("os.name") + " "
             + System.getProperty("os.version"));
+
     String val = System.getProperty("sys.install4jVersion");
     if (val != null)
     {
@@ -288,10 +328,12 @@ public class Jalview
     Cache.loadBuildProperties(true);
 
     ArgsParser aparser = new ArgsParser(args);
+
     boolean headless = false;
 
     String usrPropsFile = aparser.getValue("props");
-    Cache.loadProperties(usrPropsFile); // must do this before
+    Cache.loadProperties(usrPropsFile); // must do this
+                                        // before
     if (usrPropsFile != null)
     {
       System.out.println(
@@ -318,6 +360,9 @@ public class Jalview
       }
       // anything else!
 
+      // allow https handshakes to download intermediate certs if necessary
+      System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
+
       final String jabawsUrl = aparser.getValue("jabaws");
       if (jabawsUrl != null)
       {
@@ -362,10 +407,13 @@ public class Jalview
     }
     System.setProperty("http.agent",
             "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
+
     try
     {
-      Cache.initLogger();
-    } catch (NoClassDefFoundError error)
+      Console.initLogger();
+    } catch (
+
+    NoClassDefFoundError error)
     {
       error.printStackTrace();
       System.out.println("\nEssential logging libraries not found."
@@ -398,13 +446,13 @@ public class Jalview
         JalviewTaskbar.setTaskbar(this);
       } catch (Exception e)
       {
-        Cache.log.info("Cannot set Taskbar");
-        Cache.log.error(e.getMessage());
+        Console.info("Cannot set Taskbar");
+        Console.error(e.getMessage());
         // e.printStackTrace();
       } catch (Throwable t)
       {
-        Cache.log.info("Cannot set Taskbar");
-        Cache.log.error(t.getMessage());
+        Console.info("Cannot set Taskbar");
+        Console.error(t.getMessage());
         // t.printStackTrace();
       }
 
@@ -420,6 +468,35 @@ public class Jalview
        * @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("nowebservicediscovery"))
         {
           desktop.startServiceDiscovery();
@@ -440,7 +517,7 @@ public class Jalview
           {
             // 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!");
@@ -454,8 +531,8 @@ public class Jalview
               // 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);
             }
@@ -467,15 +544,30 @@ public class Jalview
                   .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() + ".");
+    }
+
     // Move any new getdown-launcher-new.jar into place over old
     // getdown-launcher.jar
     String appdirString = System.getProperty("getdownappdir");
@@ -496,8 +588,11 @@ public class Jalview
     }
 
     String file = null, data = null;
+
     FileFormatI format = null;
+
     DataSourceType protocol = null;
+
     FileLoader fileLoader = new FileLoader(!headless);
 
     String groovyscript = null; // script to execute after all loading is
@@ -511,6 +606,7 @@ public class Jalview
       System.out.println("No files to open!");
       System.exit(1);
     }
+
     long progress = -1;
     // Finally, deal with the remaining input data.
     if (file != null)
@@ -770,6 +866,7 @@ public class Jalview
         }
       }
     }
+
     AlignFrame startUpAlframe = null;
     // We'll only open the default file if the desktop is visible.
     // And the user
@@ -784,13 +881,16 @@ public class Jalview
      */
     {
       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"))
+              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.jar", "_2_7.jar");
+        file.replace("_2_3", "_2_7");
+        file.replace("2_7.jar", "2_7.jvp");
         // and remove the stale setting
         Cache.removeProperty("STARTUP_FILE");
       }
@@ -814,6 +914,10 @@ public class Jalview
 
       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
               format);
+      // don't ask to save when quitting if only the startup file has been
+      // opened
+      Console.debug("Resetting up-to-date flag for startup file");
+      startUpAlframe.getViewport().setSavedUpToDate(true);
       // extract groovy arguments before anything else.
     }
 
@@ -867,76 +971,62 @@ public class Jalview
       lafSet = setCrossPlatformLookAndFeel();
       if (!lafSet)
       {
-        Cache.log.error("Could not set requested laf=" + laf);
+        Console.error("Could not set requested laf=" + laf);
       }
       break;
     case "system":
       lafSet = setSystemLookAndFeel();
       if (!lafSet)
       {
-        Cache.log.error("Could not set requested laf=" + laf);
+        Console.error("Could not set requested laf=" + laf);
       }
       break;
     case "gtk":
       lafSet = setGtkLookAndFeel();
       if (!lafSet)
       {
-        Cache.log.error("Could not set requested laf=" + laf);
+        Console.error("Could not set requested laf=" + laf);
       }
       break;
     case "metal":
       lafSet = setMetalLookAndFeel();
       if (!lafSet)
       {
-        Cache.log.error("Could not set requested laf=" + laf);
+        Console.error("Could not set requested laf=" + laf);
       }
       break;
     case "nimbus":
       lafSet = setNimbusLookAndFeel();
       if (!lafSet)
       {
-        Cache.log.error("Could not set requested laf=" + laf);
+        Console.error("Could not set requested laf=" + laf);
       }
       break;
     case "flat":
       lafSet = setFlatLookAndFeel();
       if (!lafSet)
       {
-        Cache.log.error("Could not set requested laf=" + laf);
-      }
-      break;
-    case "quaqua":
-      lafSet = setQuaquaLookAndFeel();
-      if (!lafSet)
-      {
-        Cache.log.error("Could not set requested laf=" + laf);
-      }
-      break;
-    case "vaqua":
-      lafSet = setVaquaLookAndFeel();
-      if (!lafSet)
-      {
-        Cache.log.error("Could not set requested laf=" + laf);
+        Console.error("Could not set requested laf=" + laf);
       }
       break;
     case "mac":
       lafSet = setMacLookAndFeel();
       if (!lafSet)
       {
-        Cache.log.error("Could not set requested laf=" + laf);
+        Console.error("Could not set requested laf=" + laf);
       }
       break;
     case "none":
       break;
     default:
-      Cache.log.error("Requested laf=" + laf + " not implemented");
+      Console.error("Requested laf=" + laf + " not implemented");
     }
     if (!lafSet)
     {
       setSystemLookAndFeel();
       if (Platform.isLinux())
       {
-        setMetalLookAndFeel();
+        setLinuxLookAndFeel();
       }
       if (Platform.isMac())
       {
@@ -955,9 +1045,9 @@ public class Jalview
       set = true;
     } catch (Exception ex)
     {
-      Cache.log.error("Unexpected Look and Feel Exception");
-      Cache.log.error(ex.getMessage());
-      Cache.log.debug(Cache.getStackTraceString(ex));
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
     }
     return set;
   }
@@ -971,9 +1061,9 @@ public class Jalview
       set = true;
     } catch (Exception ex)
     {
-      Cache.log.error("Unexpected Look and Feel Exception");
-      Cache.log.error(ex.getMessage());
-      Cache.log.debug(Cache.getStackTraceString(ex));
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
     }
     return set;
   }
@@ -987,9 +1077,10 @@ public class Jalview
       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
       {
         if (info.getName() != null && nameStartsWith
-                ? info.getName().toLowerCase()
-                        .startsWith(name.toLowerCase())
-                : info.getName().equalsIgnoreCase(name.toLowerCase()))
+                ? info.getName().toLowerCase(Locale.ROOT)
+                        .startsWith(name.toLowerCase(Locale.ROOT))
+                : info.getName().toLowerCase(Locale.ROOT)
+                        .equals(name.toLowerCase(Locale.ROOT)))
         {
           className = info.getClassName();
           break;
@@ -999,9 +1090,9 @@ public class Jalview
       set = true;
     } catch (Exception ex)
     {
-      Cache.log.error("Unexpected Look and Feel Exception");
-      Cache.log.error(ex.getMessage());
-      Cache.log.debug(Cache.getStackTraceString(ex));
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
     }
     return set;
   }
@@ -1026,29 +1117,113 @@ public class Jalview
 
   private static boolean setFlatLookAndFeel()
   {
-    boolean set = setSpecificLookAndFeel("flatlaf light",
-            "com.formdev.flatlaf.FlatLightLaf", false);
+    boolean set = false;
+    if (SystemInfo.isMacOS)
+    {
+      try
+      {
+        UIManager.setLookAndFeel(
+                "com.formdev.flatlaf.themes.FlatMacLightLaf");
+        set = true;
+        Console.debug("Using FlatMacLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
+      }
+      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.desktop != null)
+      {
+        Console.debug("Setting transparent title bar");
+        Desktop.desktop.getRootPane()
+                .putClientProperty("apple.awt.fullWindowContent", true);
+        Desktop.desktop.getRootPane()
+                .putClientProperty("apple.awt.transparentTitleBar", true);
+        Desktop.desktop.getRootPane()
+                .putClientProperty("apple.awt.fullscreenable", true);
+      }
+      SwingUtilities.invokeLater(() -> {
+        FlatMacLightLaf.setup();
+      });
+      Console.debug("Using FlatMacLightLaf");
+      set = true;
+    }
+    if (!set)
+    {
+      try
+      {
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
+      }
+      // Windows specific properties here
+      SwingUtilities.invokeLater(() -> {
+        FlatLightLaf.setup();
+      });
+      Console.debug("Using FlatLightLaf");
+      set = true;
+    }
+    else if (SystemInfo.isLinux)
+    {
+      try
+      {
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
+      }
+      // enable custom window decorations
+      JFrame.setDefaultLookAndFeelDecorated(true);
+      JDialog.setDefaultLookAndFeelDecorated(true);
+      SwingUtilities.invokeLater(() -> {
+        FlatLightLaf.setup();
+      });
+      Console.debug("Using FlatLightLaf");
+      set = true;
+    }
+
+    if (!set)
+    {
+      try
+      {
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
+      }
+    }
+
     if (set)
     {
+      UIManager.put("TabbedPane.tabType", "card");
       UIManager.put("TabbedPane.showTabSeparators", true);
+      UIManager.put("TabbedPane.showContentSeparator", 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);
+    Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
+    return set;
   }
 
   private static boolean setMacLookAndFeel()
@@ -1057,12 +1232,28 @@ public class Jalview
     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().contains("quaqua"))
+            .toLowerCase(Locale.ROOT).contains("quaqua"))
     {
       set = setVaquaLookAndFeel();
     }
+     */
+    set = setFlatLookAndFeel();
+    return set;
+  }
+
+  private static boolean setLinuxLookAndFeel()
+  {
+    boolean set = false;
+    set = setFlatLookAndFeel();
+    if (!set)
+      set = setMetalLookAndFeel();
+    // avoid GtkLookAndFeel -- not good results especially on HiDPI
+    if (!set)
+      set = setNimbusLookAndFeel();
     return set;
   }
 
@@ -1106,7 +1297,7 @@ public class Jalview
                     + "-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"
                     + "-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 http://www.jalview.org for description of Features and Annotations file~\n\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)
@@ -1124,17 +1315,17 @@ public class Jalview
               @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);
@@ -1276,19 +1467,12 @@ public class Jalview
   }
 
   /**
-   * Quit method delegates to Desktop.quit - unless running in headless mode
-   * when it just ends the JVM
+   * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
    */
   public void quit()
   {
-    if (desktop != null)
-    {
-      desktop.quit();
-    }
-    else
-    {
-      System.exit(0);
-    }
+    // System.exit will run the shutdownHook first
+    System.exit(0);
   }
 
   public static AlignFrame getCurrentAlignFrame()