JAL-1725 broadcast empty selection (to clear selection)
[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     for (Object li : listeners)
650     {
651       if (li instanceof SequenceListener)
652       {
653         ((SequenceListener) li).highlightSequence(results);
654       }
655     }
656   }
657
658   /**
659    * highlight regions associated with a position (indexpos) in seq
660    * 
661    * @param seq
662    *          the sequence that the mouse over occurred on
663    * @param indexpos
664    *          the absolute position being mouseovered in seq (0 to seq.length())
665    * @param index
666    *          the sequence position (if -1, seq.findPosition is called to
667    *          resolve the residue number)
668    */
669   public void mouseOverSequence(SequenceI seq, int indexpos, int index,
670           VamsasSource source)
671   {
672     boolean hasSequenceListeners = handlingVamsasMo
673             || !seqmappings.isEmpty();
674     SearchResults results = null;
675     if (index == -1)
676     {
677       index = seq.findPosition(indexpos);
678     }
679     for (int i = 0; i < listeners.size(); i++)
680     {
681       Object listener = listeners.elementAt(i);
682       if (listener == source)
683       {
684         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
685         // Temporary fudge with SequenceListener.getVamsasSource()
686         continue;
687       }
688       if (listener instanceof StructureListener)
689       {
690         highlightStructure((StructureListener) listener, seq, index);
691       }
692       else
693       {
694         if (listener instanceof SequenceListener)
695         {
696           final SequenceListener seqListener = (SequenceListener) listener;
697           if (hasSequenceListeners
698                   && seqListener.getVamsasSource() != source)
699           {
700             if (relaySeqMappings)
701             {
702               if (results == null)
703               {
704                 results = MappingUtils.buildSearchResults(seq, index,
705                         seqmappings);
706               }
707               if (handlingVamsasMo)
708               {
709                 results.addResult(seq, index, index);
710
711               }
712               seqListener.highlightSequence(results);
713             }
714           }
715         }
716         else if (listener instanceof VamsasListener && !handlingVamsasMo)
717         {
718           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
719                   source);
720         }
721         else if (listener instanceof SecondaryStructureListener)
722         {
723           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
724                   indexpos);
725         }
726       }
727     }
728   }
729
730   /**
731    * Send suitable messages to a StructureListener to highlight atoms
732    * corresponding to the given sequence position.
733    * 
734    * @param sl
735    * @param seq
736    * @param index
737    */
738   protected void highlightStructure(StructureListener sl, SequenceI seq,
739           int index)
740   {
741     int atomNo;
742     List<AtomSpec> atoms = new ArrayList<AtomSpec>();
743     for (StructureMapping sm : mappings)
744     {
745       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
746       {
747         atomNo = sm.getAtomNum(index);
748
749         if (atomNo > 0)
750         {
751           atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
752                   .getPDBResNum(index), atomNo));
753         }
754       }
755     }
756     sl.highlightAtoms(atoms);
757   }
758
759   /**
760    * true if a mouse over event from an external (ie Vamsas) source is being
761    * handled
762    */
763   boolean handlingVamsasMo = false;
764
765   long lastmsg = 0;
766
767   /**
768    * as mouseOverSequence but only route event to SequenceListeners
769    * 
770    * @param sequenceI
771    * @param position
772    *          in an alignment sequence
773    */
774   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
775           VamsasSource source)
776   {
777     handlingVamsasMo = true;
778     long msg = sequenceI.hashCode() * (1 + position);
779     if (lastmsg != msg)
780     {
781       lastmsg = msg;
782       mouseOverSequence(sequenceI, position, -1, source);
783     }
784     handlingVamsasMo = false;
785   }
786
787   public Annotation[] colourSequenceFromStructure(SequenceI seq,
788           String pdbid)
789   {
790     return null;
791     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
792     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
793     /*
794      * Annotation [] annotations = new Annotation[seq.getLength()];
795      * 
796      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
797      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
798      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
799      * 
800      * for (int j = 0; j < mappings.length; j++) {
801      * 
802      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
803      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
804      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
805      * "+mappings[j].pdbfile);
806      * 
807      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
808      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
809      * 
810      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
811      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
812      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
813      * mappings[j].pdbfile); }
814      * 
815      * annotations[index] = new Annotation("X",null,' ',0,col); } return
816      * annotations; } } } }
817      * 
818      * return annotations;
819      */
820   }
821
822   public void structureSelectionChanged()
823   {
824   }
825
826   public void sequenceSelectionChanged()
827   {
828   }
829
830   public void sequenceColoursChanged(Object source)
831   {
832     StructureListener sl;
833     for (int i = 0; i < listeners.size(); i++)
834     {
835       if (listeners.elementAt(i) instanceof StructureListener)
836       {
837         sl = (StructureListener) listeners.elementAt(i);
838         sl.updateColours(source);
839       }
840     }
841   }
842
843   public StructureMapping[] getMapping(String pdbfile)
844   {
845     List<StructureMapping> tmp = new ArrayList<StructureMapping>();
846     for (StructureMapping sm : mappings)
847       {
848       if (sm.pdbfile.equals(pdbfile))
849         {
850         tmp.add(sm);
851         }
852     }
853     return tmp.toArray(new StructureMapping[tmp.size()]);
854   }
855
856   public String printMapping(String pdbfile)
857   {
858     StringBuilder sb = new StringBuilder(64);
859     for (StructureMapping sm : mappings)
860     {
861       if (sm.pdbfile.equals(pdbfile))
862       {
863         sb.append(sm.mappingDetails);
864       }
865     }
866
867     return sb.toString();
868   }
869
870   /**
871    * Decrement the reference counter for each of the given mappings, and remove
872    * it entirely if its reference counter reduces to zero.
873    * 
874    * @param set
875    */
876   public void removeMappings(Set<AlignedCodonFrame> set)
877   {
878     if (set != null)
879     {
880       for (AlignedCodonFrame acf : set)
881       {
882         removeMapping(acf);
883       }
884     }
885   }
886
887   /**
888    * Decrement the reference counter for the given mapping, and remove it
889    * entirely if its reference counter reduces to zero.
890    * 
891    * @param acf
892    */
893   public void removeMapping(AlignedCodonFrame acf)
894   {
895     if (acf != null && seqmappings.contains(acf))
896     {
897       int count = seqMappingRefCounts.get(acf);
898       count--;
899       if (count > 0)
900       {
901         seqMappingRefCounts.put(acf, count);
902       }
903       else
904       {
905         seqmappings.remove(acf);
906         seqMappingRefCounts.remove(acf);
907       }
908     }
909   }
910
911   /**
912    * Add each of the given codonFrames to the stored set. If not aready present,
913    * increments its reference count instead.
914    * 
915    * @param set
916    */
917   public void addMappings(Set<AlignedCodonFrame> set)
918   {
919     if (set != null)
920     {
921       for (AlignedCodonFrame acf : set)
922       {
923         addMapping(acf);
924       }
925     }
926   }
927
928   /**
929    * Add the given mapping to the stored set, or if already stored, increment
930    * its reference counter.
931    */
932   public void addMapping(AlignedCodonFrame acf)
933   {
934     if (acf != null)
935     {
936       if (seqmappings.contains(acf))
937       {
938         seqMappingRefCounts.put(acf, seqMappingRefCounts.get(acf) + 1);
939       }
940       else
941       {
942         seqmappings.add(acf);
943         seqMappingRefCounts.put(acf, 1);
944       }
945     }
946   }
947
948   public void addSelectionListener(SelectionListener selecter)
949   {
950     if (!sel_listeners.contains(selecter))
951     {
952       sel_listeners.add(selecter);
953     }
954   }
955
956   public void removeSelectionListener(SelectionListener toremove)
957   {
958     if (sel_listeners.contains(toremove))
959     {
960       sel_listeners.remove(toremove);
961     }
962   }
963
964   public synchronized void sendSelection(
965           jalview.datamodel.SequenceGroup selection,
966           jalview.datamodel.ColumnSelection colsel, SelectionSource source)
967   {
968     for (SelectionListener slis : sel_listeners)
969     {
970       if (slis != source)
971       {
972         slis.selection(selection, colsel, source);
973       }
974     }
975   }
976
977   Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
978
979   public synchronized void sendViewPosition(
980           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
981           int startSeq, int endSeq)
982   {
983
984     if (view_listeners != null && view_listeners.size() > 0)
985     {
986       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
987               .elements();
988       while (listeners.hasMoreElements())
989       {
990         AlignmentViewPanelListener slis = listeners.nextElement();
991         if (slis != source)
992         {
993           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
994         }
995         ;
996       }
997     }
998   }
999
1000   /**
1001    * release all references associated with this manager provider
1002    * 
1003    * @param jalviewLite
1004    */
1005   public static void release(StructureSelectionManagerProvider jalviewLite)
1006   {
1007     // synchronized (instances)
1008     {
1009       if (instances == null)
1010       {
1011         return;
1012       }
1013       StructureSelectionManager mnger = (instances.get(jalviewLite));
1014       if (mnger != null)
1015       {
1016         instances.remove(jalviewLite);
1017         try
1018         {
1019           mnger.finalize();
1020         } catch (Throwable x)
1021         {
1022         }
1023       }
1024     }
1025   }
1026
1027   public void registerPDBEntry(PDBEntry pdbentry)
1028   {
1029     if (pdbentry.getFile() != null
1030             && pdbentry.getFile().trim().length() > 0)
1031     {
1032       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1033     }
1034   }
1035
1036   public void addCommandListener(CommandListener cl)
1037   {
1038     if (!commandListeners.contains(cl))
1039     {
1040       commandListeners.add(cl);
1041     }
1042   }
1043
1044   public boolean hasCommandListener(CommandListener cl)
1045   {
1046     return this.commandListeners.contains(cl);
1047   }
1048
1049   public boolean removeCommandListener(CommandListener l)
1050   {
1051     return commandListeners.remove(l);
1052   }
1053
1054   /**
1055    * Forward a command to any command listeners (except for the command's
1056    * source).
1057    * 
1058    * @param command
1059    *          the command to be broadcast (in its form after being performed)
1060    * @param undo
1061    *          if true, the command was being 'undone'
1062    * @param source
1063    */
1064   public void commandPerformed(CommandI command, boolean undo,
1065           VamsasSource source)
1066   {
1067     for (CommandListener listener : commandListeners)
1068     {
1069       listener.mirrorCommand(command, undo, this, source);
1070     }
1071   }
1072
1073   /**
1074    * Returns a new CommandI representing the given command as mapped to the
1075    * given sequences. If no mapping could be made, or the command is not of a
1076    * mappable kind, returns null.
1077    * 
1078    * @param command
1079    * @param undo
1080    * @param mapTo
1081    * @param gapChar
1082    * @return
1083    */
1084   public CommandI mapCommand(CommandI command, boolean undo,
1085           final AlignmentI mapTo, char gapChar)
1086   {
1087     if (command instanceof EditCommand)
1088     {
1089       return MappingUtils.mapEditCommand((EditCommand) command, undo,
1090               mapTo, gapChar, seqmappings);
1091     }
1092     else if (command instanceof OrderCommand)
1093     {
1094       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1095               mapTo, seqmappings);
1096     }
1097     return null;
1098   }
1099 }