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