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