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