JAL-1620 version bump and release notes
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
index 31f91ee..65c27d9 100644 (file)
@@ -1,59 +1,70 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
- * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
+ * 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.
- * 
+ * 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/>.
+ * 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.jmol;
 
-import java.io.File;
-import java.net.URL;
-import java.util.*;
-import java.applet.Applet;
-import java.awt.*;
-import java.awt.event.*;
-
-import javax.swing.JPanel;
-
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.SequenceStructureBinding;
-import jalview.datamodel.*;
-import jalview.structure.*;
-import jalview.io.*;
+import jalview.api.StructureSelectionManagerProvider;
+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.StructureListener;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.SequenceStructureBindingModel;
+import jalview.util.MessageManager;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.io.File;
+import java.net.URL;
+import java.security.AccessControlException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Vector;
 
-import org.jmol.api.*;
 import org.jmol.adapter.smarter.SmarterJmolAdapter;
-
-import org.jmol.popup.*;
-import org.jmol.viewer.JmolConstants;
-import org.jmol.viewer.Viewer;
-
-import jalview.schemes.*;
-
-public abstract class JalviewJmolBinding implements StructureListener,
-        JmolStatusListener, SequenceStructureBinding, JmolSelectionListener
+import org.jmol.api.JmolAppConsoleInterface;
+import org.jmol.api.JmolSelectionListener;
+import org.jmol.api.JmolStatusListener;
+import org.jmol.api.JmolViewer;
+import org.jmol.constant.EnumCallback;
+import org.jmol.popup.JmolPopup;
+
+public abstract class JalviewJmolBinding extends SequenceStructureBindingModel implements StructureListener,
+        JmolStatusListener, SequenceStructureBinding,
+        JmolSelectionListener, ComponentListener,
+        StructureSelectionManagerProvider
 
 {
   /**
-   * set if Jmol 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;
-
-  /**
    * state flag used to check if the Jmol viewer's paint method can be called
    */
   private boolean finishedInit = false;
@@ -80,6 +91,8 @@ public abstract class JalviewJmolBinding implements StructureListener,
 
   public Vector chainNames;
 
+  Hashtable chainFile;
+
   /**
    * array of target chains for seuqences - tied to pdbentry and sequence[]
    */
@@ -113,7 +126,7 @@ public abstract class JalviewJmolBinding implements StructureListener,
   public PDBEntry[] pdbentry;
 
   /**
-   * datasource protocol for access to PDBEntry
+   * datasource protocol for access to PDBEntrylatest
    */
   String protocol = null;
 
@@ -124,13 +137,15 @@ public abstract class JalviewJmolBinding implements StructureListener,
    */
   public SequenceI[][] sequence;
 
-  StructureSelectionManager ssm;
+  public StructureSelectionManager ssm;
 
   public JmolViewer viewer;
 
-  public JalviewJmolBinding(PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
-          String[][] chains, String protocol)
+  public JalviewJmolBinding(StructureSelectionManager ssm,
+          PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
+          String protocol)
   {
+    this.ssm = ssm;
     this.sequence = sequenceIs;
     this.chains = chains;
     this.pdbentry = pdbentry;
@@ -148,8 +163,10 @@ public abstract class JalviewJmolBinding implements StructureListener,
      */
   }
 
-  public JalviewJmolBinding(JmolViewer viewer2)
+  public JalviewJmolBinding(StructureSelectionManager ssm,
+          JmolViewer viewer2)
   {
+    this.ssm = ssm;
     viewer = viewer2;
     viewer.setJmolStatusListener(this);
     viewer.addSelectionListener(this);
@@ -210,8 +227,9 @@ public abstract class JalviewJmolBinding implements StructureListener,
         p = mlength;
         mlength = lbl.indexOf(":", p);
       } while (p < mlength && mlength < (lbl.length() - 2));
+      // TODO: lookup each pdb id and recover proper model number for it.
       cmd.append(":" + lbl.substring(mlength + 1) + " /"
-              + getModelNum(lbl.substring(0, mlength)) + " or ");
+              + (1 + getModelNum((String) chainFile.get(lbl))) + " or ");
     }
     if (cmd.length() > 0)
       cmd.setLength(cmd.length() - 4);
@@ -222,15 +240,21 @@ public abstract class JalviewJmolBinding implements StructureListener,
   {
     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
     // remove listeners for all structures in viewer
-    StructureSelectionManager.getStructureSelectionManager()
-            .removeStructureViewerListener(this, this.getPdbFile());
+    ssm.removeStructureViewerListener(this, this.getPdbFile());
     // and shut down jmol
     viewer.evalStringQuiet("zap");
     viewer.setJmolStatusListener(null);
     lastCommand = null;
     viewer = null;
+    releaseUIResources();
   }
 
+  /**
+   * called by JalviewJmolbinding after closeViewer is called - release any
+   * resources and references so they can be garbage collected.
+   */
+  protected abstract void releaseUIResources();
+
   public void colourByChain()
   {
     colourBySequence = false;
@@ -282,198 +306,308 @@ public abstract class JalviewJmolBinding implements StructureListener,
   public void superposeStructures(AlignmentI alignment, int refStructure,
           ColumnSelection hiddenCols)
   {
+    superposeStructures(new AlignmentI[]
+    { alignment }, new int[]
+    { refStructure }, new ColumnSelection[]
+    { hiddenCols });
+  }
+
+  public void superposeStructures(AlignmentI[] _alignment,
+          int[] _refStructure, ColumnSelection[] _hiddenCols)
+  {
+    assert (_alignment.length == _refStructure.length && _alignment.length != _hiddenCols.length);
+
     String[] files = getPdbFile();
-    if (refStructure >= files.length)
+    // check to see if we are still waiting for Jmol files
+    long starttime = System.currentTimeMillis();
+    boolean waiting = true;
+    do
+    {
+      waiting = false;
+      for (String file : files)
+      {
+        try
+        {
+          // HACK - in Jalview 2.8 this call may not be threadsafe so we catch
+          // every possible exception
+          StructureMapping[] sm = ssm.getMapping(file);
+          if (sm == null || sm.length == 0)
+          {
+            waiting = true;
+          }
+        } catch (Exception x)
+        {
+          waiting = true;
+        } catch (Error q)
+        {
+          waiting = true;
+        }
+      }
+      // we wait around for a reasonable time before we give up
+    } while (waiting
+            && System.currentTimeMillis() < (10000 + 1000 * files.length + starttime));
+    if (waiting)
     {
-      System.err.println("Invalid reference structure value "
-              + refStructure);
-      refStructure = -1;
+      System.err
+              .println("RUNTIME PROBLEM: Jmol seems to be taking a long time to process all the structures.");
+      return;
     }
-    if (refStructure < -1)
+    StringBuffer selectioncom = new StringBuffer();
+    // In principle - nSeconds specifies the speed of animation for each
+    // superposition - but is seems to behave weirdly, so we don't specify it.
+    String nSeconds = " ";
+    if (files.length > 10)
     {
-      refStructure = -1;
+      nSeconds = " 0.00001 ";
     }
-    StringBuffer command = new StringBuffer(), selectioncom = new StringBuffer();
-
-    boolean matched[] = new boolean[alignment.getWidth()];
-    for (int m = 0; m < matched.length; m++)
+    else
     {
-
-      matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+      nSeconds = " " + (2.0 / files.length) + " ";
+      // if (nSeconds).substring(0,5)+" ";
     }
-
-    int commonrpositions[][] = new int[files.length][alignment.getWidth()];
-    String isel[] = new String[files.length];
-    // reference structure - all others are superposed in it
-    String[] targetC = new String[files.length];
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    // see JAL-1345 - should really automatically turn off the animation for
+    // large numbers of structures, but Jmol doesn't seem to allow that.
+    nSeconds = " ";
+    // union of all aligned positions are collected together.
+    for (int a = 0; a < _alignment.length; a++)
     {
-      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+      int refStructure = _refStructure[a];
+      AlignmentI alignment = _alignment[a];
+      ColumnSelection hiddenCols = _hiddenCols[a];
+      if (a > 0
+              && selectioncom.length() > 0
+              && !selectioncom.substring(selectioncom.length() - 1).equals(
+                      "|"))
+      {
+        selectioncom.append("|");
+      }
+      // process this alignment
+      if (refStructure >= files.length)
+      {
+        System.err.println("Invalid reference structure value "
+                + refStructure);
+        refStructure = -1;
+      }
+      if (refStructure < -1)
+      {
+        refStructure = -1;
+      }
+      StringBuffer command = new StringBuffer();
 
-      if (mapping == null || mapping.length < 1)
-        continue;
+      boolean matched[] = new boolean[alignment.getWidth()];
+      for (int m = 0; m < matched.length; m++)
+      {
 
-      int lastPos = -1;
-      for (int s = 0; s < sequence[pdbfnum].length; s++)
+        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+      }
+
+      int commonrpositions[][] = new int[files.length][alignment.getWidth()];
+      String isel[] = new String[files.length];
+      // reference structure - all others are superposed in it
+      String[] targetC = new String[files.length];
+      String[] chainNames = new String[files.length];
+      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
       {
-        for (int sp, m = 0; m < mapping.length; m++)
+        StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+        // RACE CONDITION - getMapping only returns Jmol loaded filenames once
+        // Jmol callback has completed.
+        if (mapping == null || mapping.length < 1)
         {
-          if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                  && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+          throw new Error(MessageManager.getString("error.implementation_error_jmol_getting_data"));
+        }
+        int lastPos = -1;
+        for (int s = 0; s < sequence[pdbfnum].length; s++)
+        {
+          for (int sp, m = 0; m < mapping.length; m++)
           {
-            if (refStructure == -1)
-            {
-              refStructure = pdbfnum;
-            }
-            SequenceI asp = alignment.getSequenceAt(sp);
-            for (int r = 0; r < matched.length; r++)
+            if (mapping[m].getSequence() == sequence[pdbfnum][s]
+                    && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
             {
-              if (!matched[r])
+              if (refStructure == -1)
               {
-                continue;
+                refStructure = pdbfnum;
               }
-              matched[r] = false; // assume this is not a good site
-              if (r >= asp.getLength())
+              SequenceI asp = alignment.getSequenceAt(sp);
+              for (int r = 0; r < matched.length; r++)
               {
-                continue;
-              }
+                if (!matched[r])
+                {
+                  continue;
+                }
+                matched[r] = false; // assume this is not a good site
+                if (r >= asp.getLength())
+                {
+                  continue;
+                }
 
-              if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+                if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+                {
+                  // no mapping to gaps in sequence
+                  continue;
+                }
+                int t = asp.findPosition(r); // sequence position
+                int apos = mapping[m].getAtomNum(t);
+                int pos = mapping[m].getPDBResNum(t);
+
+                if (pos < 1 || pos == lastPos)
+                {
+                  // can't align unmapped sequence
+                  continue;
+                }
+                matched[r] = true; // this is a good ite
+                lastPos = pos;
+                // just record this residue position
+                commonrpositions[pdbfnum][r] = pos;
+              }
+              // create model selection suffix
+              isel[pdbfnum] = "/" + (pdbfnum + 1) + ".1";
+              if (mapping[m].getChain() == null
+                      || mapping[m].getChain().trim().length() == 0)
               {
-                // no mapping to gaps in sequence
-                continue;
+                targetC[pdbfnum] = "";
               }
-              int t = asp.findPosition(r); // sequence position
-              int apos = mapping[m].getAtomNum(t);
-              int pos = mapping[m].getPDBResNum(t);
-
-              if (pos < 1 || pos == lastPos)
+              else
               {
-                // can't align unmapped sequence
-                continue;
+                targetC[pdbfnum] = ":" + mapping[m].getChain();
               }
-              matched[r] = true; // this is a good ite
-              lastPos = pos;
-              // just record this residue position
-              commonrpositions[pdbfnum][r] = pos;
-            }
-            // create model selection suffix
-            isel[pdbfnum] = "/" + (pdbfnum + 1) + ".1";
-            if (mapping[m].getChain() == null
-                    || mapping[m].getChain().trim().length() == 0)
-            {
-              targetC[pdbfnum] = "";
-            }
-            else
-            {
-              targetC[pdbfnum] = ":" + mapping[m].getChain();
+              chainNames[pdbfnum] = mapping[m].getPdbId()
+                      + targetC[pdbfnum];
+              // move on to next pdb file
+              s = sequence[pdbfnum].length;
+              break;
             }
-            // move on to next pdb file
-            s = sequence[pdbfnum].length;
-            break;
           }
         }
       }
-    }
-    String[] selcom = new String[files.length];
-    int nmatched = 0;
-    // generate select statements to select regions to superimpose structures
-    {
-      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+
+      // TODO: consider bailing if nmatched less than 4 because superposition
+      // not
+      // well defined.
+      // TODO: refactor superposable position search (above) from jmol selection
+      // construction (below)
+
+      String[] selcom = new String[files.length];
+      int nmatched = 0;
+      // generate select statements to select regions to superimpose structures
       {
-        String chainCd = targetC[pdbfnum];
-        int lpos = -1;
-        boolean run = false;
-        StringBuffer molsel = new StringBuffer();
-        molsel.append("{");
-        for (int r = 0; r < matched.length; r++)
+        for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
         {
-          if (matched[r])
+          String chainCd = targetC[pdbfnum];
+          int lpos = -1;
+          boolean run = false;
+          StringBuffer molsel = new StringBuffer();
+          molsel.append("{");
+          for (int r = 0; r < matched.length; r++)
           {
-            if (pdbfnum == 0)
+            if (matched[r])
             {
-              nmatched++;
-            }
-            if (lpos != commonrpositions[pdbfnum][r] - 1)
-            {
-              // discontinuity
-              if (lpos != -1)
+              if (pdbfnum == 0)
               {
-                molsel.append(lpos);
-                molsel.append(chainCd);
-                // molsel.append("} {");
-                molsel.append("|");
+                nmatched++;
               }
-            }
-            else
-            {
-              // continuous run - and lpos >-1
-              if (!run)
+              if (lpos != commonrpositions[pdbfnum][r] - 1)
+              {
+                // discontinuity
+                if (lpos != -1)
+                {
+                  molsel.append(lpos);
+                  molsel.append(chainCd);
+                  // molsel.append("} {");
+                  molsel.append("|");
+                }
+              }
+              else
               {
-                // at the beginning, so add dash
-                molsel.append(lpos);
-                molsel.append("-");
+                // continuous run - and lpos >-1
+                if (!run)
+                {
+                  // at the beginning, so add dash
+                  molsel.append(lpos);
+                  molsel.append("-");
+                }
+                run = true;
               }
-              run = true;
+              lpos = commonrpositions[pdbfnum][r];
+              // molsel.append(lpos);
             }
-            lpos = commonrpositions[pdbfnum][r];
-            // molsel.append(lpos);
+          }
+          // add final selection phrase
+          if (lpos != -1)
+          {
+            molsel.append(lpos);
+            molsel.append(chainCd);
+            molsel.append("}");
+          }
+          if (molsel.length() > 1)
+          {
+            selcom[pdbfnum] = molsel.toString();
+            selectioncom.append("((");
+            selectioncom.append(selcom[pdbfnum].substring(1,
+                    selcom[pdbfnum].length() - 1));
+            selectioncom.append(" )& ");
+            selectioncom.append(pdbfnum + 1);
+            selectioncom.append(".1)");
+            if (pdbfnum < files.length - 1)
+            {
+              selectioncom.append("|");
+            }
+          }
+          else
+          {
+            selcom[pdbfnum] = null;
           }
         }
-        // add final selection phrase
-        if (lpos != -1)
+      }
+      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+      {
+        if (pdbfnum == refStructure || selcom[pdbfnum] == null
+                || selcom[refStructure] == null)
         {
-          molsel.append(lpos);
-          molsel.append(chainCd);
-          molsel.append("}");
+          continue;
         }
-        selcom[pdbfnum] = molsel.toString();
-        selectioncom.append("((");
-        selectioncom.append(selcom[pdbfnum].substring(1,
-                selcom[pdbfnum].length() - 1));
-        selectioncom.append(" )& ");
-        selectioncom.append(pdbfnum + 1);
-        selectioncom.append(".1)");
-        if (pdbfnum < files.length - 1)
+        command.append("echo ");
+        command.append("\"Superposing (");
+        command.append(chainNames[pdbfnum]);
+        command.append(") against reference (");
+        command.append(chainNames[refStructure]);
+        command.append(")\";\ncompare " + nSeconds);
+        command.append("{");
+        command.append(1 + pdbfnum);
+        command.append(".1} {");
+        command.append(1 + refStructure);
+        command.append(".1} SUBSET {*.CA | *.P} ATOMS ");
+
+        // form the matched pair strings
+        String sep = "";
+        for (int s = 0; s < 2; s++)
         {
-          selectioncom.append("|");
+          command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
         }
+        command.append(" ROTATE TRANSLATE;\n");
       }
-    }
-    // TODO: consider bailing if nmatched less than 4 because superposition not
-    // well defined.
-    // TODO: refactor superposable position search (above) from jmol selection
-    // construction (below)
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-    {
-      if (pdbfnum == refStructure)
+      if (selectioncom.length() > 0)
       {
-        continue;
+        System.out.println("Select regions:\n" + selectioncom.toString());
+        evalStateCommand("select *; cartoons off; backbone; select ("
+                + selectioncom.toString() + "); cartoons; ");
+        // selcom.append("; ribbons; ");
+        System.out
+                .println("Superimpose command(s):\n" + command.toString());
+
+        evalStateCommand(command.toString());
       }
-      command.append("compare ");
-      command.append("{");
-      command.append(1 + pdbfnum);
-      command.append(".1} {");
-      command.append(1 + refStructure);
-      command.append(".1} SUBSET {*.CA | *.P} ATOMS ");
-
-      // form the matched pair strings
-      String sep = "";
-      for (int s = 0; s < 2; s++)
+    }
+    if (selectioncom.length() > 0)
+    {// finally, mark all regions that were superposed.
+      if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
       {
-        command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
+        selectioncom.setLength(selectioncom.length() - 1);
       }
-      command.append(" ROTATE TRANSLATE;\n");
+      System.out.println("Select regions:\n" + selectioncom.toString());
+      evalStateCommand("select *; cartoons off; backbone; select ("
+              + selectioncom.toString() + "); cartoons; ");
+      // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
     }
-    System.out.println("Select regions:\n" + selectioncom.toString());
-    evalStateCommand("select *; cartoons off; backbone; select ("
-            + selectioncom.toString() + "); cartoons; ");
-    // selcom.append("; ribbons; ");
-    System.out.println("Superimpose command(s):\n" + command.toString());
-
-    evalStateCommand(command.toString());
-
-    // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
   }
 
   public void evalStateCommand(String command)
@@ -492,87 +626,33 @@ public abstract class JalviewJmolBinding implements StructureListener,
    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
    * if colourBySequence is enabled.
    */
-  public void colourBySequence(boolean showFeatures, AlignmentI alignment)
+  public void colourBySequence(boolean showFeatures,
+          jalview.api.AlignmentViewPanel alignmentv)
   {
-    if (!colourBySequence)
+    if (!colourBySequence || !isLoadingFinished())
       return;
     if (ssm == null)
     {
       return;
     }
     String[] files = getPdbFile();
-    SequenceRenderer sr = getSequenceRenderer();
+
+    SequenceRenderer sr = getSequenceRenderer(alignmentv);
 
     FeatureRenderer fr = null;
     if (showFeatures)
     {
-      fr = getFeatureRenderer();
+      fr = getFeatureRenderer(alignmentv);
     }
+    AlignmentI alignment = alignmentv.getAlignment();
 
-    StringBuffer command = new StringBuffer();
-
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-    {
-      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-
-      if (mapping == null || mapping.length < 1)
-        continue;
-
-      int lastPos = -1;
-      for (int s = 0; s < sequence[pdbfnum].length; s++)
+    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : JmolCommands
+            .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
+                    alignment))
+      for (String cbyseq : cpdbbyseq.commands)
       {
-        for (int sp, m = 0; m < mapping.length; m++)
-        {
-          if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                  && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
-          {
-            SequenceI asp = alignment.getSequenceAt(sp);
-            for (int r = 0; r < asp.getLength(); r++)
-            {
-              // no mapping to gaps in sequence
-              if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
-              {
-                continue;
-              }
-              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
-
-              if (pos < 1 || pos == lastPos)
-                continue;
-
-              lastPos = pos;
-
-              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
-
-              if (showFeatures)
-                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
-              String newSelcom = (mapping[m].getChain() != " " ? ":"
-                      + mapping[m].getChain() : "")
-                      + "/"
-                      + (pdbfnum + 1)
-                      + ".1"
-                      + ";color["
-                      + col.getRed()
-                      + ","
-                      + col.getGreen()
-                      + ","
-                      + col.getBlue() + "]";
-              if (command.toString().endsWith(newSelcom))
-              {
-                command = condenseCommand(command.toString(), pos);
-                continue;
-              }
-              // TODO: deal with case when buffer is too large for Jmol to parse
-              // - execute command and flush
-
-              command.append(";select " + pos);
-              command.append(newSelcom);
-            }
-            break;
-          }
-        }
+        evalStateCommand(cbyseq);
       }
-    }
-    evalStateCommand(command.toString());
   }
 
   public boolean isColourBySequence()
@@ -585,30 +665,6 @@ public abstract class JalviewJmolBinding implements StructureListener,
     this.colourBySequence = colourBySequence;
   }
 
-  StringBuffer condenseCommand(String command, int pos)
-  {
-
-    StringBuffer sb = new StringBuffer(command.substring(0,
-            command.lastIndexOf("select") + 7));
-
-    command = command.substring(sb.length());
-
-    String start;
-
-    if (command.indexOf("-") > -1)
-    {
-      start = command.substring(0, command.indexOf("-"));
-    }
-    else
-    {
-      start = command.substring(0, command.indexOf(":"));
-    }
-
-    sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
-
-    return sb;
-  }
-
   public void createImage(String file, String type, int quality)
   {
     System.out.println("JMOL CREATE IMAGE");
@@ -655,9 +711,12 @@ public abstract class JalviewJmolBinding implements StructureListener,
    * returns the current featureRenderer that should be used to colour the
    * structures
    * 
+   * @param alignment
+   * 
    * @return
    */
-  public abstract FeatureRenderer getFeatureRenderer();
+  public abstract FeatureRenderer getFeatureRenderer(
+          AlignmentViewPanel alignment);
 
   /**
    * instruct the Jalview binding to update the pdbentries vector if necessary
@@ -702,10 +761,34 @@ public abstract class JalviewJmolBinding implements StructureListener,
       String mset[] = new String[viewer.getModelCount()];
       _modelFileNameMap = new int[mset.length];
       int j = 1;
-      mset[0] = viewer.getModelFileName(0);
+      String m = viewer.getModelFileName(0);
+      if (m != null)
+      {
+        try
+        {
+          mset[0] = new File(m).getAbsolutePath();
+        } catch (AccessControlException x)
+        {
+          // usually not allowed to do this in applet, so keep raw handle
+          mset[0] = m;
+          // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
+        }
+      }
       for (int i = 1; i < mset.length; i++)
       {
-        mset[j] = viewer.getModelFileName(i);
+        m = viewer.getModelFileName(i);
+        if (m != null)
+        {
+          try
+          {
+            mset[j] = new File(m).getAbsolutePath();
+          } catch (AccessControlException x)
+          {
+            // usually not allowed to do this in applet, so keep raw handle
+            mset[j] = m;
+            // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
+          }
+        }
         _modelFileNameMap[j] = i; // record the model index for the filename
         // skip any additional models in the same file (NMR structures)
         if ((mset[j] == null ? mset[j] != mset[j - 1]
@@ -733,9 +816,12 @@ public abstract class JalviewJmolBinding implements StructureListener,
    * returns the current sequenceRenderer that should be used to colour the
    * structures
    * 
+   * @param alignment
+   * 
    * @return
    */
-  public abstract SequenceRenderer getSequenceRenderer();
+  public abstract SequenceRenderer getSequenceRenderer(
+          AlignmentViewPanel alignment);
 
   // ///////////////////////////////
   // JmolStatusListener
@@ -830,6 +916,7 @@ public abstract class JalviewJmolBinding implements StructureListener,
   public void mouseOverStructure(int atomIndex, String strInfo)
   {
     int pdbResNum;
+    int alocsep = strInfo.indexOf("^");
     int mdlSep = strInfo.indexOf("/");
     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
 
@@ -842,9 +929,18 @@ public abstract class JalviewJmolBinding implements StructureListener,
         chainSeparator = mdlSep;
       }
     }
-    pdbResNum = Integer.parseInt(strInfo.substring(
-            strInfo.indexOf("]") + 1, chainSeparator));
+    // handle insertion codes
+    if (alocsep != -1)
+    {
+      pdbResNum = Integer.parseInt(strInfo.substring(
+              strInfo.indexOf("]") + 1, alocsep));
 
+    }
+    else
+    {
+      pdbResNum = Integer.parseInt(strInfo.substring(
+              strInfo.indexOf("]") + 1, chainSeparator));
+    }
     String chainId;
 
     if (strInfo.indexOf(":") > -1)
@@ -868,8 +964,19 @@ public abstract class JalviewJmolBinding implements StructureListener,
       try
       {
         // recover PDB filename for the model hovered over.
-        pdbfilename = viewer
-                .getModelFileName(new Integer(mdlId).intValue() - 1);
+        int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
+                .intValue() - 1;
+        while (mnumber < _modelFileNameMap[_mp])
+        {
+          _mp--;
+        }
+        pdbfilename = modelFileNames[_mp];
+        if (pdbfilename == null)
+        {
+          pdbfilename = new File(viewer.getModelFileName(mnumber))
+                  .getAbsolutePath();
+        }
+
       } catch (Exception e)
       {
       }
@@ -947,47 +1054,48 @@ public abstract class JalviewJmolBinding implements StructureListener,
 
   }
 
-  public void notifyCallback(int type, Object[] data)
+  @Override
+  public void notifyCallback(EnumCallback type, Object[] data)
   {
     try
     {
       switch (type)
       {
-      case JmolConstants.CALLBACK_LOADSTRUCT:
+      case LOADSTRUCT:
         notifyFileLoaded((String) data[1], (String) data[2],
                 (String) data[3], (String) data[4],
                 ((Integer) data[5]).intValue());
 
         break;
-      case JmolConstants.CALLBACK_PICK:
+      case PICK:
         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
                 (String) data[0]);
         // also highlight in alignment
-      case JmolConstants.CALLBACK_HOVER:
+      case HOVER:
         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
                 (String) data[0]);
         break;
-      case JmolConstants.CALLBACK_SCRIPT:
+      case SCRIPT:
         notifyScriptTermination((String) data[2],
                 ((Integer) data[3]).intValue());
         break;
-      case JmolConstants.CALLBACK_ECHO:
+      case ECHO:
         sendConsoleEcho((String) data[1]);
         break;
-      case JmolConstants.CALLBACK_MESSAGE:
+      case MESSAGE:
         sendConsoleMessage((data == null) ? ((String) null)
                 : (String) data[1]);
         break;
-      case JmolConstants.CALLBACK_ERROR:
+      case ERROR:
         // System.err.println("Ignoring error callback.");
         break;
-      case JmolConstants.CALLBACK_SYNC:
-      case JmolConstants.CALLBACK_RESIZE:
+      case SYNC:
+      case RESIZE:
         refreshGUI();
         break;
-      case JmolConstants.CALLBACK_MEASURE:
+      case MEASURE:
 
-      case JmolConstants.CALLBACK_CLICK:
+      case CLICK:
       default:
         System.err.println("Unhandled callback " + type + " "
                 + data[1].toString());
@@ -1000,24 +1108,25 @@ public abstract class JalviewJmolBinding implements StructureListener,
     }
   }
 
-  public boolean notifyEnabled(int callbackPick)
+  @Override
+  public boolean notifyEnabled(EnumCallback callbackPick)
   {
     switch (callbackPick)
     {
-    case JmolConstants.CALLBACK_ECHO:
-    case JmolConstants.CALLBACK_LOADSTRUCT:
-    case JmolConstants.CALLBACK_MEASURE:
-    case JmolConstants.CALLBACK_MESSAGE:
-    case JmolConstants.CALLBACK_PICK:
-    case JmolConstants.CALLBACK_SCRIPT:
-    case JmolConstants.CALLBACK_HOVER:
-    case JmolConstants.CALLBACK_ERROR:
+    case ECHO:
+    case LOADSTRUCT:
+    case MEASURE:
+    case MESSAGE:
+    case PICK:
+    case SCRIPT:
+    case HOVER:
+    case ERROR:
       return true;
-    case JmolConstants.CALLBACK_RESIZE:
-    case JmolConstants.CALLBACK_SYNC:
-    case JmolConstants.CALLBACK_CLICK:
-    case JmolConstants.CALLBACK_ANIMFRAME:
-    case JmolConstants.CALLBACK_MINIMIZATION:
+    case RESIZE:
+    case SYNC:
+    case CLICK:
+    case ANIMFRAME:
+    case MINIMIZATION:
     }
     return false;
   }
@@ -1051,9 +1160,9 @@ public abstract class JalviewJmolBinding implements StructureListener,
     String[] oldmodels = modelFileNames;
     modelFileNames = null;
     chainNames = new Vector();
+    chainFile = new Hashtable();
     boolean notifyLoaded = false;
     String[] modelfilenames = getPdbFile();
-    ssm = StructureSelectionManager.getStructureSelectionManager();
     // first check if we've lost any structures
     if (oldmodels != null && oldmodels.length > 0)
     {
@@ -1127,7 +1236,9 @@ public abstract class JalviewJmolBinding implements StructureListener,
           }
           else
           {
-            if (matches = pdbentry[pe].getFile().equals(fileName))
+            File fl;
+            if (matches = (fl = new File(pdbentry[pe].getFile()))
+                    .equals(new File(fileName)))
             {
               foundEntry = true;
               // TODO: Jmol can in principle retrieve from CLASSLOADER but
@@ -1138,7 +1249,6 @@ public abstract class JalviewJmolBinding implements StructureListener,
               String protocol = AppletFormatAdapter.URL;
               try
               {
-                File fl = new java.io.File(pdbentry[pe].getFile());
                 if (fl.exists())
                 {
                   protocol = AppletFormatAdapter.FILE;
@@ -1148,20 +1258,22 @@ public abstract class JalviewJmolBinding implements StructureListener,
               } catch (Error e)
               {
               }
-              ;
-              pdb = ssm.setMapping(sequence[pe], chains[pe],
-                      pdbentry[pe].getFile(), protocol);
+              // Explicitly map to the filename used by Jmol ;
+              pdb = ssm.setMapping(sequence[pe], chains[pe], fileName,
+                      protocol);
+              // pdbentry[pe].getFile(), protocol);
 
             }
           }
           if (matches)
           {
-            pdbentry[pe].setId(pdb.id);
             // add an entry for every chain in the model
             for (int i = 0; i < pdb.chains.size(); i++)
             {
-              chainNames.addElement(new String(pdb.id + ":"
-                      + ((MCview.PDBChain) pdb.chains.elementAt(i)).id));
+              String chid = new String(pdb.id + ":"
+                      + ((MCview.PDBChain) pdb.chains.elementAt(i)).id);
+              chainFile.put(chid, fileName);
+              chainNames.addElement(chid);
             }
             notifyLoaded = true;
           }
@@ -1193,13 +1305,12 @@ public abstract class JalviewJmolBinding implements StructureListener,
     {
       viewer.evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
     }
-    setLoadingFromArchive(false);
     // register ourselves as a listener and notify the gui that it needs to
     // update itself.
     ssm.addStructureViewerListener(this);
     if (notifyLoaded)
     {
-      FeatureRenderer fr = getFeatureRenderer();
+      FeatureRenderer fr = getFeatureRenderer(null);
       if (fr != null)
       {
         fr.featuresAdded();
@@ -1207,6 +1318,7 @@ public abstract class JalviewJmolBinding implements StructureListener,
       refreshGUI();
       loadNotifiesHandled++;
     }
+    setLoadingFromArchive(false);
   }
 
   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
@@ -1314,7 +1426,7 @@ public abstract class JalviewJmolBinding implements StructureListener,
    * @param codeBase
    * @param commandOptions
    */
-  public void allocateViewer(Component renderPanel, boolean jmolfileio,
+  public void allocateViewer(Container renderPanel, boolean jmolfileio,
           String htmlName, URL documentBase, URL codeBase,
           String commandOptions)
   {
@@ -1337,78 +1449,25 @@ public abstract class JalviewJmolBinding implements StructureListener,
    * @param buttonsToShow
    *          - buttons to show on the console, in ordr
    */
-  public void allocateViewer(Component renderPanel, boolean jmolfileio,
+  public void allocateViewer(Container renderPanel, boolean jmolfileio,
           String htmlName, URL documentBase, URL codeBase,
           String commandOptions, final Container consolePanel,
           String buttonsToShow)
   {
+    if (commandOptions == null)
+    {
+      commandOptions = "";
+    }
     viewer = JmolViewer.allocateViewer(renderPanel,
             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
                     + ((Object) this).toString(), documentBase, codeBase,
             commandOptions, this);
 
     console = createJmolConsole(viewer, consolePanel, buttonsToShow);
-    if (console != null)
+    if (consolePanel != null)
     {
-      viewer.setConsole(new JmolAppConsoleInterface()
-      {
-
-        @Override
-        public JmolScriptEditorInterface getScriptEditor()
-        {
-          return console.getScriptEditor();
-        }
-
-        @Override
-        public JmolAppConsoleInterface getAppConsole(Viewer viewer,
-                Component display)
-        {
-          return console;
-        }
-
-        public String getText()
-        {
-          return console.getText();
-        }
-
-        @Override
-        public Object getMyMenuBar()
-        {
-          return console.getMyMenuBar();
-        }
-
-        @Override
-        public void setVisible(boolean b)
-        {
-          showConsole(b);
-        }
-
-        @Override
-        public void sendConsoleEcho(String strEcho)
-        {
-          console.sendConsoleEcho(strEcho);
-
-        }
-
-        @Override
-        public void sendConsoleMessage(String strInfo)
-        {
-          console.sendConsoleMessage(strInfo);
-        }
+      consolePanel.addComponentListener(this);
 
-        @Override
-        public void zap()
-        {
-          console.zap();
-        }
-
-        @Override
-        public void dispose()
-        {
-          console.dispose();
-        }
-
-      });
     }
 
   }
@@ -1418,14 +1477,24 @@ public abstract class JalviewJmolBinding implements StructureListener,
 
   protected org.jmol.api.JmolAppConsoleInterface console = null;
 
-  public void setLoadingFromArchive(boolean loadingFromArchive)
+  public void componentResized(ComponentEvent e)
+  {
+
+  }
+
+  public void componentMoved(ComponentEvent e)
+  {
+
+  }
+
+  public void componentShown(ComponentEvent e)
   {
-    this.loadingFromArchive = loadingFromArchive;
+    showConsole(true);
   }
 
-  public boolean isLoadingFromArchive()
+  public void componentHidden(ComponentEvent e)
   {
-    return loadingFromArchive;
+    showConsole(false);
   }
 
   public void setBackgroundColour(java.awt.Color col)
@@ -1505,9 +1574,7 @@ public abstract class JalviewJmolBinding implements StructureListener,
   {
     if (pe < 0 || pe >= pdbentry.length)
     {
-      throw new Error(
-              "Implementation error - no corresponding pdbentry (for index "
-                      + pe + ") to add sequences mappings to");
+      throw new Error(MessageManager.formatMessage("error.implementation_error_no_pdbentry_from_index", new String[]{Integer.valueOf(pe).toString()}));
     }
     final String nullChain = "TheNullChain";
     Vector s = new Vector();
@@ -1573,4 +1640,22 @@ public abstract class JalviewJmolBinding implements StructureListener,
       chains[pe] = null;
     }
   }
+
+  /**
+   * 
+   * @param pdbfile
+   * @return text report of alignment between pdbfile and any associated
+   *         alignment sequences
+   */
+  public String printMapping(String pdbfile)
+  {
+    return ssm.printMapping(pdbfile);
+  }
+
+  @Override
+  public void resizeInnerPanel(String data)
+  {
+    // Jalview doesn't honour resize panel requests
+
+  }
 }