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