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