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