GPL license added
[jalview.git] / src / jalview / gui / SeqPanel.java
1 /*\r
2 * Jalview - A Sequence Alignment Editor and Viewer\r
3 * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4 *\r
5 * This program is free software; you can redistribute it and/or\r
6 * modify it under the terms of the GNU General Public License\r
7 * as published by the Free Software Foundation; either version 2\r
8 * of the License, or (at your option) any later version.\r
9 *\r
10 * This program is distributed in the hope that it will be useful,\r
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 * GNU General Public License for more details.\r
14 *\r
15 * You should have received a copy of the GNU General Public License\r
16 * along with this program; if not, write to the Free Software\r
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18 */\r
19 \r
20 package jalview.gui;\r
21 \r
22 import java.awt.*;\r
23 import java.awt.event.*;\r
24 import jalview.datamodel.*;\r
25 import javax.swing.*;\r
26 import java.util.*;\r
27 import jalview.schemes.*;\r
28 import jalview.analysis.Conservation;\r
29 \r
30 public class SeqPanel extends JPanel\r
31 {\r
32 \r
33   public    SeqCanvas         seqCanvas;\r
34   public    AlignmentPanel    ap;\r
35 \r
36   protected int lastres;\r
37   protected int startseq;\r
38   int startEdit=-1;\r
39   int endEdit=-1;\r
40 \r
41   protected AlignViewport av;\r
42 \r
43   // if character is inserted or deleted, we will need to recalculate the conservation\r
44   int seqEditOccurred = -1;\r
45 \r
46   ScrollThread scrollThread = null;\r
47   boolean mouseDragging = false;\r
48 \r
49   boolean editingSeqs = false;\r
50   boolean groupEditing = false;\r
51 \r
52 \r
53   public SeqPanel(AlignViewport avp, AlignmentPanel p) {\r
54     this.av         = avp;\r
55 \r
56     seqCanvas  = new SeqCanvas(avp);\r
57     setLayout(new BorderLayout());\r
58     add(seqCanvas, BorderLayout.CENTER);\r
59 \r
60     ap = p;\r
61 \r
62     addMouseMotionListener( new MouseMotionAdapter()\r
63     {\r
64       public void mouseMoved(MouseEvent evt)\r
65       {\r
66        if(av.getWrapAlignment())\r
67          return;\r
68         doMouseMoved(evt);    }\r
69 \r
70       public void mouseDragged(MouseEvent evt)\r
71       {\r
72         if(av.getWrapAlignment())\r
73          return;\r
74         if( editingSeqs )\r
75           doMouseDragged(evt);\r
76         else\r
77           doMouseDraggedDefineMode(evt);\r
78       }\r
79     });\r
80 \r
81     addMouseListener( new MouseAdapter()\r
82     {\r
83       public void mouseReleased(MouseEvent evt)\r
84       {\r
85         if(av.getWrapAlignment())\r
86          return;\r
87         if(editingSeqs)\r
88           doMouseReleased(evt);\r
89         else\r
90           doMouseReleasedDefineMode(evt);\r
91 \r
92       }\r
93       public void mousePressed(MouseEvent evt)\r
94       {\r
95         if(av.getWrapAlignment())\r
96          return;\r
97         if(evt.isShiftDown() || evt.isAltDown() || evt.isControlDown())\r
98         {\r
99           if(evt.isAltDown() || evt.isControlDown())\r
100             groupEditing = true;\r
101 \r
102           editingSeqs = true;\r
103           doMousePressed(evt);\r
104         }\r
105         else\r
106           doMousePressedDefineMode(evt);\r
107       }\r
108       public void mouseExited(MouseEvent evt)\r
109       {\r
110         if (av.getWrapAlignment() || editingSeqs)\r
111                 return;\r
112               doMouseExitedDefineMode(evt);\r
113 \r
114       }\r
115       public void mouseEntered(MouseEvent evt)\r
116       {\r
117         if (av.getWrapAlignment() || editingSeqs)\r
118           return;\r
119         doMouseEnteredDefineMode(evt);\r
120       }\r
121 \r
122     });\r
123     repaint();\r
124   }\r
125 \r
126 \r
127   public void doMouseReleased(MouseEvent evt) {\r
128 \r
129     if(seqEditOccurred>-1)\r
130       editOccurred(seqEditOccurred);\r
131 \r
132     startseq = -1;\r
133     lastres  = -1;\r
134     seqEditOccurred = -1;\r
135     editingSeqs  = false;\r
136     groupEditing = false;\r
137 \r
138     ap.repaint();\r
139   }\r
140 \r
141   public void doMousePressed(MouseEvent evt) {\r
142 \r
143     ap.alignFrame.addHistoryItem( new HistoryItem(\r
144         "Edit Sequence",av.alignment, HistoryItem.EDIT));\r
145 \r
146     int seq;\r
147     int res;\r
148 \r
149     int x = evt.getX();\r
150     int y = evt.getY();\r
151 \r
152     res = x/av.getCharWidth() + av.getStartRes();\r
153     seq = y/av.getCharHeight() + av.getStartSeq();\r
154 \r
155     if (seq < av.getAlignment().getHeight() &&\r
156         res < av.getAlignment().getSequenceAt(seq).getLength())\r
157     {\r
158       startseq = seq;\r
159       lastres = res;\r
160     }\r
161     else\r
162     {\r
163       startseq = -1;\r
164       lastres = -1;\r
165     }\r
166 \r
167     startEdit = lastres;\r
168     endEdit = lastres;\r
169 \r
170     return;\r
171   }\r
172 \r
173   public void doMouseMoved(MouseEvent evt)\r
174   {\r
175     int res=0, seq=0;\r
176     int x = evt.getX();\r
177     int y = evt.getY();\r
178     if(av.wrapAlignment)\r
179     {\r
180       y -= 2*av.charHeight;\r
181       int chunkHeight = (av.getAlignment().getHeight()+2)*av.charHeight;\r
182 \r
183 \r
184       res =   (int)((y/chunkHeight)*(getWidth()/av.charWidth)) +  x/av.getCharWidth() + av.getStartRes();\r
185 \r
186       y %= chunkHeight;\r
187       seq =     y / av.getCharHeight() + av.getStartSeq();\r
188 \r
189     }\r
190     else\r
191     {\r
192       res = x / av.getCharWidth()  + av.getStartRes();\r
193       seq = y / av.getCharHeight() + av.getStartSeq();\r
194     }\r
195 \r
196 \r
197     if(seq>=av.getAlignment().getHeight())\r
198       return;\r
199 \r
200     SequenceI sequence = av.getAlignment().getSequenceAt(seq);\r
201     if(res>sequence.getLength())\r
202       return;\r
203 \r
204     Object obj = ResidueProperties.aa2Triplet.get( sequence.getCharAt(res)+"" ) ;\r
205     String aa = "";\r
206     if(obj!=null)\r
207          aa = obj.toString();\r
208 \r
209     StringBuffer text = new StringBuffer("Sequence " +(seq+1)+" ID: "+sequence.getName());\r
210     if(aa!="")\r
211       text.append("  Residue: "+aa+" ("+  av.getAlignment().getSequenceAt(seq).findPosition(res)+")");\r
212 \r
213     ap.alignFrame.statusBar.setText(text.toString());\r
214 \r
215     // use aa to see if the mouse pointer is on a\r
216     if(  av.showSequenceFeatures)\r
217     {\r
218       Vector features = sequence.getSequenceFeatures();\r
219       Enumeration e = features.elements();\r
220       StringBuffer sbuffer = new StringBuffer();\r
221 \r
222 \r
223       while (e.hasMoreElements())\r
224       {\r
225         SequenceFeature sf = (SequenceFeature) e.nextElement();\r
226         if (sf.getStart() <= sequence.findPosition(res) &&\r
227             sf.getEnd() >= sequence.findPosition(res))\r
228         {\r
229           if(sbuffer.length()>0)\r
230             sbuffer.append("; ");\r
231           sbuffer.append(sf.getType() + " " + sf.getDescription());\r
232           if(sf.getStatus().length()>0)\r
233             sbuffer.append(" ("+sf.getStatus()+")");\r
234         }\r
235 \r
236       }\r
237 \r
238       ToolTipManager.sharedInstance().registerComponent(this);\r
239       this.setToolTipText(sbuffer.toString());\r
240     }\r
241 \r
242 \r
243   }\r
244 \r
245   public void doMouseDragged(MouseEvent evt) {\r
246 \r
247     // If we're dragging we're editing\r
248     int res = evt.getX() / av.getCharWidth() + av.getStartRes();\r
249     if (res < 0)\r
250       res = 0;\r
251 \r
252     if (lastres == -1 || lastres == res)\r
253       return;\r
254 \r
255     boolean dragRight = true;\r
256     if (res < av.getAlignment().getWidth() && res < lastres)\r
257       dragRight = false;\r
258 \r
259 \r
260     if (res != lastres)\r
261     {\r
262         // Group editing\r
263         if (groupEditing)\r
264         {\r
265           SequenceGroup sg = av.getSelectionGroup();\r
266           if(sg==null)\r
267           {\r
268             lastres=-1;\r
269             return;\r
270           }\r
271 \r
272             // drag to right\r
273             if(dragRight)\r
274                 sg.setEndRes(sg.getEndRes() + (res-lastres));\r
275 \r
276             // drag to left\r
277             else\r
278             {\r
279                /// Are we able to delete?\r
280                // ie are all columns blank?\r
281                boolean deleteAllowed = false;\r
282               for (int s = 0; s < sg.getSize(); s++)\r
283               {\r
284                 SequenceI seq = sg.getSequenceAt(s);\r
285                 for (int j=res; j<lastres; j++)\r
286                 {\r
287                   if(seq.getSequence().length()<=j)\r
288                     continue;\r
289 \r
290                   if(!jalview.util.Comparison.isGap(seq.getSequence().charAt(j)))\r
291                   {\r
292                     // Not a gap, block edit not valid\r
293                     res=j+1;\r
294                     deleteAllowed = false;\r
295                     continue;\r
296                   }\r
297                   deleteAllowed = true;\r
298                 }\r
299               }\r
300 \r
301               if(!deleteAllowed)\r
302               {\r
303                 lastres = -1;\r
304                 return;\r
305               }\r
306 \r
307               sg.setEndRes(sg.getEndRes() - (lastres-res));\r
308             }\r
309 \r
310 \r
311             for (int i = 0; i < sg.getSize(); i++)\r
312             {\r
313               SequenceI s = sg.getSequenceAt(i);\r
314               int k = av.alignment.findIndex(s);\r
315 \r
316               // drag to right\r
317               if (dragRight)\r
318                 for (int j = lastres; j < res; j++)\r
319                   insertChar(j, k);\r
320 \r
321               // drag to left\r
322               else\r
323               {\r
324                 for (int j = res; j < lastres; j++)\r
325                 {\r
326                   if(s.getLength()>j)\r
327                     deleteChar(res, k);\r
328                 }\r
329               }\r
330             }\r
331         }\r
332         else /////Editing a single sequence///////////\r
333         {\r
334           if (res < av.getAlignment().getWidth() && res > lastres)\r
335           {\r
336             // dragging to the right\r
337             for (int j = lastres; j < res; j++)\r
338               insertChar(j, startseq);\r
339           }\r
340           else if (res < av.getAlignment().getWidth() && res < lastres)\r
341           {\r
342             // dragging to the left\r
343             for (int j = lastres; j > res; j--)\r
344             {\r
345               if( jalview.util.Comparison.isGap(\r
346                 av.alignment.getSequenceAt(startseq).getSequence().charAt(res)))\r
347 \r
348               deleteChar(res, startseq);\r
349               else\r
350               {\r
351 \r
352                 break;\r
353               }\r
354             }\r
355           }\r
356 \r
357         }\r
358     }\r
359 \r
360     endEdit = res;\r
361     lastres = res;\r
362     repaint();\r
363   }\r
364 \r
365   public void drawChars(int seqstart, int seqend, int start) {\r
366     seqCanvas.drawPanel(seqCanvas.gg, start,av.getEndRes(),seqstart,seqend,av.getStartRes(),av.getStartSeq(),0);\r
367     repaint();\r
368   }\r
369 \r
370   public void insertChar(int j, int seq)\r
371   {\r
372     av.alignment.getSequenceAt(seq).insertCharAt(j, av.getGapCharacter());\r
373     seqEditOccurred=seq;\r
374   }\r
375 \r
376   public void deleteChar(int j, int seq)\r
377   {\r
378     av.alignment.getSequenceAt(seq).deleteCharAt(j);\r
379     seqEditOccurred=seq;\r
380 \r
381     av.alignment.getWidth();\r
382     repaint();\r
383   }\r
384 \r
385 \r
386   void editOccurred(int i)\r
387   {\r
388     if(endEdit==startEdit)\r
389     {\r
390       ap.alignFrame.historyList.pop();\r
391       ap.alignFrame.updateEditMenuBar();\r
392     }\r
393 \r
394     av.updateConservation();\r
395     av.updateConsensus();\r
396 \r
397     // Y O Y CLUSTALX\r
398     ColourSchemeI cs = av.getGlobalColourScheme();\r
399     if(cs instanceof ConservationColourScheme)\r
400     {\r
401       ConservationColourScheme ccs =  (ConservationColourScheme) cs;\r
402       if(ccs.cs instanceof ClustalxColourScheme)\r
403      {\r
404         Conservation c = new Conservation("All",\r
405                                           ResidueProperties.propHash, 3,\r
406                                           av.alignment.getSequences(), 0,\r
407                                           av.alignment.getWidth() - 1);\r
408         c.calculate();\r
409         c.verdict(false, av.ConsPercGaps);\r
410 \r
411         ClustalxColourScheme cxs = (ClustalxColourScheme)ccs.cs;\r
412         cxs.resetClustalX(av.alignment.getSequences(),  av.alignment.getWidth());\r
413         ccs = new ConservationColourScheme(c, cxs);\r
414         av.setGlobalColourScheme(ccs);\r
415      }\r
416     }\r
417 \r
418     if(cs instanceof ClustalxColourScheme)\r
419     {\r
420       ((ClustalxColourScheme)cs).resetClustalX(av.alignment.getSequences(),\r
421                                     av.alignment.getWidth());\r
422       av.setGlobalColourScheme(cs);\r
423     }\r
424 \r
425   }\r
426 \r
427 //////////////////////////////////////////\r
428 /////Everything below this is for defining the boundary of the rubberband\r
429 //////////////////////////////////////////\r
430   int oldSeq = -1;\r
431   public void doMousePressedDefineMode(MouseEvent evt)\r
432   {\r
433     int res = evt.getX()/av.getCharWidth() + av.getStartRes();\r
434     int seq = evt.getY()/av.getCharHeight() + av.getStartSeq();\r
435     oldSeq = seq;\r
436 \r
437     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);\r
438 \r
439     if(sequence==null || res>sequence.getLength())\r
440       return;\r
441 \r
442     stretchGroup = av.getSelectionGroup();\r
443 \r
444     if(stretchGroup == null)\r
445      {\r
446        stretchGroup = av.alignment.findGroup( sequence );\r
447        if(stretchGroup!=null && res>stretchGroup.getStartRes() && res<stretchGroup.getEndRes())\r
448          av.setSelectionGroup(stretchGroup);\r
449        else\r
450          stretchGroup = null;\r
451      }\r
452 \r
453     else if(!stretchGroup.sequences.contains(sequence)\r
454             || stretchGroup.getStartRes()>res\r
455             || stretchGroup.getEndRes()<res)\r
456      {\r
457        stretchGroup = null;\r
458 \r
459        SequenceGroup[] allGroups = av.alignment.findAllGroups( sequence );\r
460 \r
461        if (allGroups != null)\r
462          for (int i = 0; i < allGroups.length; i++)\r
463            if (allGroups[i].getStartRes() <= res &&\r
464                allGroups[i].getEndRes() >= res)\r
465            {\r
466              stretchGroup = allGroups[i];\r
467              av.setSelectionGroup(stretchGroup);\r
468              break;\r
469            }\r
470      }\r
471 \r
472     if(stretchGroup==null)\r
473     {\r
474       // define a new group here\r
475       SequenceGroup sg = new SequenceGroup();\r
476       sg.setStartRes(res);\r
477       sg.setEndRes(res);\r
478       sg.addSequence( sequence );\r
479       av.setSelectionGroup( sg );\r
480       stretchGroup = sg;\r
481 \r
482       if(av.getConservationSelected())\r
483         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(), "Background");\r
484       if(av.getAbovePIDThreshold())\r
485         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(), "Background");\r
486 \r
487     }\r
488     else if( javax.swing.SwingUtilities.isRightMouseButton(evt))\r
489     {\r
490         jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu( ap , null);\r
491         pop.show(this, evt.getX(), evt.getY());\r
492 \r
493     // edit the properties of existing group\r
494     }\r
495 \r
496     if(stretchGroup!=null && stretchGroup.getEndRes()==res)\r
497       // Edit end res position of selected group\r
498       changeEndRes = true;\r
499 \r
500    else if(stretchGroup!=null && stretchGroup.getStartRes()==res)\r
501       // Edit end res position of selected group\r
502       changeStartRes = true;\r
503 \r
504     stretchGroup.getWidth();\r
505 \r
506     repaint();\r
507 \r
508   }\r
509 \r
510   boolean changeEndSeq = false;\r
511   boolean changeStartSeq = false;\r
512   boolean changeEndRes = false;\r
513   boolean changeStartRes = false;\r
514   SequenceGroup stretchGroup = null;\r
515 \r
516   public void doMouseReleasedDefineMode(MouseEvent evt)\r
517   {\r
518     mouseDragging = false;\r
519 \r
520     if(stretchGroup==null)\r
521       return;\r
522 \r
523     if(stretchGroup.cs instanceof ClustalxColourScheme)\r
524     {\r
525       stretchGroup.cs = new ClustalxColourScheme(stretchGroup.sequences, av.alignment.getWidth());\r
526       repaint();\r
527     }\r
528 \r
529     else if(stretchGroup.cs instanceof ConservationColourScheme)\r
530     {\r
531        ConservationColourScheme ccs = (ConservationColourScheme)stretchGroup.cs;\r
532        stretchGroup.cs = ccs;\r
533        SliderPanel.setConservationSlider(ap, stretchGroup.cs, stretchGroup.getName()) ;\r
534 \r
535        repaint();\r
536     }\r
537     else\r
538         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs, stretchGroup.getName());\r
539 \r
540 \r
541     changeEndRes = false;\r
542     changeStartRes = false;\r
543     stretchGroup = null;\r
544     ap.idPanel.repaint();\r
545   }\r
546 \r
547 \r
548   boolean remove = false;\r
549   public void doMouseDraggedDefineMode(MouseEvent evt)\r
550   {\r
551     int res = evt.getX()/av.getCharWidth() + av.getStartRes();\r
552     int y = evt.getY()/av.getCharHeight() + av.getStartSeq();\r
553 \r
554     if(stretchGroup==null)\r
555       return;\r
556 \r
557     if(res>av.alignment.getWidth())\r
558       res = av.alignment.getWidth()-1;\r
559 \r
560 \r
561     if(stretchGroup.getEndRes()==res)\r
562       // Edit end res position of selected group\r
563       changeEndRes = true;\r
564 \r
565     else if(stretchGroup.getStartRes()==res)\r
566       // Edit start res position of selected group\r
567       changeStartRes = true;\r
568 \r
569 \r
570     if(res<av.getStartRes())\r
571       res = av.getStartRes();\r
572     else if(res>av.getEndRes())\r
573       res = av.getEndRes();\r
574 \r
575     if(changeEndRes)\r
576     {\r
577       if(res>stretchGroup.getStartRes()-1)\r
578         stretchGroup.setEndRes( res );\r
579     }\r
580     else if(changeStartRes)\r
581     {\r
582       if(res<stretchGroup.getEndRes()+1)\r
583         stretchGroup.setStartRes( res );\r
584     }\r
585 \r
586     int dragDirection = 0;\r
587     if (y > oldSeq)\r
588       dragDirection = 1;\r
589     else if (y < oldSeq)\r
590       dragDirection = -1;\r
591 \r
592     while (y != oldSeq && oldSeq>0 && y<av.alignment.getHeight())\r
593     {\r
594       // This routine ensures we don't skip any sequences, as the\r
595       // selection is quite slow.\r
596       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
597 \r
598       oldSeq += dragDirection;\r
599       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
600 \r
601       if (stretchGroup.sequences.contains(nextSeq))\r
602       {\r
603         stretchGroup.deleteSequence(seq);\r
604         stretchGroup.deleteSequence(nextSeq);\r
605       }\r
606       else\r
607       {\r
608        if(seq!=null)\r
609         stretchGroup.addSequence(seq);\r
610         stretchGroup.addSequence(nextSeq);\r
611       }\r
612     }\r
613     oldSeq = y;\r
614     mouseDragging = true;\r
615     if(scrollThread!=null)\r
616       scrollThread.setEvent(evt);\r
617 \r
618     repaint();\r
619   }\r
620 \r
621   public void doMouseEnteredDefineMode(MouseEvent e)\r
622   {\r
623     if (scrollThread != null)\r
624       scrollThread.running = false;\r
625   }\r
626 \r
627   public void doMouseExitedDefineMode(MouseEvent e)\r
628   {\r
629     if (av.getWrapAlignment())\r
630       return;\r
631 \r
632     if(mouseDragging)\r
633       scrollThread = new ScrollThread();\r
634 \r
635   }\r
636   // this class allows scrolling off the bottom of the visible alignment\r
637   class ScrollThread extends Thread\r
638   {\r
639     MouseEvent evt;\r
640     boolean running = false;\r
641     public ScrollThread()\r
642     {\r
643       start();\r
644     }\r
645 \r
646     public void setEvent(MouseEvent e)\r
647     {\r
648       evt = e;\r
649     }\r
650 \r
651     public void stopScrolling()\r
652     {\r
653       running = false;\r
654     }\r
655 \r
656     public void run()\r
657     {\r
658       running = true;\r
659       while (running)\r
660       {\r
661         if(evt!=null)\r
662         {\r
663 \r
664           if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)\r
665             running = ap.scrollUp(true);\r
666 \r
667           if (mouseDragging && evt.getY() >= getHeight() &&\r
668               av.alignment.getHeight() > av.getEndSeq())\r
669             running = ap.scrollUp(false);\r
670 \r
671           if (mouseDragging && evt.getX() < 0)\r
672             running = ap.scrollRight(true);\r
673 \r
674           else if (mouseDragging && evt.getX() >= getWidth())\r
675             running = ap.scrollRight(false);\r
676         }\r
677 \r
678         try\r
679         {\r
680           Thread.sleep(75);\r
681         }\r
682         catch (Exception ex)\r
683         {}\r
684       }\r
685     }\r
686 }\r
687 \r
688 \r
689 \r
690 }\r
691 \r
692 \r
693 \r
694 \r