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