JAL-3390 pull up of getShownResidues() to AAStructureBindingModel
[jalview.git] / src / jalview / ext / rbvi / chimera / JalviewChimeraBinding.java
index 18a614d..44bcbe4 100644 (file)
  */
 package jalview.ext.rbvi.chimera;
 
+import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
+import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SearchResults;
-import jalview.datamodel.SearchResults.Match;
+import jalview.datamodel.SearchResultMatchI;
+import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.httpserver.AbstractRequestHandler;
+import jalview.io.DataSourceType;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
@@ -47,7 +48,9 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.BindException;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
+import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -61,8 +64,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 {
   public static final String CHIMERA_FEATURE_GROUP = "Chimera";
 
-  private static final String CHIMERA_FEATURE_PREFIX = "chim_";
-
   // Chimera clause to exclude alternate locations in atom selection
   private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
 
@@ -75,6 +76,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 
   private static final String ALPHACARBON = "CA";
 
+  private List<String> chainNames = new ArrayList<>();
+
+  private Hashtable<String, String> chainFile = new Hashtable<>();
+
   /*
    * Object through which we talk to Chimera
    */
@@ -98,12 +103,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   private boolean loadingFinished = true;
 
-  public String fileLoadingError;
-
   /*
    * Map of ChimeraModel objects keyed by PDB full local file name
    */
-  private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
+  private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<>();
 
   String lastHighlightCommand;
 
@@ -114,6 +117,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   private long loadNotifiesHandled = 0;
 
+  private Thread chimeraMonitor;
+
   /**
    * Open a PDB structure file in Chimera and set up mappings from Jalview.
    * 
@@ -128,7 +133,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     String file = pe.getFile();
     try
     {
-      List<ChimeraModel> modelsToMap = new ArrayList<ChimeraModel>();
+      List<ChimeraModel> modelsToMap = new ArrayList<>();
       List<ChimeraModel> oldList = viewer.getModelList();
       boolean alreadyOpen = false;
 
@@ -167,13 +172,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       if (getSsm() != null)
       {
         getSsm().addStructureViewerListener(this);
-        // ssm.addSelectionListener(this);
-        FeatureRenderer fr = getFeatureRenderer(null);
-        if (fr != null)
-        {
-          fr.featuresAdded();
-        }
-        refreshGUI();
       }
       return true;
     } catch (Exception q)
@@ -191,16 +189,45 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * @param ssm
    * @param pdbentry
    * @param sequenceIs
-   * @param chains
    * @param protocol
    */
   public JalviewChimeraBinding(StructureSelectionManager ssm,
-          PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
-          String protocol)
+          PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
+          DataSourceType protocol)
   {
-    super(ssm, pdbentry, sequenceIs, chains, protocol);
-    viewer = new ChimeraManager(
-            new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
+    super(ssm, pdbentry, sequenceIs, protocol);
+    viewer = new ChimeraManager(new StructureManager(true));
+  }
+
+  /**
+   * Starts a thread that waits for the Chimera process to finish, so that we
+   * can then close the associated resources. This avoids leaving orphaned
+   * Chimera viewer panels in Jalview if the user closes Chimera.
+   */
+  protected void startChimeraProcessMonitor()
+  {
+    final Process p = viewer.getChimeraProcess();
+    chimeraMonitor = new Thread(new Runnable()
+    {
+
+      @Override
+      public void run()
+      {
+        try
+        {
+          p.waitFor();
+          JalviewStructureDisplayI display = getViewer();
+          if (display != null)
+          {
+            display.closeViewer(false);
+          }
+        } catch (InterruptedException e)
+        {
+          // exit thread if Chimera Viewer is closed in Jalview
+        }
+      }
+    });
+    chimeraMonitor.start();
   }
 
   /**
@@ -215,55 +242,9 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       viewer.startListening(chimeraListener.getUri());
     } catch (BindException e)
     {
-      System.err.println("Failed to start Chimera listener: "
-              + e.getMessage());
-    }
-  }
-
-  /**
-   * Construct a title string for the viewer window based on the data Jalview
-   * knows about
-   * 
-   * @param verbose
-   * @return
-   */
-  public String getViewerTitle(boolean verbose)
-  {
-    return getViewerTitle(CHIMERA_FEATURE_GROUP, verbose);
-  }
-
-  /**
-   * Tells Chimera to display only the specified chains
-   * 
-   * @param toshow
-   */
-  public void showChains(List<String> toshow)
-  {
-    /*
-     * Construct a chimera command like
-     * 
-     * ~display #*;~ribbon #*;ribbon :.A,:.B
-     */
-    StringBuilder cmd = new StringBuilder(64);
-    boolean first = true;
-    for (String chain : toshow)
-    {
-      if (!first)
-      {
-        cmd.append(",");
-      }
-      cmd.append(":.").append(chain);
-      first = false;
+      System.err.println(
+              "Failed to start Chimera listener: " + e.getMessage());
     }
-
-    /*
-     * could append ";focus" to this command to resize the display to fill the
-     * window, but it looks more helpful not to (easier to relate chains to the
-     * whole)
-     */
-    final String command = "~display #*; ~ribbon #*; ribbon "
-            + cmd.toString();
-    sendChimeraCommand(command, false);
   }
 
   /**
@@ -272,7 +253,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   public void closeViewer(boolean closeChimera)
   {
-    getSsm().removeStructureViewerListener(this, this.getPdbFile());
+    getSsm().removeStructureViewerListener(this, this.getStructureFiles());
     if (closeChimera)
     {
       viewer.exitChimera();
@@ -284,9 +265,14 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
     viewer = null;
 
+    if (chimeraMonitor != null)
+    {
+      chimeraMonitor.interrupt();
+    }
     releaseUIResources();
   }
 
+  @Override
   public void colourByChain()
   {
     colourBySequence = false;
@@ -302,6 +288,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * <li>all others - white</li>
    * </ul>
    */
+  @Override
   public void colourByCharge()
   {
     colourBySequence = false;
@@ -310,28 +297,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Construct and send a command to align structures against a reference
-   * structure, based on one or more sequence alignments
-   * 
-   * @param _alignment
-   *          an array of alignments to process
-   * @param _refStructure
-   *          an array of corresponding reference structures (index into pdb
-   *          file array); if a negative value is passed, the first PDB file
-   *          mapped to an alignment sequence is used as the reference for
-   *          superposition
-   * @param _hiddenCols
-   *          an array of corresponding hidden columns for each alignment
+   * {@inheritDoc}
    */
-  public void superposeStructures(AlignmentI[] _alignment,
-          int[] _refStructure, ColumnSelection[] _hiddenCols)
+  @Override
+  public String superposeStructures(AlignmentI[] _alignment,
+          int[] _refStructure, HiddenColumns[] _hiddenCols)
   {
     StringBuilder allComs = new StringBuilder(128);
-    String[] files = getPdbFile();
+    String[] files = getStructureFiles();
 
     if (!waitForFileLoad(files))
     {
-      return;
+      return null;
     }
 
     refreshPdbEntries();
@@ -340,7 +317,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     {
       int refStructure = _refStructure[a];
       AlignmentI alignment = _alignment[a];
-      ColumnSelection hiddenCols = _hiddenCols[a];
+      HiddenColumns hiddenCols = _hiddenCols[a];
 
       if (refStructure >= files.length)
       {
@@ -350,13 +327,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
 
       /*
-       * 'matched' array will hold 'true' for visible alignment columns where
+       * 'matched' bit i will be set for visible alignment columns i where
        * all sequences have a residue with a mapping to the PDB structure
        */
-      boolean matched[] = new boolean[alignment.getWidth()];
-      for (int m = 0; m < matched.length; m++)
+      BitSet matched = new BitSet();
+      for (int m = 0; m < alignment.getWidth(); m++)
       {
-        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+        if (hiddenCols == null || hiddenCols.isVisible(m))
+        {
+          matched.set(m);
+        }
       }
 
       SuperposeData[] structures = new SuperposeData[files.length];
@@ -380,17 +360,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         refStructure = candidateRefStructure;
       }
 
-      int nmatched = 0;
-      for (boolean b : matched)
-      {
-        if (b)
-        {
-          nmatched++;
-        }
-      }
+      int nmatched = matched.cardinality();
       if (nmatched < 4)
       {
-        // TODO: bail out here because superposition illdefined?
+        return MessageManager.formatMessage("label.insufficient_residues",
+                nmatched);
       }
 
       /*
@@ -403,41 +377,41 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         int lpos = -1;
         boolean run = false;
         StringBuilder molsel = new StringBuilder();
-        for (int r = 0; r < matched.length; r++)
+
+        int nextColumnMatch = matched.nextSetBit(0);
+        while (nextColumnMatch != -1)
         {
-          if (matched[r])
+          int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch];
+          if (lpos != pdbResNum - 1)
           {
-            int pdbResNum = structures[pdbfnum].pdbResNo[r];
-            if (lpos != pdbResNum - 1)
+            /*
+             * discontiguous - append last residue now
+             */
+            if (lpos != -1)
             {
-              /*
-               * discontiguous - append last residue now
-               */
-              if (lpos != -1)
-              {
-                molsel.append(String.valueOf(lpos));
-                molsel.append(chainCd);
-                molsel.append(",");
-              }
-              run = false;
+              molsel.append(String.valueOf(lpos));
+              molsel.append(chainCd);
+              molsel.append(",");
             }
-            else
+            run = false;
+          }
+          else
+          {
+            /*
+             * extending a contiguous run
+             */
+            if (!run)
             {
               /*
-               * extending a contiguous run
+               * start the range selection
                */
-              if (!run)
-              {
-                /*
-                 * start the range selection
-                 */
-                molsel.append(String.valueOf(lpos));
-                molsel.append("-");
-              }
-              run = true;
+              molsel.append(String.valueOf(lpos));
+              molsel.append("-");
             }
-            lpos = pdbResNum;
+            run = true;
           }
+          lpos = pdbResNum;
+          nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
         }
 
         /*
@@ -488,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);
@@ -505,14 +480,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         if (debug)
         {
           System.out.println("Select regions:\n" + selectioncom.toString());
-          System.out.println("Superimpose command(s):\n"
-                  + command.toString());
+          System.out.println(
+                  "Superimpose command(s):\n" + command.toString());
         }
-        allComs.append("~display all; chain @CA|P; ribbon ")
-                .append(selectioncom.toString())
+        allComs/*.append("~display all; chain @CA|P; ribbon ")
+                .append(selectioncom.toString())*/
                 .append(";" + command.toString());
       }
     }
+
+    String error = null;
     if (selectioncom.length() > 0)
     {
       // TODO: visually distinguish regions that were superposed
@@ -524,11 +501,30 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       {
         System.out.println("Select regions:\n" + selectioncom.toString());
       }
-      allComs.append("; ~display all; chain @CA|P; ribbon ")
-              .append(selectioncom.toString()).append("; focus");
-      sendChimeraCommand(allComs.toString(), false);
+      allComs.append("; ~display "); // all");
+      if (!isShowAlignmentOnly())
+      {
+        allComs.append("; ribbon; chain @CA|P");
+      }
+      else
+      {
+        allComs.append("; ~ribbon");
+      }
+      allComs.append("; ribbon ").append(selectioncom.toString())
+              .append("; focus");
+      List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
+              true);
+      for (String reply : chimeraReplies)
+      {
+        String lowerCase = reply.toLowerCase();
+        if (lowerCase.contains("unequal numbers of atoms")
+                || lowerCase.contains("at least"))
+        {
+          error = reply;
+        }
+      }
     }
-
+    return error;
   }
 
   /**
@@ -544,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())
     {
@@ -557,30 +554,37 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      * to the Chimera command 'list models type molecule', see
      * ChimeraManager.getModelList().
      */
-    List<ChimeraModel> maps = chimeraMaps.get(getPdbFile()[pdbfnum]);
+    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;
   }
 
   /**
    * Launch Chimera, unless an instance linked to this object is already
-   * running. Returns true if chimera is successfully launched, or already
+   * running. Returns true if Chimera is successfully launched, or already
    * running, else false.
    * 
    * @return
    */
   public boolean launchChimera()
   {
-    if (!viewer.isChimeraLaunched())
-    {
-      return viewer.launchChimera(StructureManager.getChimeraPaths());
-    }
     if (viewer.isChimeraLaunched())
     {
       return true;
     }
-    log("Failed to launch Chimera!");
-    return false;
+
+    boolean launched = viewer
+            .launchChimera(StructureManager.getChimeraPaths());
+    if (launched)
+    {
+      startChimeraProcessMonitor();
+    }
+    else
+    {
+      log("Failed to launch Chimera!");
+    }
+    return launched;
   }
 
   /**
@@ -644,39 +648,34 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
           String progressMsg);
 
   /**
-   * colour any structures associated with sequences in the given alignment
-   * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
-   * if colourBySequence is enabled.
+   * Sends a set of colour commands to the structure viewer
+   * 
+   * @param colourBySequenceCommands
    */
-  public void colourBySequence(boolean showFeatures,
-          jalview.api.AlignmentViewPanel alignmentv)
+  @Override
+  protected void colourBySequence(
+          StructureMappingcommandSet[] colourBySequenceCommands)
   {
-    if (!colourBySequence || !loadingFinished)
-    {
-      return;
-    }
-    if (getSsm() == null)
-    {
-      return;
-    }
-    String[] files = getPdbFile();
-
-    SequenceRenderer sr = getSequenceRenderer(alignmentv);
-
-    FeatureRenderer fr = null;
-    if (showFeatures)
+    for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
     {
-      fr = getFeatureRenderer(alignmentv);
+      for (String command : cpdbbyseq.commands)
+      {
+        sendAsynchronousCommand(command, COLOURING_CHIMERA);
+      }
     }
-    AlignmentI alignment = alignmentv.getAlignment();
+  }
 
-    StructureMappingcommandSet colourBySequenceCommands = ChimeraCommands
-            .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
-                    fr, alignment);
-    for (String command : colourBySequenceCommands.commands)
-    {
-      sendAsynchronousCommand(command, COLOURING_CHIMERA);
-    }
+  /**
+   * @param files
+   * @param viewPanel
+   * @return
+   */
+  @Override
+  protected StructureMappingcommandSet[] getColourBySequenceCommands(
+          String[] files, AlignmentViewPanel viewPanel)
+  {
+    return ChimeraCommands.getColourBySequenceCommand(files, viewPanel,
+            this);
   }
 
   /**
@@ -706,40 +705,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   // //////////////////////////
 
   /**
-   * returns the current featureRenderer that should be used to colour the
-   * structures
-   * 
-   * @param alignment
-   * 
-   * @return
-   */
-  public abstract FeatureRenderer getFeatureRenderer(
-          AlignmentViewPanel alignment);
-
-  /**
    * instruct the Jalview binding to update the pdbentries vector if necessary
    * prior to matching the viewer's contents to the list of structure files
    * Jalview knows about.
    */
   public abstract void refreshPdbEntries();
 
-  private int getModelNum(String modelFileName)
-  {
-    String[] mfn = getPdbFile();
-    if (mfn == null)
-    {
-      return -1;
-    }
-    for (int i = 0; i < mfn.length; i++)
-    {
-      if (mfn[i].equalsIgnoreCase(modelFileName))
-      {
-        return i;
-      }
-    }
-    return -1;
-  }
-
   /**
    * map between index of model filename returned from getPdbFile and the first
    * index of models from this file in the viewer. Note - this is not trimmed -
@@ -750,39 +721,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   // ////////////////////////////////
   // /StructureListener
   @Override
-  public synchronized String[] getPdbFile()
+  public synchronized String[] getStructureFiles()
   {
     if (viewer == null)
     {
       return new String[0];
     }
-    // if (modelFileNames == null)
-    // {
-    // Collection<ChimeraModel> chimodels = viewer.getChimeraModels();
-    // _modelFileNameMap = new int[chimodels.size()];
-    // int j = 0;
-    // for (ChimeraModel chimodel : chimodels)
-    // {
-    // String mdlName = chimodel.getModelName();
-    // }
-    // modelFileNames = new String[j];
-    // // System.arraycopy(mset, 0, modelFileNames, 0, j);
-    // }
-
-    return chimeraMaps.keySet().toArray(
-            modelFileNames = new String[chimeraMaps.size()]);
-  }
 
-  /**
-   * returns the current sequenceRenderer that should be used to colour the
-   * structures
-   * 
-   * @param alignment
-   * 
-   * @return
-   */
-  public abstract SequenceRenderer getSequenceRenderer(
-          AlignmentViewPanel alignment);
+    return chimeraMaps.keySet()
+            .toArray(modelFileNames = new String[chimeraMaps.size()]);
+  }
 
   /**
    * Construct and send a command to highlight zero, one or more atoms. We do
@@ -865,7 +813,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      * Parse model number, residue and chain for each selected position,
      * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain)
      */
-    List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(selection);
+    List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(
+            selection);
 
     /*
      * Broadcast the selection (which may be empty, if the user just cleared all
@@ -884,7 +833,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   protected List<AtomSpec> convertStructureResiduesToAlignment(
           List<String> structureSelection)
   {
-    List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
+    List<AtomSpec> atomSpecs = new ArrayList<>();
     for (String atomSpec : structureSelection)
     {
       try
@@ -941,6 +890,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     return loadNotifiesHandled;
   }
 
+  @Override
   public void setJalviewColourScheme(ColourSchemeI cs)
   {
     colourBySequence = false;
@@ -950,19 +900,24 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       return;
     }
 
-    // Chimera expects RBG values in the range 0-1
+    // Chimera expects RGB values in the range 0-1
     final double normalise = 255D;
     viewerCommandHistory(false);
     StringBuilder command = new StringBuilder(128);
 
     List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
             false);
-    for (String res : residueSet)
+    for (String resName : residueSet)
     {
-      Color col = cs.findColour(res.charAt(0));
-      command.append("color " + col.getRed() / normalise + ","
-              + col.getGreen() / normalise + "," + col.getBlue()
-              / normalise + " ::" + res + ";");
+      char res = resName.length() == 3
+              ? ResidueProperties.getSingleCharacterCode(resName)
+              : resName.charAt(0);
+      Color col = cs.findColour(res, 0, null, null, 0f);
+      command.append("color ")
+              .append(String.valueOf(col.getRed() / normalise)).append(",")
+              .append(String.valueOf(col.getGreen() / normalise))
+              .append(",").append(String.valueOf(col.getBlue() / normalise))
+              .append(" ::").append(resName).append(";");
     }
 
     sendAsynchronousCommand(command.toString(), COLOURING_CHIMERA);
@@ -1008,18 +963,19 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   /**
    * Send the Chimera 'background solid <color>" command.
    * 
-   * @see https 
+   * @see https
    *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background
    *      .html
    * @param col
    */
+  @Override
   public void setBackgroundColour(Color col)
   {
     viewerCommandHistory(false);
     double normalise = 255D;
     final String command = "background solid " + col.getRed() / normalise
-            + "," + col.getGreen() / normalise + "," + col.getBlue()
-            / normalise + ";";
+            + "," + col.getGreen() / normalise + ","
+            + col.getBlue() / normalise + ";";
     viewer.sendChimeraCommand(command, false);
     viewerCommandHistory(true);
   }
@@ -1070,27 +1026,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * 
    * @return
    */
+  @Override
   public List<String> getChainNames()
   {
-    List<String> names = new ArrayList<String>();
-    String[][] allNames = getChains();
-    if (allNames != null)
-    {
-      for (String[] chainsForPdb : allNames)
-      {
-        if (chainsForPdb != null)
-        {
-          for (String chain : chainsForPdb)
-          {
-            if (chain != null && !names.contains(chain))
-            {
-              names.add(chain);
-            }
-          }
-        }
-      }
-    }
-    return names;
+    return chainNames;
   }
 
   /**
@@ -1134,30 +1073,21 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * features visible in Jalview
    * 
    * @param avp
+   * @return
    */
-  public void sendFeaturesToViewer(AlignmentViewPanel avp)
+  public int sendFeaturesToViewer(AlignmentViewPanel avp)
   {
     // TODO refactor as required to pull up to an interface
     AlignmentI alignment = avp.getAlignment();
-    FeatureRenderer fr = getFeatureRenderer(avp);
-
-    /*
-     * fr is null if feature display is turned off
-     */
-    if (fr == null)
-    {
-      return;
-    }
 
-    String[] files = getPdbFile();
+    String[] files = getStructureFiles();
     if (files == null)
     {
-      return;
+      return 0;
     }
 
     StructureMappingcommandSet commandSet = ChimeraCommands
-            .getSetAttributeCommandsForFeatures(getSsm(), files,
-                    getSequence(), fr, alignment);
+            .getSetAttributeCommandsForFeatures(avp, this);
     String[] commands = commandSet.commands;
     if (commands.length > 10)
     {
@@ -1170,6 +1100,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         sendAsynchronousCommand(command, null);
       }
     }
+    return commands.length;
   }
 
   /**
@@ -1196,9 +1127,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       sendAsynchronousCommand("open cmd:" + path, null);
     } catch (IOException e)
     {
-      System.err
-              .println("Sending commands to Chimera via file failed with "
-                      + e.getMessage());
+      System.err.println("Sending commands to Chimera via file failed with "
+              + e.getMessage());
     }
   }
 
@@ -1225,36 +1155,31 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     String cmd = "list residues attr '" + attName + "'";
     List<String> residues = sendChimeraCommand(cmd, true);
 
-    /*
-     * TODO check if Jalview already has this feature name, if so give it a 
-     * distinguishing prefix e.g. chim_
-     */
-    FeatureRenderer fr = alignmentPanel.getFeatureRenderer();
-    // todo should getRenderOrder be in api.FeatureRenderer?
-    // FIXME this is empty if feature display is turned off
-    List<String> existingFeatures = ((jalview.gui.FeatureRenderer) fr)
-            .getRenderOrder();
-    if (existingFeatures.contains(attName))
+    boolean featureAdded = createFeaturesForAttributes(attName, residues);
+    if (featureAdded)
     {
-      // TODO check if feature of this name is in group Chimera
-      // if so don't create a new feature name!
-      // problem: needs a lookup of features for feature group
-      attName = getStructureFeaturePrefix() + attName;
+      alignmentPanel.getFeatureRenderer().featuresAdded();
     }
+  }
 
-    /*
-     * Expect 0, 1 or more reply lines of the format (chi2 is attName):
-     * residue id #0:5.A chi2 -155.000836316 index 5
-     * or
-     * residue id #0:6.A chi3 None
-     * 
-     * We assume here that attributes on structure do not naturally convert
-     * to ranges on sequence, i.e. we just set one feature per mapped position.
-     * 
-     * To conflate positions, would need to first build a map 
-     * Map<String value, Map<Sequence seq, List<Integer position>>>
-     * and then traverse it to find feature ranges.
-     */
+  /**
+   * Create features in Jalview for the given attribute name and structure
+   * residues.
+   * 
+   * <pre>
+   * The residue list should be 0, 1 or more reply lines of the format: 
+   *     residue id #0:5.A isHelix -155.000836316 index 5 
+   * or 
+   *     residue id #0:6.A isHelix None
+   * </pre>
+   * 
+   * @param attName
+   * @param residues
+   * @return
+   */
+  protected boolean createFeaturesForAttributes(String attName,
+          List<String> residues)
+  {
     boolean featureAdded = false;
     String featureGroup = getViewerFeatureGroup();
 
@@ -1303,14 +1228,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       spec.setPdbFile(pdbFile);
 
       List<AtomSpec> atoms = Collections.singletonList(spec);
-      SearchResults sr = getSsm()
+
+      /*
+       * locate the mapped position in the alignment (if any)
+       */
+      SearchResultsI sr = getSsm()
               .findAlignmentPositionsForStructurePositions(atoms);
 
       /*
        * expect one matched alignment position, or none 
        * (if the structure position is not mapped)
        */
-      for (Match m : sr.getResults())
+      for (SearchResultMatchI m : sr.getResults())
       {
         SequenceI seq = m.getSequence();
         int start = m.getStart();
@@ -1322,22 +1251,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
         featureAdded |= seq.addSequenceFeature(sf);
       }
     }
-    if (featureAdded)
-    {
-      fr.featuresAdded();
-    }
-  }
-
-  /**
-   * Answers a 'namespace' prefix to use for features created in Jalview from
-   * attributes in the structure viewer
-   * 
-   * @return
-   */
-  protected String getStructureFeaturePrefix()
-  {
-    // TODO pull up as abstract
-    return CHIMERA_FEATURE_PREFIX;
+    return featureAdded;
   }
 
   /**
@@ -1351,4 +1265,45 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     // todo pull up to interface
     return CHIMERA_FEATURE_GROUP;
   }
+
+  public Hashtable<String, String> getChainFile()
+  {
+    return chainFile;
+  }
+
+  public List<ChimeraModel> getChimeraModelByChain(String chain)
+  {
+    return chimeraMaps.get(chainFile.get(chain));
+  }
+
+  public int getModelNoForChain(String chain)
+  {
+    List<ChimeraModel> foundModels = getChimeraModelByChain(chain);
+    if (foundModels != null && !foundModels.isEmpty())
+    {
+      return foundModels.get(0).getModelNumber();
+    }
+    return -1;
+  }
+
+  @Override
+  public void showStructures(AlignViewportI av, boolean refocus)
+  {
+    StringBuilder cmd = new StringBuilder(128);
+    cmd.append("~display; ~ribbon;");
+
+    AtomSpecModel model = getShownResidues(av);
+    String atomSpec = ChimeraCommands.getAtomSpec(model, this);
+    
+    cmd.append("ribbon ").append(atomSpec);
+    if (!isShowAlignmentOnly())
+    {
+      cmd.append("chain @CA|P; ribbon");
+    }
+    if (refocus)
+    {
+      cmd.append("; focus");
+    }
+    sendChimeraCommand(cmd.toString(), false);
+  }
 }