JAL-2321 use configured values for structure derived annotation
[jalview.git] / src / jalview / structure / StructureSelectionManager.java
index e9053ed..cd986c0 100644 (file)
@@ -29,14 +29,21 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResults;
+import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceI;
+import jalview.ext.jmol.JmolParser;
+import jalview.gui.IProgressIndicator;
 import jalview.io.AppletFormatAdapter;
+import jalview.io.DataSourceType;
+import jalview.io.StructureFile;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 import jalview.ws.sifts.SiftsClient;
 import jalview.ws.sifts.SiftsException;
+import jalview.ws.sifts.SiftsSettings;
 
 import java.io.PrintStream;
 import java.util.ArrayList;
@@ -45,10 +52,8 @@ import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Vector;
 
 import MCview.Atom;
@@ -61,7 +66,7 @@ public class StructureSelectionManager
 
   static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
 
-  private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
+  private List<StructureMapping> mappings = new ArrayList<>();
 
   private boolean processSecondaryStructure = false;
 
@@ -72,11 +77,11 @@ public class StructureSelectionManager
   /*
    * Set of any registered mappings between (dataset) sequences.
    */
-  public Set<AlignedCodonFrame> seqmappings = new LinkedHashSet<AlignedCodonFrame>();
+  private List<AlignedCodonFrame> seqmappings = new ArrayList<>();
 
-  private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
+  private List<CommandListener> commandListeners = new ArrayList<>();
 
-  private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
+  private List<SelectionListener> sel_listeners = new ArrayList<>();
 
   /**
    * @return true if will try to use external services for processing secondary
@@ -150,8 +155,8 @@ public class StructureSelectionManager
     }
     else
     {
-      System.err.println("reportMapping: There are " + mappings.size()
-              + " mappings.");
+      System.err.println(
+              "reportMapping: There are " + mappings.size() + " mappings.");
       int i = 0;
       for (StructureMapping sm : mappings)
       {
@@ -164,9 +169,9 @@ public class StructureSelectionManager
    * map between the PDB IDs (or structure identifiers) used by Jalview and the
    * absolute filenames for PDB data that corresponds to it
    */
-  Map<String, String> pdbIdFileName = new HashMap<String, String>();
+  Map<String, String> pdbIdFileName = new HashMap<>();
 
-  Map<String, String> pdbFileNameId = new HashMap<String, String>();
+  Map<String, String> pdbFileNameId = new HashMap<>();
 
   public void registerPDBFile(String idForFile, String absoluteFile)
   {
@@ -203,9 +208,8 @@ public class StructureSelectionManager
       {
         if (instances != null)
         {
-          throw new Error(
-                  MessageManager
-                          .getString("error.implementation_error_structure_selection_manager_null"),
+          throw new Error(MessageManager.getString(
+                  "error.implementation_error_structure_selection_manager_null"),
                   new NullPointerException(MessageManager
                           .getString("exception.ssm_context_is_null")));
         }
@@ -218,7 +222,7 @@ public class StructureSelectionManager
     }
     if (instances == null)
     {
-      instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
+      instances = new java.util.IdentityHashMap<>();
     }
     StructureSelectionManager instance = instances.get(context);
     if (instance == null)
@@ -281,7 +285,8 @@ public class StructureSelectionManager
   }
 
   /**
-   * Returns the file name for a mapped PDB id (or null if not mapped).
+   * Returns the filename the PDB id is already mapped to if known, or null if
+   * it is not mapped
    * 
    * @param pdbid
    * @return
@@ -290,7 +295,7 @@ public class StructureSelectionManager
   {
     for (StructureMapping sm : mappings)
     {
-      if (sm.getPdbId().equals(pdbid))
+      if (sm.getPdbId().equalsIgnoreCase(pdbid))
       {
         return sm.pdbfile;
       }
@@ -313,15 +318,44 @@ public class StructureSelectionManager
    *          - how to resolve data from resource
    * @return null or the structure data parsed as a pdb file
    */
-  synchronized public PDBfile setMapping(SequenceI[] sequence,
-          String[] targetChains, String pdbFile, String protocol)
+  synchronized public StructureFile setMapping(SequenceI[] sequence,
+          String[] targetChains, String pdbFile, DataSourceType protocol, 
+          IProgressIndicator progress)
   {
-    return setMapping(true, sequence, targetChains, pdbFile, protocol);
+    return computeMapping(true, sequence, targetChains, pdbFile, protocol,
+            progress);
+  }
+
+  /**
+   * Import a single structure file and register sequence structure mappings for
+   * broadcasting colouring, mouseovers and selection events (convenience
+   * wrapper).
+   * 
+   * @param forStructureView
+   *          when true, record the mapping for use in mouseOvers
+   * @param sequence
+   *          - one or more sequences to be mapped to pdbFile
+   * @param targetChains
+   *          - optional chain specification for mapping each sequence to pdb
+   *          (may be nill, individual elements may be nill)
+   * @param pdbFile
+   *          - structure data resource
+   * @param protocol
+   *          - how to resolve data from resource
+   * @return null or the structure data parsed as a pdb file
+   */
+  synchronized public StructureFile setMapping(boolean forStructureView,
+          SequenceI[] sequenceArray, String[] targetChainIds,
+          String pdbFile, DataSourceType sourceType)
+  {
+    return computeMapping(forStructureView, sequenceArray, targetChainIds,
+            pdbFile, sourceType, null);
   }
 
   /**
    * create sequence structure mappings between each sequence and the given
-   * pdbFile (retrieved via the given protocol).
+   * pdbFile (retrieved via the given protocol). Either constructs a mapping
+   * using NW alignment or derives one from any available SIFTS mapping data.
    * 
    * @param forStructureView
    *          when true, record the mapping for use in mouseOvers
@@ -330,68 +364,62 @@ public class StructureSelectionManager
    *          - one or more sequences to be mapped to pdbFile
    * @param targetChainIds
    *          - optional chain specification for mapping each sequence to pdb
-   *          (may be nill, individual elements may be nill)
+   *          (may be nill, individual elements may be nill) - JBPNote: JAL-2693
+   *          - this should be List<List<String>>, empty lists indicate no
+   *          predefined mappings
    * @param pdbFile
    *          - structure data resource
-   * @param protocol
+   * @param sourceType
    *          - how to resolve data from resource
+   * @param IProgressIndicator
+   *          reference to UI component that maintains a progress bar for the
+   *          mapping operation
    * @return null or the structure data parsed as a pdb file
    */
-  synchronized public PDBfile setMapping(boolean forStructureView,
-          SequenceI[] sequenceArray, String[] targetChainIds,
-          String pdbFile,
-          String protocol)
+  synchronized public StructureFile computeMapping(
+          boolean forStructureView, SequenceI[] sequenceArray,
+          String[] targetChainIds, String pdbFile, DataSourceType sourceType,
+          IProgressIndicator progress)
   {
-    /*
-     * There will be better ways of doing this in the future, for now we'll use
-     * the tried and tested MCview pdb mapping
+    long progressSessionId = System.currentTimeMillis() * 3;
+
+    /**
+     * do we extract and transfer annotation from 3D data ?
      */
-    boolean parseSecStr = processSecondaryStructure;
-    if (isPDBFileRegistered(pdbFile))
-    {
-      for (SequenceI sq : sequenceArray)
-      {
-        SequenceI ds = sq;
-        while (ds.getDatasetSequence() != null)
-        {
-          ds = ds.getDatasetSequence();
-        }
-        ;
-        if (ds.getAnnotation() != null)
-        {
-          for (AlignmentAnnotation ala : ds.getAnnotation())
-          {
-            // false if any annotation present from this structure
-            // JBPNote this fails for jmol/chimera view because the *file* is
-            // passed, not the structure data ID -
-            if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
-            {
-              parseSecStr = false;
-            }
-          }
-        }
-      }
-    }
-    PDBfile pdb = null;
-    boolean isMapUsingSIFTs = Boolean.valueOf(jalview.bin.Cache.getDefault(
-            "MAP_WITH_SIFTS", "false"));
-    SiftsClient siftsClient = null;
+    // FIXME: possibly should just delete
+
+    boolean parseSecStr = processSecondaryStructure
+            ? isStructureFileProcessed(pdbFile, sequenceArray)
+            : false;
+
+    StructureFile pdb = null;
+    boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
     try
     {
-      pdb = new PDBfile(addTempFacAnnot, parseSecStr, secStructServices,
-              pdbFile, protocol);
-
-      if (pdb.id != null && pdb.id.trim().length() > 0
-              && AppletFormatAdapter.FILE.equals(protocol))
+      // FIXME if sourceType is not null, we've lost data here
+      sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
+      pdb = new JmolParser(false, pdbFile, sourceType);
+      pdb.addSettings(parseSecStr && processSecondaryStructure,
+              parseSecStr && addTempFacAnnot,
+              parseSecStr && secStructServices);
+      pdb.doParse();
+      if (pdb.getId() != null && pdb.getId().trim().length() > 0
+              && DataSourceType.FILE == sourceType)
       {
-        registerPDBFile(pdb.id.trim(), pdbFile);
+        registerPDBFile(pdb.getId().trim(), pdbFile);
       }
+      // if PDBId is unavailable then skip SIFTS mapping execution path
+      isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable();
+
     } catch (Exception ex)
     {
       ex.printStackTrace();
       return null;
     }
-
+    /*
+     * sifts client - non null if SIFTS mappings are to be used 
+     */
+    SiftsClient siftsClient = null;
     try
     {
       if (isMapUsingSIFTs)
@@ -402,6 +430,7 @@ public class StructureSelectionManager
     {
       isMapUsingSIFTs = false;
       e.printStackTrace();
+      siftsClient = null;
     }
 
     String targetChainId;
@@ -409,6 +438,12 @@ public class StructureSelectionManager
     {
       boolean infChain = true;
       final SequenceI seq = sequenceArray[s];
+      SequenceI ds = seq;
+      while (ds.getDatasetSequence() != null)
+      {
+        ds = ds.getDatasetSequence();
+      }
+
       if (targetChainIds != null && targetChainIds[s] != null)
       {
         infChain = false;
@@ -416,8 +451,8 @@ public class StructureSelectionManager
       }
       else if (seq.getName().indexOf("|") > -1)
       {
-        targetChainId = seq.getName().substring(
-                seq.getName().lastIndexOf("|") + 1);
+        targetChainId = seq.getName()
+                .substring(seq.getName().lastIndexOf("|") + 1);
         if (targetChainId.length() > 1)
         {
           if (targetChainId.trim().length() == 0)
@@ -440,12 +475,12 @@ public class StructureSelectionManager
        * Attempt pairwise alignment of the sequence with each chain in the PDB,
        * and remember the highest scoring chain
        */
-      int max = -10;
+      float max = -10;
       AlignSeq maxAlignseq = null;
       String maxChainId = " ";
       PDBChain maxChain = null;
       boolean first = true;
-      for (PDBChain chain : pdb.chains)
+      for (PDBChain chain : pdb.getChains())
       {
         if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
                 && !infChain)
@@ -477,75 +512,215 @@ public class StructureSelectionManager
         continue;
       }
 
-      if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
+      if (sourceType == DataSourceType.PASTE)
       {
-        pdbFile = "INLINE" + pdb.id;
+        pdbFile = "INLINE" + pdb.getId();
       }
 
-      ArrayList<StructureMapping> seqToStrucMapping = null;
-      if (isMapUsingSIFTs)
+      List<StructureMapping> seqToStrucMapping = new ArrayList<>();
+      if (isMapUsingSIFTs && seq.isProtein())
       {
-        try
+        if (progress!=null) {
+          progress.setProgressBar(MessageManager
+                .getString("status.obtaining_mapping_with_sifts"),
+                  progressSessionId);
+        }
+        jalview.datamodel.Mapping sqmpping = maxAlignseq
+                .getMappingFromS1(false);
+        if (targetChainId != null && !targetChainId.trim().isEmpty())
         {
-          jalview.datamodel.Mapping sqmpping = maxAlignseq
-                  .getMappingFromS1(false);
-          seqToStrucMapping = new ArrayList<StructureMapping>();
-          if (targetChainId != null && !targetChainId.trim().isEmpty())
+          StructureMapping siftsMapping;
+          try
           {
-            StructureMapping curChainMapping = siftsClient
-                    .getSiftsStructureMapping(seq, pdbFile, targetChainId);
-            seqToStrucMapping.add(curChainMapping);
-            maxChainId = targetChainId;
-            PDBChain chain = pdb.findChain(targetChainId);
-            if (chain != null)
+            siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
+                    pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
+            seqToStrucMapping.add(siftsMapping);
+            maxChain.makeExactMapping(siftsMapping, seq);
+            maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS");// FIXME: is this
+                                                       // "IEA:SIFTS" ?
+            maxChain.transferResidueAnnotation(siftsMapping, null);
+            ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
+
+          } catch (SiftsException e)
+          {
+            // fall back to NW alignment
+            System.err.println(e.getMessage());
+            StructureMapping nwMapping = getNWMappings(seq, pdbFile,
+                    targetChainId, maxChain, pdb, maxAlignseq);
+            seqToStrucMapping.add(nwMapping);
+            maxChain.makeExactMapping(maxAlignseq, seq);
+            maxChain.transferRESNUMFeatures(seq, "IEA:Jalview"); // FIXME: is
+                                                                 // this
+                                                        // "IEA:Jalview" ?
+            maxChain.transferResidueAnnotation(nwMapping, sqmpping);
+            ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
+          }
+        }
+        else
+        {
+          List<StructureMapping> foundSiftsMappings = new ArrayList<>();
+          for (PDBChain chain : pdb.getChains())
+          {
+            StructureMapping siftsMapping = null;
+            try
+            {
+              siftsMapping = getStructureMapping(seq,
+                      pdbFile, chain.id, pdb, chain, sqmpping, maxAlignseq,
+                      siftsClient);
+              foundSiftsMappings.add(siftsMapping);
+              chain.makeExactMapping(siftsMapping, seq);
+              chain.transferRESNUMFeatures(seq, "IEA: SIFTS");// FIXME: is this
+              // "IEA:SIFTS" ?
+              chain.transferResidueAnnotation(siftsMapping, null);
+            } catch (SiftsException e)
             {
-              chain.transferResidueAnnotation(curChainMapping, sqmpping);
+              System.err.println(e.getMessage());
             }
+            catch (Exception e)
+            {
+              System.err
+                      .println(
+                              "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair");
+              System.err.println(e.getMessage());
+            }
+          }
+          if (!foundSiftsMappings.isEmpty())
+          {
+            seqToStrucMapping.addAll(foundSiftsMappings);
+            ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
           }
           else
           {
-            for (PDBChain chain : pdb.chains)
-            {
-              StructureMapping curChainMapping = siftsClient
-                      .getSiftsStructureMapping(seq, pdbFile, chain.id);
-              seqToStrucMapping.add(curChainMapping);
-              maxChainId = chain.id;
-              chain.transferResidueAnnotation(curChainMapping, sqmpping);
-            }
+            StructureMapping nwMapping = getNWMappings(seq, pdbFile,
+                    maxChainId, maxChain, pdb, maxAlignseq);
+            seqToStrucMapping.add(nwMapping);
+            maxChain.transferRESNUMFeatures(seq, null); // FIXME: is this
+                                                        // "IEA:Jalview" ?
+            maxChain.transferResidueAnnotation(nwMapping, sqmpping);
+            ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
           }
-        } catch (SiftsException e)
-        {
-          e.printStackTrace();
-          System.err
-                  .println(">>>>>>> SIFTs mapping could not be obtained... Now mapping with NW alignment");
-          seqToStrucMapping = getNWMappings(seq, pdbFile, maxChainId,
-                  maxChain, pdb, maxAlignseq);
         }
       }
       else
       {
-        seqToStrucMapping = getNWMappings(seq, pdbFile,
-                maxChainId, maxChain, pdb,
-                maxAlignseq);
+        if (progress != null)
+        {
+          progress.setProgressBar(MessageManager
+                                 .getString("status.obtaining_mapping_with_nw_alignment"),
+                  progressSessionId);
+        }
+        StructureMapping nwMapping = getNWMappings(seq, pdbFile, maxChainId,
+                maxChain, pdb, maxAlignseq);
+        seqToStrucMapping.add(nwMapping);
+        ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
       }
-
       if (forStructureView)
       {
-        // mappings.add(seqToStrucMapping);
-        mappings.addAll(seqToStrucMapping);
+        for (StructureMapping sm : seqToStrucMapping)
+        {
+          addStructureMapping(sm); // not addAll!
+        }
+      }
+      if (progress != null)
+      {
+        progress.setProgressBar(null, progressSessionId);
       }
     }
     return pdb;
   }
 
-  private ArrayList<StructureMapping> getNWMappings(SequenceI seq,
-          String pdbFile,
-          String maxChainId, PDBChain maxChain, PDBfile pdb,
+  /**
+   * check if we need to extract secondary structure from given pdbFile and
+   * transfer to sequences
+   * 
+   * @param pdbFile
+   * @param sequenceArray
+   * @return
+   */
+  private boolean isStructureFileProcessed(String pdbFile,
+          SequenceI[] sequenceArray)
+  {
+    boolean parseSecStr = true;
+    if (isPDBFileRegistered(pdbFile))
+    {
+      for (SequenceI sq : sequenceArray)
+      {
+        SequenceI ds = sq;
+        while (ds.getDatasetSequence() != null)
+        {
+          ds = ds.getDatasetSequence();
+        }
+        ;
+        if (ds.getAnnotation() != null)
+        {
+          for (AlignmentAnnotation ala : ds.getAnnotation())
+          {
+            // false if any annotation present from this structure
+            // JBPNote this fails for jmol/chimera view because the *file* is
+            // passed, not the structure data ID -
+            if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
+            {
+              parseSecStr = false;
+            }
+          }
+        }
+      }
+    }
+    return parseSecStr;
+  }
+
+  public void addStructureMapping(StructureMapping sm)
+  {
+    if (!mappings.contains(sm))
+    {
+      mappings.add(sm);
+    }
+  }
+
+  /**
+   * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
+   * uniprot or PDB
+   * 
+   * @param seq
+   * @param pdbFile
+   * @param targetChainId
+   * @param pdb
+   * @param maxChain
+   * @param sqmpping
+   * @param maxAlignseq
+   * @param siftsClient
+   *          client for retrieval of SIFTS mappings for this structure
+   * @return
+   * @throws SiftsException
+   */
+  private StructureMapping getStructureMapping(SequenceI seq,
+          String pdbFile, String targetChainId, StructureFile pdb,
+          PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
+          AlignSeq maxAlignseq, SiftsClient siftsClient) throws SiftsException
+  {
+    StructureMapping curChainMapping = siftsClient
+            .getSiftsStructureMapping(seq, pdbFile, targetChainId);
+    try
+    {
+      PDBChain chain = pdb.findChain(targetChainId);
+      if (chain != null)
+      {
+        chain.transferResidueAnnotation(curChainMapping, null);
+      }
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+    return curChainMapping;
+  }
+
+  private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
+          String maxChainId, PDBChain maxChain, StructureFile pdb,
           AlignSeq maxAlignseq)
   {
     final StringBuilder mappingDetails = new StringBuilder(128);
-    mappingDetails.append(NEWLINE).append(
-            "Sequence \u27f7 Structure mapping details");
+    mappingDetails.append(NEWLINE)
+            .append("Sequence \u27f7 Structure mapping details");
     mappingDetails.append(NEWLINE);
     mappingDetails
             .append("Method: inferred with Needleman & Wunsch alignment");
@@ -577,33 +752,36 @@ public class StructureSelectionManager
             .append(" ");
     mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
     mappingDetails.append(NEWLINE).append("SEQ start/end ");
-    mappingDetails.append(
-            String.valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
+    mappingDetails
+            .append(String
+                    .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
             .append(" ");
-    mappingDetails.append(String.valueOf(maxAlignseq.seq1end
-            + (seq.getStart() - 1)));
+    mappingDetails.append(
+            String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
     mappingDetails.append(NEWLINE);
     maxChain.makeExactMapping(maxAlignseq, seq);
     jalview.datamodel.Mapping sqmpping = maxAlignseq
             .getMappingFromS1(false);
     maxChain.transferRESNUMFeatures(seq, null);
 
-    HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
+    HashMap<Integer, int[]> mapping = new HashMap<>();
     int resNum = -10000;
     int index = 0;
+    char insCode = ' ';
 
     do
     {
       Atom tmp = maxChain.atoms.elementAt(index);
-      if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
+      if ((resNum != tmp.resNumber || insCode != tmp.insCode)
+              && tmp.alignmentMapping != -1)
       {
         resNum = tmp.resNumber;
+        insCode = tmp.insCode;
         if (tmp.alignmentMapping >= -1)
         {
-          // TODO (JAL-1836) address root cause: negative residue no in PDB
-          // file
-          mapping.put(tmp.alignmentMapping + 1, new int[] { tmp.resNumber,
-              tmp.atomIndex });
+          mapping.put(tmp.alignmentMapping + 1,
+                  new int[]
+                  { tmp.resNumber, tmp.atomIndex });
         }
       }
 
@@ -611,11 +789,9 @@ public class StructureSelectionManager
     } while (index < maxChain.atoms.size());
 
     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
-            pdb.id, maxChainId, mapping, mappingDetails.toString());
+            pdb.getId(), maxChainId, mapping, mappingDetails.toString());
     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
-    ArrayList<StructureMapping> mappings = new ArrayList<StructureMapping>();
-    mappings.add(nwMapping);
-    return mappings;
+    return nwMapping;
   }
 
   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
@@ -642,7 +818,7 @@ public class StructureSelectionManager
      * Remove mappings to the closed listener's PDB files, but first check if
      * another listener is still interested
      */
-    List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
+    List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
 
     StructureListener sl;
     for (int i = 0; i < listeners.size(); i++)
@@ -650,7 +826,7 @@ public class StructureSelectionManager
       if (listeners.elementAt(i) instanceof StructureListener)
       {
         sl = (StructureListener) listeners.elementAt(i);
-        for (String pdbfile : sl.getPdbFile())
+        for (String pdbfile : sl.getStructureFiles())
         {
           pdbs.remove(pdbfile);
         }
@@ -663,7 +839,7 @@ public class StructureSelectionManager
      */
     if (pdbs.size() > 0)
     {
-      List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+      List<StructureMapping> tmp = new ArrayList<>();
       for (StructureMapping sm : mappings)
       {
         if (!pdbs.contains(sm.pdbfile))
@@ -683,7 +859,8 @@ public class StructureSelectionManager
    * @param chain
    * @param pdbfile
    */
-  public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
+  public void mouseOverStructure(int pdbResNum, String chain,
+          String pdbfile)
   {
     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
@@ -715,7 +892,29 @@ public class StructureSelectionManager
       return;
     }
 
-    SearchResults results = new SearchResults();
+    SearchResultsI results = findAlignmentPositionsForStructurePositions(
+            atoms);
+    for (Object li : listeners)
+    {
+      if (li instanceof SequenceListener)
+      {
+        ((SequenceListener) li).highlightSequence(results);
+      }
+    }
+  }
+
+  /**
+   * Constructs a SearchResults object holding regions (if any) in the Jalview
+   * alignment which have a mapping to the structure viewer positions in the
+   * supplied list
+   * 
+   * @param atoms
+   * @return
+   */
+  public SearchResultsI findAlignmentPositionsForStructurePositions(
+          List<AtomSpec> atoms)
+  {
+    SearchResultsI results = new SearchResults();
     for (AtomSpec atom : atoms)
     {
       SequenceI lastseq = null;
@@ -726,7 +925,7 @@ public class StructureSelectionManager
                 && sm.pdbchain.equals(atom.getChain()))
         {
           int indexpos = sm.getSeqPos(atom.getPdbResNum());
-          if (lastipos != indexpos && lastseq != sm.sequence)
+          if (lastipos != indexpos || lastseq != sm.sequence)
           {
             results.addResult(sm.sequence, indexpos, indexpos);
             lastipos = indexpos;
@@ -740,13 +939,7 @@ public class StructureSelectionManager
         }
       }
     }
-    for (Object li : listeners)
-    {
-      if (li instanceof SequenceListener)
-      {
-        ((SequenceListener) li).highlightSequence(results);
-      }
-    }
+    return results;
   }
 
   /**
@@ -756,19 +949,19 @@ public class StructureSelectionManager
    *          the sequence that the mouse over occurred on
    * @param indexpos
    *          the absolute position being mouseovered in seq (0 to seq.length())
-   * @param index
+   * @param seqPos
    *          the sequence position (if -1, seq.findPosition is called to
    *          resolve the residue number)
    */
-  public void mouseOverSequence(SequenceI seq, int indexpos, int index,
+  public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
           VamsasSource source)
   {
     boolean hasSequenceListeners = handlingVamsasMo
             || !seqmappings.isEmpty();
-    SearchResults results = null;
-    if (index == -1)
+    SearchResultsI results = null;
+    if (seqPos == -1)
     {
-      index = seq.findPosition(indexpos);
+      seqPos = seq.findPosition(indexpos);
     }
     for (int i = 0; i < listeners.size(); i++)
     {
@@ -781,7 +974,7 @@ public class StructureSelectionManager
       }
       if (listener instanceof StructureListener)
       {
-        highlightStructure((StructureListener) listener, seq, index);
+        highlightStructure((StructureListener) listener, seq, seqPos);
       }
       else
       {
@@ -795,12 +988,12 @@ public class StructureSelectionManager
             {
               if (results == null)
               {
-                results = MappingUtils.buildSearchResults(seq, index,
+                results = MappingUtils.buildSearchResults(seq, seqPos,
                         seqmappings);
               }
               if (handlingVamsasMo)
               {
-                results.addResult(seq, index, index);
+                results.addResult(seq, seqPos, seqPos);
 
               }
               if (!results.isEmpty())
@@ -818,7 +1011,7 @@ public class StructureSelectionManager
         else if (listener instanceof SecondaryStructureListener)
         {
           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
-                  indexpos, index);
+                  indexpos, seqPos);
         }
       }
     }
@@ -826,31 +1019,36 @@ public class StructureSelectionManager
 
   /**
    * Send suitable messages to a StructureListener to highlight atoms
-   * corresponding to the given sequence position.
+   * corresponding to the given sequence position(s)
    * 
    * @param sl
    * @param seq
-   * @param index
+   * @param positions
    */
-  protected void highlightStructure(StructureListener sl, SequenceI seq,
-          int index)
+  public void highlightStructure(StructureListener sl, SequenceI seq,
+          int... positions)
   {
     if (!sl.isListeningFor(seq))
     {
       return;
     }
     int atomNo;
-    List<AtomSpec> atoms = new ArrayList<AtomSpec>();
+    List<AtomSpec> atoms = new ArrayList<>();
     for (StructureMapping sm : mappings)
     {
-      if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
+      if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
+              || (sm.sequence.getDatasetSequence() != null && sm.sequence
+                      .getDatasetSequence() == seq.getDatasetSequence()))
       {
-        atomNo = sm.getAtomNum(index);
-
-        if (atomNo > 0)
+        for (int index : positions)
         {
-          atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
-                  .getPDBResNum(index), atomNo));
+          atomNo = sm.getAtomNum(index);
+
+          if (atomNo > 0)
+          {
+            atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
+                    sm.getPDBResNum(index), atomNo));
+          }
         }
       }
     }
@@ -943,7 +1141,7 @@ public class StructureSelectionManager
 
   public StructureMapping[] getMapping(String pdbfile)
   {
-    List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+    List<StructureMapping> tmp = new ArrayList<>();
     for (StructureMapping sm : mappings)
     {
       if (sm.pdbfile.equals(pdbfile))
@@ -1006,13 +1204,13 @@ public class StructureSelectionManager
   /**
    * Add each of the given codonFrames to the stored set, if not aready present.
    * 
-   * @param set
+   * @param mappings
    */
-  public void registerMappings(Set<AlignedCodonFrame> set)
+  public void registerMappings(List<AlignedCodonFrame> mappings)
   {
-    if (set != null)
+    if (mappings != null)
     {
-      for (AlignedCodonFrame acf : set)
+      for (AlignedCodonFrame acf : mappings)
       {
         registerMapping(acf);
       }
@@ -1091,18 +1289,19 @@ public class StructureSelectionManager
 
   public synchronized void sendSelection(
           jalview.datamodel.SequenceGroup selection,
-          jalview.datamodel.ColumnSelection colsel, SelectionSource source)
+          jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
+          SelectionSource source)
   {
     for (SelectionListener slis : sel_listeners)
     {
       if (slis != source)
       {
-        slis.selection(selection, colsel, source);
+        slis.selection(selection, colsel, hidden, source);
       }
     }
   }
 
-  Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
+  Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
 
   public synchronized void sendViewPosition(
           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
@@ -1214,8 +1413,8 @@ public class StructureSelectionManager
   {
     if (command instanceof EditCommand)
     {
-      return MappingUtils.mapEditCommand((EditCommand) command, undo,
-              mapTo, gapChar, seqmappings);
+      return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
+              gapChar, seqmappings);
     }
     else if (command instanceof OrderCommand)
     {
@@ -1224,4 +1423,10 @@ public class StructureSelectionManager
     }
     return null;
   }
+
+  public List<AlignedCodonFrame> getSequenceMappings()
+  {
+    return seqmappings;
+  }
+
 }