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