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