JAL-1619 load/align cDNA for protein (wip)
[jalview.git] / src / jalview / ext / rbvi / chimera / JalviewChimeraBinding.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.rbvi.chimera;
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.event.ComponentEvent;
44 import java.io.File;
45 import java.util.ArrayList;
46 import java.util.Enumeration;
47 import java.util.HashMap;
48 import java.util.LinkedHashMap;
49 import java.util.List;
50 import java.util.Map;
51
52 import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
53 import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
54 import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
55 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
56
57 public abstract class JalviewChimeraBinding extends
58         SequenceStructureBindingModel implements StructureListener,
59         SequenceStructureBinding, StructureSelectionManagerProvider
60
61 {
62   private static final String PHOSPHORUS = "P";
63
64   private static final String ALPHACARBON = "CA";
65
66   private StructureManager csm;
67
68   private ChimeraManager viewer;
69
70   /**
71    * set if chimera state is being restored from some source - instructs binding
72    * not to apply default display style when structure set is updated for first
73    * time.
74    */
75   private boolean loadingFromArchive = false;
76
77   /**
78    * second flag to indicate if the Chimera viewer should ignore sequence
79    * colouring events from the structure manager because the GUI is still
80    * setting up
81    */
82   private boolean loadingFinished = true;
83
84   /**
85    * state flag used to check if the Chimera viewer's paint method can be called
86    */
87   private boolean finishedInit = false;
88
89   public boolean isFinishedInit()
90   {
91     return finishedInit;
92   }
93
94   public void setFinishedInit(boolean finishedInit)
95   {
96     this.finishedInit = finishedInit;
97   }
98
99   boolean allChainsSelected = false;
100
101   /**
102    * when true, try to search the associated datamodel for sequences that are
103    * associated with any unknown structures in the Chimera view.
104    */
105   private boolean associateNewStructs = false;
106
107   List<String> atomsPicked = new ArrayList<String>();
108
109   public List<String> chainNames;
110
111   private Map<String, String> chainFile;
112
113   /**
114    * array of target chains for sequences - tied to pdbentry and sequence[]
115    */
116   protected String[][] chains;
117
118   boolean colourBySequence = true;
119
120   StringBuffer eval = new StringBuffer();
121
122   public String fileLoadingError;
123
124   private Map<String, List<ChimeraModel>> chimmaps = new LinkedHashMap<String, List<ChimeraModel>>();
125
126   private List<String> mdlToFile = new ArrayList<String>();
127
128   /**
129    * the default or current model displayed if the model cannot be identified
130    * from the selection message
131    */
132   int frameNo = 0;
133
134   String lastCommand;
135
136   String lastMessage;
137
138   boolean loadedInline;
139
140   public boolean openFile(PDBEntry pe)
141   {
142     String file = pe.getFile();
143     try
144     {
145       List<ChimeraModel> oldList = viewer.getModelList();
146       viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
147       List<ChimeraModel> newList = viewer.getModelList();
148       if (oldList.size() < newList.size())
149       {
150         while (oldList.size() > 0)
151         {
152           oldList.remove(0);
153           newList.remove(0);
154         }
155         chimmaps.put(file, newList);
156         for (ChimeraModel cm : newList)
157         {
158           while (mdlToFile.size() < 1 + cm.getModelNumber())
159           {
160             mdlToFile.add(new String(""));
161           }
162           mdlToFile.set(cm.getModelNumber(), file);
163         }
164
165         File fl = new File(file);
166         String protocol = AppletFormatAdapter.URL;
167         try
168         {
169           if (fl.exists())
170           {
171             protocol = AppletFormatAdapter.FILE;
172           }
173         } catch (Exception e)
174         {
175         } catch (Error e)
176         {
177         }
178         // Explicitly map to the filename used by Chimera ;
179         // pdbentry[pe].getFile(), protocol);
180
181         if (ssm != null)
182         {
183           ssm.addStructureViewerListener(this);
184           // ssm.addSelectionListener(this);
185           FeatureRenderer fr = getFeatureRenderer(null);
186           if (fr != null)
187           {
188             fr.featuresAdded();
189           }
190           refreshGUI();
191         }
192         return true;
193       }
194     } catch (Exception q)
195     {
196       log("Exception when trying to open model " + file + "\n"
197               + q.toString());
198       q.printStackTrace();
199     }
200     return false;
201   }
202
203   /**
204    * current set of model filenames loaded
205    */
206   String[] modelFileNames = null;
207
208   public PDBEntry[] pdbentry;
209
210   /**
211    * datasource protocol for access to PDBEntrylatest
212    */
213   String protocol = null;
214
215   StringBuffer resetLastRes = new StringBuffer();
216
217   /**
218    * sequences mapped to each pdbentry
219    */
220   public SequenceI[][] sequence;
221
222   public StructureSelectionManager ssm;
223
224   private List<String> lastReply;
225
226   public JalviewChimeraBinding(StructureSelectionManager ssm,
227           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
228           String protocol)
229   {
230     this.ssm = ssm;
231     this.sequence = sequenceIs;
232     this.chains = chains;
233     this.pdbentry = pdbentry;
234     this.protocol = protocol;
235     if (chains == null)
236     {
237       this.chains = new String[pdbentry.length][];
238     }
239     viewer = new ChimeraManager(
240             csm = new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
241   }
242
243   public JalviewChimeraBinding(StructureSelectionManager ssm,
244           ChimeraManager viewer2)
245   {
246     this.ssm = ssm;
247     viewer = viewer2;
248     csm = viewer.getStructureManager();
249   }
250
251   /**
252    * Construct a title string for the viewer window based on the data Jalview
253    * knows about
254    * 
255    * @param verbose
256    * @return
257    */
258   public String getViewerTitle(boolean verbose)
259   {
260     if (sequence == null || pdbentry == null || sequence.length < 1
261             || pdbentry.length < 1 || sequence[0].length < 1)
262     {
263       return ("Jalview Chimera Window");
264     }
265     // TODO: give a more informative title when multiple structures are
266     // displayed.
267     StringBuilder title = new StringBuilder(64);
268     title.append("Chimera view for " + sequence[0][0].getName() + ":"
269             + pdbentry[0].getId());
270
271     if (verbose)
272     {
273       if (pdbentry[0].getProperty() != null)
274       {
275         if (pdbentry[0].getProperty().get("method") != null)
276         {
277           title.append(" Method: ");
278           title.append(pdbentry[0].getProperty().get("method"));
279         }
280         if (pdbentry[0].getProperty().get("chains") != null)
281         {
282           title.append(" Chain:");
283           title.append(pdbentry[0].getProperty().get("chains"));
284         }
285       }
286     }
287     return title.toString();
288   }
289
290   /**
291    * prepare the view for a given set of models/chains. chainList contains
292    * strings of the form 'pdbfilename:Chaincode'
293    * 
294    * @param toshow
295    *          list of chains to make visible
296    */
297   public void centerViewer(List<String> toshow)
298   {
299     StringBuilder cmd = new StringBuilder(64);
300     int mlength, p;
301     for (String lbl : toshow)
302     {
303       mlength = 0;
304       do
305       {
306         p = mlength;
307         mlength = lbl.indexOf(":", p);
308       } while (p < mlength && mlength < (lbl.length() - 2));
309       // TODO: lookup each pdb id and recover proper model number for it.
310       cmd.append("#" + getModelNum(chainFile.get(lbl)) + "."
311               + lbl.substring(mlength + 1) + " or ");
312     }
313     if (cmd.length() > 0)
314     {
315       cmd.setLength(cmd.length() - 4);
316     }
317     String cmdstring = cmd.toString();
318     evalStateCommand("~display #*; ~ribbon #*; ribbon " + cmdstring
319             + ";focus " + cmdstring, false);
320   }
321
322   /**
323    * Close down the Jalview viewer, and (optionally) the associate Chimera
324    * window.
325    */
326   public void closeViewer(boolean closeChimera)
327   {
328     ssm.removeStructureViewerListener(this, this.getPdbFile());
329     if (closeChimera)
330     {
331       viewer.exitChimera();
332     }
333     lastCommand = null;
334     viewer = null;
335     releaseUIResources();
336   }
337
338   /**
339    * called by JalviewChimerabinding after closeViewer is called - release any
340    * resources and references so they can be garbage collected.
341    */
342   protected abstract void releaseUIResources();
343
344   public void colourByChain()
345   {
346     colourBySequence = false;
347     evalStateCommand("rainbow chain", false);
348   }
349
350   public void colourByCharge()
351   {
352     colourBySequence = false;
353     evalStateCommand(
354             "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS",
355             false);
356   }
357
358   /**
359    * superpose the structures associated with sequences in the alignment
360    * according to their corresponding positions.
361    */
362   public void superposeStructures(AlignmentI alignment)
363   {
364     superposeStructures(alignment, -1, null);
365   }
366
367   /**
368    * superpose the structures associated with sequences in the alignment
369    * according to their corresponding positions. ded)
370    * 
371    * @param refStructure
372    *          - select which pdb file to use as reference (default is -1 - the
373    *          first structure in the alignment)
374    */
375   public void superposeStructures(AlignmentI alignment, int refStructure)
376   {
377     superposeStructures(alignment, refStructure, null);
378   }
379
380   /**
381    * superpose the structures associated with sequences in the alignment
382    * according to their corresponding positions. ded)
383    * 
384    * @param refStructure
385    *          - select which pdb file to use as reference (default is -1 - the
386    *          first structure in the alignment)
387    * @param hiddenCols
388    *          TODO
389    */
390   public void superposeStructures(AlignmentI alignment, int refStructure,
391           ColumnSelection hiddenCols)
392   {
393     superposeStructures(new AlignmentI[]
394     { alignment }, new int[]
395     { refStructure }, new ColumnSelection[]
396     { hiddenCols });
397   }
398
399   public void superposeStructures(AlignmentI[] _alignment,
400           int[] _refStructure, ColumnSelection[] _hiddenCols)
401   {
402     assert (_alignment.length == _refStructure.length && _alignment.length != _hiddenCols.length);
403     StringBuilder allComs = new StringBuilder(128); // Chimera superposition cmd
404     String[] files = getPdbFile();
405     // check to see if we are still waiting for Chimera files
406     long starttime = System.currentTimeMillis();
407     boolean waiting = true;
408     do
409     {
410       waiting = false;
411       for (String file : files)
412       {
413         try
414         {
415           // HACK - in Jalview 2.8 this call may not be threadsafe so we catch
416           // every possible exception
417           StructureMapping[] sm = ssm.getMapping(file);
418           if (sm == null || sm.length == 0)
419           {
420             waiting = true;
421           }
422         } catch (Exception x)
423         {
424           waiting = true;
425         } catch (Error q)
426         {
427           waiting = true;
428         }
429       }
430       // we wait around for a reasonable time before we give up
431     } while (waiting
432             && System.currentTimeMillis() < (10000 + 1000 * files.length + starttime));
433     if (waiting)
434     {
435       System.err
436               .println("RUNTIME PROBLEM: Chimera seems to be taking a long time to process all the structures.");
437       return;
438     }
439     refreshPdbEntries();
440     StringBuffer selectioncom = new StringBuffer();
441     for (int a = 0; a < _alignment.length; a++)
442     {
443       int refStructure = _refStructure[a];
444       AlignmentI alignment = _alignment[a];
445       ColumnSelection hiddenCols = _hiddenCols[a];
446       if (a > 0
447               && selectioncom.length() > 0
448               && !selectioncom.substring(selectioncom.length() - 1).equals(
449                       " "))
450       {
451         selectioncom.append(" ");
452       }
453       // process this alignment
454       if (refStructure >= files.length)
455       {
456         System.err.println("Invalid reference structure value "
457                 + refStructure);
458         refStructure = -1;
459       }
460       if (refStructure < -1)
461       {
462         refStructure = -1;
463       }
464
465       boolean matched[] = new boolean[alignment.getWidth()];
466       for (int m = 0; m < matched.length; m++)
467       {
468
469         matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
470       }
471
472       int commonrpositions[][] = new int[files.length][alignment.getWidth()];
473       String isel[] = new String[files.length];
474       String[] targetC = new String[files.length];
475       String[] chainNames = new String[files.length];
476       String[] atomSpec = new String[files.length];
477       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
478       {
479         StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
480         // RACE CONDITION - getMapping only returns Jmol loaded filenames once
481         // Jmol callback has completed.
482         if (mapping == null || mapping.length < 1)
483         {
484           throw new Error(MessageManager.getString("error.implementation_error_chimera_getting_data"));
485         }
486         int lastPos = -1;
487         for (int s = 0; s < sequence[pdbfnum].length; s++)
488         {
489           for (int sp, m = 0; m < mapping.length; m++)
490           {
491             if (mapping[m].getSequence() == sequence[pdbfnum][s]
492                     && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
493             {
494               if (refStructure == -1)
495               {
496                 refStructure = pdbfnum;
497               }
498               SequenceI asp = alignment.getSequenceAt(sp);
499               for (int r = 0; r < matched.length; r++)
500               {
501                 if (!matched[r])
502                 {
503                   continue;
504                 }
505                 matched[r] = false; // assume this is not a good site
506                 if (r >= asp.getLength())
507                 {
508                   continue;
509                 }
510
511                 if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
512                 {
513                   // no mapping to gaps in sequence
514                   continue;
515                 }
516                 int t = asp.findPosition(r); // sequence position
517                 int apos = mapping[m].getAtomNum(t);
518                 int pos = mapping[m].getPDBResNum(t);
519
520                 if (pos < 1 || pos == lastPos)
521                 {
522                   // can't align unmapped sequence
523                   continue;
524                 }
525                 matched[r] = true; // this is a good ite
526                 lastPos = pos;
527                 // just record this residue position
528                 commonrpositions[pdbfnum][r] = pos;
529               }
530               // create model selection suffix
531               isel[pdbfnum] = "#" + pdbfnum;
532               if (mapping[m].getChain() == null
533                       || mapping[m].getChain().trim().length() == 0)
534               {
535                 targetC[pdbfnum] = "";
536               }
537               else
538               {
539                 targetC[pdbfnum] = "." + mapping[m].getChain();
540               }
541               chainNames[pdbfnum] = mapping[m].getPdbId()
542                       + targetC[pdbfnum];
543               atomSpec[pdbfnum] = asp.getRNA() != null ? PHOSPHORUS : ALPHACARBON;
544               // move on to next pdb file
545               s = sequence[pdbfnum].length;
546               break;
547             }
548           }
549         }
550       }
551
552       // TODO: consider bailing if nmatched less than 4 because superposition
553       // not
554       // well defined.
555       // TODO: refactor superposable position search (above) from jmol selection
556       // construction (below)
557
558       String[] selcom = new String[files.length];
559       int nmatched = 0;
560       String sep = "";
561       // generate select statements to select regions to superimpose structures
562       {
563         for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
564         {
565           String chainCd = targetC[pdbfnum];
566           int lpos = -1;
567           boolean run = false;
568           StringBuffer molsel = new StringBuffer();
569           for (int r = 0; r < matched.length; r++)
570           {
571             if (matched[r])
572             {
573               if (pdbfnum == 0)
574               {
575                 nmatched++;
576               }
577               if (lpos != commonrpositions[pdbfnum][r] - 1)
578               {
579                 // discontinuity
580                 if (lpos != -1)
581                 {
582                   molsel.append((run ? "" : ":") + lpos);
583                   molsel.append(chainCd);
584                   molsel.append(",");
585                 }
586               }
587               else
588               {
589                 // continuous run - and lpos >-1
590                 if (!run)
591                 {
592                   // at the beginning, so add dash
593                   molsel.append(":" + lpos);
594                   molsel.append("-");
595                 }
596                 run = true;
597               }
598               lpos = commonrpositions[pdbfnum][r];
599               // molsel.append(lpos);
600             }
601           }
602           // add final selection phrase
603           if (lpos != -1)
604           {
605             molsel.append((run ? "" : ":") + lpos);
606             molsel.append(chainCd);
607             // molsel.append("");
608           }
609           if (molsel.length() > 1)
610           {
611             selcom[pdbfnum] = molsel.toString();
612             selectioncom.append("#" + pdbfnum);
613             selectioncom.append(selcom[pdbfnum]);
614             selectioncom.append(" ");
615             if (pdbfnum < files.length - 1)
616             {
617               selectioncom.append("| ");
618             }
619           }
620           else
621           {
622             selcom[pdbfnum] = null;
623           }
624         }
625       }
626       StringBuilder command = new StringBuilder(256);
627       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
628       {
629         if (pdbfnum == refStructure || selcom[pdbfnum] == null
630                 || selcom[refStructure] == null)
631         {
632           continue;
633         }
634         if (command.length() > 0)
635         {
636           command.append(";");
637         }
638
639         /*
640          * Form Chimera match command, from the 'new' structure to the
641          * 'reference' structure e.g. (residues 1-91, chain B/A, alphacarbons):
642          * 
643          * match #1:1-91.B@CA #0:1-91.A@CA
644          * 
645          * @see
646          * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
647          */
648         command.append("match #" + pdbfnum /* +".1" */);
649         // TODO: handle sub-models
650         command.append(selcom[pdbfnum]);
651         command.append("@" + atomSpec[pdbfnum]);
652         command.append(" #" + refStructure /* +".1" */);
653         command.append(selcom[refStructure]);
654         command.append("@" + atomSpec[refStructure]);
655       }
656       if (selectioncom.length() > 0)
657       {
658         // TODO remove debug output
659         System.out.println("Select regions:\n" + selectioncom.toString());
660         System.out
661                 .println("Superimpose command(s):\n" + command.toString());
662         allComs.append("~display all; chain @CA|P; ribbon "
663                 + selectioncom.toString() + ";"+command.toString());
664         // selcom.append("; ribbons; ");
665       }
666     }
667     if (selectioncom.length() > 0)
668     {// finally, mark all regions that were superposed.
669       if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
670       {
671         selectioncom.setLength(selectioncom.length() - 1);
672       }
673       System.out.println("Select regions:\n" + selectioncom.toString());
674       allComs.append("; ~display all; chain @CA|P; ribbon "
675               + selectioncom.toString() + "; focus");
676       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
677       evalStateCommand(allComs.toString(), true /* false */);
678     }
679     
680   }
681
682   private void checkLaunched()
683   {
684     if (!viewer.isChimeraLaunched())
685     {
686       viewer.launchChimera(csm.getChimeraPaths());
687     }
688     if (!viewer.isChimeraLaunched())
689     {
690       log("Failed to launch Chimera!");
691     }
692   }
693
694   /**
695    * Answers true if the Chimera process is still running, false if ended or not
696    * started.
697    * 
698    * @return
699    */
700   public boolean isChimeraRunning()
701   {
702     return viewer.isChimeraLaunched();
703   }
704
705   /**
706    * Send a command to Chimera, and optionally log any responses.
707    * 
708    * @param command
709    * @param logResponse
710    */
711   public void evalStateCommand(final String command, boolean logResponse)
712   {
713     viewerCommandHistory(false);
714     checkLaunched();
715     if (lastCommand == null || !lastCommand.equals(command))
716     {
717 //      Thread t = new Thread(new Runnable()
718 //      {
719 //        @Override
720 //        public void run()
721 //        {
722       // trim command or it may never find a match in the replyLog!!
723       lastReply = viewer.sendChimeraCommand(command.trim(), logResponse);
724       if (debug && logResponse)
725           {
726             log("Response from command ('" + command + "') was:\n"
727                     + lastReply);
728           }
729 //        }
730 //      });
731       // TODO - use j7/8 thread management
732 //      try
733 //      {
734 //        t.join();
735 //      } catch (InterruptedException foo)
736 //      {
737 //      }
738 //      ;
739     }
740     viewerCommandHistory(true);
741     lastCommand = command;
742   }
743
744   /**
745    * colour any structures associated with sequences in the given alignment
746    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
747    * if colourBySequence is enabled.
748    */
749   public void colourBySequence(boolean showFeatures,
750           jalview.api.AlignmentViewPanel alignmentv)
751   {
752     if (!colourBySequence || !loadingFinished)
753     {
754       return;
755     }
756     if (ssm == null)
757     {
758       return;
759     }
760     String[] files = getPdbFile();
761
762     SequenceRenderer sr = getSequenceRenderer(alignmentv);
763
764     FeatureRenderer fr = null;
765     if (showFeatures)
766     {
767       fr = getFeatureRenderer(alignmentv);
768     }
769     AlignmentI alignment = alignmentv.getAlignment();
770
771     for (jalview.structure.StructureMappingcommandSet cpdbbyseq : ChimeraCommands
772             .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
773                     alignment))
774     {
775       for (String cbyseq : cpdbbyseq.commands)
776       {
777         waitForChimera();
778         evalStateCommand(cbyseq, false);
779         waitForChimera();
780       }
781     }
782   }
783
784   private void waitForChimera()
785   {
786     while (viewer != null && viewer.isBusy())
787     {
788       try {
789         Thread.sleep(15);
790       } catch (InterruptedException q)
791       {}
792     }
793   }
794
795   public boolean isColourBySequence()
796   {
797     return colourBySequence;
798   }
799
800   public void setColourBySequence(boolean colourBySequence)
801   {
802     this.colourBySequence = colourBySequence;
803   }
804
805   // End StructureListener
806   // //////////////////////////
807
808   public float[][] functionXY(String functionName, int x, int y)
809   {
810     return null;
811   }
812
813   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
814   {
815     // TODO Auto-generated method stub
816     return null;
817   }
818
819   public Color getColour(int atomIndex, int pdbResNum, String chain,
820           String pdbfile)
821   {
822     if (getModelNum(pdbfile) < 0)
823     {
824       return null;
825     }
826     log("get model / residue colour attribute unimplemented");
827     return null;
828   }
829
830   /**
831    * returns the current featureRenderer that should be used to colour the
832    * structures
833    * 
834    * @param alignment
835    * 
836    * @return
837    */
838   public abstract FeatureRenderer getFeatureRenderer(
839           AlignmentViewPanel alignment);
840
841   /**
842    * instruct the Jalview binding to update the pdbentries vector if necessary
843    * prior to matching the jmol view's contents to the list of structure files
844    * Jalview knows about.
845    */
846   public abstract void refreshPdbEntries();
847
848   private int getModelNum(String modelFileName)
849   {
850     String[] mfn = getPdbFile();
851     if (mfn == null)
852     {
853       return -1;
854     }
855     for (int i = 0; i < mfn.length; i++)
856     {
857       if (mfn[i].equalsIgnoreCase(modelFileName))
858       {
859         return i;
860       }
861     }
862     return -1;
863   }
864
865   /**
866    * map between index of model filename returned from getPdbFile and the first
867    * index of models from this file in the viewer. Note - this is not trimmed -
868    * use getPdbFile to get number of unique models.
869    */
870   private int _modelFileNameMap[];
871
872   // ////////////////////////////////
873   // /StructureListener
874   public synchronized String[] getPdbFile()
875   {
876     if (viewer == null)
877     {
878       return new String[0];
879     }
880     // if (modelFileNames == null)
881     // {
882     // Collection<ChimeraModel> chimodels = viewer.getChimeraModels();
883     // _modelFileNameMap = new int[chimodels.size()];
884     // int j = 0;
885     // for (ChimeraModel chimodel : chimodels)
886     // {
887     // String mdlName = chimodel.getModelName();
888     // }
889     // modelFileNames = new String[j];
890     // // System.arraycopy(mset, 0, modelFileNames, 0, j);
891     // }
892
893     return chimmaps.keySet().toArray(
894             modelFileNames = new String[chimmaps.size()]);
895   }
896
897   /**
898    * map from string to applet
899    */
900   public Map getRegistryInfo()
901   {
902     // TODO Auto-generated method stub
903     return null;
904   }
905
906   /**
907    * returns the current sequenceRenderer that should be used to colour the
908    * structures
909    * 
910    * @param alignment
911    * 
912    * @return
913    */
914   public abstract SequenceRenderer getSequenceRenderer(
915           AlignmentViewPanel alignment);
916
917   /**
918    * Highlight the specified atom positions in the structure.
919    * 
920    * @param atomIndex
921    * @param pdbResNum
922    * @param chain
923    * @param pdbfile
924    */
925   @Override
926   public void highlightAtoms(List<AtomSpec> atoms)
927   {
928     for (AtomSpec atom : atoms)
929     {
930       int pdbResNum = atom.getPdbResNum();
931       String chain = atom.getChain();
932       List<ChimeraModel> cms = chimmaps.get(atom.getPdbId());
933       if (cms != null)
934       {
935         int mdlNum = cms.get(0).getModelNumber();
936
937         viewerCommandHistory(false);
938         // viewer.stopListening();
939         if (resetLastRes.length() > 0)
940         {
941           eval.setLength(0);
942           eval.append(resetLastRes.toString() + ";");
943         }
944
945         eval.append("display "); // +modelNum
946
947         resetLastRes.setLength(0);
948         resetLastRes.append("~display ");
949         {
950           eval.append(" #" + (mdlNum));
951           resetLastRes.append(" #" + (mdlNum));
952         }
953         // complete select string
954
955         eval.append(":" + pdbResNum);
956         resetLastRes.append(":" + pdbResNum);
957         if (!chain.equals(" "))
958         {
959           eval.append("." + chain);
960           resetLastRes.append("." + chain);
961         }
962
963         viewer.sendChimeraCommand(eval.toString(), false);
964         viewerCommandHistory(true);
965         // viewer.startListening();
966       }
967     }
968   }
969
970   boolean debug = false;
971
972   private void log(String message)
973   {
974     System.err.println("## Chimera log: " + message);
975   }
976
977   private void viewerCommandHistory(boolean enable)
978   {
979     // log("(Not yet implemented) History "
980     // + ((debug || enable) ? "on" : "off"));
981   }
982
983   public void loadInline(String string)
984   {
985     loadedInline = true;
986     // TODO: re JAL-623
987     // viewer.loadInline(strModel, isAppend);
988     // could do this:
989     // construct fake fullPathName and fileName so we can identify the file
990     // later.
991     // Then, construct pass a reader for the string to Jmol.
992     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
993     // fileName, null, reader, false, null, null, 0);
994     // viewer.openStringInline(string);
995     log("cannot load inline in Chimera, yet");
996   }
997
998   public void mouseOverStructure(int atomIndex, String strInfo)
999   {
1000     // function to parse a mouseOver event from Chimera
1001     //
1002     int pdbResNum;
1003     int alocsep = strInfo.indexOf("^");
1004     int mdlSep = strInfo.indexOf("/");
1005     int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
1006
1007     if (chainSeparator == -1)
1008     {
1009       chainSeparator = strInfo.indexOf(".");
1010       if (mdlSep > -1 && mdlSep < chainSeparator)
1011       {
1012         chainSeparator1 = chainSeparator;
1013         chainSeparator = mdlSep;
1014       }
1015     }
1016     // handle insertion codes
1017     if (alocsep != -1)
1018     {
1019       pdbResNum = Integer.parseInt(strInfo.substring(
1020               strInfo.indexOf("]") + 1, alocsep));
1021
1022     }
1023     else
1024     {
1025       pdbResNum = Integer.parseInt(strInfo.substring(
1026               strInfo.indexOf("]") + 1, chainSeparator));
1027     }
1028     String chainId;
1029
1030     if (strInfo.indexOf(":") > -1)
1031     {
1032       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
1033               strInfo.indexOf("."));
1034     }
1035     else
1036     {
1037       chainId = " ";
1038     }
1039
1040     String pdbfilename = modelFileNames[frameNo]; // default is first or current
1041     // model
1042     if (mdlSep > -1)
1043     {
1044       if (chainSeparator1 == -1)
1045       {
1046         chainSeparator1 = strInfo.indexOf(".", mdlSep);
1047       }
1048       String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
1049               chainSeparator1) : strInfo.substring(mdlSep + 1);
1050       try
1051       {
1052         // recover PDB filename for the model hovered over.
1053         int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
1054                 .intValue() - 1;
1055         while (mnumber < _modelFileNameMap[_mp])
1056         {
1057           _mp--;
1058         }
1059         pdbfilename = modelFileNames[_mp];
1060         if (pdbfilename == null)
1061         {
1062           // pdbfilename = new File(viewer.getModelFileName(mnumber))
1063           // .getAbsolutePath();
1064         }
1065
1066       } catch (Exception e)
1067       {
1068       }
1069       ;
1070     }
1071     if (lastMessage == null || !lastMessage.equals(strInfo))
1072     {
1073       ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
1074     }
1075
1076     lastMessage = strInfo;
1077   }
1078
1079   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
1080   {
1081     /**
1082      * this implements the toggle label behaviour copied from the original
1083      * structure viewer, MCView
1084      */
1085     if (strData != null)
1086     {
1087       System.err.println("Ignoring additional pick data string " + strData);
1088     }
1089     // rewrite these selections for chimera (DNA, RNA and protein)
1090     int chainSeparator = strInfo.indexOf(":");
1091     int p = 0;
1092     if (chainSeparator == -1)
1093     {
1094       chainSeparator = strInfo.indexOf(".");
1095     }
1096
1097     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
1098             chainSeparator);
1099     String mdlString = "";
1100     if ((p = strInfo.indexOf(":")) > -1)
1101     {
1102       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
1103     }
1104
1105     if ((p = strInfo.indexOf("/")) > -1)
1106     {
1107       mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
1108     }
1109     picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
1110             + mdlString + "))";
1111     viewerCommandHistory(false);
1112
1113     if (!atomsPicked.contains(picked))
1114     {
1115       viewer.select(picked);
1116       atomsPicked.add(picked);
1117     }
1118     else
1119     {
1120       viewer.select("not " + picked);
1121       atomsPicked.remove(picked);
1122     }
1123     viewerCommandHistory(true);
1124     // TODO: in application this happens
1125     //
1126     // if (scriptWindow != null)
1127     // {
1128     // scriptWindow.sendConsoleMessage(strInfo);
1129     // scriptWindow.sendConsoleMessage("\n");
1130     // }
1131
1132   }
1133
1134   // incremented every time a load notification is successfully handled -
1135   // lightweight mechanism for other threads to detect when they can start
1136   // referring to new structures.
1137   private long loadNotifiesHandled = 0;
1138
1139   public long getLoadNotifiesHandled()
1140   {
1141     return loadNotifiesHandled;
1142   }
1143
1144   public void notifyFileLoaded(String fullPathName, String fileName2,
1145           String modelName, String errorMsg, int modelParts)
1146   {
1147     if (errorMsg != null)
1148     {
1149       fileLoadingError = errorMsg;
1150       refreshGUI();
1151       return;
1152     }
1153     // TODO: deal sensibly with models loaded inLine:
1154     // modelName will be null, as will fullPathName.
1155
1156     // the rest of this routine ignores the arguments, and simply interrogates
1157     // the Jmol view to find out what structures it contains, and adds them to
1158     // the structure selection manager.
1159     fileLoadingError = null;
1160     String[] oldmodels = modelFileNames;
1161     modelFileNames = null;
1162     chainNames = new ArrayList<String>();
1163     chainFile = new HashMap<String, String>();
1164     boolean notifyLoaded = false;
1165     String[] modelfilenames = getPdbFile();
1166     // first check if we've lost any structures
1167     if (oldmodels != null && oldmodels.length > 0)
1168     {
1169       int oldm = 0;
1170       for (int i = 0; i < oldmodels.length; i++)
1171       {
1172         for (int n = 0; n < modelfilenames.length; n++)
1173         {
1174           if (modelfilenames[n] == oldmodels[i])
1175           {
1176             oldmodels[i] = null;
1177             break;
1178           }
1179         }
1180         if (oldmodels[i] != null)
1181         {
1182           oldm++;
1183         }
1184       }
1185       if (oldm > 0)
1186       {
1187         String[] oldmfn = new String[oldm];
1188         oldm = 0;
1189         for (int i = 0; i < oldmodels.length; i++)
1190         {
1191           if (oldmodels[i] != null)
1192           {
1193             oldmfn[oldm++] = oldmodels[i];
1194           }
1195         }
1196         // deregister the Jmol instance for these structures - we'll add
1197         // ourselves again at the end for the current structure set.
1198         ssm.removeStructureViewerListener(this, oldmfn);
1199       }
1200     }
1201
1202     // register ourselves as a listener and notify the gui that it needs to
1203     // update itself.
1204     ssm.addStructureViewerListener(this);
1205
1206     if (notifyLoaded)
1207     {
1208       FeatureRenderer fr = getFeatureRenderer(null);
1209       if (fr != null)
1210       {
1211         fr.featuresAdded();
1212       }
1213       refreshGUI();
1214       loadNotifiesHandled++;
1215     }
1216     setLoadingFromArchive(false);
1217   }
1218
1219   public void setJalviewColourScheme(ColourSchemeI cs)
1220   {
1221     colourBySequence = false;
1222
1223     if (cs == null)
1224     {
1225       return;
1226     }
1227
1228     String res;
1229     int index;
1230     Color col;
1231     // Chimera expects RBG values in the range 0-1
1232     final double normalise = 255D;
1233     viewerCommandHistory(false);
1234     // TODO: Switch between nucleotide or aa selection expressions
1235     Enumeration en = ResidueProperties.aa3Hash.keys();
1236     StringBuilder command = new StringBuilder(128);
1237     command.append("color white;");
1238     while (en.hasMoreElements())
1239     {
1240       res = en.nextElement().toString();
1241       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
1242       if (index > 20)
1243       {
1244         continue;
1245       }
1246
1247       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
1248       command.append("color " + col.getRed() / normalise + ","
1249               + col.getGreen() / normalise + "," + col.getBlue()
1250               / normalise + " ::" + res + ";");
1251     }
1252
1253     evalStateCommand(command.toString(),false);
1254     viewerCommandHistory(true);
1255   }
1256
1257   /**
1258    * called when the binding thinks the UI needs to be refreshed after a Chimera
1259    * state change. this could be because structures were loaded, or because an
1260    * error has occurred.
1261    */
1262   public abstract void refreshGUI();
1263
1264   public void componentResized(ComponentEvent e)
1265   {
1266
1267   }
1268
1269   public void componentMoved(ComponentEvent e)
1270   {
1271
1272   }
1273
1274   public void componentShown(ComponentEvent e)
1275   {
1276   }
1277
1278   public void componentHidden(ComponentEvent e)
1279   {
1280   }
1281
1282   public void setLoadingFromArchive(boolean loadingFromArchive)
1283   {
1284     this.loadingFromArchive = loadingFromArchive;
1285   }
1286
1287   /**
1288    * 
1289    * @return true if Chimeral is still restoring state or loading is still going
1290    *         on (see setFinsihedLoadingFromArchive)
1291    */
1292   public boolean isLoadingFromArchive()
1293   {
1294     return loadingFromArchive && !loadingFinished;
1295   }
1296
1297   /**
1298    * modify flag which controls if sequence colouring events are honoured by the
1299    * binding. Should be true for normal operation
1300    * 
1301    * @param finishedLoading
1302    */
1303   public void setFinishedLoadingFromArchive(boolean finishedLoading)
1304   {
1305     loadingFinished = finishedLoading;
1306   }
1307
1308   /**
1309    * Send the Chimera 'background solid <color>" command.
1310    * 
1311    * @see https
1312    *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background
1313    *      .html
1314    * @param col
1315    */
1316   public void setBackgroundColour(Color col)
1317   {
1318     viewerCommandHistory(false);
1319     double normalise = 255D;
1320     final String command = "background solid " + col.getRed() / normalise + ","
1321             + col.getGreen() / normalise + "," + col.getBlue()
1322             / normalise + ";";
1323     viewer.sendChimeraCommand(command, false);
1324     viewerCommandHistory(true);
1325   }
1326
1327   /**
1328    * add structures and any known sequence associations
1329    * 
1330    * @returns the pdb entries added to the current set.
1331    */
1332   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
1333           SequenceI[][] seq, String[][] chns)
1334   {
1335     List<PDBEntry> v = new ArrayList<PDBEntry>();
1336     List<int[]> rtn = new ArrayList<int[]>();
1337     for (int i = 0; i < pdbentry.length; i++)
1338     {
1339       v.add(pdbentry[i]);
1340     }
1341     for (int i = 0; i < pdbe.length; i++)
1342     {
1343       int r = v.indexOf(pdbe[i]);
1344       if (r == -1 || r >= pdbentry.length)
1345       {
1346         rtn.add(new int[]
1347         { v.size(), i });
1348         v.add(pdbe[i]);
1349       }
1350       else
1351       {
1352         // just make sure the sequence/chain entries are all up to date
1353         addSequenceAndChain(r, seq[i], chns[i]);
1354       }
1355     }
1356     pdbe = v.toArray(new PDBEntry[v.size()]);
1357     pdbentry = pdbe;
1358     if (rtn.size() > 0)
1359     {
1360       // expand the tied sequence[] and string[] arrays
1361       SequenceI[][] sqs = new SequenceI[pdbentry.length][];
1362       String[][] sch = new String[pdbentry.length][];
1363       System.arraycopy(sequence, 0, sqs, 0, sequence.length);
1364       System.arraycopy(chains, 0, sch, 0, this.chains.length);
1365       sequence = sqs;
1366       chains = sch;
1367       pdbe = new PDBEntry[rtn.size()];
1368       for (int r = 0; r < pdbe.length; r++)
1369       {
1370         int[] stri = (rtn.get(r));
1371         // record the pdb file as a new addition
1372         pdbe[r] = pdbentry[stri[0]];
1373         // and add the new sequence/chain entries
1374         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
1375       }
1376     }
1377     else
1378     {
1379       pdbe = null;
1380     }
1381     return pdbe;
1382   }
1383
1384   /**
1385    * Adds sequences to the pe'th pdbentry's sequence set.
1386    * 
1387    * @param pe
1388    * @param seq
1389    */
1390   public void addSequence(int pe, SequenceI[] seq)
1391   {
1392     addSequenceAndChain(pe, seq, null);
1393   }
1394
1395   private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
1396   {
1397     if (pe < 0 || pe >= pdbentry.length)
1398     {
1399       throw new Error(MessageManager.formatMessage(
1400               "error.implementation_error_no_pdbentry_from_index",
1401               new Object[]
1402               { Integer.valueOf(pe).toString() }));
1403     }
1404     final String nullChain = "TheNullChain";
1405     List<SequenceI> s = new ArrayList<SequenceI>();
1406     List<String> c = new ArrayList<String>();
1407     if (chains == null)
1408     {
1409       chains = new String[pdbentry.length][];
1410     }
1411     if (sequence[pe] != null)
1412     {
1413       for (int i = 0; i < sequence[pe].length; i++)
1414       {
1415         s.add(sequence[pe][i]);
1416         if (chains[pe] != null)
1417         {
1418           if (i < chains[pe].length)
1419           {
1420             c.add(chains[pe][i]);
1421           }
1422           else
1423           {
1424             c.add(nullChain);
1425           }
1426         }
1427         else
1428         {
1429           if (tchain != null && tchain.length > 0)
1430           {
1431             c.add(nullChain);
1432           }
1433         }
1434       }
1435     }
1436     for (int i = 0; i < seq.length; i++)
1437     {
1438       if (!s.contains(seq[i]))
1439       {
1440         s.add(seq[i]);
1441         if (tchain != null && i < tchain.length)
1442         {
1443           c.add(tchain[i] == null ? nullChain : tchain[i]);
1444         }
1445       }
1446     }
1447     SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
1448     sequence[pe] = tmp;
1449     if (c.size() > 0)
1450     {
1451       String[] tch = c.toArray(new String[c.size()]);
1452       for (int i = 0; i < tch.length; i++)
1453       {
1454         if (tch[i] == nullChain)
1455         {
1456           tch[i] = null;
1457         }
1458       }
1459       chains[pe] = tch;
1460     }
1461     else
1462     {
1463       chains[pe] = null;
1464     }
1465   }
1466
1467   /**
1468    * 
1469    * @param pdbfile
1470    * @return text report of alignment between pdbfile and any associated
1471    *         alignment sequences
1472    */
1473   public String printMapping(String pdbfile)
1474   {
1475     return ssm.printMapping(pdbfile);
1476   }
1477
1478 }