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