846ec9c9fc19213e28bc180c6f695b00322fc422
[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(
507             "Sequence \u27f7 Structure mapping details");
508     mappingDetails.append(NEWLINE).append("PDB Sequence is :")
509             .append(NEWLINE).append("Sequence = ")
510             .append(maxChain.sequence.getSequenceAsString());
511     mappingDetails.append(NEWLINE).append("No of residues = ")
512             .append(maxChain.residues.size()).append(NEWLINE)
513             .append(NEWLINE);
514     PrintStream ps = new PrintStream(System.out)
515     {
516       @Override
517       public void print(String x)
518       {
519         mappingDetails.append(x);
520       }
521
522       @Override
523       public void println()
524       {
525         mappingDetails.append(NEWLINE);
526       }
527     };
528
529     maxAlignseq.printAlignment(ps);
530
531     mappingDetails.append(NEWLINE).append("PDB start/end ");
532     mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
533             .append(" ");
534     mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
535     mappingDetails.append(NEWLINE).append("SEQ start/end ");
536     mappingDetails.append(
537             String.valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
538             .append(" ");
539     mappingDetails.append(String.valueOf(maxAlignseq.seq1end
540             + (seq.getStart() - 1)));
541     mappingDetails.append(NEWLINE);
542     mappingDetails
543             .append("Mapping inferred with Needleman & Wunsch alignment");
544     mappingDetails.append(NEWLINE);
545     maxChain.makeExactMapping(maxAlignseq, seq);
546     jalview.datamodel.Mapping sqmpping = maxAlignseq
547             .getMappingFromS1(false);
548     maxChain.transferRESNUMFeatures(seq, null);
549
550     // allocate enough slots to store the mapping from positions in
551     // sequence[s] to the associated chain
552     int[][] mapping = new int[seq.findPosition(seq.getLength()) + 2][2];
553     int resNum = -10000;
554     int index = 0;
555
556     do
557     {
558       Atom tmp = maxChain.atoms.elementAt(index);
559       if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
560       {
561         resNum = tmp.resNumber;
562         if (tmp.alignmentMapping >= -1)
563         {
564           // TODO (JAL-1836) address root cause: negative residue no in PDB
565           // file
566           mapping[tmp.alignmentMapping + 1][0] = tmp.resNumber;
567           mapping[tmp.alignmentMapping + 1][1] = tmp.atomIndex;
568         }
569       }
570
571       index++;
572     } while (index < maxChain.atoms.size());
573
574     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
575             pdb.id, maxChainId, mapping, mappingDetails.toString());
576     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
577     return nwMapping;
578   }
579
580   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
581   {
582     listeners.removeElement(svl);
583     if (svl instanceof SequenceListener)
584     {
585       for (int i = 0; i < listeners.size(); i++)
586       {
587         if (listeners.elementAt(i) instanceof StructureListener)
588         {
589           ((StructureListener) listeners.elementAt(i))
590                   .releaseReferences(svl);
591         }
592       }
593     }
594
595     if (pdbfiles == null)
596     {
597       return;
598     }
599
600     /*
601      * Remove mappings to the closed listener's PDB files, but first check if
602      * another listener is still interested
603      */
604     List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
605
606     StructureListener sl;
607     for (int i = 0; i < listeners.size(); i++)
608     {
609       if (listeners.elementAt(i) instanceof StructureListener)
610       {
611         sl = (StructureListener) listeners.elementAt(i);
612         for (String pdbfile : sl.getPdbFile())
613         {
614           pdbs.remove(pdbfile);
615         }
616       }
617     }
618
619     /*
620      * Rebuild the mappings set, retaining only those which are for 'other' PDB
621      * files
622      */
623     if (pdbs.size() > 0)
624     {
625       List<StructureMapping> tmp = new ArrayList<StructureMapping>();
626       for (StructureMapping sm : mappings)
627       {
628         if (!pdbs.contains(sm.pdbfile))
629         {
630           tmp.add(sm);
631         }
632       }
633
634       mappings = tmp;
635     }
636   }
637
638   /**
639    * Propagate mouseover of a single position in a structure
640    * 
641    * @param pdbResNum
642    * @param chain
643    * @param pdbfile
644    */
645   public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
646   {
647     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
648     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
649     mouseOverStructure(atoms);
650   }
651
652   /**
653    * Propagate mouseover or selection of multiple positions in a structure
654    * 
655    * @param atoms
656    */
657   public void mouseOverStructure(List<AtomSpec> atoms)
658   {
659     if (listeners == null)
660     {
661       // old or prematurely sent event
662       return;
663     }
664     boolean hasSequenceListener = false;
665     for (int i = 0; i < listeners.size(); i++)
666     {
667       if (listeners.elementAt(i) instanceof SequenceListener)
668       {
669         hasSequenceListener = true;
670       }
671     }
672     if (!hasSequenceListener)
673     {
674       return;
675     }
676
677     SearchResults results = new SearchResults();
678     for (AtomSpec atom : atoms)
679     {
680       SequenceI lastseq = null;
681       int lastipos = -1;
682       for (StructureMapping sm : mappings)
683       {
684         if (sm.pdbfile.equals(atom.getPdbFile())
685                 && sm.pdbchain.equals(atom.getChain()))
686         {
687           int indexpos = sm.getSeqPos(atom.getPdbResNum());
688           if (lastipos != indexpos && lastseq != sm.sequence)
689           {
690             results.addResult(sm.sequence, indexpos, indexpos);
691             lastipos = indexpos;
692             lastseq = sm.sequence;
693             // construct highlighted sequence list
694             for (AlignedCodonFrame acf : seqmappings)
695             {
696               acf.markMappedRegion(sm.sequence, indexpos, results);
697             }
698           }
699         }
700       }
701     }
702     for (Object li : listeners)
703     {
704       if (li instanceof SequenceListener)
705       {
706         ((SequenceListener) li).highlightSequence(results);
707       }
708     }
709   }
710
711   /**
712    * highlight regions associated with a position (indexpos) in seq
713    * 
714    * @param seq
715    *          the sequence that the mouse over occurred on
716    * @param indexpos
717    *          the absolute position being mouseovered in seq (0 to seq.length())
718    * @param index
719    *          the sequence position (if -1, seq.findPosition is called to
720    *          resolve the residue number)
721    */
722   public void mouseOverSequence(SequenceI seq, int indexpos, int index,
723           VamsasSource source)
724   {
725     boolean hasSequenceListeners = handlingVamsasMo
726             || !seqmappings.isEmpty();
727     SearchResults results = null;
728     if (index == -1)
729     {
730       index = seq.findPosition(indexpos);
731     }
732     for (int i = 0; i < listeners.size(); i++)
733     {
734       Object listener = listeners.elementAt(i);
735       if (listener == source)
736       {
737         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
738         // Temporary fudge with SequenceListener.getVamsasSource()
739         continue;
740       }
741       if (listener instanceof StructureListener)
742       {
743         highlightStructure((StructureListener) listener, seq, index);
744       }
745       else
746       {
747         if (listener instanceof SequenceListener)
748         {
749           final SequenceListener seqListener = (SequenceListener) listener;
750           if (hasSequenceListeners
751                   && seqListener.getVamsasSource() != source)
752           {
753             if (relaySeqMappings)
754             {
755               if (results == null)
756               {
757                 results = MappingUtils.buildSearchResults(seq, index,
758                         seqmappings);
759               }
760               if (handlingVamsasMo)
761               {
762                 results.addResult(seq, index, index);
763
764               }
765               if (!results.isEmpty())
766               {
767                 seqListener.highlightSequence(results);
768               }
769             }
770           }
771         }
772         else if (listener instanceof VamsasListener && !handlingVamsasMo)
773         {
774           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
775                   source);
776         }
777         else if (listener instanceof SecondaryStructureListener)
778         {
779           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
780                   indexpos, index);
781         }
782       }
783     }
784   }
785
786   /**
787    * Send suitable messages to a StructureListener to highlight atoms
788    * corresponding to the given sequence position.
789    * 
790    * @param sl
791    * @param seq
792    * @param index
793    */
794   protected void highlightStructure(StructureListener sl, SequenceI seq,
795           int index)
796   {
797     if (!sl.isListeningFor(seq))
798     {
799       return;
800     }
801     int atomNo;
802     List<AtomSpec> atoms = new ArrayList<AtomSpec>();
803     for (StructureMapping sm : mappings)
804     {
805       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
806       {
807         atomNo = sm.getAtomNum(index);
808
809         if (atomNo > 0)
810         {
811           atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
812                   .getPDBResNum(index), atomNo));
813         }
814       }
815     }
816     sl.highlightAtoms(atoms);
817   }
818
819   /**
820    * true if a mouse over event from an external (ie Vamsas) source is being
821    * handled
822    */
823   boolean handlingVamsasMo = false;
824
825   long lastmsg = 0;
826
827   /**
828    * as mouseOverSequence but only route event to SequenceListeners
829    * 
830    * @param sequenceI
831    * @param position
832    *          in an alignment sequence
833    */
834   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
835           VamsasSource source)
836   {
837     handlingVamsasMo = true;
838     long msg = sequenceI.hashCode() * (1 + position);
839     if (lastmsg != msg)
840     {
841       lastmsg = msg;
842       mouseOverSequence(sequenceI, position, -1, source);
843     }
844     handlingVamsasMo = false;
845   }
846
847   public Annotation[] colourSequenceFromStructure(SequenceI seq,
848           String pdbid)
849   {
850     return null;
851     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
852     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
853     /*
854      * Annotation [] annotations = new Annotation[seq.getLength()];
855      * 
856      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
857      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
858      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
859      * 
860      * for (int j = 0; j < mappings.length; j++) {
861      * 
862      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
863      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
864      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
865      * "+mappings[j].pdbfile);
866      * 
867      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
868      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
869      * 
870      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
871      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
872      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
873      * mappings[j].pdbfile); }
874      * 
875      * annotations[index] = new Annotation("X",null,' ',0,col); } return
876      * annotations; } } } }
877      * 
878      * return annotations;
879      */
880   }
881
882   public void structureSelectionChanged()
883   {
884   }
885
886   public void sequenceSelectionChanged()
887   {
888   }
889
890   public void sequenceColoursChanged(Object source)
891   {
892     StructureListener sl;
893     for (int i = 0; i < listeners.size(); i++)
894     {
895       if (listeners.elementAt(i) instanceof StructureListener)
896       {
897         sl = (StructureListener) listeners.elementAt(i);
898         sl.updateColours(source);
899       }
900     }
901   }
902
903   public StructureMapping[] getMapping(String pdbfile)
904   {
905     List<StructureMapping> tmp = new ArrayList<StructureMapping>();
906     for (StructureMapping sm : mappings)
907     {
908       if (sm.pdbfile.equals(pdbfile))
909       {
910         tmp.add(sm);
911       }
912     }
913     return tmp.toArray(new StructureMapping[tmp.size()]);
914   }
915
916   /**
917    * Returns a readable description of all mappings for the given pdbfile to any
918    * of the given sequences
919    * 
920    * @param pdbfile
921    * @param seqs
922    * @return
923    */
924   public String printMappings(String pdbfile, List<SequenceI> seqs)
925   {
926     if (pdbfile == null || seqs == null || seqs.isEmpty())
927     {
928       return "";
929     }
930
931     StringBuilder sb = new StringBuilder(64);
932     for (StructureMapping sm : mappings)
933     {
934       if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
935       {
936         sb.append(sm.mappingDetails);
937         sb.append(NEWLINE);
938         // separator makes it easier to read multiple mappings
939         sb.append("=====================");
940         sb.append(NEWLINE);
941       }
942     }
943     sb.append(NEWLINE);
944
945     return sb.toString();
946   }
947
948   /**
949    * Remove the given mapping
950    * 
951    * @param acf
952    */
953   public void deregisterMapping(AlignedCodonFrame acf)
954   {
955     if (acf != null)
956     {
957       boolean removed = seqmappings.remove(acf);
958       if (removed && seqmappings.isEmpty())
959       { // debug
960         System.out.println("All mappings removed");
961       }
962     }
963   }
964
965   /**
966    * Add each of the given codonFrames to the stored set, if not aready present.
967    * 
968    * @param set
969    */
970   public void registerMappings(Set<AlignedCodonFrame> set)
971   {
972     if (set != null)
973     {
974       for (AlignedCodonFrame acf : set)
975       {
976         registerMapping(acf);
977       }
978     }
979   }
980
981   /**
982    * Add the given mapping to the stored set, unless already stored.
983    */
984   public void registerMapping(AlignedCodonFrame acf)
985   {
986     if (acf != null)
987     {
988       if (!seqmappings.contains(acf))
989       {
990         seqmappings.add(acf);
991       }
992     }
993   }
994
995   /**
996    * Resets this object to its initial state by removing all registered
997    * listeners, codon mappings, PDB file mappings
998    */
999   public void resetAll()
1000   {
1001     if (mappings != null)
1002     {
1003       mappings.clear();
1004     }
1005     if (seqmappings != null)
1006     {
1007       seqmappings.clear();
1008     }
1009     if (sel_listeners != null)
1010     {
1011       sel_listeners.clear();
1012     }
1013     if (listeners != null)
1014     {
1015       listeners.clear();
1016     }
1017     if (commandListeners != null)
1018     {
1019       commandListeners.clear();
1020     }
1021     if (view_listeners != null)
1022     {
1023       view_listeners.clear();
1024     }
1025     if (pdbFileNameId != null)
1026     {
1027       pdbFileNameId.clear();
1028     }
1029     if (pdbIdFileName != null)
1030     {
1031       pdbIdFileName.clear();
1032     }
1033   }
1034
1035   public void addSelectionListener(SelectionListener selecter)
1036   {
1037     if (!sel_listeners.contains(selecter))
1038     {
1039       sel_listeners.add(selecter);
1040     }
1041   }
1042
1043   public void removeSelectionListener(SelectionListener toremove)
1044   {
1045     if (sel_listeners.contains(toremove))
1046     {
1047       sel_listeners.remove(toremove);
1048     }
1049   }
1050
1051   public synchronized void sendSelection(
1052           jalview.datamodel.SequenceGroup selection,
1053           jalview.datamodel.ColumnSelection colsel, SelectionSource source)
1054   {
1055     for (SelectionListener slis : sel_listeners)
1056     {
1057       if (slis != source)
1058       {
1059         slis.selection(selection, colsel, source);
1060       }
1061     }
1062   }
1063
1064   Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
1065
1066   public synchronized void sendViewPosition(
1067           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1068           int startSeq, int endSeq)
1069   {
1070
1071     if (view_listeners != null && view_listeners.size() > 0)
1072     {
1073       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1074               .elements();
1075       while (listeners.hasMoreElements())
1076       {
1077         AlignmentViewPanelListener slis = listeners.nextElement();
1078         if (slis != source)
1079         {
1080           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1081         }
1082         ;
1083       }
1084     }
1085   }
1086
1087   /**
1088    * release all references associated with this manager provider
1089    * 
1090    * @param jalviewLite
1091    */
1092   public static void release(StructureSelectionManagerProvider jalviewLite)
1093   {
1094     // synchronized (instances)
1095     {
1096       if (instances == null)
1097       {
1098         return;
1099       }
1100       StructureSelectionManager mnger = (instances.get(jalviewLite));
1101       if (mnger != null)
1102       {
1103         instances.remove(jalviewLite);
1104         try
1105         {
1106           mnger.finalize();
1107         } catch (Throwable x)
1108         {
1109         }
1110       }
1111     }
1112   }
1113
1114   public void registerPDBEntry(PDBEntry pdbentry)
1115   {
1116     if (pdbentry.getFile() != null
1117             && pdbentry.getFile().trim().length() > 0)
1118     {
1119       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1120     }
1121   }
1122
1123   public void addCommandListener(CommandListener cl)
1124   {
1125     if (!commandListeners.contains(cl))
1126     {
1127       commandListeners.add(cl);
1128     }
1129   }
1130
1131   public boolean hasCommandListener(CommandListener cl)
1132   {
1133     return this.commandListeners.contains(cl);
1134   }
1135
1136   public boolean removeCommandListener(CommandListener l)
1137   {
1138     return commandListeners.remove(l);
1139   }
1140
1141   /**
1142    * Forward a command to any command listeners (except for the command's
1143    * source).
1144    * 
1145    * @param command
1146    *          the command to be broadcast (in its form after being performed)
1147    * @param undo
1148    *          if true, the command was being 'undone'
1149    * @param source
1150    */
1151   public void commandPerformed(CommandI command, boolean undo,
1152           VamsasSource source)
1153   {
1154     for (CommandListener listener : commandListeners)
1155     {
1156       listener.mirrorCommand(command, undo, this, source);
1157     }
1158   }
1159
1160   /**
1161    * Returns a new CommandI representing the given command as mapped to the
1162    * given sequences. If no mapping could be made, or the command is not of a
1163    * mappable kind, returns null.
1164    * 
1165    * @param command
1166    * @param undo
1167    * @param mapTo
1168    * @param gapChar
1169    * @return
1170    */
1171   public CommandI mapCommand(CommandI command, boolean undo,
1172           final AlignmentI mapTo, char gapChar)
1173   {
1174     if (command instanceof EditCommand)
1175     {
1176       return MappingUtils.mapEditCommand((EditCommand) command, undo,
1177               mapTo, gapChar, seqmappings);
1178     }
1179     else if (command instanceof OrderCommand)
1180     {
1181       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1182               mapTo, seqmappings);
1183     }
1184     return null;
1185   }
1186 }