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