JAL-3949 Complete new abstracted logging framework in jalview.log. Updated log calls...
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
index 7a394f7..f133f20 100644 (file)
  */
 package jalview.ext.jmol;
 
-import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceI;
-import jalview.io.AppletFormatAdapter;
-import jalview.io.StructureFile;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ResidueProperties;
-import jalview.structure.AtomSpec;
-import jalview.structure.StructureMappingcommandSet;
-import jalview.structure.StructureSelectionManager;
-import jalview.structures.models.AAStructureBindingModel;
-
-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.ArrayList;
-import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.StringTokenizer;
 import java.util.Vector;
 
+import javax.swing.SwingUtilities;
+
 import org.jmol.adapter.smarter.SmarterJmolAdapter;
 import org.jmol.api.JmolAppConsoleInterface;
 import org.jmol.api.JmolSelectionListener;
 import org.jmol.api.JmolStatusListener;
 import org.jmol.api.JmolViewer;
 import org.jmol.c.CBK;
-import org.jmol.script.T;
 import org.jmol.viewer.Viewer;
 
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.FeatureSettingsModelI;
+import jalview.api.SequenceRenderer;
+import jalview.bin.Cache;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AppJmol;
+import jalview.gui.IProgressIndicator;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.DataSourceType;
+import jalview.io.StructureFile;
+import jalview.structure.AtomSpec;
+import jalview.structure.StructureCommand;
+import jalview.structure.StructureCommandI;
+import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.ws.dbsources.Pdb;
+import javajs.util.BS;
+
 public abstract class JalviewJmolBinding extends AAStructureBindingModel
         implements JmolStatusListener, JmolSelectionListener,
         ComponentListener
 {
-  boolean allChainsSelected = false;
+  private String lastMessage;
 
   /*
    * when true, try to search the associated datamodel for sequences that are
@@ -70,41 +73,26 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
    */
   private boolean associateNewStructs = false;
 
-  Vector<String> atomsPicked = new Vector<String>();
-
-  private List<String> chainNames;
+  private Vector<String> atomsPicked = new Vector<>();
 
-  Hashtable<String, String> chainFile;
+  private String lastCommand;
 
-  public String fileLoadingError;
+  private boolean loadedInline;
 
-  /*
-   * the default or current model displayed if the model cannot be identified
-   * from the selection message
-   */
-  int frameNo = 0;
-
-  // protected JmolGenericPopup jmolpopup; // not used - remove?
-
-  String lastCommand;
-
-  String lastMessage;
-
-  boolean loadedInline;
+  private StringBuffer resetLastRes = new StringBuffer();
 
-  StringBuffer resetLastRes = new StringBuffer();
-
-  public Viewer viewer;
+  public Viewer jmolViewer;
 
   public JalviewJmolBinding(StructureSelectionManager ssm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
-          String protocol)
+          DataSourceType protocol)
   {
     super(ssm, pdbentry, sequenceIs, protocol);
+    setStructureCommands(new JmolCommands());
     /*
      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
-     * "jalviewJmol", ap.av.applet .getDocumentBase(),
-     * ap.av.applet.getCodeBase(), "", this);
+     * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
+     * "", this);
      * 
      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
      */
@@ -115,9 +103,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   {
     super(ssm, seqs);
 
-    viewer = theViewer;
-    viewer.setJmolStatusListener(this);
-    viewer.addSelectionListener(this);
+    jmolViewer = theViewer;
+    jmolViewer.setJmolStatusListener(this);
+    jmolViewer.addSelectionListener(this);
+    setStructureCommands(new JmolCommands());
   }
 
   /**
@@ -131,420 +120,32 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return getViewerTitle("Jmol", true);
   }
 
-  /**
-   * prepare the view for a given set of models/chains. chainList contains
-   * strings of the form 'pdbfilename:Chaincode'
-   * 
-   * @param chainList
-   *          list of chains to make visible
-   */
-  public void centerViewer(Vector<String> chainList)
-  {
-    StringBuilder cmd = new StringBuilder(128);
-    int mlength, p;
-    for (String lbl : chainList)
-    {
-      mlength = 0;
-      do
-      {
-        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) + " /"
-              + (1 + getModelNum(chainFile.get(lbl))) + " or ");
-    }
-    if (cmd.length() > 0)
-    {
-      cmd.setLength(cmd.length() - 4);
-    }
-    evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
-  }
-
-  public void closeViewer()
+  private String jmolScript(String script)
   {
-    // remove listeners for all structures in viewer
-    getSsm().removeStructureViewerListener(this, this.getPdbFile());
-    viewer.dispose();
-    lastCommand = null;
-    viewer = null;
-    releaseUIResources();
-  }
+    Cache.debug(">>Jmol>> " + script);
+    String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
+    Cache.debug("<<Jmol<< " + s);
 
-  public void colourByChain()
-  {
-    colourBySequence = false;
-    // TODO: colour by chain should colour each chain distinctly across all
-    // visible models
-    // TODO: http://issues.jalview.org/browse/JAL-628
-    evalStateCommand("select *;color chain");
+    return s;
   }
 
-  public void colourByCharge()
-  {
-    colourBySequence = false;
-    evalStateCommand("select *;color white;select ASP,GLU;color red;"
-            + "select LYS,ARG;color blue;select CYS;color yellow");
-  }
-
-  /**
-   * superpose the structures associated with sequences in the alignment
-   * according to their corresponding positions.
-   */
-  public void superposeStructures(AlignmentI alignment)
-  {
-    superposeStructures(alignment, -1, null);
-  }
-
-  /**
-   * superpose the structures associated with sequences in the alignment
-   * according to their corresponding positions. ded)
-   * 
-   * @param refStructure
-   *          - select which pdb file to use as reference (default is -1 - the
-   *          first structure in the alignment)
-   */
-  public void superposeStructures(AlignmentI alignment, int refStructure)
-  {
-    superposeStructures(alignment, refStructure, null);
-  }
-
-  /**
-   * superpose the structures associated with sequences in the alignment
-   * according to their corresponding positions. ded)
-   * 
-   * @param refStructure
-   *          - select which pdb file to use as reference (default is -1 - the
-   *          first structure in the alignment)
-   * @param hiddenCols
-   *          TODO
-   */
-  public void superposeStructures(AlignmentI alignment, int refStructure,
-          ColumnSelection hiddenCols)
-  {
-    superposeStructures(new AlignmentI[] { alignment },
-            new int[] { refStructure },
-            new ColumnSelection[] { hiddenCols });
-  }
-
-  /**
-   * 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
-   */
-  public void superposeStructures(AlignmentI[] _alignment,
-          int[] _refStructure, ColumnSelection[] _hiddenCols)
+  @Override
+  public List<String> executeCommand(StructureCommandI command,
+          boolean getReply)
   {
-    while (viewer.isScriptExecuting())
+    if (command == null)
     {
-      try
-      {
-        Thread.sleep(10);
-      } catch (InterruptedException i)
-      {
-      }
-    }
-
-    /*
-     * get the distinct structure files modelled
-     * (a file with multiple chains may map to multiple sequences)
-     */
-    String[] files = getPdbFile();
-    if (!waitForFileLoad(files))
-    {
-      return;
-    }
-
-    StringBuilder selectioncom = new StringBuilder(256);
-    // 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)
-    {
-      nSeconds = " 0.005 ";
-    }
-    else
-    {
-      nSeconds = " " + (2.0 / files.length) + " ";
-      // if (nSeconds).substring(0,5)+" ";
-    }
-    // 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++)
-    {
-      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;
-      }
-
-      /*
-       * 'matched' array will hold 'true' for visible alignment columns where
-       * all sequences have a residue with a mapping to the PDB structure
-       */
-      // TODO could use a BitSet for matched
-      boolean matched[] = new boolean[alignment.getWidth()];
-      for (int m = 0; m < matched.length; m++)
-      {
-        matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
-      }
-
-      SuperposeData[] structures = new SuperposeData[files.length];
-      for (int f = 0; f < files.length; f++)
-      {
-        structures[f] = new SuperposeData(alignment.getWidth());
-      }
-
-      /*
-       * Calculate the superposable alignment columns ('matched'), and the
-       * corresponding structure residue positions (structures.pdbResNo)
-       */
-      int candidateRefStructure = findSuperposableResidues(alignment,
-              matched, structures);
-      if (refStructure < 0)
-      {
-        /*
-         * If no reference structure was specified, pick the first one that has
-         * a mapping in the alignment
-         */
-        refStructure = candidateRefStructure;
-      }
-
-      String[] selcom = new String[files.length];
-      int nmatched = 0;
-      for (boolean b : matched)
-      {
-        if (b)
-        {
-          nmatched++;
-        }
-      }
-      if (nmatched < 4)
-      {
-        // TODO: bail out here because superposition illdefined?
-      }
-
-      /*
-       * generate select statements to select regions to superimpose structures
-       */
-      {
-        // TODO extract method to construct selection statements
-        for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-        {
-          String chainCd = ":" + structures[pdbfnum].chain;
-          int lpos = -1;
-          boolean run = false;
-          StringBuilder molsel = new StringBuilder();
-          molsel.append("{");
-          for (int r = 0; r < matched.length; r++)
-          {
-            if (matched[r])
-            {
-              int pdbResNo = structures[pdbfnum].pdbResNo[r];
-              if (lpos != pdbResNo - 1)
-              {
-                // discontinuity
-                if (lpos != -1)
-                {
-                  molsel.append(lpos);
-                  molsel.append(chainCd);
-                  molsel.append("|");
-                }
-                run = false;
-              }
-              else
-              {
-                // continuous run - and lpos >-1
-                if (!run)
-                {
-                  // at the beginning, so add dash
-                  molsel.append(lpos);
-                  molsel.append("-");
-                }
-                run = true;
-              }
-              lpos = pdbResNo;
-            }
-          }
-          /*
-           * 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;
-          }
-        }
-      }
-      StringBuilder command = new StringBuilder(256);
-      // command.append("set spinFps 10;\n");
-
-      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-      {
-        if (pdbfnum == refStructure || selcom[pdbfnum] == null
-                || selcom[refStructure] == null)
-        {
-          continue;
-        }
-        command.append("echo ");
-        command.append("\"Superposing (");
-        command.append(structures[pdbfnum].pdbId);
-        command.append(") against reference (");
-        command.append(structures[refStructure].pdbId);
-        command.append(")\";\ncompare " + nSeconds);
-        command.append("{");
-        command.append(Integer.toString(1 + pdbfnum));
-        command.append(".1} {");
-        command.append(Integer.toString(1 + refStructure));
-        // conformation=1 excludes alternate locations for CA (JAL-1757)
-        command.append(".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
-
-        // for (int s = 0; s < 2; s++)
-        // {
-        // command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
-        // }
-        command.append(selcom[pdbfnum]);
-        command.append(selcom[refStructure]);
-        command.append(" ROTATE TRANSLATE;\n");
-      }
-      if (selectioncom.length() > 0)
-      {
-        // TODO is performing selectioncom redundant here? is done later on
-        // System.out.println("Select regions:\n" + selectioncom.toString());
-        evalStateCommand("select *; cartoons off; backbone; select ("
-                + selectioncom.toString() + "); cartoons; ");
-        // selcom.append("; ribbons; ");
-        String cmdString = command.toString();
-        // System.out.println("Superimpose command(s):\n" + cmdString);
-
-        evalStateCommand(cmdString);
-      }
-    }
-    if (selectioncom.length() > 0)
-    {// finally, mark all regions that were superposed.
-      if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
-      {
-        selectioncom.setLength(selectioncom.length() - 1);
-      }
-      // 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());
+      return null;
     }
-  }
-
-  public void evalStateCommand(String command)
-  {
+    String cmd = command.getCommand();
     jmolHistory(false);
-    if (lastCommand == null || !lastCommand.equals(command))
+    if (lastCommand == null || !lastCommand.equals(cmd))
     {
-      viewer.evalStringQuiet(command + "\n");
+      jmolScript(cmd + "\n");
     }
     jmolHistory(true);
-    lastCommand = command;
-  }
-
-  /**
-   * colour any structures associated with sequences in the given alignment
-   * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
-   * if colourBySequence is enabled.
-   */
-  public void colourBySequence(AlignmentViewPanel alignmentv)
-  {
-    boolean showFeatures = alignmentv.getAlignViewport()
-            .isShowSequenceFeatures();
-    if (!colourBySequence || !isLoadingFinished())
-    {
-      return;
-    }
-    if (getSsm() == null)
-    {
-      return;
-    }
-    String[] files = getPdbFile();
-
-    SequenceRenderer sr = getSequenceRenderer(alignmentv);
-
-    FeatureRenderer fr = null;
-    if (showFeatures)
-    {
-      fr = getFeatureRenderer(alignmentv);
-    }
-    AlignmentI alignment = alignmentv.getAlignment();
-
-    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(
-            files, sr, fr, alignment))
-    {
-      for (String cbyseq : cpdbbyseq.commands)
-      {
-        executeWhenReady(cbyseq);
-      }
-    }
-  }
-
-  /**
-   * @param files
-   * @param sr
-   * @param fr
-   * @param alignment
-   * @return
-   */
-  protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, FeatureRenderer fr,
-          AlignmentI alignment)
-  {
-    return JmolCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, fr, alignment);
-  }
-
-  /**
-   * @param command
-   */
-  protected void executeWhenReady(String command)
-  {
-    evalStateCommand(command);
+    lastCommand = cmd;
+    return null;
   }
 
   public void createImage(String file, String type, int quality)
@@ -578,60 +179,13 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   }
 
   @Override
-  public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
+  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)
-  {
-    if (getModelNum(pdbfile) < 0)
-    {
-      return null;
-    }
-    // TODO: verify atomIndex is selecting correct model.
-    // return new Color(viewer.getAtomArgb(atomIndex)); Jmol 12.2.4
-    int colour = viewer.ms.at[atomIndex].atomPropertyInt(T.color);
-    return new Color(colour);
-  }
-
-  /**
-   * 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 jmol view'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 -
@@ -639,71 +193,37 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
    */
   private int _modelFileNameMap[];
 
-  // ////////////////////////////////
-  // /StructureListener
   @Override
-  public synchronized String[] getPdbFile()
+  public synchronized String[] getStructureFiles()
   {
-    if (viewer == null)
+    if (jmolViewer == null)
     {
       return new String[0];
     }
+
     if (modelFileNames == null)
     {
-      List<String> mset = new ArrayList<String>();
-      _modelFileNameMap = new int[viewer.ms.mc];
-      String m = viewer.ms.getModelFileName(0);
-      if (m != null)
+      int modelCount = jmolViewer.ms.mc;
+      String filePath = null;
+      List<String> mset = new ArrayList<>();
+      for (int i = 0; i < modelCount; ++i)
       {
-        String filePath = m;
-        try
-        {
-          filePath = new File(m).getAbsolutePath();
-        } catch (AccessControlException x)
-        {
-          // usually not allowed to do this in applet
-          System.err
-                  .println("jmolBinding: Using local file string from Jmol: "
-                          + m);
-        }
-        if (filePath.indexOf("/file:") != -1)
-        {
-          // applet path with docroot - discard as format won't match pdbfile
-          filePath = m;
-        }
-        mset.add(filePath);
-        _modelFileNameMap[0] = 0; // filename index for first model is always 0.
-      }
-      int j = 1;
-      for (int i = 1; i < viewer.ms.mc; i++)
-      {
-        m = viewer.ms.getModelFileName(i);
-        String filePath = m;
-        if (m != null)
-        {
-          try
-          {
-            filePath = new File(m).getAbsolutePath();
-          } catch (AccessControlException x)
-          {
-            // usually not allowed to do this in applet, so keep raw handle
-            // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
-          }
-        }
-
         /*
-         * add this model unless it is read from a structure file we have
-         * already seen (example: 2MJW is an NMR structure with 10 models)
+         * defensive check for null as getModelFileName can return null even when model
+         * count ms.mc is > 0
          */
-        if (!mset.contains(filePath))
+        filePath = jmolViewer.ms.getModelFileName(i);
+        if (filePath != null && !mset.contains(filePath))
         {
           mset.add(filePath);
-          _modelFileNameMap[j] = i; // record the model index for the filename
-          j++;
         }
       }
-      modelFileNames = mset.toArray(new String[mset.size()]);
+      if (!mset.isEmpty())
+      {
+        modelFileNames = mset.toArray(new String[mset.size()]);
+      }
     }
+
     return modelFileNames;
   }
 
@@ -717,17 +237,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return null;
   }
 
-  /**
-   * returns the current sequenceRenderer that should be used to colour the
-   * structures
-   * 
-   * @param alignment
-   * 
-   * @return
-   */
-  public abstract SequenceRenderer getSequenceRenderer(
-          AlignmentViewPanel alignment);
-
   // ///////////////////////////////
   // JmolStatusListener
 
@@ -747,7 +256,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     {
       if (resetLastRes.length() > 0)
       {
-        viewer.evalStringQuiet(resetLastRes.toString());
+        jmolScript(resetLastRes.toString());
         resetLastRes.setLength(0);
       }
       for (AtomSpec atom : atoms)
@@ -762,59 +271,39 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
           String pdbfile)
   {
-    if (modelFileNames == null)
-    {
-      return;
-    }
-
-    // look up file model number for this pdbfile
-    int mdlNum = 0;
-    // may need to adjust for URLencoding here - we don't worry about that yet.
-    while (mdlNum < modelFileNames.length
-            && !pdbfile.equals(modelFileNames[mdlNum]))
-    {
-      mdlNum++;
-    }
-    if (mdlNum == modelFileNames.length)
+    String modelId = getModelIdForFile(pdbfile);
+    if (modelId.isEmpty())
     {
       return;
     }
 
     jmolHistory(false);
 
+    StringBuilder selection = new StringBuilder(32);
     StringBuilder cmd = new StringBuilder(64);
-    cmd.append("select " + pdbResNum); // +modelNum
-
-    resetLastRes.append("select " + pdbResNum); // +modelNum
-
-    cmd.append(":");
-    resetLastRes.append(":");
+    selection.append("select ").append(String.valueOf(pdbResNum));
+    selection.append(":");
     if (!chain.equals(" "))
     {
-      cmd.append(chain);
-      resetLastRes.append(chain);
-    }
-    {
-      cmd.append(" /" + (mdlNum + 1));
-      resetLastRes.append("/" + (mdlNum + 1));
+      selection.append(chain);
     }
-    cmd.append(";wireframe 100;" + cmd.toString() + " and not hetero;");
+    selection.append(" /").append(modelId);
 
-    resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
-            + " and not hetero; spacefill 0;");
+    cmd.append(selection).append(";wireframe 100;").append(selection)
+            .append(" and not hetero;").append("spacefill 200;select none");
 
-    cmd.append("spacefill 200;select none");
+    resetLastRes.append(selection).append(";wireframe 0;").append(selection)
+            .append(" and not hetero; spacefill 0;");
 
-    viewer.evalStringQuiet(cmd.toString());
+    jmolScript(cmd.toString());
     jmolHistory(true);
-
   }
 
-  boolean debug = true;
+  private boolean debug = true;
 
   private void jmolHistory(boolean enable)
   {
-    viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
+    jmolScript("History " + ((debug || enable) ? "on" : "off"));
   }
 
   public void loadInline(String string)
@@ -828,10 +317,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     // Then, construct pass a reader for the string to Jmol.
     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
     // fileName, null, reader, false, null, null, 0);
-    viewer.openStringInline(string);
+    jmolViewer.openStringInline(string);
   }
 
-  public void mouseOverStructure(int atomIndex, String strInfo)
+  protected void mouseOverStructure(int atomIndex, final String strInfo)
   {
     int pdbResNum;
     int alocsep = strInfo.indexOf("^");
@@ -850,14 +339,14 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     // handle insertion codes
     if (alocsep != -1)
     {
-      pdbResNum = Integer.parseInt(strInfo.substring(
-              strInfo.indexOf("]") + 1, alocsep));
+      pdbResNum = Integer.parseInt(
+              strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
 
     }
     else
     {
-      pdbResNum = Integer.parseInt(strInfo.substring(
-              strInfo.indexOf("]") + 1, chainSeparator));
+      pdbResNum = Integer.parseInt(
+              strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
     }
     String chainId;
 
@@ -871,47 +360,76 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       chainId = " ";
     }
 
-    String pdbfilename = modelFileNames[frameNo]; // default is first or current
-    // model
+    String pdbfilename = modelFileNames[0]; // default is first model
     if (mdlSep > -1)
     {
       if (chainSeparator1 == -1)
       {
         chainSeparator1 = strInfo.indexOf(".", mdlSep);
       }
-      String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
-              chainSeparator1) : strInfo.substring(mdlSep + 1);
+      String mdlId = (chainSeparator1 > -1)
+              ? strInfo.substring(mdlSep + 1, chainSeparator1)
+              : strInfo.substring(mdlSep + 1);
       try
       {
         // recover PDB filename for the model hovered over.
-        int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
-                .intValue() - 1;
-        while (mnumber < _modelFileNameMap[_mp])
+        int mnumber = Integer.valueOf(mdlId).intValue() - 1;
+        if (_modelFileNameMap != null)
         {
-          _mp--;
+          int _mp = _modelFileNameMap.length - 1;
+
+          while (mnumber < _modelFileNameMap[_mp])
+          {
+            _mp--;
+          }
+          pdbfilename = modelFileNames[_mp];
         }
-        pdbfilename = modelFileNames[_mp];
-        if (pdbfilename == null)
+        else
         {
-          pdbfilename = new File(viewer.ms.getModelFileName(mnumber))
-                  .getAbsolutePath();
-        }
+          if (mnumber >= 0 && mnumber < modelFileNames.length)
+          {
+            pdbfilename = modelFileNames[mnumber];
+          }
 
+          if (pdbfilename == null)
+          {
+            pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
+                    .getAbsolutePath();
+          }
+        }
       } catch (Exception e)
       {
       }
-      ;
     }
-    if (lastMessage == null || !lastMessage.equals(strInfo))
+
+    /*
+     * highlight position on alignment(s); if some text is returned, show this as a
+     * second line on the structure hover tooltip
+     */
+    String label = getSsm().mouseOverStructure(pdbResNum, chainId,
+            pdbfilename);
+    if (label != null)
     {
-      getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
+      // change comma to pipe separator (newline token for Jmol)
+      label = label.replace(',', '|');
+      StringTokenizer toks = new StringTokenizer(strInfo, " ");
+      StringBuilder sb = new StringBuilder();
+      sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
+              .append(chainId).append("/1");
+      sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
+              .append(toks.nextToken());
+      sb.append("|").append(label).append("\"");
+      executeCommand(new StructureCommand(sb.toString()), false);
     }
-
-    lastMessage = strInfo;
   }
 
   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
   {
+    if (strInfo.equals(lastMessage))
+    {
+      return;
+    }
+    lastMessage = strInfo;
     if (data != null)
     {
       System.err.println("Ignoring additional hover info: " + data
@@ -922,15 +440,16 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   /*
    * { if (history != null && strStatus != null &&
-   * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
-   * } }
+   * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
+   * }
    */
 
-  public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
+  public void notifyAtomPicked(int atomIndex, String strInfo,
+          String strData)
   {
     /**
      * this implements the toggle label behaviour copied from the original
-     * structure viewer, MCView
+     * structure viewer, mc_view
      */
     if (strData != null)
     {
@@ -961,12 +480,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
     if (!atomsPicked.contains(picked))
     {
-      viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
+      jmolScript("select " + picked + ";label %n %r:%c");
       atomsPicked.addElement(picked);
     }
     else
     {
-      viewer.evalString("select " + picked + ";label off");
+      jmolViewer.evalString("select " + picked + ";label off");
       atomsPicked.removeElement(picked);
     }
     jmolHistory(true);
@@ -983,6 +502,28 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   @Override
   public void notifyCallback(CBK type, Object[] data)
   {
+    /*
+     * ensure processed in AWT thread to avoid risk of deadlocks
+     */
+    SwingUtilities.invokeLater(new Runnable()
+    {
+
+      @Override
+      public void run()
+      {
+        processCallback(type, data);
+      }
+    });
+  }
+
+  /**
+   * Processes one callback notification from Jmol
+   * 
+   * @param type
+   * @param data
+   */
+  protected void processCallback(CBK type, Object[] data)
+  {
     try
     {
       switch (type)
@@ -997,6 +538,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
                 (String) data[0]);
         // also highlight in alignment
+        // deliberate fall through
       case HOVER:
         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
                 (String) data[0]);
@@ -1009,8 +551,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         sendConsoleEcho((String) data[1]);
         break;
       case MESSAGE:
-        sendConsoleMessage((data == null) ? ((String) null)
-                : (String) data[1]);
+        sendConsoleMessage(
+                (data == null) ? ((String) null) : (String) data[1]);
         break;
       case ERROR:
         // System.err.println("Ignoring error callback.");
@@ -1023,8 +565,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
       case CLICK:
       default:
-        System.err.println("Unhandled callback " + type + " "
-                + data[1].toString());
+        System.err.println(
+                "Unhandled callback " + type + " " + data[1].toString());
         break;
       }
     } catch (Exception e)
@@ -1081,10 +623,13 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     fileLoadingError = null;
     String[] oldmodels = modelFileNames;
     modelFileNames = null;
-    chainNames = new ArrayList<String>();
-    chainFile = new Hashtable<String, String>();
     boolean notifyLoaded = false;
-    String[] modelfilenames = getPdbFile();
+    String[] modelfilenames = getStructureFiles();
+    if (modelfilenames == null)
+    {
+      // Jmol is still loading files!
+      return;
+    }
     // first check if we've lost any structures
     if (oldmodels != null && oldmodels.length > 0)
     {
@@ -1133,8 +678,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         // calculate essential attributes for the pdb data imported inline.
         // prolly need to resolve modelnumber properly - for now just use our
         // 'best guess'
-        pdbfile = viewer.getData("" + (1 + _modelFileNameMap[modelnum])
-                + ".0", "PDB");
+        pdbfile = jmolViewer.getData(
+                "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
       }
       // search pdbentries and sequences to find correct pdbentry for this
       // model
@@ -1148,7 +693,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           // see JAL-623 - need method of matching pasted data up
           {
             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
-                    pdbfile, AppletFormatAdapter.PASTE);
+                    pdbfile, DataSourceType.PASTE, getIProgressIndicator());
             getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
             matches = true;
             foundEntry = true;
@@ -1166,12 +711,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
             // needs
             // to be tested. See mantis bug
             // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
-            String protocol = AppletFormatAdapter.URL;
+            DataSourceType protocol = DataSourceType.URL;
             try
             {
               if (fl.exists())
               {
-                protocol = AppletFormatAdapter.FILE;
+                protocol = DataSourceType.FILE;
               }
             } catch (Exception e)
             {
@@ -1180,21 +725,14 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
             }
             // Explicitly map to the filename used by Jmol ;
             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
-                    fileName, protocol);
+                    fileName, protocol, getIProgressIndicator());
             // pdbentry[pe].getFile(), protocol);
 
           }
         }
         if (matches)
         {
-          // add an entry for every chain in the model
-          for (int i = 0; i < pdb.getChains().size(); i++)
-          {
-            String chid = new String(pdb.getId() + ":"
-                    + pdb.getChains().elementAt(i).id);
-            chainFile.put(chid, fileName);
-            chainNames.add(chid);
-          }
+          stashFoundChains(pdb, fileName);
           notifyLoaded = true;
         }
       }
@@ -1204,7 +742,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         // this is a foreign pdb file that jalview doesn't know about - add
         // it to the dataset and try to find a home - either on a matching
         // sequence or as a new sequence.
-        String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
+        String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
                 "PDB");
         // parse pdb file into a chain, etc.
         // locate best match for pdb in associated views and add mapping to
@@ -1223,7 +761,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     // }
     if (!isLoadingFromArchive())
     {
-      viewer.evalStringQuiet("model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
+      jmolScript(
+              "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
     }
     // register ourselves as a listener and notify the gui that it needs to
     // update itself.
@@ -1233,7 +772,9 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       FeatureRenderer fr = getFeatureRenderer(null);
       if (fr != null)
       {
-        fr.featuresAdded();
+        FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
+        ((AppJmol) getViewer()).getAlignmentPanel().av
+                .applyFeaturesStyle(colours);
       }
       refreshGUI();
       loadNotifiesHandled++;
@@ -1241,10 +782,9 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     setLoadingFromArchive(false);
   }
 
-  @Override
-  public List<String> getChainNames()
+  protected IProgressIndicator getIProgressIndicator()
   {
-    return chainNames;
+    return null;
   }
 
   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
@@ -1263,8 +803,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public abstract void sendConsoleEcho(String strEcho); /*
                                                          * { showConsole(true);
                                                          * 
-                                                         * history.append("\n" +
-                                                         * strEcho); }
+                                                         * history.append("\n" + strEcho); }
                                                          */
 
   // /End JmolStatusListener
@@ -1286,34 +825,11 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   }
 
-  public void setJalviewColourScheme(ColourSchemeI cs)
-  {
-    colourBySequence = false;
-
-    if (cs == null)
-    {
-      return;
-    }
-
-    jmolHistory(false);
-    StringBuilder command = new StringBuilder(128);
-    command.append("select *;color white;");
-    List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
-            false);
-    for (String res : residueSet)
-    {
-      Color col = cs.findColour(res.charAt(0));
-      command.append("select " + res + ";color[" + col.getRed() + ","
-              + col.getGreen() + "," + col.getBlue() + "];");
-    }
-
-    evalStateCommand(command.toString());
-    jmolHistory(true);
-  }
-
   public void showHelp()
   {
-    showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
+    showUrl("http://wiki.jmol.org"
+    // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
+            , "jmolHelp");
   }
 
   /**
@@ -1324,20 +840,22 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public abstract void showUrl(String url, String target);
 
   /**
-   * called when the binding thinks the UI needs to be refreshed after a Jmol
-   * state change. this could be because structures were loaded, or because an
-   * error has occured.
-   */
-  public abstract void refreshGUI();
-
-  /**
    * called to show or hide the associated console window container.
    * 
    * @param show
    */
   public abstract void showConsole(boolean show);
 
+  public static Viewer getJmolData(JmolParser jmolParser)
+  {
+    return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
+            "-x -o -n", jmolParser);
+  }
+
   /**
+   * 
+   * 
+   * 
    * @param renderPanel
    * @param jmolfileio
    *          - when true will initialise jmol's file IO system (should be false
@@ -1368,25 +886,36 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
    * @param consolePanel
    *          - panel to contain Jmol console
    * @param buttonsToShow
-   *          - buttons to show on the console, in ordr
+   *          - buttons to show on the console, in order
    */
   public void allocateViewer(Container renderPanel, boolean jmolfileio,
           String htmlName, URL documentBase, URL codeBase,
           String commandOptions, final Container consolePanel,
           String buttonsToShow)
   {
+
+    System.err.println("Allocating Jmol Viewer: " + commandOptions);
+
     if (commandOptions == null)
     {
       commandOptions = "";
     }
-    viewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
-            (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
-                    + ((Object) this).toString(), documentBase, codeBase,
+    jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
+            (jmolfileio ? new SmarterJmolAdapter() : null),
+            htmlName + ((Object) this).toString(), documentBase, codeBase,
             commandOptions, this);
 
-    viewer.setJmolStatusListener(this); // extends JmolCallbackListener
+    jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
 
-    console = createJmolConsole(consolePanel, buttonsToShow);
+    try
+    {
+      console = createJmolConsole(consolePanel, buttonsToShow);
+    } catch (Throwable e)
+    {
+      System.err.println("Could not create Jmol application console. "
+              + e.getMessage());
+      e.printStackTrace();
+    }
     if (consolePanel != null)
     {
       consolePanel.addComponentListener(this);
@@ -1398,15 +927,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   protected abstract JmolAppConsoleInterface createJmolConsole(
           Container consolePanel, String buttonsToShow);
 
-  protected org.jmol.api.JmolAppConsoleInterface console = null;
+  // BH 2018 -- Jmol console is not working due to problems with styled
+  // documents.
 
-  public void setBackgroundColour(java.awt.Color col)
-  {
-    jmolHistory(false);
-    viewer.evalStringQuiet("background [" + col.getRed() + ","
-            + col.getGreen() + "," + col.getBlue() + "];");
-    jmolHistory(true);
-  }
+  protected org.jmol.api.JmolAppConsoleInterface console = null;
 
   @Override
   public int[] resizeInnerPanel(String data)
@@ -1468,4 +992,64 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   {
     showConsole(false);
   }
+
+  @Override
+  protected String getModelIdForFile(String pdbFile)
+  {
+    if (modelFileNames == null)
+    {
+      return "";
+    }
+    for (int i = 0; i < modelFileNames.length; i++)
+    {
+      if (modelFileNames[i].equalsIgnoreCase(pdbFile))
+      {
+        return String.valueOf(i + 1);
+      }
+    }
+    return "";
+  }
+
+  @Override
+  protected ViewerType getViewerType()
+  {
+    return ViewerType.JMOL;
+  }
+
+  @Override
+  protected String getModelId(int pdbfnum, String file)
+  {
+    return String.valueOf(pdbfnum + 1);
+  }
+
+  /**
+   * Returns ".spt" - the Jmol session file extension
+   * 
+   * @return
+   * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
+   */
+  @Override
+  public String getSessionFileExtension()
+  {
+    return ".spt";
+  }
+
+  @Override
+  public void selectionChanged(BS arg0)
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
+  {
+    return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
+  }
+
+  @Override
+  public String getHelpURL()
+  {
+    return "http://wiki.jmol.org"; // BH 2018
+  }
 }