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