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