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