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