Merge branch 'develop' into releases/Release_2_11_2_Branch
authorJim Procter <j.procter@dundee.ac.uk>
Fri, 1 Jul 2022 13:12:45 +0000 (14:12 +0100)
committerJim Procter <j.procter@dundee.ac.uk>
Fri, 1 Jul 2022 13:12:45 +0000 (14:12 +0100)
14 files changed:
build.gradle
help/help/html/features/uniprotsequencefetcher.html
help/markdown/releases/release-2_11_2_3.md
help/markdown/whatsnew/whatsnew-2_11_2_3.md
j11lib/flatlaf-2.3.jar [new file with mode: 0644]
j11lib/flatlaf-extras-2.3.jar [new file with mode: 0644]
j8lib/flatlaf-2.3.jar [new file with mode: 0644]
j8lib/flatlaf-extras-2.3.jar [new file with mode: 0644]
src/jalview/bin/Jalview.java
src/jalview/fts/service/uniprot/UniProtFTSRestClient.java
src/jalview/gui/APQHandlers.java [deleted file]
src/jalview/gui/Desktop.java
src/jalview/jbgui/APQHandlers.java [new file with mode: 0644]
src/jalview/jbgui/GDesktop.java

index 51c7eb2..4f9798c 100644 (file)
@@ -48,7 +48,7 @@ plugins {
   id "com.diffplug.gradle.spotless" version "3.28.0"
   id 'com.github.johnrengelman.shadow' version '4.0.3'
   id 'com.install4j.gradle' version '9.0.6'
-  id 'com.dorongold.task-tree' version '1.5' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
+  id 'com.dorongold.task-tree' version '2.1.0' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
   id 'com.palantir.git-version' version '0.13.0' apply false
 }
 
@@ -1407,14 +1407,52 @@ def getMdSections(String content) {
   return sections
 }
 
+
+task copyHelp(type: Copy) {
+  def inputDir = helpSourceDir
+  def outputDir = "${helpBuildDir}/${help_dir}"
+  from(inputDir) {
+    include('**/*.txt')
+    include('**/*.md')
+    include('**/*.html')
+    include('**/*.hs')
+    include('**/*.xml')
+    include('**/*.jhm')
+    filter(ReplaceTokens,
+      beginToken: '$$',
+      endToken: '$$',
+      tokens: [
+        'Version-Rel': JALVIEW_VERSION,
+        'Year-Rel': getDate("yyyy")
+      ]
+    )
+  }
+  from(inputDir) {
+    exclude('**/*.txt')
+    exclude('**/*.md')
+    exclude('**/*.html')
+    exclude('**/*.hs')
+    exclude('**/*.xml')
+    exclude('**/*.jhm')
+  }
+  into outputDir
+
+  inputs.dir(inputDir)
+  outputs.files(helpFile)
+  outputs.dir(outputDir)
+}
+
+
 task releasesTemplates {
   group "help"
   description "Recreate whatsNew.html and releases.html from markdown files and templates in help"
 
+  dependsOn copyHelp
+
   def releasesTemplateFile = file("${jalviewDir}/${releases_template}")
   def whatsnewTemplateFile = file("${jalviewDir}/${whatsnew_template}")
-  def releasesHtmlFile = file("${helpSourceDir}/${releases_html}")
-  def whatsnewHtmlFile = file("${helpSourceDir}/${whatsnew_html}")
+  def releasesHtmlFile = file("${helpBuildDir}/${help_dir}/${releases_html}")
+  def whatsnewHtmlFile = file("${helpBuildDir}/${help_dir}/${whatsnew_html}")
   def releasesMdDir = "${jalviewDir}/${releases_dir}"
   def whatsnewMdDir = "${jalviewDir}/${whatsnew_dir}"
 
@@ -1478,16 +1516,16 @@ task releasesTemplates {
       }
       def displayDate = releaseFilesDates[rFile].format("dd/MM/yyyy")
 
-        def lm = null
-        def rContentProcessed = ""
-        rContent.eachLine { line ->
-          if (lm = line =~ /^(\s*-)(\s*<!--[^>]*?-->)(.*)$/) {
-            line = "${lm[0][1]}${lm[0][3]}${lm[0][2]}"
-        } else if (lm = line =~ /^###([^#]+.*)$/) {
-            line = "_${lm[0][1].trim()}_"
-          }
-          rContentProcessed += line + "\n"
+      def lm = null
+      def rContentProcessed = ""
+      rContent.eachLine { line ->
+        if (lm = line =~ /^(\s*-)(\s*<!--[^>]*?-->)(.*)$/) {
+          line = "${lm[0][1]}${lm[0][3]}${lm[0][2]}"
+      } else if (lm = line =~ /^###([^#]+.*)$/) {
+          line = "_${lm[0][1].trim()}_"
         }
+        rContentProcessed += line + "\n"
+      }
 
       def rContentSections = getMdSections(rContentProcessed)
       def rVersion = versionTemplate
@@ -1526,6 +1564,8 @@ task releasesTemplates {
           DISPLAY_DATE: wnDisplayDate
         ]
       )
+    } else if (gradle.taskGraph.hasTask(":linkCheck")) {
+      whatsnewHtmlFile.text = "Development build " + getDate("yyyy-MM-dd HH:mm:ss")
     }
 
   }
@@ -1539,43 +1579,6 @@ task releasesTemplates {
 }
 
 
-task copyHelp(type: Copy) {
-  dependsOn releasesTemplates
-
-  def inputDir = helpSourceDir
-  def outputDir = "${helpBuildDir}/${help_dir}"
-  from(inputDir) {
-    include('**/*.txt')
-    include('**/*.md')
-    include('**/*.html')
-    include('**/*.hs')
-    include('**/*.xml')
-    include('**/*.jhm')
-    filter(ReplaceTokens,
-      beginToken: '$$',
-      endToken: '$$',
-      tokens: [
-        'Version-Rel': JALVIEW_VERSION,
-        'Year-Rel': getDate("yyyy")
-      ]
-    )
-  }
-  from(inputDir) {
-    exclude('**/*.txt')
-    exclude('**/*.md')
-    exclude('**/*.html')
-    exclude('**/*.hs')
-    exclude('**/*.xml')
-    exclude('**/*.jhm')
-  }
-  into outputDir
-
-  inputs.dir(inputDir)
-  outputs.files(helpFile)
-  outputs.dir(outputDir)
-}
-
-
 task copyResources(type: Copy) {
   group = "build"
   description = "Copy (and make text substitutions in) the resources dir to the build area"
@@ -1673,6 +1676,7 @@ task prepare {
   dependsOn buildResources
   dependsOn copyDocs
   dependsOn copyHelp
+  dependsOn releasesTemplates
   dependsOn convertMdFiles
   dependsOn buildIndices
 }
@@ -1745,7 +1749,7 @@ task linkCheck(type: JavaExec) {
   def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
   classpath = files("${jalviewDir}/${utils_dir}")
   main = "HelpLinksChecker"
-  workingDir = jalviewDir
+  workingDir = "${helpBuildDir}"
   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
 
   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
index 72e7649..25d1a17 100644 (file)
   allows sequences to be located via gene name, keywords, or even
   <em>via</em> manual cross-referencing from UniProt or other
   bioinformatics websites.
+  <br/>
+  <br />
+  <strong>Please Note:</strong>Versions of Jalview older than 2.11.2.3 may need a configuration change
+  in order to access freetext search. Please see this post:
+  <a
+    href="https://discourse.jalview.org/t/uniprot-free-text-search-not-working-in-jalview-2-11-2-2-and-earlier/1825">https://discourse.jalview.org/t/uniprot-free-text-search-not-working-in-jalview-2-11-2-2-and-earlier/1825</a>
+  in Jalview's discussion forum for a workaround.
+  <br />
   <p>
     To open the UniProt Sequence Fetcher, select UniProt as the database
     from any <a href="seqfetch.html">Sequence Fetcher</a> dialog (opened
index 99cb736..c5bc048 100644 (file)
@@ -6,14 +6,18 @@ channel: "release"
 
 ## New Features
 
+
+- <!-- JAL-1988,JAL-3416 --> "Do you really want to Quit ?" prompt on OSX/Java 8
 - <!-- JAL-4004 --> Release notes and what's new documentation pages now generated from individual markdown files
 - <!-- JAL-3989 --> Release process generates Hugo friendly JSON metadata to use when publishing releases on www.jalview.org
 - <!-- JAL-3553 --> New gradle tasks for publishing to the Jalview version archive
 - <!-- JAL-4023 --> Tree branch labels shown using Scientific notation for very small or large lengths
+- <!-- JAL-4036 --> Uniprot Free Text Search now uses legacy.uniprot.org rather than main Uniprot query service
 
 
 ## Issues Resolved
 
+- <!-- JAL-4036 --> Uniprot Free Text Search in Jalview 2.11.2.2 and earlier stopped working on 29th June 2022
 - <!-- JAL-4008 --> Validation fails when trying to configure custom JABAWS server
 - <!-- JAL-4020 --> Jalview doesn't call PymolWIN.exe correctly - improved recognition of binaries on Windows
 - <!-- JAL-4024 --> Jumping from left to far right via rapid drag of scroll bar or clicking the overview window can cause Jalview to temporarily hang when working with alignments with more than 10 thousand columns
index cda0ff2..f911062 100644 (file)
@@ -1 +1 @@
-Jalview 2.11.2.3 is the third patch release in the 2.11.2 series. It addresses a critical bugs affecting users working with very wide alignments and those needing to use alternate JABAWS servers, support for display of very small and large tree lengths with scientific notation, and essential improvements to the build and release notes documentation system.
+Jalview 2.11.2.3 is the third patch release in the 2.11.2 series. It addresses a critical bugs affecting Uniprot free text search, users working with very wide alignments and those needing to use alternate JABAWS servers, support for display of very small and large tree lengths with scientific notation, and essential improvements to the build and release notes documentation system.
diff --git a/j11lib/flatlaf-2.3.jar b/j11lib/flatlaf-2.3.jar
new file mode 100644 (file)
index 0000000..9f292d2
Binary files /dev/null and b/j11lib/flatlaf-2.3.jar differ
diff --git a/j11lib/flatlaf-extras-2.3.jar b/j11lib/flatlaf-extras-2.3.jar
new file mode 100644 (file)
index 0000000..39e9701
Binary files /dev/null and b/j11lib/flatlaf-extras-2.3.jar differ
diff --git a/j8lib/flatlaf-2.3.jar b/j8lib/flatlaf-2.3.jar
new file mode 100644 (file)
index 0000000..9f292d2
Binary files /dev/null and b/j8lib/flatlaf-2.3.jar differ
diff --git a/j8lib/flatlaf-extras-2.3.jar b/j8lib/flatlaf-extras-2.3.jar
new file mode 100644 (file)
index 0000000..39e9701
Binary files /dev/null and b/j8lib/flatlaf-extras-2.3.jar differ
index fc4c821..fd1783a 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,9 +45,12 @@ import java.util.logging.ConsoleHandler;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+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;
@@ -862,8 +866,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");
@@ -915,6 +919,13 @@ public class Jalview
         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)
@@ -1035,6 +1046,45 @@ public class Jalview
             "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.desktop != null)
+        {
+          Desktop.desktop.getRootPane()
+                  .putClientProperty("apple.awt.fullWindowContent", true);
+          Desktop.desktop.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",
index 4fd12df..2606b62 100644 (file)
 
 package jalview.fts.service.uniprot;
 
-import jalview.bin.Cache;
-import jalview.fts.api.FTSData;
-import jalview.fts.api.FTSDataColumnI;
-import jalview.fts.api.FTSRestClientI;
-import jalview.fts.core.FTSRestClient;
-import jalview.fts.core.FTSRestRequest;
-import jalview.fts.core.FTSRestResponse;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -43,9 +33,20 @@ import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.WebResource;
 import com.sun.jersey.api.client.config.DefaultClientConfig;
 
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.fts.api.FTSData;
+import jalview.fts.api.FTSDataColumnI;
+import jalview.fts.api.FTSRestClientI;
+import jalview.fts.core.FTSRestClient;
+import jalview.fts.core.FTSRestRequest;
+import jalview.fts.core.FTSRestResponse;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
 public class UniProtFTSRestClient extends FTSRestClient
 {
-  private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org";
+  private static final String DEFAULT_UNIPROT_DOMAIN = "https://legacy.uniprot.org";
 
   static
   {
@@ -123,6 +124,10 @@ public class UniProtFTSRestClient extends FTSRestClient
               .queryParam("limit", String.valueOf(responseSize))
               .queryParam("offset", String.valueOf(offSet))
               .queryParam("sort", "score").queryParam("query", query);
+      if (Console.isDebugEnabled())
+      {
+        Console.debug("Uniprot FTS Request: " + webResource.toString());
+      }
       // Execute the REST request
       ClientResponse clientResponse = webResource
               .accept(MediaType.TEXT_PLAIN).get(clientResponseClass);
diff --git a/src/jalview/gui/APQHandlers.java b/src/jalview/gui/APQHandlers.java
deleted file mode 100644 (file)
index e5e328f..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.gui;
-
-import jalview.bin.Cache;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-
-import java.awt.Desktop;
-import java.awt.desktop.AboutEvent;
-import java.awt.desktop.AboutHandler;
-import java.awt.desktop.PreferencesEvent;
-import java.awt.desktop.PreferencesHandler;
-import java.awt.desktop.QuitEvent;
-import java.awt.desktop.QuitHandler;
-import java.awt.desktop.QuitResponse;
-import java.awt.desktop.QuitStrategy;
-
-import javax.swing.JOptionPane;
-
-public class APQHandlers
-{
-  private static boolean setAPQHandlers = false;
-
-  public APQHandlers()
-  {
-  }
-
-  protected static boolean setAPQHandlers(
-          jalview.gui.Desktop jalviewDesktop)
-  {
-    // flagging this test to avoid unnecessary reflection
-    if (!setAPQHandlers)
-    {
-      // see if the Quit, About and Preferences handlers are available
-      Class desktopClass = Desktop.class;
-      Desktop hdesktop = Desktop.getDesktop();
-
-      try
-      {
-        Float specversion = Float.parseFloat(
-                System.getProperty("java.specification.version"));
-
-        if (specversion >= 9)
-        {
-          if (Platform.isAMacAndNotJS())
-          {
-            if (desktopClass.getDeclaredMethod("setAboutHandler",
-                    new Class[]
-                    { AboutHandler.class }) != null)
-            {
-
-              hdesktop.setAboutHandler(new AboutHandler()
-              {
-                @Override
-                public void handleAbout(AboutEvent e)
-                {
-                  jalviewDesktop.aboutMenuItem_actionPerformed(null);
-                }
-              });
-
-            }
-
-            if (desktopClass.getDeclaredMethod("setPreferencesHandler",
-                    new Class[]
-                    { PreferencesHandler.class }) != null)
-            {
-
-              hdesktop.setPreferencesHandler(new PreferencesHandler()
-              {
-                @Override
-                public void handlePreferences(PreferencesEvent e)
-                {
-                  jalviewDesktop.preferences_actionPerformed(null);
-                }
-              });
-
-            }
-
-            if (desktopClass.getDeclaredMethod("setQuitHandler",
-                    new Class[]
-                    { QuitHandler.class }) != null)
-            {
-
-              hdesktop.setQuitHandler(new QuitHandler()
-              {
-                @Override
-                public void handleQuitRequestWith(QuitEvent e,
-                        QuitResponse r)
-                {
-                  boolean confirmQuit = Cache.getDefault(
-                          jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
-                  int n;
-                  if (confirmQuit)
-                  {
-                    n = JOptionPane.showConfirmDialog(null,
-                            MessageManager.getString("label.quit_jalview"),
-                            MessageManager.getString("action.quit"),
-                            JOptionPane.OK_CANCEL_OPTION,
-                            JOptionPane.PLAIN_MESSAGE, null);
-                  }
-                  else
-                  {
-                    n = JOptionPane.OK_OPTION;
-                  }
-                  if (n == JOptionPane.OK_OPTION)
-                  {
-                    System.out.println("Shortcut Quit confirmed by user");
-                    jalviewDesktop.quit();
-                    r.performQuit(); // probably won't reach this line, but just
-                                     // in
-                                     // case
-                  }
-                  else
-                  {
-                    r.cancelQuit();
-                    System.out.println("Shortcut Quit cancelled by user");
-                  }
-                }
-              });
-              hdesktop.setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);
-
-            }
-          }
-          setAPQHandlers = true;
-        }
-        else
-        {
-          System.out.println(
-                  "Not going to try setting APQ Handlers as java.spec.version is "
-                          + specversion);
-        }
-
-      } catch (Exception e)
-      {
-        System.out.println(
-                "Exception when looking for About, Preferences, Quit Handlers");
-        // e.printStackTrace();
-      } catch (Throwable t)
-      {
-        System.out.println(
-                "Throwable when looking for About, Preferences, Quit Handlers");
-        // t.printStackTrace();
-      }
-
-    }
-
-    return setAPQHandlers;
-  }
-
-}
index aeb0fac..3ad5384 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.gui;
 
-import java.util.Locale;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Dimension;
@@ -64,6 +62,7 @@ import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.Locale;
 import java.util.Vector;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -183,7 +182,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
 
-  protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
+  public static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
 
   public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
 
@@ -444,24 +443,6 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
     }
 
-    /**
-     * APQHandlers sets handlers for About, Preferences and Quit actions
-     * peculiar to macOS's application menu. APQHandlers will check to see if a
-     * handler is supported before setting it.
-     */
-    try
-    {
-      APQHandlers.setAPQHandlers(this);
-    } catch (Exception e)
-    {
-      System.out.println("Cannot set APQHandlers");
-      // e.printStackTrace();
-    } catch (Throwable t)
-    {
-      jalview.bin.Console
-              .warn("Error setting APQHandlers: " + t.toString());
-      jalview.bin.Console.trace(Cache.getStackTraceString(t));
-    }
     setIconImages(ChannelProperties.getIconList());
 
     addWindowListener(new WindowAdapter()
diff --git a/src/jalview/jbgui/APQHandlers.java b/src/jalview/jbgui/APQHandlers.java
new file mode 100644 (file)
index 0000000..1a7e971
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.jbgui;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+import com.formdev.flatlaf.extras.FlatDesktop;
+import com.formdev.flatlaf.extras.FlatDesktop.Action;
+
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
+public class APQHandlers
+{
+  public static boolean setAbout = false;
+
+  public static boolean setPreferences = false;
+
+  public static boolean setQuit = false;
+
+  public static boolean setAPQHandlers(GDesktop desktop)
+  {
+    if (Platform.isJS())
+    {
+      return false;
+    }
+    if (FlatDesktop.isSupported(Action.APP_ABOUT))
+    {
+      FlatDesktop.setAboutHandler(() -> {
+        desktop.aboutMenuItem_actionPerformed(null);
+      });
+      setAbout = true;
+    }
+    if (FlatDesktop.isSupported(Action.APP_PREFERENCES))
+    {
+      FlatDesktop.setPreferencesHandler(() -> {
+        desktop.preferences_actionPerformed(null);
+      });
+      setPreferences = true;
+    }
+    if (FlatDesktop.isSupported(Action.APP_QUIT_HANDLER))
+    {
+      FlatDesktop.setQuitHandler(response -> {
+        boolean confirmQuit = jalview.bin.Cache.getDefault(
+                jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
+        boolean canQuit = !confirmQuit;
+        int n;
+        if (confirmQuit)
+        {
+          // ensure Jalview window is brought to front for Quit confirmation
+          // window to be visible
+
+          // this method of raising the Jalview window is broken in java
+          // jalviewDesktop.setVisible(true);
+          // jalviewDesktop.toFront();
+
+          // a better hack which works instead
+          JFrame dialogParent = new JFrame();
+          dialogParent.setAlwaysOnTop(true);
+
+          n = JOptionPane.showConfirmDialog(dialogParent,
+                  MessageManager.getString("label.quit_jalview"),
+                  MessageManager.getString("action.quit"),
+                  JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE,
+                  null);
+
+          dialogParent.setAlwaysOnTop(false);
+          dialogParent.dispose();
+        }
+        else
+        {
+          n = JOptionPane.OK_OPTION;
+        }
+        canQuit = (n == JOptionPane.OK_OPTION);
+        if (canQuit)
+        {
+          response.performQuit();
+        }
+        else
+        {
+          response.cancelQuit();
+        }
+      });
+      setQuit = true;
+    }
+    // if we got to here, no exceptions occurred when we set the handlers.
+    return setAbout || setPreferences || setQuit;
+  }
+
+}
index e1224c2..ca95222 100755 (executable)
  */
 package jalview.jbgui;
 
-import jalview.api.AlignmentViewPanel;
-import jalview.io.FileFormatException;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -35,6 +30,12 @@ import javax.swing.JMenu;
 import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.io.FileFormatException;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
 /**
  * DOCUMENT ME!
  * 
@@ -139,6 +140,26 @@ public class GDesktop extends JFrame
    */
   private void jbInit() throws Exception
   {
+    boolean apqHandlersSet = false;
+    /**
+     * APQHandlers sets handlers for About, Preferences and Quit actions
+     * peculiar to macOS's application menu. APQHandlers will check to see if a
+     * handler is supported before setting it.
+     */
+    try
+    {
+      apqHandlersSet = APQHandlers.setAPQHandlers(this);
+    } catch (Exception e)
+    {
+      System.out.println("Cannot set APQHandlers");
+      // e.printStackTrace();
+    } catch (Throwable t)
+    {
+      jalview.bin.Console
+              .warn("Error setting APQHandlers: " + t.toString());
+      jalview.bin.Console.trace(Cache.getStackTraceString(t));
+    }
+
     setName("jalview-desktop");
     FileMenu.setText(MessageManager.getString("action.file"));
     HelpMenu.setText(MessageManager.getString("action.help"));
@@ -369,10 +390,6 @@ public class GDesktop extends JFrame
       }
     });
 
-    Float specversion = Platform.isJS() ? Float.valueOf(8)
-            : Float.parseFloat(
-                    System.getProperty("java.specification.version"));
-
     desktopMenubar.add(FileMenu);
     desktopMenubar.add(toolsMenu);
     desktopMenubar.add(HelpMenu);
@@ -384,10 +401,16 @@ public class GDesktop extends JFrame
     FileMenu.add(saveAsState);
     FileMenu.add(loadState);
     FileMenu.addSeparator();
-    FileMenu.add(quit);
-    HelpMenu.add(aboutMenuItem);
+    if (!APQHandlers.setQuit)
+    {
+      FileMenu.add(quit);
+    }
+    if (!APQHandlers.setAbout)
+    {
+      HelpMenu.add(aboutMenuItem);
+    }
     HelpMenu.add(documentationMenuItem);
-    if (!Platform.isAMacAndNotJS() || specversion < 11)
+    if (!APQHandlers.setPreferences)
     {
       toolsMenu.add(preferences);
     }