Merge branch 'feature/JAL-2664' into feature/JAL-2527
[jalview.git] / src / jalview / structure / StructureSelectionManager.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.structure;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.api.StructureSelectionManagerProvider;
25 import jalview.commands.CommandI;
26 import jalview.commands.EditCommand;
27 import jalview.commands.OrderCommand;
28 import jalview.datamodel.AlignedCodonFrame;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.Annotation;
32 import jalview.datamodel.HiddenColumns;
33 import jalview.datamodel.PDBEntry;
34 import jalview.datamodel.SearchResults;
35 import jalview.datamodel.SearchResultsI;
36 import jalview.datamodel.SequenceI;
37 import jalview.ext.jmol.JmolParser;
38 import jalview.gui.IProgressIndicator;
39 import jalview.io.AppletFormatAdapter;
40 import jalview.io.DataSourceType;
41 import jalview.io.StructureFile;
42 import jalview.util.MappingUtils;
43 import jalview.util.MessageManager;
44 import jalview.ws.sifts.SiftsClient;
45 import jalview.ws.sifts.SiftsException;
46 import jalview.ws.sifts.SiftsSettings;
47
48 import java.io.PrintStream;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.Enumeration;
53 import java.util.HashMap;
54 import java.util.IdentityHashMap;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Vector;
58
59 import MCview.Atom;
60 import MCview.PDBChain;
61 import MCview.PDBfile;
62
63 public class StructureSelectionManager
64 {
65   public final static String NEWLINE = System.lineSeparator();
66
67   static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
68
69   private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
70
71   private boolean processSecondaryStructure = false;
72
73   private boolean secStructServices = false;
74
75   private boolean addTempFacAnnot = false;
76
77   private IProgressIndicator progressIndicator;
78
79   private SiftsClient siftsClient = null;
80
81   private long progressSessionId;
82
83   /*
84    * Set of any registered mappings between (dataset) sequences.
85    */
86   private List<AlignedCodonFrame> seqmappings = new ArrayList<AlignedCodonFrame>();
87
88   private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
89
90   private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
91
92   /**
93    * @return true if will try to use external services for processing secondary
94    *         structure
95    */
96   public boolean isSecStructServices()
97   {
98     return secStructServices;
99   }
100
101   /**
102    * control use of external services for processing secondary structure
103    * 
104    * @param secStructServices
105    */
106   public void setSecStructServices(boolean secStructServices)
107   {
108     this.secStructServices = secStructServices;
109   }
110
111   /**
112    * flag controlling addition of any kind of structural annotation
113    * 
114    * @return true if temperature factor annotation will be added
115    */
116   public boolean isAddTempFacAnnot()
117   {
118     return addTempFacAnnot;
119   }
120
121   /**
122    * set flag controlling addition of structural annotation
123    * 
124    * @param addTempFacAnnot
125    */
126   public void setAddTempFacAnnot(boolean addTempFacAnnot)
127   {
128     this.addTempFacAnnot = addTempFacAnnot;
129   }
130
131   /**
132    * 
133    * @return if true, the structure manager will attempt to add secondary
134    *         structure lines for unannotated sequences
135    */
136
137   public boolean isProcessSecondaryStructure()
138   {
139     return processSecondaryStructure;
140   }
141
142   /**
143    * Control whether structure manager will try to annotate mapped sequences
144    * with secondary structure from PDB data.
145    * 
146    * @param enable
147    */
148   public void setProcessSecondaryStructure(boolean enable)
149   {
150     processSecondaryStructure = enable;
151   }
152
153   /**
154    * debug function - write all mappings to stdout
155    */
156   public void reportMapping()
157   {
158     if (mappings.isEmpty())
159     {
160       System.err.println("reportMapping: No PDB/Sequence mappings.");
161     }
162     else
163     {
164       System.err.println(
165               "reportMapping: There are " + mappings.size() + " mappings.");
166       int i = 0;
167       for (StructureMapping sm : mappings)
168       {
169         System.err.println("mapping " + i++ + " : " + sm.pdbfile);
170       }
171     }
172   }
173
174   /**
175    * map between the PDB IDs (or structure identifiers) used by Jalview and the
176    * absolute filenames for PDB data that corresponds to it
177    */
178   Map<String, String> pdbIdFileName = new HashMap<String, String>();
179
180   Map<String, String> pdbFileNameId = new HashMap<String, String>();
181
182   public void registerPDBFile(String idForFile, String absoluteFile)
183   {
184     pdbIdFileName.put(idForFile, absoluteFile);
185     pdbFileNameId.put(absoluteFile, idForFile);
186   }
187
188   public String findIdForPDBFile(String idOrFile)
189   {
190     String id = pdbFileNameId.get(idOrFile);
191     return id;
192   }
193
194   public String findFileForPDBId(String idOrFile)
195   {
196     String id = pdbIdFileName.get(idOrFile);
197     return id;
198   }
199
200   public boolean isPDBFileRegistered(String idOrFile)
201   {
202     return pdbFileNameId.containsKey(idOrFile)
203             || pdbIdFileName.containsKey(idOrFile);
204   }
205
206   private static StructureSelectionManager nullProvider = null;
207
208   public static StructureSelectionManager getStructureSelectionManager(
209           StructureSelectionManagerProvider context)
210   {
211     if (context == null)
212     {
213       if (nullProvider == null)
214       {
215         if (instances != null)
216         {
217           throw new Error(MessageManager.getString(
218                   "error.implementation_error_structure_selection_manager_null"),
219                   new NullPointerException(MessageManager
220                           .getString("exception.ssm_context_is_null")));
221         }
222         else
223         {
224           nullProvider = new StructureSelectionManager();
225         }
226         return nullProvider;
227       }
228     }
229     if (instances == null)
230     {
231       instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
232     }
233     StructureSelectionManager instance = instances.get(context);
234     if (instance == null)
235     {
236       if (nullProvider != null)
237       {
238         instance = nullProvider;
239       }
240       else
241       {
242         instance = new StructureSelectionManager();
243       }
244       instances.put(context, instance);
245     }
246     return instance;
247   }
248
249   /**
250    * flag controlling whether SeqMappings are relayed from received sequence
251    * mouse over events to other sequences
252    */
253   boolean relaySeqMappings = true;
254
255   /**
256    * Enable or disable relay of seqMapping events to other sequences. You might
257    * want to do this if there are many sequence mappings and the host computer
258    * is slow
259    * 
260    * @param relay
261    */
262   public void setRelaySeqMappings(boolean relay)
263   {
264     relaySeqMappings = relay;
265   }
266
267   /**
268    * get the state of the relay seqMappings flag.
269    * 
270    * @return true if sequence mouse overs are being relayed to other mapped
271    *         sequences
272    */
273   public boolean isRelaySeqMappingsEnabled()
274   {
275     return relaySeqMappings;
276   }
277
278   Vector listeners = new Vector();
279
280   /**
281    * register a listener for alignment sequence mouseover events
282    * 
283    * @param svl
284    */
285   public void addStructureViewerListener(Object svl)
286   {
287     if (!listeners.contains(svl))
288     {
289       listeners.addElement(svl);
290     }
291   }
292
293   /**
294    * Returns the file name for a mapped PDB id (or null if not mapped).
295    * 
296    * @param pdbid
297    * @return
298    */
299   public String alreadyMappedToFile(String pdbid)
300   {
301     for (StructureMapping sm : mappings)
302     {
303       if (sm.getPdbId().equals(pdbid))
304       {
305         return sm.pdbfile;
306       }
307     }
308     return null;
309   }
310
311   /**
312    * Import structure data and register a structure mapping for broadcasting
313    * colouring, mouseovers and selection events (convenience wrapper).
314    * 
315    * @param sequence
316    *          - one or more sequences to be mapped to pdbFile
317    * @param targetChains
318    *          - optional chain specification for mapping each sequence to pdb
319    *          (may be nill, individual elements may be nill)
320    * @param pdbFile
321    *          - structure data resource
322    * @param protocol
323    *          - how to resolve data from resource
324    * @return null or the structure data parsed as a pdb file
325    */
326   synchronized public StructureFile setMapping(SequenceI[] sequence,
327           String[] targetChains, String pdbFile, DataSourceType protocol)
328   {
329     return setMapping(true, sequence, targetChains, pdbFile, protocol);
330   }
331
332   /**
333    * create sequence structure mappings between each sequence and the given
334    * pdbFile (retrieved via the given protocol).
335    * 
336    * @param forStructureView
337    *          when true, record the mapping for use in mouseOvers
338    * 
339    * @param sequenceArray
340    *          - one or more sequences to be mapped to pdbFile
341    * @param targetChainIds
342    *          - optional chain specification for mapping each sequence to pdb
343    *          (may be nill, individual elements may be nill)
344    * @param pdbFile
345    *          - structure data resource
346    * @param sourceType
347    *          - how to resolve data from resource
348    * @return null or the structure data parsed as a pdb file
349    */
350   synchronized public StructureFile setMapping(boolean forStructureView,
351           SequenceI[] sequenceArray, String[] targetChainIds,
352           String pdbFile, DataSourceType sourceType)
353   {
354     /*
355      * There will be better ways of doing this in the future, for now we'll use
356      * the tried and tested MCview pdb mapping
357      */
358     boolean parseSecStr = processSecondaryStructure;
359     if (isPDBFileRegistered(pdbFile))
360     {
361       for (SequenceI sq : sequenceArray)
362       {
363         SequenceI ds = sq;
364         while (ds.getDatasetSequence() != null)
365         {
366           ds = ds.getDatasetSequence();
367         }
368         ;
369         if (ds.getAnnotation() != null)
370         {
371           for (AlignmentAnnotation ala : ds.getAnnotation())
372           {
373             // false if any annotation present from this structure
374             // JBPNote this fails for jmol/chimera view because the *file* is
375             // passed, not the structure data ID -
376             if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
377             {
378               parseSecStr = false;
379             }
380           }
381         }
382       }
383     }
384     StructureFile pdb = null;
385     boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
386     try
387     {
388       sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
389       pdb = new JmolParser(pdbFile, sourceType);
390
391       if (pdb.getId() != null && pdb.getId().trim().length() > 0
392               && DataSourceType.FILE == sourceType)
393       {
394         registerPDBFile(pdb.getId().trim(), pdbFile);
395       }
396       // if PDBId is unavailable then skip SIFTS mapping execution path
397       isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable();
398
399     } catch (Exception ex)
400     {
401       ex.printStackTrace();
402       return null;
403     }
404
405     try
406     {
407       if (isMapUsingSIFTs)
408       {
409         siftsClient = new SiftsClient(pdb);
410       }
411     } catch (SiftsException e)
412     {
413       isMapUsingSIFTs = false;
414       e.printStackTrace();
415     }
416
417     String targetChainId;
418     for (int s = 0; s < sequenceArray.length; s++)
419     {
420       boolean infChain = true;
421       final SequenceI seq = sequenceArray[s];
422       SequenceI ds = seq;
423       while (ds.getDatasetSequence() != null)
424       {
425         ds = ds.getDatasetSequence();
426       }
427
428       if (targetChainIds != null && targetChainIds[s] != null)
429       {
430         infChain = false;
431         targetChainId = targetChainIds[s];
432       }
433       else if (seq.getName().indexOf("|") > -1)
434       {
435         targetChainId = seq.getName()
436                 .substring(seq.getName().lastIndexOf("|") + 1);
437         if (targetChainId.length() > 1)
438         {
439           if (targetChainId.trim().length() == 0)
440           {
441             targetChainId = " ";
442           }
443           else
444           {
445             // not a valid chain identifier
446             targetChainId = "";
447           }
448         }
449       }
450       else
451       {
452         targetChainId = "";
453       }
454
455       /*
456        * Attempt pairwise alignment of the sequence with each chain in the PDB,
457        * and remember the highest scoring chain
458        */
459       float max = -10;
460       AlignSeq maxAlignseq = null;
461       String maxChainId = " ";
462       PDBChain maxChain = null;
463       boolean first = true;
464       for (PDBChain chain : pdb.getChains())
465       {
466         if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
467                 && !infChain)
468         {
469           continue; // don't try to map chains don't match.
470         }
471         // TODO: correctly determine sequence type for mixed na/peptide
472         // structures
473         final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
474         AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
475                 type);
476         // equivalent to:
477         // AlignSeq as = new AlignSeq(sequence[s], chain.sequence, type);
478         // as.calcScoreMatrix();
479         // as.traceAlignment();
480
481         if (first || as.maxscore > max
482                 || (as.maxscore == max && chain.id.equals(targetChainId)))
483         {
484           first = false;
485           maxChain = chain;
486           max = as.maxscore;
487           maxAlignseq = as;
488           maxChainId = chain.id;
489         }
490       }
491       if (maxChain == null)
492       {
493         continue;
494       }
495
496       if (sourceType == DataSourceType.PASTE)
497       {
498         pdbFile = "INLINE" + pdb.getId();
499       }
500
501       List<StructureMapping> seqToStrucMapping = new ArrayList<StructureMapping>();
502       if (isMapUsingSIFTs && seq.isProtein())
503       {
504         setProgressBar(null);
505         setProgressBar(MessageManager
506                 .getString("status.obtaining_mapping_with_sifts"));
507         jalview.datamodel.Mapping sqmpping = maxAlignseq
508                 .getMappingFromS1(false);
509         if (targetChainId != null && !targetChainId.trim().isEmpty())
510         {
511           StructureMapping siftsMapping;
512           try
513           {
514             siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
515                     pdb, maxChain, sqmpping, maxAlignseq);
516             seqToStrucMapping.add(siftsMapping);
517             maxChain.makeExactMapping(maxAlignseq, seq);
518             maxChain.transferRESNUMFeatures(seq, null);// FIXME: is this
519                                                        // "IEA:SIFTS" ?
520             maxChain.transferResidueAnnotation(siftsMapping, sqmpping);
521             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
522
523           } catch (SiftsException e)
524           {
525             // fall back to NW alignment
526             System.err.println(e.getMessage());
527             StructureMapping nwMapping = getNWMappings(seq, pdbFile,
528                     targetChainId, maxChain, pdb, maxAlignseq);
529             seqToStrucMapping.add(nwMapping);
530             maxChain.makeExactMapping(maxAlignseq, seq);
531             maxChain.transferRESNUMFeatures(seq, null); // FIXME: is this
532                                                         // "IEA:Jalview" ?
533             maxChain.transferResidueAnnotation(nwMapping, sqmpping);
534             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
535           }
536         }
537         else
538         {
539           List<StructureMapping> foundSiftsMappings = new ArrayList<StructureMapping>();
540           for (PDBChain chain : pdb.getChains())
541           {
542             try
543             {
544               StructureMapping siftsMapping = getStructureMapping(seq,
545                       pdbFile, chain.id, pdb, chain, sqmpping, maxAlignseq);
546               foundSiftsMappings.add(siftsMapping);
547             } catch (SiftsException e)
548             {
549               System.err.println(e.getMessage());
550             }
551           }
552           if (!foundSiftsMappings.isEmpty())
553           {
554             seqToStrucMapping.addAll(foundSiftsMappings);
555             maxChain.makeExactMapping(maxAlignseq, seq);
556             maxChain.transferRESNUMFeatures(seq, null);// FIXME: is this
557                                                        // "IEA:SIFTS" ?
558             maxChain.transferResidueAnnotation(foundSiftsMappings.get(0),
559                     sqmpping);
560             ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
561           }
562           else
563           {
564             StructureMapping nwMapping = getNWMappings(seq, pdbFile,
565                     maxChainId, maxChain, pdb, maxAlignseq);
566             seqToStrucMapping.add(nwMapping);
567             maxChain.transferRESNUMFeatures(seq, null); // FIXME: is this
568                                                         // "IEA:Jalview" ?
569             maxChain.transferResidueAnnotation(nwMapping, sqmpping);
570             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
571           }
572         }
573       }
574       else
575       {
576         setProgressBar(null);
577         setProgressBar(MessageManager
578                 .getString("status.obtaining_mapping_with_nw_alignment"));
579         StructureMapping nwMapping = getNWMappings(seq, pdbFile, maxChainId,
580                 maxChain, pdb, maxAlignseq);
581         seqToStrucMapping.add(nwMapping);
582         ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
583
584       }
585
586       if (forStructureView)
587       {
588         mappings.addAll(seqToStrucMapping);
589       }
590     }
591     return pdb;
592   }
593
594   public void addStructureMapping(StructureMapping sm)
595   {
596     mappings.add(sm);
597   }
598
599   /**
600    * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
601    * uniprot or PDB
602    * 
603    * @param seq
604    * @param pdbFile
605    * @param targetChainId
606    * @param pdb
607    * @param maxChain
608    * @param sqmpping
609    * @param maxAlignseq
610    * @return
611    * @throws SiftsException
612    */
613   private StructureMapping getStructureMapping(SequenceI seq,
614           String pdbFile, String targetChainId, StructureFile pdb,
615           PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
616           AlignSeq maxAlignseq) throws SiftsException
617   {
618     StructureMapping curChainMapping = siftsClient
619             .getSiftsStructureMapping(seq, pdbFile, targetChainId);
620     try
621     {
622       PDBChain chain = pdb.findChain(targetChainId);
623       if (chain != null)
624       {
625         chain.transferResidueAnnotation(curChainMapping, sqmpping);
626       }
627     } catch (Exception e)
628     {
629       e.printStackTrace();
630     }
631     return curChainMapping;
632   }
633
634   private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
635           String maxChainId, PDBChain maxChain, StructureFile pdb,
636           AlignSeq maxAlignseq)
637   {
638     final StringBuilder mappingDetails = new StringBuilder(128);
639     mappingDetails.append(NEWLINE)
640             .append("Sequence \u27f7 Structure mapping details");
641     mappingDetails.append(NEWLINE);
642     mappingDetails
643             .append("Method: inferred with Needleman & Wunsch alignment");
644     mappingDetails.append(NEWLINE).append("PDB Sequence is :")
645             .append(NEWLINE).append("Sequence = ")
646             .append(maxChain.sequence.getSequenceAsString());
647     mappingDetails.append(NEWLINE).append("No of residues = ")
648             .append(maxChain.residues.size()).append(NEWLINE)
649             .append(NEWLINE);
650     PrintStream ps = new PrintStream(System.out)
651     {
652       @Override
653       public void print(String x)
654       {
655         mappingDetails.append(x);
656       }
657
658       @Override
659       public void println()
660       {
661         mappingDetails.append(NEWLINE);
662       }
663     };
664
665     maxAlignseq.printAlignment(ps);
666
667     mappingDetails.append(NEWLINE).append("PDB start/end ");
668     mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
669             .append(" ");
670     mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
671     mappingDetails.append(NEWLINE).append("SEQ start/end ");
672     mappingDetails
673             .append(String
674                     .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
675             .append(" ");
676     mappingDetails.append(
677             String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
678     mappingDetails.append(NEWLINE);
679     maxChain.makeExactMapping(maxAlignseq, seq);
680     jalview.datamodel.Mapping sqmpping = maxAlignseq
681             .getMappingFromS1(false);
682     maxChain.transferRESNUMFeatures(seq, null);
683
684     HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
685     int resNum = -10000;
686     int index = 0;
687     char insCode = ' ';
688
689     do
690     {
691       Atom tmp = maxChain.atoms.elementAt(index);
692       if ((resNum != tmp.resNumber || insCode != tmp.insCode)
693               && tmp.alignmentMapping != -1)
694       {
695         resNum = tmp.resNumber;
696         insCode = tmp.insCode;
697         if (tmp.alignmentMapping >= -1)
698         {
699           mapping.put(tmp.alignmentMapping + 1,
700                   new int[]
701                   { tmp.resNumber, tmp.atomIndex });
702         }
703       }
704
705       index++;
706     } while (index < maxChain.atoms.size());
707
708     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
709             pdb.getId(), maxChainId, mapping, mappingDetails.toString());
710     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
711     return nwMapping;
712   }
713
714   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
715   {
716     listeners.removeElement(svl);
717     if (svl instanceof SequenceListener)
718     {
719       for (int i = 0; i < listeners.size(); i++)
720       {
721         if (listeners.elementAt(i) instanceof StructureListener)
722         {
723           ((StructureListener) listeners.elementAt(i))
724                   .releaseReferences(svl);
725         }
726       }
727     }
728
729     if (pdbfiles == null)
730     {
731       return;
732     }
733
734     /*
735      * Remove mappings to the closed listener's PDB files, but first check if
736      * another listener is still interested
737      */
738     List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
739
740     StructureListener sl;
741     for (int i = 0; i < listeners.size(); i++)
742     {
743       if (listeners.elementAt(i) instanceof StructureListener)
744       {
745         sl = (StructureListener) listeners.elementAt(i);
746         for (String pdbfile : sl.getStructureFiles())
747         {
748           pdbs.remove(pdbfile);
749         }
750       }
751     }
752
753     /*
754      * Rebuild the mappings set, retaining only those which are for 'other' PDB
755      * files
756      */
757     if (pdbs.size() > 0)
758     {
759       List<StructureMapping> tmp = new ArrayList<StructureMapping>();
760       for (StructureMapping sm : mappings)
761       {
762         if (!pdbs.contains(sm.pdbfile))
763         {
764           tmp.add(sm);
765         }
766       }
767
768       mappings = tmp;
769     }
770   }
771
772   /**
773    * Propagate mouseover of a single position in a structure
774    * 
775    * @param pdbResNum
776    * @param chain
777    * @param pdbfile
778    */
779   public void mouseOverStructure(int pdbResNum, String chain,
780           String pdbfile)
781   {
782     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
783     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
784     mouseOverStructure(atoms);
785   }
786
787   /**
788    * Propagate mouseover or selection of multiple positions in a structure
789    * 
790    * @param atoms
791    */
792   public void mouseOverStructure(List<AtomSpec> atoms)
793   {
794     if (listeners == null)
795     {
796       // old or prematurely sent event
797       return;
798     }
799     boolean hasSequenceListener = false;
800     for (int i = 0; i < listeners.size(); i++)
801     {
802       if (listeners.elementAt(i) instanceof SequenceListener)
803       {
804         hasSequenceListener = true;
805       }
806     }
807     if (!hasSequenceListener)
808     {
809       return;
810     }
811
812     SearchResultsI results = findAlignmentPositionsForStructurePositions(
813             atoms);
814     for (Object li : listeners)
815     {
816       if (li instanceof SequenceListener)
817       {
818         ((SequenceListener) li).highlightSequence(results);
819       }
820     }
821   }
822
823   /**
824    * Constructs a SearchResults object holding regions (if any) in the Jalview
825    * alignment which have a mapping to the structure viewer positions in the
826    * supplied list
827    * 
828    * @param atoms
829    * @return
830    */
831   public SearchResultsI findAlignmentPositionsForStructurePositions(
832           List<AtomSpec> atoms)
833   {
834     SearchResultsI results = new SearchResults();
835     for (AtomSpec atom : atoms)
836     {
837       SequenceI lastseq = null;
838       int lastipos = -1;
839       for (StructureMapping sm : mappings)
840       {
841         if (sm.pdbfile.equals(atom.getPdbFile())
842                 && sm.pdbchain.equals(atom.getChain()))
843         {
844           int indexpos = sm.getSeqPos(atom.getPdbResNum());
845           if (lastipos != indexpos && lastseq != sm.sequence)
846           {
847             results.addResult(sm.sequence, indexpos, indexpos);
848             lastipos = indexpos;
849             lastseq = sm.sequence;
850             // construct highlighted sequence list
851             for (AlignedCodonFrame acf : seqmappings)
852             {
853               acf.markMappedRegion(sm.sequence, indexpos, results);
854             }
855           }
856         }
857       }
858     }
859     return results;
860   }
861
862   /**
863    * highlight regions associated with a position (indexpos) in seq
864    * 
865    * @param seq
866    *          the sequence that the mouse over occurred on
867    * @param indexpos
868    *          the absolute position being mouseovered in seq (0 to seq.length())
869    * @param seqPos
870    *          the sequence position (if -1, seq.findPosition is called to
871    *          resolve the residue number)
872    */
873   public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
874           VamsasSource source)
875   {
876     boolean hasSequenceListeners = handlingVamsasMo
877             || !seqmappings.isEmpty();
878     SearchResultsI results = null;
879     if (seqPos == -1)
880     {
881       seqPos = seq.findPosition(indexpos);
882     }
883     for (int i = 0; i < listeners.size(); i++)
884     {
885       Object listener = listeners.elementAt(i);
886       if (listener == source)
887       {
888         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
889         // Temporary fudge with SequenceListener.getVamsasSource()
890         continue;
891       }
892       if (listener instanceof StructureListener)
893       {
894         highlightStructure((StructureListener) listener, seq, seqPos);
895       }
896       else
897       {
898         if (listener instanceof SequenceListener)
899         {
900           final SequenceListener seqListener = (SequenceListener) listener;
901           if (hasSequenceListeners
902                   && seqListener.getVamsasSource() != source)
903           {
904             if (relaySeqMappings)
905             {
906               if (results == null)
907               {
908                 results = MappingUtils.buildSearchResults(seq, seqPos,
909                         seqmappings);
910               }
911               if (handlingVamsasMo)
912               {
913                 results.addResult(seq, seqPos, seqPos);
914
915               }
916               if (!results.isEmpty())
917               {
918                 seqListener.highlightSequence(results);
919               }
920             }
921           }
922         }
923         else if (listener instanceof VamsasListener && !handlingVamsasMo)
924         {
925           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
926                   source);
927         }
928         else if (listener instanceof SecondaryStructureListener)
929         {
930           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
931                   indexpos, seqPos);
932         }
933       }
934     }
935   }
936
937   /**
938    * Send suitable messages to a StructureListener to highlight atoms
939    * corresponding to the given sequence position(s)
940    * 
941    * @param sl
942    * @param seq
943    * @param positions
944    */
945   public void highlightStructure(StructureListener sl, SequenceI seq,
946           int... positions)
947   {
948     if (!sl.isListeningFor(seq))
949     {
950       return;
951     }
952     int atomNo;
953     List<AtomSpec> atoms = new ArrayList<AtomSpec>();
954     for (StructureMapping sm : mappings)
955     {
956       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
957               || (sm.sequence.getDatasetSequence() != null && sm.sequence
958                       .getDatasetSequence() == seq.getDatasetSequence()))
959       {
960         for (int index : positions)
961         {
962           atomNo = sm.getAtomNum(index);
963
964           if (atomNo > 0)
965           {
966             atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
967                     sm.getPDBResNum(index), atomNo));
968           }
969         }
970       }
971     }
972     sl.highlightAtoms(atoms);
973   }
974
975   /**
976    * true if a mouse over event from an external (ie Vamsas) source is being
977    * handled
978    */
979   boolean handlingVamsasMo = false;
980
981   long lastmsg = 0;
982
983   /**
984    * as mouseOverSequence but only route event to SequenceListeners
985    * 
986    * @param sequenceI
987    * @param position
988    *          in an alignment sequence
989    */
990   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
991           VamsasSource source)
992   {
993     handlingVamsasMo = true;
994     long msg = sequenceI.hashCode() * (1 + position);
995     if (lastmsg != msg)
996     {
997       lastmsg = msg;
998       mouseOverSequence(sequenceI, position, -1, source);
999     }
1000     handlingVamsasMo = false;
1001   }
1002
1003   public Annotation[] colourSequenceFromStructure(SequenceI seq,
1004           String pdbid)
1005   {
1006     return null;
1007     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
1008     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
1009     /*
1010      * Annotation [] annotations = new Annotation[seq.getLength()];
1011      * 
1012      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
1013      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
1014      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
1015      * 
1016      * for (int j = 0; j < mappings.length; j++) {
1017      * 
1018      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
1019      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
1020      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
1021      * "+mappings[j].pdbfile);
1022      * 
1023      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
1024      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
1025      * 
1026      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
1027      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
1028      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
1029      * mappings[j].pdbfile); }
1030      * 
1031      * annotations[index] = new Annotation("X",null,' ',0,col); } return
1032      * annotations; } } } }
1033      * 
1034      * return annotations;
1035      */
1036   }
1037
1038   public void structureSelectionChanged()
1039   {
1040   }
1041
1042   public void sequenceSelectionChanged()
1043   {
1044   }
1045
1046   public void sequenceColoursChanged(Object source)
1047   {
1048     StructureListener sl;
1049     for (int i = 0; i < listeners.size(); i++)
1050     {
1051       if (listeners.elementAt(i) instanceof StructureListener)
1052       {
1053         sl = (StructureListener) listeners.elementAt(i);
1054         sl.updateColours(source);
1055       }
1056     }
1057   }
1058
1059   public StructureMapping[] getMapping(String pdbfile)
1060   {
1061     List<StructureMapping> tmp = new ArrayList<StructureMapping>();
1062     for (StructureMapping sm : mappings)
1063     {
1064       if (sm.pdbfile.equals(pdbfile))
1065       {
1066         tmp.add(sm);
1067       }
1068     }
1069     return tmp.toArray(new StructureMapping[tmp.size()]);
1070   }
1071
1072   /**
1073    * Returns a readable description of all mappings for the given pdbfile to any
1074    * of the given sequences
1075    * 
1076    * @param pdbfile
1077    * @param seqs
1078    * @return
1079    */
1080   public String printMappings(String pdbfile, List<SequenceI> seqs)
1081   {
1082     if (pdbfile == null || seqs == null || seqs.isEmpty())
1083     {
1084       return "";
1085     }
1086
1087     StringBuilder sb = new StringBuilder(64);
1088     for (StructureMapping sm : mappings)
1089     {
1090       if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
1091       {
1092         sb.append(sm.mappingDetails);
1093         sb.append(NEWLINE);
1094         // separator makes it easier to read multiple mappings
1095         sb.append("=====================");
1096         sb.append(NEWLINE);
1097       }
1098     }
1099     sb.append(NEWLINE);
1100
1101     return sb.toString();
1102   }
1103
1104   /**
1105    * Remove the given mapping
1106    * 
1107    * @param acf
1108    */
1109   public void deregisterMapping(AlignedCodonFrame acf)
1110   {
1111     if (acf != null)
1112     {
1113       boolean removed = seqmappings.remove(acf);
1114       if (removed && seqmappings.isEmpty())
1115       { // debug
1116         System.out.println("All mappings removed");
1117       }
1118     }
1119   }
1120
1121   /**
1122    * Add each of the given codonFrames to the stored set, if not aready present.
1123    * 
1124    * @param mappings
1125    */
1126   public void registerMappings(List<AlignedCodonFrame> mappings)
1127   {
1128     if (mappings != null)
1129     {
1130       for (AlignedCodonFrame acf : mappings)
1131       {
1132         registerMapping(acf);
1133       }
1134     }
1135   }
1136
1137   /**
1138    * Add the given mapping to the stored set, unless already stored.
1139    */
1140   public void registerMapping(AlignedCodonFrame acf)
1141   {
1142     if (acf != null)
1143     {
1144       if (!seqmappings.contains(acf))
1145       {
1146         seqmappings.add(acf);
1147       }
1148     }
1149   }
1150
1151   /**
1152    * Resets this object to its initial state by removing all registered
1153    * listeners, codon mappings, PDB file mappings
1154    */
1155   public void resetAll()
1156   {
1157     if (mappings != null)
1158     {
1159       mappings.clear();
1160     }
1161     if (seqmappings != null)
1162     {
1163       seqmappings.clear();
1164     }
1165     if (sel_listeners != null)
1166     {
1167       sel_listeners.clear();
1168     }
1169     if (listeners != null)
1170     {
1171       listeners.clear();
1172     }
1173     if (commandListeners != null)
1174     {
1175       commandListeners.clear();
1176     }
1177     if (view_listeners != null)
1178     {
1179       view_listeners.clear();
1180     }
1181     if (pdbFileNameId != null)
1182     {
1183       pdbFileNameId.clear();
1184     }
1185     if (pdbIdFileName != null)
1186     {
1187       pdbIdFileName.clear();
1188     }
1189   }
1190
1191   public void addSelectionListener(SelectionListener selecter)
1192   {
1193     if (!sel_listeners.contains(selecter))
1194     {
1195       sel_listeners.add(selecter);
1196     }
1197   }
1198
1199   public void removeSelectionListener(SelectionListener toremove)
1200   {
1201     if (sel_listeners.contains(toremove))
1202     {
1203       sel_listeners.remove(toremove);
1204     }
1205   }
1206
1207   public synchronized void sendSelection(
1208           jalview.datamodel.SequenceGroup selection,
1209           jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
1210           SelectionSource source)
1211   {
1212     for (SelectionListener slis : sel_listeners)
1213     {
1214       if (slis != source)
1215       {
1216         slis.selection(selection, colsel, hidden, source);
1217       }
1218     }
1219   }
1220
1221   Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
1222
1223   public synchronized void sendViewPosition(
1224           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1225           int startSeq, int endSeq)
1226   {
1227
1228     if (view_listeners != null && view_listeners.size() > 0)
1229     {
1230       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1231               .elements();
1232       while (listeners.hasMoreElements())
1233       {
1234         AlignmentViewPanelListener slis = listeners.nextElement();
1235         if (slis != source)
1236         {
1237           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1238         }
1239         ;
1240       }
1241     }
1242   }
1243
1244   /**
1245    * release all references associated with this manager provider
1246    * 
1247    * @param jalviewLite
1248    */
1249   public static void release(StructureSelectionManagerProvider jalviewLite)
1250   {
1251     // synchronized (instances)
1252     {
1253       if (instances == null)
1254       {
1255         return;
1256       }
1257       StructureSelectionManager mnger = (instances.get(jalviewLite));
1258       if (mnger != null)
1259       {
1260         instances.remove(jalviewLite);
1261         try
1262         {
1263           mnger.finalize();
1264         } catch (Throwable x)
1265         {
1266         }
1267       }
1268     }
1269   }
1270
1271   public void registerPDBEntry(PDBEntry pdbentry)
1272   {
1273     if (pdbentry.getFile() != null
1274             && pdbentry.getFile().trim().length() > 0)
1275     {
1276       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1277     }
1278   }
1279
1280   public void addCommandListener(CommandListener cl)
1281   {
1282     if (!commandListeners.contains(cl))
1283     {
1284       commandListeners.add(cl);
1285     }
1286   }
1287
1288   public boolean hasCommandListener(CommandListener cl)
1289   {
1290     return this.commandListeners.contains(cl);
1291   }
1292
1293   public boolean removeCommandListener(CommandListener l)
1294   {
1295     return commandListeners.remove(l);
1296   }
1297
1298   /**
1299    * Forward a command to any command listeners (except for the command's
1300    * source).
1301    * 
1302    * @param command
1303    *          the command to be broadcast (in its form after being performed)
1304    * @param undo
1305    *          if true, the command was being 'undone'
1306    * @param source
1307    */
1308   public void commandPerformed(CommandI command, boolean undo,
1309           VamsasSource source)
1310   {
1311     for (CommandListener listener : commandListeners)
1312     {
1313       listener.mirrorCommand(command, undo, this, source);
1314     }
1315   }
1316
1317   /**
1318    * Returns a new CommandI representing the given command as mapped to the
1319    * given sequences. If no mapping could be made, or the command is not of a
1320    * mappable kind, returns null.
1321    * 
1322    * @param command
1323    * @param undo
1324    * @param mapTo
1325    * @param gapChar
1326    * @return
1327    */
1328   public CommandI mapCommand(CommandI command, boolean undo,
1329           final AlignmentI mapTo, char gapChar)
1330   {
1331     if (command instanceof EditCommand)
1332     {
1333       return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
1334               gapChar, seqmappings);
1335     }
1336     else if (command instanceof OrderCommand)
1337     {
1338       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1339               mapTo, seqmappings);
1340     }
1341     return null;
1342   }
1343
1344   public IProgressIndicator getProgressIndicator()
1345   {
1346     return progressIndicator;
1347   }
1348
1349   public void setProgressIndicator(IProgressIndicator progressIndicator)
1350   {
1351     this.progressIndicator = progressIndicator;
1352   }
1353
1354   public long getProgressSessionId()
1355   {
1356     return progressSessionId;
1357   }
1358
1359   public void setProgressSessionId(long progressSessionId)
1360   {
1361     this.progressSessionId = progressSessionId;
1362   }
1363
1364   public void setProgressBar(String message)
1365   {
1366     if (progressIndicator == null)
1367     {
1368       return;
1369     }
1370     progressIndicator.setProgressBar(message, progressSessionId);
1371   }
1372
1373   public List<AlignedCodonFrame> getSequenceMappings()
1374   {
1375     return seqmappings;
1376   }
1377
1378 }