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