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