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