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