upgrading structure/sequence binding so that a structure view can contain many pdb...
[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
347         for (int j = 0; j < mappings.length; j++)
348         {
349           if (mappings[j].sequence == seq
350                   || mappings[j].sequence == seq.getDatasetSequence())
351           {
352             atomNo = mappings[j].getAtomNum(index);
353
354             if (atomNo > 0)
355             {
356               sl.highlightAtom(atomNo, mappings[j].getPDBResNum(index),
357                       mappings[j].pdbchain, mappings[j].pdbfile);
358             }
359           }
360         }
361       }
362       else
363       {
364         if (relaySeqMappings && hasSequenceListeners
365                 && listeners.elementAt(i) instanceof SequenceListener)
366         {
367           // DEBUG
368           // System.err.println("relay Seq " + seq.getDisplayId(false) + " " +
369           // index);
370
371           if (results == null)
372           {
373             results = new SearchResults();
374             if (index >= seq.getStart() && index <= seq.getEnd())
375             {
376               // construct highlighted sequence list
377
378               if (seqmappings != null)
379               {
380                 Enumeration e = seqmappings.elements();
381                 while (e.hasMoreElements())
382
383                 {
384                   ((AlignedCodonFrame) e.nextElement()).markMappedRegion(
385                           seq, index, results);
386                 }
387               }
388               // hasSequenceListeners = results.getSize() > 0;
389               if (handlingVamsasMo)
390               {
391                 // maybe have to resolve seq to a dataset seqeunce...
392                 // add in additional direct sequence and/or dataset sequence
393                 // highlighting
394                 results.addResult(seq, index, index);
395               }
396             }
397           }
398           if (hasSequenceListeners)
399           {
400             ((SequenceListener) listeners.elementAt(i))
401                     .highlightSequence(results);
402           }
403         }
404         else if (listeners.elementAt(i) instanceof VamsasListener
405                 && !handlingVamsasMo)
406         {
407           // DEBUG
408           // System.err.println("Vamsas from Seq " + seq.getDisplayId(false) + "
409           // " +
410           // index);
411           // pass the mouse over and absolute position onto the
412           // VamsasListener(s)
413           ((VamsasListener) listeners.elementAt(i))
414                   .mouseOver(seq, indexpos);
415         }
416       }
417     }
418   }
419
420   /**
421    * true if a mouse over event from an external (ie Vamsas) source is being
422    * handled
423    */
424   boolean handlingVamsasMo = false;
425
426   long lastmsg = 0;
427
428   /**
429    * as mouseOverSequence but only route event to SequenceListeners
430    * 
431    * @param sequenceI
432    * @param position
433    *          in an alignment sequence
434    */
435   public void mouseOverVamsasSequence(SequenceI sequenceI, int position)
436   {
437     handlingVamsasMo = true;
438     long msg = sequenceI.hashCode() * (1 + position);
439     if (lastmsg != msg)
440     {
441       lastmsg = msg;
442       mouseOverSequence(sequenceI, position, -1);
443     }
444     handlingVamsasMo = false;
445   }
446
447   public Annotation[] colourSequenceFromStructure(SequenceI seq,
448           String pdbid)
449   {
450     return null;
451     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
452     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
453     /*
454      * Annotation [] annotations = new Annotation[seq.getLength()];
455      * 
456      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
457      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
458      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
459      * 
460      * for (int j = 0; j < mappings.length; j++) {
461      * 
462      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
463      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
464      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
465      * "+mappings[j].pdbfile);
466      * 
467      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
468      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
469      * 
470      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
471      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
472      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
473      * mappings[j].pdbfile); }
474      * 
475      * annotations[index] = new Annotation("X",null,' ',0,col); } return
476      * annotations; } } } }
477      * 
478      * return annotations;
479      */
480   }
481
482   public void structureSelectionChanged()
483   {
484   }
485
486   public void sequenceSelectionChanged()
487   {
488   }
489
490   public void sequenceColoursChanged(Object source)
491   {
492     StructureListener sl;
493     for (int i = 0; i < listeners.size(); i++)
494     {
495       if (listeners.elementAt(i) instanceof StructureListener)
496       {
497         sl = (StructureListener) listeners.elementAt(i);
498         sl.updateColours(source);
499       }
500     }
501   }
502
503   public StructureMapping[] getMapping(String pdbfile)
504   {
505     Vector tmp = new Vector();
506     for (int i = 0; i < mappings.length; i++)
507     {
508       if (mappings[i].pdbfile.equals(pdbfile))
509       {
510         tmp.addElement(mappings[i]);
511       }
512     }
513
514     StructureMapping[] ret = new StructureMapping[tmp.size()];
515     for (int i = 0; i < tmp.size(); i++)
516     {
517       ret[i] = (StructureMapping) tmp.elementAt(i);
518     }
519
520     return ret;
521   }
522
523   public String printMapping(String pdbfile)
524   {
525     StringBuffer sb = new StringBuffer();
526     for (int i = 0; i < mappings.length; i++)
527     {
528       if (mappings[i].pdbfile.equals(pdbfile))
529       {
530         sb.append(mappings[i].mappingDetails);
531       }
532     }
533
534     return sb.toString();
535   }
536
537   private int[] seqmappingrefs = null; // refcount for seqmappings elements
538
539   private synchronized void modifySeqMappingList(boolean add,
540           AlignedCodonFrame[] codonFrames)
541   {
542     if (!add && (seqmappings == null || seqmappings.size() == 0))
543       return;
544     if (seqmappings == null)
545       seqmappings = new Vector();
546     if (codonFrames != null && codonFrames.length > 0)
547     {
548       for (int cf = 0; cf < codonFrames.length; cf++)
549       {
550         if (seqmappings.contains(codonFrames[cf]))
551         {
552           if (add)
553           {
554             seqmappingrefs[seqmappings.indexOf(codonFrames[cf])]++;
555           }
556           else
557           {
558             if (--seqmappingrefs[seqmappings.indexOf(codonFrames[cf])] <= 0)
559             {
560               int pos = seqmappings.indexOf(codonFrames[cf]);
561               int[] nr = new int[seqmappingrefs.length - 1];
562               if (pos > 0)
563               {
564                 System.arraycopy(seqmappingrefs, 0, nr, 0, pos);
565               }
566               if (pos < seqmappingrefs.length - 1)
567               {
568                 System.arraycopy(seqmappingrefs, pos + 1, nr, 0,
569                         seqmappingrefs.length - pos - 2);
570               }
571             }
572           }
573         }
574         else
575         {
576           if (add)
577           {
578             seqmappings.addElement(codonFrames[cf]);
579
580             int[] nsr = new int[(seqmappingrefs == null) ? 1
581                     : seqmappingrefs.length + 1];
582             if (seqmappingrefs != null && seqmappingrefs.length > 0)
583               System.arraycopy(seqmappingrefs, 0, nsr, 0,
584                       seqmappingrefs.length);
585             nsr[(seqmappingrefs == null) ? 0 : seqmappingrefs.length] = 1;
586             seqmappingrefs = nsr;
587           }
588         }
589       }
590     }
591   }
592
593   public void removeMappings(AlignedCodonFrame[] codonFrames)
594   {
595     modifySeqMappingList(false, codonFrames);
596   }
597
598   public void addMappings(AlignedCodonFrame[] codonFrames)
599   {
600     modifySeqMappingList(true, codonFrames);
601   }
602
603   Vector sel_listeners = new Vector();
604
605   public void addSelectionListener(SelectionListener selecter)
606   {
607     if (!sel_listeners.contains(selecter))
608     {
609       sel_listeners.addElement(selecter);
610     }
611   }
612
613   public void removeSelectionListener(SelectionListener toremove)
614   {
615     if (sel_listeners.contains(toremove))
616     {
617       sel_listeners.removeElement(toremove);
618     }
619   }
620
621   public synchronized void sendSelection(
622           jalview.datamodel.SequenceGroup selection,
623           jalview.datamodel.ColumnSelection colsel, SelectionSource source)
624   {
625     if (sel_listeners != null && sel_listeners.size() > 0)
626     {
627       Enumeration listeners = sel_listeners.elements();
628       while (listeners.hasMoreElements())
629       {
630         SelectionListener slis = ((SelectionListener) listeners
631                 .nextElement());
632         if (slis != source)
633         {
634           slis.selection(selection, colsel, source);
635         }
636         ;
637       }
638     }
639   }
640 }