JAL-3390 pull up of getShownResidues() to AAStructureBindingModel
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 6 Aug 2019 12:05:45 +0000 (13:05 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 6 Aug 2019 12:05:45 +0000 (13:05 +0100)
src/jalview/ext/rbvi/chimera/AtomSpecModel.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/structures/models/AAStructureBindingModel.java
test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java [deleted file]
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java

index 39d6704..f0d1e84 100644 (file)
  */
 package jalview.ext.rbvi.chimera;
 
-import jalview.util.IntRangeComparator;
-
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
 /**
- * A class to model a Chimera atomspec pattern, for example
- * 
- * <pre>
- * #0:15.A,28.A,54.A,63.A,70-72.A,83-84.A,97-98.A|#1:2.A,6.A,11.A,13-14.A,70.A,82.A,96-97.A
- * </pre>
- * 
- * where
- * <ul>
- * <li>#0 is a model number</li>
- * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
- * <li>.A is a chain identifier</li>
- * <li>residue ranges are separated by comma</li>
- * <li>atomspecs for distinct models are separated by | (or)</li>
- * </ul>
- * 
- * <pre>
- * &#64;see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
- * </pre>
+ * A class to model a Chimera or Jmol residue set, as
+ * {@code Map<modelNumber, Map<chainId, List<residueRange>>>}. This can then be
+ * traversed to generate the required display command in Chimera or Jmol syntax.
  */
 public class AtomSpecModel
 {
@@ -58,7 +40,12 @@ public class AtomSpecModel
    */
   public AtomSpecModel()
   {
-    atomSpec = new TreeMap<Integer, Map<String, List<int[]>>>();
+    atomSpec = new TreeMap<>();
+  }
+
+  public Map<Integer, Map<String, List<int[]>>> getMap()
+  {
+    return atomSpec;
   }
 
   /**
@@ -77,7 +64,7 @@ public class AtomSpecModel
     Map<String, List<int[]>> modelData = atomSpec.get(model);
     if (modelData == null)
     {
-      atomSpec.put(model, modelData = new TreeMap<String, List<int[]>>());
+      atomSpec.put(model, modelData = new TreeMap<>());
     }
 
     /*
@@ -86,7 +73,7 @@ public class AtomSpecModel
     List<int[]> chainData = modelData.get(chain);
     if (chainData == null)
     {
-      chainData = new ArrayList<int[]>();
+      chainData = new ArrayList<>();
       modelData.put(chain, chainData);
     }
 
@@ -98,104 +85,42 @@ public class AtomSpecModel
   }
 
   /**
-   * Returns the range(s) formatted as a Chimera atomspec
+   * Answers an iterable set of the structure models in this model
    * 
    * @return
    */
-  public String getAtomSpec()
+  public Iterable<Integer> getModels()
   {
-    StringBuilder sb = new StringBuilder(128);
-    boolean firstModel = true;
-    for (Integer model : atomSpec.keySet())
-    {
-      if (!firstModel)
-      {
-        sb.append("|");
-      }
-      firstModel = false;
-      sb.append("#").append(model).append(":");
-
-      boolean firstPositionForModel = true;
-      final Map<String, List<int[]>> modelData = atomSpec.get(model);
-
-      for (String chain : modelData.keySet())
-      {
-        chain = " ".equals(chain) ? chain : chain.trim();
-
-        List<int[]> rangeList = modelData.get(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;
-        }
-      }
-    }
-    return sb.toString();
+    return atomSpec.keySet();
   }
 
   /**
-   * @param sb
-   * @param start
-   * @param end
-   * @param chain
-   * @param firstPositionForModel
+   * Answers an iterable set of the chains in this model for the given structure
+   * model, or an empty set if none
+   * 
+   * @param model
+   * @return
    */
-  protected void appendRange(StringBuilder sb, int start, int end,
-          String chain, boolean firstPositionForModel)
+  public Iterable<String> getChains(Integer model)
   {
-    if (!firstPositionForModel)
-    {
-      sb.append(",");
-    }
-    if (end == start)
+    if (atomSpec.containsKey(model))
     {
-      sb.append(start);
-    }
-    else
-    {
-      sb.append(start).append("-").append(end);
+      return atomSpec.get(model).keySet();
     }
+    return Collections.emptySet();
+  }
 
-    sb.append(".");
-    if (!" ".equals(chain)) {
-      sb.append(chain);
+  public List<int[]> getRanges(Integer model, String chain)
+  {
+    Map<String, List<int[]>> modelData = atomSpec.get(model);
+    if (modelData != null)
+    {
+      List<int[]> chainData = modelData.get(chain);
+      if (chainData != null)
+      {
+        return chainData;
+      }
     }
+    return Collections.EMPTY_LIST;
   }
 }
index dc53c2b..c52b9a2 100644 (file)
@@ -35,10 +35,13 @@ 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;
@@ -53,42 +56,34 @@ public class ChimeraCommands
 {
   public static final String NAMESPACE_PREFIX = "jv_";
 
+  /*
+   * colour for residues shown in structure but hidden in alignment
+   */
   private static final String COLOR_GRAY_HEX = "color "
           + ColorUtils.toTkCode(Color.GRAY);
 
   /**
    * Constructs Chimera commands to colour residues as per the Jalview alignment
    * 
-   * @param ssm
    * @param files
-   * @param sequence
-   * @param sr
-   * @param fr
    * @param viewPanel
+   * @param binding
    * @return
    */
   public static StructureMappingcommandSet[] getColourBySequenceCommand(
-          StructureSelectionManager ssm, String[] files,
-          AAStructureBindingModel binding, AlignmentViewPanel viewPanel)
+          String[] files, AlignmentViewPanel viewPanel,
+          AAStructureBindingModel binding)
   {
+    StructureSelectionManager ssm = binding.getSsm();
     SequenceRenderer sr = binding.getSequenceRenderer(viewPanel);
     SequenceI[][] sequence = binding.getSequence();
     boolean hideHiddenRegions = binding.isShowAlignmentOnly()
             && binding.isHideHiddenRegions();
 
-    return getColourBySequenceCommand(ssm, files, sequence, sr,
-            hideHiddenRegions, viewPanel);
-  }
-
-  static StructureMappingcommandSet[] getColourBySequenceCommand(
-          StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr,
-          boolean hideHiddenRegions, AlignmentViewPanel viewPanel)
-  {
     Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
             sequence, sr, hideHiddenRegions, viewPanel);
 
-    List<String> colourCommands = buildColourCommands(colourMap);
+    List<String> colourCommands = buildColourCommands(colourMap, binding);
 
     StructureMappingcommandSet cs = new StructureMappingcommandSet(
             ChimeraCommands.class, null,
@@ -110,10 +105,12 @@ public class ChimeraCommands
    * </pre>
    * 
    * @param colourMap
+   * @param binding
    * @return
    */
   protected static List<String> buildColourCommands(
-          Map<Object, AtomSpecModel> colourMap)
+          Map<Object, AtomSpecModel> colourMap,
+          AAStructureBindingModel binding)
   {
     /*
      * This version concatenates all commands into a single String (semi-colon
@@ -131,64 +128,13 @@ public class ChimeraCommands
       sb.append("; ");
       sb.append("color ").append(colourCode).append(" ");
       final AtomSpecModel colourData = colourMap.get(colour);
-      sb.append(colourData.getAtomSpec());
+      sb.append(getAtomSpec(colourData, binding));
     }
     commands.add(sb.toString());
     return commands;
   }
 
   /**
-   * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
-   * builds a Chimera format atom spec
-   * 
-   * @param modelAndChainRanges
-   */
-  protected static String getAtomSpec(
-          Map<Integer, Map<String, List<int[]>>> modelAndChainRanges)
-  {
-    StringBuilder sb = new StringBuilder(128);
-    boolean firstModelForColour = true;
-    for (Integer model : modelAndChainRanges.keySet())
-    {
-      boolean firstPositionForModel = true;
-      if (!firstModelForColour)
-      {
-        sb.append("|");
-      }
-      firstModelForColour = false;
-      sb.append("#").append(model).append(":");
-
-      final Map<String, List<int[]>> modelData = modelAndChainRanges
-              .get(model);
-      for (String chain : modelData.keySet())
-      {
-        boolean hasChain = !"".equals(chain.trim());
-        for (int[] range : modelData.get(chain))
-        {
-          if (!firstPositionForModel)
-          {
-            sb.append(",");
-          }
-          if (range[0] == range[1])
-          {
-            sb.append(range[0]);
-          }
-          else
-          {
-            sb.append(range[0]).append("-").append(range[1]);
-          }
-          if (hasChain)
-          {
-            sb.append(".").append(chain);
-          }
-          firstPositionForModel = false;
-        }
-      }
-    }
-    return sb.toString();
-  }
-
-  /**
    * Build a data structure which records contiguous subsequences for each colour.
    * From this we can easily generate the Chimera command for colour by sequence.
    * 
@@ -339,23 +285,27 @@ public class ChimeraCommands
 
   /**
    * 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.
+   * corresponding to features in Jalview. Attribute names are the Jalview feature
+   * type, with a "jv_" prefix.
    * 
    * @param ssm
    * @param files
    * @param seqs
    * @param viewPanel
+   * @param binding
    * @return
    */
   public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
-          StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
-          AlignmentViewPanel viewPanel)
+          AlignmentViewPanel viewPanel, AAStructureBindingModel binding)
   {
+    StructureSelectionManager ssm = binding.getSsm();
+    String[] files = binding.getStructureFiles();
+    SequenceI[][] seqs = binding.getSequence();
+
     Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
             ssm, files, seqs, viewPanel);
 
-    List<String> commands = buildSetAttributeCommands(featureMap);
+    List<String> commands = buildSetAttributeCommands(featureMap, binding);
 
     StructureMappingcommandSet cs = new StructureMappingcommandSet(
             ChimeraCommands.class, null,
@@ -501,10 +451,12 @@ public class ChimeraCommands
    * </pre>
    * 
    * @param featureMap
+   * @param binding
    * @return
    */
   protected static List<String> buildSetAttributeCommands(
-          Map<String, Map<Object, AtomSpecModel>> featureMap)
+          Map<String, Map<Object, AtomSpecModel>> featureMap,
+          AAStructureBindingModel binding)
   {
     List<String> commands = new ArrayList<>();
     for (String featureType : featureMap.keySet())
@@ -530,7 +482,7 @@ public class ChimeraCommands
         featureValue = featureValue.replaceAll("\\'", "&#39;");
         sb.append("setattr r ").append(attributeName).append(" '")
                 .append(featureValue).append("' ");
-        sb.append(values.get(value).getAtomSpec());
+        sb.append(getAtomSpec(values.get(value), binding));
         commands.add(sb.toString());
       }
     }
@@ -574,4 +526,111 @@ public class ChimeraCommands
     return attName;
   }
 
+  /**
+   * Returns the range(s) formatted as a Chimera atomspec
+   * 
+   * @return
+   */
+  public static String getAtomSpec(AtomSpecModel atomSpec,
+          AAStructureBindingModel binding)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    boolean firstModel = true;
+    for (Integer model : atomSpec.getModels())
+    {
+      if (!firstModel)
+      {
+        sb.append("|");
+      }
+      firstModel = false;
+      // todo use JalviewChimeraBinding.getModelSpec(model)
+      // which means this cannot be static
+      sb.append(binding.getModelSpec(model)).append(":");
+
+      boolean firstPositionForModel = true;
+
+      for (String chain : atomSpec.getChains(model))
+      {
+        chain = " ".equals(chain) ? chain : chain.trim();
+
+        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;
+        }
+      }
+    }
+    return sb.toString();
+  }
+
+  /**
+   * A helper method that appends one start-end range to a Chimera atomspec
+   * 
+   * @param sb
+   * @param start
+   * @param end
+   * @param chain
+   * @param firstPositionForModel
+   */
+  static void appendRange(StringBuilder sb, int start, int end,
+          String chain, boolean firstPositionForModel)
+  {
+    if (!firstPositionForModel)
+    {
+      sb.append(",");
+    }
+    if (end == start)
+    {
+      sb.append(start);
+    }
+    else
+    {
+      sb.append(start).append("-").append(end);
+    }
+
+    sb.append(".");
+    if (!" ".equals(chain))
+    {
+      sb.append(chain);
+    }
+  }
+
 }
index 216320f..44bcbe4 100644 (file)
@@ -36,7 +36,6 @@ import jalview.io.DataSourceType;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
-import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
@@ -52,7 +51,6 @@ import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Hashtable;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -464,7 +462,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
          * @see
          * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
          */
-        command.append("match ").append(getModelSpec(pdbfnum)).append(":");
+        command.append("match ").append(getModelSpec(pdbfnum))
+                .append(":");
         command.append(selcom[pdbfnum]);
         command.append("@").append(
                 structures[pdbfnum].isRna ? PHOSPHORUS : ALPHACARBON);
@@ -541,7 +540,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * @param pdbfnum
    * @return
    */
-  protected String getModelSpec(int pdbfnum)
+  @Override
+  public String getModelSpec(int pdbfnum)
   {
     if (pdbfnum < 0 || pdbfnum >= getPdbCount())
     {
@@ -556,7 +556,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      */
     List<ChimeraModel> maps = chimeraMaps.get(getStructureFiles()[pdbfnum]);
     boolean hasSubModels = maps != null && maps.size() > 1;
-    return "#" + String.valueOf(pdbfnum) + (hasSubModels ? ".1" : "");
+    String spec = "#" + String.valueOf(pdbfnum);
+    return hasSubModels ? spec + ".1" : spec;
   }
 
   /**
@@ -673,8 +674,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   protected StructureMappingcommandSet[] getColourBySequenceCommands(
           String[] files, AlignmentViewPanel viewPanel)
   {
-    return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
-            this, viewPanel);
+    return ChimeraCommands.getColourBySequenceCommand(files, viewPanel,
+            this);
   }
 
   /**
@@ -1086,8 +1087,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
 
     StructureMappingcommandSet commandSet = ChimeraCommands
-            .getSetAttributeCommandsForFeatures(getSsm(), files,
-                    getSequence(), avp);
+            .getSetAttributeCommandsForFeatures(avp, this);
     String[] commands = commandSet.commands;
     if (commands.length > 10)
     {
@@ -1291,7 +1291,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   {
     StringBuilder cmd = new StringBuilder(128);
     cmd.append("~display; ~ribbon;");
-    String atomSpec = getMappedResidues(av);
+
+    AtomSpecModel model = getShownResidues(av);
+    String atomSpec = ChimeraCommands.getAtomSpec(model, this);
+    
     cmd.append("ribbon ").append(atomSpec);
     if (!isShowAlignmentOnly())
     {
@@ -1303,95 +1306,4 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
     sendChimeraCommand(cmd.toString(), false);
   }
-
-  /**
-   * Builds a Chimera atomSpec of residues mapped from sequences, of the format
-   * (#model:residues.chain)
-   * 
-   * <pre>
-   * #0:2-94.A | #1:1-93.C | #2:1-93.A
-   * </pre>
-   * 
-   * Only residues visible in the alignment are included, that is, hidden columns
-   * and sequences are excluded.
-   * 
-   * @param av
-   * @return
-   */
-  private String getMappedResidues(AlignViewportI av)
-  {
-    AlignmentI alignment = av.getAlignment();
-    final int width = alignment.getWidth();
-  
-    String[] files = getStructureFiles();
-
-    StringBuilder atomSpec = new StringBuilder(256);
-
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-    {
-      StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
-
-      /*
-       * Find the first mapped sequence (if any) for this PDB entry which is in
-       * the alignment
-       */
-      final int seqCountForPdbFile = getSequence()[pdbfnum].length;
-      for (int s = 0; s < seqCountForPdbFile; s++)
-      {
-        for (StructureMapping mapping : mappings)
-        {
-          final SequenceI theSequence = getSequence()[pdbfnum][s];
-          if (mapping.getSequence() == theSequence
-                  && alignment.findIndex(theSequence) > -1)
-          {
-            String chainCd = mapping.getChain();
-            if (!isShowChain(mapping.getPdbId(), chainCd))
-            {
-              continue;
-            }
-            Iterator<int[]> visible;
-            if (isShowAlignmentOnly() && isHideHiddenRegions())
-            {
-              visible = alignment.getHiddenColumns()
-                    .getVisContigsIterator(0, width, true);
-            }
-            else
-            {
-              visible = Collections.singletonList(new int[] { 0, width })
-                      .iterator();
-            }
-            while (visible.hasNext())
-            {
-              int[] visibleRegion = visible.next();
-              int seqStartPos = theSequence.findPosition(visibleRegion[0]);
-              int seqEndPos = theSequence.findPosition(visibleRegion[1]);
-              List<int[]> residueRanges = mapping
-                      .getPDBResNumRanges(seqStartPos, seqEndPos);
-              if (!residueRanges.isEmpty())
-              {
-                if (atomSpec.length() > 0)
-                {
-                  atomSpec.append("| ");
-                }
-                atomSpec.append(getModelSpec(pdbfnum)).append(":");
-                boolean first = true;
-                for (int[] range : residueRanges)
-                {
-                  if (!first)
-                  {
-                    atomSpec.append(",");
-                  }
-                  first = false;
-                  atomSpec.append(range[0]).append("-").append(range[1]);
-                  atomSpec.append(".").append(chainCd);
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-
-    return atomSpec.toString();
-  }
 }
index d2fff3f..7a89961 100644 (file)
@@ -29,6 +29,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.AtomSpecModel;
 import jalview.io.DataSourceType;
 import jalview.schemes.ColourSchemeI;
 import jalview.structure.AtomSpec;
@@ -43,6 +44,8 @@ import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -954,4 +957,97 @@ public abstract class AAStructureBindingModel
     }
     return chainsToShow.contains(pdbId + ":" + chainId);
   }
+
+  @Override
+  public abstract String[] getStructureFiles();
+
+  /**
+   * Builds a model of residues mapped from sequences to show on structure, taking
+   * into account user choices of
+   * <ul>
+   * <li>which chains are shown</li>
+   * <li>whether all structure is shown, or only that mapped to the alignment</li>
+   * <li>whether hidden regions of the alignment are hidden (excluded) or grayed
+   * out (included)</li>
+   * </ul>
+   * 
+   * @param av
+   * @return
+   */
+  protected AtomSpecModel getShownResidues(AlignViewportI av)
+  {
+    AlignmentI alignment = av.getAlignment();
+    final int width = alignment.getWidth();
+  
+    String[] files = getStructureFiles();
+  
+    AtomSpecModel model = new AtomSpecModel();
+  
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
+  
+      /*
+       * Find the first mapped sequence (if any) for this PDB entry which is in
+       * the alignment
+       */
+      final int seqCountForPdbFile = getSequence()[pdbfnum].length;
+      for (int s = 0; s < seqCountForPdbFile; s++)
+      {
+        for (StructureMapping mapping : mappings)
+        {
+          final SequenceI theSequence = getSequence()[pdbfnum][s];
+          if (mapping.getSequence() == theSequence
+                  && alignment.findIndex(theSequence) > -1)
+          {
+            String chainCd = mapping.getChain();
+            if (!isShowChain(mapping.getPdbId(), chainCd))
+            {
+              continue;
+            }
+            Iterator<int[]> visible;
+            if (isShowAlignmentOnly() && isHideHiddenRegions())
+            {
+              visible = alignment.getHiddenColumns()
+                    .getVisContigsIterator(0, width, true);
+            }
+            else
+            {
+              visible = Collections.singletonList(new int[] { 0, width })
+                      .iterator();
+            }
+            while (visible.hasNext())
+            {
+              int[] visibleRegion = visible.next();
+              int seqStartPos = theSequence.findPosition(visibleRegion[0]);
+              int seqEndPos = theSequence.findPosition(visibleRegion[1]);
+              List<int[]> residueRanges = mapping
+                      .getPDBResNumRanges(seqStartPos, seqEndPos);
+              if (!residueRanges.isEmpty())
+              {
+                for (int[] range : residueRanges)
+                {
+                  model.addRange(pdbfnum, range[0], range[1], chainCd);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  
+    return model;
+  }
+
+  /**
+   * Answers a default structure model specification which is simply the string
+   * form of the model number. Override if needed to specify submodels.
+   * 
+   * @param model
+   * @return
+   */
+  public String getModelSpec(int model)
+  {
+    return String.valueOf(model);
+  }
 }
diff --git a/test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java b/test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java
deleted file mode 100644 (file)
index 63d5e4e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package jalview.ext.rbvi.chimera;
-
-import static org.testng.Assert.assertEquals;
-
-import org.testng.annotations.Test;
-
-public class AtomSpecModelTest
-{
-  @Test(groups = "Functional")
-  public void testGetAtomSpec()
-  {
-    AtomSpecModel model = new AtomSpecModel();
-    assertEquals(model.getAtomSpec(), "");
-    model.addRange(1, 2, 4, "A");
-    assertEquals(model.getAtomSpec(), "#1:2-4.A");
-    model.addRange(1, 8, 8, "A");
-    assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A");
-    model.addRange(1, 5, 7, "B");
-    assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A,5-7.B");
-    model.addRange(1, 3, 5, "A");
-    assertEquals(model.getAtomSpec(), "#1:2-5.A,8.A,5-7.B");
-    model.addRange(0, 1, 4, "B");
-    assertEquals(model.getAtomSpec(), "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
-    model.addRange(0, 5, 9, "C");
-    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
-    model.addRange(1, 8, 10, "B");
-    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
-    model.addRange(1, 8, 9, "B");
-    assertEquals(model.getAtomSpec(), "#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
-    assertEquals(model.getAtomSpec(), "#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
-    assertEquals(model.getAtomSpec(),
-            "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
-
-  }
-
-}
index fbfe33c..24ca6e9 100644 (file)
@@ -23,18 +23,24 @@ package jalview.ext.rbvi.chimera;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.gui.SequenceRenderer;
+import jalview.schemes.ColourSchemeI;
 import jalview.schemes.JalviewColourScheme;
+import jalview.structure.AtomSpec;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.AAStructureBindingModel;
 
 import java.awt.Color;
 import java.util.HashMap;
@@ -45,9 +51,98 @@ import java.util.Map;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class ChimeraCommandsTest
 {
 
+  private SequenceRenderer sr;
+  
+  private AAStructureBindingModel mockBinding = new AAStructureBindingModel(
+          null, null)
+  {
+    @Override
+    public void releaseReferences(Object svl)
+    {
+    }
+
+    @Override
+    public void highlightAtoms(List<AtomSpec> atoms)
+    {
+    }
+
+    @Override
+    public List<String> getChainNames()
+    {
+      return null;
+    }
+
+    @Override
+    public void setJalviewColourScheme(ColourSchemeI cs)
+    {
+    }
+
+    @Override
+    public String superposeStructures(AlignmentI[] alignments,
+            int[] structureIndices, HiddenColumns[] hiddenCols)
+    {
+      return null;
+    }
+
+    @Override
+    public void setBackgroundColour(Color col)
+    {
+    }
+
+    @Override
+    protected StructureMappingcommandSet[] getColourBySequenceCommands(
+            String[] files, AlignmentViewPanel avp)
+    {
+      return null;
+    }
+
+    @Override
+    public jalview.api.SequenceRenderer getSequenceRenderer(
+            AlignmentViewPanel alignment)
+    {
+      return sr;
+    }
+
+    @Override
+    protected void colourBySequence(
+            StructureMappingcommandSet[] colourBySequenceCommands)
+    {
+    }
+
+    @Override
+    public void colourByChain()
+    {
+    }
+
+    @Override
+    public void colourByCharge()
+    {
+    }
+
+    @Override
+    public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
+    {
+      return null;
+    }
+
+    @Override
+    public String[] getStructureFiles()
+    {
+      return null;
+    }
+
+    @Override
+    public String getModelSpec(int model)
+    {
+      return "#" + String.valueOf(model);
+    }
+  };
+
   @BeforeClass(alwaysRun = true)
   public void setUpJvOptionPane()
   {
@@ -73,7 +168,8 @@ public class ChimeraCommandsTest
     // Colours should appear in the Chimera command in the order in which
     // they were added; within colour, by model, by chain, ranges in start order
     // all prefixed with #808080 to colour hidden regions (if shown) gray
-    String command = ChimeraCommands.buildColourCommands(map).get(0);
+    String command = ChimeraCommands.buildColourCommands(map, mockBinding)
+            .get(0);
     assertEquals(
             command,
             "color #808080; color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:3-5.A,8.A; color #ff0000 #0:3-9.A");
@@ -95,7 +191,7 @@ public class ChimeraCommandsTest
     ChimeraCommands.addColourRange(featureValues, "X", 0, 8, 20, "A");
   
     List<String> commands = ChimeraCommands
-            .buildSetAttributeCommands(featuresMap);
+            .buildSetAttributeCommands(featuresMap, mockBinding);
     assertEquals(1, commands.size());
 
     /*
@@ -108,7 +204,8 @@ public class ChimeraCommandsTest
     ChimeraCommands.addColourRange(featureValues, "X", 0, 3, 9, "A");
     // same feature value, contiguous range
     ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "A");
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap,
+            mockBinding);
     assertEquals(1, commands.size());
     assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A");
 
@@ -116,14 +213,16 @@ public class ChimeraCommandsTest
     ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "B");
     // same feature value and chain, different model
     ChimeraCommands.addColourRange(featureValues, "X", 1, 26, 30, "A");
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap,
+            mockBinding);
     assertEquals(1, commands.size());
     assertEquals(commands.get(0),
             "setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
 
     // same feature, different value
     ChimeraCommands.addColourRange(featureValues, "Y", 0, 40, 50, "A");
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap,
+            mockBinding);
     assertEquals(2, commands.size());
     // commands are ordered by feature type but not by value
     // so use contains to test for the expected command:
@@ -139,7 +238,8 @@ public class ChimeraCommandsTest
             "A");
     // feature names are sanitised to change non-alphanumeric to underscore
     // feature values are sanitised to encode single quote characters
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap,
+            mockBinding);
     assertTrue(commands
             .contains("setattr r jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
   }
@@ -182,7 +282,7 @@ public class ChimeraCommandsTest
     cs.addElement(4);
     af.getViewport().setColumnSelection(cs);
     af.hideSelColumns_actionPerformed(null);
-    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
+    sr = new SequenceRenderer(af.getViewport());
     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
     StructureSelectionManager ssm = new StructureSelectionManager();
@@ -202,9 +302,14 @@ public class ChimeraCommandsTest
             "B", map, null);
     ssm.addStructureMapping(sm2);
 
+    /*
+     * put data into the mock binding object
+     */
+    PA.setValue(mockBinding, "ssm", ssm);
+    PA.setValue(mockBinding, "sequence", seqs);
+
     StructureMappingcommandSet[] commands = ChimeraCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, false,
-                    af.alignPanel);
+            .getColourBySequenceCommand(files, af.alignPanel, mockBinding);
     assertEquals(1, commands.length);
     assertEquals(1, commands[0].commands.length);
     String theCommand = commands[0].commands[0];
@@ -219,4 +324,43 @@ public class ChimeraCommandsTest
     // S and G are both coloured #4949b6
     assertTrue(theCommand.contains("color #4949b6 #0:26-30.A|#1:26-30.B"));
   }
+
+  @Test(groups = "Functional")
+  public void testGetAtomSpec()
+  {
+    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");
+    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");
+    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");
+    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
+    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
+    assertEquals(ChimeraCommands.getAtomSpec(model, mockBinding),
+            "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
+  
+  }
 }