458a17820bcc9157f265a002686ee472156cc651
[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         {
407           throw new Error("Implementation error - Jmol seems to be still working on getting its data - report at http://issues.jalview.org/browse/JAL-1016");
408         }
409         int lastPos = -1;
410         for (int s = 0; s < sequence[pdbfnum].length; s++)
411         {
412           for (int sp, m = 0; m < mapping.length; m++)
413           {
414             if (mapping[m].getSequence() == sequence[pdbfnum][s]
415                     && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
416             {
417               if (refStructure == -1)
418               {
419                 refStructure = pdbfnum;
420               }
421               SequenceI asp = alignment.getSequenceAt(sp);
422               for (int r = 0; r < matched.length; r++)
423               {
424                 if (!matched[r])
425                 {
426                   continue;
427                 }
428                 matched[r] = false; // assume this is not a good site
429                 if (r >= asp.getLength())
430                 {
431                   continue;
432                 }
433
434                 if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
435                 {
436                   // no mapping to gaps in sequence
437                   continue;
438                 }
439                 int t = asp.findPosition(r); // sequence position
440                 int apos = mapping[m].getAtomNum(t);
441                 int pos = mapping[m].getPDBResNum(t);
442
443                 if (pos < 1 || pos == lastPos)
444                 {
445                   // can't align unmapped sequence
446                   continue;
447                 }
448                 matched[r] = true; // this is a good ite
449                 lastPos = pos;
450                 // just record this residue position
451                 commonrpositions[pdbfnum][r] = pos;
452               }
453               // create model selection suffix
454               isel[pdbfnum] = "/" + (pdbfnum + 1) + ".1";
455               if (mapping[m].getChain() == null
456                       || mapping[m].getChain().trim().length() == 0)
457               {
458                 targetC[pdbfnum] = "";
459               }
460               else
461               {
462                 targetC[pdbfnum] = ":" + mapping[m].getChain();
463               }
464               chainNames[pdbfnum] = mapping[m].getPdbId()
465                       + targetC[pdbfnum];
466               // move on to next pdb file
467               s = sequence[pdbfnum].length;
468               break;
469             }
470           }
471         }
472       }
473       
474       // TODO: consider bailing if nmatched less than 4 because superposition
475       // not
476       // well defined.
477       // TODO: refactor superposable position search (above) from jmol selection
478       // construction (below)
479
480       String[] selcom = new String[files.length];
481       int nmatched = 0;
482       // generate select statements to select regions to superimpose structures
483       {
484         for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
485         {
486           String chainCd = targetC[pdbfnum];
487           int lpos = -1;
488           boolean run = false;
489           StringBuffer molsel = new StringBuffer();
490           molsel.append("{");
491           for (int r = 0; r < matched.length; r++)
492           {
493             if (matched[r])
494             {
495               if (pdbfnum == 0)
496               {
497                 nmatched++;
498               }
499               if (lpos != commonrpositions[pdbfnum][r] - 1)
500               {
501                 // discontinuity
502                 if (lpos != -1)
503                 {
504                   molsel.append(lpos);
505                   molsel.append(chainCd);
506                   // molsel.append("} {");
507                   molsel.append("|");
508                 }
509               }
510               else
511               {
512                 // continuous run - and lpos >-1
513                 if (!run)
514                 {
515                   // at the beginning, so add dash
516                   molsel.append(lpos);
517                   molsel.append("-");
518                 }
519                 run = true;
520               }
521               lpos = commonrpositions[pdbfnum][r];
522               // molsel.append(lpos);
523             }
524           }
525           // add final selection phrase
526           if (lpos != -1)
527           {
528             molsel.append(lpos);
529             molsel.append(chainCd);
530             molsel.append("}");
531           }
532           selcom[pdbfnum] = molsel.toString();
533           selectioncom.append("((");
534           selectioncom.append(selcom[pdbfnum].substring(1,
535                   selcom[pdbfnum].length() - 1));
536           selectioncom.append(" )& ");
537           selectioncom.append(pdbfnum + 1);
538           selectioncom.append(".1)");
539           if (pdbfnum < files.length - 1)
540           {
541             selectioncom.append("|");
542           }
543         }
544       }
545       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
546       {
547         if (pdbfnum == refStructure)
548         {
549           continue;
550         }
551         command.append("echo ");
552         command.append("\"Superposing (");
553         command.append(chainNames[pdbfnum]);
554         command.append(") against reference (");
555         command.append(chainNames[refStructure]);
556         command.append(")\";\ncompare ");
557         command.append("{");
558         command.append(1 + pdbfnum);
559         command.append(".1} {");
560         command.append(1 + refStructure);
561         command.append(".1} SUBSET {*.CA | *.P} ATOMS ");
562
563         // form the matched pair strings
564         String sep = "";
565         for (int s = 0; s < 2; s++)
566         {
567           command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
568         }
569         command.append(" ROTATE TRANSLATE;\n");
570       }
571       System.out.println("Select regions:\n" + selectioncom.toString());
572       evalStateCommand("select *; cartoons off; backbone; select ("
573               + selectioncom.toString() + "); cartoons; ");
574       // selcom.append("; ribbons; ");
575       System.out.println("Superimpose command(s):\n" + command.toString());
576
577       evalStateCommand(command.toString());
578     }
579     if (selectioncom.length() > 0)
580     {// finally, mark all regions that were superposed.
581       if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
582       {
583         selectioncom.setLength(selectioncom.length() - 1);
584       }
585       System.out.println("Select regions:\n" + selectioncom.toString());
586       evalStateCommand("select *; cartoons off; backbone; select ("
587               + selectioncom.toString() + "); cartoons; ");
588       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
589     }
590   }
591
592   public void evalStateCommand(String command)
593   {
594     jmolHistory(false);
595     if (lastCommand == null || !lastCommand.equals(command))
596     {
597       viewer.evalStringQuiet(command + "\n");
598     }
599     jmolHistory(true);
600     lastCommand = command;
601   }
602
603   /**
604    * colour any structures associated with sequences in the given alignment
605    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
606    * if colourBySequence is enabled.
607    */
608   public void colourBySequence(boolean showFeatures,
609           jalview.api.AlignmentViewPanel alignmentv)
610   {
611     if (!colourBySequence || !loadingFinished)
612       return;
613     if (ssm == null)
614     {
615       return;
616     }
617     String[] files = getPdbFile();
618
619     SequenceRenderer sr = getSequenceRenderer(alignmentv);
620
621     FeatureRenderer fr = null;
622     if (showFeatures)
623     {
624       fr = getFeatureRenderer(alignmentv);
625     }
626     AlignmentI alignment = alignmentv.getAlignment();
627
628     for (jalview.structure.StructureMappingcommandSet cpdbbyseq : JmolCommands
629             .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
630                     alignment))
631       for (String cbyseq : cpdbbyseq.commands)
632       {
633         evalStateCommand(cbyseq);
634       }
635   }
636
637   public boolean isColourBySequence()
638   {
639     return colourBySequence;
640   }
641
642   public void setColourBySequence(boolean colourBySequence)
643   {
644     this.colourBySequence = colourBySequence;
645   }
646
647   public void createImage(String file, String type, int quality)
648   {
649     System.out.println("JMOL CREATE IMAGE");
650   }
651
652   public String createImage(String fileName, String type,
653           Object textOrBytes, int quality)
654   {
655     System.out.println("JMOL CREATE IMAGE");
656     return null;
657   }
658
659   public String eval(String strEval)
660   {
661     // System.out.println(strEval);
662     // "# 'eval' is implemented only for the applet.";
663     return null;
664   }
665
666   // End StructureListener
667   // //////////////////////////
668
669   public float[][] functionXY(String functionName, int x, int y)
670   {
671     return null;
672   }
673
674   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
675   {
676     // TODO Auto-generated method stub
677     return null;
678   }
679
680   public Color getColour(int atomIndex, int pdbResNum, String chain,
681           String pdbfile)
682   {
683     if (getModelNum(pdbfile) < 0)
684       return null;
685     // TODO: verify atomIndex is selecting correct model.
686     return new Color(viewer.getAtomArgb(atomIndex));
687   }
688
689   /**
690    * returns the current featureRenderer that should be used to colour the
691    * structures
692    * 
693    * @param alignment
694    * 
695    * @return
696    */
697   public abstract FeatureRenderer getFeatureRenderer(
698           AlignmentViewPanel alignment);
699
700   /**
701    * instruct the Jalview binding to update the pdbentries vector if necessary
702    * prior to matching the jmol view's contents to the list of structure files
703    * Jalview knows about.
704    */
705   public abstract void refreshPdbEntries();
706
707   private int getModelNum(String modelFileName)
708   {
709     String[] mfn = getPdbFile();
710     if (mfn == null)
711     {
712       return -1;
713     }
714     for (int i = 0; i < mfn.length; i++)
715     {
716       if (mfn[i].equalsIgnoreCase(modelFileName))
717         return i;
718     }
719     return -1;
720   }
721
722   /**
723    * map between index of model filename returned from getPdbFile and the first
724    * index of models from this file in the viewer. Note - this is not trimmed -
725    * use getPdbFile to get number of unique models.
726    */
727   private int _modelFileNameMap[];
728
729   // ////////////////////////////////
730   // /StructureListener
731   public synchronized String[] getPdbFile()
732   {
733     if (viewer == null)
734     {
735       return new String[0];
736     }
737     if (modelFileNames == null)
738     {
739
740       String mset[] = new String[viewer.getModelCount()];
741       _modelFileNameMap = new int[mset.length];
742       int j = 1;
743       String m = viewer.getModelFileName(0);
744       if (m != null)
745       {
746         try
747         {
748           mset[0] = new File(m).getAbsolutePath();
749         } catch (AccessControlException x)
750         {
751           // usually not allowed to do this in applet, so keep raw handle
752           mset[0] = m;
753           // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
754         }
755       }
756       for (int i = 1; i < mset.length; i++)
757       {
758         m = viewer.getModelFileName(i);
759         if (m != null)
760         {
761           try
762           {
763             mset[j] = new File(m).getAbsolutePath();
764           } catch (AccessControlException x)
765           {
766             // usually not allowed to do this in applet, so keep raw handle
767             mset[j] = m;
768             // System.err.println("jmolBinding: Using local file string from Jmol: "+m);
769           }
770         }
771         _modelFileNameMap[j] = i; // record the model index for the filename
772         // skip any additional models in the same file (NMR structures)
773         if ((mset[j] == null ? mset[j] != mset[j - 1]
774                 : (mset[j - 1] == null || !mset[j].equals(mset[j - 1]))))
775         {
776           j++;
777         }
778       }
779       modelFileNames = new String[j];
780       System.arraycopy(mset, 0, modelFileNames, 0, j);
781     }
782     return modelFileNames;
783   }
784
785   /**
786    * map from string to applet
787    */
788   public Map getRegistryInfo()
789   {
790     // TODO Auto-generated method stub
791     return null;
792   }
793
794   /**
795    * returns the current sequenceRenderer that should be used to colour the
796    * structures
797    * 
798    * @param alignment
799    * 
800    * @return
801    */
802   public abstract SequenceRenderer getSequenceRenderer(
803           AlignmentViewPanel alignment);
804
805   // ///////////////////////////////
806   // JmolStatusListener
807
808   public void handlePopupMenu(int x, int y)
809   {
810     jmolpopup.show(x, y);
811   }
812
813   // jmol/ssm only
814   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
815           String pdbfile)
816   {
817     if (modelFileNames == null)
818     {
819       return;
820     }
821
822     // look up file model number for this pdbfile
823     int mdlNum = 0;
824     String fn;
825     // may need to adjust for URLencoding here - we don't worry about that yet.
826     while (mdlNum < modelFileNames.length
827             && !pdbfile.equals(modelFileNames[mdlNum]))
828     {
829       // System.out.println("nomatch:"+pdbfile+"\nmodelfn:"+fn);
830       mdlNum++;
831     }
832     if (mdlNum == modelFileNames.length)
833     {
834       return;
835     }
836
837     jmolHistory(false);
838     // if (!pdbfile.equals(pdbentry.getFile()))
839     // return;
840     if (resetLastRes.length() > 0)
841     {
842       viewer.evalStringQuiet(resetLastRes.toString());
843     }
844
845     eval.setLength(0);
846     eval.append("select " + pdbResNum); // +modelNum
847
848     resetLastRes.setLength(0);
849     resetLastRes.append("select " + pdbResNum); // +modelNum
850
851     eval.append(":");
852     resetLastRes.append(":");
853     if (!chain.equals(" "))
854     {
855       eval.append(chain);
856       resetLastRes.append(chain);
857     }
858     {
859       eval.append(" /" + (mdlNum + 1));
860       resetLastRes.append("/" + (mdlNum + 1));
861     }
862     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
863
864     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
865             + " and not hetero; spacefill 0;");
866
867     eval.append("spacefill 200;select none");
868
869     viewer.evalStringQuiet(eval.toString());
870     jmolHistory(true);
871
872   }
873
874   boolean debug = true;
875
876   private void jmolHistory(boolean enable)
877   {
878     viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
879   }
880
881   public void loadInline(String string)
882   {
883     loadedInline = true;
884     // TODO: re JAL-623
885     // viewer.loadInline(strModel, isAppend);
886     // could do this:
887     // construct fake fullPathName and fileName so we can identify the file
888     // later.
889     // Then, construct pass a reader for the string to Jmol.
890     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
891     // fileName, null, reader, false, null, null, 0);
892     viewer.openStringInline(string);
893   }
894
895   public void mouseOverStructure(int atomIndex, String strInfo)
896   {
897     int pdbResNum;
898     int alocsep = strInfo.indexOf("^");
899     int mdlSep = strInfo.indexOf("/");
900     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
901
902     if (chainSeparator == -1)
903     {
904       chainSeparator = strInfo.indexOf(".");
905       if (mdlSep > -1 && mdlSep < chainSeparator)
906       {
907         chainSeparator1 = chainSeparator;
908         chainSeparator = mdlSep;
909       }
910     }
911     // handle insertion codes
912     if (alocsep != -1)
913     {
914       pdbResNum = Integer.parseInt(strInfo.substring(
915               strInfo.indexOf("]") + 1, alocsep));
916
917     }
918     else
919     {
920       pdbResNum = Integer.parseInt(strInfo.substring(
921               strInfo.indexOf("]") + 1, chainSeparator));
922     }
923     String chainId;
924
925     if (strInfo.indexOf(":") > -1)
926       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
927               strInfo.indexOf("."));
928     else
929     {
930       chainId = " ";
931     }
932
933     String pdbfilename = modelFileNames[frameNo]; // default is first or current
934     // model
935     if (mdlSep > -1)
936     {
937       if (chainSeparator1 == -1)
938       {
939         chainSeparator1 = strInfo.indexOf(".", mdlSep);
940       }
941       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
942               chainSeparator1) : strInfo.substring(mdlSep + 1);
943       try
944       {
945         // recover PDB filename for the model hovered over.
946         int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
947                 .intValue() - 1;
948         while (mnumber < _modelFileNameMap[_mp])
949         {
950           _mp--;
951         }
952         pdbfilename = modelFileNames[_mp];
953         if (pdbfilename == null)
954         {
955           pdbfilename = new File(viewer.getModelFileName(mnumber))
956                   .getAbsolutePath();
957         }
958
959       } catch (Exception e)
960       {
961       }
962       ;
963     }
964     if (lastMessage == null || !lastMessage.equals(strInfo))
965       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
966
967     lastMessage = strInfo;
968   }
969
970   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
971   {
972     if (data != null)
973     {
974       System.err.println("Ignoring additional hover info: " + data
975               + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
976     }
977     mouseOverStructure(atomIndex, strInfo);
978   }
979
980   /*
981    * { if (history != null && strStatus != null &&
982    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
983    * } }
984    */
985
986   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
987   {
988     /**
989      * this implements the toggle label behaviour copied from the original
990      * structure viewer, MCView
991      */
992     if (strData != null)
993     {
994       System.err.println("Ignoring additional pick data string " + strData);
995     }
996     int chainSeparator = strInfo.indexOf(":");
997     int p = 0;
998     if (chainSeparator == -1)
999       chainSeparator = strInfo.indexOf(".");
1000
1001     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
1002             chainSeparator);
1003     String mdlString = "";
1004     if ((p = strInfo.indexOf(":")) > -1)
1005       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
1006
1007     if ((p = strInfo.indexOf("/")) > -1)
1008     {
1009       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
1010     }
1011     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
1012             + mdlString + "))";
1013     jmolHistory(false);
1014
1015     if (!atomsPicked.contains(picked))
1016     {
1017       viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
1018       atomsPicked.addElement(picked);
1019     }
1020     else
1021     {
1022       viewer.evalString("select " + picked + ";label off");
1023       atomsPicked.removeElement(picked);
1024     }
1025     jmolHistory(true);
1026     // TODO: in application this happens
1027     //
1028     // if (scriptWindow != null)
1029     // {
1030     // scriptWindow.sendConsoleMessage(strInfo);
1031     // scriptWindow.sendConsoleMessage("\n");
1032     // }
1033
1034   }
1035
1036   @Override
1037   public void notifyCallback(EnumCallback type, Object[] data)
1038   {
1039     try
1040     {
1041       switch (type)
1042       {
1043       case LOADSTRUCT:
1044         notifyFileLoaded((String) data[1], (String) data[2],
1045                 (String) data[3], (String) data[4],
1046                 ((Integer) data[5]).intValue());
1047
1048         break;
1049       case PICK:
1050         notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
1051                 (String) data[0]);
1052         // also highlight in alignment
1053       case HOVER:
1054         notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
1055                 (String) data[0]);
1056         break;
1057       case SCRIPT:
1058         notifyScriptTermination((String) data[2],
1059                 ((Integer) data[3]).intValue());
1060         break;
1061       case ECHO:
1062         sendConsoleEcho((String) data[1]);
1063         break;
1064       case MESSAGE:
1065         sendConsoleMessage((data == null) ? ((String) null)
1066                 : (String) data[1]);
1067         break;
1068       case ERROR:
1069         // System.err.println("Ignoring error callback.");
1070         break;
1071       case SYNC:
1072       case RESIZE:
1073         refreshGUI();
1074         break;
1075       case MEASURE:
1076
1077       case CLICK:
1078       default:
1079         System.err.println("Unhandled callback " + type + " "
1080                 + data[1].toString());
1081         break;
1082       }
1083     } catch (Exception e)
1084     {
1085       System.err.println("Squashed Jmol callback handler error:");
1086       e.printStackTrace();
1087     }
1088   }
1089
1090   @Override
1091   public boolean notifyEnabled(EnumCallback callbackPick)
1092   {
1093     switch (callbackPick)
1094     {
1095     case ECHO:
1096     case LOADSTRUCT:
1097     case MEASURE:
1098     case MESSAGE:
1099     case PICK:
1100     case SCRIPT:
1101     case HOVER:
1102     case ERROR:
1103       return true;
1104     case RESIZE:
1105     case SYNC:
1106     case CLICK:
1107     case ANIMFRAME:
1108     case MINIMIZATION:
1109     }
1110     return false;
1111   }
1112
1113   // incremented every time a load notification is successfully handled -
1114   // lightweight mechanism for other threads to detect when they can start
1115   // referrring to new structures.
1116   private long loadNotifiesHandled = 0;
1117
1118   public long getLoadNotifiesHandled()
1119   {
1120     return loadNotifiesHandled;
1121   }
1122
1123   public void notifyFileLoaded(String fullPathName, String fileName2,
1124           String modelName, String errorMsg, int modelParts)
1125   {
1126     if (errorMsg != null)
1127     {
1128       fileLoadingError = errorMsg;
1129       refreshGUI();
1130       return;
1131     }
1132     // TODO: deal sensibly with models loaded inLine:
1133     // modelName will be null, as will fullPathName.
1134
1135     // the rest of this routine ignores the arguments, and simply interrogates
1136     // the Jmol view to find out what structures it contains, and adds them to
1137     // the structure selection manager.
1138     fileLoadingError = null;
1139     String[] oldmodels = modelFileNames;
1140     modelFileNames = null;
1141     chainNames = new Vector();
1142     chainFile = new Hashtable();
1143     boolean notifyLoaded = false;
1144     String[] modelfilenames = getPdbFile();
1145     // first check if we've lost any structures
1146     if (oldmodels != null && oldmodels.length > 0)
1147     {
1148       int oldm = 0;
1149       for (int i = 0; i < oldmodels.length; i++)
1150       {
1151         for (int n = 0; n < modelfilenames.length; n++)
1152         {
1153           if (modelfilenames[n] == oldmodels[i])
1154           {
1155             oldmodels[i] = null;
1156             break;
1157           }
1158         }
1159         if (oldmodels[i] != null)
1160         {
1161           oldm++;
1162         }
1163       }
1164       if (oldm > 0)
1165       {
1166         String[] oldmfn = new String[oldm];
1167         oldm = 0;
1168         for (int i = 0; i < oldmodels.length; i++)
1169         {
1170           if (oldmodels[i] != null)
1171           {
1172             oldmfn[oldm++] = oldmodels[i];
1173           }
1174         }
1175         // deregister the Jmol instance for these structures - we'll add
1176         // ourselves again at the end for the current structure set.
1177         ssm.removeStructureViewerListener(this, oldmfn);
1178       }
1179     }
1180     refreshPdbEntries();
1181     for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
1182     {
1183       String fileName = modelfilenames[modelnum];
1184       boolean foundEntry = false;
1185       MCview.PDBfile pdb = null;
1186       String pdbfile = null, pdbfhash = null;
1187       // model was probably loaded inline - so check the pdb file hashcode
1188       if (loadedInline)
1189       {
1190         // calculate essential attributes for the pdb data imported inline.
1191         // prolly need to resolve modelnumber properly - for now just use our
1192         // 'best guess'
1193         pdbfile = viewer.getData("" + (1 + _modelFileNameMap[modelnum])
1194                 + ".0", "PDB");
1195         pdbfhash = "" + pdbfile.hashCode();
1196       }
1197       if (pdbentry != null)
1198       {
1199         // search pdbentries and sequences to find correct pdbentry for this
1200         // model
1201         for (int pe = 0; pe < pdbentry.length; pe++)
1202         {
1203           boolean matches = false;
1204           if (fileName == null)
1205           {
1206             if (false)
1207             // see JAL-623 - need method of matching pasted data up
1208             {
1209               pdb = ssm.setMapping(sequence[pe], chains[pe], pdbfile,
1210                       AppletFormatAdapter.PASTE);
1211               pdbentry[modelnum].setFile("INLINE" + pdb.id);
1212               matches = true;
1213               foundEntry = true;
1214             }
1215           }
1216           else
1217           {
1218             File fl;
1219             if (matches = (fl = new File(pdbentry[pe].getFile()))
1220                     .equals(new File(fileName)))
1221             {
1222               foundEntry = true;
1223               // TODO: Jmol can in principle retrieve from CLASSLOADER but
1224               // this
1225               // needs
1226               // to be tested. See mantis bug
1227               // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1228               String protocol = AppletFormatAdapter.URL;
1229               try
1230               {
1231                 if (fl.exists())
1232                 {
1233                   protocol = AppletFormatAdapter.FILE;
1234                 }
1235               } catch (Exception e)
1236               {
1237               } catch (Error e)
1238               {
1239               }
1240               // Explicitly map to the filename used by Jmol ;
1241               pdb = ssm.setMapping(sequence[pe], chains[pe], fileName,
1242                       protocol);
1243               // pdbentry[pe].getFile(), protocol);
1244
1245             }
1246           }
1247           if (matches)
1248           {
1249             // add an entry for every chain in the model
1250             for (int i = 0; i < pdb.chains.size(); i++)
1251             {
1252               String chid = new String(pdb.id + ":"
1253                       + ((MCview.PDBChain) pdb.chains.elementAt(i)).id);
1254               chainFile.put(chid, fileName);
1255               chainNames.addElement(chid);
1256             }
1257             notifyLoaded = true;
1258           }
1259         }
1260       }
1261       if (!foundEntry && associateNewStructs)
1262       {
1263         // this is a foreign pdb file that jalview doesn't know about - add
1264         // it to the dataset and try to find a home - either on a matching
1265         // sequence or as a new sequence.
1266         String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
1267                 "PDB");
1268         // parse pdb file into a chain, etc.
1269         // locate best match for pdb in associated views and add mapping to
1270         // ssm
1271         // if properly registered then
1272         notifyLoaded = true;
1273
1274       }
1275     }
1276     // FILE LOADED OK
1277     // so finally, update the jmol bits and pieces
1278     if (jmolpopup != null)
1279     {
1280       // potential for deadlock here:
1281       // jmolpopup.updateComputedMenus();
1282     }
1283     if (!isLoadingFromArchive())
1284     {
1285       viewer.evalStringQuiet("model 0; select backbone;restrict;cartoon;wireframe off;spacefill off");
1286     }
1287     // register ourselves as a listener and notify the gui that it needs to
1288     // update itself.
1289     ssm.addStructureViewerListener(this);
1290     if (notifyLoaded)
1291     {
1292       FeatureRenderer fr = getFeatureRenderer(null);
1293       if (fr != null)
1294       {
1295         fr.featuresAdded();
1296       }
1297       refreshGUI();
1298       loadNotifiesHandled++;
1299     }
1300     setLoadingFromArchive(false);
1301   }
1302
1303   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
1304   {
1305     notifyAtomPicked(iatom, strMeasure, null);
1306   }
1307
1308   public abstract void notifyScriptTermination(String strStatus,
1309           int msWalltime);
1310
1311   /**
1312    * display a message echoed from the jmol viewer
1313    * 
1314    * @param strEcho
1315    */
1316   public abstract void sendConsoleEcho(String strEcho); /*
1317                                                          * { showConsole(true);
1318                                                          * 
1319                                                          * history.append("\n" +
1320                                                          * strEcho); }
1321                                                          */
1322
1323   // /End JmolStatusListener
1324   // /////////////////////////////
1325
1326   /**
1327    * @param strStatus
1328    *          status message - usually the response received after a script
1329    *          executed
1330    */
1331   public abstract void sendConsoleMessage(String strStatus);
1332
1333   public void setCallbackFunction(String callbackType,
1334           String callbackFunction)
1335   {
1336     System.err.println("Ignoring set-callback request to associate "
1337             + callbackType + " with function " + callbackFunction);
1338
1339   }
1340
1341   public void setJalviewColourScheme(ColourSchemeI cs)
1342   {
1343     colourBySequence = false;
1344
1345     if (cs == null)
1346       return;
1347
1348     String res;
1349     int index;
1350     Color col;
1351     jmolHistory(false);
1352     // TODO: Switch between nucleotide or aa selection expressions
1353     Enumeration en = ResidueProperties.aa3Hash.keys();
1354     StringBuffer command = new StringBuffer("select *;color white;");
1355     while (en.hasMoreElements())
1356     {
1357       res = en.nextElement().toString();
1358       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
1359       if (index > 20)
1360         continue;
1361
1362       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
1363
1364       command.append("select " + res + ";color[" + col.getRed() + ","
1365               + col.getGreen() + "," + col.getBlue() + "];");
1366     }
1367
1368     evalStateCommand(command.toString());
1369     jmolHistory(true);
1370   }
1371
1372   public void showHelp()
1373   {
1374     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
1375   }
1376
1377   /**
1378    * open the URL somehow
1379    * 
1380    * @param target
1381    */
1382   public abstract void showUrl(String url, String target);
1383
1384   /**
1385    * called when the binding thinks the UI needs to be refreshed after a Jmol
1386    * state change. this could be because structures were loaded, or because an
1387    * error has occured.
1388    */
1389   public abstract void refreshGUI();
1390
1391   /**
1392    * called to show or hide the associated console window container.
1393    * 
1394    * @param show
1395    */
1396   public abstract void showConsole(boolean show);
1397
1398   /**
1399    * @param renderPanel
1400    * @param jmolfileio
1401    *          - when true will initialise jmol's file IO system (should be false
1402    *          in applet context)
1403    * @param htmlName
1404    * @param documentBase
1405    * @param codeBase
1406    * @param commandOptions
1407    */
1408   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1409           String htmlName, URL documentBase, URL codeBase,
1410           String commandOptions)
1411   {
1412     allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
1413             codeBase, commandOptions, null, null);
1414   }
1415
1416   /**
1417    * 
1418    * @param renderPanel
1419    * @param jmolfileio
1420    *          - when true will initialise jmol's file IO system (should be false
1421    *          in applet context)
1422    * @param htmlName
1423    * @param documentBase
1424    * @param codeBase
1425    * @param commandOptions
1426    * @param consolePanel
1427    *          - panel to contain Jmol console
1428    * @param buttonsToShow
1429    *          - buttons to show on the console, in ordr
1430    */
1431   public void allocateViewer(Container renderPanel, boolean jmolfileio,
1432           String htmlName, URL documentBase, URL codeBase,
1433           String commandOptions, final Container consolePanel,
1434           String buttonsToShow)
1435   {
1436     if (commandOptions == null)
1437     {
1438       commandOptions = "";
1439     }
1440     viewer = JmolViewer.allocateViewer(renderPanel,
1441             (jmolfileio ? new SmarterJmolAdapter() : null), htmlName
1442                     + ((Object) this).toString(), documentBase, codeBase,
1443             commandOptions, this);
1444
1445     console = createJmolConsole(viewer, consolePanel, buttonsToShow);
1446     if (consolePanel != null)
1447     {
1448       consolePanel.addComponentListener(this);
1449
1450     }
1451
1452   }
1453
1454   protected abstract JmolAppConsoleInterface createJmolConsole(
1455           JmolViewer viewer2, Container consolePanel, String buttonsToShow);
1456
1457   protected org.jmol.api.JmolAppConsoleInterface console = null;
1458
1459   public void componentResized(ComponentEvent e)
1460   {
1461
1462   }
1463
1464   public void componentMoved(ComponentEvent e)
1465   {
1466
1467   }
1468
1469   public void componentShown(ComponentEvent e)
1470   {
1471     showConsole(true);
1472   }
1473
1474   public void componentHidden(ComponentEvent e)
1475   {
1476     showConsole(false);
1477   }
1478
1479   public void setLoadingFromArchive(boolean loadingFromArchive)
1480   {
1481     this.loadingFromArchive = loadingFromArchive;
1482   }
1483   
1484   /**
1485    * 
1486    * @return true if Jmol is still restoring state or loading is still going on (see setFinsihedLoadingFromArchive)
1487    */
1488   public boolean isLoadingFromArchive()
1489   {
1490     return loadingFromArchive && !loadingFinished;
1491   }
1492
1493   /**
1494    * modify flag which controls if sequence colouring events are honoured by the binding. 
1495    * Should be true for normal operation
1496    * @param finishedLoading
1497    */
1498   public void setFinishedLoadingFromArchive(boolean finishedLoading)
1499   {
1500     loadingFinished = finishedLoading;
1501   }
1502
1503   public void setBackgroundColour(java.awt.Color col)
1504   {
1505     jmolHistory(false);
1506     viewer.evalStringQuiet("background [" + col.getRed() + ","
1507             + col.getGreen() + "," + col.getBlue() + "];");
1508     jmolHistory(true);
1509   }
1510
1511   /**
1512    * add structures and any known sequence associations
1513    * 
1514    * @returns the pdb entries added to the current set.
1515    */
1516   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
1517           SequenceI[][] seq, String[][] chns)
1518   {
1519     int pe = -1;
1520     Vector v = new Vector();
1521     Vector rtn = new Vector();
1522     for (int i = 0; i < pdbentry.length; i++)
1523     {
1524       v.addElement(pdbentry[i]);
1525     }
1526     for (int i = 0; i < pdbe.length; i++)
1527     {
1528       int r = v.indexOf(pdbe[i]);
1529       if (r == -1 || r >= pdbentry.length)
1530       {
1531         rtn.addElement(new int[]
1532         { v.size(), i });
1533         v.addElement(pdbe[i]);
1534       }
1535       else
1536       {
1537         // just make sure the sequence/chain entries are all up to date
1538         addSequenceAndChain(r, seq[i], chns[i]);
1539       }
1540     }
1541     pdbe = new PDBEntry[v.size()];
1542     v.copyInto(pdbe);
1543     pdbentry = pdbe;
1544     if (rtn.size() > 0)
1545     {
1546       // expand the tied seuqence[] and string[] arrays
1547       SequenceI[][] sqs = new SequenceI[pdbentry.length][];
1548       String[][] sch = new String[pdbentry.length][];
1549       System.arraycopy(sequence, 0, sqs, 0, sequence.length);
1550       System.arraycopy(chains, 0, sch, 0, this.chains.length);
1551       sequence = sqs;
1552       chains = sch;
1553       pdbe = new PDBEntry[rtn.size()];
1554       for (int r = 0; r < pdbe.length; r++)
1555       {
1556         int[] stri = ((int[]) rtn.elementAt(r));
1557         // record the pdb file as a new addition
1558         pdbe[r] = pdbentry[stri[0]];
1559         // and add the new sequence/chain entries
1560         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
1561       }
1562     }
1563     else
1564     {
1565       pdbe = null;
1566     }
1567     return pdbe;
1568   }
1569
1570   public void addSequence(int pe, SequenceI[] seq)
1571   {
1572     // add sequences to the pe'th pdbentry's seuqence set.
1573     addSequenceAndChain(pe, seq, null);
1574   }
1575
1576   private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
1577   {
1578     if (pe < 0 || pe >= pdbentry.length)
1579     {
1580       throw new Error(
1581               "Implementation error - no corresponding pdbentry (for index "
1582                       + pe + ") to add sequences mappings to");
1583     }
1584     final String nullChain = "TheNullChain";
1585     Vector s = new Vector();
1586     Vector c = new Vector();
1587     if (chains == null)
1588     {
1589       chains = new String[pdbentry.length][];
1590     }
1591     if (sequence[pe] != null)
1592     {
1593       for (int i = 0; i < sequence[pe].length; i++)
1594       {
1595         s.addElement(sequence[pe][i]);
1596         if (chains[pe] != null)
1597         {
1598           if (i < chains[pe].length)
1599           {
1600             c.addElement(chains[pe][i]);
1601           }
1602           else
1603           {
1604             c.addElement(nullChain);
1605           }
1606         }
1607         else
1608         {
1609           if (tchain != null && tchain.length > 0)
1610           {
1611             c.addElement(nullChain);
1612           }
1613         }
1614       }
1615     }
1616     for (int i = 0; i < seq.length; i++)
1617     {
1618       if (!s.contains(seq[i]))
1619       {
1620         s.addElement(seq[i]);
1621         if (tchain != null && i < tchain.length)
1622         {
1623           c.addElement(tchain[i] == null ? nullChain : tchain[i]);
1624         }
1625       }
1626     }
1627     SequenceI[] tmp = new SequenceI[s.size()];
1628     s.copyInto(tmp);
1629     sequence[pe] = tmp;
1630     if (c.size() > 0)
1631     {
1632       String[] tch = new String[c.size()];
1633       c.copyInto(tch);
1634       for (int i = 0; i < tch.length; i++)
1635       {
1636         if (tch[i] == nullChain)
1637         {
1638           tch[i] = null;
1639         }
1640       }
1641       chains[pe] = tch;
1642     }
1643     else
1644     {
1645       chains[pe] = null;
1646     }
1647   }
1648
1649   /**
1650    * 
1651    * @param pdbfile
1652    * @return text report of alignment between pdbfile and any associated
1653    *         alignment sequences
1654    */
1655   public String printMapping(String pdbfile)
1656   {
1657     return ssm.printMapping(pdbfile);
1658   }
1659
1660   @Override
1661   public void resizeInnerPanel(String data)
1662   {
1663     // Jalview doesn't honour resize panel requests
1664
1665   }
1666 }