JAL-3390 StructureCommands atomspec helper, corrected Jmol atomspec/test
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 8 Aug 2019 15:01:24 +0000 (16:01 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 8 Aug 2019 15:01:24 +0000 (16:01 +0100)
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/ext/rbvi/chimera/StructureCommands.java [new file with mode: 0644]
src/jalview/structures/models/AAStructureBindingModel.java
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java

index c727baf..7acafd7 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.ext.rbvi.chimera.AtomSpecModel;
+import jalview.ext.rbvi.chimera.StructureCommands;
 import jalview.gui.IProgressIndicator;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
@@ -458,7 +459,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       }
       // System.out.println("Select regions:\n" + selectioncom.toString());
       evalStateCommand("select *; cartoons off; backbone; select ("
-              + selectioncom.toString() + "); cartoons; ");
+              + selectioncom.toString() + "); cartoons; zoom 0");
       // evalStateCommand("select *; backbone; select "+selcom.toString()+";
       // cartoons; center "+selcom.toString());
     }
@@ -515,7 +516,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   protected String[] getColourBySequenceCommands(
           String[] files, AlignmentViewPanel viewPanel)
   {
-    Map<Object, AtomSpecModel> map = buildColoursMap(viewPanel);
+    Map<Object, AtomSpecModel> map = StructureCommands.buildColoursMap(this, viewPanel);
 
     return JmolCommands.getColourBySequenceCommand(map);
   }
@@ -1430,21 +1431,19 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public void showStructures(AlignViewportI av, boolean refocus)
   {
     StringBuilder cmd = new StringBuilder(128);
-
     if (isShowAlignmentOnly())
     {
-      cmd.append("hide *;");
-
       AtomSpecModel model = getShownResidues(av);
       String atomSpec = JmolCommands.getAtomSpec(model);
 
-      cmd.append("display ").append(atomSpec);
+      cmd.append("hide *;display ").append(atomSpec)
+              .append("; select displayed");
     }
     else
     {
-      cmd.append("display *");
+      cmd.append(";display *");
     }
-    cmd.append("; cartoon");
+    cmd.append("; cartoon only");
     if (refocus)
     {
       cmd.append("; zoom 0");
index e3625fa..512080a 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.ext.rbvi.chimera.AtomSpecModel;
+import jalview.ext.rbvi.chimera.StructureCommands;
 import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
@@ -40,15 +41,13 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * Routines for generating Jmol commands for Jalview/Jmol binding another
- * cruisecontrol test.
+ * Routines for generating Jmol commands for Jalview/Jmol binding
  * 
  * @author JimP
  * 
  */
-public class JmolCommands
+public class JmolCommands extends StructureCommands
 {
-
   private static final String COMMA = ",";
 
   /**
@@ -272,20 +271,19 @@ public class JmolCommands
           sb.append(COMMA);
         }
         boolean firstRange = true;
-        for (int[] range : atomSpecModel.getRanges(model, chain))
+
+        List<int[]> rangeList = atomSpecModel.getRanges(model, chain);
+
+        if (rangeList.size() > 1)
         {
-          if (!firstRange)
-          {
-            sb.append(COMMA);
-          }
-          firstRange = false;
-          sb.append(range[0]);
-          if (range[1] != range[0])
-          {
-            sb.append("-").append(range[1]);
-          }
+          sb.append("(");
+        }
+        appendResidueRange(sb, rangeList, null, firstRange);
+        if (rangeList.size() > 1)
+        {
+          sb.append(")&");
         }
-        sb.append(":").append(chain).append("/")
+        sb.append(":").append(chain.trim()).append("/")
                 .append(String.valueOf(model + 1))
                 .append(".1");
       }
index 45b22f7..8c00e74 100644 (file)
@@ -23,27 +23,20 @@ package jalview.ext.rbvi.chimera;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.MappedFeatures;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.gui.Desktop;
-import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.ColorUtils;
-import jalview.util.Comparison;
-import jalview.util.IntRangeComparator;
 
 import java.awt.Color;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -54,7 +47,7 @@ import java.util.Map;
  * @author JimP
  * 
  */
-public class ChimeraCommands
+public class ChimeraCommands extends StructureCommands
 {
   public static final String NAMESPACE_PREFIX = "jv_";
 
@@ -123,161 +116,6 @@ public class ChimeraCommands
   }
 
   /**
-   * Build a data structure which records contiguous subsequences for each colour.
-   * From this we can easily generate the Chimera command for colour by sequence.
-   * 
-   * <pre>
-   * Color
-   *     Model number
-   *         Chain
-   *             list of start/end ranges
-   * </pre>
-   * 
-   * Ordering is by order of addition (for colours and positions), natural
-   * ordering (for models and chains)
-   * 
-   * @param ssm
-   * @param files
-   * @param sequence
-   * @param sr
-   * @param hideHiddenRegions
-   * @param viewPanel
-   * @return
-   */
-  protected static Map<Object, AtomSpecModel> buildColoursMap(
-          StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr,
-          boolean hideHiddenRegions, AlignmentViewPanel viewPanel)
-  {
-    FeatureRenderer fr = viewPanel.getFeatureRenderer();
-    FeatureColourFinder finder = new FeatureColourFinder(fr);
-    AlignViewportI viewport = viewPanel.getAlignViewport();
-    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
-    AlignmentI al = viewport.getAlignment();
-    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
-    Color lastColour = null;
-
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-    {
-      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-
-      if (mapping == null || mapping.length < 1)
-      {
-        continue;
-      }
-
-      int startPos = -1, lastPos = -1;
-      String lastChain = "";
-      for (int s = 0; s < sequence[pdbfnum].length; s++)
-      {
-        for (int sp, m = 0; m < mapping.length; m++)
-        {
-          final SequenceI seq = sequence[pdbfnum][s];
-          if (mapping[m].getSequence() == seq
-                  && (sp = al.findIndex(seq)) > -1)
-          {
-            SequenceI asp = al.getSequenceAt(sp);
-            for (int r = 0; r < asp.getLength(); r++)
-            {
-              // no mapping to gaps in sequence
-              if (Comparison.isGap(asp.getCharAt(r)))
-              {
-                continue;
-              }
-              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
-
-              if (pos < 1 || pos == lastPos)
-              {
-                continue;
-              }
-
-              Color colour = sr.getResidueColour(seq, r, finder);
-
-              /*
-               * hidden regions are shown gray or, optionally, ignored
-               */
-              if (!cs.isVisible(r))
-              {
-                if (hideHiddenRegions)
-                {
-                  continue;
-                }
-                else
-                {
-                  colour = Color.GRAY;
-                }
-              }
-
-              final String chain = mapping[m].getChain();
-
-              /*
-               * Just keep incrementing the end position for this colour range
-               * _unless_ colour, PDB model or chain has changed, or there is a
-               * gap in the mapped residue sequence
-               */
-              final boolean newColour = !colour.equals(lastColour);
-              final boolean nonContig = lastPos + 1 != pos;
-              final boolean newChain = !chain.equals(lastChain);
-              if (newColour || nonContig || newChain)
-              {
-                if (startPos != -1)
-                {
-                  addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
-                          lastPos, lastChain);
-                }
-                startPos = pos;
-              }
-              lastColour = colour;
-              lastPos = pos;
-              lastChain = chain;
-            }
-            // final colour range
-            if (lastColour != null)
-            {
-              addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
-                      lastPos, lastChain);
-            }
-            // break;
-          }
-        }
-      }
-    }
-    return colourMap;
-  }
-
-  /**
-   * Helper method to add one contiguous range to the AtomSpec model for the given
-   * value (creating the model if necessary). As used by Jalview, {@code value} is
-   * <ul>
-   * <li>a colour, when building a 'colour structure by sequence' command</li>
-   * <li>a feature value, when building a 'set Chimera attributes from features'
-   * command</li>
-   * </ul>
-   * 
-   * @param map
-   * @param value
-   * @param model
-   * @param startPos
-   * @param endPos
-   * @param chain
-   */
-  public static void addAtomSpecRange(Map<Object, AtomSpecModel> map,
-          Object value, int model, int startPos, int endPos, String chain)
-  {
-    /*
-     * Get/initialize map of data for the colour
-     */
-    AtomSpecModel atomSpec = map.get(value);
-    if (atomSpec == null)
-    {
-      atomSpec = new AtomSpecModel();
-      map.put(value, atomSpec);
-    }
-
-    atomSpec.addRange(model, startPos, endPos, chain);
-  }
-
-  /**
    * Constructs and returns Chimera commands to set attributes on residues
    * corresponding to features in Jalview. Attribute names are the Jalview feature
    * type, with a "jv_" prefix.
@@ -638,8 +476,6 @@ public class ChimeraCommands
         sb.append("|");
       }
       firstModel = false;
-      // todo use JalviewChimeraBinding.getModelSpec(model)
-      // which means this cannot be static
       sb.append(binding.getModelSpec(model)).append(":");
 
       boolean firstPositionForModel = true;
@@ -650,77 +486,24 @@ public class ChimeraCommands
 
         List<int[]> rangeList = atomSpec.getRanges(model, chain);
 
-        /*
-         * sort ranges into ascending start position order
-         */
-        Collections.sort(rangeList, IntRangeComparator.ASCENDING);
-
-        int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
-        int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
-
-        Iterator<int[]> iterator = rangeList.iterator();
-        while (iterator.hasNext())
-        {
-          int[] range = iterator.next();
-          if (range[0] <= end + 1)
-          {
-            /*
-             * range overlaps or is contiguous with the last one
-             * - so just extend the end position, and carry on
-             * (unless this is the last in the list)
-             */
-            end = Math.max(end, range[1]);
-          }
-          else
-          {
-            /*
-             * we have a break so append the last range
-             */
-            appendRange(sb, start, end, chain, firstPositionForModel);
-            firstPositionForModel = false;
-            start = range[0];
-            end = range[1];
-          }
-        }
-
-        /*
-         * and append the last range
-         */
-        if (!rangeList.isEmpty())
-        {
-          appendRange(sb, start, end, chain, firstPositionForModel);
-          firstPositionForModel = false;
-        }
+        String chainToken = " ".equals(chain) ? "." : "." + chain;
+        appendResidueRange(sb, rangeList, chainToken,
+                firstPositionForModel);
+        firstPositionForModel = false;
       }
     }
     return sb.toString();
   }
 
   /**
-   * A helper method that appends one start-end range to a Chimera atomspec
+   * Chimera atomspec requires chain to be specified for each start-end residue
+   * range, otherwise it will apply to all chains
    * 
    * @param sb
-   * @param start
-   * @param end
    * @param chain
-   * @param firstPositionForModel
    */
-  static void appendRange(StringBuilder sb, int start, int end,
-          String chain, boolean firstPositionForModel)
+  protected static void appendChainToRange(StringBuilder sb, String chain)
   {
-    if (!firstPositionForModel)
-    {
-      sb.append(",");
-    }
-    if (end == start)
-    {
-      sb.append(start);
-    }
-    else
-    {
-      sb.append(start).append("-").append(end);
-    }
-
     sb.append(".");
     if (!" ".equals(chain))
     {
index a5273f5..2bb24d9 100644 (file)
@@ -671,7 +671,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   protected String[] getColourBySequenceCommands(
           String[] files, AlignmentViewPanel viewPanel)
   {
-    Map<Object, AtomSpecModel> colourMap = buildColoursMap(viewPanel);
+    Map<Object, AtomSpecModel> colourMap = StructureCommands.buildColoursMap(this, viewPanel);
 
     return ChimeraCommands.getColourBySequenceCommand(colourMap, this);
   }
diff --git a/src/jalview/ext/rbvi/chimera/StructureCommands.java b/src/jalview/ext/rbvi/chimera/StructureCommands.java
new file mode 100644 (file)
index 0000000..5f707ae
--- /dev/null
@@ -0,0 +1,274 @@
+package jalview.ext.rbvi.chimera;
+
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.SequenceRenderer;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.structure.StructureMapping;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.Comparison;
+import jalview.util.IntRangeComparator;
+
+import java.awt.Color;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class with common methods for building commands for Jmol, Chimera, or other
+ * 
+ * @author gmcarstairs
+ */
+public abstract class StructureCommands
+{
+
+  /**
+   * Helper method to add one contiguous range to the AtomSpec model for the given
+   * value (creating the model if necessary). As used by Jalview, {@code value} is
+   * <ul>
+   * <li>a colour, when building a 'colour structure by sequence' command</li>
+   * <li>a feature value, when building a 'set Chimera attributes from features'
+   * command</li>
+   * </ul>
+   * 
+   * @param map
+   * @param value
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   */
+  protected static void addAtomSpecRange(Map<Object, AtomSpecModel> map,
+          Object value,
+          int model, int startPos, int endPos, String chain)
+  {
+    /*
+     * Get/initialize map of data for the colour
+     */
+    AtomSpecModel atomSpec = map.get(value);
+    if (atomSpec == null)
+    {
+      atomSpec = new AtomSpecModel();
+      map.put(value, atomSpec);
+    }
+  
+    atomSpec.addRange(model, startPos, endPos, chain);
+  }
+
+  /**
+   * Build a data structure which records contiguous subsequences by model and
+   * chain. From this we can easily generate the Chimera or Jmol specific
+   * selection expression.
+   * 
+   * <pre>
+   * Color
+   *     Model number
+   *         Chain
+   *             list of start/end ranges
+   * </pre>
+   * 
+   * Ordering is by order of addition (for colours and positions), natural
+   * ordering (for models and chains)
+   * 
+   * @param viewPanel
+   * @return
+   */
+  public static Map<Object, AtomSpecModel> buildColoursMap(
+          AAStructureBindingModel binding, AlignmentViewPanel viewPanel)
+  {
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
+    AlignmentI al = viewport.getAlignment();
+    SequenceRenderer sr = binding.getSequenceRenderer(viewPanel);
+    String[] files = binding.getStructureFiles();
+    SequenceI[][] sequence = binding.getSequence();
+    
+    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
+    Color lastColour = null;
+  
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      StructureMapping[] mapping = binding.getSsm()
+              .getMapping(files[pdbfnum]);
+  
+      if (mapping == null || mapping.length < 1)
+      {
+        continue;
+      }
+  
+      int startPos = -1, lastPos = -1;
+      String lastChain = "";
+      for (int s = 0; s < sequence[pdbfnum].length; s++)
+      {
+        for (int sp, m = 0; m < mapping.length; m++)
+        {
+          final SequenceI seq = sequence[pdbfnum][s];
+          if (mapping[m].getSequence() == seq
+                  && (sp = al.findIndex(seq)) > -1)
+          {
+            SequenceI asp = al.getSequenceAt(sp);
+            for (int r = 0; r < asp.getLength(); r++)
+            {
+              // no mapping to gaps in sequence
+              if (Comparison.isGap(asp.getCharAt(r)))
+              {
+                continue;
+              }
+              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
+  
+              if (pos < 1 || pos == lastPos)
+              {
+                continue;
+              }
+  
+              Color colour = sr.getResidueColour(seq, r, finder);
+  
+              /*
+               * hidden regions are shown gray or, optionally, ignored
+               */
+              if (!cs.isVisible(r))
+              {
+                if (binding.isHideHiddenRegions())
+                {
+                  continue;
+                }
+                else
+                {
+                  colour = Color.GRAY;
+                }
+              }
+  
+              final String chain = mapping[m].getChain();
+  
+              /*
+               * Just keep incrementing the end position for this colour range
+               * _unless_ colour, PDB model or chain has changed, or there is a
+               * gap in the mapped residue sequence
+               */
+              final boolean newColour = !colour.equals(lastColour);
+              final boolean nonContig = lastPos + 1 != pos;
+              final boolean newChain = !chain.equals(lastChain);
+              if (newColour || nonContig || newChain)
+              {
+                if (startPos != -1)
+                {
+                  StructureCommands.addAtomSpecRange(colourMap, lastColour,
+                          pdbfnum, startPos,
+                          lastPos, lastChain);
+                }
+                startPos = pos;
+              }
+              lastColour = colour;
+              lastPos = pos;
+              lastChain = chain;
+            }
+            // final colour range
+            if (lastColour != null)
+            {
+              StructureCommands.addAtomSpecRange(colourMap, lastColour,
+                      pdbfnum,
+                      startPos, lastPos, lastChain);
+            }
+          }
+        }
+      }
+    }
+    return colourMap;
+  }
+
+  /**
+   * A helper method that takes a list of [start-end] ranges, format them as
+   * s1-e1,s2-e2 etc and appends to the string buffer. Ranges are sorted, and
+   * coalesced if they overlap. The chain token, if not null, is appended to each
+   * resulting subrange.
+   * 
+   * @param sb
+   * @param rangeList
+   * @param chainToken
+   * @param firstPositionForModel
+   */
+  protected static void appendResidueRange(StringBuilder sb, List<int[]> rangeList,
+          String chainToken, boolean firstPositionForModel)
+  {
+    /*
+     * sort ranges into ascending start position order
+     */
+    Collections.sort(rangeList, IntRangeComparator.ASCENDING);
+  
+    int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
+    int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
+  
+    Iterator<int[]> iterator = rangeList.iterator();
+    while (iterator.hasNext())
+    {
+      int[] range = iterator.next();
+      if (range[0] <= end + 1)
+      {
+        /*
+         * range overlaps or is contiguous with the last one
+         * - so just extend the end position, and carry on
+         * (unless this is the last in the list)
+         */
+        end = Math.max(end, range[1]);
+      }
+      else
+      {
+        /*
+         * we have a break so append the last range
+         */
+        appendRange(sb, start, end, chainToken, firstPositionForModel);
+        firstPositionForModel = false;
+        start = range[0];
+        end = range[1];
+      }
+    }
+  
+    /*
+     * and append the last range
+     */
+    if (!rangeList.isEmpty())
+    {
+      appendRange(sb, start, end, chainToken, firstPositionForModel);
+    }
+  }
+
+  /**
+   * A helper method that appends one start-end range, and an (optional) chain
+   * token to an atomspec (a null token is not appended)
+   * 
+   * @param sb
+   * @param start
+   * @param end
+   * @param chainToken
+   * @param firstPositionForModel
+   */
+  protected static void appendRange(StringBuilder sb, int start, int end,
+          String chainToken, boolean firstPositionForModel)
+  {
+    if (!firstPositionForModel)
+    {
+      sb.append(",");
+    }
+    if (end == start)
+    {
+      sb.append(start);
+    }
+    else
+    {
+      sb.append(start).append("-").append(end);
+    }
+    if (chainToken != null)
+    {
+      sb.append(chainToken);
+    }
+  }
+
+}
index b55885d..1dea2c6 100644 (file)
@@ -22,7 +22,6 @@ package jalview.structures.models;
 
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.StructureSelectionManagerProvider;
 import jalview.api.structures.JalviewStructureDisplayI;
@@ -31,9 +30,7 @@ import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.ext.rbvi.chimera.AtomSpecModel;
-import jalview.ext.rbvi.chimera.ChimeraCommands;
 import jalview.io.DataSourceType;
-import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.schemes.ColourSchemeI;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
@@ -48,9 +45,7 @@ import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * 
@@ -1051,127 +1046,6 @@ public abstract class AAStructureBindingModel
   }
 
   /**
-   * Build a data structure which records contiguous subsequences for each colour.
-   * From this we can easily generate the Chimera command for colour by sequence.
-   * 
-   * <pre>
-   * Color
-   *     Model number
-   *         Chain
-   *             list of start/end ranges
-   * </pre>
-   * 
-   * Ordering is by order of addition (for colours and positions), natural
-   * ordering (for models and chains)
-   * 
-   * @param viewPanel
-   * @return
-   */
-  public Map<Object, AtomSpecModel> buildColoursMap(
-          AlignmentViewPanel viewPanel)
-  {
-    FeatureRenderer fr = viewPanel.getFeatureRenderer();
-    FeatureColourFinder finder = new FeatureColourFinder(fr);
-    AlignViewportI viewport = viewPanel.getAlignViewport();
-    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
-    AlignmentI al = viewport.getAlignment();
-    SequenceRenderer sr = getSequenceRenderer(viewPanel);
-    String[] files = getStructureFiles();
-    
-    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
-    Color lastColour = null;
-  
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-    {
-      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-  
-      if (mapping == null || mapping.length < 1)
-      {
-        continue;
-      }
-  
-      int startPos = -1, lastPos = -1;
-      String lastChain = "";
-      for (int s = 0; s < sequence[pdbfnum].length; s++)
-      {
-        for (int sp, m = 0; m < mapping.length; m++)
-        {
-          final SequenceI seq = sequence[pdbfnum][s];
-          if (mapping[m].getSequence() == seq
-                  && (sp = al.findIndex(seq)) > -1)
-          {
-            SequenceI asp = al.getSequenceAt(sp);
-            for (int r = 0; r < asp.getLength(); r++)
-            {
-              // no mapping to gaps in sequence
-              if (Comparison.isGap(asp.getCharAt(r)))
-              {
-                continue;
-              }
-              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
-  
-              if (pos < 1 || pos == lastPos)
-              {
-                continue;
-              }
-  
-              Color colour = sr.getResidueColour(seq, r, finder);
-  
-              /*
-               * hidden regions are shown gray or, optionally, ignored
-               */
-              if (!cs.isVisible(r))
-              {
-                if (hideHiddenRegions)
-                {
-                  continue;
-                }
-                else
-                {
-                  colour = Color.GRAY;
-                }
-              }
-  
-              final String chain = mapping[m].getChain();
-  
-              /*
-               * Just keep incrementing the end position for this colour range
-               * _unless_ colour, PDB model or chain has changed, or there is a
-               * gap in the mapped residue sequence
-               */
-              final boolean newColour = !colour.equals(lastColour);
-              final boolean nonContig = lastPos + 1 != pos;
-              final boolean newChain = !chain.equals(lastChain);
-              if (newColour || nonContig || newChain)
-              {
-                if (startPos != -1)
-                {
-                  ChimeraCommands.addAtomSpecRange(colourMap, lastColour,
-                          pdbfnum, startPos,
-                          lastPos, lastChain);
-                }
-                startPos = pos;
-              }
-              lastColour = colour;
-              lastPos = pos;
-              lastChain = chain;
-            }
-            // final colour range
-            if (lastColour != null)
-            {
-              ChimeraCommands.addAtomSpecRange(colourMap, lastColour,
-                      pdbfnum,
-                      startPos, lastPos, lastChain);
-            }
-            // break;
-          }
-        }
-      }
-    }
-    return colourMap;
-  }
-
-  /**
    * Returns a list of chains mapped in this viewer. Note this list is not
    * currently scoped per structure.
    * 
index e42b54f..f1cc820 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.AtomSpecModel;
 import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.gui.SequenceRenderer;
@@ -66,7 +67,8 @@ public class JmolCommandsTest
     // need some mappings!
 
     StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+            .getColourBySequenceCommand(ssm, files, seqs, sr,
+                    af.alignPanel);
   }
 
   @Test(groups = { "Functional" })
@@ -91,11 +93,11 @@ public class JmolCommandsTest
     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
     StructureSelectionManager ssm = new StructureSelectionManager();
-  
+
     /*
      * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
      */
-    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+    HashMap<Integer, int[]> map = new HashMap<>();
     for (int pos = 1; pos <= seq1.getLength(); pos++)
     {
       map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
@@ -106,37 +108,91 @@ public class JmolCommandsTest
     StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
             "B", map, null);
     ssm.addStructureMapping(sm2);
-  
+
     StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+            .getColourBySequenceCommand(ssm, files, seqs, sr,
+                    af.alignPanel);
     assertEquals(commands.length, 2);
     assertEquals(commands[0].commands.length, 1);
 
     String chainACommand = commands[0].commands[0];
     // M colour is #82827d == (130, 130, 125) (see strand.html help page)
-    assertTrue(chainACommand
-            .contains("select 21:A/1.1;color[130,130,125]")); // first one
+    assertTrue(
+            chainACommand.contains("select 21:A/1.1;color[130,130,125]")); // first one
     // H colour is #60609f == (96, 96, 159)
     assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]"));
     // hidden columns are Gray (128, 128, 128)
     assertTrue(chainACommand
             .contains(";select 23-25:A/1.1;color[128,128,128]"));
     // S and G are both coloured #4949b6 == (73, 73, 182)
-    assertTrue(chainACommand
-            .contains(";select 26-30:A/1.1;color[73,73,182]"));
+    assertTrue(
+            chainACommand.contains(";select 26-30:A/1.1;color[73,73,182]"));
 
     String chainBCommand = commands[1].commands[0];
     // M colour is #82827d == (130, 130, 125)
-    assertTrue(chainBCommand
-            .contains("select 21:B/2.1;color[130,130,125]"));
+    assertTrue(
+            chainBCommand.contains("select 21:B/2.1;color[130,130,125]"));
     // V colour is #ffff00 == (255, 255, 0)
-    assertTrue(chainBCommand
-.contains(";select 22:B/2.1;color[255,255,0]"));
+    assertTrue(chainBCommand.contains(";select 22:B/2.1;color[255,255,0]"));
     // hidden columns are Gray (128, 128, 128)
     assertTrue(chainBCommand
             .contains(";select 23-25:B/2.1;color[128,128,128]"));
     // S and G are both coloured #4949b6 == (73, 73, 182)
-    assertTrue(chainBCommand
-            .contains(";select 26-30:B/2.1;color[73,73,182]"));
+    assertTrue(
+            chainBCommand.contains(";select 26-30:B/2.1;color[73,73,182]"));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetAtomSpec()
+  {
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(JmolCommands.getAtomSpec(model), "");
+
+    /*
+     * Jalview numbers models from 0, Jmol from 1
+     */
+    model.addRange(1, 2, 4, "A");
+    assertEquals(JmolCommands.getAtomSpec(model), "2-4:A/2.1");
+
+    model.addRange(1, 8, 8, "A");
+    assertEquals(JmolCommands.getAtomSpec(model), "(2-4,8)&:A/2.1");
+
+    model.addRange(1, 5, 7, "B");
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "(2-4,8)&:A/2.1,5-7:B/2.1");
+
+    model.addRange(1, 3, 5, "A");
+    // 3-5 merges with 2-4 to make 2-5:
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "(2-5,8)&:A/2.1,5-7:B/2.1");
+
+    model.addRange(0, 1, 4, "B");
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "1-4:B/1.1,(2-5,8)&:A/2.1,5-7:B/2.1");
+
+    model.addRange(0, 5, 9, "C");
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "1-4:B/1.1,5-9:C/1.1,(2-5,8)&:A/2.1,5-7:B/2.1");
+
+    model.addRange(1, 8, 10, "B");
+    // 8-10 extends 5-7 to make 5-10
+    // note code generates (5-10)&:B which is equivalent to 5-10:B
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "1-4:B/1.1,5-9:C/1.1,(2-5,8)&:A/2.1,(5-10)&:B/2.1");
+
+    model.addRange(1, 8, 9, "B");
+    // subsumed by 5-10 so makes no difference
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "1-4:B/1.1,5-9:C/1.1,(2-5,8)&:A/2.1,(5-10)&:B/2.1");
+
+    model.addRange(0, 3, 10, "C");
+    // subsumes 5-9 so replaces it
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "1-4:B/1.1,(3-10)&:C/1.1,(2-5,8)&:A/2.1,(5-10)&:B/2.1");
+
+    // empty chain code - e.g. from homology modelling
+    model.addRange(5, 25, 35, " ");
+    assertEquals(JmolCommands.getAtomSpec(model),
+            "1-4:B/1.1,(3-10)&:C/1.1,(2-5,8)&:A/2.1,(5-10)&:B/2.1,25-35:/6.1");
   }
 }
index 18c4a14..9adbd8e 100644 (file)
@@ -300,8 +300,7 @@ public class ChimeraCommandsTest
     PA.setValue(mockBinding, "ssm", ssm);
     PA.setValue(mockBinding, "sequence", seqs);
 
-    Map<Object, AtomSpecModel> colourMap = mockBinding
-            .buildColoursMap(af.alignPanel);
+    Map<Object, AtomSpecModel> colourMap = StructureCommands.buildColoursMap(mockBinding, af.alignPanel);
     String[] commands = ChimeraCommands
             .getColourBySequenceCommand(colourMap, mockBinding);
     assertEquals(1, commands.length);
@@ -323,35 +322,49 @@ public class ChimeraCommandsTest
   {
     AtomSpecModel model = new AtomSpecModel();
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding), "");
+
     model.addRange(1, 2, 4, "A");
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#1:2-4.A");
+
     model.addRange(1, 8, 8, "A");
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#1:2-4.A,8.A");
+
     model.addRange(1, 5, 7, "B");
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#1:2-4.A,8.A,5-7.B");
+
     model.addRange(1, 3, 5, "A");
+    // 3-5 combines with 2-4 to make 2-5:
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#1:2-5.A,8.A,5-7.B");
+
     model.addRange(0, 1, 4, "B");
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
+
     model.addRange(0, 5, 9, "C");
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
+
     model.addRange(1, 8, 10, "B");
+    // 8-10 extends 5-7 to make 5-10
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+
     model.addRange(1, 8, 9, "B");
+    // subsumed range makes no difference
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
-    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+
+    model.addRange(0, 3, 10, "C");
+    // subsumes 5-9 so replaces it
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
-    model.addRange(5, 25, 35, " "); // empty chain code - e.g. from homology
-                                    // modelling
+
+    // empty chain code - e.g. from homology modelling
+    model.addRange(5, 25, 35, " ");
     assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
             "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");