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