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