Merge branch 'task/JAL-3597memoryLeakTest' into develop
authorJim Procter <jprocter@issues.jalview.org>
Mon, 17 Aug 2020 09:55:48 +0000 (10:55 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Mon, 17 Aug 2020 09:55:48 +0000 (10:55 +0100)
31 files changed:
build.gradle
help/help/html/features/clarguments.html
help/help/html/memory.html
j11lib/htsjdk-2.12.0.jar [deleted file]
j11lib/htsjdk-2.23.0.jar [new file with mode: 0644]
j8lib/htsjdk-2.12.0.jar [deleted file]
j8lib/htsjdk-2.23.0.jar [new file with mode: 0644]
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationRowFilter.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FeatureTypeSettings.java
src/jalview/gui/OptsAndParamsPage.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/Slider.java [new file with mode: 0644]
src/jalview/io/FileParse.java
src/jalview/io/vcf/VCFLoader.java
src/jalview/util/Platform.java
src/jalview/ws/dbsources/Pfam.java
src/jalview/ws/dbsources/PfamFull.java
src/jalview/ws/dbsources/PfamSeed.java
src/jalview/ws/dbsources/Rfam.java
src/jalview/ws/dbsources/RfamFull.java
src/jalview/ws/dbsources/RfamSeed.java
src/jalview/ws/dbsources/Xfam.java
test/jalview/ws/dbsources/PfamFullTest.java
test/jalview/ws/dbsources/PfamSeedTest.java
test/jalview/ws/dbsources/RfamFullTest.java
test/jalview/ws/dbsources/RfamSeedTest.java

index 0f4ad16..bfd7a8f 100644 (file)
@@ -1220,6 +1220,11 @@ test {
 
   workingDir = jalviewDir
   //systemProperties 'clover.jar' System.properties.clover.jar
+  def testLaf = project.findProperty("test_laf")
+  if (testLaf != null) {
+    println("Setting Test LaF to '${testLaf}'")
+    systemProperty "laf", testLaf
+  }
   sourceCompatibility = compile_source_compatibility
   targetCompatibility = compile_target_compatibility
   jvmArgs += additional_compiler_args
index 60db069..0d800cf 100644 (file)
           </div>
       </td>
     </tr>
+    <tr>
+      <td><div align="center">-jvmmempc=PERCENT</div></td>
+      <td><div align="left"><em>Only available with standalone executable jar or jalview.bin.Launcher.</em>
+          Limit maximum heap size (memory) to PERCENT% of total physical memory detected.
+         This defaults to 90 if total physical memory can be detected.
+         See <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
+          </div>
+      </td>
+    </tr>
+    <tr>
+      <td><div align="center">-jvmmemmax=MAXMEMORY</div></td>
+      <td><div align="left"><em>Only available with standalone executable jar or jalview.bin.Launcher.</em>
+          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 <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
+          </div>
+      </td>
+    </tr>
   </table>
 </body>
 </html>
index 81190a9..0374dcb 100755 (executable)
     <li><em><font size="3">Maximum memory limit</em><br/>
       Since 2.11.1.0, Jalview's configuration includes a 'maximum memory limit':
       <pre>jalview.jvmmemmax = 32G</pre>
-      Adjusting this default (via a JVL file, above) will allow larger amounts of memory to be allocated to Jalview in connjunction with the jalview.jvmmempc setting. 
+      Adjusting this default (via a JVL file, above) will allow larger amounts (or can limit the amount) of memory to be allocated to Jalview in conjunction with the jalview.jvmmempc setting. 
+      <br/><br/>
+    </li>
+    <li><em><font size="3"><a name="jar">Command line arguments when using the executable jar (jalview-all.jar) or jalview.bin.Launcher</a></em><br/>
+      If you are using the Jalview standalone executable jar (usually named <em>jalview-all-....jar</em> with a Jalview and Java version designation) or using <em>jalview.bin.Launcher</em> to start Jalview,
+      then you can set the <em>jvmmempc</em> and <em>jvmmemmax</em> values using application command line arguments <em>-jvmmempc=PERCENT</em>
+      and <em>-jvmmemmax=MAXMEMORY</em> respectively.  <em>PERCENT</em> should be an integer between 1 and 100, and MAXMEMORY should be an amount of memory in bytes, or you can append a "k", "m", "g", or "t" to use units of kilobytes, megabytes, gigabytes or terabytes, e.g.
+      <pre>java -jar jalview-all-2.11.1.0-j1.8.jar -jvmmempc=50 -jvmmemmax=20g</pre>
+      (this example will launch Jalview with a maximum heap size of the smaller of 20GB or 50% of physical memory detected).
+      <br/>The default value for jvmmempc is 90, whilst the default value for jvmmemmax is 32g if Jalview can determine a total physical memory size of the host system, and a more cautious 8g if Jalview is unable to determine a total physical memory size.
+      <br/><br/>
     </li>
     <li><em><font size="3"><a name="jvm"/>Directly opening Jalview
           with a JVM</font></em> <br /> Launching Jalview directly with a JVM is
diff --git a/j11lib/htsjdk-2.12.0.jar b/j11lib/htsjdk-2.12.0.jar
deleted file mode 100644 (file)
index 1df12b2..0000000
Binary files a/j11lib/htsjdk-2.12.0.jar and /dev/null differ
diff --git a/j11lib/htsjdk-2.23.0.jar b/j11lib/htsjdk-2.23.0.jar
new file mode 100644 (file)
index 0000000..bcf201f
Binary files /dev/null and b/j11lib/htsjdk-2.23.0.jar differ
diff --git a/j8lib/htsjdk-2.12.0.jar b/j8lib/htsjdk-2.12.0.jar
deleted file mode 100644 (file)
index 1df12b2..0000000
Binary files a/j8lib/htsjdk-2.12.0.jar and /dev/null differ
diff --git a/j8lib/htsjdk-2.23.0.jar b/j8lib/htsjdk-2.23.0.jar
new file mode 100644 (file)
index 0000000..bcf201f
Binary files /dev/null and b/j8lib/htsjdk-2.23.0.jar differ
index 85d23df..ff475b6 100755 (executable)
@@ -48,6 +48,10 @@ import java.util.Locale;
 import java.util.Properties;
 import java.util.StringTokenizer;
 import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import javax.swing.LookAndFeel;
+import javax.swing.UIManager;
 
 import org.apache.log4j.ConsoleAppender;
 import org.apache.log4j.Level;
@@ -1158,7 +1162,12 @@ public class Cache
             System.getProperty("installer_template_version"), "\n", null);
     appendIfNotNull(sb, "Launcher version: ",
             System.getProperty("launcher_version"), "\n", null);
-    if (jalview.bin.Cache.getDefault("VERSION", "TEST").equals("DEVELOPMENT")) {
+    LookAndFeel laf = UIManager.getLookAndFeel();
+    String lafName = laf == null?"Not obtained":laf.getName();
+    String lafClass = laf == null?"unknown":laf.getClass().getName();
+    appendIfNotNull(sb, "LookAndFeel: ", lafName+" ("+lafClass+")", "\n", null);
+    // Not displayed in release version ( determined by possible version number regex 9[9.]*9[.-_a9]* )
+    if (Pattern.matches("^\\d[\\d\\.]*\\d[\\.\\-\\w]*$", jalview.bin.Cache.getDefault("VERSION", "TEST"))) {
       appendIfNotNull(sb, "Getdown appdir: ",
               System.getProperty("getdownappdir"), "\n", null);
       appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
index cdd8cc1..f5b7009 100755 (executable)
  */
 package jalview.bin;
 
-import jalview.ext.so.SequenceOntology;
-import jalview.gui.AlignFrame;
-import jalview.gui.Desktop;
-import jalview.gui.PromptUserConfig;
-import jalview.io.AppletFormatAdapter;
-import jalview.io.BioJsHTMLOutput;
-import jalview.io.DataSourceType;
-import jalview.io.FileFormat;
-import jalview.io.FileFormatException;
-import jalview.io.FileFormatI;
-import jalview.io.FileLoader;
-import jalview.io.HtmlSvgOutput;
-import jalview.io.IdentifyFile;
-import jalview.io.NewickFile;
-import jalview.io.gff.SequenceOntologyFactory;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ColourSchemeProperty;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.ws.jws2.Jws2Discoverer;
-
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -64,13 +43,33 @@ import java.util.logging.ConsoleHandler;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.swing.LookAndFeel;
 import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
 
 import com.threerings.getdown.util.LaunchUtil;
 
 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.io.AppletFormatAdapter;
+import jalview.io.BioJsHTMLOutput;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
+import jalview.io.FileFormatException;
+import jalview.io.FileFormatI;
+import jalview.io.FileLoader;
+import jalview.io.HtmlSvgOutput;
+import jalview.io.IdentifyFile;
+import jalview.io.NewickFile;
+import jalview.io.gff.SequenceOntologyFactory;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeProperty;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.ws.jws2.Jws2Discoverer;
 
 /**
  * Main class for Jalview Application <br>
@@ -112,21 +111,21 @@ public class Jalview
      */
     {
       // grab all the rights we can for the JVM
-           Policy.setPolicy(new Policy()
-           {
-             @Override
-             public PermissionCollection getPermissions(CodeSource codesource)
-             {
-               Permissions perms = new Permissions();
-               perms.add(new AllPermission());
-               return (perms);
-             }
-       
-             @Override
-             public void refresh()
-             {
-             }
-           });
+      Policy.setPolicy(new Policy()
+      {
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource)
+        {
+          Permissions perms = new Permissions();
+          perms.add(new AllPermission());
+          return (perms);
+        }
+
+        @Override
+        public void refresh()
+        {
+        }
+      });
     }
   }
 
@@ -198,27 +197,27 @@ public class Jalview
    * main class for Jalview application
    * 
    * @param args
-   *               open <em>filename</em>
+   *          open <em>filename</em>
    */
   public static void main(String[] args)
   {
-//     setLogging(); // BH - for event debugging in JavaScript
+    // setLogging(); // BH - for event debugging in JavaScript
     instance = new Jalview();
     instance.doMain(args);
-}
+  }
 
-  private static void logClass(String name) 
-  {    
-         // BH - for event debugging in JavaScript
-      ConsoleHandler consoleHandler = new ConsoleHandler();
-      consoleHandler.setLevel(Level.ALL);
-      Logger logger = Logger.getLogger(name);
-      logger.setLevel(Level.ALL);
-      logger.addHandler(consoleHandler);
+  private static void logClass(String name)
+  {
+    // BH - for event debugging in JavaScript
+    ConsoleHandler consoleHandler = new ConsoleHandler();
+    consoleHandler.setLevel(Level.ALL);
+    Logger logger = Logger.getLogger(name);
+    logger.setLevel(Level.ALL);
+    logger.addHandler(consoleHandler);
   }
 
   @SuppressWarnings("unused")
-  private static void setLogging() 
+  private static void setLogging()
   {
 
     /**
@@ -229,26 +228,23 @@ public class Jalview
       System.out.println("not in js");
     }
 
-         // BH - for event debugging in JavaScript (Java mode only)
+    // BH - for event debugging in JavaScript (Java mode only)
     if (!Platform.isJS())
     /**
      * Java only
      * 
      * @j2sIgnore
      */
-       {
-               Logger.getLogger("").setLevel(Level.ALL);
-        logClass("java.awt.EventDispatchThread");
-        logClass("java.awt.EventQueue");
-        logClass("java.awt.Component");
-        logClass("java.awt.focus.Component");
-        logClass("java.awt.focus.DefaultKeyboardFocusManager"); 
-       }       
+    {
+      Logger.getLogger("").setLevel(Level.ALL);
+      logClass("java.awt.EventDispatchThread");
+      logClass("java.awt.EventQueue");
+      logClass("java.awt.Component");
+      logClass("java.awt.focus.Component");
+      logClass("java.awt.focus.DefaultKeyboardFocusManager");
+    }
 
   }
-  
-
-  
 
   /**
    * @param args
@@ -262,22 +258,24 @@ public class Jalview
     }
 
     System.out
-            .println("Java version: "
-                    + System.getProperty("java.version"));
+            .println("Java version: " + System.getProperty("java.version"));
     System.out.println("Java Home: " + System.getProperty("java.home"));
     System.out.println(System.getProperty("os.arch") + " "
             + System.getProperty("os.name") + " "
             + System.getProperty("os.version"));
     String val = System.getProperty("sys.install4jVersion");
-    if (val != null) {
-    System.out.println("Install4j version: " + val);
+    if (val != null)
+    {
+      System.out.println("Install4j version: " + val);
     }
     val = System.getProperty("installer_template_version");
-    if (val != null) {
+    if (val != null)
+    {
       System.out.println("Install4j template version: " + val);
     }
     val = System.getProperty("launcher_version");
-    if (val != null) {
+    if (val != null)
+    {
       System.out.println("Launcher version: " + val);
     }
 
@@ -344,7 +342,7 @@ public class Jalview
         System.out.println("Executing setprop argument: " + defs);
         if (Platform.isJS())
         {
-          Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
+          Cache.setProperty(defs.substring(0, p), defs.substring(p + 1));
         }
       }
       defs = aparser.getValue("setprop");
@@ -369,49 +367,89 @@ public class Jalview
 
     desktop = null;
 
-    try
+    // property laf = "crossplatform", "system", "gtk", "metal" or "mac"
+    // 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)
     {
-      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-    } catch (Exception ex)
+      laf = lafProp;
+    }
+    else if (lafSetting != null)
     {
-      System.err.println("Unexpected Look and Feel Exception");
-      ex.printStackTrace();
+      laf = lafSetting;
     }
-    if (Platform.isAMacAndNotJS())
+    boolean lafSet = false;
+    switch (laf)
     {
-
-      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)
+    case "crossplatform":
+      lafSet = setCrossPlatformLookAndFeel();
+      if (!lafSet)
       {
-        try
-        {
-          UIManager.setLookAndFeel(lookAndFeel);
-        } catch (Throwable e)
-        {
-          System.err.println(
-                  "Failed to set QuaQua look and feel: " + e.toString());
-        }
+        System.err.println("Could not set requested laf=" + laf);
       }
-      if (lookAndFeel == null
-              || !(lookAndFeel.getClass().isAssignableFrom(
-                      UIManager.getLookAndFeel().getClass()))
-              || !UIManager.getLookAndFeel().getClass().toString()
-                      .toLowerCase().contains("quaqua"))
+      break;
+    case "system":
+      lafSet = setSystemLookAndFeel();
+      if (!lafSet)
       {
-        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());
-        }
+        System.err.println("Could not set requested laf=" + laf);
+      }
+      break;
+    case "gtk":
+      lafSet = setGtkLookAndFeel();
+    {
+      System.err.println("Could not set requested laf=" + laf);
+    }
+      break;
+    case "metal":
+      lafSet = setMetalLookAndFeel();
+    {
+      System.err.println("Could not set requested laf=" + laf);
+    }
+      break;
+    case "nimbus":
+      lafSet = setNimbusLookAndFeel();
+    {
+      System.err.println("Could not set requested laf=" + laf);
+    }
+      break;
+    case "quaqua":
+      lafSet = setQuaquaLookAndFeel();
+    {
+      System.err.println("Could not set requested laf=" + laf);
+    }
+      break;
+    case "vaqua":
+      lafSet = setVaquaLookAndFeel();
+    {
+      System.err.println("Could not set requested laf=" + laf);
+    }
+      break;
+    case "mac":
+      lafSet = setMacLookAndFeel();
+      if (!lafSet)
+      {
+        System.err.println("Could not set requested laf=" + laf);
+      }
+      break;
+    case "none":
+      break;
+    default:
+      System.err.println("Requested laf=" + laf + " not implemented");
+    }
+    if (!lafSet)
+    {
+      setSystemLookAndFeel();
+      if (Platform.isLinux() && !Platform.isJS())
+      {
+        setMetalLookAndFeel();
+      }
+      if (Platform.isAMacAndNotJS())
+      {
+        setMacLookAndFeel();
       }
     }
 
@@ -549,11 +587,11 @@ public class Jalview
       System.out.println("CMD [-open " + file + "] executed successfully!");
 
       if (!Platform.isJS())
-        /**
-         * ignore in JavaScript -- can't just file existence - could load it?
-         * 
-         * @j2sIgnore
-         */
+      /**
+       * ignore in JavaScript -- can't just file existence - could load it?
+       * 
+       * @j2sIgnore
+       */
       {
         if (!file.startsWith("http://") && !file.startsWith("https://"))
         // BH 2019 added https check for Java
@@ -569,7 +607,7 @@ public class Jalview
         }
       }
 
-        protocol = AppletFormatAdapter.checkProtocol(file);
+      protocol = AppletFormatAdapter.checkProtocol(file);
 
       try
       {
@@ -789,8 +827,7 @@ public class Jalview
      */
     {
       file = Cache.getDefault("STARTUP_FILE",
-              Cache.getDefault("www.jalview.org",
-                      "http://www.jalview.org")
+              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"))
@@ -854,6 +891,107 @@ public class Jalview
     }
   }
 
+  private static boolean setCrossPlatformLookAndFeel()
+  {
+    return setGenericLookAndFeel(false);
+  }
+
+  private static boolean setSystemLookAndFeel()
+  {
+    return setGenericLookAndFeel(true);
+  }
+
+  private static boolean setGenericLookAndFeel(boolean system)
+  {
+    boolean set = false;
+    try
+    {
+      UIManager.setLookAndFeel(
+              system ? UIManager.getSystemLookAndFeelClassName()
+                      : UIManager.getCrossPlatformLookAndFeelClassName());
+      set = true;
+    } catch (Exception ex)
+    {
+      System.err.println("Unexpected Look and Feel Exception");
+      ex.printStackTrace();
+    }
+    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()
+                        .startsWith(name.toLowerCase())
+                : info.getName().toLowerCase().equals(name.toLowerCase()))
+        {
+          className = info.getClassName();
+          break;
+        }
+      }
+      UIManager.setLookAndFeel(className);
+      set = true;
+    } catch (Exception ex)
+    {
+      System.err.println("Unexpected Look and Feel Exception");
+      ex.printStackTrace();
+    }
+    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 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",
+            "Jalview");
+    System.setProperty("apple.laf.useScreenMenuBar", "true");
+    set = setQuaquaLookAndFeel();
+    if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
+            .toLowerCase().contains("quaqua"))
+    {
+      set = setVaquaLookAndFeel();
+    }
+    return set;
+  }
+
   private static void showUsage()
   {
     System.out.println(
@@ -892,6 +1030,8 @@ public class Jalview
                     + "-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"
+                    + "-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");
   }
 
@@ -930,10 +1070,10 @@ public class Jalview
    * Locate the given string as a file and pass it to the groovy interpreter.
    * 
    * @param groovyscript
-   *                         the script to execute
+   *          the script to execute
    * @param jalviewContext
-   *                         the Jalview Desktop object passed in to the groovy
-   *                         binding as the 'Jalview' object.
+   *          the Jalview Desktop object passed in to the groovy binding as the
+   *          'Jalview' object.
    */
   private void executeGroovyScript(String groovyscript, AlignFrame af)
   {
@@ -1062,8 +1202,8 @@ public class Jalview
   }
 
   /**
-   * 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()
   {
index e89c1c2..ad013f5 100644 (file)
@@ -53,8 +53,6 @@ import net.miginfocom.swing.MigLayout;
 @SuppressWarnings("serial")
 public class AnnotationColourChooser extends AnnotationRowFilter
 {
-  private static final int ONETHOUSAND = 1000;
-
   private ColourSchemeI oldcs;
 
   private JButton defColours;
@@ -147,7 +145,8 @@ public class AnnotationColourChooser extends AnnotationRowFilter
                 "error.implementation_error_dont_know_about_threshold_setting"));
       }
       thresholdIsMin.setSelected(acg.isThresholdIsMinMax());
-      thresholdValue.setText("" + acg.getAnnotationThreshold());
+      thresholdValue
+              .setText(String.valueOf(acg.getAnnotationThreshold()));
     }
 
     jbInit();
@@ -329,7 +328,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       {
         updateView();
       }
-      getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
+      getCurrentAnnotation().threshold.value = getSliderValue();
       propagateSeqAssociatedThreshold(updateAllAnnotation,
               getCurrentAnnotation());
       ap.paintAlignment(false, false);
@@ -369,6 +368,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
     thresholdValue.setEnabled(true);
     thresholdIsMin.setEnabled(!useOriginalColours.isSelected());
 
+    final AlignmentAnnotation currentAnnotation = getCurrentAnnotation();
     if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
     {
       slider.setEnabled(false);
@@ -377,33 +377,26 @@ public class AnnotationColourChooser extends AnnotationRowFilter
       thresholdIsMin.setEnabled(false);
     }
     else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD
-            && getCurrentAnnotation().threshold == null)
+            && currentAnnotation.threshold == null)
     {
-      getCurrentAnnotation().setThreshold(new GraphLine(
-              (getCurrentAnnotation().graphMax
-                      - getCurrentAnnotation().graphMin) / 2f,
+      currentAnnotation.setThreshold(new GraphLine(
+              (currentAnnotation.graphMax - currentAnnotation.graphMin)
+                      / 2f,
               "Threshold", Color.black));
     }
 
     if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
     {
       adjusting = true;
-      float range = getCurrentAnnotation().graphMax * ONETHOUSAND
-              - getCurrentAnnotation().graphMin * ONETHOUSAND;
-
-      slider.setMinimum(
-              (int) (getCurrentAnnotation().graphMin * ONETHOUSAND));
-      slider.setMaximum(
-              (int) (getCurrentAnnotation().graphMax * ONETHOUSAND));
-      slider.setValue(
-              (int) (getCurrentAnnotation().threshold.value * ONETHOUSAND));
-      thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
-      slider.setMajorTickSpacing((int) (range / 10f));
+      setSliderModel(currentAnnotation.graphMin, currentAnnotation.graphMax,
+              currentAnnotation.threshold.value);
       slider.setEnabled(true);
+
+      setThresholdValueText();
       thresholdValue.setEnabled(true);
       adjusting = false;
     }
-    colorAlignmentContaining(getCurrentAnnotation(), selectedThresholdItem);
+    colorAlignmentContaining(currentAnnotation, selectedThresholdItem);
 
     ap.alignmentChanged();
   }
index fbc93b5..589f4bd 100644 (file)
@@ -21,6 +21,7 @@
 
 package jalview.gui;
 
+import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.HiddenColumns;
 import jalview.io.cache.JvCacheableInputBox;
 import jalview.schemes.AnnotationColourGradient;
@@ -255,7 +256,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
   {
     if (slider.isEnabled())
     {
-      getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
+      getCurrentAnnotation().threshold.value = getSliderValue();
       updateView();
       propagateSeqAssociatedThreshold(updateAllAnnotation,
               getCurrentAnnotation());
@@ -285,6 +286,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
     thresholdValue.setEnabled(true);
     percentThreshold.setEnabled(true);
 
+    final AlignmentAnnotation currentAnnotation = getCurrentAnnotation();
     if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
     {
       slider.setEnabled(false);
@@ -295,26 +297,22 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
     }
     else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
     {
-      if (getCurrentAnnotation().threshold == null)
+      if (currentAnnotation.threshold == null)
       {
-        getCurrentAnnotation().setThreshold(new jalview.datamodel.GraphLine(
-                (getCurrentAnnotation().graphMax
-                        - getCurrentAnnotation().graphMin) / 2f,
+        currentAnnotation.setThreshold(new jalview.datamodel.GraphLine(
+                (currentAnnotation.graphMax
+                        - currentAnnotation.graphMin) / 2f,
                 "Threshold", Color.black));
       }
 
       adjusting = true;
-      float range = getCurrentAnnotation().graphMax * 1000
-              - getCurrentAnnotation().graphMin * 1000;
 
-      slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
-      slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
-      slider.setValue(
-              (int) (getCurrentAnnotation().threshold.value * 1000));
+      setSliderModel(currentAnnotation.graphMin,
+              currentAnnotation.graphMax,
+              currentAnnotation.threshold.value);
 
       setThresholdValueText();
 
-      slider.setMajorTickSpacing((int) (range / 10f));
       slider.setEnabled(true);
       thresholdValue.setEnabled(true);
       adjusting = false;
@@ -322,10 +320,10 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
       // build filter params
       filterParams.setThresholdType(
               AnnotationFilterParameter.ThresholdType.NO_THRESHOLD);
-      if (getCurrentAnnotation().isQuantitative())
+      if (currentAnnotation.isQuantitative())
       {
         filterParams
-                .setThresholdValue(getCurrentAnnotation().threshold.value);
+                .setThresholdValue(currentAnnotation.threshold.value);
 
         if (selectedThresholdItem == AnnotationColourGradient.ABOVE_THRESHOLD)
         {
@@ -381,7 +379,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
     // adding them to the selection
     av.showAllHiddenColumns();
     av.getColumnSelection().filterAnnotations(
-            getCurrentAnnotation().annotations, filterParams);
+            currentAnnotation.annotations, filterParams);
 
     boolean hideCols = getActionOption() == ACTION_OPTION_HIDE;
     if (hideCols)
index f13cb10..6f72e10 100644 (file)
@@ -35,6 +35,8 @@ import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.math.BigDecimal;
+import java.math.MathContext;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Vector;
@@ -44,7 +46,6 @@ import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JInternalFrame;
 import javax.swing.JPanel;
-import javax.swing.JSlider;
 import javax.swing.JTextField;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
@@ -52,6 +53,10 @@ import javax.swing.event.ChangeListener;
 @SuppressWarnings("serial")
 public abstract class AnnotationRowFilter extends JPanel
 {
+  private static final String TWO_DP = "%.2f";
+
+  private final static MathContext FOUR_SIG_FIG = new MathContext(4);
+
   protected AlignViewport av;
 
   protected AlignmentPanel ap;
@@ -64,7 +69,7 @@ public abstract class AnnotationRowFilter extends JPanel
 
   protected JCheckBox percentThreshold = new JCheckBox();
 
-  protected JSlider slider = new JSlider();
+  protected Slider slider;
 
   protected JTextField thresholdValue = new JTextField(20);
 
@@ -103,6 +108,8 @@ public abstract class AnnotationRowFilter extends JPanel
   {
     this.av = viewport;
     this.ap = alignPanel;
+    this.slider = new Slider(0f, 100f, 50f);
+
     thresholdValue.addFocusListener(new FocusAdapter()
     {
       @Override
@@ -140,16 +147,62 @@ public abstract class AnnotationRowFilter extends JPanel
     adjusting = true;
     if (percentThreshold.isSelected())
     {
-      thresholdValue.setText("" + (slider.getValue() - slider.getMinimum())
-              * 100f / (slider.getMaximum() - slider.getMinimum()));
+      thresholdValue
+              .setText(String.format(TWO_DP, getSliderPercentageValue()));
     }
     else
     {
-      thresholdValue.setText((slider.getValue() / 1000f) + "");
+      /*
+       * round to 4 significant digits without trailing zeroes
+       */
+      float f = getSliderValue();
+      BigDecimal formatted = new BigDecimal(f).round(FOUR_SIG_FIG)
+              .stripTrailingZeros();
+      thresholdValue.setText(formatted.toPlainString());
     }
     adjusting = oldadj;
   }
 
+  /**
+   * Answers the value of the slider position (descaled to 'true' value)
+   * 
+   * @return
+   */
+  protected float getSliderValue()
+  {
+    return slider.getSliderValue();
+  }
+
+  /**
+   * Sets the slider value (scaled from the true value to the slider range)
+   * 
+   * @param value
+   */
+  protected void setSliderValue(float value)
+  {
+    slider.setSliderValue(value);
+  }
+  /**
+   * Answers the value of the slider position as a percentage between minimum and
+   * maximum of its range
+   * 
+   * @return
+   */
+  protected float getSliderPercentageValue()
+  {
+    return slider.getSliderPercentageValue();
+  }
+
+  /**
+   * Sets the slider position for a given percentage value of its min-max range
+   * 
+   * @param pct
+   */
+  protected void setSliderPercentageValue(float pct)
+  {
+    slider.setSliderPercentageValue(pct);
+  }
+
   protected void addSliderMouseListeners()
   {
 
@@ -290,6 +343,10 @@ public abstract class AnnotationRowFilter extends JPanel
     updateView();
   }
 
+  /**
+   * Updates the slider position, and the display, for an update in the slider's
+   * text input field
+   */
   protected void thresholdValue_actionPerformed()
   {
     try
@@ -297,12 +354,11 @@ public abstract class AnnotationRowFilter extends JPanel
       float f = Float.parseFloat(thresholdValue.getText());
       if (percentThreshold.isSelected())
       {
-        slider.setValue(slider.getMinimum() + ((int) ((f / 100f)
-                * (slider.getMaximum() - slider.getMinimum()))));
+        setSliderPercentageValue(f);
       }
       else
       {
-        slider.setValue((int) (f * 1000));
+        setSliderValue(f);
       }
       updateView();
     } catch (NumberFormatException ex)
@@ -528,4 +584,23 @@ public abstract class AnnotationRowFilter extends JPanel
       valueChanged(true);
     }
   }
+
+  /**
+   * Sets the min-max range and current value of the slider, with rescaling from
+   * true values to slider range as required
+   * 
+   * @param min
+   * @param max
+   * @param value
+   */
+  protected void setSliderModel(float min, float max, float value)
+  {
+    slider.setSliderModel(min, max, value);
+
+    /*
+     * tick mark every 10th position
+     */
+    slider.setMajorTickSpacing(
+            (slider.getMaximum() - slider.getMinimum()) / 10);
+  }
 }
index b49593a..e636455 100644 (file)
@@ -150,7 +150,7 @@ public class FeatureSettings extends JPanel
    */
   Object[][] originalData;
 
-  float originalTransparency;
+  private float originalTransparency;
 
   private ViewStyleI originalViewStyle;
 
@@ -182,7 +182,7 @@ public class FeatureSettings extends JPanel
   /*
    * true when Feature Settings are updating from feature renderer
    */
-  boolean handlingUpdate = false;
+  private boolean handlingUpdate = false;
 
   /*
    * a change listener to ensure the dialog is updated if
index 200911d..54eeba7 100644 (file)
@@ -50,6 +50,8 @@ import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.math.BigDecimal;
+import java.math.MathContext;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.List;
@@ -63,7 +65,6 @@ import javax.swing.JComboBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
-import javax.swing.JSlider;
 import javax.swing.JTextField;
 import javax.swing.border.EmptyBorder;
 import javax.swing.border.LineBorder;
@@ -79,6 +80,8 @@ import javax.swing.event.ChangeListener;
  */
 public class FeatureTypeSettings extends JalviewDialog
 {
+  private final static MathContext FOUR_SIG_FIG = new MathContext(4);
+
   private final static String LABEL_18N = MessageManager
           .getString("label.label");
 
@@ -142,11 +145,6 @@ public class FeatureTypeSettings extends JalviewDialog
   private float max;
 
   /*
-   * scale factor for conversion between absolute min-max and slider
-   */
-  float scaleFactor;
-
-  /*
    * radio button group, to select what to colour by:
    * simple colour, by category (text), or graduated
    */
@@ -168,7 +166,7 @@ public class FeatureTypeSettings extends JalviewDialog
 
   private JComboBox<Object> threshold = new JComboBox<>();
 
-  JSlider slider = new JSlider();
+  private Slider slider;
 
   JTextField thresholdValue = new JTextField(20);
 
@@ -347,12 +345,11 @@ public class FeatureTypeSettings extends JalviewDialog
        * update min-max scaling if there is a range to work with,
        * else disable the widgets (this shouldn't happen if only 
        * valid options are offered in the combo box)
+       * offset slider to have only non-negative values if necessary (JAL-2983)
        */
-      scaleFactor = (max == min) ? 1f : 100f / (max - min);
-      float range = (max - min) * scaleFactor;
-      slider.setMinimum((int) (min * scaleFactor));
-      slider.setMaximum((int) (max * scaleFactor));
-      slider.setMajorTickSpacing((int) (range / 10f));
+      slider.setSliderModel(min, max, min);
+      slider.setMajorTickSpacing(
+              (int) ((slider.getMaximum() - slider.getMinimum()) / 10f));
 
       threshline = new GraphLine((max - min) / 2f, "Threshold",
               Color.black);
@@ -364,8 +361,8 @@ public class FeatureTypeSettings extends JalviewDialog
                 fc.isAboveThreshold() ? ABOVE_THRESHOLD_OPTION
                         : BELOW_THRESHOLD_OPTION);
         slider.setEnabled(true);
-        slider.setValue((int) (fc.getThreshold() * scaleFactor));
-        thresholdValue.setText(String.valueOf(fc.getThreshold()));
+        slider.setSliderValue(fc.getThreshold());
+        setThresholdValueText(fc.getThreshold());
         thresholdValue.setEnabled(true);
         thresholdIsMin.setEnabled(true);
       }
@@ -643,6 +640,7 @@ public class FeatureTypeSettings extends JalviewDialog
         thresholdValue_actionPerformed();
       }
     });
+    slider = new Slider(0f, 100f, 50f);
     slider.setPaintLabels(false);
     slider.setPaintTicks(true);
     slider.setBackground(Color.white);
@@ -659,8 +657,7 @@ public class FeatureTypeSettings extends JalviewDialog
       {
         if (!adjusting)
         {
-          thresholdValue
-                  .setText(String.valueOf(slider.getValue() / scaleFactor));
+          setThresholdValueText(slider.getSliderValue());
           thresholdValue.setBackground(Color.white); // to reset red for invalid
           sliderValueChanged();
         }
@@ -1050,8 +1047,8 @@ public class FeatureTypeSettings extends JalviewDialog
       float f = Float.parseFloat(thresholdValue.getText());
       f = Float.max(f,  this.min);
       f = Float.min(f, this.max);
-      thresholdValue.setText(String.valueOf(f));
-      slider.setValue((int) (f * scaleFactor));
+      setThresholdValueText(f);
+      slider.setSliderValue(f);
       threshline.value = f;
       thresholdValue.setBackground(Color.white); // ok
       adjusting = false;
@@ -1064,13 +1061,25 @@ public class FeatureTypeSettings extends JalviewDialog
   }
 
   /**
+   * Sets the text field for threshold value, rounded to four significant figures
+   * 
+   * @param f
+   */
+  void setThresholdValueText(float f)
+  {
+    BigDecimal formatted = new BigDecimal(f).round(FOUR_SIG_FIG)
+            .stripTrailingZeros();
+    thresholdValue.setText(formatted.toPlainString());
+  }
+
+  /**
    * Action on change of threshold slider value. This may be done interactively
    * (by moving the slider), or programmatically (to update the slider after
    * manual input of a threshold value).
    */
   protected void sliderValueChanged()
   {
-    threshline.value = getRoundedSliderValue();
+    threshline.value = slider.getSliderValue();
 
     /*
      * repaint alignment, but not Overview or structure,
@@ -1079,21 +1088,6 @@ public class FeatureTypeSettings extends JalviewDialog
     colourChanged(false);
   }
 
-  /**
-   * Converts the slider value to its absolute value by dividing by the
-   * scaleFactor. Rounding errors are squashed by forcing min/max of slider
-   * range to the actual min/max of feature score range
-   * 
-   * @return
-   */
-  private float getRoundedSliderValue()
-  {
-    int value = slider.getValue();
-    float f = value == slider.getMaximum() ? max
-            : (value == slider.getMinimum() ? min : value / scaleFactor);
-    return f;
-  }
-
   void addActionListener(ActionListener listener)
   {
     if (featureSettings != null)
index 5342c90..0f4d0e7 100644 (file)
@@ -53,7 +53,6 @@ import javax.swing.JMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.JScrollPane;
-import javax.swing.JSlider;
 import javax.swing.JTextArea;
 import javax.swing.JTextField;
 import javax.swing.border.TitledBorder;
@@ -277,7 +276,7 @@ public class OptsAndParamsPage
 
     boolean choice = false;
 
-    JComboBox choicebox;
+    JComboBox<String> choicebox;
 
     JPanel controlPanel = new JPanel();
 
@@ -289,7 +288,7 @@ public class OptsAndParamsPage
 
     boolean integ = false;
 
-    Object lastVal;
+    String lastVal;
 
     ParameterI parameter;
 
@@ -299,7 +298,7 @@ public class OptsAndParamsPage
 
     JButton showDesc = new JButton();
 
-    JSlider slider = null;
+    Slider slider = null;
 
     JTextArea string = new JTextArea();
 
@@ -363,7 +362,7 @@ public class OptsAndParamsPage
       validate();
     }
 
-    private void makeExpanderParam(ParameterI parm)
+    private void makeExpanderParam(final ParameterI parm)
     {
       setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
       setBorder(new TitledBorder(parm.getName()));
@@ -451,6 +450,9 @@ public class OptsAndParamsPage
       validate();
     }
 
+    /**
+     * Action on input in text field
+     */
     @Override
     public void actionPerformed(ActionEvent e)
     {
@@ -467,24 +469,20 @@ public class OptsAndParamsPage
 
     private void checkIfModified()
     {
-      Object cstate = updateSliderFromValueField();
-      boolean notmod = false;
-      if (cstate.getClass() == lastVal.getClass())
-      {
-        if (cstate instanceof int[])
-        {
-          notmod = (((int[]) cstate)[0] == ((int[]) lastVal)[0]);
-        }
-        else if (cstate instanceof float[])
-        {
-          notmod = (((float[]) cstate)[0] == ((float[]) lastVal)[0]);
-        }
-        else if (cstate instanceof String[])
-        {
-          notmod = (((String[]) cstate)[0].equals(((String[]) lastVal)[0]));
-        }
-      }
-      pmdialogbox.argSetModified(this, !notmod);
+      Object cstate = getCurrentValue();
+      boolean modified = !cstate.equals(lastVal);
+      pmdialogbox.argSetModified(this, modified);
+    }
+
+    /**
+     * Answers the current value of the parameter, as text
+     * 
+     * @return
+     */
+    private String getCurrentValue()
+    {
+      return choice ? (String) choicebox.getSelectedItem()
+              : valueField.getText();
     }
 
     @Override
@@ -566,16 +564,20 @@ public class OptsAndParamsPage
 
     }
 
+    /**
+     * Action on change of slider value
+     */
     @Override
     public void stateChanged(ChangeEvent e)
     {
       if (!adjusting)
       {
-        valueField.setText("" + ((integ) ? ("" + slider.getValue())
-                : ("" + slider.getValue() / 1000f)));
+        float value = slider.getSliderValue();
+        valueField.setText(
+                integ ? Integer.toString((int) value)
+                        : Float.toString(value));
         checkIfModified();
       }
-
     }
 
     public void updateControls(ParameterI parm)
@@ -592,8 +594,6 @@ public class OptsAndParamsPage
         }
         else
         {
-          slider = new JSlider();
-          slider.addChangeListener(this);
           valueField = new JTextField();
           valueField.addActionListener(this);
           valueField.addKeyListener(new KeyListener()
@@ -622,9 +622,11 @@ public class OptsAndParamsPage
             }
           });
           valueField.setPreferredSize(new Dimension(60, 25));
+          slider = makeSlider(parm.getValidValue());
+          slider.addChangeListener(this);
+
           controlPanel.add(slider, BorderLayout.WEST);
           controlPanel.add(valueField, BorderLayout.EAST);
-
         }
       }
 
@@ -634,8 +636,8 @@ public class OptsAndParamsPage
         {
           if (init)
           {
-            List vals = parm.getPossibleValues();
-            for (Object val : vals)
+            List<String> vals = parm.getPossibleValues();
+            for (String val : vals)
             {
               choicebox.addItem(val);
             }
@@ -651,96 +653,105 @@ public class OptsAndParamsPage
           valueField.setText(parm.getValue());
         }
       }
-      lastVal = updateSliderFromValueField();
+      lastVal = getCurrentValue();
       adjusting = false;
     }
 
-    public Object updateSliderFromValueField()
+    private Slider makeSlider(ValueConstrainI validValue)
+    {
+      if (validValue != null)
+      {
+        final Number minValue = validValue.getMin();
+        final Number maxValue = validValue.getMax();
+        if (minValue != null && maxValue != null)
+        {
+          return new Slider(minValue.floatValue(), maxValue.floatValue(),
+                  minValue.floatValue());
+        }
+      }
+
+      /*
+       * otherwise, a nominal slider which will not be visible
+       */
+      return new Slider(0, 100, 50);
+    }
+
+    public void updateSliderFromValueField()
     {
-      int iVal;
-      float fVal;
       if (validator != null)
       {
+        final Number minValue = validator.getMin();
+        final Number maxValue = validator.getMax();
         if (integ)
         {
-          iVal = 0;
+          int iVal = 0;
           try
           {
             valueField.setText(valueField.getText().trim());
             iVal = Integer.valueOf(valueField.getText());
-            if (validator.getMin() != null
-                    && validator.getMin().intValue() > iVal)
+            if (minValue != null
+                    && minValue.intValue() > iVal)
             {
-              iVal = validator.getMin().intValue();
+              iVal = minValue.intValue();
               // TODO: provide visual indication that hard limit was reached for
               // this parameter
             }
-            if (validator.getMax() != null
-                    && validator.getMax().intValue() < iVal)
+            if (maxValue != null && maxValue.intValue() < iVal)
             {
-              iVal = validator.getMax().intValue();
-              // TODO: provide visual indication that hard limit was reached for
-              // this parameter
+              iVal = maxValue.intValue();
             }
-          } catch (Exception e)
+          } catch (NumberFormatException e)
           {
+            System.err.println(e.toString());
           }
-          ;
-          // update value field to reflect any bound checking we performed.
-          valueField.setText("" + iVal);
-          if (validator.getMin() != null && validator.getMax() != null)
+          if (minValue != null || maxValue != null)
           {
-            slider.getModel().setRangeProperties(iVal, 1,
-                    validator.getMin().intValue(),
-                    validator.getMax().intValue() + 1, true);
+            valueField.setText(String.valueOf(iVal));
+            slider.setSliderValue(iVal);
           }
           else
           {
             slider.setVisible(false);
           }
-          return new int[] { iVal };
         }
         else
         {
-          fVal = 0f;
+          float fVal = 0f;
           try
           {
             valueField.setText(valueField.getText().trim());
             fVal = Float.valueOf(valueField.getText());
-            if (validator.getMin() != null
-                    && validator.getMin().floatValue() > fVal)
+            if (minValue != null
+                    && minValue.floatValue() > fVal)
             {
-              fVal = validator.getMin().floatValue();
+              fVal = minValue.floatValue();
               // TODO: provide visual indication that hard limit was reached for
               // this parameter
               // update value field to reflect any bound checking we performed.
               valueField.setText("" + fVal);
             }
-            if (validator.getMax() != null
-                    && validator.getMax().floatValue() < fVal)
+            if (maxValue != null
+                    && maxValue.floatValue() < fVal)
             {
-              fVal = validator.getMax().floatValue();
+              fVal = maxValue.floatValue();
               // TODO: provide visual indication that hard limit was reached for
               // this parameter
               // update value field to reflect any bound checking we performed.
               valueField.setText("" + fVal);
             }
-          } catch (Exception e)
+          } catch (NumberFormatException e)
           {
+            System.err.println(e.toString());
           }
-          ;
-          if (validator.getMin() != null && validator.getMax() != null)
+          if (minValue != null && maxValue != null)
           {
-            slider.getModel().setRangeProperties((int) (fVal * 1000f), 1,
-                    (int) (validator.getMin().floatValue() * 1000f),
-                    1 + (int) (validator.getMax().floatValue() * 1000f),
-                    true);
+            slider.setSliderModel(minValue.floatValue(),
+                    maxValue.floatValue(), fVal);
           }
           else
           {
             slider.setVisible(false);
           }
-          return new float[] { fVal };
         }
       }
       else
@@ -748,14 +759,8 @@ public class OptsAndParamsPage
         if (!choice)
         {
           slider.setVisible(false);
-          return new String[] { valueField.getText().trim() };
-        }
-        else
-        {
-          return new String[] { (String) choicebox.getSelectedItem() };
         }
       }
-
     }
   }
 
@@ -801,9 +806,9 @@ public class OptsAndParamsPage
 
   URL linkImageURL = getClass().getResource("/images/link.gif");
 
-  Map<String, OptionBox> optSet = new java.util.LinkedHashMap<String, OptionBox>();
+  Map<String, OptionBox> optSet = new java.util.LinkedHashMap<>();
 
-  Map<String, ParamBox> paramSet = new java.util.LinkedHashMap<String, ParamBox>();
+  Map<String, ParamBox> paramSet = new java.util.LinkedHashMap<>();
 
   public Map<String, OptionBox> getOptSet()
   {
@@ -904,7 +909,7 @@ public class OptsAndParamsPage
    */
   public List<ArgumentI> getCurrentSettings()
   {
-    List<ArgumentI> argSet = new ArrayList<ArgumentI>();
+    List<ArgumentI> argSet = new ArrayList<>();
     for (OptionBox opts : getOptSet().values())
     {
       OptionI opt = opts.getOptionIfEnabled();
index d22ddd4..8a49092 100644 (file)
@@ -1084,7 +1084,7 @@ public class SeqPanel extends JPanel
                   pos);
           if (mf != null)
           {
-            unshownFeatures = seqARep.appendFeatures(tooltipText,
+            unshownFeatures += seqARep.appendFeatures(tooltipText,
                     pos, mf, fr2, MAX_TOOLTIP_LENGTH);
           }
         }
diff --git a/src/jalview/gui/Slider.java b/src/jalview/gui/Slider.java
new file mode 100644 (file)
index 0000000..7f18461
--- /dev/null
@@ -0,0 +1,113 @@
+package jalview.gui;
+
+import javax.swing.JSlider;
+
+/**
+ * A modified {@code javax.swing.JSlider} that
+ * <ul>
+ * <li>supports float valued numbers (by scaling up integer values)</li>
+ * <li>rescales 'true' value range to avoid negative values, as these are not
+ * rendered correctly by some look and feel libraries</li>
+ * </ul>
+ * 
+ * @author gmcarstairs
+ */
+@SuppressWarnings("serial")
+public class Slider extends JSlider
+{
+  /*
+   * 'true' value corresponding to zero on the slider
+   */
+  private float trueMin;
+
+  /*
+   * 'true' value corresponding to slider maximum
+   */
+  private float trueMax;
+
+  /*
+   * scaleFactor applied to true value range to give a
+   * slider range of 0 - 100
+   */
+  private float sliderScaleFactor;
+
+  /**
+   * Constructor that rescales min - max to 0 - 100 for the slider
+   * 
+   * @param min
+   * @param max
+   * @param value
+   */
+  public Slider(float min, float max, float value)
+  {
+    super();
+    setSliderModel(min, max, value);
+  }
+
+  /**
+   * Sets the min-max range and current value of the slider, with rescaling from
+   * true values to slider range as required
+   * 
+   * @param min
+   * @param max
+   * @param value
+   */
+  public void setSliderModel(float min, float max, float value)
+  {
+    trueMin = min;
+    trueMax = max;
+    setMinimum(0);
+    sliderScaleFactor = 100f / (max - min);
+    int sliderMax = (int) ((max - min) * sliderScaleFactor);
+    setMaximum(sliderMax);
+    setSliderValue(value);
+  }
+
+  /**
+   * Answers the value of the slider position (descaled to 'true' value)
+   * 
+   * @return
+   */
+  public float getSliderValue()
+  {
+    /*
+     * convert slider max to 'true max' in case of rounding errors
+     */
+    int value = getValue();
+    return value == getMaximum() ? trueMax
+            : value / sliderScaleFactor + trueMin;
+  }
+
+  /**
+   * Sets the slider value (scaled from the true value to the slider range)
+   * 
+   * @param value
+   */
+  public void setSliderValue(float value)
+  {
+    setValue(Math.round((value - trueMin) * sliderScaleFactor));
+  }
+
+  /**
+   * Answers the value of the slider position as a percentage between minimum and
+   * maximum of its range
+   * 
+   * @return
+   */
+  public float getSliderPercentageValue()
+  {
+    return (getValue() - getMinimum()) * 100f
+            / (getMaximum() - getMinimum());
+  }
+
+  /**
+   * Sets the slider position for a given percentage value of its min-max range
+   * 
+   * @param pct
+   */
+  public void setSliderPercentageValue(float pct)
+  {
+    float pc = pct / 100f * getMaximum();
+    setValue((int) pc);
+  }
+}
index a5a4e36..39d8ad4 100755 (executable)
  */
 package jalview.io;
 
+import jalview.api.AlignExportSettingsI;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureSettingsModelI;
+import jalview.util.MessageManager;
+
+import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -30,15 +37,12 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.StringReader;
+import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.net.URLConnection;
 import java.util.zip.GZIPInputStream;
 
-import jalview.api.AlignExportSettingsI;
-import jalview.api.AlignViewportI;
-import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureSettingsModelI;
-import jalview.util.MessageManager;
 import jalview.util.Platform;
 
 /**
@@ -64,6 +68,7 @@ public class FileParse
   {
     return bytes;
   }
+
   /**
    * a viewport associated with the current file operation. May be null. May
    * move to different object.
@@ -190,29 +195,73 @@ public class FileParse
     }
     if (!error)
     {
-      if (fileStr.toLowerCase().endsWith(".gz"))
+      try
       {
-        try
-        {
-          dataIn = tryAsGzipSource(new FileInputStream(fileStr));
-          dataName = fileStr;
-          return error;
-        } catch (Exception x)
-        {
-          warningMessage = "Failed  to resolve as a GZ stream ("
-                  + x.getMessage() + ")";
-          // x.printStackTrace();
-        }
-        ;
+        dataIn = checkForGzipStream(new FileInputStream(fileStr));
+        dataName = fileStr;
+      } catch (Exception x)
+      {
+        warningMessage = "Failed to resolve " + fileStr
+                + " as a data source. (" + x.getMessage() + ")";
+        // x.printStackTrace();
+        error = true;
       }
-
-      dataIn = new BufferedReader(new FileReader(fileStr));
-      dataName = fileStr;
+      ;
     }
     return error;
   }
+  
+  /**
+   * Recognise the 2-byte magic header for gzip streams
+   * 
+   * https://recalll.co/ask/v/topic/java-How-to-check-if-InputStream-is-Gzipped/555aadd62bd27354438b90f6
+   * 
+   * @param bytes - at least two bytes 
+   * @return 
+   */
+  private static boolean isGzipStream(byte[] bytes) {
+    int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
+    return (GZIPInputStream.GZIP_MAGIC == head);
+  }
 
-  private BufferedReader tryAsGzipSource(InputStream inputStream)
+  /**
+   * Returns a Reader for the given input after wrapping it in a buffered input
+   * stream, and then checking if it needs to be wrapped by a GZipInputStream
+   * 
+   * @param input
+   * @return
+   */
+  private BufferedReader checkForGzipStream(InputStream input) throws Exception {
+
+    // NB: stackoverflow https://stackoverflow.com/questions/4818468/how-to-check-if-inputstream-is-gzipped
+    // could use a PushBackInputStream rather than a BufferedInputStream
+    
+    BufferedInputStream bufinput;
+    if (!input.markSupported()) {
+       bufinput= new BufferedInputStream(input,16);
+       input = bufinput;
+    }
+    input.mark(4);
+    byte[] bytes=input.readNBytes(2);
+    input.reset();
+    if (bytes.length==2 && isGzipStream(bytes)) {
+      return getGzipReader(input);
+    }
+    // return a buffered reader for the stream.
+    InputStreamReader isReader= new InputStreamReader(input);
+    BufferedReader toReadFrom=new BufferedReader(isReader);
+    return toReadFrom;
+  }
+  /**
+   * Returns a {@code BufferedReader} which wraps the input stream with a
+   * GZIPInputStream. Throws a {@code ZipException} if a GZIP format error
+   * occurs or the compression method used is unsupported.
+   * 
+   * @param inputStream
+   * @return
+   * @throws Exception
+   */
+  private BufferedReader getGzipReader(InputStream inputStream)
           throws Exception
   {
     BufferedReader inData = new BufferedReader(
@@ -223,44 +272,74 @@ public class FileParse
     return inData;
   }
 
-  private boolean checkURLSource(String fileStr)
+  /**
+   * Tries to read from the given URL. If successful, saves a reader to the
+   * response in field {@code dataIn}, otherwise (on exception, or HTTP response
+   * status not 200), throws an exception.
+   * <p>
+   * If the response status includes
+   * 
+   * <pre>
+   * Content-Type : application/x-gzip
+   * </pre>
+   * 
+   * then tries to read as gzipped content.
+   * 
+   * @param urlStr
+   * @throws IOException
+   * @throws MalformedURLException
+   */
+  private void checkURLSource(String urlStr)
           throws IOException, MalformedURLException
   {
     errormessage = "URL NOT FOUND";
-    URL url = new URL(fileStr);
-    //
-    // GZIPInputStream code borrowed from Aquaria (soon to be open sourced) via
-    // Kenny Sabir
-    Exception e = null;
-    if (fileStr.toLowerCase().endsWith(".gz"))
+    URL url = new URL(urlStr);
+    URLConnection _conn = url.openConnection();
+    if (_conn instanceof HttpURLConnection)
     {
-      try
+      HttpURLConnection conn = (HttpURLConnection) _conn;
+      int rc = conn.getResponseCode();
+      if (rc != HttpURLConnection.HTTP_OK)
       {
-        InputStream inputStream = url.openStream();
-        dataIn = tryAsGzipSource(inputStream);
-        dataName = fileStr;
-        return false;
+        throw new IOException(
+                "Response status from " + urlStr + " was " + rc);
+      }
+    } else {
+      try {
+      dataIn = checkForGzipStream(_conn.getInputStream());
+      dataName=urlStr;
+      } catch (IOException ex)
+      {
+        throw new IOException("Failed to handle non-HTTP URI stream",ex);
       } catch (Exception ex)
       {
-        e = ex;
+        throw new IOException("Failed to determine type of input stream for given URI",ex);
       }
+      return;
     }
-
-    try
-    {
-      dataIn = new BufferedReader(new InputStreamReader(url.openStream()));
-    } catch (IOException q)
+    String encoding = _conn.getContentEncoding();
+    String contentType = _conn.getContentType();
+    boolean isgzipped = "application/x-gzip".equalsIgnoreCase(contentType)
+            || "gzip".equals(encoding);
+    Exception e = null;
+    InputStream inputStream = _conn.getInputStream();
+    if (isgzipped)
     {
-      if (e != null)
+      try
+      {
+        dataIn = getGzipReader(inputStream);
+        dataName = urlStr;
+      } catch (Exception e1)
       {
         throw new IOException(MessageManager
                 .getString("exception.failed_to_resolve_gzip_stream"), e);
       }
-      throw q;
+      return;
     }
-    // record URL as name of datasource.
-    dataName = fileStr;
-    return false;
+
+    dataIn = new BufferedReader(new InputStreamReader(inputStream));
+    dataName = urlStr;
+    return;
   }
 
   /**
@@ -345,7 +424,8 @@ public class FileParse
       {
         // this will be from JavaScript
         inFile = file;
-        dataIn = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
+        dataIn = new BufferedReader(
+                new InputStreamReader(new ByteArrayInputStream(bytes)));
         dataName = fileStr;
       }
       else if (checkFileSource(fileStr))
@@ -453,8 +533,7 @@ public class FileParse
     {
       // pass up the reason why we have no source to read from
       throw new IOException(MessageManager.formatMessage(
-              "exception.failed_to_read_data_from_source",
-              new String[]
+              "exception.failed_to_read_data_from_source", new String[]
               { errormessage }));
     }
     error = false;
index 1eeb922..f4ffc0c 100644 (file)
@@ -912,7 +912,7 @@ public class VCFLoader
          * RuntimeException throwable by htsjdk
          */
         String msg = String.format("Error reading VCF for %s:%d-%d: %s ",
-                map.chromosome, vcfStart, vcfEnd);
+                map.chromosome, vcfStart, vcfEnd,e.getLocalizedMessage());
         Cache.log.error(msg);
       }
     }
index d6627c4..71d798d 100644 (file)
@@ -52,7 +52,7 @@ public class Platform
           false;
 
   private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
-          isWin = null;
+          isWin = null, isLinux = null;
 
   private static Boolean isHeadless = null;
 
@@ -81,6 +81,18 @@ public class Platform
   }
 
   /**
+   * added to check LaF for Linux
+   * 
+   * @return
+   */
+  public static boolean isLinux()
+  {
+    return (isLinux == null
+            ? (isLinux = (System.getProperty("os.name").indexOf("Linux") >= 0))
+            : isLinux);
+  }
+
+  /**
    * 
    * @return true if HTML5 JavaScript
    */
index 8877c34..47e66ac 100644 (file)
@@ -35,6 +35,11 @@ import com.stevesoft.pat.Regex;
  */
 abstract public class Pfam extends Xfam
 {
+  /*
+   * append to URLs to retrieve as a gzipped file
+   */
+  protected static final String GZIPPED = "/gzipped";
+
   static final String PFAM_BASEURL_KEY = "PFAM_BASEURL";
 
   private static final String DEFAULT_PFAM_BASEURL = "https://pfam.xfam.org";
index 0600427..d71892b 100644 (file)
@@ -34,7 +34,7 @@ public class PfamFull extends Pfam
   @Override
   public String getURLSuffix()
   {
-    return "/alignment/full";
+    return "/alignment/full" + GZIPPED;
   }
 
   /*
index dff8a17..f64d07f 100644 (file)
@@ -36,7 +36,7 @@ public class PfamSeed extends Pfam
   @Override
   public String getURLSuffix()
   {
-    return "/alignment/seed";
+    return "/alignment/seed" + GZIPPED;
   }
 
   /*
index 1d9d99a..c9ee7fc 100644 (file)
@@ -36,6 +36,11 @@ abstract public class Rfam extends Xfam
 
   private static final String DEFAULT_RFAM_BASEURL = "https://rfam.xfam.org";
 
+  /*
+   * append to URLs to retrieve as a gzipped file
+   */
+  protected static final String GZIPPED = "?gzip=1&download=1";
+
   @Override
   protected String getURLPrefix()
   {
index d815336..396511c 100644 (file)
@@ -36,7 +36,7 @@ public class RfamFull extends Rfam
   @Override
   public String getURLSuffix()
   {
-    return "/alignment/full";
+    return "/alignment/full" + GZIPPED;
   }
 
   /*
index a74e829..eaa574b 100644 (file)
@@ -36,8 +36,7 @@ public class RfamSeed extends Rfam
   @Override
   public String getURLSuffix()
   {
-    // to download gzipped file add '?gzip=1'
-    return "/alignment/stockholm";
+    return "/alignment/stockholm" + GZIPPED;
   }
 
   /*
index b83f558..f0cb14b 100644 (file)
@@ -36,7 +36,6 @@ import jalview.ws.seqfetcher.DbSourceProxyImpl;
  */
 public abstract class Xfam extends DbSourceProxyImpl
 {
-
   public Xfam()
   {
     super();
index f5cc640..23cceb2 100644 (file)
@@ -38,7 +38,7 @@ public class PfamFullTest
   @Test(groups = "Functional")
   public void testGetURL()
   {
-    String path = "pfam.xfam.org/family/ABC/alignment/full";
+    String path = "pfam.xfam.org/family/ABC/alignment/full/gzipped";
 
     // with default value for domain
     String url = new PfamFull().getURL(" abc ");
index 355ef0c..451810b 100644 (file)
@@ -38,7 +38,7 @@ public class PfamSeedTest
   @Test(groups = "Functional")
   public void testGetURL()
   {
-    String path = "pfam.xfam.org/family/ABC/alignment/seed";
+    String path = "pfam.xfam.org/family/ABC/alignment/seed/gzipped";
 
     // with default value for domain
     String url = new PfamSeed().getURL(" abc ");
index 2d1497f..87b963f 100644 (file)
@@ -38,7 +38,7 @@ public class RfamFullTest
   @Test(groups = "Functional")
   public void testGetURL()
   {
-    String path = "rfam.xfam.org/family/ABC/alignment/full";
+    String path = "rfam.xfam.org/family/ABC/alignment/full?gzip=1&download=1";
 
     // with default value for domain
     String url = new RfamFull().getURL(" abc ");
index 745ba2e..1165d1f 100644 (file)
@@ -38,7 +38,7 @@ public class RfamSeedTest
   @Test(groups = "Functional")
   public void testGetURL()
   {
-    String path = "rfam.xfam.org/family/ABC/alignment/stockholm";
+    String path = "rfam.xfam.org/family/ABC/alignment/stockholm?gzip=1&download=1";
 
     // with default value for domain
     String url = new RfamSeed().getURL(" abc ");