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