916579fe26681739bbd84e6ad50802d6e4a444d7
[jalview.git] / src / jalview / ext / jmol / JalviewJmolBinding.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ext.jmol;
19
20 import java.io.File;
21 import java.net.URL;
22 import java.util.*;
23 import java.applet.Applet;
24 import java.awt.*;
25 import java.awt.event.*;
26
27 import javax.swing.JPanel;
28
29 import jalview.api.FeatureRenderer;
30 import jalview.api.SequenceRenderer;
31 import jalview.api.SequenceStructureBinding;
32 import jalview.datamodel.*;
33 import jalview.structure.*;
34 import jalview.io.*;
35
36 import org.jmol.api.*;
37 import org.jmol.adapter.smarter.SmarterJmolAdapter;
38
39 import org.jmol.popup.*;
40 import org.jmol.viewer.JmolConstants;
41 import org.jmol.viewer.Viewer;
42
43 import jalview.schemes.*;
44
45 public abstract class JalviewJmolBinding implements StructureListener,
46         JmolStatusListener, SequenceStructureBinding, JmolSelectionListener, ComponentListener
47
48 {
49   /**
50    * set if Jmol state is being restored from some source - instructs binding
51    * not to apply default display style when structure set is updated for first
52    * time.
53    */
54   private boolean loadingFromArchive = false;
55
56   /**
57    * state flag used to check if the Jmol viewer's paint method can be called
58    */
59   private boolean finishedInit = false;
60
61   public boolean isFinishedInit()
62   {
63     return finishedInit;
64   }
65
66   public void setFinishedInit(boolean finishedInit)
67   {
68     this.finishedInit = finishedInit;
69   }
70
71   boolean allChainsSelected = false;
72
73   /**
74    * when true, try to search the associated datamodel for sequences that are
75    * associated with any unknown structures in the Jmol view.
76    */
77   private boolean associateNewStructs = false;
78
79   Vector atomsPicked = new Vector();
80
81   public Vector chainNames;
82   Hashtable chainFile;
83
84   /**
85    * array of target chains for seuqences - tied to pdbentry and sequence[]
86    */
87   protected String[][] chains;
88
89   boolean colourBySequence = true;
90
91   StringBuffer eval = new StringBuffer();
92
93   public String fileLoadingError;
94
95   /**
96    * the default or current model displayed if the model cannot be identified
97    * from the selection message
98    */
99   int frameNo = 0;
100
101   protected JmolPopup jmolpopup;
102
103   String lastCommand;
104
105   String lastMessage;
106
107   boolean loadedInline;
108
109   /**
110    * current set of model filenames loaded in the Jmol instance
111    */
112   String[] modelFileNames = null;
113
114   public PDBEntry[] pdbentry;
115
116   /**
117    * datasource protocol for access to PDBEntry
118    */
119   String protocol = null;
120
121   StringBuffer resetLastRes = new StringBuffer();
122
123   /**
124    * sequences mapped to each pdbentry
125    */
126   public SequenceI[][] sequence;
127
128   StructureSelectionManager ssm;
129
130   public JmolViewer viewer;
131
132   public JalviewJmolBinding(PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
133           String[][] chains, String protocol)
134   {
135     this.sequence = sequenceIs;
136     this.chains = chains;
137     this.pdbentry = pdbentry;
138     this.protocol = protocol;
139     if (chains == null)
140     {
141       this.chains = new String[pdbentry.length][];
142     }
143     /*
144      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
145      * "jalviewJmol", ap.av.applet .getDocumentBase(),
146      * ap.av.applet.getCodeBase(), "", this);
147      * 
148      * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
149      */
150   }
151
152   public JalviewJmolBinding(JmolViewer viewer2)
153   {
154     viewer = viewer2;
155     viewer.setJmolStatusListener(this);
156     viewer.addSelectionListener(this);
157   }
158
159   /**
160    * construct a title string for the viewer window based on the data jalview
161    * knows about
162    * 
163    * @return
164    */
165   public String getViewerTitle()
166   {
167     if (sequence == null || pdbentry == null || sequence.length < 1
168             || pdbentry.length < 1 || sequence[0].length < 1)
169     {
170       return ("Jalview Jmol Window");
171     }
172     // TODO: give a more informative title when multiple structures are
173     // displayed.
174     StringBuffer title = new StringBuffer(sequence[0][0].getName() + ":"
175             + pdbentry[0].getId());
176
177     if (pdbentry[0].getProperty() != null)
178     {
179       if (pdbentry[0].getProperty().get("method") != null)
180       {
181         title.append(" Method: ");
182         title.append(pdbentry[0].getProperty().get("method"));
183       }
184       if (pdbentry[0].getProperty().get("chains") != null)
185       {
186         title.append(" Chain:");
187         title.append(pdbentry[0].getProperty().get("chains"));
188       }
189     }
190     return title.toString();
191   }
192
193   /**
194    * prepare the view for a given set of models/chains. chainList contains
195    * strings of the form 'pdbfilename:Chaincode'
196    * 
197    * @param chainList
198    *          list of chains to make visible
199    */
200   public void centerViewer(Vector chainList)
201   {
202     StringBuffer cmd = new StringBuffer();
203     String lbl;
204     int mlength, p;
205     for (int i = 0, iSize = chainList.size(); i < iSize; i++)
206     {
207       mlength = 0;
208       lbl = (String) chainList.elementAt(i);
209       do
210       {
211         p = mlength;
212         mlength = lbl.indexOf(":", p);
213       } while (p < mlength && mlength < (lbl.length() - 2));
214       // TODO: lookup each pdb id and recover proper model number for it.
215       cmd.append(":" +  lbl.substring(mlength + 1) + " /"
216               + (1+getModelNum((String)chainFile.get(lbl))) + " or ");
217     }
218     if (cmd.length() > 0)
219       cmd.setLength(cmd.length() - 4);
220     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
221   }
222
223   public void closeViewer()
224   {
225     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
226     // remove listeners for all structures in viewer
227     StructureSelectionManager.getStructureSelectionManager()
228             .removeStructureViewerListener(this, this.getPdbFile());
229     // and shut down jmol
230     viewer.evalStringQuiet("zap");
231     viewer.setJmolStatusListener(null);
232     lastCommand = null;
233     viewer = null;
234   }
235
236   public void colourByChain()
237   {
238     colourBySequence = false;
239     // TODO: colour by chain should colour each chain distinctly across all
240     // visible models
241     // TODO: http://issues.jalview.org/browse/JAL-628
242     evalStateCommand("select *;color chain");
243   }
244
245   public void colourByCharge()
246   {
247     colourBySequence = false;
248     evalStateCommand("select *;color white;select ASP,GLU;color red;"
249             + "select LYS,ARG;color blue;select CYS;color yellow");
250   }
251
252   /**
253    * superpose the structures associated with sequences in the alignment
254    * according to their corresponding positions.
255    */
256   public void superposeStructures(AlignmentI alignment)
257   {
258     superposeStructures(alignment, -1, null);
259   }
260
261   /**
262    * superpose the structures associated with sequences in the alignment
263    * according to their corresponding positions. ded)
264    * 
265    * @param refStructure
266    *          - select which pdb file to use as reference (default is -1 - the
267    *          first structure in the alignment)
268    */
269   public void superposeStructures(AlignmentI alignment, int refStructure)
270   {
271     superposeStructures(alignment, refStructure, null);
272   }
273
274   /**
275    * superpose the structures associated with sequences in the alignment
276    * according to their corresponding positions. ded)
277    * 
278    * @param refStructure
279    *          - select which pdb file to use as reference (default is -1 - the
280    *          first structure in the alignment)
281    * @param hiddenCols
282    *          TODO
283    */
284   public void superposeStructures(AlignmentI alignment, int refStructure,
285           ColumnSelection hiddenCols)
286   {
287     String[] files = getPdbFile();
288     if (refStructure >= files.length)
289     {
290       System.err.println("Invalid reference structure value "
291               + refStructure);
292       refStructure = -1;
293     }
294     if (refStructure < -1)
295     {
296       refStructure = -1;
297     }
298     StringBuffer command = new StringBuffer(), selectioncom = new StringBuffer();
299
300     boolean matched[] = new boolean[alignment.getWidth()];
301     for (int m = 0; m < matched.length; m++)
302     {
303
304       matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
305     }
306
307     int commonrpositions[][] = new int[files.length][alignment.getWidth()];
308     String isel[] = new String[files.length];
309     // reference structure - all others are superposed in it
310     String[] targetC = new String[files.length];
311     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
312     {
313       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
314
315       if (mapping == null || mapping.length < 1)
316         continue;
317
318       int lastPos = -1;
319       for (int s = 0; s < sequence[pdbfnum].length; s++)
320       {
321         for (int sp, m = 0; m < mapping.length; m++)
322         {
323           if (mapping[m].getSequence() == sequence[pdbfnum][s]
324                   && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
325           {
326             if (refStructure == -1)
327             {
328               refStructure = pdbfnum;
329             }
330             SequenceI asp = alignment.getSequenceAt(sp);
331             for (int r = 0; r < matched.length; r++)
332             {
333               if (!matched[r])
334               {
335                 continue;
336               }
337               matched[r] = false; // assume this is not a good site
338               if (r >= asp.getLength())
339               {
340                 continue;
341               }
342
343               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
344               {
345                 // no mapping to gaps in sequence
346                 continue;
347               }
348               int t = asp.findPosition(r); // sequence position
349               int apos = mapping[m].getAtomNum(t);
350               int pos = mapping[m].getPDBResNum(t);
351
352               if (pos < 1 || pos == lastPos)
353               {
354                 // can't align unmapped sequence
355                 continue;
356               }
357               matched[r] = true; // this is a good ite
358               lastPos = pos;
359               // just record this residue position
360               commonrpositions[pdbfnum][r] = pos;
361             }
362             // create model selection suffix
363             isel[pdbfnum] = "/" + (pdbfnum + 1) + ".1";
364             if (mapping[m].getChain() == null
365                     || mapping[m].getChain().trim().length() == 0)
366             {
367               targetC[pdbfnum] = "";
368             }
369             else
370             {
371               targetC[pdbfnum] = ":" + mapping[m].getChain();
372             }
373             // move on to next pdb file
374             s = sequence[pdbfnum].length;
375             break;
376           }
377         }
378       }
379     }
380     String[] selcom = new String[files.length];
381     int nmatched = 0;
382     // generate select statements to select regions to superimpose structures
383     {
384       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
385       {
386         String chainCd = targetC[pdbfnum];
387         int lpos = -1;
388         boolean run = false;
389         StringBuffer molsel = new StringBuffer();
390         molsel.append("{");
391         for (int r = 0; r < matched.length; r++)
392         {
393           if (matched[r])
394           {
395             if (pdbfnum == 0)
396             {
397               nmatched++;
398             }
399             if (lpos != commonrpositions[pdbfnum][r] - 1)
400             {
401               // discontinuity
402               if (lpos != -1)
403               {
404                 molsel.append(lpos);
405                 molsel.append(chainCd);
406                 // molsel.append("} {");
407                 molsel.append("|");
408               }
409             }
410             else
411             {
412               // continuous run - and lpos >-1
413               if (!run)
414               {
415                 // at the beginning, so add dash
416                 molsel.append(lpos);
417                 molsel.append("-");
418               }
419               run = true;
420             }
421             lpos = commonrpositions[pdbfnum][r];
422             // molsel.append(lpos);
423           }
424         }
425         // add final selection phrase
426         if (lpos != -1)
427         {
428           molsel.append(lpos);
429           molsel.append(chainCd);
430           molsel.append("}");
431         }
432         selcom[pdbfnum] = molsel.toString();
433         selectioncom.append("((");
434         selectioncom.append(selcom[pdbfnum].substring(1,
435                 selcom[pdbfnum].length() - 1));
436         selectioncom.append(" )& ");
437         selectioncom.append(pdbfnum + 1);
438         selectioncom.append(".1)");
439         if (pdbfnum < files.length - 1)
440         {
441           selectioncom.append("|");
442         }
443       }
444     }
445     // TODO: consider bailing if nmatched less than 4 because superposition not
446     // well defined.
447     // TODO: refactor superposable position search (above) from jmol selection
448     // construction (below)
449     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
450     {
451       if (pdbfnum == refStructure)
452       {
453         continue;
454       }
455       command.append("compare ");
456       command.append("{");
457       command.append(1 + pdbfnum);
458       command.append(".1} {");
459       command.append(1 + refStructure);
460       command.append(".1} SUBSET {*.CA | *.P} ATOMS ");
461
462       // form the matched pair strings
463       String sep = "";
464       for (int s = 0; s < 2; s++)
465       {
466         command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
467       }
468       command.append(" ROTATE TRANSLATE;\n");
469     }
470     System.out.println("Select regions:\n" + selectioncom.toString());
471     evalStateCommand("select *; cartoons off; backbone; select ("
472             + selectioncom.toString() + "); cartoons; ");
473     // selcom.append("; ribbons; ");
474     System.out.println("Superimpose command(s):\n" + command.toString());
475
476     evalStateCommand(command.toString());
477
478     // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
479   }
480
481   public void evalStateCommand(String command)
482   {
483     jmolHistory(false);
484     if (lastCommand == null || !lastCommand.equals(command))
485     {
486       viewer.evalStringQuiet(command + "\n");
487     }
488     jmolHistory(true);
489     lastCommand = command;
490   }
491
492   /**
493    * colour any structures associated with sequences in the given alignment
494    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
495    * if colourBySequence is enabled.
496    */
497   public void colourBySequence(boolean showFeatures, AlignmentI alignment)
498   {
499     if (!colourBySequence)
500       return;
501     if (ssm == null)
502     {
503       return;
504     }
505     String[] files = getPdbFile();
506     SequenceRenderer sr = getSequenceRenderer();
507
508     FeatureRenderer fr = null;
509     if (showFeatures)
510     {
511       fr = getFeatureRenderer();
512     }
513
514     StringBuffer command = new StringBuffer();
515
516     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
517     {
518       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
519
520       if (mapping == null || mapping.length < 1)
521         continue;
522
523       int lastPos = -1;
524       for (int s = 0; s < sequence[pdbfnum].length; s++)
525       {
526         for (int sp, m = 0; m < mapping.length; m++)
527         {
528           if (mapping[m].getSequence() == sequence[pdbfnum][s]
529                   && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
530           {
531             SequenceI asp = alignment.getSequenceAt(sp);
532             for (int r = 0; r < asp.getLength(); r++)
533             {
534               // no mapping to gaps in sequence
535               if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
536               {
537                 continue;
538               }
539               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
540
541               if (pos < 1 || pos == lastPos)
542                 continue;
543
544               lastPos = pos;
545
546               Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
547
548               if (showFeatures && fr!=null)
549                 col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
550               String newSelcom = (mapping[m].getChain() != " " ? ":"
551                       + mapping[m].getChain() : "")
552                       + "/"
553                       + (pdbfnum + 1)
554                       + ".1"
555                       + ";color["
556                       + col.getRed()
557                       + ","
558                       + col.getGreen()
559                       + ","
560                       + col.getBlue() + "]";
561               if (command.toString().endsWith(newSelcom))
562               {
563                 command = condenseCommand(command.toString(), pos);
564                 continue;
565               }
566               // TODO: deal with case when buffer is too large for Jmol to parse
567               // - execute command and flush
568
569               command.append(";select " + pos);
570               command.append(newSelcom);
571             }
572             break;
573           }
574         }
575       }
576     }
577     evalStateCommand(command.toString());
578   }
579
580   public boolean isColourBySequence()
581   {
582     return colourBySequence;
583   }
584
585   public void setColourBySequence(boolean colourBySequence)
586   {
587     this.colourBySequence = colourBySequence;
588   }
589
590   StringBuffer condenseCommand(String command, int pos)
591   {
592
593     StringBuffer sb = new StringBuffer(command.substring(0,
594             command.lastIndexOf("select") + 7));
595
596     command = command.substring(sb.length());
597
598     String start;
599
600     if (command.indexOf("-") > -1)
601     {
602       start = command.substring(0, command.indexOf("-"));
603     }
604     else
605     {
606       start = command.substring(0, command.indexOf(":"));
607     }
608
609     sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
610
611     return sb;
612   }
613
614   public void createImage(String file, String type, int quality)
615   {
616     System.out.println("JMOL CREATE IMAGE");
617   }
618
619   public String createImage(String fileName, String type,
620           Object textOrBytes, int quality)
621   {
622     System.out.println("JMOL CREATE IMAGE");
623     return null;
624   }
625
626   public String eval(String strEval)
627   {
628     // System.out.println(strEval);
629     // "# 'eval' is implemented only for the applet.";
630     return null;
631   }
632
633   // End StructureListener
634   // //////////////////////////
635
636   public float[][] functionXY(String functionName, int x, int y)
637   {
638     return null;
639   }
640
641   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
642   {
643     // TODO Auto-generated method stub
644     return null;
645   }
646
647   public Color getColour(int atomIndex, int pdbResNum, String chain,
648           String pdbfile)
649   {
650     if (getModelNum(pdbfile) < 0)
651       return null;
652     // TODO: verify atomIndex is selecting correct model.
653     return new Color(viewer.getAtomArgb(atomIndex));
654   }
655
656   /**
657    * returns the current featureRenderer that should be used to colour the
658    * structures
659    * 
660    * @return
661    */
662   public abstract FeatureRenderer getFeatureRenderer();
663
664   /**
665    * instruct the Jalview binding to update the pdbentries vector if necessary
666    * prior to matching the jmol view's contents to the list of structure files
667    * Jalview knows about.
668    */
669   public abstract void refreshPdbEntries();
670
671   private int getModelNum(String modelFileName)
672   {
673     String[] mfn = getPdbFile();
674     if (mfn == null)
675     {
676       return -1;
677     }
678     for (int i = 0; i < mfn.length; i++)
679     {
680       if (mfn[i].equalsIgnoreCase(modelFileName))
681         return i;
682     }
683     return -1;
684   }
685
686   /**
687    * map between index of model filename returned from getPdbFile and the first
688    * index of models from this file in the viewer. Note - this is not trimmed -
689    * use getPdbFile to get number of unique models.
690    */
691   private int _modelFileNameMap[];
692
693   // ////////////////////////////////
694   // /StructureListener
695   public synchronized String[] getPdbFile()
696   {
697     if (viewer == null)
698     {
699       return new String[0];
700     }
701     if (modelFileNames == null)
702     {
703
704       String mset[] = new String[viewer.getModelCount()];
705       _modelFileNameMap = new int[mset.length];
706       int j = 1;
707       mset[0] = viewer.getModelFileName(0);
708       for (int i = 1; i < mset.length; i++)
709       {
710         mset[j] = viewer.getModelFileName(i);
711         _modelFileNameMap[j] = i; // record the model index for the filename
712         // skip any additional models in the same file (NMR structures)
713         if ((mset[j] == null ? mset[j] != mset[j - 1]
714                 : (mset[j - 1] == null || !mset[j].equals(mset[j - 1]))))
715         {
716           j++;
717         }
718       }
719       modelFileNames = new String[j];
720       System.arraycopy(mset, 0, modelFileNames, 0, j);
721     }
722     return modelFileNames;
723   }
724
725   /**
726    * map from string to applet
727    */
728   public Map getRegistryInfo()
729   {
730     // TODO Auto-generated method stub
731     return null;
732   }
733
734   /**
735    * returns the current sequenceRenderer that should be used to colour the
736    * structures
737    * 
738    * @return
739    */
740   public abstract SequenceRenderer getSequenceRenderer();
741
742   // ///////////////////////////////
743   // JmolStatusListener
744
745   public void handlePopupMenu(int x, int y)
746   {
747     jmolpopup.show(x, y);
748   }
749
750   // jmol/ssm only
751   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
752           String pdbfile)
753   {
754     if (modelFileNames == null)
755     {
756       return;
757     }
758
759     // look up file model number for this pdbfile
760     int mdlNum = 0;
761     String fn;
762     // may need to adjust for URLencoding here - we don't worry about that yet.
763     while (mdlNum < modelFileNames.length
764             && !pdbfile.equals(modelFileNames[mdlNum]))
765     {
766       // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
767       mdlNum++;
768     }
769     if (mdlNum == modelFileNames.length)
770     {
771       return;
772     }
773
774     jmolHistory(false);
775     // if (!pdbfile.equals(pdbentry.getFile()))
776     // return;
777     if (resetLastRes.length() > 0)
778     {
779       viewer.evalStringQuiet(resetLastRes.toString());
780     }
781
782     eval.setLength(0);
783     eval.append("select " + pdbResNum); // +modelNum
784
785     resetLastRes.setLength(0);
786     resetLastRes.append("select " + pdbResNum); // +modelNum
787
788     eval.append(":");
789     resetLastRes.append(":");
790     if (!chain.equals(" "))
791     {
792       eval.append(chain);
793       resetLastRes.append(chain);
794     }
795     {
796       eval.append(" /" + (mdlNum + 1));
797       resetLastRes.append("/" + (mdlNum + 1));
798     }
799     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
800
801     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
802             + " and not hetero; spacefill 0;");
803
804     eval.append("spacefill 200;select none");
805
806     viewer.evalStringQuiet(eval.toString());
807     jmolHistory(true);
808
809   }
810
811   boolean debug = true;
812
813   private void jmolHistory(boolean enable)
814   {
815     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
816   }
817
818   public void loadInline(String string)
819   {
820     loadedInline = true;
821     // TODO: re JAL-623
822     // viewer.loadInline(strModel, isAppend);
823     // could do this:
824     // construct fake fullPathName and fileName so we can identify the file
825     // later.
826     // Then, construct pass a reader for the string to Jmol.
827     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
828     // fileName, null, reader, false, null, null, 0);
829     viewer.openStringInline(string);
830   }
831
832   public void mouseOverStructure(int atomIndex, String strInfo)
833   {
834     int pdbResNum;
835     int mdlSep = strInfo.indexOf("/");
836     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
837
838     if (chainSeparator == -1)
839     {
840       chainSeparator = strInfo.indexOf(".");
841       if (mdlSep > -1 && mdlSep < chainSeparator)
842       {
843         chainSeparator1 = chainSeparator;
844         chainSeparator = mdlSep;
845       }
846     }
847     pdbResNum = Integer.parseInt(strInfo.substring(
848             strInfo.indexOf("]") + 1, chainSeparator));
849
850     String chainId;
851
852     if (strInfo.indexOf(":") > -1)
853       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
854               strInfo.indexOf("."));
855     else
856     {
857       chainId = " ";
858     }
859
860     String pdbfilename = modelFileNames[frameNo]; // default is first or current
861     // model
862     if (mdlSep > -1)
863     {
864       if (chainSeparator1 == -1)
865       {
866         chainSeparator1 = strInfo.indexOf(".", mdlSep);
867       }
868       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
869               chainSeparator1) : strInfo.substring(mdlSep + 1);
870       try
871       {
872         // recover PDB filename for the model hovered over.
873         pdbfilename = viewer
874                 .getModelFileName(new Integer(mdlId).intValue() - 1);
875       } catch (Exception e)
876       {
877       }
878       ;
879     }
880     if (lastMessage == null || !lastMessage.equals(strInfo))
881       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
882
883     lastMessage = strInfo;
884   }
885
886   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
887   {
888     if (data != null)
889     {
890       System.err.println("Ignoring additional hover info: " + data
891               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
892     }
893     mouseOverStructure(atomIndex, strInfo);
894   }
895
896   /*
897    * { if (history != null && strStatus != null &&
898    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
899    * } }
900    */
901
902   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
903   {
904     /**
905      * this implements the toggle label behaviour copied from the original
906      * structure viewer, MCView
907      */
908     if (strData != null)
909     {
910       System.err.println("Ignoring additional pick data string " + strData);
911     }
912     int chainSeparator = strInfo.indexOf(":");
913     int p = 0;
914     if (chainSeparator == -1)
915       chainSeparator = strInfo.indexOf(".");
916
917     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
918             chainSeparator);
919     String mdlString = "";
920     if ((p = strInfo.indexOf(":")) > -1)
921       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
922
923     if ((p = strInfo.indexOf("/")) > -1)
924     {
925       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
926     }
927     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
928             + mdlString + "))";
929     jmolHistory(false);
930
931     if (!atomsPicked.contains(picked))
932     {
933       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
934       atomsPicked.addElement(picked);
935     }
936     else
937     {
938       viewer.evalString("select " + picked + ";label off");
939       atomsPicked.removeElement(picked);
940     }
941     jmolHistory(true);
942     // TODO: in application this happens
943     //
944     // if (scriptWindow != null)
945     // {
946     // scriptWindow.sendConsoleMessage(strInfo);
947     // scriptWindow.sendConsoleMessage("\n");
948     // }
949
950   }
951
952   public void notifyCallback(int type, Object[] data)
953   {
954     try
955     {
956       switch (type)
957       {
958       case JmolConstants.CALLBACK_LOADSTRUCT:
959         notifyFileLoaded((String) data[1], (String) data[2],
960                 (String) data[3], (String) data[4],
961                 ((Integer) data[5]).intValue());
962
963         break;
964       case JmolConstants.CALLBACK_PICK:
965         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
966                 (String) data[0]);
967         // also highlight in alignment
968       case JmolConstants.CALLBACK_HOVER:
969         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
970                 (String) data[0]);
971         break;
972       case JmolConstants.CALLBACK_SCRIPT:
973         notifyScriptTermination((String) data[2],
974                 ((Integer) data[3]).intValue());
975         break;
976       case JmolConstants.CALLBACK_ECHO:
977         sendConsoleEcho((String) data[1]);
978         break;
979       case JmolConstants.CALLBACK_MESSAGE:
980         sendConsoleMessage((data == null) ? ((String) null)
981                 : (String) data[1]);
982         break;
983       case JmolConstants.CALLBACK_ERROR:
984         // System.err.println("Ignoring error callback.");
985         break;
986       case JmolConstants.CALLBACK_SYNC:
987       case JmolConstants.CALLBACK_RESIZE:
988         refreshGUI();
989         break;
990       case JmolConstants.CALLBACK_MEASURE:
991
992       case JmolConstants.CALLBACK_CLICK:
993       default:
994         System.err.println("Unhandled callback " + type + " "
995                 + data[1].toString());
996         break;
997       }
998     } catch (Exception e)
999     {
1000       System.err.println("Squashed Jmol callback handler error:");
1001       e.printStackTrace();
1002     }
1003   }
1004
1005   public boolean notifyEnabled(int callbackPick)
1006   {
1007     switch (callbackPick)
1008     {
1009     case JmolConstants.CALLBACK_ECHO:
1010     case JmolConstants.CALLBACK_LOADSTRUCT:
1011     case JmolConstants.CALLBACK_MEASURE:
1012     case JmolConstants.CALLBACK_MESSAGE:
1013     case JmolConstants.CALLBACK_PICK:
1014     case JmolConstants.CALLBACK_SCRIPT:
1015     case JmolConstants.CALLBACK_HOVER:
1016     case JmolConstants.CALLBACK_ERROR:
1017       return true;
1018     case JmolConstants.CALLBACK_RESIZE:
1019     case JmolConstants.CALLBACK_SYNC:
1020     case JmolConstants.CALLBACK_CLICK:
1021     case JmolConstants.CALLBACK_ANIMFRAME:
1022     case JmolConstants.CALLBACK_MINIMIZATION:
1023     }
1024     return false;
1025   }
1026
1027   // incremented every time a load notification is successfully handled -
1028   // lightweight mechanism for other threads to detect when they can start
1029   // referrring to new structures.
1030   private long loadNotifiesHandled = 0;
1031
1032   public long getLoadNotifiesHandled()
1033   {
1034     return loadNotifiesHandled;
1035   }
1036
1037   public void notifyFileLoaded(String fullPathName, String fileName2,
1038           String modelName, String errorMsg, int modelParts)
1039   {
1040     if (errorMsg != null)
1041     {
1042       fileLoadingError = errorMsg;
1043       refreshGUI();
1044       return;
1045     }
1046     // TODO: deal sensibly with models loaded inLine:
1047     // modelName will be null, as will fullPathName.
1048
1049     // the rest of this routine ignores the arguments, and simply interrogates
1050     // the Jmol view to find out what structures it contains, and adds them to
1051     // the structure selection manager.
1052     fileLoadingError = null;
1053     String[] oldmodels = modelFileNames;
1054     modelFileNames = null;
1055     chainNames = new Vector();
1056     chainFile = new Hashtable();
1057     boolean notifyLoaded = false;
1058     String[] modelfilenames = getPdbFile();
1059     ssm = StructureSelectionManager.getStructureSelectionManager();
1060     // first check if we've lost any structures
1061     if (oldmodels != null && oldmodels.length > 0)
1062     {
1063       int oldm = 0;
1064       for (int i = 0; i < oldmodels.length; i++)
1065       {
1066         for (int n = 0; n < modelfilenames.length; n++)
1067         {
1068           if (modelfilenames[n] == oldmodels[i])
1069           {
1070             oldmodels[i] = null;
1071             break;
1072           }
1073         }
1074         if (oldmodels[i] != null)
1075         {
1076           oldm++;
1077         }
1078       }
1079       if (oldm > 0)
1080       {
1081         String[] oldmfn = new String[oldm];
1082         oldm = 0;
1083         for (int i = 0; i < oldmodels.length; i++)
1084         {
1085           if (oldmodels[i] != null)
1086           {
1087             oldmfn[oldm++] = oldmodels[i];
1088           }
1089         }
1090         // deregister the Jmol instance for these structures - we'll add
1091         // ourselves again at the end for the current structure set.
1092         ssm.removeStructureViewerListener(this, oldmfn);
1093       }
1094     }
1095     refreshPdbEntries();
1096     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
1097     {
1098       String fileName = modelfilenames[modelnum];
1099       boolean foundEntry = false;
1100       MCview.PDBfile pdb = null;
1101       String pdbfile = null, pdbfhash = null;
1102       // model was probably loaded inline - so check the pdb file hashcode
1103       if (loadedInline)
1104       {
1105         // calculate essential attributes for the pdb data imported inline.
1106         // prolly need to resolve modelnumber properly - for now just use our
1107         // 'best guess'
1108         pdbfile = viewer.getData("" + (1 + _modelFileNameMap[modelnum])
1109                 + ".0", "PDB");
1110         pdbfhash = "" + pdbfile.hashCode();
1111       }
1112       if (pdbentry != null)
1113       {
1114         // search pdbentries and sequences to find correct pdbentry for this
1115         // model
1116         for (int pe = 0; pe < pdbentry.length; pe++)
1117         {
1118           boolean matches = false;
1119           if (fileName == null)
1120           {
1121             if (false)
1122             // see JAL-623 - need method of matching pasted data up
1123             {
1124               pdb = ssm.setMapping(sequence[pe], chains[pe], pdbfile,
1125                       AppletFormatAdapter.PASTE);
1126               pdbentry[modelnum].setFile("INLINE" + pdb.id);
1127               matches = true;
1128               foundEntry = true;
1129             }
1130           }
1131           else
1132           {
1133             if (matches = pdbentry[pe].getFile().equals(fileName))
1134             {
1135               foundEntry = true;
1136               // TODO: Jmol can in principle retrieve from CLASSLOADER but
1137               // this
1138               // needs
1139               // to be tested. See mantis bug
1140               // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1141               String protocol = AppletFormatAdapter.URL;
1142               try
1143               {
1144                 File fl = new java.io.File(pdbentry[pe].getFile());
1145                 if (fl.exists())
1146                 {
1147                   protocol = AppletFormatAdapter.FILE;
1148                 }
1149               } catch (Exception e)
1150               {
1151               } catch (Error e)
1152               {
1153               }
1154               ;
1155               pdb = ssm.setMapping(sequence[pe], chains[pe],
1156                       pdbentry[pe].getFile(), protocol);
1157
1158             }
1159           }
1160           if (matches)
1161           {
1162             pdbentry[pe].setId(pdb.id);
1163             // add an entry for every chain in the model
1164             for (int i = 0; i < pdb.chains.size(); i++)
1165             {
1166               String chid = new String(pdb.id + ":"
1167                       + ((MCview.PDBChain) pdb.chains.elementAt(i)).id);
1168               chainFile.put(chid, pdbentry[pe].getFile());
1169               chainNames.addElement(chid);
1170             }
1171             notifyLoaded = true;
1172           }
1173         }
1174       }
1175       if (!foundEntry && associateNewStructs)
1176       {
1177         // this is a foreign pdb file that jalview doesn't know about - add
1178         // it to the dataset and try to find a home - either on a matching
1179         // sequence or as a new sequence.
1180         String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
1181                 "PDB");
1182         // parse pdb file into a chain, etc.
1183         // locate best match for pdb in associated views and add mapping to
1184         // ssm
1185         // if properly registered then
1186         notifyLoaded = true;
1187
1188       }
1189     }
1190     // FILE LOADED OK
1191     // so finally, update the jmol bits and pieces
1192     if (jmolpopup != null)
1193     {
1194       // potential for deadlock here:
1195       // jmolpopup.updateComputedMenus();
1196     }
1197     if (!isLoadingFromArchive())
1198     {
1199       viewer.evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
1200     }
1201     setLoadingFromArchive(false);
1202     // register ourselves as a listener and notify the gui that it needs to
1203     // update itself.
1204     ssm.addStructureViewerListener(this);
1205     if (notifyLoaded)
1206     {
1207       FeatureRenderer fr = getFeatureRenderer();
1208       if (fr != null)
1209       {
1210         fr.featuresAdded();
1211       }
1212       refreshGUI();
1213       loadNotifiesHandled++;
1214     }
1215   }
1216
1217   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
1218   {
1219     notifyAtomPicked(iatom, strMeasure, null);
1220   }
1221
1222   public abstract void notifyScriptTermination(String strStatus,
1223           int msWalltime);
1224
1225   /**
1226    * display a message echoed from the jmol viewer
1227    * 
1228    * @param strEcho
1229    */
1230   public abstract void sendConsoleEcho(String strEcho); /*
1231                                                          * { showConsole(true);
1232                                                          * 
1233                                                          * history.append("\n" +
1234                                                          * strEcho); }
1235                                                          */
1236
1237   // /End JmolStatusListener
1238   // /////////////////////////////
1239
1240   /**
1241    * @param strStatus
1242    *          status message - usually the response received after a script
1243    *          executed
1244    */
1245   public abstract void sendConsoleMessage(String strStatus);
1246
1247   public void setCallbackFunction(String callbackType,
1248           String callbackFunction)
1249   {
1250     System.err.println("Ignoring set-callback request to associate "
1251             + callbackType + " with function " + callbackFunction);
1252
1253   }
1254
1255   public void setJalviewColourScheme(ColourSchemeI cs)
1256   {
1257     colourBySequence = false;
1258
1259     if (cs == null)
1260       return;
1261
1262     String res;
1263     int index;
1264     Color col;
1265     jmolHistory(false);
1266     // TODO: Switch between nucleotide or aa selection expressions
1267     Enumeration en = ResidueProperties.aa3Hash.keys();
1268     StringBuffer command = new StringBuffer("select *;color white;");
1269     while (en.hasMoreElements())
1270     {
1271       res = en.nextElement().toString();
1272       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
1273       if (index > 20)
1274         continue;
1275
1276       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
1277
1278       command.append("select " + res + ";color[" + col.getRed() + ","
1279               + col.getGreen() + "," + col.getBlue() + "];");
1280     }
1281
1282     evalStateCommand(command.toString());
1283     jmolHistory(true);
1284   }
1285
1286   public void showHelp()
1287   {
1288     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1289   }
1290
1291   /**
1292    * open the URL somehow
1293    * 
1294    * @param target
1295    */
1296   public abstract void showUrl(String url, String target);
1297
1298   /**
1299    * called when the binding thinks the UI needs to be refreshed after a Jmol
1300    * state change. this could be because structures were loaded, or because an
1301    * error has occured.
1302    */
1303   public abstract void refreshGUI();
1304
1305   /**
1306    * called to show or hide the associated console window container.
1307    * 
1308    * @param show
1309    */
1310   public abstract void showConsole(boolean show);
1311
1312   /**
1313    * @param renderPanel
1314    * @param jmolfileio
1315    *          - when true will initialise jmol's file IO system (should be false
1316    *          in applet context)
1317    * @param htmlName
1318    * @param documentBase
1319    * @param codeBase
1320    * @param commandOptions
1321    */
1322   public void allocateViewer(Component renderPanel, boolean jmolfileio,
1323           String htmlName, URL documentBase, URL codeBase,
1324           String commandOptions)
1325   {
1326     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
1327             codeBase, commandOptions, null, null);
1328   }
1329
1330   /**
1331    * 
1332    * @param renderPanel
1333    * @param jmolfileio
1334    *          - when true will initialise jmol's file IO system (should be false
1335    *          in applet context)
1336    * @param htmlName
1337    * @param documentBase
1338    * @param codeBase
1339    * @param commandOptions
1340    * @param consolePanel
1341    *          - panel to contain Jmol console
1342    * @param buttonsToShow
1343    *          - buttons to show on the console, in ordr
1344    */
1345   public void allocateViewer(Component renderPanel, boolean jmolfileio,
1346           String htmlName, URL documentBase, URL codeBase,
1347           String commandOptions, final Container consolePanel,
1348           String buttonsToShow)
1349   {
1350     viewer = JmolViewer.allocateViewer(renderPanel,
1351             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
1352                     + ((Object) this).toString(), documentBase, codeBase,
1353             commandOptions, this);
1354
1355     console = createJmolConsole(viewer, consolePanel, buttonsToShow);
1356     if (consolePanel != null)
1357     {
1358       consolePanel.addComponentListener(this);
1359       
1360     }
1361
1362
1363   }
1364
1365   protected abstract JmolAppConsoleInterface createJmolConsole(
1366           JmolViewer viewer2, Container consolePanel, String buttonsToShow);
1367
1368   protected org.jmol.api.JmolAppConsoleInterface console = null;
1369
1370 @Override
1371 public void componentResized(ComponentEvent e)
1372 {
1373   
1374 }
1375
1376 @Override
1377 public void componentMoved(ComponentEvent e)
1378 {
1379   
1380 }
1381
1382 @Override
1383 public void componentShown(ComponentEvent e)
1384 {
1385   showConsole(true);
1386 }
1387
1388 @Override
1389 public void componentHidden(ComponentEvent e)
1390 {
1391   showConsole(false);
1392 }
1393
1394
1395   public void setLoadingFromArchive(boolean loadingFromArchive)
1396   {
1397     this.loadingFromArchive = loadingFromArchive;
1398   }
1399
1400   public boolean isLoadingFromArchive()
1401   {
1402     return loadingFromArchive;
1403   }
1404
1405   public void setBackgroundColour(java.awt.Color col)
1406   {
1407     jmolHistory(false);
1408     viewer.evalStringQuiet("background [" + col.getRed() + ","
1409             + col.getGreen() + "," + col.getBlue() + "];");
1410     jmolHistory(true);
1411   }
1412
1413   /**
1414    * add structures and any known sequence associations
1415    * 
1416    * @returns the pdb entries added to the current set.
1417    */
1418   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
1419           SequenceI[][] seq, String[][] chns)
1420   {
1421     int pe = -1;
1422     Vector v = new Vector();
1423     Vector rtn = new Vector();
1424     for (int i = 0; i < pdbentry.length; i++)
1425     {
1426       v.addElement(pdbentry[i]);
1427     }
1428     for (int i = 0; i < pdbe.length; i++)
1429     {
1430       int r = v.indexOf(pdbe[i]);
1431       if (r == -1 || r >= pdbentry.length)
1432       {
1433         rtn.addElement(new int[]
1434         { v.size(), i });
1435         v.addElement(pdbe[i]);
1436       }
1437       else
1438       {
1439         // just make sure the sequence/chain entries are all up to date
1440         addSequenceAndChain(r, seq[i], chns[i]);
1441       }
1442     }
1443     pdbe = new PDBEntry[v.size()];
1444     v.copyInto(pdbe);
1445     pdbentry = pdbe;
1446     if (rtn.size() > 0)
1447     {
1448       // expand the tied seuqence[] and string[] arrays
1449       SequenceI[][] sqs = new SequenceI[pdbentry.length][];
1450       String[][] sch = new String[pdbentry.length][];
1451       System.arraycopy(sequence, 0, sqs, 0, sequence.length);
1452       System.arraycopy(chains, 0, sch, 0, this.chains.length);
1453       sequence = sqs;
1454       chains = sch;
1455       pdbe = new PDBEntry[rtn.size()];
1456       for (int r = 0; r < pdbe.length; r++)
1457       {
1458         int[] stri = ((int[]) rtn.elementAt(r));
1459         // record the pdb file as a new addition
1460         pdbe[r] = pdbentry[stri[0]];
1461         // and add the new sequence/chain entries
1462         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
1463       }
1464     }
1465     else
1466     {
1467       pdbe = null;
1468     }
1469     return pdbe;
1470   }
1471
1472   public void addSequence(int pe, SequenceI[] seq)
1473   {
1474     // add sequences to the pe'th pdbentry's seuqence set.
1475     addSequenceAndChain(pe, seq, null);
1476   }
1477
1478   private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
1479   {
1480     if (pe < 0 || pe >= pdbentry.length)
1481     {
1482       throw new Error(
1483               "Implementation error - no corresponding pdbentry (for index "
1484                       + pe + ") to add sequences mappings to");
1485     }
1486     final String nullChain = "TheNullChain";
1487     Vector s = new Vector();
1488     Vector c = new Vector();
1489     if (chains == null)
1490     {
1491       chains = new String[pdbentry.length][];
1492     }
1493     if (sequence[pe] != null)
1494     {
1495       for (int i = 0; i < sequence[pe].length; i++)
1496       {
1497         s.addElement(sequence[pe][i]);
1498         if (chains[pe] != null)
1499         {
1500           if (i < chains[pe].length)
1501           {
1502             c.addElement(chains[pe][i]);
1503           }
1504           else
1505           {
1506             c.addElement(nullChain);
1507           }
1508         }
1509         else
1510         {
1511           if (tchain != null && tchain.length > 0)
1512           {
1513             c.addElement(nullChain);
1514           }
1515         }
1516       }
1517     }
1518     for (int i = 0; i < seq.length; i++)
1519     {
1520       if (!s.contains(seq[i]))
1521       {
1522         s.addElement(seq[i]);
1523         if (tchain != null && i < tchain.length)
1524         {
1525           c.addElement(tchain[i] == null ? nullChain : tchain[i]);
1526         }
1527       }
1528     }
1529     SequenceI[] tmp = new SequenceI[s.size()];
1530     s.copyInto(tmp);
1531     sequence[pe] = tmp;
1532     if (c.size() > 0)
1533     {
1534       String[] tch = new String[c.size()];
1535       c.copyInto(tch);
1536       for (int i = 0; i < tch.length; i++)
1537       {
1538         if (tch[i] == nullChain)
1539         {
1540           tch[i] = null;
1541         }
1542       }
1543       chains[pe] = tch;
1544     }
1545     else
1546     {
1547       chains[pe] = null;
1548     }
1549   }
1550 }