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