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