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