JAL-1988 Make sensible choices if only the opening file has been opened, and alter...
[jalview.git] / src / jalview / bin / Jalview.java
index 357f0e4..cb078b8 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.bin;
 
+import java.awt.Color;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -44,17 +45,28 @@ 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;
@@ -264,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
@@ -272,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)
     {
@@ -292,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(
@@ -373,7 +411,9 @@ public class Jalview
     try
     {
       Console.initLogger();
-    } catch (NoClassDefFoundError error)
+    } catch (
+
+    NoClassDefFoundError error)
     {
       error.printStackTrace();
       System.out.println("\nEssential logging libraries not found."
@@ -428,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();
@@ -482,13 +551,23 @@ public class Jalview
         }
 
         if (!aparser.contains("nohtmltemplates")
-              || Cache.getProperty("NOHTMLTEMPLATES") == null)
+                || 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");
@@ -509,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
@@ -524,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)
@@ -783,6 +866,7 @@ public class Jalview
         }
       }
     }
+
     AlignFrame startUpAlframe = null;
     // We'll only open the default file if the desktop is visible.
     // And the user
@@ -830,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.
     }
 
@@ -861,8 +949,8 @@ public class Jalview
 
   private static void setLookAndFeel()
   {
-    // property laf = "crossplatform", "system", "gtk", "metal", "nimbus" or
-    // "mac"
+    // 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");
@@ -914,15 +1002,8 @@ public class Jalview
         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();
+    case "flat":
+      lafSet = setFlatLookAndFeel();
       if (!lafSet)
       {
         Console.error("Could not set requested laf=" + laf);
@@ -945,7 +1026,7 @@ public class Jalview
       setSystemLookAndFeel();
       if (Platform.isLinux())
       {
-        setMetalLookAndFeel();
+        setLinuxLookAndFeel();
       }
       if (Platform.isMac())
       {
@@ -1034,18 +1115,115 @@ public class Jalview
             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
   }
 
-  private static boolean setQuaquaLookAndFeel()
+  private static boolean setFlatLookAndFeel()
   {
-    return setSpecificLookAndFeel("quaqua",
-            ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel().getClass()
-                    .getName(),
-            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;
+    }
 
-  private static boolean setVaquaLookAndFeel()
-  {
-    return setSpecificLookAndFeel("vaqua",
-            "org.violetlib.aqua.AquaLookAndFeel", false);
+    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);
+    }
+
+    Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
+    return set;
   }
 
   private static boolean setMacLookAndFeel()
@@ -1054,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(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;
   }
 
@@ -1121,7 +1315,8 @@ public class Jalview
               @Override
               public void run()
               {
-                Console.debug("Initialising googletracker for usage stats.");
+                Console.debug(
+                        "Initialising googletracker for usage stats.");
                 Cache.initGoogleTracker();
                 Console.debug("Tracking enabled.");
               }
@@ -1272,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()