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