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