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