Merge branch 'develop' into features/JAL-3982_JAL-3960_mouseover_genome_to_3dstructur... features/JAL-3982_JAL-3960_mouseover_genome_to_3dstructure_for_2_11_3
authorJames Procter <j.procter@dundee.ac.uk>
Tue, 23 Jan 2024 17:28:31 +0000 (17:28 +0000)
committerJames Procter <j.procter@dundee.ac.uk>
Tue, 23 Jan 2024 17:28:31 +0000 (17:28 +0000)
31 files changed:
RELEASE
build.gradle
doc/building.md
help/markdown/releases/release-2_11_3_0.md
help/markdown/releases/release-2_11_3_3.md
src/jalview/bin/Commands.java
src/jalview/bin/Launcher.java
src/jalview/bin/MemorySetting.java
src/jalview/bin/argparser/Arg.java
src/jalview/bin/argparser/ArgParser.java
src/jalview/bin/argparser/ArgValue.java
src/jalview/bin/argparser/ArgValues.java
src/jalview/bin/argparser/ArgValuesMap.java
src/jalview/bin/argparser/BootstrapArgs.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/AppJmol.java
src/jalview/gui/Desktop.java
src/jalview/gui/IdPanel.java
src/jalview/gui/IdwidthAdjuster.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SplashScreen.java
src/jalview/gui/StructureChooser.java
src/jalview/jbgui/GAlignmentPanel.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/util/ColorUtils.java
src/jalview/ws/dbsources/Uniprot.java
test/jalview/bin/CommandLineOperations.java
test/jalview/bin/CommandsTest.java
test/jalview/bin/argparser/ArgParserTest.java

diff --git a/RELEASE b/RELEASE
index f733246..a44846c 100644 (file)
--- a/RELEASE
+++ b/RELEASE
@@ -1,2 +1,2 @@
 jalview.release=releases/Release_2_11_3_Branch
-jalview.version=2.11.3.2
+jalview.version=2.11.3.3
index 3b81404..3ed6c56 100644 (file)
@@ -2150,6 +2150,16 @@ task cleanDist {
 }
 
 
+task launcherJar(type: Jar) {
+  manifest {
+      attributes (
+        "Main-Class": shadow_jar_main_class,
+        "Implementation-Version": JALVIEW_VERSION,
+        "Application-Name": applicationName
+      )
+  }
+}
+
 shadowJar {
   group = "distribution"
   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
@@ -2163,6 +2173,10 @@ shadowJar {
   from groovyJars
   from otherJars
 
+  manifest {
+    // shadowJar manifest must inheritFrom another Jar task.  Can't set attributes here.
+    inheritFrom(project.tasks.launcherJar.manifest)
+  }
   // we need to include the groovy-swing Include-Package for it to run in the shadowJar
   doFirst {
     def jarFileManifests = []
@@ -2172,9 +2186,7 @@ shadowJar {
         jarFileManifests += mf
       }
     }
-
     manifest {
-      attributes "Implementation-Version": JALVIEW_VERSION, "Application-Name": applicationName
       from (jarFileManifests) {
         eachEntry { details ->
           if (!details.key.equals("Import-Package")) {
@@ -2187,6 +2199,7 @@ shadowJar {
 
   duplicatesStrategy "INCLUDE"
 
+  // this mainClassName is mandatory but gets ignored due to manifest created in doFirst{}. Set the Main-Class as an attribute in launcherJar instead
   mainClassName = shadow_jar_main_class
   mergeServiceFiles()
   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
index bbcc011..81700ac 100644 (file)
@@ -5,9 +5,9 @@
 ```
 # download
 git clone http://source.jalview.org/git/jalview.git
-# compile
+# compile - needs java 11 JDK
 cd ./jalview
-gradle shadowJar
+gradle shadowJar  # needs to be gradle 7.6.3 or above (but not gradle 8)
 # run
 java -jar build/libs/jalview-all-*-j11.jar
 
index 35ff569..c732130 100644 (file)
@@ -16,7 +16,7 @@ channel: "release"
 - <!-- JAL-4089 --> Use selected columns for superposition
 - <!-- JAL-4086 --> Highlight aligned positions on all associated structures when mousing over a column
 - <!-- JAL-4221 --> sequence descriptions are updated from database reference sources if not already defined
-- <!-- JAL-4273 --> Visible adjuster marks to grab and adjust annotation panel height and id width
+- <!-- JAL-4273,JAL-3497 --> Visible adjuster marks to grab and adjust annotation panel height and id width
 - <!-- JAL-4260 --> Adjustable ID margin when alignment is wrapped
 - <!-- JAL-4274 --> Command line options and configurable bitmap export (via preferences file) for height, width and scale factor
 - <!-- JAL-4307 --> Show or hide ligands in a Jmol structure view via View Ligands submenu
index cc8e049..dcbe8e8 100644 (file)
@@ -1,14 +1,22 @@
 ---
 version: 2.11.3.3
-date: 2023-12-14
+date: 2024-01-24
 channel: "release"
 ---
 
 ## New Features
 
+- <!-- JAL-4192 --> Minimum versions of Jalview's build toolchain are explicitly specified in doc/building.md
+
 ## Issues Resolved
 
 - <!-- JAL-840 --> Clustal colourscheme documentation not consistent with implementation
-
-
-
+- <!-- JAL-4367 --> Handles to adjust ID panel width or annotation panel height can be dragged out of the Alignment window
+- <!-- JAL-2934 --> Trackpad scrolling not proportional to gesture velocity
+- <!-- JAL-4353 --> Cannot output individual images of more than one structure view attached to one open alignment
+- <!-- JAL-4373 --> --jvmempc and --jvmemmax don't work for jalview installed via conda or jar
+- <!-- JAL-4368 --> User's default colour scheme is erroneously applied to alignments in Jalview projects opened via command line when no --colour argument supplied
+- <!-- JAL-4290 --> Headless alignment export with structure annotations doesn't include secondary structure and temperature factor
+- <!-- JAL-4369 --> Uniprot record retrieval not working in JalviewJS (affects all versions)
+- <!-- JAL-4243 --> bio.tools record version number updated
+- <!-- JAL-4217 --> Miscellaneous patches to fix hangs in 2.11.3 test suite
index 475eba2..b6a5a25 100644 (file)
@@ -179,8 +179,7 @@ public class Commands
 
     if (argParser.getBoolean(Arg.QUIT))
     {
-      Jalview.getInstance().exit(
-              "Exiting due to " + Arg.QUIT.argString() + " argument.",
+      Jalview.exit("Exiting due to " + Arg.QUIT.argString() + " argument.",
               ExitCode.OK);
       return true;
     }
@@ -210,8 +209,13 @@ public class Commands
 
     Boolean isError = Boolean.valueOf(false);
 
-    // set wrap scope here so it can be applied after structures are opened
+    // set wrap, showSSAnnotations, showAnnotations and hideTFrows scope here so
+    // it can be applied after structures are opened
     boolean wrap = false;
+    boolean showSSAnnotations = false;
+    boolean showAnnotations = false;
+    boolean hideTFrows = false;
+    AlignFrame af = null;
 
     if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
     {
@@ -220,7 +224,6 @@ public class Commands
 
       boolean first = true;
       boolean progressBarSet = false;
-      AlignFrame af;
       // Combine the APPEND and OPEN files into one list, along with whether it
       // was APPEND or OPEN
       List<ArgValue> openAvList = new ArrayList<>();
@@ -328,9 +331,17 @@ public class Commands
           }
 
           // colour alignment
-          String colour = avm.getFromSubValArgOrPref(av, Arg.COLOUR, sv,
-                  null, "DEFAULT_COLOUR_PROT", "");
-          this.colourAlignFrame(af, colour);
+          String colour = null;
+          if (avm.containsArg(Arg.COLOUR)
+                  || !(format == FileFormat.Jalview))
+          {
+            colour = avm.getFromSubValArgOrPref(av, Arg.COLOUR, sv, null,
+                    "DEFAULT_COLOUR_PROT", null);
+          }
+          if (colour != null)
+          {
+            this.colourAlignFrame(af, colour);
+          }
 
           // Change alignment frame title
           String title = avm.getFromSubValArgOrPref(av, Arg.TITLE, sv, null,
@@ -392,53 +403,17 @@ public class Commands
           }
 
           // Show secondary structure annotations?
-          boolean showSSAnnotations = avm.getFromSubValArgOrPref(
+          showSSAnnotations = avm.getFromSubValArgOrPref(
                   Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
                   "STRUCT_FROM_PDB", true);
-
           // Show sequence annotations?
-          boolean showAnnotations = avm.getFromSubValArgOrPref(
-                  Arg.SHOWANNOTATIONS, av.getSubVals(), null,
-                  "SHOW_ANNOTATIONS", true);
-
-          boolean hideTFrows = (avm.getBoolean(Arg.NOTEMPFAC));
-          final AlignFrame _af = af;
-          // many of jalview's format/layout methods are only thread safe on the
-          // swingworker thread.
-          // all these methods should be on the alignViewController so it can
-          // coordinate such details
-          try
-          {
-            SwingUtilities.invokeAndWait(new Runnable()
-            {
-
-              @Override
-              public void run()
-              {
-                _af.setAnnotationsVisibility(showSSAnnotations, true,
-                        false);
-
-                _af.setAnnotationsVisibility(showAnnotations, false, true);
+          showAnnotations = avm.getFromSubValArgOrPref(Arg.SHOWANNOTATIONS,
+                  av.getSubVals(), null, "SHOW_ANNOTATIONS", true);
+          // hide the Temperature Factor row?
+          hideTFrows = (avm.getBoolean(Arg.NOTEMPFAC));
 
-                // show temperature factor annotations?
-                if (hideTFrows)
-                {
-                  // do this better (annotation types?)
-                  List<String> hideThese = new ArrayList<>();
-                  hideThese.add("Temperature Factor");
-                  hideThese.add(AlphaFoldAnnotationRowBuilder.LABEL);
-                  AlignmentUtils.showOrHideSequenceAnnotations(
-                          _af.getCurrentView().getAlignment(), hideThese,
-                          null, false, false);
-                }
-              }
-            });
-          } catch (Exception x)
-          {
-            Console.warn(
-                    "Unexpected exception adjusting annotation row visibility.",
-                    x);
-          }
+          // showSSAnnotations, showAnnotations, hideTFrows used after opening
+          // structure
 
           // wrap alignment? do this last for formatting reasons
           wrap = avm.getFromSubValArgOrPref(Arg.WRAP, sv, null,
@@ -496,20 +471,20 @@ public class Commands
     // open the structure (from same PDB file or given PDBfile)
     if (!avm.getBoolean(Arg.NOSTRUCTURE))
     {
-
-      AlignFrame af = afMap.get(id);
+      if (af == null)
+      {
+        af = afMap.get(id);
+      }
       if (avm.containsArg(Arg.STRUCTURE))
       {
         commandArgsProvided = true;
-        for (
-
-        ArgValue av : avm.getArgValueList(Arg.STRUCTURE))
+        for (ArgValue structureAv : avm.getArgValueList(Arg.STRUCTURE))
         {
           argParser.setStructureFilename(null);
-          String val = av.getValue();
-          SubVals subVals = av.getSubVals();
-          int argIndex = av.getArgIndex();
-          SequenceI seq = getSpecifiedSequence(af, avm, av);
+          String val = structureAv.getValue();
+          SubVals subVals = structureAv.getSubVals();
+          int argIndex = structureAv.getArgIndex();
+          SequenceI seq = getSpecifiedSequence(af, avm, structureAv);
           if (seq == null)
           {
             // Could not find sequence from subId, let's assume the first
@@ -578,8 +553,8 @@ public class Commands
 
           // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
           String paeFilepath = avm.getFromSubValArgOrPrefWithSubstitutions(
-                  argParser, Arg.PAEMATRIX, ArgValuesMap.Position.AFTER, av,
-                  subVals, null, null, null);
+                  argParser, Arg.PAEMATRIX, ArgValuesMap.Position.AFTER,
+                  structureAv, subVals, null, null, null);
           if (paeFilepath != null)
           {
             File paeFile = new File(paeFilepath);
@@ -603,8 +578,8 @@ public class Commands
           // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
           // reference annotations
           String tftString = avm.getFromSubValArgOrPrefWithSubstitutions(
-                  argParser, Arg.TEMPFAC, ArgValuesMap.Position.AFTER, av,
-                  subVals, null, null, null);
+                  argParser, Arg.TEMPFAC, ArgValuesMap.Position.AFTER,
+                  structureAv, subVals, null, null, null);
           boolean notempfac = avm.getFromSubValArgOrPref(Arg.NOTEMPFAC,
                   subVals, null, "ADD_TEMPFACT_ANN", false, true);
           TFType tft = notempfac ? null : TFType.DEFAULT;
@@ -636,8 +611,8 @@ public class Commands
           }
 
           String sViewerName = avm.getFromSubValArgOrPref(
-                  Arg.STRUCTUREVIEWER, ArgValuesMap.Position.AFTER, av,
-                  subVals, null, null, "jmol");
+                  Arg.STRUCTUREVIEWER, ArgValuesMap.Position.AFTER,
+                  structureAv, subVals, null, null, "jmol");
           ViewerType viewerType = ViewerType.getFromString(sViewerName);
 
           // TODO use ssFromStructure
@@ -698,7 +673,8 @@ public class Commands
           if (avm.containsArg(Arg.STRUCTUREIMAGE))
           {
             for (ArgValue structureImageArgValue : avm
-                    .getArgValueList(Arg.STRUCTUREIMAGE))
+                    .getArgValueListFromSubValOrArg(structureAv,
+                            Arg.STRUCTUREIMAGE, subVals))
             {
               String structureImageFilename = argParser.makeSubstitutions(
                       structureImageArgValue.getValue(), id, true);
@@ -868,10 +844,47 @@ public class Commands
       }
     }
 
-    if (wrap)
+    if (af == null)
+    {
+      af = afMap.get(id);
+    }
+    // many of jalview's format/layout methods are only thread safe on the
+    // swingworker thread.
+    // all these methods should be on the alignViewController so it can
+    // coordinate such details
+    if (headless)
     {
+      showOrHideAnnotations(af, showSSAnnotations, showAnnotations,
+              hideTFrows);
+    }
+    else
+    {
+      try
+      {
+        AlignFrame _af = af;
+        final boolean _showSSAnnotations = showSSAnnotations;
+        final boolean _showAnnotations = showAnnotations;
+        final boolean _hideTFrows = hideTFrows;
+        SwingUtilities.invokeAndWait(() -> {
+          showOrHideAnnotations(_af, _showSSAnnotations, _showAnnotations,
+                  _hideTFrows);
+        }
 
-      AlignFrame af = afMap.get(id);
+        );
+      } catch (Exception x)
+      {
+        Console.warn(
+                "Unexpected exception adjusting annotation row visibility.",
+                x);
+      }
+    }
+
+    if (wrap)
+    {
+      if (af == null)
+      {
+        af = afMap.get(id);
+      }
       if (af != null)
       {
         af.setWrapFormat(wrap, true);
@@ -898,6 +911,26 @@ public class Commands
     return theseArgsWereParsed && !isError;
   }
 
+  private static void showOrHideAnnotations(AlignFrame af,
+          boolean showSSAnnotations, boolean showAnnotations,
+          boolean hideTFrows)
+  {
+    af.setAnnotationsVisibility(showSSAnnotations, true, false);
+    af.setAnnotationsVisibility(showAnnotations, false, true);
+
+    // show temperature factor annotations?
+    if (hideTFrows)
+    {
+      // do this better (annotation types?)
+      List<String> hideThese = new ArrayList<>();
+      hideThese.add("Temperature Factor");
+      hideThese.add(AlphaFoldAnnotationRowBuilder.LABEL);
+      AlignmentUtils.showOrHideSequenceAnnotations(
+              af.getCurrentView().getAlignment(), hideThese, null, false,
+              false);
+    }
+  }
+
   protected void processGroovyScript(String id)
   {
     ArgValuesMap avm = argParser.getLinkedArgs(id);
@@ -1268,8 +1301,19 @@ public class Commands
 
   private void colourAlignFrame(AlignFrame af, ColourSchemeI cs)
   {
-    // Note that cs == null removes colour scheme from af
-    af.changeColour(cs);
+    try {
+    SwingUtilities.invokeAndWait(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        // Note that cs == null removes colour scheme from af
+        af.changeColour(cs);
+      }
+    }); } catch (Exception x) {
+      Console.trace("Interrupted whilst waiting for colorAlignFrame action",x);
+      
+    }
   }
 
   private ColourSchemeI getColourScheme(AlignFrame af)
index 507f501..2a78d8e 100644 (file)
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
+import jalview.bin.argparser.Arg;
 import jalview.util.ChannelProperties;
 import jalview.util.LaunchUtils;
 
@@ -35,12 +36,12 @@ import jalview.util.LaunchUtils;
  * A Launcher class for Jalview. This class is used to launch Jalview from the
  * shadowJar when Getdown is not used or available. It attempts to take all the
  * command line arguments to pass on to the jalview.bin.Jalview class, but to
- * insert a -Xmx memory setting to a sensible default, using the -jvmmempc and
+ * insert a -Xmx memory setting to a sensible default, using the --jvmmempc and
  * -jvmmemmax application arguments if specified. If not specified then system
  * properties will be looked for by jalview.bin.MemorySetting. If the user has
- * provided the JVM with a -Xmx setting directly and not set -jvmmempc or
- * -jvmmemmax then this setting will be used and system properties ignored. If
- * -Xmx is set as well as -jvmmempc or -jvmmemmax as argument(s) then the -Xmx
+ * provided the JVM with a -Xmx setting directly and not set --jvmmempc or
+ * --jvmmemmax then this setting will be used and system properties ignored. If
+ * -Xmx is set as well as --jvmmempc or --jvmmemmax as argument(s) then the -Xmx
  * argument will NOT be passed on to the main application launch.
  * 
  * @author bsoares
@@ -62,8 +63,8 @@ public class Launcher
 
   /**
    * main method for jalview.bin.Launcher. This restarts the same JRE's JVM with
-   * the same arguments but with memory adjusted based on extracted -jvmmempc
-   * and -jvmmemmax application arguments. If on a Mac then extra dock:icon and
+   * the same arguments but with memory adjusted based on extracted --jvmmempc
+   * and --jvmmemmax application arguments. If on a Mac then extra dock:icon and
    * dock:name arguments are also set.
    * 
    * @param args
@@ -168,32 +169,24 @@ public class Launcher
       }
       // jvmmempc and jvmmemmax args used to set memory and are not passed on to
       // startClass
-      if (arg.startsWith(
-              "-" + MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "="))
+      final String jvmmempcArg = Arg.JVMMEMPC.getName();
+      final String jvmmemmaxArg = Arg.JVMMEMMAX.getName();
+      if (arg.startsWith("-" + jvmmempcArg + "="))
       {
-        jvmmempc = arg.substring(
-                MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.length()
-                        + 2);
+        jvmmempc = arg.substring(jvmmempcArg.length() + 2);
       }
-      else if (arg.startsWith(
-              "-" + MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME + "="))
+      else if (arg.startsWith("-" + jvmmemmaxArg + "="))
       {
-        jvmmemmax = arg.substring(
-                MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME.length() + 2);
+        jvmmemmax = arg.substring(jvmmemmaxArg.length() + 2);
       }
       // --doubledash versions
-      else if (arg.startsWith("--"
-              + MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "="))
+      else if (arg.startsWith("--" + jvmmempcArg + "="))
       {
-        jvmmempc = arg.substring(
-                MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.length()
-                        + 3);
+        jvmmempc = arg.substring(jvmmempcArg.length() + 3);
       }
-      else if (arg.startsWith(
-              "--" + MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME + "="))
+      else if (arg.startsWith("--" + jvmmemmaxArg + "="))
       {
-        jvmmemmax = arg.substring(
-                MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME.length() + 3);
+        jvmmemmax = arg.substring(jvmmemmaxArg.length() + 3);
       }
       // retain arg
       else
index 7458d55..738d1e7 100644 (file)
@@ -24,6 +24,8 @@ package jalview.bin;
 
 import java.util.Locale;
 
+import jalview.bin.argparser.Arg;
+
 /**
  * Methods to decide on appropriate memory setting for Jalview based on two
  * optionally provided values: jvmmempc - the maximum percentage of total
@@ -37,9 +39,11 @@ import java.util.Locale;
  */
 public class MemorySetting
 {
-  public static final String MAX_HEAPSIZE_PERCENT_PROPERTY_NAME = "jvmmempc";
+  public static final String MAX_HEAPSIZE_PERCENT_PROPERTY_NAME = Arg.JVMMEMPC
+          .getName();
 
-  public static final String MAX_HEAPSIZE_PROPERTY_NAME = "jvmmemmax";
+  public static final String MAX_HEAPSIZE_PROPERTY_NAME = Arg.JVMMEMMAX
+          .getName();
 
   private static final int MAX_HEAPSIZE_PERCENT_DEFAULT = 90; // 90%
 
index d043d16..36f7794 100644 (file)
@@ -25,9 +25,11 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import jalview.bin.argparser.Arg.Opt;
@@ -181,7 +183,8 @@ public enum Arg
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.MULTIVALUE,
           Opt.ALLOWMULTIID, Opt.REQUIREINPUT, Opt.OUTPUTFILE, Opt.PRIMARY),
-  STRUCTUREIMAGE(Type.IMAGE,
+  STRUCTUREIMAGE(new Type[]
+  { Type.IMAGE, Type.STRUCTUREIMAGE },
           "Export an image of a 3D structure opened in JMOL", Opt.STRING,
           Opt.LINKED, Opt.MULTIVALUE, Opt.OUTPUTFILE, Opt.ALLOWMULTIID,
           Opt.PRIMARY),
@@ -213,26 +216,6 @@ public enum Arg
   BGCOLOUR(Type.IMAGE, "bgcolor", // being a bit soft on the Americans!
           "Applies a background colour to the structure image. Valid values are named colours known to Java or RRGGBB 6 digit hex-string.",
           Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWMULTIID),
-  /*
-  STRUCTUREIMAGETYPE(Type.IMAGE,
-          "Set the structure image format for the preceding --structureimage. Valid values are:\n"
-                  + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
-  STRUCTUREIMAGETEXTRENDERER(Type.IMAGE,
-          "Sets whether text in a vector structure image format (SVG, EPS) should be rendered as text or vector line-art. Possible values are:\n"
-                  + "text,\n" + "lineart.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
-  STRUCTUREIMAGESCALE(Type.IMAGE,
-          "Sets a scaling for bitmap structure image format (PNG). Should be given as a floating point number. If used in conjunction with --structureimagewidth and --structureimageheight then the smallest scaling will be used (structureimagescale, structureimagewidth and structureimageheight provide bounds for the structure image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
-  STRUCTUREIMAGEWIDTH(Type.IMAGE,
-          "Sets a width for bitmap structure image format (PNG) with the height maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --structureimagescale and --structureimageheight then the smallest scaling will be used (structureimagescale, structureimagewidth and structureimageheight provide bounds for the structure image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
-  STRUCTUREIMAGEHEIGHT(Type.IMAGE,
-          "Sets a height for bitmap structure image format (PNG) with the width maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --structureimagescale and --structureimagewidth then the smallest scaling will be used (structureimagescale, structureimagewidth and structureimageheight provide bounds for the structure image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
-  */
-
   OUTPUT(Type.OUTPUT,
           "Export the open alignment to file filename. The format name is specified by the subval modifier format=name, a following --format name argument or guessed from the file extension. Valid format names (and file extensions) are:\n"
                   + "fasta (fa, fasta, mfa, fastq),\n" + "pfam (pfam),\n"
@@ -328,7 +311,7 @@ public enum Arg
 
   // these last two have no purpose in the normal Jalview application but are
   // used by jalview.bin.Launcher to set memory settings. They are not used by
-  // argparser but are here for Usage statement reasons.
+  // argparser but are here for Usage statement and parsing reasons.
   JVMMEMPC(Type.CONFIG,
           "Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected.\n"
                   + "The equals sign (\"=\") separator must be used with no spaces.",
@@ -499,9 +482,8 @@ public enum Arg
     STRUCTURE("arguments used to add and format 3D structure data"),
     PROCESS("arguments used to process an alignment once opened"),
     OUTPUT("arguments used to save data from a processed alignment"),
-    IMAGE("arguments used to export an image of an alignment or structure"),
-    // IMAGE("arguments used to export an image of an alignment"),
-    // STRUCTUREIMAGE("arguments used to export an image of an structure"),
+    IMAGE("arguments used to export an image of an alignment"),
+    STRUCTUREIMAGE("arguments used to export an image of an structure"),
     FLOW("arguments that control processing of the other arguments"), //
     ALL("all arguments"), // mostly just a place-holder for --help-all
     NONE, // mostly a place-holder for --help
@@ -533,29 +515,53 @@ public enum Arg
 
   private String description;
 
-  private Type type;
+  private Type[] types;
 
   private Arg(Type type, String description, Opt... options)
   {
+    this(new Type[] { type }, description, options);
+  }
+
+  private Arg(Type[] type, String description, Opt... options)
+  {
     this(type, null, description, false, options);
   }
 
   private Arg(Type type, String description, boolean defaultBoolean,
           Opt... options)
   {
+    this(new Type[] { type }, description, defaultBoolean, options);
+  }
+
+  private Arg(Type[] type, String description, boolean defaultBoolean,
+          Opt... options)
+  {
     this(type, null, description, defaultBoolean, options);
   }
 
   private Arg(Type type, String alternativeName, String description,
           Opt... options)
   {
+    this(new Type[] { type }, alternativeName, description, options);
+  }
+
+  private Arg(Type[] type, String alternativeName, String description,
+          Opt... options)
+  {
     this(type, alternativeName, description, false, options);
   }
 
   private Arg(Type type, String alternativeName, String description,
           boolean defaultBoolean, Opt... options)
   {
-    this.type = type;
+    this(new Type[] { type }, alternativeName, description, defaultBoolean,
+            options);
+  }
+
+  private Arg(Type[] type, String alternativeName, String description,
+          boolean defaultBoolean, Opt... options)
+  {
+    this.types = type;
     this.description = description;
     this.defaultBoolValue = defaultBoolean;
     this.setOptions(options);
@@ -596,7 +602,11 @@ public enum Arg
     if (getNames().length > 0)
       sb.append('"');
     sb.append(")\n");
-    sb.append("\nType: " + type.name());
+    for (Type type : getTypes())
+    {
+      String typeName = type.name();
+      sb.append("\nType: " + typeName);
+    }
     sb.append("\nOpt: ");
     // map List<Opt> to List<String> for the String.join
     List<String> optList = Arrays.asList(argOptions).stream()
@@ -661,9 +671,37 @@ public enum Arg
     return defaultBoolValue;
   }
 
-  public Type getType()
+  public Type getFirstType()
   {
-    return this.type;
+    return this.getTypes()[0];
+  }
+
+  public Type[] getTypes()
+  {
+    return this.types;
+  }
+
+  public boolean sharesType(Arg a)
+  {
+    return this.hasType(a.getTypes());
+  }
+
+  public boolean hasType(Type... types)
+  {
+    Set<Type> typesSet = new HashSet<>(Arrays.asList(types));
+    return this.hasType(typesSet);
+  }
+
+  public boolean hasType(Set<Type> typesSet)
+  {
+    for (Type type : getTypes())
+    {
+      if (typesSet.contains(type))
+      {
+        return true;
+      }
+    }
+    return false;
   }
 
   protected String getDescription()
@@ -690,9 +728,17 @@ public enum Arg
   public static final void appendUsageGeneral(StringBuilder sb,
           int maxArgLength)
   {
+    Set<Type> firstTypes = new HashSet<>();
+    for (Arg a : EnumSet.allOf(Arg.class))
+    {
+      if (!firstTypes.contains(a.getFirstType()))
+      {
+        firstTypes.add(a.getFirstType());
+      }
+    }
     for (Type t : EnumSet.allOf(Type.class))
     {
-      if (t.description() != null)
+      if (t.description() != null && firstTypes.contains(t))
       {
         StringBuilder argSb = new StringBuilder();
         argSb.append(Arg.HELP.argString()).append(ArgParser.SINGLEDASH)
@@ -727,20 +773,6 @@ public enum Arg
     {
       List<Arg> args = argsSortedForDisplay(types);
 
-      /*
-       * just use a set maxArgLength of DESCRIPTIONINDENT
-       
-      int maxArgLength = 0;
-      for (Arg a : args)
-      {
-        if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
-          continue;
-      
-        String argS = argDisplayString(a);
-        if (argS.length() > maxArgLength)
-          maxArgLength = argS.length();
-      }
-      */
       int maxArgLength = DESCRIPTIONINDENT;
 
       // always show --help
@@ -764,10 +796,10 @@ public enum Arg
           continue;
         }
 
-        if (a.getType() != typeSection)
+        if (a.getFirstType() != typeSection)
         {
-          typeSection = a.getType();
-          String typeDescription = a.getType().description();
+          typeSection = a.getFirstType();
+          String typeDescription = a.getFirstType().description();
           if (typeDescription != null && typeDescription.length() > 0)
           {
             // typeDescription = typeDescription.substring(0,
@@ -958,7 +990,7 @@ public enum Arg
   {
     Opt[] opts = options == null ? new Opt[] {} : options;
     return EnumSet.allOf(Arg.class).stream().filter(a -> {
-      if (a.getType() != type)
+      if (!a.hasType(type))
         return false;
       for (Opt o : opts)
       {
@@ -1013,7 +1045,7 @@ class ArgDisplayComparator implements Comparator<Arg>
     if (b == null)
       return -1;
     // first compare types (in enum order)
-    int i = a.getType().compareTo(b.getType());
+    int i = a.getFirstType().compareTo(b.getFirstType());
     if (i != 0)
       return i;
     // do Opt.LAST next (oddly). Reversed args important!
index 155f69e..12e1b1d 100644 (file)
@@ -365,6 +365,8 @@ public class ArgParser
         List<String> globVals = null; // for Opt.GLOB only
         SubVals globSubVals = null; // also for use by Opt.GLOB only
         String linkedId = null;
+        String givenLinkedId = null; // this is preserved to add to each
+                                     // "ArgValue"
         Type type = null;
 
         // look for equals e.g. --arg=value
@@ -385,6 +387,7 @@ public class ArgParser
         if (idOpen > -1 && idClose == argName.length() - 1)
         {
           linkedId = argName.substring(idOpen + 1, idClose);
+          givenLinkedId = linkedId;
           argName = argName.substring(0, idOpen);
         }
 
@@ -595,6 +598,12 @@ public class ArgParser
             {
               linkedId = MATCHALLLINKEDIDS;
             }
+            if (allLinkedIds)
+            {
+              // user has made conscious decision for these args to apply to
+              // all, so set givenLinkedId too
+              givenLinkedId = linkedId;
+            }
             else if (a.hasOption(Opt.ALLOWMULTIID)
                     && this.storedLinkedIds != null
                     && this.storedLinkedIds.size() > 0)
@@ -661,7 +670,7 @@ public class ArgParser
 
         // set allstructures to all non-primary structure options in this linked
         // id if --allstructures has been set
-        if (allStructures && (a.getType() == Type.STRUCTURE
+        if (allStructures && (a.hasType(Type.STRUCTURE)
         // || a.getType() == Type.STRUCTUREIMAGE)
         ) && !a.hasOption(Opt.PRIMARY))
         {
@@ -686,7 +695,8 @@ public class ArgParser
             {
               String v = gve.nextElement();
               SubVals vsv = new SubVals(globSubVals, v);
-              addValue(linkedId, type, avs, vsv, v, argIndex++, true);
+              addValue(linkedId, givenLinkedId, type, avs, vsv, v,
+                      argIndex++, true);
               // if we're using defaultLinkedId and the arg increments the
               // counter:
               if (gve.hasMoreElements() && usingDefaultLinkedId
@@ -703,18 +713,19 @@ public class ArgParser
           else
           {
             // addValue(linkedId, type, avs, val, argIndex, true);
-            addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
-                    val, argIndex, true);
+            addValue(linkedId, givenLinkedId, type, avs,
+                    addNewSubVals ? subvals : null, val, argIndex, true);
           }
         }
         else if (a.hasOption(Opt.BOOLEAN))
         {
-          setBoolean(linkedId, type, avs, !negated, argIndex);
+          setBoolean(linkedId, givenLinkedId, type, avs, !negated,
+                  argIndex);
           setNegated(linkedId, avs, negated);
         }
         else if (a.hasOption(Opt.UNARY))
         {
-          setBoolean(linkedId, type, avs, true, argIndex);
+          setBoolean(linkedId, givenLinkedId, type, avs, true, argIndex);
         }
 
         // remove the '*' or 'open*' linkedId that should be empty if it was
@@ -1055,30 +1066,30 @@ public class ArgParser
   /**
    * This version inserts the subvals sv into all created values
    */
-  private void addValue(String linkedId, Type type, ArgValues avs,
-          SubVals sv, String v, int argIndex, boolean doSubs)
+  private void addValue(String linkedId, String givenLinkedId, Type type,
+          ArgValues avs, SubVals sv, String v, int argIndex, boolean doSubs)
   {
-    this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
-            argIndex, doSubs);
+    this.argValueOperation(Op.ADDVALUE, linkedId, givenLinkedId, type, avs,
+            sv, v, false, argIndex, doSubs);
   }
 
-  private void setBoolean(String linkedId, Type type, ArgValues avs,
-          boolean b, int argIndex)
+  private void setBoolean(String linkedId, String givenLinkedId, Type type,
+          ArgValues avs, boolean b, int argIndex)
   {
-    this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
-            b, argIndex, false);
+    this.argValueOperation(Op.SETBOOLEAN, linkedId, givenLinkedId, type,
+            avs, null, null, b, argIndex, false);
   }
 
   private void setNegated(String linkedId, ArgValues avs, boolean b)
   {
-    this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
-            b, 0, false);
+    this.argValueOperation(Op.SETNEGATED, linkedId, null, null, avs, null,
+            null, b, 0, false);
   }
 
   private void incrementCount(String linkedId, ArgValues avs)
   {
-    this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
-            null, false, 0, false);
+    this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, null, avs,
+            null, null, false, 0, false);
   }
 
   private enum Op
@@ -1086,13 +1097,13 @@ public class ArgParser
     ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
   }
 
-  private void argValueOperation(Op op, String linkedId, Type type,
-          ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
-          boolean doSubs)
+  private void argValueOperation(Op op, String linkedId,
+          String givenLinkedId, Type type, ArgValues avs, SubVals sv,
+          String v, boolean b, int argIndex, boolean doSubs)
   {
     // default to merge subvals if subvals are provided
-    argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
-            doSubs);
+    argValueOperation(op, linkedId, givenLinkedId, type, avs, sv, true, v,
+            b, argIndex, doSubs);
   }
 
   /**
@@ -1122,9 +1133,9 @@ public class ArgParser
    * @param doSubs
    *          Whether to perform substitutions on the subvals and value.
    */
-  private void argValueOperation(Op op, String linkedId, Type type,
-          ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
-          int argIndex, boolean doSubs)
+  private void argValueOperation(Op op, String linkedId,
+          String givenLinkedId, Type type, ArgValues avs, SubVals sv,
+          boolean merge, String v, boolean b, int argIndex, boolean doSubs)
   {
     Arg a = avs.arg();
 
@@ -1155,15 +1166,16 @@ public class ArgParser
     // linkedIds
     if (wildcardLinkedIds != null)
     {
-      for (String id : wildcardLinkedIds)
+      for (String matchedLinkedId : wildcardLinkedIds)
       {
         // skip incorrectly stored wildcard ids!
-        if (id == null || MATCHALLLINKEDIDS.equals(id)
-                || MATCHOPENEDLINKEDIDS.equals(id))
+        if (matchedLinkedId == null
+                || MATCHALLLINKEDIDS.equals(matchedLinkedId)
+                || MATCHOPENEDLINKEDIDS.equals(matchedLinkedId))
         {
           continue;
         }
-        ArgValuesMap avm = linkedArgs.get(id);
+        ArgValuesMap avm = linkedArgs.get(matchedLinkedId);
         // don't set an output if there isn't an input
         if (a.hasOption(Opt.REQUIREINPUT)
                 && !avm.hasArgWithOption(Opt.INPUT))
@@ -1180,24 +1192,24 @@ public class ArgParser
             if (doSubs)
             {
               sv = new SubVals(sv, val, merge);
-              val = makeSubstitutions(sv.getContent(), id);
+              val = makeSubstitutions(sv.getContent(), matchedLinkedId);
             }
-            tavs.addValue(sv, type, val, argIndex, true);
+            tavs.addValue(sv, type, val, argIndex, true, givenLinkedId);
           }
           else
           {
             if (doSubs)
             {
-              val = makeSubstitutions(v, id);
+              val = makeSubstitutions(v, matchedLinkedId);
             }
-            tavs.addValue(type, val, argIndex, true);
+            tavs.addValue(type, val, argIndex, true, givenLinkedId);
           }
-          finaliseStoringArgValue(id, tavs);
+          finaliseStoringArgValue(matchedLinkedId, tavs);
           break;
 
         case SETBOOLEAN:
-          tavs.setBoolean(type, b, argIndex, true);
-          finaliseStoringArgValue(id, tavs);
+          tavs.setBoolean(type, b, argIndex, true, givenLinkedId);
+          finaliseStoringArgValue(matchedLinkedId, tavs);
           break;
 
         case SETNEGATED:
@@ -1228,7 +1240,7 @@ public class ArgParser
             val = makeSubstitutions(v, linkedId);
             sv = new SubVals(sv, val);
           }
-          avs.addValue(sv, type, val, argIndex, false);
+          avs.addValue(sv, type, val, argIndex, false, givenLinkedId);
         }
         else
         {
@@ -1236,13 +1248,13 @@ public class ArgParser
           {
             val = makeSubstitutions(v, linkedId);
           }
-          avs.addValue(type, val, argIndex, false);
+          avs.addValue(type, val, argIndex, false, givenLinkedId);
         }
         finaliseStoringArgValue(linkedId, avs);
         break;
 
       case SETBOOLEAN:
-        avs.setBoolean(type, b, argIndex, false);
+        avs.setBoolean(type, b, argIndex, false, givenLinkedId);
         finaliseStoringArgValue(linkedId, avs);
         break;
 
index eab38ca..e7ee1a2 100644 (file)
@@ -34,6 +34,12 @@ public class ArgValue implements Comparable<ArgValue>
 
   private String value;
 
+  private String givenLinkedId = null;
+
+  private String assignedLinkedId = null;
+
+  private boolean setByWildcardLinkedId = false;
+
   /*
    * Type type is only really used by --help-type
    */
@@ -48,22 +54,31 @@ public class ArgValue implements Comparable<ArgValue>
   private SubVals subVals;
 
   protected ArgValue(Arg a, SubVals sv, Type type, String content,
-          int argIndex)
+          int argIndex, boolean setByWildcardLinkedId, String givenLinkedId,
+          String assignedLinkedId)
   {
     this.arg = a;
     this.value = content;
     this.argIndex = argIndex;
     this.subVals = sv == null ? new SubVals("") : sv;
     this.setType(type);
+    this.setByWildcardLinkedId = setByWildcardLinkedId;
+    this.givenLinkedId = givenLinkedId;
+    this.assignedLinkedId = assignedLinkedId;
   }
 
-  protected ArgValue(Arg a, Type type, String value, int argIndex)
+  protected ArgValue(Arg a, Type type, String value, int argIndex,
+          boolean setByWildcardLinkedId, String givenLinkedId,
+          String assignedLinkedId)
   {
     this.arg = a;
     this.argIndex = argIndex;
     this.subVals = new SubVals(value);
     this.value = getSubVals().getContent();
     this.setType(type);
+    this.setByWildcardLinkedId = setByWildcardLinkedId;
+    this.givenLinkedId = givenLinkedId;
+    this.assignedLinkedId = assignedLinkedId;
   }
 
   protected void setType(Type t)
@@ -124,4 +139,37 @@ public class ArgValue implements Comparable<ArgValue>
   {
     return this.getArgIndex() - o.getArgIndex();
   }
+
+  @Override
+  public String toString()
+  {
+    StringBuilder sb = new StringBuilder();
+    sb.append(this.getArg().argString());
+    sb.append("=");
+    if (!this.getSubVals().getSubValMap().isEmpty())
+    {
+      sb.append(this.getSubVals().toString());
+    }
+    sb.append("'");
+    sb.append(this.getValue());
+    sb.append("'");
+
+    return sb.toString();
+  }
+
+  public String getGivenLinkedId()
+  {
+    return this.givenLinkedId;
+  }
+
+  public String getAssignedLinkedId()
+  {
+    return this.assignedLinkedId;
+  }
+
+  public boolean setByWildcardLinkedId()
+  {
+    // looking for deliberately user set wildcard
+    return this.setByWildcardLinkedId && this.getGivenLinkedId() != null;
+  }
 }
\ No newline at end of file
index 8e4fe2c..55e1211 100644 (file)
@@ -44,6 +44,8 @@ public class ArgValues
 
   private boolean setByWildcard = false;
 
+  private String givenLinkedId = null;
+
   private int boolIndex = -1;
 
   private List<Integer> argsIndexes;
@@ -52,16 +54,19 @@ public class ArgValues
 
   private Map<String, ArgValue> idMap = new HashMap<>();
 
+  private ArgValuesMap avm;
+
   /*
    * Type type is only really used by --help-type
    */
   private Type type = null;
 
-  protected ArgValues(Arg a)
+  protected ArgValues(Arg a, ArgValuesMap avm)
   {
     this.arg = a;
     this.argValueList = new ArrayList<ArgValue>();
     this.boolValue = arg.getDefaultBoolValue();
+    this.avm = avm;
   }
 
   protected boolean setByWildcard()
@@ -114,7 +119,7 @@ public class ArgValues
   }
 
   protected void setBoolean(Type t, boolean b, int i,
-          boolean beingSetByWildcard)
+          boolean beingSetByWildcard, String givenLinkedId)
   {
     this.setType(t);
     // don't overwrite a wildcard set boolean with a non-wildcard set boolean
@@ -123,6 +128,7 @@ public class ArgValues
     this.boolValue = b;
     this.boolIndex = i;
     this.setSetByWildcard(beingSetByWildcard);
+    this.givenLinkedId = givenLinkedId;
   }
 
   protected boolean getBoolean()
@@ -157,15 +163,17 @@ public class ArgValues
   }
 
   protected void addValue(Type type, String val, int argIndex,
-          boolean wildcard)
+          boolean wildcard, String givenLinkedId)
   {
-    addArgValue(new ArgValue(arg(), type, val, argIndex), wildcard);
+    addArgValue(new ArgValue(arg(), type, val, argIndex, wildcard,
+            givenLinkedId, avm.getLinkedId()), wildcard);
   }
 
   protected void addValue(SubVals sv, Type type, String content,
-          int argIndex, boolean wildcard)
+          int argIndex, boolean wildcard, String givenLinkedId)
   {
-    addArgValue(new ArgValue(arg(), sv, type, content, argIndex), wildcard);
+    addArgValue(new ArgValue(arg(), sv, type, content, argIndex, wildcard,
+            givenLinkedId, avm.getLinkedId()), wildcard);
   }
 
   protected void addArgValue(ArgValue av, boolean beingSetByWildcard)
@@ -238,4 +246,11 @@ public class ArgValues
     }
     return false;
   }
+
+  public boolean setByWildcardLinkedId()
+  {
+    // looking for deliberately user set wildcard
+    return this.setByWildcard && this.givenLinkedId != null;
+  }
+
 }
\ No newline at end of file
index fc1e090..7385db0 100644 (file)
@@ -22,8 +22,10 @@ package jalview.bin.argparser;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -77,9 +79,13 @@ public class ArgValuesMap
   private void newArg(Arg a)
   {
     if (m == null)
+    {
       newMap();
+    }
     if (!containsArg(a))
-      m.put(a, new ArgValues(a));
+    {
+      m.put(a, new ArgValues(a, this));
+    }
   }
 
   public ArgValues getArgValues(Arg a)
@@ -101,6 +107,112 @@ public class ArgValuesMap
     return avs == null ? new ArrayList<>() : avs.getArgValueList();
   }
 
+  public List<ArgValue> getArgValueListFromSubValOrArg(ArgValue av, Arg a,
+          SubVals sv)
+  {
+    return getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
+            null, a, Position.AFTER, av, sv, null, null, null, true, null);
+  }
+
+  public List<ArgValue> getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
+          ArgParser ap, Arg a, ArgValuesMap.Position pos, ArgValue av,
+          SubVals sv, String key, String pref, String def,
+          boolean withinTypes, Type type)
+  {
+    if (key == null)
+    {
+      key = a.getName();
+    }
+    Set<Type> types = new HashSet<>();
+    if (type == null)
+    {
+      types.addAll(Arrays.asList(av.getArg().getTypes()));
+    }
+    else
+    {
+      types.add(type);
+    }
+    List<ArgValue> avList = new ArrayList<>();
+    if (sv != null && sv.has(key) && sv.get(key) != null)
+    {
+      String value = ap == null ? sv.get(key)
+              : sv.getWithSubstitutions(ap, getLinkedId(), key);
+      // protected ArgValue(Arg a, SubVals sv, Type type, String content, int
+      // argIndex)
+
+      ArgValue svav = new ArgValue(a, null, null, value, av.getArgIndex(),
+              false, null, this.getLinkedId());
+      avList.add(svav);
+    }
+    else if (containsArg(a))
+    {
+      if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
+        avList.add(getArgValue(a));
+      else if (pos == ArgValuesMap.Position.BEFORE
+              && getClosestPreviousArgValueOfArg(av, a) != null)
+      {
+        for (ArgValue tmpAv : getArgValues(a).getArgValueList())
+        {
+          if (tmpAv.getArgIndex() >= av.getArgIndex())
+          {
+            continue;
+          }
+          avList.add(tmpAv);
+        }
+      }
+      else if (pos == ArgValuesMap.Position.AFTER
+              && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
+      {
+        for (ArgValue tmpAv : getArgValues(a).getArgValueList())
+        {
+          if (tmpAv.getArgIndex() <= av.getArgIndex())
+          {
+            continue;
+          }
+          avList.add(tmpAv);
+        }
+      }
+    }
+
+    // check if withinType the avs don't belong to the next primary arg
+    // of this type. Checking for *any* shared type.
+    if (withinTypes && !avList.isEmpty())
+    {
+      int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
+      // run through every Arg used in this ArgValuesMap
+      for (Arg tmpA : this.getArgKeys())
+      {
+        // only interested in looking up to next Opt.PRIMARY args of the same
+        // type as av (or provided type)
+        if (tmpA.hasType(types) && tmpA.hasOption(Opt.PRIMARY))
+        {
+          for (ArgValue tmpAv : getArgValueList(tmpA))
+          {
+            int tmpArgIndex = tmpAv.getArgIndex();
+            if (tmpArgIndex > av.getArgIndex()
+                    && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
+            {
+              nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
+            }
+          }
+        }
+      }
+      List<ArgValue> tmpList = new ArrayList<>();
+      for (ArgValue tmpAv : avList)
+      {
+        int tmpAvIndex = tmpAv.getArgIndex();
+        if (av.getArgIndex() < tmpAvIndex
+                && tmpAvIndex < nextPrimaryArgOfSameTypeIndex)
+        {
+          tmpList.add(tmpAv);
+        }
+      }
+      avList = tmpList;
+    }
+
+    return avList;
+  }
+
   public ArgValue getArgValue(Arg a)
   {
     List<ArgValue> vals = getArgValueList(a);
@@ -113,6 +225,25 @@ public class ArgValuesMap
     return av == null ? null : av.getValue();
   }
 
+  public List<String> getValues(Arg a)
+  {
+    return toValues(getArgValueList(a));
+  }
+
+  public static List<String> toValues(List<ArgValue> avl)
+  {
+    if (avl == null)
+    {
+      return null;
+    }
+    List<String> vl = new ArrayList<>();
+    for (ArgValue av : avl)
+    {
+      vl.add(av.getValue());
+    }
+    return vl;
+  }
+
   public boolean containsArg(Arg a)
   {
     if (m == null || !m.containsKey(a))
@@ -193,7 +324,7 @@ public class ArgValuesMap
   }
 
   public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a,
-          boolean withinType)
+          boolean withinTypes)
   {
     // this looks for the *next* arg that *might* be referring back to
     // a thisAv. Such an arg would have no subValues (if it does it should
@@ -215,14 +346,14 @@ public class ArgValuesMap
     }
 
     // check if withinType this closestAv doesn't belong to the next primary arg
-    // of this type
-    if (withinType && closestAv != null)
+    // of this type. Checking for *any* shared type.
+    if (withinTypes && closestAv != null)
     {
       int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
       for (Arg tmpA : this.getArgKeys())
       {
         // interested in Opt.PRIMARY args of the same type
-        if (tmpA.getType() == a.getType() && tmpA.hasOption(Opt.PRIMARY))
+        if (tmpA.sharesType(a) && tmpA.hasOption(Opt.PRIMARY))
         {
           for (ArgValue tmpAv : getArgValueList(tmpA))
           {
@@ -237,7 +368,7 @@ public class ArgValuesMap
       }
       if (nextPrimaryArgOfSameTypeIndex < closestAv.getArgIndex())
       {
-        // looks licke closestAv actually belongs to a different primary Arg
+        // looks like closestAv actually belongs to a different primary Arg
         return null;
       }
     }
@@ -370,33 +501,32 @@ public class ArgValuesMap
   public String getFromSubValArgOrPref(ArgValue av, Arg a, SubVals sv,
           String key, String pref, String def)
   {
-    return getFromSubValArgOrPref(a, ArgValuesMap.Position.AFTER, av, sv,
-            key, pref, def);
+    return getFromSubValArgOrPref(a, Position.AFTER, av, sv, key, pref,
+            def);
   }
 
   /**
    * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
    * Arg of type a or subval key or preference pref or default def
    */
-  public String getFromSubValArgOrPref(Arg a, ArgValuesMap.Position pos,
-          ArgValue av, SubVals sv, String key, String pref, String def)
+  public String getFromSubValArgOrPref(Arg a, Position pos, ArgValue av,
+          SubVals sv, String key, String pref, String def)
   {
     return getFromSubValArgOrPrefWithSubstitutions(null, a, pos, av, sv,
             key, pref, def);
   }
 
   public String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, Arg a,
-          ArgValuesMap.Position pos, ArgValue av, SubVals sv, String key,
-          String pref, String def)
+          Position pos, ArgValue av, SubVals sv, String key, String pref,
+          String def)
   {
-    return getFromSubValArgOrPrefWithSubstitutionsWithinType(ap, a, pos, av,
-            sv, key, pref, def, true);
+    return getFromSubValArgOrPrefWithSubstitutionsWithinTypes(ap, a, pos,
+            av, sv, key, pref, def, true);
   }
 
-  public String getFromSubValArgOrPrefWithSubstitutionsWithinType(
-          ArgParser ap, Arg a, ArgValuesMap.Position pos, ArgValue av,
-          SubVals sv, String key, String pref, String def,
-          boolean withinType)
+  public String getFromSubValArgOrPrefWithSubstitutionsWithinTypes(
+          ArgParser ap, Arg a, Position pos, ArgValue av, SubVals sv,
+          String key, String pref, String def, boolean withinTypes)
   {
     if (key == null)
       key = a.getName();
@@ -414,14 +544,14 @@ public class ArgValuesMap
               && getClosestPreviousArgValueOfArg(av, a) != null)
         value = getClosestPreviousArgValueOfArg(av, a).getValue();
       else if (pos == ArgValuesMap.Position.AFTER
-              && getClosestNextArgValueOfArg(av, a, withinType) != null)
-        value = getClosestNextArgValueOfArg(av, a, withinType).getValue();
+              && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
+        value = getClosestNextArgValueOfArg(av, a, withinTypes).getValue();
 
       // look for allstructures subval for Type.STRUCTURE
       Arg arg = av.getArg();
       if (value == null && arg.hasOption(Opt.PRIMARY)
-              && arg.getType() == Type.STRUCTURE
-              && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
+              && arg.hasType(Type.STRUCTURE) && !a.hasOption(Opt.PRIMARY)
+              && (a.getFirstType() == Type.STRUCTURE
               // || a.getType() == Type.STRUCTUREIMAGE))
               ))
       {
@@ -432,6 +562,18 @@ public class ArgValuesMap
           value = av2.getValue();
         }
       }
+
+      if (value == null)
+      {
+        // look for --all --a occurrences
+        for (ArgValue tmpAv : this.getArgValueList(a))
+        {
+          if (tmpAv.setByWildcardLinkedId())
+          {
+            value = tmpAv.getValue();
+          }
+        }
+      }
     }
     if (value == null)
     {
@@ -511,6 +653,24 @@ public class ArgValuesMap
     return pref != null ? (invertPref ? !prefVal : prefVal) : def;
   }
 
+  @Override
+  public String toString()
+  {
+    StringBuilder sb = new StringBuilder();
+    for (Arg a : this.getArgKeys())
+    {
+      sb.append(a.argString());
+      sb.append(":\n");
+      for (ArgValue av : this.getArgValueList(a))
+      {
+        sb.append("  ");
+        sb.append(av.getValue());
+        sb.append("\n");
+      }
+    }
+    return sb.toString();
+  }
+
   public class ArgInfo implements Comparable<ArgInfo>
   {
     private Arg arg;
index 80e08c5..51f8147 100644 (file)
@@ -160,10 +160,12 @@ public class BootstrapArgs
               argsOptions.add(opt);
             }
           }
-          Type t = a.getType();
-          if (!argsTypes.contains(t))
+          for (Type t : a.getTypes())
           {
-            argsTypes.add(t);
+            if (!argsTypes.contains(t))
+            {
+              argsTypes.add(t);
+            }
           }
         }
 
index d3f80ce..0f5a9e8 100644 (file)
@@ -42,6 +42,7 @@ import java.io.FileWriter;
 import java.io.PrintWriter;
 import java.util.List;
 
+import javax.swing.BoundedRangeModel;
 import javax.swing.SwingUtilities;
 
 import jalview.analysis.AnnotationSorter;
@@ -387,7 +388,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
     // Also check annotation label widths
     if (includeAnnotations && al.getAlignmentAnnotation() != null)
     {
-      fm = c.getFontMetrics(getAlabels().getFont());
+      if (legacy)
+      {
+        fm = c.getFontMetrics(getAlabels().getFont());
+      }
 
       if (!legacy || Jalview.isHeadlessMode())
       {
@@ -789,6 +793,22 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
+   * Answers true if the panel has no horizontal scrollbar, or the scrollbar is
+   * at its rightmost position, else false.
+   * 
+   * @return
+   */
+  boolean isScrolledFullyRight()
+  {
+    if (hscroll == null)
+    {
+      return true;
+    }
+    BoundedRangeModel model = hscroll.getModel();
+    return (model.getExtent() + model.getValue() >= model.getMaximum());
+  }
+
+  /**
    * Respond to adjustment event when horizontal or vertical scrollbar is
    * changed
    * 
@@ -1283,8 +1303,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
       return idwidth.intValue() + ID_WIDTH_PADDING;
     }
 
-    int w = getIdPanel().getWidth();
-    w = calculateIdWidth(-1, true, true).width;
+    // int w = getIdPanel().getWidth();
+    // w = calculateIdWidth(-1, true, true).width;
+    int w = calculateIdWidth(-1, true, true).width;
     return (w > 0 ? w : calculateIdWidth().width);
   }
 
@@ -1362,7 +1383,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
         // need to obtain default alignment width and then add in any
         // additional allowance for id margin
         // this duplicates the calculation in getWrappedHeight but adjusts for
-        // offscreen idWith
+        // offscreen idWidth
         width = alignFrame.getWidth() - vscroll.getPreferredSize().width
                 - alignFrame.getInsets().left - alignFrame.getInsets().right
                 - getVisibleIdWidth() + getVisibleIdWidth(false);
index 94f8790..7c5afcd 100755 (executable)
@@ -793,9 +793,15 @@ public class AnnotationLabels extends JPanel
     {
       Dimension d = ap.annotationScroller.getPreferredSize();
       int dif = evt.getY() - oldY;
+      dif -= dif % ap.av.getCharHeight();
 
-      dif /= ap.av.getCharHeight();
-      dif *= ap.av.getCharHeight();
+      // don't allow setting an annotation panel height larger than visible
+      // (otherwise you can't get back)
+      if (d.height - dif > ap.idPanelHolder.getHeight()
+              - ap.getIdSpaceFillerPanel1().getHeight())
+      {
+        return;
+      }
 
       if ((d.height - dif) > 20)
       {
@@ -1206,8 +1212,8 @@ public class AnnotationLabels extends JPanel
     if (ap != null)
     {
       iwa = ap.idwidthAdjuster;
-      if ((Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
-              || Jalview.isHeadlessMode()))
+      if (Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
+              || Jalview.isHeadlessMode())
       {
         Graphics2D g2d = (Graphics2D) g;
         Graphics dummy = g2d.create();
index 68ceff0..ea32684 100755 (executable)
@@ -192,21 +192,27 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     renderer = new AnnotationRenderer();
   }
 
+  /**
+   * Responds to a mouse wheel movement by scrolling the annotations up or down.
+   * Annotation labels are scrolled via method adjustmentValueChanged when the
+   * vertical scrollbar is adjusted.
+   * <p>
+   * If shift is pressed, then scrolling is left or right instead, and is
+   * delegated to AlignmentPanel, so that both sequences and annotations are
+   * scrolled together.
+   * <p>
+   * This object is a MouseWheelListener to AnnotationLabels, so mouse wheel
+   * events over the labels are delegated to this method.
+   * <p>
+   * Note that this method may also be fired by scrolling with a gesture on a
+   * trackpad.
+   */
   @Override
   public void mouseWheelMoved(MouseWheelEvent e)
   {
     if (e.isShiftDown())
     {
-      e.consume();
-      double wheelRotation = e.getPreciseWheelRotation();
-      if (wheelRotation > 0)
-      {
-        av.getRanges().scrollRight(true);
-      }
-      else if (wheelRotation < 0)
-      {
-        av.getRanges().scrollRight(false);
-      }
+      ap.getSeqPanel().mouseWheelMoved(e);
     }
     else
     {
index 0aa4878..4d0de1b 100644 (file)
@@ -421,6 +421,14 @@ public class AppJmol extends StructureViewerBase
 
   }
 
+  public boolean isRepainting()
+  {
+    if (renderPanel!=null && renderPanel.isVisible())
+    {
+      return renderPanel.repainting;
+    }
+    return false;
+  }
   /**
    * Outputs the Jmol viewer image as an image file, after prompting the user to
    * choose a file and (for EPS) choice of Text or Lineart character rendering
@@ -431,6 +439,13 @@ public class AppJmol extends StructureViewerBase
   @Override
   public void makePDBImage(ImageMaker.TYPE type)
   {
+    while (!isRepainting())
+    {
+      try {
+        Thread.sleep(2);
+      } catch (Exception q)
+      {}
+    }
     try
     {
       makePDBImage(null, type, null,
@@ -627,10 +642,17 @@ public class AppJmol extends StructureViewerBase
       }
       else
       {
-        jmb.jmolViewer.renderScreenImage(g, currentSize.width,
-                currentSize.height);
+        repainting=true;
+        synchronized (jmb)
+        {
+          jmb.jmolViewer.renderScreenImage(g, currentSize.width,
+                  currentSize.height);
+          
+        }
+        repainting=false;
       }
     }
+    volatile boolean repainting=false;
   }
 
   @Override
index 35c7818..bbd4dae 100644 (file)
@@ -680,22 +680,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     // configure services
     StructureSelectionManager ssm = StructureSelectionManager
             .getStructureSelectionManager(this);
-    if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
-    {
-      ssm.setAddTempFacAnnot(
-              Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
-      ssm.setProcessSecondaryStructure(
-              Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
-      // JAL-3915 - RNAView is no longer an option so this has no effect
-      ssm.setSecStructServices(
-              Cache.getDefault(Preferences.USE_RNAVIEW, false));
-    }
-    else
-    {
-      ssm.setAddTempFacAnnot(false);
-      ssm.setProcessSecondaryStructure(false);
-      ssm.setSecStructServices(false);
-    }
+    StructureSelectionManager.doConfigureStructurePrefs(ssm);
   }
 
   public void checkForNews()
index 7b491e4..3fff67b 100755 (executable)
@@ -26,8 +26,6 @@ import java.awt.event.ActionListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.event.MouseWheelListener;
 import java.util.List;
 
 import javax.swing.JPanel;
@@ -50,12 +48,9 @@ import jalview.viewmodel.ViewportRanges;
 /**
  * This panel hosts alignment sequence ids and responds to mouse clicks on them,
  * as well as highlighting ids matched by a search from the Find menu.
- * 
- * @author $author$
- * @version $Revision$
  */
 public class IdPanel extends JPanel
-        implements MouseListener, MouseMotionListener, MouseWheelListener
+        implements MouseListener, MouseMotionListener
 {
   private IdCanvas idCanvas;
 
@@ -90,7 +85,7 @@ public class IdPanel extends JPanel
     add(getIdCanvas(), BorderLayout.CENTER);
     addMouseListener(this);
     addMouseMotionListener(this);
-    addMouseWheelListener(this);
+    addMouseWheelListener(alignPanel.getSeqPanel());
     ToolTipManager.sharedInstance().registerComponent(this);
   }
 
@@ -171,38 +166,6 @@ public class IdPanel extends JPanel
   }
 
   /**
-   * Response to the mouse wheel by scrolling the alignment panel.
-   */
-  @Override
-  public void mouseWheelMoved(MouseWheelEvent e)
-  {
-    e.consume();
-    double wheelRotation = e.getPreciseWheelRotation();
-    if (wheelRotation > 0)
-    {
-      if (e.isShiftDown())
-      {
-        av.getRanges().scrollRight(true);
-      }
-      else
-      {
-        av.getRanges().scrollUp(false);
-      }
-    }
-    else if (wheelRotation < 0)
-    {
-      if (e.isShiftDown())
-      {
-        av.getRanges().scrollRight(false);
-      }
-      else
-      {
-        av.getRanges().scrollUp(true);
-      }
-    }
-  }
-
-  /**
    * Handle a mouse click event. Currently only responds to a double-click. The
    * action is to try to open a browser window at a URL that searches for the
    * selected sequence id. The search URL is configured in Preferences |
index 99a39d4..029f184 100755 (executable)
@@ -132,7 +132,7 @@ public class IdwidthAdjuster extends JPanel
     /*
      * don't drag below minimum width
      */
-    if (newWidth < MIN_ID_WIDTH)
+    if (newWidth < MIN_ID_WIDTH || newWidth > this.ap.getWidth())
     {
       return;
     }
index 2caea17..845004b 100644 (file)
@@ -2221,32 +2221,85 @@ public class SeqPanel extends JPanel
     }
   }
 
+  /**
+   * Responds to a mouse wheel movement by scrolling the alignment
+   * <ul>
+   * <li>left or right, if the shift key is down, else up or down</li>
+   * <li>right (or down) if the reported mouse movement is positive</li>
+   * <li>left (or up) if the reported mouse movement is negative</li>
+   * </ul>
+   * Note that this method may also be fired by scrolling with a gesture on a
+   * trackpad.
+   */
   @Override
   public void mouseWheelMoved(MouseWheelEvent e)
   {
     e.consume();
     double wheelRotation = e.getPreciseWheelRotation();
+
+    /*
+     * scroll more for large (fast) mouse movements
+     */
+    int size = 1 + (int) Math.abs(wheelRotation);
+
     if (wheelRotation > 0)
     {
       if (e.isShiftDown())
       {
-        av.getRanges().scrollRight(true);
-
+        /*
+         * scroll right
+         * stop trying to scroll right when limit is reached (saves
+         * expensive calls to Alignment.getWidth())
+         */
+        while (size-- > 0 && !ap.isScrolledFullyRight())
+        {
+          if (!av.getRanges().scrollRight(true))
+          {
+            break;
+          }
+        }
       }
       else
       {
-        av.getRanges().scrollUp(false);
+        /*
+         * scroll down
+         */
+        while (size-- > 0)
+        {
+          if (!av.getRanges().scrollUp(false))
+          {
+            break;
+          }
+        }
       }
     }
     else if (wheelRotation < 0)
     {
       if (e.isShiftDown())
       {
-        av.getRanges().scrollRight(false);
+        /*
+         * scroll left
+         */
+        while (size-- > 0)
+        {
+          if (!av.getRanges().scrollRight(false))
+          {
+            break;
+          }
+        }
       }
       else
       {
-        av.getRanges().scrollUp(true);
+        /*
+         * scroll up
+         */
+        while (size-- > 0)
+        {
+          if (!av.getRanges().scrollUp(true))
+          {
+            break;
+          }
+        }
       }
     }
 
index 1060d8a..465bc0d 100755 (executable)
@@ -36,6 +36,7 @@ import javax.swing.JLabel;
 import javax.swing.JLayeredPane;
 import javax.swing.JPanel;
 import javax.swing.JTextPane;
+import javax.swing.SwingUtilities;
 import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkListener;
 
@@ -341,8 +342,20 @@ public class SplashScreen extends JPanel
     }
     try
     {
-
-      iframe.setClosed(true); // ##### implicated BLOCKED
+      final JInternalFrame frme = iframe;
+      SwingUtilities.invokeLater(new Runnable()
+      {
+        @Override
+        public void run()
+        {
+          try
+          {
+            frme.setClosed(true);
+          } catch (Exception ex)
+          {
+          }
+        }
+      });
     } catch (Exception ex)
     {
     }
index 666ff74..6132908 100644 (file)
@@ -1826,6 +1826,7 @@ public class StructureChooser extends GStructureChooser
     if (ssm == null)
     {
       ssm = ap.getStructureSelectionManager();
+      StructureSelectionManager.doConfigureStructurePrefs(ssm);
     }
 
     PDBEntry fileEntry = new AssociatePdbFileWithSeq().associatePdbWithSeq(
index 2f0c75e..2d6f569 100755 (executable)
@@ -145,4 +145,9 @@ public class GAlignmentPanel extends JPanel
     hscrollHolder.add(hscrollFillerPanel, BorderLayout.WEST);
     this.add(sequenceHolderPanel, BorderLayout.CENTER);
   }
+
+  public JPanel getIdSpaceFillerPanel1()
+  {
+    return idSpaceFillerPanel1;
+  }
 }
index 86fc2de..e6fc683 100644 (file)
@@ -34,6 +34,7 @@ import java.util.Vector;
 
 import jalview.analysis.AlignSeq;
 import jalview.api.StructureSelectionManagerProvider;
+import jalview.bin.Cache;
 import jalview.bin.Console;
 import jalview.commands.CommandI;
 import jalview.commands.EditCommand;
@@ -51,6 +52,7 @@ import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceI;
 import jalview.ext.jmol.JmolParser;
 import jalview.gui.IProgressIndicator;
+import jalview.gui.Preferences;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
@@ -1716,4 +1718,34 @@ public class StructureSelectionManager
     return pdbIdFileName;
   }
 
+  public static void doConfigureStructurePrefs(
+          StructureSelectionManager ssm)
+  {
+    doConfigureStructurePrefs(ssm,
+            Cache.getDefault(Preferences.ADD_SS_ANN, true),
+            Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true),
+            Cache.getDefault(Preferences.STRUCT_FROM_PDB, true),
+            Cache.getDefault(Preferences.USE_RNAVIEW, false));
+  }
+
+  public static void doConfigureStructurePrefs(
+          StructureSelectionManager ssm, boolean add_ss_ann,
+          boolean add_tempfact_ann, boolean struct_from_pdb,
+          boolean use_rnaview)
+  {
+    if (add_ss_ann)
+    {
+      ssm.setAddTempFacAnnot(add_tempfact_ann);
+      ssm.setProcessSecondaryStructure(struct_from_pdb);
+      // JAL-3915 - RNAView is no longer an option so this has no effect
+      ssm.setSecStructServices(use_rnaview);
+    }
+    else
+    {
+      ssm.setAddTempFacAnnot(false);
+      ssm.setProcessSecondaryStructure(false);
+      ssm.setSecStructServices(false);
+    }
+  }
+
 }
index 9ed98bd..24128ea 100644 (file)
 
 package jalview.util;
 
-import java.util.Locale;
-
 import java.awt.Color;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
 
@@ -234,6 +233,12 @@ public class ColorUtils
     colour = colour.trim();
 
     Color col = null;
+
+    if ("random".equals(colour))
+    {
+      return generateRandomColor(null);
+    }
+
     try
     {
       int value = Integer.parseInt(colour, 16);
@@ -259,7 +264,8 @@ public class ColorUtils
           int b = Integer.parseInt(tokens[2].trim());
           col = new Color(r, g, b);
         }
-      } catch (Exception ex)
+      } catch (IllegalArgumentException ex) // IllegalArgumentException includes
+                                            // NumberFormatException
       {
         // non-numeric token or out of 0-255 range
       }
index 69ee379..299224d 100644 (file)
@@ -68,7 +68,7 @@ import jalview.xml.binding.uniprot.PropertyType;
  */
 public class Uniprot extends DbSourceProxyImpl
 {
-  private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org";
+  private static final String DEFAULT_UNIPROT_DOMAIN = "https://rest.uniprot.org";
 
   private static final String BAR_DELIMITER = "|";
 
@@ -144,7 +144,7 @@ public class Uniprot extends DbSourceProxyImpl
               "(UNIPROT\\|?|UNIPROT_|UNIREF\\d+_|UNIREF\\d+\\|?)", "");
       AlignmentI al = null;
 
-      String downloadstring = getDomain() + "/uniprot/" + queries + ".xml";
+      String downloadstring = getDomain() + "/uniprotkb/" + queries + ".xml";
 
       URL url = new URL(downloadstring);
       HttpURLConnection urlconn = (HttpURLConnection) url.openConnection();
index 77cbd92..3855dc7 100644 (file)
@@ -71,7 +71,7 @@ public class CommandLineOperations
    * @author jimp
    * 
    */
-  private static class Worker extends Thread
+  public static class Worker extends Thread
   {
     private final Process process;
 
@@ -156,7 +156,7 @@ public class CommandLineOperations
     return classpath;
   }
 
-  private Worker getJalviewDesktopRunner(boolean withAwt, String cmd,
+  public static Worker getJalviewDesktopRunner(boolean withAwt, String cmd,
           int timeout)
   {
     // Note: JAL-3065 - don't include quotes for lib/* because the arguments are
@@ -183,7 +183,7 @@ public class CommandLineOperations
               new InputStreamReader(ls2_proc.getInputStream()));
       BufferedReader errorReader = new BufferedReader(
               new InputStreamReader(ls2_proc.getErrorStream()));
-      worker = new Worker(ls2_proc);
+      worker = new CommandLineOperations.Worker(ls2_proc);
       worker.start();
       try
       {
index 42b93a9..7ca4e55 100644 (file)
  */
 package jalview.bin;
 
+import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
 import java.nio.file.Files;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
+import javax.imageio.ImageIO;
 import javax.swing.SwingUtilities;
 
 import org.testng.Assert;
@@ -93,7 +94,12 @@ public class CommandsTest
 
   public static void callJalviewMain(String[] args)
   {
-    if (Jalview.getInstance() != null)
+    callJalviewMain(args, false);
+  }
+
+  public static void callJalviewMain(String[] args, boolean newJalview)
+  {
+    if (Jalview.getInstance() != null && !newJalview)
     {
       Jalview.getInstance().doMain(args);
     }
@@ -116,7 +122,12 @@ public class CommandsTest
   }
   */
 
-  @Test(groups = { "Functional", "testTask3" }, dataProvider = "cmdLines")
+  @Test(
+    groups =
+    { "Functional", "testTask3" },
+    dataProvider = "cmdLines",
+    singleThreaded = true)
+
   public void commandsOpenTest(String cmdLine, boolean cmdArgs,
           int numFrames, String[] sequences)
   {
@@ -168,32 +179,62 @@ public class CommandsTest
   @Test(
     groups =
     { "Functional", "testTask3" },
-    dataProvider = "structureImageOutputFiles")
+    dataProvider = "structureImageOutputFiles",
+    singleThreaded = true)
   public void structureImageOutputTest(String cmdLine, String[] filenames)
           throws IOException
   {
     cleanupFiles(filenames);
-    String[] args = (cmdLine + " --gui").split("\\s+");
+    String[] args = (cmdLine).split("\\s+");
     try
     {
-      callJalviewMain(args);
+      callJalviewMain(args, true);
       Commands cmds = Jalview.getInstance().getCommands();
       Assert.assertNotNull(cmds);
-      File lastFile = null;
-      for (String filename : filenames)
+      verifyIncreasingSize(cmdLine, filenames);
+    } catch (Exception x)
+    {
+      Assert.fail("Unexpected exception during structureImageOutputTest",
+              x);
+    } finally
+    {
+      cleanupFiles(filenames);
+      tearDown();
+    }
+  }
+
+  /**
+   * given two command lines, compare the output files produced - they should
+   * exist and be equal in size
+   */
+  @Test(
+    groups =
+    { "Functional", "testTask3" },
+    dataProvider = "compareHeadlessAndGUIOps",
+    singleThreaded = true)
+  public void headlessOrGuiImageOutputTest(String[] cmdLines,
+          String[] filenames) throws IOException
+  {
+    cleanupFiles(filenames);
+    try
+    {
+      for (String cmdLine : cmdLines)
       {
-        File file = new File(filename);
-        Assert.assertTrue(file.exists(), "File '" + filename
-                + "' was not created by '" + cmdLine + "'");
-        Assert.assertTrue(file.isFile(), "File '" + filename
-                + "' is not a file from '" + cmdLine + "'");
-        Assert.assertTrue(Files.size(file.toPath()) > 0, "File '" + filename
-                + "' has no content from '" + cmdLine + "'");
-        // make sure the successive output files get bigger!
-        if (lastFile != null)
-          Assert.assertTrue(Files.size(file.toPath()) > Files
-                  .size(lastFile.toPath()));
+        CommandLineOperations.Worker runner = CommandLineOperations
+                .getJalviewDesktopRunner(false, cmdLine, 1000);
+        long timeOut = 10000;
+        while (runner.isAlive() && timeOut > 0)
+        {
+          Thread.sleep(25);
+          timeOut -= 25;
+        }
       }
+
+      verifyOrderedFileSet(cmdLines[0] + " vs " + cmdLines[1], filenames,
+              false);
+
+      verifySimilarEnoughImages(cmdLines[0] + " vs " + cmdLines[1],
+              filenames, 0f, 0f);
     } catch (Exception x)
     {
       Assert.fail("Unexpected exception during structureImageOutputTest",
@@ -205,7 +246,162 @@ public class CommandsTest
     }
   }
 
-  @Test(groups = "Functional", dataProvider = "argfileOutputFiles")
+  @DataProvider(name = "compareHeadlessAndGUIOps")
+  public Object[][] compareHeadlessAndGUIOps()
+  {
+    return new Object[][] {
+        new Object[]
+        { new String[] { "--open examples/uniref50.fa "
+                + "--structure [seqid=FER1_SPIOL,tempfac=plddt,showssannotations,structureviewer=jmol]"
+                + "examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json --image="
+                + testfiles + "/"
+                + "test-al-pae-ss-gui.png --overwrite --gui --quit",
+            "--open examples/uniref50.fa "
+                    + "--structure [seqid=FER1_SPIOL,tempfac=plddt,showssannotations,structureviewer=jmol]"
+                    + "examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                    + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json --image="
+                    + testfiles + "/"
+                    + "test-al-pae-ss-nogui.png --overwrite --nogui" },
+            new String[]
+            { testfiles + "/test-al-pae-ss-gui.png",
+                testfiles + "/test-al-pae-ss-nogui.png", } } };
+  }
+
+  private static void verifyIncreasingSize(String cmdLine,
+          String[] filenames) throws Exception
+  {
+    verifyOrderedFileSet(cmdLine, filenames, true);
+  }
+
+  private static void verifyOrderedFileSet(String cmdLine,
+          String[] filenames, boolean increasingSize) throws Exception
+  {
+    File lastFile = null;
+    for (String filename : filenames)
+    {
+      File file = new File(filename);
+      Assert.assertTrue(file.exists(), "File '" + filename
+              + "' was not created by '" + cmdLine + "'");
+      Assert.assertTrue(file.isFile(), "File '" + filename
+              + "' is not a file from '" + cmdLine + "'");
+      Assert.assertTrue(Files.size(file.toPath()) > 0, "File '" + filename
+              + "' has no content from '" + cmdLine + "'");
+      // make sure the successive output files get bigger!
+      if (lastFile != null)
+      {
+        waitForLastWrite(file, 25);
+
+        if (increasingSize)
+        {
+          Assert.assertTrue(
+                  Files.size(file.toPath()) > Files.size(lastFile.toPath()),
+                  "Expected " + file.toPath() + " to be larger than "
+                          + lastFile.toPath());
+        }
+        else
+        {
+          Assert.assertEquals(Files.size(file.toPath()),
+                  Files.size(lastFile.toPath()),
+                  "New file " + file.toPath()
+                          + " (actual size) not same as last file's size "
+                          + lastFile.toString());
+        }
+      }
+      // remember it for next file
+      lastFile = file;
+    }
+
+  }
+
+  private static void verifySimilarEnoughImages(String cmdLine,
+          String[] filenames, float w_tolerance_pc, float h_tolerance_pc)
+          throws Exception
+  {
+    int min_w = -1;
+    int max_w = -1;
+    int min_h = -1;
+    int max_h = -1;
+    for (String filename : filenames)
+    {
+      File file = new File(filename);
+      Assert.assertTrue(file.exists(), "File '" + filename
+              + "' was not created by '" + cmdLine + "'");
+      Assert.assertTrue(file.isFile(), "File '" + filename
+              + "' is not a file from '" + cmdLine + "'");
+      Assert.assertTrue(Files.size(file.toPath()) > 0, "File '" + filename
+              + "' has no content from '" + cmdLine + "'");
+
+      BufferedImage img = ImageIO.read(file);
+      if (img.getWidth() < min_w || min_w == -1)
+      {
+        min_w = img.getWidth();
+      }
+      if (img.getWidth() > max_w || max_w == -1)
+      {
+        max_w = img.getWidth();
+      }
+      if (img.getHeight() < min_h || min_h == -1)
+      {
+        min_h = img.getHeight();
+      }
+      if (img.getHeight() > max_h || max_h == -1)
+      {
+        max_h = img.getHeight();
+      }
+    }
+    Assert.assertTrue(min_w > 0,
+            "Minimum width is not positive (" + min_w + ")");
+    Assert.assertTrue(max_w > 0,
+            "Maximum width is not positive (" + max_w + ")");
+    Assert.assertTrue(min_h > 0,
+            "Minimum height is not positive (" + min_h + ")");
+    Assert.assertTrue(max_h > 0,
+            "Maximum height is not positive (" + max_h + ")");
+    // tolerance
+    Assert.assertTrue(100 * (max_w - min_w) / min_w <= w_tolerance_pc,
+            "Width variation (" + (max_w - min_w)
+                    + " not within tolerance (" + w_tolerance_pc
+                    + "%) of minimum width (" + min_w + ")");
+    if (max_w != min_w)
+    {
+      System.out.println("Widths within tolerance (" + w_tolerance_pc
+              + "%), min_w=" + min_w + " < max_w=" + max_w);
+    }
+    Assert.assertTrue(100 * (max_h - min_h) / min_h <= h_tolerance_pc,
+            "Height variation (" + (max_h - min_h)
+                    + " not within tolerance (" + h_tolerance_pc
+                    + "%) of minimum height (" + min_h + ")");
+    if (max_h != min_h)
+    {
+      System.out.println("Heights within tolerance (" + h_tolerance_pc
+              + "%), min_h=" + min_h + " < max_h=" + max_h);
+    }
+  }
+
+  private static long waitForLastWrite(File file, int i) throws IOException
+  {
+    long lastSize, stableSize = Files.size(file.toPath());
+    // wait around until we are sure the file has been completely written.
+    do
+    {
+      lastSize = stableSize;
+      try
+      {
+        Thread.sleep(i);
+      } catch (Exception x)
+      {
+      }
+      stableSize = Files.size(file.toPath());
+    } while (stableSize != lastSize);
+    return stableSize;
+  }
+
+  @Test(
+    groups = "Functional",
+    dataProvider = "argfileOutputFiles",
+    singleThreaded = true)
+
   public void argFilesGlobAndSubstitutionsTest(String cmdLine,
           String[] filenames) throws IOException
   {
@@ -228,8 +424,15 @@ public class CommandsTest
                 + "' has no content from '" + cmdLine + "'");
         // make sure the successive output files get bigger!
         if (lastFile != null)
+        {
           Assert.assertTrue(Files.size(file.toPath()) > Files
                   .size(lastFile.toPath()));
+          System.out.println("this file: " + file + " +"
+                  + Files.size(file.toPath()) + " greater than "
+                  + Files.size(lastFile.toPath()));
+        }
+        // remember it for next file
+        lastFile = file;
       }
     } catch (Exception x)
     {
@@ -248,36 +451,60 @@ public class CommandsTest
   {
     return new Object[][] {
         //
+        /*
+                */
         { "--gui --nonews --nosplash --open=./examples/test_fab41.result/sample.a2m "
                 + "--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb "
-                + "--structureimage=" + testfiles + "/structureimage1.png "
+                + "--structureimage=" + testfiles
+                + "/structureimage0-1.png "
                 + "--open=./examples/test_fab41.result/sample.a2m "
                 + "--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb "
                 + "--structureimage=" + testfiles
-                + "/structureimage2.png --scale=1.5 "
+                + "/structureimage0-2.png --scale=1.5 "
                 + "--open=./examples/test_fab41.result/sample.a2m "
                 + "--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb "
                 + "--structureimage=" + testfiles
-                + "/structureimage3.png --scale=2.0 ",
+                + "/structureimage0-3.png --scale=2.0 ",
             new String[]
-            { testfiles + "/structureimage1.png",
-                testfiles + "/structureimage2.png",
-                testfiles + "/structureimage3.png" } },
+            { testfiles + "/structureimage0-1.png",
+                testfiles + "/structureimage0-2.png",
+                testfiles + "/structureimage0-3.png" } },
         { "--headless --noquit --open=./examples/test_fab41.result/sample.a2m "
                 + "--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb "
-                + "--structureimage=" + testfiles + "/structureimage1.png "
+                + "--structureimage=" + testfiles
+                + "/structureimage1-1.png "
                 + "--open=./examples/test_fab41.result/sample.a2m "
                 + "--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb "
                 + "--structureimage=" + testfiles
-                + "/structureimage2.png --scale=1.5 "
+                + "/structureimage1-2.png --scale=1.5 "
                 + "--open=./examples/test_fab41.result/sample.a2m "
                 + "--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb "
                 + "--structureimage=" + testfiles
-                + "/structureimage3.png --scale=2.0 ",
+                + "/structureimage1-3.png --scale=2.0 ",
+            new String[]
+            { testfiles + "/structureimage1-1.png",
+                testfiles + "/structureimage1-2.png",
+                testfiles + "/structureimage1-3.png" } },
+        { "--gui --nonews --nosplash --open examples/1gaq.txt --append ./examples/3W5V.pdb "
+                + "--structure examples/1gaq.txt --seqid \"1GAQ|A\" "
+                + "--structureimage " + testfiles
+                + "/structureimage2-1gaq.png --structure examples/3W5V.pdb "
+                + "--seqid \"3W5V|A\" --structureimage " + testfiles
+                + "/structureimage2-3w5v.png --overwrite",
+
+            new String[]
+            { testfiles + "/structureimage2-3w5v.png",
+                testfiles + "/structureimage2-1gaq.png", } },
+        { "--headless --noquit --open ./examples/1gaq.txt --append ./examples/3W5V.pdb "
+                + "--structure examples/1gaq.txt --seqid \"1GAQ|A\" "
+                + "--structureimage " + testfiles
+                + "/structureimage3-1gaq.png --structure examples/3W5V.pdb "
+                + "--seqid \"3W5V|A\" --structureimage " + testfiles
+                + "/structureimage3-3w5v.png --overwrite",
+
             new String[]
-            { testfiles + "/structureimage1.png",
-                testfiles + "/structureimage2.png",
-                testfiles + "/structureimage3.png" } },
+            { testfiles + "/structureimage3-3w5v.png",
+                testfiles + "/structureimage3-1gaq.png", } }
         /*
                 */
         //
@@ -633,4 +860,105 @@ public class CommandsTest
     };
   }
 
+  @Test(
+    groups =
+    { "Functional", "testTask3" },
+    dataProvider = "structureImageAnnotationsOutputFiles",
+    singleThreaded = true)
+  public void structureImageAnnotationsOutputTest(String cmdLine,
+          String filename, int height) throws IOException
+  {
+    cleanupFiles(new String[] { filename });
+    String[] args = (cmdLine).split("\\s+");
+    callJalviewMain(args, true); // Create new instance of Jalview each time for
+                                 // linkedIds
+    BufferedImage img = ImageIO.read(new File(filename));
+    Assert.assertEquals(height, img.getHeight(), "Output image '" + filename
+            + "' is not in the expected height range, possibly because of the wrong number of annotations");
+
+    cleanupFiles(new String[] { filename });
+    tearDown();
+  }
+
+  @DataProvider(name = "structureImageAnnotationsOutputFiles")
+  public Object[][] structureImageAnnotationsOutputFiles()
+  {
+    String filename = "test/jalview/bin/argparser/testfiles/test_annotations.png";
+    return new Object[][] {
+        // MUST use --noquit with --headless to avoid a System.exit()
+        { "--noquit --headless --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--noshowssannotations " + "--noshowannotations", //
+            filename, //
+            252 }, //
+        { "--noquit --headless --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--showssannotations " + "--noshowannotations", //
+            filename, //
+            368 }, //
+        { "--noquit --headless --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--noshowssannotations " + "--showannotations", //
+            filename, //
+            524 }, //
+        { "--noquit --headless --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--showssannotations " + "--showannotations", //
+            filename, //
+            660 }, //
+        { "--gui --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--noshowssannotations " + "--noshowannotations", //
+            filename, //
+            252 }, //
+        { "--gui --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--showssannotations " + "--noshowannotations", //
+            filename, //
+            368 }, //
+        { "--gui --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--noshowssannotations " + "--showannotations", //
+            filename, //
+            524 }, //
+        { "--gui --nonews --nosplash --open=./examples/uniref50.fa "
+                + "--structure=examples/AlphaFold/AF-P00221-F1-model_v4.pdb "
+                + "--seqid=FER1_SPIOL --structureviewer=jmol "
+                + "--paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--image=" + filename + " " + "--tempfac=plddt "
+                + "--overwrite " //
+                + "--showssannotations " + "--showannotations", //
+            filename, //
+            660 }, //
+    };
+  }
+
 }
index 442812e..89c034a 100644 (file)
@@ -22,9 +22,13 @@ package jalview.bin.argparser;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Properties;
 
+import org.apache.logging.log4j.util.Strings;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
@@ -32,6 +36,7 @@ import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import jalview.bin.Cache;
+import jalview.bin.Console;
 import jalview.gui.Desktop;
 
 @Test(singleThreaded = true)
@@ -452,4 +457,366 @@ public class ArgParserTest
     };
   }
 
+  @Test(groups = "Functional", dataProvider = "argsWithinTypesData")
+  public void checkArgsWithinTypesTest(String commandLineArgs,
+          Object[] stuff)
+  {
+    String linkedId = "JALVIEW:0";
+    String[] args = commandLineArgs.split("\\s+");
+    ArgParser argparser = new ArgParser(args);
+    ArgValuesMap avm = argparser.getLinkedArgs(linkedId);
+
+    ArgAndValues avals = (ArgAndValues) stuff[0];
+
+    Object[] moreStuff = (Object[]) stuff[1];
+
+    ArgAndValues[] subAv = (ArgAndValues[]) moreStuff[0];
+
+    Object[] secondaryArgAndAuxStuff = (Object[]) stuff[2];
+
+    // Now look at the argparser
+
+    Arg primaryArg = avals.arg;
+
+    List<ArgValue> parsed_ArgValues = avm.getArgValueList(primaryArg);
+
+    Assert.assertTrue(
+            areEqualSets(avals.values,
+                    ArgValuesMap.toValues(parsed_ArgValues)),
+            "Primary arg (" + primaryArg.toString()
+                    + ") does not have the expected values. Expected "
+                    + avals.values + " and got "
+                    + ArgValuesMap.toValues(parsed_ArgValues));
+
+    for (int i = 0; i < parsed_ArgValues.size(); i++)
+    {
+      ArgValue parsed_ArgValue = parsed_ArgValues.get(i);
+      String value = avals.values.get(i);
+
+      Console.debug("- primary arg '" + primaryArg + "' = '" + value + "'");
+
+      Assert.assertEquals(parsed_ArgValue.getValue(), value,
+              "Primary arg value not as expected");
+
+      ArgAndValues[] aux_avals = (ArgAndValues[]) moreStuff[i];
+
+      for (ArgAndValues aux_aval : aux_avals)
+      {
+        Arg auxArg = aux_aval.arg;
+        List<String> auxValues = aux_aval.values;
+
+        String parsed_auxValue = avm.getFromSubValArgOrPref(auxArg,
+                ArgValuesMap.Position.AFTER, parsed_ArgValue, null, null,
+                null, null);
+
+        if (auxValues.isEmpty())
+        {
+          Assert.assertTrue(parsed_auxValue == null,
+                  "Not expecting to parse a value for '" + auxArg
+                          + "' but found '" + parsed_auxValue + "'");
+        }
+
+        for (String auxValue : auxValues)
+        {
+
+          Console.debug("- + primary aux arg '" + auxArg + "' = '"
+                  + auxValue + "'");
+
+          Assert.assertEquals(parsed_auxValue, auxValue,
+                  "Primary auxiliary arg (" + auxArg.toString()
+                          + ") values do not match");
+
+        }
+      }
+
+      // Now for the secondary args
+      Object[] secondaryStuff = (Object[]) secondaryArgAndAuxStuff[i];
+      ArgAndValues secondaryArgAndValues = (ArgAndValues) secondaryStuff[0];
+      Arg secondaryArg = secondaryArgAndValues.arg;
+      List<String> secondaryValues = secondaryArgAndValues.values;
+
+      List<ArgValue> parsed_secondaryArgValues = avm
+              .getArgValueListFromSubValOrArg(parsed_ArgValue, secondaryArg,
+                      null);
+
+      Assert.assertTrue(
+              areEqualSets(secondaryValues,
+                      ArgValuesMap.toValues(parsed_secondaryArgValues)),
+              "Secondary arg (" + secondaryArg.toString()
+                      + ") does not have the expected values");
+
+      Object[] secondaryMoreStuff = (Object[]) secondaryStuff[1];
+
+      for (int j = 0; j < parsed_secondaryArgValues.size(); j++)
+      {
+        ArgValue parsed_secondaryArgValue = parsed_secondaryArgValues
+                .get(j);
+        String secondary_value = secondaryValues.get(j);
+
+        Console.debug("-- secondary arg '" + secondaryArg + "' = '"
+                + secondary_value + "'");
+
+        Assert.assertEquals(parsed_secondaryArgValue.getValue(),
+                secondary_value, "Secondary arg value not as expected");
+
+        ArgAndValues[] secondary_aux_avals = (ArgAndValues[]) secondaryMoreStuff[j];
+
+        for (ArgAndValues secondary_aux_aval : secondary_aux_avals)
+        {
+          Arg secondary_auxArg = secondary_aux_aval.arg;
+          List<String> secondary_auxValues = secondary_aux_aval.values;
+
+          String parsed_secondary_auxValue = avm.getValueFromSubValOrArg(
+                  parsed_secondaryArgValue, secondary_auxArg, null);
+
+          if (secondary_auxValues.isEmpty())
+          {
+            Assert.assertTrue(parsed_secondary_auxValue == null,
+                    "Not expecting to parse a value for '"
+                            + secondary_auxArg + "' but found '"
+                            + parsed_secondary_auxValue + "'");
+          }
+
+          for (String secondary_auxValue : secondary_auxValues)
+          {
+            Console.debug("-- + secondary aux arg '" + secondary_auxArg
+                    + "' for value '" + secondary_auxValue + "'");
+
+            Assert.assertEquals(parsed_secondary_auxValue,
+                    secondary_auxValue,
+                    "Secondary auxiliary arg ("
+                            + secondary_auxArg.toString()
+                            + ") values do not match");
+          }
+        }
+      }
+    }
+  }
+
+  @DataProvider(name = "argsWithinTypesData")
+  public Object[][] argsWithinTypesData()
+  {
+    return new Object[][] {
+        /*
+         * { cmdline args },
+         * {
+         *   { Primary Arg, Secondary Args },
+         *   { { Secondary Arg => { Values } } }
+         *  },
+         */
+        /*
+        */
+        { //
+          // commandLineArgs
+            "--open=alignment.fa --structure=structure0.pdb"
+                    // structureimage0a and structureimage args
+                    + " --structureimage=image0a.png --bgcolour=bg0a --imagecolour=col0a"
+                    // structureimage0b and structureimage args
+                    + " --structureimage=image0b.png --bgcolour=bg0b --imagecolour=col0b"
+                    // more structure args
+                    + " --structureviewer=sv0 --paematrix=pae0" //
+                    // structure1
+                    + " --structure=structure1.pdb"
+                    // structure args
+                    + " --structureviewer=sv1 --paematrix=pae1"
+                    // structureimage1a with NO structureimage args
+                    // (see `--all --bgcolour=pineapple` later)
+                    + " --structureimage=image1a.png"
+                    // structureimage1b and structureimage args
+                    + " --structureimage=image1b.png --bgcolour=bg1b --imagecolour=col1b"
+                    // --all args, should apply to structureimage1a only
+                    + " --all --bgcolour=pineapple" //
+            ,
+            // stuff
+            new Object[]
+            {
+                // avals (0) and (1)
+                av(Arg.STRUCTURE, "structure0.pdb", "structure1.pdb"),
+                // moreStuff (0) and (1)
+                new ArgAndValues[][]
+                { //
+                    { av(Arg.STRUCTUREVIEWER, "sv0"),
+                        av(Arg.PAEMATRIX, "pae0") },
+                    { av(Arg.STRUCTUREVIEWER, "sv1"),
+                        av(Arg.PAEMATRIX, "pae1") } //
+                },
+                // secondaryArgAndAuxStuff
+                // (same size as values of avals)
+                new Object[][]
+                {
+                    // secondaryStuff (0)
+                    {
+                        // secondaryArgAndValues (a) and (b)
+                        av(Arg.STRUCTUREIMAGE, "image0a.png",
+                                "image0b.png"),
+                        // secondaryMoreStuff
+                        // (same size as values of secondaryArgAndValues)
+                        new ArgAndValues[][]
+                        {
+                            // secondary_aux_avals (a)
+                            { av(Arg.BGCOLOUR, "bg0a"),
+                                av(Arg.IMAGECOLOUR, "col0a") },
+                            // secondary_aux_avals (b)
+                            { av(Arg.BGCOLOUR, "bg0b"),
+                                av(Arg.IMAGECOLOUR, "col0b") }, //
+                        }, //
+                    },
+                    // secondaryStuff (1)
+                    {
+                        // secondaryArgAndValues (a) and (b)
+                        av(Arg.STRUCTUREIMAGE, "image1a.png",
+                                "image1b.png"),
+                        // secondaryMoreStuff
+                        new ArgAndValues[][]
+                        {
+                            // secondary_aux_avals (a)
+                            { av(Arg.BGCOLOUR, "pineapple"),
+                                av(Arg.IMAGECOLOUR) },
+                            // secondary_aux_avals (b)
+                            { av(Arg.BGCOLOUR, "bg1b"),
+                                av(Arg.IMAGECOLOUR, "col1b") }, //
+                        }, //
+                    }, //
+                } //
+            } //
+        }, //
+        { //
+            "--open=alignment.fa --wrap --colour=gecose-flower"
+                    // structure0
+                    + " --structure=structure0.pdb"
+                    + " --structureimage=image0a.png --bgcolour=bg0a --scale=3"
+                    + " --structureimage=image0b.png --imagecolour=col0b --scale=4"
+                    + " --structureviewer=sv0 --paematrix=pae0" //
+                    + " --structureimage=image0c.png"
+                    // structure1
+                    + " --structure=structure1.pdb"
+                    + " --structureviewer=sv1 --paematrix=pae1"
+                    + " --structureimage=image1a.png"
+                    + " --structureimage=image1b.png --bgcolour=bg1b --imagecolour=col1b"
+                    + " --structureimage=image1c.png --bgcolour=bg1c --imagecolour=col1c --scale=5"
+                    + " --structureimage=image1d.png --imagecolour=col1d --scale=6"
+                    + " --structureimage=image1e.png --bgcolour=bg1e"
+                    // structure2
+                    + " --structure=structure2.pdb"
+                    + " --structureimage=image2a.png --bgcolour=bg2a --scale=23"
+                    + " --all --bgcolour=pineapple --imagecolour=banana --scale=2" //
+            ,
+            // stuff
+            new Object[]
+            { av(Arg.STRUCTURE, "structure0.pdb", "structure1.pdb",
+                    "structure2.pdb"),
+                new ArgAndValues[][]
+                { //
+                    { av(Arg.STRUCTUREVIEWER, "sv0"),
+                        av(Arg.PAEMATRIX, "pae0") },
+                    { av(Arg.STRUCTUREVIEWER, "sv1"),
+                        av(Arg.PAEMATRIX, "pae1") },
+                    { av(Arg.STRUCTUREVIEWER), av(Arg.PAEMATRIX) } //
+                }, //
+                new Object[][]
+                { //
+                    { //
+                        av(Arg.STRUCTUREIMAGE, "image0a.png", "image0b.png",
+                                "image0c.png"), //
+                        new ArgAndValues[][]
+                        { //
+                            { av(Arg.BGCOLOUR, "bg0a"),
+                                av(Arg.IMAGECOLOUR, "banana"),
+                                av(Arg.SCALE, "3") },
+                            { av(Arg.BGCOLOUR, "pineapple"),
+                                av(Arg.IMAGECOLOUR, "col0b"),
+                                av(Arg.SCALE, "4") },
+                            { av(Arg.BGCOLOUR, "pineapple"),
+                                av(Arg.IMAGECOLOUR, "banana"),
+                                av(Arg.SCALE, "2") }, //
+                        }, //
+                    }, //
+                    { //
+                        av(Arg.STRUCTUREIMAGE, "image1a.png", "image1b.png",
+                                "image1c.png", "image1d.png",
+                                "image1e.png"),
+                        new ArgAndValues[][]
+                        { //
+                            { av(Arg.BGCOLOUR, "pineapple"),
+                                av(Arg.IMAGECOLOUR, "banana"),
+                                av(Arg.SCALE, "2") },
+                            { av(Arg.BGCOLOUR, "bg1b"),
+                                av(Arg.IMAGECOLOUR, "col1b"),
+                                av(Arg.SCALE, "2") },
+                            { av(Arg.BGCOLOUR, "bg1c"),
+                                av(Arg.IMAGECOLOUR, "col1c"),
+                                av(Arg.SCALE, "5") },
+                            { av(Arg.BGCOLOUR, "pineapple"),
+                                av(Arg.IMAGECOLOUR, "col1d"),
+                                av(Arg.SCALE, "6") },
+                            { av(Arg.BGCOLOUR, "bg1e"),
+                                av(Arg.IMAGECOLOUR, "banana"),
+                                av(Arg.SCALE, "2") } //
+                        }, //
+                    }, //
+                    { //
+                        av(Arg.STRUCTUREIMAGE, "image2a.png"),
+                        new ArgAndValues[][]
+                        { //
+                            { av(Arg.BGCOLOUR, "bg2a"),
+                                av(Arg.IMAGECOLOUR, "banana"),
+                                av(Arg.SCALE, "23") }, //
+                        }, //
+                    }, //
+                } //
+            } //
+        } //
+    };
+  }
+
+  protected ArgAndValues av(Arg a, String... vals)
+  {
+    return new ArgAndValues(a, vals);
+  }
+
+  protected class ArgAndValues
+  {
+    protected Arg arg;
+
+    protected List<String> values;
+
+    ArgAndValues(Arg a, String... vals)
+    {
+      arg = a;
+      values = vals == null ? new ArrayList<String>() : Arrays.asList(vals);
+    }
+
+    @Override
+    public String toString()
+    {
+      StringBuilder sb = new StringBuilder();
+      sb.append(arg.argString());
+      sb.append("\n");
+      sb.append(Strings.join(values, ','));
+      sb.append("\n");
+      return sb.toString();
+    }
+  }
+
+  private static boolean areEqualSets(String[] strArray,
+          List<String> strList)
+  {
+    return areEqualSets(Arrays.asList(strArray), strList);
+  }
+
+  private static boolean areEqualSets(List<String> l1, List<String> l2)
+  {
+    if (l1 == null && l2 == null)
+    {
+      Console.info(
+              "Comparing null lists, should be okay but you might want to know");
+      return true;
+    }
+    if (l1 == null || l2 == null)
+    {
+      return false;
+    }
+    return new HashSet<String>(l1).equals(new HashSet<String>(l2));
+  }
+
 }
\ No newline at end of file