JAL-1967 JAL-1479 refactored sequence<->structure mapping implementation
[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     HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
592     int resNum = -10000;
593     int index = 0;
594
595     do
596     {
597       Atom tmp = maxChain.atoms.elementAt(index);
598       if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
599       {
600         resNum = tmp.resNumber;
601         if (tmp.alignmentMapping >= -1)
602         {
603           // TODO (JAL-1836) address root cause: negative residue no in PDB
604           // file
605           mapping.put(tmp.alignmentMapping + 1, new int[] { tmp.resNumber,
606               tmp.atomIndex });
607         }
608       }
609
610       index++;
611     } while (index < maxChain.atoms.size());
612
613     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
614             pdb.id, maxChainId, mapping, mappingDetails.toString());
615     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
616     ArrayList<StructureMapping> mappings = new ArrayList<StructureMapping>();
617     mappings.add(nwMapping);
618     return mappings;
619   }
620
621   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
622   {
623     listeners.removeElement(svl);
624     if (svl instanceof SequenceListener)
625     {
626       for (int i = 0; i < listeners.size(); i++)
627       {
628         if (listeners.elementAt(i) instanceof StructureListener)
629         {
630           ((StructureListener) listeners.elementAt(i))
631                   .releaseReferences(svl);
632         }
633       }
634     }
635
636     if (pdbfiles == null)
637     {
638       return;
639     }
640
641     /*
642      * Remove mappings to the closed listener's PDB files, but first check if
643      * another listener is still interested
644      */
645     List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
646
647     StructureListener sl;
648     for (int i = 0; i < listeners.size(); i++)
649     {
650       if (listeners.elementAt(i) instanceof StructureListener)
651       {
652         sl = (StructureListener) listeners.elementAt(i);
653         for (String pdbfile : sl.getPdbFile())
654         {
655           pdbs.remove(pdbfile);
656         }
657       }
658     }
659
660     /*
661      * Rebuild the mappings set, retaining only those which are for 'other' PDB
662      * files
663      */
664     if (pdbs.size() > 0)
665     {
666       List<StructureMapping> tmp = new ArrayList<StructureMapping>();
667       for (StructureMapping sm : mappings)
668       {
669         if (!pdbs.contains(sm.pdbfile))
670         {
671           tmp.add(sm);
672         }
673       }
674
675       mappings = tmp;
676     }
677   }
678
679   /**
680    * Propagate mouseover of a single position in a structure
681    * 
682    * @param pdbResNum
683    * @param chain
684    * @param pdbfile
685    */
686   public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
687   {
688     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
689     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
690     mouseOverStructure(atoms);
691   }
692
693   /**
694    * Propagate mouseover or selection of multiple positions in a structure
695    * 
696    * @param atoms
697    */
698   public void mouseOverStructure(List<AtomSpec> atoms)
699   {
700     if (listeners == null)
701     {
702       // old or prematurely sent event
703       return;
704     }
705     boolean hasSequenceListener = false;
706     for (int i = 0; i < listeners.size(); i++)
707     {
708       if (listeners.elementAt(i) instanceof SequenceListener)
709       {
710         hasSequenceListener = true;
711       }
712     }
713     if (!hasSequenceListener)
714     {
715       return;
716     }
717
718     SearchResults results = new SearchResults();
719     for (AtomSpec atom : atoms)
720     {
721       SequenceI lastseq = null;
722       int lastipos = -1;
723       for (StructureMapping sm : mappings)
724       {
725         if (sm.pdbfile.equals(atom.getPdbFile())
726                 && sm.pdbchain.equals(atom.getChain()))
727         {
728           int indexpos = sm.getSeqPos(atom.getPdbResNum());
729           if (lastipos != indexpos && lastseq != sm.sequence)
730           {
731             results.addResult(sm.sequence, indexpos, indexpos);
732             lastipos = indexpos;
733             lastseq = sm.sequence;
734             // construct highlighted sequence list
735             for (AlignedCodonFrame acf : seqmappings)
736             {
737               acf.markMappedRegion(sm.sequence, indexpos, results);
738             }
739           }
740         }
741       }
742     }
743     for (Object li : listeners)
744     {
745       if (li instanceof SequenceListener)
746       {
747         ((SequenceListener) li).highlightSequence(results);
748       }
749     }
750   }
751
752   /**
753    * highlight regions associated with a position (indexpos) in seq
754    * 
755    * @param seq
756    *          the sequence that the mouse over occurred on
757    * @param indexpos
758    *          the absolute position being mouseovered in seq (0 to seq.length())
759    * @param index
760    *          the sequence position (if -1, seq.findPosition is called to
761    *          resolve the residue number)
762    */
763   public void mouseOverSequence(SequenceI seq, int indexpos, int index,
764           VamsasSource source)
765   {
766     boolean hasSequenceListeners = handlingVamsasMo
767             || !seqmappings.isEmpty();
768     SearchResults results = null;
769     if (index == -1)
770     {
771       index = seq.findPosition(indexpos);
772     }
773     for (int i = 0; i < listeners.size(); i++)
774     {
775       Object listener = listeners.elementAt(i);
776       if (listener == source)
777       {
778         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
779         // Temporary fudge with SequenceListener.getVamsasSource()
780         continue;
781       }
782       if (listener instanceof StructureListener)
783       {
784         highlightStructure((StructureListener) listener, seq, index);
785       }
786       else
787       {
788         if (listener instanceof SequenceListener)
789         {
790           final SequenceListener seqListener = (SequenceListener) listener;
791           if (hasSequenceListeners
792                   && seqListener.getVamsasSource() != source)
793           {
794             if (relaySeqMappings)
795             {
796               if (results == null)
797               {
798                 results = MappingUtils.buildSearchResults(seq, index,
799                         seqmappings);
800               }
801               if (handlingVamsasMo)
802               {
803                 results.addResult(seq, index, index);
804
805               }
806               if (!results.isEmpty())
807               {
808                 seqListener.highlightSequence(results);
809               }
810             }
811           }
812         }
813         else if (listener instanceof VamsasListener && !handlingVamsasMo)
814         {
815           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
816                   source);
817         }
818         else if (listener instanceof SecondaryStructureListener)
819         {
820           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
821                   indexpos, index);
822         }
823       }
824     }
825   }
826
827   /**
828    * Send suitable messages to a StructureListener to highlight atoms
829    * corresponding to the given sequence position.
830    * 
831    * @param sl
832    * @param seq
833    * @param index
834    */
835   protected void highlightStructure(StructureListener sl, SequenceI seq,
836           int index)
837   {
838     if (!sl.isListeningFor(seq))
839     {
840       return;
841     }
842     int atomNo;
843     List<AtomSpec> atoms = new ArrayList<AtomSpec>();
844     for (StructureMapping sm : mappings)
845     {
846       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
847       {
848         atomNo = sm.getAtomNum(index);
849
850         if (atomNo > 0)
851         {
852           atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
853                   .getPDBResNum(index), atomNo));
854         }
855       }
856     }
857     sl.highlightAtoms(atoms);
858   }
859
860   /**
861    * true if a mouse over event from an external (ie Vamsas) source is being
862    * handled
863    */
864   boolean handlingVamsasMo = false;
865
866   long lastmsg = 0;
867
868   /**
869    * as mouseOverSequence but only route event to SequenceListeners
870    * 
871    * @param sequenceI
872    * @param position
873    *          in an alignment sequence
874    */
875   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
876           VamsasSource source)
877   {
878     handlingVamsasMo = true;
879     long msg = sequenceI.hashCode() * (1 + position);
880     if (lastmsg != msg)
881     {
882       lastmsg = msg;
883       mouseOverSequence(sequenceI, position, -1, source);
884     }
885     handlingVamsasMo = false;
886   }
887
888   public Annotation[] colourSequenceFromStructure(SequenceI seq,
889           String pdbid)
890   {
891     return null;
892     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
893     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
894     /*
895      * Annotation [] annotations = new Annotation[seq.getLength()];
896      * 
897      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
898      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
899      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
900      * 
901      * for (int j = 0; j < mappings.length; j++) {
902      * 
903      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
904      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
905      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
906      * "+mappings[j].pdbfile);
907      * 
908      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
909      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
910      * 
911      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
912      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
913      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
914      * mappings[j].pdbfile); }
915      * 
916      * annotations[index] = new Annotation("X",null,' ',0,col); } return
917      * annotations; } } } }
918      * 
919      * return annotations;
920      */
921   }
922
923   public void structureSelectionChanged()
924   {
925   }
926
927   public void sequenceSelectionChanged()
928   {
929   }
930
931   public void sequenceColoursChanged(Object source)
932   {
933     StructureListener sl;
934     for (int i = 0; i < listeners.size(); i++)
935     {
936       if (listeners.elementAt(i) instanceof StructureListener)
937       {
938         sl = (StructureListener) listeners.elementAt(i);
939         sl.updateColours(source);
940       }
941     }
942   }
943
944   public StructureMapping[] getMapping(String pdbfile)
945   {
946     List<StructureMapping> tmp = new ArrayList<StructureMapping>();
947     for (StructureMapping sm : mappings)
948     {
949       if (sm.pdbfile.equals(pdbfile))
950       {
951         tmp.add(sm);
952       }
953     }
954     return tmp.toArray(new StructureMapping[tmp.size()]);
955   }
956
957   /**
958    * Returns a readable description of all mappings for the given pdbfile to any
959    * of the given sequences
960    * 
961    * @param pdbfile
962    * @param seqs
963    * @return
964    */
965   public String printMappings(String pdbfile, List<SequenceI> seqs)
966   {
967     if (pdbfile == null || seqs == null || seqs.isEmpty())
968     {
969       return "";
970     }
971
972     StringBuilder sb = new StringBuilder(64);
973     for (StructureMapping sm : mappings)
974     {
975       if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
976       {
977         sb.append(sm.mappingDetails);
978         sb.append(NEWLINE);
979         // separator makes it easier to read multiple mappings
980         sb.append("=====================");
981         sb.append(NEWLINE);
982       }
983     }
984     sb.append(NEWLINE);
985
986     return sb.toString();
987   }
988
989   /**
990    * Remove the given mapping
991    * 
992    * @param acf
993    */
994   public void deregisterMapping(AlignedCodonFrame acf)
995   {
996     if (acf != null)
997     {
998       boolean removed = seqmappings.remove(acf);
999       if (removed && seqmappings.isEmpty())
1000       { // debug
1001         System.out.println("All mappings removed");
1002       }
1003     }
1004   }
1005
1006   /**
1007    * Add each of the given codonFrames to the stored set, if not aready present.
1008    * 
1009    * @param set
1010    */
1011   public void registerMappings(Set<AlignedCodonFrame> set)
1012   {
1013     if (set != null)
1014     {
1015       for (AlignedCodonFrame acf : set)
1016       {
1017         registerMapping(acf);
1018       }
1019     }
1020   }
1021
1022   /**
1023    * Add the given mapping to the stored set, unless already stored.
1024    */
1025   public void registerMapping(AlignedCodonFrame acf)
1026   {
1027     if (acf != null)
1028     {
1029       if (!seqmappings.contains(acf))
1030       {
1031         seqmappings.add(acf);
1032       }
1033     }
1034   }
1035
1036   /**
1037    * Resets this object to its initial state by removing all registered
1038    * listeners, codon mappings, PDB file mappings
1039    */
1040   public void resetAll()
1041   {
1042     if (mappings != null)
1043     {
1044       mappings.clear();
1045     }
1046     if (seqmappings != null)
1047     {
1048       seqmappings.clear();
1049     }
1050     if (sel_listeners != null)
1051     {
1052       sel_listeners.clear();
1053     }
1054     if (listeners != null)
1055     {
1056       listeners.clear();
1057     }
1058     if (commandListeners != null)
1059     {
1060       commandListeners.clear();
1061     }
1062     if (view_listeners != null)
1063     {
1064       view_listeners.clear();
1065     }
1066     if (pdbFileNameId != null)
1067     {
1068       pdbFileNameId.clear();
1069     }
1070     if (pdbIdFileName != null)
1071     {
1072       pdbIdFileName.clear();
1073     }
1074   }
1075
1076   public void addSelectionListener(SelectionListener selecter)
1077   {
1078     if (!sel_listeners.contains(selecter))
1079     {
1080       sel_listeners.add(selecter);
1081     }
1082   }
1083
1084   public void removeSelectionListener(SelectionListener toremove)
1085   {
1086     if (sel_listeners.contains(toremove))
1087     {
1088       sel_listeners.remove(toremove);
1089     }
1090   }
1091
1092   public synchronized void sendSelection(
1093           jalview.datamodel.SequenceGroup selection,
1094           jalview.datamodel.ColumnSelection colsel, SelectionSource source)
1095   {
1096     for (SelectionListener slis : sel_listeners)
1097     {
1098       if (slis != source)
1099       {
1100         slis.selection(selection, colsel, source);
1101       }
1102     }
1103   }
1104
1105   Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
1106
1107   public synchronized void sendViewPosition(
1108           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1109           int startSeq, int endSeq)
1110   {
1111
1112     if (view_listeners != null && view_listeners.size() > 0)
1113     {
1114       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1115               .elements();
1116       while (listeners.hasMoreElements())
1117       {
1118         AlignmentViewPanelListener slis = listeners.nextElement();
1119         if (slis != source)
1120         {
1121           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1122         }
1123         ;
1124       }
1125     }
1126   }
1127
1128   /**
1129    * release all references associated with this manager provider
1130    * 
1131    * @param jalviewLite
1132    */
1133   public static void release(StructureSelectionManagerProvider jalviewLite)
1134   {
1135     // synchronized (instances)
1136     {
1137       if (instances == null)
1138       {
1139         return;
1140       }
1141       StructureSelectionManager mnger = (instances.get(jalviewLite));
1142       if (mnger != null)
1143       {
1144         instances.remove(jalviewLite);
1145         try
1146         {
1147           mnger.finalize();
1148         } catch (Throwable x)
1149         {
1150         }
1151       }
1152     }
1153   }
1154
1155   public void registerPDBEntry(PDBEntry pdbentry)
1156   {
1157     if (pdbentry.getFile() != null
1158             && pdbentry.getFile().trim().length() > 0)
1159     {
1160       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1161     }
1162   }
1163
1164   public void addCommandListener(CommandListener cl)
1165   {
1166     if (!commandListeners.contains(cl))
1167     {
1168       commandListeners.add(cl);
1169     }
1170   }
1171
1172   public boolean hasCommandListener(CommandListener cl)
1173   {
1174     return this.commandListeners.contains(cl);
1175   }
1176
1177   public boolean removeCommandListener(CommandListener l)
1178   {
1179     return commandListeners.remove(l);
1180   }
1181
1182   /**
1183    * Forward a command to any command listeners (except for the command's
1184    * source).
1185    * 
1186    * @param command
1187    *          the command to be broadcast (in its form after being performed)
1188    * @param undo
1189    *          if true, the command was being 'undone'
1190    * @param source
1191    */
1192   public void commandPerformed(CommandI command, boolean undo,
1193           VamsasSource source)
1194   {
1195     for (CommandListener listener : commandListeners)
1196     {
1197       listener.mirrorCommand(command, undo, this, source);
1198     }
1199   }
1200
1201   /**
1202    * Returns a new CommandI representing the given command as mapped to the
1203    * given sequences. If no mapping could be made, or the command is not of a
1204    * mappable kind, returns null.
1205    * 
1206    * @param command
1207    * @param undo
1208    * @param mapTo
1209    * @param gapChar
1210    * @return
1211    */
1212   public CommandI mapCommand(CommandI command, boolean undo,
1213           final AlignmentI mapTo, char gapChar)
1214   {
1215     if (command instanceof EditCommand)
1216     {
1217       return MappingUtils.mapEditCommand((EditCommand) command, undo,
1218               mapTo, gapChar, seqmappings);
1219     }
1220     else if (command instanceof OrderCommand)
1221     {
1222       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1223               mapTo, seqmappings);
1224     }
1225     return null;
1226   }
1227 }