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