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