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