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