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