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