Merge remote-tracking branch 'origin/develop' into
[jalview.git] / src / jalview / ext / rbvi / chimera / JalviewChimeraBinding.java
index 4975e3e..f511a65 100644 (file)
@@ -1,47 +1,23 @@
-/*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
 package jalview.ext.rbvi.chimera;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
-import jalview.api.SequenceStructureBinding;
-import jalview.api.StructureSelectionManagerProvider;
+import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.io.AppletFormatAdapter;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
-import jalview.structure.AtomSpec;
-import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
+import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
-import jalview.structures.models.SequenceStructureBindingModel;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.Comparison;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
-import java.awt.event.ComponentEvent;
-import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -53,11 +29,11 @@ import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
 import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
 
-public abstract class JalviewChimeraBinding extends
-        SequenceStructureBindingModel implements StructureListener,
-        SequenceStructureBinding, StructureSelectionManagerProvider
-
+public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 {
+
+  private static final boolean debug = false;
+
   private static final String PHOSPHORUS = "P";
 
   private static final String ALPHACARBON = "CA";
@@ -66,130 +42,106 @@ public abstract class JalviewChimeraBinding extends
 
   private ChimeraManager viewer;
 
-  /**
+  /*
    * set if chimera state is being restored from some source - instructs binding
    * not to apply default display style when structure set is updated for first
    * time.
    */
   private boolean loadingFromArchive = false;
 
-  /**
-   * second flag to indicate if the Chimera viewer should ignore sequence
-   * colouring events from the structure manager because the GUI is still
-   * setting up
+  /*
+   * flag to indicate if the Chimera viewer should ignore sequence colouring
+   * events from the structure manager because the GUI is still setting up
    */
   private boolean loadingFinished = true;
 
-  /**
+  /*
    * state flag used to check if the Chimera viewer's paint method can be called
    */
   private boolean finishedInit = false;
 
-  public boolean isFinishedInit()
-  {
-    return finishedInit;
-  }
-
-  public void setFinishedInit(boolean finishedInit)
-  {
-    this.finishedInit = finishedInit;
-  }
-
-  boolean allChainsSelected = false;
+  private List<String> atomsPicked = new ArrayList<String>();
 
-  /**
-   * when true, try to search the associated datamodel for sequences that are
-   * associated with any unknown structures in the Chimera view.
-   */
-  private boolean associateNewStructs = false;
-
-  List<String> atomsPicked = new ArrayList<String>();
-
-  public List<String> chainNames;
+  private List<String> chainNames;
 
   private Map<String, String> chainFile;
 
-  /**
-   * array of target chains for sequences - tied to pdbentry and sequence[]
-   */
-  protected String[][] chains;
-
-  boolean colourBySequence = true;
-
-  StringBuffer eval = new StringBuffer();
+  private StringBuffer eval = new StringBuffer();
 
   public String fileLoadingError;
 
-  private Map<String, List<ChimeraModel>> chimmaps = new LinkedHashMap<String, List<ChimeraModel>>();
-
-  private List<String> mdlToFile = new ArrayList<String>();
+  /*
+   * Map of ChimeraModel objects keyed by PDB full local file name
+   */
+  private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
 
-  /**
+  /*
    * the default or current model displayed if the model cannot be identified
    * from the selection message
    */
-  int frameNo = 0;
+  private int frameNo = 0;
 
-  String lastCommand;
+  private String lastCommand;
 
-  String lastMessage;
+  private String lastMessage;
 
-  boolean loadedInline;
+  private boolean loadedInline;
 
+  /**
+   * Open a PDB structure file in Chimera and set up mappings from Jalview.
+   * 
+   * We check if the PDB model id is already loaded in Chimera, if so don't
+   * reopen it. This is the case if Chimera has opened a saved session file.
+   * 
+   * @param pe
+   * @return
+   */
   public boolean openFile(PDBEntry pe)
   {
     String file = pe.getFile();
     try
     {
+      List<ChimeraModel> modelsToMap = new ArrayList<ChimeraModel>();
       List<ChimeraModel> oldList = viewer.getModelList();
-      viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
-      List<ChimeraModel> newList = viewer.getModelList();
-      if (oldList.size() < newList.size())
+      boolean alreadyOpen = false;
+
+      /*
+       * If Chimera already has this model, don't reopen it, but do remap it.
+       */
+      for (ChimeraModel open : oldList)
       {
-        while (oldList.size() > 0)
-        {
-          oldList.remove(0);
-          newList.remove(0);
-        }
-        chimmaps.put(file, newList);
-        for (ChimeraModel cm : newList)
+        if (open.getModelName().equals(pe.getId()))
         {
-          while (mdlToFile.size() < 1 + cm.getModelNumber())
-          {
-            mdlToFile.add(new String(""));
-          }
-          mdlToFile.set(cm.getModelNumber(), file);
+          alreadyOpen = true;
+          modelsToMap.add(open);
         }
+      }
 
-        File fl = new File(file);
-        String protocol = AppletFormatAdapter.URL;
-        try
-        {
-          if (fl.exists())
-          {
-            protocol = AppletFormatAdapter.FILE;
-          }
-        } catch (Exception e)
-        {
-        } catch (Error e)
-        {
-        }
-        // Explicitly map to the filename used by Chimera ;
-        // pdbentry[pe].getFile(), protocol);
+      /*
+       * If Chimera doesn't yet have this model, ask it to open it, and retrieve
+       * the model names added by Chimera.
+       */
+      if (!alreadyOpen)
+      {
+        viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
+        modelsToMap = viewer.getModelList();
+        modelsToMap.removeAll(oldList);
+      }
+
+      chimeraMaps.put(file, modelsToMap);
 
-        if (ssm != null)
+      if (getSsm() != null)
+      {
+        getSsm().addStructureViewerListener(this);
+        // ssm.addSelectionListener(this);
+        FeatureRenderer fr = getFeatureRenderer(null);
+        if (fr != null)
         {
-          ssm.addStructureViewerListener(this);
-          // ssm.addSelectionListener(this);
-          FeatureRenderer fr = getFeatureRenderer(null);
-          if (fr != null)
-          {
-            fr.featuresAdded();
-          }
-          refreshGUI();
+          fr.featuresAdded();
         }
-        return true;
+        refreshGUI();
       }
+      return true;
     } catch (Exception q)
     {
       log("Exception when trying to open model " + file + "\n"
@@ -204,46 +156,40 @@ public abstract class JalviewChimeraBinding extends
    */
   String[] modelFileNames = null;
 
-  public PDBEntry[] pdbentry;
-
-  /**
-   * datasource protocol for access to PDBEntrylatest
-   */
-  String protocol = null;
 
   StringBuffer resetLastRes = new StringBuffer();
 
-  /**
-   * sequences mapped to each pdbentry
-   */
-  public SequenceI[][] sequence;
-
-  public StructureSelectionManager ssm;
-
   private List<String> lastReply;
 
+  /**
+   * Constructor
+   * 
+   * @param ssm
+   * @param pdbentry
+   * @param sequenceIs
+   * @param chains
+   * @param protocol
+   */
   public JalviewChimeraBinding(StructureSelectionManager ssm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
           String protocol)
   {
-    this.ssm = ssm;
-    this.sequence = sequenceIs;
-    this.chains = chains;
-    this.pdbentry = pdbentry;
-    this.protocol = protocol;
-    if (chains == null)
-    {
-      this.chains = new String[pdbentry.length][];
-    }
+    super(ssm, pdbentry, sequenceIs, chains, protocol);
     viewer = new ChimeraManager(
             csm = new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
   }
 
+  /**
+   * Constructor
+   * 
+   * @param ssm
+   * @param theViewer
+   */
   public JalviewChimeraBinding(StructureSelectionManager ssm,
-          ChimeraManager viewer2)
+          ChimeraManager theViewer)
   {
-    this.ssm = ssm;
-    viewer = viewer2;
+    super(ssm, null);
+    viewer = theViewer;
     csm = viewer.getStructureManager();
   }
 
@@ -256,34 +202,7 @@ public abstract class JalviewChimeraBinding extends
    */
   public String getViewerTitle(boolean verbose)
   {
-    if (sequence == null || pdbentry == null || sequence.length < 1
-            || pdbentry.length < 1 || sequence[0].length < 1)
-    {
-      return ("Jalview Chimera Window");
-    }
-    // TODO: give a more informative title when multiple structures are
-    // displayed.
-    StringBuilder title = new StringBuilder(64);
-    title.append("Chimera view for " + sequence[0][0].getName() + ":"
-            + pdbentry[0].getId());
-
-    if (verbose)
-    {
-      if (pdbentry[0].getProperty() != null)
-      {
-        if (pdbentry[0].getProperty().get("method") != null)
-        {
-          title.append(" Method: ");
-          title.append(pdbentry[0].getProperty().get("method"));
-        }
-        if (pdbentry[0].getProperty().get("chains") != null)
-        {
-          title.append(" Chain:");
-          title.append(pdbentry[0].getProperty().get("chains"));
-        }
-      }
-    }
-    return title.toString();
+    return getViewerTitle("Chimera", verbose);
   }
 
   /**
@@ -319,12 +238,12 @@ public abstract class JalviewChimeraBinding extends
   }
 
   /**
-   * Close down the Jalview viewer, and (optionally) the associate Chimera
+   * Close down the Jalview viewer, and (optionally) the associated Chimera
    * window.
    */
   public void closeViewer(boolean closeChimera)
   {
-    ssm.removeStructureViewerListener(this, this.getPdbFile());
+    getSsm().removeStructureViewerListener(this, this.getPdbFile());
     if (closeChimera)
     {
       viewer.exitChimera();
@@ -334,12 +253,6 @@ public abstract class JalviewChimeraBinding extends
     releaseUIResources();
   }
 
-  /**
-   * called by JalviewChimerabinding after closeViewer is called - release any
-   * resources and references so they can be garbage collected.
-   */
-  protected abstract void releaseUIResources();
-
   public void colourByChain()
   {
     colourBySequence = false;
@@ -413,7 +326,7 @@ public abstract class JalviewChimeraBinding extends
         {
           // HACK - in Jalview 2.8 this call may not be threadsafe so we catch
           // every possible exception
-          StructureMapping[] sm = ssm.getMapping(file);
+          StructureMapping[] sm = getSsm().getMapping(file);
           if (sm == null || sm.length == 0)
           {
             waiting = true;
@@ -475,7 +388,7 @@ public abstract class JalviewChimeraBinding extends
       String[] atomSpec = new String[files.length];
       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
       {
-        StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+        StructureMapping[] mapping = getSsm().getMapping(files[pdbfnum]);
         // RACE CONDITION - getMapping only returns Jmol loaded filenames once
         // Jmol callback has completed.
         if (mapping == null || mapping.length < 1)
@@ -483,12 +396,14 @@ public abstract class JalviewChimeraBinding extends
           throw new Error(MessageManager.getString("error.implementation_error_chimera_getting_data"));
         }
         int lastPos = -1;
-        for (int s = 0; s < sequence[pdbfnum].length; s++)
+        final int seqCountForPdbFile = getSequence()[pdbfnum].length;
+        for (int s = 0; s < seqCountForPdbFile; s++)
         {
           for (int sp, m = 0; m < mapping.length; m++)
           {
-            if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                    && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+            final SequenceI theSequence = getSequence()[pdbfnum][s];
+            if (mapping[m].getSequence() == theSequence
+                    && (sp = alignment.findIndex(theSequence)) > -1)
             {
               if (refStructure == -1)
               {
@@ -507,7 +422,7 @@ public abstract class JalviewChimeraBinding extends
                   continue;
                 }
 
-                if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+                if (Comparison.isGap(asp.getCharAt(r)))
                 {
                   // no mapping to gaps in sequence
                   continue;
@@ -541,7 +456,7 @@ public abstract class JalviewChimeraBinding extends
                       + targetC[pdbfnum];
               atomSpec[pdbfnum] = asp.getRNA() != null ? PHOSPHORUS : ALPHACARBON;
               // move on to next pdb file
-              s = sequence[pdbfnum].length;
+              s = seqCountForPdbFile;
               break;
             }
           }
@@ -654,10 +569,12 @@ public abstract class JalviewChimeraBinding extends
       }
       if (selectioncom.length() > 0)
       {
-        // TODO remove debug output
-        System.out.println("Select regions:\n" + selectioncom.toString());
-        System.out
-                .println("Superimpose command(s):\n" + command.toString());
+        if (debug)
+        {
+          System.out.println("Select regions:\n" + selectioncom.toString());
+          System.out.println("Superimpose command(s):\n"
+                  + command.toString());
+        }
         allComs.append("~display all; chain @CA|P; ribbon "
                 + selectioncom.toString() + ";"+command.toString());
         // selcom.append("; ribbons; ");
@@ -669,7 +586,10 @@ public abstract class JalviewChimeraBinding extends
       {
         selectioncom.setLength(selectioncom.length() - 1);
       }
-      System.out.println("Select regions:\n" + selectioncom.toString());
+      if (debug)
+      {
+        System.out.println("Select regions:\n" + selectioncom.toString());
+      }
       allComs.append("; ~display all; chain @CA|P; ribbon "
               + selectioncom.toString() + "; focus");
       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
@@ -682,7 +602,7 @@ public abstract class JalviewChimeraBinding extends
   {
     if (!viewer.isChimeraLaunched())
     {
-      viewer.launchChimera(csm.getChimeraPaths());
+      viewer.launchChimera(StructureManager.getChimeraPaths());
     }
     if (!viewer.isChimeraLaunched())
     {
@@ -702,7 +622,8 @@ public abstract class JalviewChimeraBinding extends
   }
 
   /**
-   * Send a command to Chimera, and optionally log any responses.
+   * Send a command to Chimera, launching it first if necessary, and optionally
+   * log any responses.
    * 
    * @param command
    * @param logResponse
@@ -713,28 +634,12 @@ public abstract class JalviewChimeraBinding extends
     checkLaunched();
     if (lastCommand == null || !lastCommand.equals(command))
     {
-//      Thread t = new Thread(new Runnable()
-//      {
-//        @Override
-//        public void run()
-//        {
       // trim command or it may never find a match in the replyLog!!
       lastReply = viewer.sendChimeraCommand(command.trim(), logResponse);
       if (debug && logResponse)
-          {
-            log("Response from command ('" + command + "') was:\n"
-                    + lastReply);
-          }
-//        }
-//      });
-      // TODO - use j7/8 thread management
-//      try
-//      {
-//        t.join();
-//      } catch (InterruptedException foo)
-//      {
-//      }
-//      ;
+      {
+        log("Response from command ('" + command + "') was:\n" + lastReply);
+      }
     }
     viewerCommandHistory(true);
     lastCommand = command;
@@ -752,7 +657,7 @@ public abstract class JalviewChimeraBinding extends
     {
       return;
     }
-    if (ssm == null)
+    if (getSsm() == null)
     {
       return;
     }
@@ -767,19 +672,41 @@ public abstract class JalviewChimeraBinding extends
     }
     AlignmentI alignment = alignmentv.getAlignment();
 
-    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : ChimeraCommands
-            .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
-                    alignment))
+    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(
+            files, sr, fr, alignment))
     {
-      for (String cbyseq : cpdbbyseq.commands)
+      for (String command : cpdbbyseq.commands)
       {
-        waitForChimera();
-        evalStateCommand(cbyseq, false);
-        waitForChimera();
+        executeWhenReady(command);
       }
     }
   }
 
+  /**
+   * @param files
+   * @param sr
+   * @param fr
+   * @param alignment
+   * @return
+   */
+  protected StructureMappingcommandSet[] getColourBySequenceCommands(
+          String[] files, SequenceRenderer sr, FeatureRenderer fr,
+          AlignmentI alignment)
+  {
+    return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
+            getSequence(), sr, fr, alignment);
+  }
+
+  /**
+   * @param command
+   */
+  protected void executeWhenReady(String command)
+  {
+    waitForChimera();
+    evalStateCommand(command, false);
+    waitForChimera();
+  }
+
   private void waitForChimera()
   {
     while (viewer != null && viewer.isBusy())
@@ -791,30 +718,11 @@ public abstract class JalviewChimeraBinding extends
     }
   }
 
-  public boolean isColourBySequence()
-  {
-    return colourBySequence;
-  }
 
-  public void setColourBySequence(boolean colourBySequence)
-  {
-    this.colourBySequence = colourBySequence;
-  }
 
   // End StructureListener
   // //////////////////////////
 
-  public float[][] functionXY(String functionName, int x, int y)
-  {
-    return null;
-  }
-
-  public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
-  {
-    // TODO Auto-generated method stub
-    return null;
-  }
-
   public Color getColour(int atomIndex, int pdbResNum, String chain,
           String pdbfile)
   {
@@ -889,8 +797,8 @@ public abstract class JalviewChimeraBinding extends
     // // System.arraycopy(mset, 0, modelFileNames, 0, j);
     // }
 
-    return chimmaps.keySet().toArray(
-            modelFileNames = new String[chimmaps.size()]);
+    return chimeraMaps.keySet().toArray(
+            modelFileNames = new String[chimeraMaps.size()]);
   }
 
   /**
@@ -913,61 +821,47 @@ public abstract class JalviewChimeraBinding extends
   public abstract SequenceRenderer getSequenceRenderer(
           AlignmentViewPanel alignment);
 
-  /**
-   * Highlight the specified atom positions in the structure.
-   * 
-   * @param atomIndex
-   * @param pdbResNum
-   * @param chain
-   * @param pdbfile
-   */
-  @Override
-  public void highlightAtoms(List<AtomSpec> atoms)
+  // jmol/ssm only
+  public void highlightAtom(int atomIndex, int pdbResNum, String chain,
+          String pdbfile)
   {
-    for (AtomSpec atom : atoms)
+    List<ChimeraModel> cms = chimeraMaps.get(pdbfile);
+    if (cms != null)
     {
-      int pdbResNum = atom.getPdbResNum();
-      String chain = atom.getChain();
-      List<ChimeraModel> cms = chimmaps.get(atom.getPdbId());
-      if (cms != null)
-      {
-        int mdlNum = cms.get(0).getModelNumber();
-
-        viewerCommandHistory(false);
-        // viewer.stopListening();
-        if (resetLastRes.length() > 0)
-        {
-          eval.setLength(0);
-          eval.append(resetLastRes.toString() + ";");
-        }
+      int mdlNum = cms.get(0).getModelNumber();
 
-        eval.append("display "); // +modelNum
+      viewerCommandHistory(false);
+      // viewer.stopListening();
+      if (resetLastRes.length() > 0)
+      {
+        eval.setLength(0);
+        eval.append(resetLastRes.toString() + ";");
+      }
 
-        resetLastRes.setLength(0);
-        resetLastRes.append("~display ");
-        {
-          eval.append(" #" + (mdlNum));
-          resetLastRes.append(" #" + (mdlNum));
-        }
-        // complete select string
+      eval.append("display "); // +modelNum
 
-        eval.append(":" + pdbResNum);
-        resetLastRes.append(":" + pdbResNum);
-        if (!chain.equals(" "))
-        {
-          eval.append("." + chain);
-          resetLastRes.append("." + chain);
-        }
+      resetLastRes.setLength(0);
+      resetLastRes.append("~display ");
+      {
+        eval.append(" #" + (mdlNum));
+        resetLastRes.append(" #" + (mdlNum));
+      }
+      // complete select string
 
-        viewer.sendChimeraCommand(eval.toString(), false);
-        viewerCommandHistory(true);
-        // viewer.startListening();
+      eval.append(":" + pdbResNum);
+      resetLastRes.append(":" + pdbResNum);
+      if (!chain.equals(" "))
+      {
+        eval.append("." + chain);
+        resetLastRes.append("." + chain);
       }
+
+      viewer.sendChimeraCommand(eval.toString(), false);
+      viewerCommandHistory(true);
+      // viewer.startListening();
     }
   }
 
-  boolean debug = false;
-
   private void log(String message)
   {
     System.err.println("## Chimera log: " + message);
@@ -1069,7 +963,7 @@ public abstract class JalviewChimeraBinding extends
     }
     if (lastMessage == null || !lastMessage.equals(strInfo))
     {
-      ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
+      getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
     }
 
     lastMessage = strInfo;
@@ -1194,13 +1088,13 @@ public abstract class JalviewChimeraBinding extends
         }
         // deregister the Jmol instance for these structures - we'll add
         // ourselves again at the end for the current structure set.
-        ssm.removeStructureViewerListener(this, oldmfn);
+        getSsm().removeStructureViewerListener(this, oldmfn);
       }
     }
 
     // register ourselves as a listener and notify the gui that it needs to
     // update itself.
-    ssm.addStructureViewerListener(this);
+    getSsm().addStructureViewerListener(this);
 
     if (notifyLoaded)
     {
@@ -1257,24 +1151,6 @@ public abstract class JalviewChimeraBinding extends
    */
   public abstract void refreshGUI();
 
-  public void componentResized(ComponentEvent e)
-  {
-
-  }
-
-  public void componentMoved(ComponentEvent e)
-  {
-
-  }
-
-  public void componentShown(ComponentEvent e)
-  {
-  }
-
-  public void componentHidden(ComponentEvent e)
-  {
-  }
-
   public void setLoadingFromArchive(boolean loadingFromArchive)
   {
     this.loadingFromArchive = loadingFromArchive;
@@ -1321,154 +1197,69 @@ public abstract class JalviewChimeraBinding extends
   }
 
   /**
-   * add structures and any known sequence associations
    * 
-   * @returns the pdb entries added to the current set.
+   * @param pdbfile
+   * @return text report of alignment between pdbfile and any associated
+   *         alignment sequences
+   */
+  public String printMapping(String pdbfile)
+  {
+    return getSsm().printMapping(pdbfile);
+  }
+
+  /**
+   * Ask Chimera to save its session to the given file. Returns true if
+   * successful, else false.
+   * 
+   * @param filepath
+   * @return
    */
-  public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
-          SequenceI[][] seq, String[][] chns)
+  public boolean saveSession(String filepath)
   {
-    List<PDBEntry> v = new ArrayList<PDBEntry>();
-    List<int[]> rtn = new ArrayList<int[]>();
-    for (int i = 0; i < pdbentry.length; i++)
-    {
-      v.add(pdbentry[i]);
-    }
-    for (int i = 0; i < pdbe.length; i++)
+    if (isChimeraRunning())
     {
-      int r = v.indexOf(pdbe[i]);
-      if (r == -1 || r >= pdbentry.length)
+      List<String> reply = viewer.sendChimeraCommand("save " + filepath,
+              true);
+      if (reply.contains("Session written"))
       {
-        rtn.add(new int[]
-        { v.size(), i });
-        v.add(pdbe[i]);
+        return true;
       }
       else
       {
-        // just make sure the sequence/chain entries are all up to date
-        addSequenceAndChain(r, seq[i], chns[i]);
-      }
-    }
-    pdbe = v.toArray(new PDBEntry[v.size()]);
-    pdbentry = pdbe;
-    if (rtn.size() > 0)
-    {
-      // expand the tied sequence[] and string[] arrays
-      SequenceI[][] sqs = new SequenceI[pdbentry.length][];
-      String[][] sch = new String[pdbentry.length][];
-      System.arraycopy(sequence, 0, sqs, 0, sequence.length);
-      System.arraycopy(chains, 0, sch, 0, this.chains.length);
-      sequence = sqs;
-      chains = sch;
-      pdbe = new PDBEntry[rtn.size()];
-      for (int r = 0; r < pdbe.length; r++)
-      {
-        int[] stri = (rtn.get(r));
-        // record the pdb file as a new addition
-        pdbe[r] = pdbentry[stri[0]];
-        // and add the new sequence/chain entries
-        addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
+        Cache.log
+                .error("Error saving Chimera session: " + reply.toString());
       }
     }
-    else
-    {
-      pdbe = null;
-    }
-    return pdbe;
+    return false;
   }
 
   /**
-   * Adds sequences to the pe'th pdbentry's sequence set.
+   * Ask Chimera to open a session file. Returns true if successful, else false.
+   * The filename must have a .py extension for this command to work.
    * 
-   * @param pe
-   * @param seq
+   * @param filepath
+   * @return
    */
-  public void addSequence(int pe, SequenceI[] seq)
+  public boolean openSession(String filepath)
   {
-    addSequenceAndChain(pe, seq, null);
+    evalStateCommand("open " + filepath, true);
+    // todo: test for failure - how?
+    return true;
   }
 
-  private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
+  public boolean isFinishedInit()
   {
-    if (pe < 0 || pe >= pdbentry.length)
-    {
-      throw new Error(MessageManager.formatMessage(
-              "error.implementation_error_no_pdbentry_from_index",
-              new Object[]
-              { Integer.valueOf(pe).toString() }));
-    }
-    final String nullChain = "TheNullChain";
-    List<SequenceI> s = new ArrayList<SequenceI>();
-    List<String> c = new ArrayList<String>();
-    if (chains == null)
-    {
-      chains = new String[pdbentry.length][];
-    }
-    if (sequence[pe] != null)
-    {
-      for (int i = 0; i < sequence[pe].length; i++)
-      {
-        s.add(sequence[pe][i]);
-        if (chains[pe] != null)
-        {
-          if (i < chains[pe].length)
-          {
-            c.add(chains[pe][i]);
-          }
-          else
-          {
-            c.add(nullChain);
-          }
-        }
-        else
-        {
-          if (tchain != null && tchain.length > 0)
-          {
-            c.add(nullChain);
-          }
-        }
-      }
-    }
-    for (int i = 0; i < seq.length; i++)
-    {
-      if (!s.contains(seq[i]))
-      {
-        s.add(seq[i]);
-        if (tchain != null && i < tchain.length)
-        {
-          c.add(tchain[i] == null ? nullChain : tchain[i]);
-        }
-      }
-    }
-    SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
-    sequence[pe] = tmp;
-    if (c.size() > 0)
-    {
-      String[] tch = c.toArray(new String[c.size()]);
-      for (int i = 0; i < tch.length; i++)
-      {
-        if (tch[i] == nullChain)
-        {
-          tch[i] = null;
-        }
-      }
-      chains[pe] = tch;
-    }
-    else
-    {
-      chains[pe] = null;
-    }
+    return finishedInit;
   }
 
-  /**
-   * 
-   * @param pdbfile
-   * @return text report of alignment between pdbfile and any associated
-   *         alignment sequences
-   */
-  public String printMapping(String pdbfile)
+  public void setFinishedInit(boolean finishedInit)
+  {
+    this.finishedInit = finishedInit;
+  }
+
+  public List<String> getChainNames()
   {
-    return ssm.printMapping(pdbfile);
+    return chainNames;
   }
 
-}
+}
\ No newline at end of file