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