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