refactor to allow distinct StructureSelectionManager instances for
[jalview.git] / src / jalview / structure / StructureSelectionManager.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.structure;
19
20 import java.io.*;
21 import java.util.*;
22
23 import MCview.*;
24 import jalview.analysis.*;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.api.StructureSelectionManagerProvider;
27 import jalview.datamodel.*;
28
29 public class StructureSelectionManager
30 {
31   static IdentityHashMap<StructureSelectionManagerProvider,StructureSelectionManager> instances;
32
33   StructureMapping[] mappings;
34
35   /**
36    * debug function - write all mappings to stdout
37    */
38   public void reportMapping() {\r
39     if (mappings==null)\r
40     {\r
41       System.err.println("reportMapping: No PDB/Sequence mappings.");\r
42     }else{\r
43       System.err.println("reportMapping: There are "+mappings.length+" mappings.");\r
44       for (int m=0;m<mappings.length;m++)\r
45       {\r
46         System.err.println("mapping "+m+" : "+mappings[m].pdbfile);\r
47       }\r
48     }\r
49   }\r
50   Hashtable mappingData = new Hashtable();
51
52   public static StructureSelectionManager getStructureSelectionManager(StructureSelectionManagerProvider context)
53   {
54     if (instances == null)
55     {
56       instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider,StructureSelectionManager>();
57     }
58     StructureSelectionManager instance=instances.get(context);
59     if (instance==null)
60     {
61       instances.put(context, instance=new StructureSelectionManager());
62     }
63     return instance;
64   }
65
66   /**
67    * flag controlling whether SeqMappings are relayed from received sequence
68    * mouse over events to other sequences
69    */
70   boolean relaySeqMappings = true;
71
72   /**
73    * Enable or disable relay of seqMapping events to other sequences. You might
74    * want to do this if there are many sequence mappings and the host computer
75    * is slow
76    * 
77    * @param relay
78    */
79   public void setRelaySeqMappings(boolean relay)
80   {
81     relaySeqMappings = relay;
82   }
83
84   /**
85    * get the state of the relay seqMappings flag.
86    * 
87    * @return true if sequence mouse overs are being relayed to other mapped
88    *         sequences
89    */
90   public boolean isRelaySeqMappingsEnabled()
91   {
92     return relaySeqMappings;
93   }
94
95   Vector listeners = new Vector();
96
97   /**
98    * register a listener for alignment sequence mouseover events
99    * @param svl
100    */
101   public void addStructureViewerListener(Object svl)
102   {
103     if (!listeners.contains(svl))
104     {
105       listeners.addElement(svl);
106     }
107   }
108
109   public String alreadyMappedToFile(String pdbid)
110   {
111     if (mappings != null)
112     {
113       for (int i = 0; i < mappings.length; i++)
114       {
115         if (mappings[i].getPdbId().equals(pdbid))
116         {
117           return mappings[i].pdbfile;
118         }
119       }
120     }
121     return null;
122   }
123
124   /**
125    * create sequence structure mappings between each sequence and the given
126    * pdbFile (retrieved via the given protocol).
127    * 
128    * @param sequence
129    *          - one or more sequences to be mapped to pdbFile
130    * @param targetChains
131    *          - optional chain specification for mapping each sequence to pdb
132    *          (may be nill, individual elements may be nill)
133    * @param pdbFile
134    *          - structure data resource
135    * @param protocol
136    *          - how to resolve data from resource
137    * @return null or the structure data parsed as a pdb file
138    */
139   synchronized public MCview.PDBfile setMapping(SequenceI[] sequence,
140           String[] targetChains, String pdbFile, String protocol)
141   {
142     /*
143      * There will be better ways of doing this in the future, for now we'll use
144      * the tried and tested MCview pdb mapping
145      */
146     MCview.PDBfile pdb = null;
147     try
148     {
149       pdb = new MCview.PDBfile(pdbFile, protocol);
150     } catch (Exception ex)
151     {
152       ex.printStackTrace();
153       return null;
154     }
155
156     String targetChain;
157     for (int s = 0; s < sequence.length; s++)
158     {
159       if (targetChains != null && targetChains[s] != null)
160         targetChain = targetChains[s];
161       else if (sequence[s].getName().indexOf("|") > -1)
162       {
163         targetChain = sequence[s].getName().substring(
164                 sequence[s].getName().lastIndexOf("|") + 1);
165       }
166       else
167         targetChain = "";
168
169       int max = -10;
170       AlignSeq maxAlignseq = null;
171       String maxChainId = " ";
172       PDBChain maxChain = null;
173       boolean first = true;
174       for (int i = 0; i < pdb.chains.size(); i++)
175       {
176         // TODO: re http://issues.jalview.org/browse/JAL-583 : this patch may
177         // need to be revoked
178         PDBChain chain = ((PDBChain) pdb.chains.elementAt(i));
179         if (targetChain.length() > 0 && !targetChain.equals(chain.id))
180         {
181           continue; // don't try to map chains don't match.
182         }
183         // end of patch for limiting computed mappings
184         // TODO: correctly determine sequence type for mixed na/peptide
185         // structures
186         AlignSeq as = new AlignSeq(sequence[s],
187                 ((PDBChain) pdb.chains.elementAt(i)).sequence,
188                 ((PDBChain) pdb.chains.elementAt(i)).isNa ? AlignSeq.DNA
189                         : AlignSeq.PEP);
190         as.calcScoreMatrix();
191         as.traceAlignment();
192
193         if (first || as.maxscore > max
194                 || (as.maxscore == max && chain.id.equals(targetChain)))
195         {
196           first = false;
197           maxChain = chain;
198           max = as.maxscore;
199           maxAlignseq = as;
200           maxChainId = chain.id;
201         }
202       }
203       if (maxChain == null)
204       {
205         continue;
206       }
207       final StringBuffer mappingDetails = new StringBuffer();
208       mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
209               + maxChain.sequence.getSequenceAsString());
210       mappingDetails.append("\nNo of residues = "
211               + maxChain.residues.size() + "\n\n");
212       PrintStream ps = new PrintStream(System.out)
213       {
214         public void print(String x)
215         {
216           mappingDetails.append(x);
217         }
218
219         public void println()
220         {
221           mappingDetails.append("\n");
222         }
223       };
224
225       maxAlignseq.printAlignment(ps);
226
227       mappingDetails.append("\nPDB start/end " + maxAlignseq.seq2start
228               + " " + maxAlignseq.seq2end);
229       mappingDetails.append("\nSEQ start/end "
230               + (maxAlignseq.seq1start + sequence[s].getStart() - 1) + " "
231               + (maxAlignseq.seq1end + sequence[s].getEnd() - 1));
232
233       maxChain.makeExactMapping(maxAlignseq, sequence[s]);
234
235       maxChain.transferRESNUMFeatures(sequence[s], null);
236
237       // allocate enough slots to store the mapping from positions in
238       // sequence[s] to the associated chain
239       int[][] mapping = new int[sequence[s].findPosition(sequence[s].getLength()) + 2][2];
240       int resNum = -10000;
241       int index = 0;
242
243       do
244       {
245         Atom tmp = (Atom) maxChain.atoms.elementAt(index);
246         if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
247         {
248           resNum = tmp.resNumber;
249           mapping[tmp.alignmentMapping + 1][0] = tmp.resNumber;
250           mapping[tmp.alignmentMapping + 1][1] = tmp.atomIndex;
251         }
252
253         index++;
254       } while (index < maxChain.atoms.size());
255
256       if (mappings == null)
257       {
258         mappings = new StructureMapping[1];
259       }
260       else
261       {
262         StructureMapping[] tmp = new StructureMapping[mappings.length + 1];
263         System.arraycopy(mappings, 0, tmp, 0, mappings.length);
264         mappings = tmp;
265       }
266
267       if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
268         pdbFile = "INLINE" + pdb.id;
269
270       mappings[mappings.length - 1] = new StructureMapping(sequence[s],
271               pdbFile, pdb.id, maxChainId, mapping,
272               mappingDetails.toString());
273       maxChain.transferResidueAnnotation(mappings[mappings.length - 1]);
274     }
275     // ///////
276
277     return pdb;
278   }
279
280   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
281   {
282     listeners.removeElement(svl);
283     if (svl instanceof SequenceListener)
284     {
285       for (int i=0;i<listeners.size();i++)
286       {
287         if (listeners.elementAt(i) instanceof StructureListener)
288         {
289           ((StructureListener)listeners.elementAt(i)).releaseReferences(svl);
290         }
291       }
292     }
293       
294     if (pdbfiles == null)
295     {
296       return;
297     }
298     boolean removeMapping = true;
299     String[] handlepdbs;
300     Vector pdbs = new Vector();
301     for (int i = 0; i < pdbfiles.length; pdbs.addElement(pdbfiles[i++]))
302       ;
303     StructureListener sl;
304     for (int i = 0; i < listeners.size(); i++)
305     {
306       if (listeners.elementAt(i) instanceof StructureListener)
307       {
308         sl = (StructureListener) listeners.elementAt(i);
309         handlepdbs = sl.getPdbFile();
310         for (int j = 0; j < handlepdbs.length; j++)
311         {
312           if (pdbs.contains(handlepdbs[j]))
313           {
314             pdbs.removeElement(handlepdbs[j]);
315           }
316         }
317
318       }
319     }
320
321     if (pdbs.size() > 0 && mappings != null)
322     {
323       Vector tmp = new Vector();
324       for (int i = 0; i < mappings.length; i++)
325       {
326         if (!pdbs.contains(mappings[i].pdbfile))
327         {
328           tmp.addElement(mappings[i]);
329         }
330       }
331
332       mappings = new StructureMapping[tmp.size()];
333       tmp.copyInto(mappings);
334     }
335   }
336
337   public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
338   {
339     boolean hasSequenceListeners = handlingVamsasMo || seqmappings != null;
340     SearchResults results = null;
341     SequenceI lastseq = null;
342     int lastipos = -1, indexpos;
343     for (int i = 0; i < listeners.size(); i++)
344     {
345       if (listeners.elementAt(i) instanceof SequenceListener)
346       {
347         if (results == null)
348         {
349           results = new SearchResults();
350         }
351         if (mappings != null)
352         {
353           for (int j = 0; j < mappings.length; j++)
354           {
355             if (mappings[j].pdbfile.equals(pdbfile)
356                     && mappings[j].pdbchain.equals(chain))
357             {
358               indexpos = mappings[j].getSeqPos(pdbResNum);
359               if (lastipos != indexpos && lastseq != mappings[j].sequence)
360               {
361                 results.addResult(mappings[j].sequence, indexpos, indexpos);
362                 lastipos = indexpos;
363                 lastseq = mappings[j].sequence;
364                 // construct highlighted sequence list
365                 if (seqmappings != null)
366                 {
367
368                   Enumeration e = seqmappings.elements();
369                   while (e.hasMoreElements())
370
371                   {
372                     ((AlignedCodonFrame) e.nextElement()).markMappedRegion(
373                             mappings[j].sequence, indexpos, results);
374                   }
375                 }
376               }
377
378             }
379           }
380         }
381       }
382     }
383     if (results!=null)
384     {
385       for (int i = 0; i < listeners.size(); i++)
386       {
387         Object li = listeners.elementAt(i);
388         if (li instanceof SequenceListener)
389           ((SequenceListener) li).highlightSequence(results);
390       }
391     }
392   }
393
394   Vector seqmappings = null; // should be a simpler list of mapped seuqence
395
396   /**
397    * highlight regions associated with a position (indexpos) in seq
398    * 
399    * @param seq
400    *          the sequeence that the mouse over occured on
401    * @param indexpos
402    *          the absolute position being mouseovered in seq (0 to seq.length())
403    * @param index
404    *          the sequence position (if -1, seq.findPosition is called to
405    *          resolve the residue number)
406    */
407   public void mouseOverSequence(SequenceI seq, int indexpos, int index,
408           VamsasSource source)
409   {
410     boolean hasSequenceListeners = handlingVamsasMo || seqmappings != null;
411     SearchResults results = null;
412     if (index == -1)
413       index = seq.findPosition(indexpos);
414     StructureListener sl;
415     int atomNo = 0;
416     for (int i = 0; i < listeners.size(); i++)
417     {
418       if (listeners.elementAt(i) instanceof StructureListener)
419       {
420         sl = (StructureListener) listeners.elementAt(i);
421         if (mappings == null)
422         {
423           continue;
424         }
425         for (int j = 0; j < mappings.length; j++)
426         {
427           if (mappings[j].sequence == seq
428                   || mappings[j].sequence == seq.getDatasetSequence())
429           {
430             atomNo = mappings[j].getAtomNum(index);
431
432             if (atomNo > 0)
433             {
434               sl.highlightAtom(atomNo, mappings[j].getPDBResNum(index),
435                       mappings[j].pdbchain, mappings[j].pdbfile);
436             }
437           }
438         }
439       }
440       else
441       {
442         if (relaySeqMappings && hasSequenceListeners
443                 && listeners.elementAt(i) instanceof SequenceListener)
444         {
445           // DEBUG
446           // System.err.println("relay Seq " + seq.getDisplayId(false) + " " +
447           // index);
448
449           if (results == null)
450           {
451             results = new SearchResults();
452             if (index >= seq.getStart() && index <= seq.getEnd())
453             {
454               // construct highlighted sequence list
455
456               if (seqmappings != null)
457               {
458                 Enumeration e = seqmappings.elements();
459                 while (e.hasMoreElements())
460
461                 {
462                   ((AlignedCodonFrame) e.nextElement()).markMappedRegion(
463                           seq, index, results);
464                 }
465               }
466               // hasSequenceListeners = results.getSize() > 0;
467               if (handlingVamsasMo)
468               {
469                 // maybe have to resolve seq to a dataset seqeunce...
470                 // add in additional direct sequence and/or dataset sequence
471                 // highlighting
472                 results.addResult(seq, index, index);
473               }
474             }
475           }
476           if (hasSequenceListeners)
477           {
478             ((SequenceListener) listeners.elementAt(i))
479                     .highlightSequence(results);
480           }
481         }
482         else if (listeners.elementAt(i) instanceof VamsasListener
483                 && !handlingVamsasMo)
484         {
485           // DEBUG
486           // System.err.println("Vamsas from Seq " + seq.getDisplayId(false) + "
487           // " +
488           // index);
489           // pass the mouse over and absolute position onto the
490           // VamsasListener(s)
491           ((VamsasListener) listeners.elementAt(i)).mouseOver(seq,
492                   indexpos, source);
493         }
494       }
495     }
496   }
497
498   /**
499    * true if a mouse over event from an external (ie Vamsas) source is being
500    * handled
501    */
502   boolean handlingVamsasMo = false;
503
504   long lastmsg = 0;
505
506   /**
507    * as mouseOverSequence but only route event to SequenceListeners
508    * 
509    * @param sequenceI
510    * @param position
511    *          in an alignment sequence
512    */
513   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
514           VamsasSource source)
515   {
516     handlingVamsasMo = true;
517     long msg = sequenceI.hashCode() * (1 + position);
518     if (lastmsg != msg)
519     {
520       lastmsg = msg;
521       mouseOverSequence(sequenceI, position, -1, source);
522     }
523     handlingVamsasMo = false;
524   }
525
526   public Annotation[] colourSequenceFromStructure(SequenceI seq,
527           String pdbid)
528   {
529     return null;
530     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
531     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
532     /*
533      * Annotation [] annotations = new Annotation[seq.getLength()];
534      * 
535      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
536      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
537      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
538      * 
539      * for (int j = 0; j < mappings.length; j++) {
540      * 
541      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
542      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
543      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
544      * "+mappings[j].pdbfile);
545      * 
546      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
547      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
548      * 
549      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
550      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
551      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
552      * mappings[j].pdbfile); }
553      * 
554      * annotations[index] = new Annotation("X",null,' ',0,col); } return
555      * annotations; } } } }
556      * 
557      * return annotations;
558      */
559   }
560
561   public void structureSelectionChanged()
562   {
563   }
564
565   public void sequenceSelectionChanged()
566   {
567   }
568
569   public void sequenceColoursChanged(Object source)
570   {
571     StructureListener sl;
572     for (int i = 0; i < listeners.size(); i++)
573     {
574       if (listeners.elementAt(i) instanceof StructureListener)
575       {
576         sl = (StructureListener) listeners.elementAt(i);
577         sl.updateColours(source);
578       }
579     }
580   }
581
582   public StructureMapping[] getMapping(String pdbfile)
583   {
584     Vector tmp = new Vector();
585     if (mappings != null)
586     {
587       for (int i = 0; i < mappings.length; i++)
588       {
589         if (mappings[i].pdbfile.equals(pdbfile))
590         {
591           tmp.addElement(mappings[i]);
592         }
593       }
594     }
595     StructureMapping[] ret = new StructureMapping[tmp.size()];
596     for (int i = 0; i < tmp.size(); i++)
597     {
598       ret[i] = (StructureMapping) tmp.elementAt(i);
599     }
600
601     return ret;
602   }
603
604   public String printMapping(String pdbfile)
605   {
606     StringBuffer sb = new StringBuffer();
607     for (int i = 0; i < mappings.length; i++)
608     {
609       if (mappings[i].pdbfile.equals(pdbfile))
610       {
611         sb.append(mappings[i].mappingDetails);
612       }
613     }
614
615     return sb.toString();
616   }
617
618   private int[] seqmappingrefs = null; // refcount for seqmappings elements
619
620   private synchronized void modifySeqMappingList(boolean add,
621           AlignedCodonFrame[] codonFrames)
622   {
623     if (!add && (seqmappings == null || seqmappings.size() == 0))
624       return;
625     if (seqmappings == null)
626       seqmappings = new Vector();
627     if (codonFrames != null && codonFrames.length > 0)
628     {
629       for (int cf = 0; cf < codonFrames.length; cf++)
630       {
631         if (seqmappings.contains(codonFrames[cf]))
632         {
633           if (add)
634           {
635             seqmappingrefs[seqmappings.indexOf(codonFrames[cf])]++;
636           }
637           else
638           {
639             if (--seqmappingrefs[seqmappings.indexOf(codonFrames[cf])] <= 0)
640             {
641               int pos = seqmappings.indexOf(codonFrames[cf]);
642               int[] nr = new int[seqmappingrefs.length - 1];
643               if (pos > 0)
644               {
645                 System.arraycopy(seqmappingrefs, 0, nr, 0, pos);
646               }
647               if (pos < seqmappingrefs.length - 1)
648               {
649                 System.arraycopy(seqmappingrefs, pos + 1, nr, 0,
650                         seqmappingrefs.length - pos - 2);
651               }
652             }
653           }
654         }
655         else
656         {
657           if (add)
658           {
659             seqmappings.addElement(codonFrames[cf]);
660
661             int[] nsr = new int[(seqmappingrefs == null) ? 1
662                     : seqmappingrefs.length + 1];
663             if (seqmappingrefs != null && seqmappingrefs.length > 0)
664               System.arraycopy(seqmappingrefs, 0, nsr, 0,
665                       seqmappingrefs.length);
666             nsr[(seqmappingrefs == null) ? 0 : seqmappingrefs.length] = 1;
667             seqmappingrefs = nsr;
668           }
669         }
670       }
671     }
672   }
673
674   public void removeMappings(AlignedCodonFrame[] codonFrames)
675   {
676     modifySeqMappingList(false, codonFrames);
677   }
678
679   public void addMappings(AlignedCodonFrame[] codonFrames)
680   {
681     modifySeqMappingList(true, codonFrames);
682   }
683
684   Vector<SelectionListener> sel_listeners = new Vector<SelectionListener>();
685
686   public void addSelectionListener(SelectionListener selecter)
687   {
688     if (!sel_listeners.contains(selecter))
689     {
690       sel_listeners.addElement(selecter);
691     }
692   }
693
694   public void removeSelectionListener(SelectionListener toremove)
695   {
696     if (sel_listeners.contains(toremove))
697     {
698       sel_listeners.removeElement(toremove);
699     }
700   }
701
702   public synchronized void sendSelection(
703           jalview.datamodel.SequenceGroup selection,
704           jalview.datamodel.ColumnSelection colsel, SelectionSource source)
705   {
706     if (sel_listeners != null && sel_listeners.size() > 0)
707     {
708       Enumeration listeners = sel_listeners.elements();
709       while (listeners.hasMoreElements())
710       {
711         SelectionListener slis = ((SelectionListener) listeners
712                 .nextElement());
713         if (slis != source)
714         {
715           slis.selection(selection, colsel, source);
716         }
717         ;
718       }
719     }
720   }
721   
722   Vector<AlignmentViewPanelListener> view_listeners=new Vector<AlignmentViewPanelListener>();
723   public synchronized void sendViewPosition(jalview.api.AlignmentViewPanel source, int startRes,
724           int endRes, int startSeq, int endSeq)
725   {
726
727     if (view_listeners != null && view_listeners.size() > 0)
728     {
729       Enumeration<AlignmentViewPanelListener> listeners = view_listeners.elements();
730       while (listeners.hasMoreElements())
731       {
732         AlignmentViewPanelListener slis = listeners
733                 .nextElement();
734         if (slis != source)
735         {
736           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
737         }
738         ;
739       }
740     }
741   }
742   
743
744   public void finalize() throws Throwable {
745     if (listeners!=null) {
746       listeners.clear();
747       listeners=null;
748     }
749     if (mappingData!=null)
750     {
751       mappingData.clear();
752       mappingData=null;
753     }
754     if (sel_listeners!=null)
755     {
756       sel_listeners.clear();
757       sel_listeners=null;
758     }
759     if (view_listeners!=null)
760     {
761       view_listeners.clear();
762       view_listeners=null;
763     }
764     mappings=null;
765     seqmappingrefs=null;
766   }
767
768   /**
769    * release all references associated with this manager provider
770    * @param jalviewLite
771    */
772   public static void release(StructureSelectionManagerProvider jalviewLite)
773   {
774     StructureSelectionManager mnger=(instances.get(jalviewLite));
775     if (mnger!=null)
776     {
777       instances.remove(jalviewLite);
778       try {
779         mnger.finalize();
780       } catch (Throwable x){};
781     }
782   }
783
784 }