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