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