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