upgrading structure/sequence binding so that a structure view can contain many pdb...
[jalview.git] / src / jalview / gui / AppJmol.java
index 7945fa7..dcca874 100644 (file)
@@ -26,6 +26,7 @@ import java.awt.event.*;
 import java.io.*;
 
 import jalview.jbgui.GStructureViewer;
+import jalview.api.SequenceStructureBinding;
 import jalview.bin.Cache;
 import jalview.datamodel.*;
 import jalview.gui.*;
@@ -41,7 +42,7 @@ import org.jmol.popup.*;
 import org.jmol.viewer.JmolConstants;
 
 public class AppJmol extends GStructureViewer implements StructureListener,
-        JmolStatusListener, Runnable
+        JmolStatusListener, Runnable, SequenceStructureBinding
 
 {
   JmolViewer viewer;
@@ -280,14 +281,32 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
   void centerViewer()
   {
+    jmolHistory(false);
     StringBuffer cmd = new StringBuffer();
+    String lbl;
+    int mlength, p,mnum;
     for (int i = 0; i < chainMenu.getItemCount(); i++)
     {
       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
       {
         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
         if (item.isSelected())
-          cmd.append(":" + item.getText() + " or ");
+        {          lbl = item.getText();
+        mlength = 0;
+        do
+        {
+          p = mlength;
+          mlength = lbl.indexOf(":", p);
+        } while (p < mlength && mlength < (lbl.length() - 2));
+        if (pdbentry.getId().equals(lbl.substring(0,mlength)))
+        {
+          mnum = 1+getModelNum(pdbentry.getFile());
+        if (mnum>0)
+          {cmd.append(":" + lbl.substring(mlength + 1) + " /"
+                + mnum + " or ");
+          }
+        }
+        }
       }
     }
 
@@ -296,19 +315,33 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
     viewer.evalStringQuiet("select *;restrict " + cmd + ";cartoon;center "
             + cmd);
+    jmolHistory(true);
+  }
+  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;
   }
 
   void closeViewer()
   {
     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
+    // remove listeners for all structures in viewer
+    StructureSelectionManager.getStructureSelectionManager()
+            .removeStructureViewerListener(this, getPdbFile());
+    // and shut down jmol
     viewer.evalStringQuiet("zap");
     viewer.setJmolStatusListener(null);
     viewer = null;
-
-    // We'll need to find out what other
-    // listeners need to be shut down in Jmol
-    StructureSelectionManager.getStructureSelectionManager()
-            .removeStructureViewerListener(this, pdbentry.getFile());
   }
 
   public void run()
@@ -451,15 +484,19 @@ public class AppJmol extends GStructureViewer implements StructureListener,
   {
     colourBySequence = false;
     seqColour.setSelected(false);
+    jmolHistory(false);
     viewer.evalStringQuiet("select *;color chain");
+    jmolHistory(true);
   }
 
   public void chargeColour_actionPerformed(ActionEvent actionEvent)
   {
     colourBySequence = false;
     seqColour.setSelected(false);
+    jmolHistory(false);
     viewer.evalStringQuiet("select *;color white;select ASP,GLU;color red;"
             + "select LYS,ARG;color blue;select CYS;color yellow");
+    jmolHistory(true);
   }
 
   public void zappoColour_actionPerformed(ActionEvent actionEvent)
@@ -499,6 +536,7 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
   public void setJalviewColourScheme(ColourSchemeI cs)
   {
+    jmolHistory(false);
     colourBySequence = false;
     seqColour.setSelected(false);
 
@@ -525,6 +563,7 @@ public class AppJmol extends GStructureViewer implements StructureListener,
     }
 
     viewer.evalStringQuiet(command.toString());
+    jmolHistory(true);
   }
 
   public void userColour_actionPerformed(ActionEvent actionEvent)
@@ -539,10 +578,16 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
     if (col != null)
     {
+      jmolHistory(false);
       viewer.evalStringQuiet("background [" + col.getRed() + ","
               + col.getGreen() + "," + col.getBlue() + "];");
+      jmolHistory(true);
     }
   }
+  private void jmolHistory(boolean enable)
+  {
+    viewer.setBooleanProperty("history", enable);
+  }
 
   public void jmolHelp_actionPerformed(ActionEvent actionEvent)
   {
@@ -554,39 +599,122 @@ public class AppJmol extends GStructureViewer implements StructureListener,
     {
     }
   }
+  String[] modelFileNames = null;
 
   // ////////////////////////////////
   // /StructureListener
-  public String getPdbFile()
+  public String[] getPdbFile()
   {
-    return pdbentry.getFile();
+    if (modelFileNames == null)
+    {
+      String mset[] = new String[viewer.getModelCount()];
+      for (int i = 0; i < mset.length; i++)
+      {
+        try {
+          String mname = viewer.getModelFileName(i);
+          if (mname==null)
+          {
+            System.err.println("Model "+i+" has no filename!");
+            continue;
+          }
+          File fpath = new File(mname);
+          mset[i] = fpath.toString();
+        } catch (Exception e)
+        {
+          System.err.println("Couldn't parse "+viewer.getModelFileName(i)+" as a file!");
+        }
+      }
+      modelFileNames = mset;
+    }
+    return modelFileNames;
   }
 
   Pattern pattern = Pattern
-          .compile("\\[(.*)\\]([0-9]+)(:[a-zA-Z]*)?\\.([a-zA-Z]+)(/[0-9]*)?");
+          .compile("\\[(.*)\\]([0-9]+)(:[a-zA-Z]*)?\\.([a-zA-Z]+).*(/[0-9]*)?");
 
   String lastMessage;
 
   public void mouseOverStructure(int atomIndex, String strInfo)
   {
+    // copied from AppJmol - will be refactored to binding eventually
+    int pdbResNum;
+    int mdlSep = strInfo.indexOf("/");
+    int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
+
+    if (chainSeparator == -1)
+    {
+      chainSeparator = strInfo.indexOf(".");
+      if (mdlSep > -1 && mdlSep < chainSeparator)
+      {
+        chainSeparator1 = chainSeparator;
+        chainSeparator = mdlSep;
+      }
+    }
+    pdbResNum = Integer.parseInt(strInfo.substring(
+            strInfo.indexOf("]") + 1, chainSeparator));
+
+    String chainId;
+
+    if (strInfo.indexOf(":") > -1)
+      chainId = strInfo.substring(strInfo.indexOf(":") + 1, strInfo
+              .indexOf("."));
+    else
+    {
+      chainId = " ";
+    }
+
+    String pdbfilename = pdbentry.getFile();
+    if (mdlSep > -1)
+    {
+      if (chainSeparator1 == -1)
+      {
+        chainSeparator1 = strInfo.indexOf(".", mdlSep);
+      }
+      String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
+              chainSeparator1) : strInfo.substring(mdlSep + 1);
+      try
+      {
+        // recover PDB filename for the model hovered over.
+        pdbfilename = viewer
+                .getModelFileName(new Integer(mdlId).intValue() - 1);
+      } catch (Exception e)
+      {
+      }
+      ;
+    }
+    if (lastMessage == null || !lastMessage.equals(strInfo))
+      ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
+
+    lastMessage = strInfo;
+/*
+ * Old Implementation based on Pattern regex.
     Matcher matcher = pattern.matcher(strInfo);
     matcher.find();
     matcher.group(1);
     int pdbResNum = Integer.parseInt(matcher.group(2));
     String chainId = matcher.group(3);
-
+    
     if (chainId != null)
       chainId = chainId.substring(1, chainId.length());
     else
     {
       chainId = " ";
     }
+    String mdlId = matcher.group(4);
+    String pdbfilename = pdbentry.getFile();
 
+    if (mdlId!=null && mdlId.length()>0)
+    {
+      try {
+        // recover PDB filename for the model hovered over.
+        pdbfilename = viewer.getModelFileName(new Integer(mdlId).intValue()-1);
+      } catch (Exception e) {};
+    }
     if (lastMessage == null || !lastMessage.equals(strInfo))
     {
-      ssm.mouseOverStructure(pdbResNum, chainId, pdbentry.getFile());
+      ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
     }
-    lastMessage = strInfo;
+    lastMessage = strInfo; */
   }
 
   StringBuffer resetLastRes = new StringBuffer();
@@ -596,38 +724,47 @@ public class AppJmol extends GStructureViewer implements StructureListener,
   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
           String pdbfile)
   {
-    // TODO: rna: remove CA dependency in select string
-    if (!pdbfile.equals(pdbentry.getFile()))
+    int mdlNum = 1+getModelNum(pdbfile);
+    if (mdlNum==0)
+    {
       return;
+    }
 
+    jmolHistory(false);
+    // if (!pdbfile.equals(pdbentry.getFile()))
+    // return;
     if (resetLastRes.length() > 0)
     {
       viewer.evalStringQuiet(resetLastRes.toString());
     }
 
     eval.setLength(0);
-    eval.append("select " + pdbResNum);
+    eval.append("select " + pdbResNum); // +modelNum
 
     resetLastRes.setLength(0);
-    resetLastRes.append("select " + pdbResNum);
+    resetLastRes.append("select " + pdbResNum); // +modelNum
 
-    eval.append(":");
-    resetLastRes.append(":");
     if (!chain.equals(" "))
     {
+      eval.append(":");
+      resetLastRes.append(":");
       eval.append(chain);
       resetLastRes.append(chain);
     }
-
-    eval.append(";wireframe 100;" + eval.toString() + " and not hetero;"); // ".*;");
+    // if (mdlNum != 0)
+    {
+      eval.append(" /" + (mdlNum));
+      resetLastRes.append(" /" + (mdlNum));
+    }
+    eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
 
     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
-    // + ".*;spacefill 0;");
-            + " and not hetero;spacefill 0;");
+            + " and not hetero; spacefill 0;");
 
     eval.append("spacefill 200;select none");
-    // System.out.println("jmol:\n"+eval+"\n");
+
     viewer.evalStringQuiet(eval.toString());
+    jmolHistory(true);
   }
 
   public Color getColour(int atomIndex, int pdbResNum, String chain,
@@ -658,11 +795,8 @@ public class AppJmol extends GStructureViewer implements StructureListener,
     if (!colourBySequence || ap.alignFrame.getCurrentView() != ap.av)
       return;
 
-    StructureMapping[] mapping = ssm.getMapping(pdbentry.getFile());
-
-    if (mapping.length < 1)
-      return;
-
+    String[] files = getPdbFile();
+    
     SequenceRenderer sr = new SequenceRenderer(ap.av);
 
     boolean showFeatures = false;
@@ -679,6 +813,13 @@ public class AppJmol extends GStructureViewer implements StructureListener,
     }
 
     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 sp, s = 0; s < sequence.length; s++)
@@ -707,36 +848,38 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
             if (showFeatures)
               col = fr.findFeatureColour(col, asp, r);
-
-            if (command.toString().endsWith(
-                    ":" + mapping[m].getChain() + ";color[" + col.getRed()
-                            + "," + col.getGreen() + "," + col.getBlue()
-                            + "]"))
+            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, pos);
               continue;
             }
 
             command.append(";select " + pos);
-
-            if (!mapping[m].getChain().equals(" "))
-            {
-              command.append(":" + mapping[m].getChain());
-            }
-
-            command.append(";color[" + col.getRed() + "," + col.getGreen()
-                    + "," + col.getBlue() + "]");
-
+            command.append(newSelcom);
           }
           break;
         }
       }
+      }
     }
+    jmolHistory(false);
 
     if (lastCommand == null || !lastCommand.equals(command.toString()))
     {
       viewer.evalStringQuiet(command.toString());
     }
+    jmolHistory(true);
     lastCommand = command.toString();
   }
 
@@ -778,7 +921,7 @@ public class AppJmol extends GStructureViewer implements StructureListener,
     System.out.println("JMOL CREATE IMAGE");
   }
 
-  public void notifyFileLoaded(String fullPathName, String fileName,
+  public void notifyFileLoaded(String fullPathName, String fileName2,
           String modelName, String errorMsg, int modelParts)
   {
     if (errorMsg != null)
@@ -789,29 +932,38 @@ public class AppJmol extends GStructureViewer implements StructureListener,
     }
 
     fileLoadingError = null;
+    modelFileNames = null;
+    
+    String[] modelfilenames = getPdbFile();
+    ssm = StructureSelectionManager.getStructureSelectionManager();
+    boolean modelsloaded=false;
+    for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
+    {
+      String fileName = modelfilenames[modelnum];
 
     if (fileName != null)
     {
-      // TODO: do some checking using the modelPts number of parts against our own estimate of the number of chains
+      modelsloaded=true;
+      // search pdbentries and sequences to find correct pdbentry and sequence[] pair for this filename
+      if (pdbentry.getFile().equals(fileName))
+      {
+      // TODO: do some checking using the modelPts number of parts against our
+      // own estimate of the number of chains
       // FILE LOADED OK
-      ssm = StructureSelectionManager.getStructureSelectionManager();
       MCview.PDBfile pdbFile = ssm.setMapping(sequence, chains, pdbentry
               .getFile(), AppletFormatAdapter.FILE);
-      ssm.addStructureViewerListener(this);
       Vector chains = new Vector();
       for (int i = 0; i < pdbFile.chains.size(); i++)
       {
         chains
-                .addElement(((MCview.PDBChain) pdbFile.chains.elementAt(i)).id);
+                .addElement(new String(pdbFile.id+":"+((MCview.PDBChain) pdbFile.chains.elementAt(i)).id));
       }
       setChainMenuItems(chains);
 
-      jmolpopup.updateComputedMenus();
-
       if (!loadingFromArchive)
       {
         viewer
-                .evalStringQuiet("select backbone;restrict;cartoon;wireframe off;spacefill off");
+                .evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
 
         colourBySequence(ap);
       }
@@ -820,8 +972,23 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
       loadingFromArchive = false;
     }
-    else
-      return;
+    else {
+      // 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",
+              "PDB");
+      // parse pdb file into a chain, etc.
+      // locate best match for pdb in associated views and add mapping to
+      // ssm
+      modelsloaded=true;
+    }
+    }
+    }
+    if (modelsloaded)
+    {
+      ssm.addStructureViewerListener(this);
+      jmolpopup.updateComputedMenus();
+    }
   }
 
   public void sendConsoleEcho(String strEcho)
@@ -854,10 +1021,12 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
   {
-    if (strData!=null)
+    if (strData != null)
     {
-      Cache.log.info("Non null pick data string: "+strData+" (other info: '"+strInfo+"' pos "+atomIndex+")");
+      Cache.log.info("Non null pick data string: " + strData
+              + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
     }
+    /*
     Matcher matcher = pattern.matcher(strInfo);
     matcher.find();
 
@@ -867,17 +1036,35 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
     String picked = resnum;
 
+
     if (chainId != null)
       picked += (":" + chainId.substring(1, chainId.length()));
-
-    picked = "(("+picked+".CA" + ")|("+picked+".P"+"))";
-
+*/
+    int chainSeparator = strInfo.indexOf(":");
+    int p=0;
+    if (chainSeparator == -1)
+      chainSeparator = strInfo.indexOf(".");
+
+    String picked = strInfo.substring(strInfo.indexOf("]") + 1,
+            chainSeparator);
+    String mdlString="";
+    if ((p=strInfo.indexOf(":")) > -1)
+      picked += strInfo.substring(p + 1, strInfo
+              .indexOf("."));
+
+    if ((p=strInfo.indexOf("/"))> -1)
+            {
+      mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
+            }
+    picked = "((" + picked + ".CA" + mdlString+")|(" + picked + ".P" + mdlString+"))";
+    jmolHistory(false);
     if (!atomsPicked.contains(picked))
     {
-      if (chainId != null)
+      // TODO: re-instate chain ID separator dependent labelling for both applet and application
+//      if (chainId != null)
         viewer.evalString("select " + picked + ";label %n %r:%c");
-      else
-        viewer.evalString("select " + picked + ";label %n %r");
+//      else
+//        viewer.evalString("select " + picked + ";label %n %r");
       atomsPicked.addElement(picked);
     }
     else
@@ -885,7 +1072,7 @@ public class AppJmol extends GStructureViewer implements StructureListener,
       viewer.evalString("select " + picked + ";label off");
       atomsPicked.removeElement(picked);
     }
-
+    jmolHistory(true);
     if (scriptWindow != null)
     {
       scriptWindow.sendConsoleMessage(strInfo);
@@ -895,9 +1082,10 @@ public class AppJmol extends GStructureViewer implements StructureListener,
 
   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
   {
-    if (data!=null)
+    if (data != null)
     {
-      Cache.log.info("Non null hover data string: "+data+" (other info: '"+strInfo+"' pos "+atomIndex+")");
+      Cache.log.info("Non null hover data string: " + data
+              + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
     }
     mouseOverStructure(atomIndex, strInfo);
   }
@@ -905,11 +1093,12 @@ public class AppJmol extends GStructureViewer implements StructureListener,
   @Override
   public void showUrl(String url)
   {
-    try {
+    try
+    {
       jalview.util.BrowserLauncher.openURL(url);
     } catch (IOException e)
     {
-      Cache.log.error("Failed to launch Jmol-associated url "+url,e);
+      Cache.log.error("Failed to launch Jmol-associated url " + url, e);
       // TODO: 2.6 : warn user if browser was not configured.
     }
   }
@@ -1024,39 +1213,44 @@ public class AppJmol extends GStructureViewer implements StructureListener,
   @Override
   public void notifyCallback(int type, Object[] data)
   {
-    try {
-    switch (type)
+    try
     {
-    case JmolConstants.CALLBACK_LOADSTRUCT:
-      notifyFileLoaded((String) data[1], (String) data[2], 
-              (String) data[3], (String) data[4], ((Integer) data[5]).intValue());
-              
-      break;
-    case JmolConstants.CALLBACK_PICK:
-      notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
-      // also highlight in alignment
-    case JmolConstants.CALLBACK_HOVER:
-      notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
-      break;
-    case JmolConstants.CALLBACK_SCRIPT:
-      notifyScriptTermination((String)data[2], ((Integer)data[3]).intValue());
-      break;
-    case JmolConstants.CALLBACK_ECHO:
-      sendConsoleEcho((String)data[1]);
-      break;
-    case JmolConstants.CALLBACK_MESSAGE:
-      sendConsoleMessage((data==null) ? ((String) null) : (String)data[1]);
-      break;
-    case JmolConstants.CALLBACK_MEASURE:
-    case JmolConstants.CALLBACK_CLICK:
+      switch (type)
+      {
+      case JmolConstants.CALLBACK_LOADSTRUCT:
+        notifyFileLoaded((String) data[1], (String) data[2],
+                (String) data[3], (String) data[4], ((Integer) data[5])
+                        .intValue());
+
+        break;
+      case JmolConstants.CALLBACK_PICK:
+        notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
+                (String) data[0]);
+        // also highlight in alignment
+      case JmolConstants.CALLBACK_HOVER:
+        notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
+                (String) data[0]);
+        break;
+      case JmolConstants.CALLBACK_SCRIPT:
+        notifyScriptTermination((String) data[2], ((Integer) data[3])
+                .intValue());
+        break;
+      case JmolConstants.CALLBACK_ECHO:
+        sendConsoleEcho((String) data[1]);
+        break;
+      case JmolConstants.CALLBACK_MESSAGE:
+        sendConsoleMessage((data == null) ? ((String) null)
+                : (String) data[1]);
+        break;
+      case JmolConstants.CALLBACK_MEASURE:
+      case JmolConstants.CALLBACK_CLICK:
       default:
-        System.err.println("Unhandled callback "+type+" "+data);
+        System.err.println("Unhandled callback " + type + " " + data);
         break;
-    }
-    }
-    catch (Exception e)
+      }
+    } catch (Exception e)
     {
-      Cache.log.warn("Squashed Jmol callback handler error: ",e);
+      Cache.log.warn("Squashed Jmol callback handler error: ", e);
     }
   }
 
@@ -1075,7 +1269,7 @@ public class AppJmol extends GStructureViewer implements StructureListener,
     case JmolConstants.CALLBACK_ERROR:
       return true;
     case JmolConstants.CALLBACK_CLICK:
-      case JmolConstants.CALLBACK_ANIMFRAME:
+    case JmolConstants.CALLBACK_ANIMFRAME:
     case JmolConstants.CALLBACK_MINIMIZATION:
     case JmolConstants.CALLBACK_RESIZE:
     case JmolConstants.CALLBACK_SYNC:
@@ -1087,8 +1281,9 @@ public class AppJmol extends GStructureViewer implements StructureListener,
   public void setCallbackFunction(String callbackType,
           String callbackFunction)
   {
-    Cache.log.debug("Ignoring set-callback request to associate "+callbackType+" with function "+callbackFunction);
-    
+    Cache.log.debug("Ignoring set-callback request to associate "
+            + callbackType + " with function " + callbackFunction);
+
   }
 
 }