GPL license added
[jalview.git] / src / jalview / appletgui / 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.appletgui;\r
21 \r
22 import java.awt.*;\r
23 import java.awt.event.*;\r
24 import jalview.datamodel.*;\r
25 import java.util.*;\r
26 import jalview.schemes.*;\r
27 \r
28 \r
29 public class SeqPanel extends Panel\r
30 {\r
31 \r
32   public    SeqCanvas         seqCanvas;\r
33   public    AlignmentPanel    ap;\r
34 \r
35   protected int lastres;\r
36   protected int startseq;\r
37   int startEdit=-1;\r
38   int endEdit=-1;\r
39 \r
40   protected AlignViewport av;\r
41 \r
42   // if character is inserted or deleted, we will need to recalculate the conservation\r
43   int seqEditOccurred = -1;\r
44 \r
45   ScrollThread scrollThread = null;\r
46   boolean mouseDragging = false;\r
47 \r
48   boolean editingSeqs = false;\r
49   boolean groupEditing = false;\r
50 \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);\r
59 \r
60     ap = p;\r
61 \r
62     seqCanvas.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     seqCanvas.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     seqCanvas.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     ap.repaint();\r
138   }\r
139 \r
140   public void doMousePressed(MouseEvent evt) {\r
141     ap.alignFrame.addHistoryItem( new HistoryItem(\r
142         "Edit Sequence",av.alignment, HistoryItem.EDIT));\r
143     int seq;\r
144     int res;\r
145 \r
146     int x = evt.getX();\r
147     int y = evt.getY();\r
148 \r
149     res = x/av.getCharWidth() + av.getStartRes();\r
150     seq = y/av.getCharHeight() + av.getStartSeq();\r
151 \r
152     if (seq < av.getAlignment().getHeight() &&\r
153         res < av.getAlignment().getSequenceAt(seq).getLength())\r
154     {\r
155       //char resstr = align.getSequenceAt(seq).getSequence().charAt(res);\r
156       // Find the residue's position in the sequence (res is the position\r
157       // in the alignment\r
158 \r
159       startseq = seq;\r
160       lastres = res;\r
161     }\r
162     else\r
163     {\r
164       startseq = -1;\r
165       lastres = -1;\r
166     }\r
167 \r
168     return;\r
169   }\r
170 \r
171   public void doMouseMoved(MouseEvent evt)\r
172   {\r
173     int res=0, seq=0;\r
174     int x = evt.getX();\r
175     int y = evt.getY();\r
176     if(av.wrapAlignment)\r
177     {\r
178       y -= 2*av.charHeight;\r
179       int chunkHeight = (av.getAlignment().getHeight()+2)*av.charHeight;\r
180 \r
181 \r
182       res =   (int)((y/chunkHeight)*(getSize().width/av.charWidth)) +  x/av.getCharWidth() + av.getStartRes();\r
183 \r
184       y %= chunkHeight;\r
185       seq =     y / av.getCharHeight() + av.getStartSeq();\r
186 \r
187     }\r
188     else\r
189     {\r
190       res = x / av.getCharWidth()  + av.getStartRes();\r
191       seq = y / av.getCharHeight() + av.getStartSeq();\r
192     }\r
193 \r
194 \r
195     if(seq>=av.getAlignment().getHeight())\r
196       return;\r
197 \r
198     SequenceI sequence = av.getAlignment().getSequenceAt(seq);\r
199     if(res>sequence.getLength())\r
200       return;\r
201 \r
202     Object obj = ResidueProperties.aa2Triplet.get( sequence.getCharAt(res)+"" ) ;\r
203     String aa = "";\r
204     if(obj!=null)\r
205          aa = obj.toString();\r
206 \r
207     StringBuffer text = new StringBuffer("Sequence " +(seq+1)+" ID: "+sequence.getName());\r
208     if(aa!="")\r
209       text.append("  Residue: "+aa+" ("+  av.getAlignment().getSequenceAt(seq).findPosition(res)+")");\r
210 \r
211     ap.alignFrame.statusBar.setText(text.toString());\r
212 \r
213     // use aa to see if the mouse pointer is on a\r
214     if(  av.showSequenceFeatures)\r
215     {\r
216       Vector features = sequence.getSequenceFeatures();\r
217       Enumeration e = features.elements();\r
218       StringBuffer sbuffer = new StringBuffer();\r
219 \r
220 \r
221       while (e.hasMoreElements())\r
222       {\r
223         SequenceFeature sf = (SequenceFeature) e.nextElement();\r
224         if (sf.getStart() <= sequence.findPosition(res) &&\r
225             sf.getEnd() >= sequence.findPosition(res))\r
226         {\r
227           if(sbuffer.length()>0)\r
228             sbuffer.append("; ");\r
229           sbuffer.append(sf.getType() + " " + sf.getDescription());\r
230           if(sf.getStatus().length()>0)\r
231             sbuffer.append(" ("+sf.getStatus()+")");\r
232         }\r
233 \r
234       }\r
235     }\r
236 \r
237 \r
238   }\r
239 \r
240   public void doMouseDragged(MouseEvent evt) {\r
241 \r
242       // If we're dragging we're editing\r
243       int res = evt.getX() / av.getCharWidth() + av.getStartRes();\r
244       if (res < 0)\r
245         res = 0;\r
246 \r
247       if (lastres == -1 || lastres == res)\r
248         return;\r
249 \r
250       boolean dragRight = true;\r
251       if (res < av.getAlignment().getWidth() && res < lastres)\r
252         dragRight = false;\r
253 \r
254 \r
255       if (res != lastres)\r
256      {\r
257          // Group editing\r
258          if (groupEditing)\r
259          {\r
260            SequenceGroup sg = av.getSelectionGroup();\r
261            if(sg==null)\r
262            {\r
263              lastres=-1;\r
264              return;\r
265            }\r
266 \r
267              // drag to right\r
268              if(dragRight)\r
269                  sg.setEndRes(sg.getEndRes() + (res-lastres));\r
270 \r
271              // drag to left\r
272              else\r
273              {\r
274                 /// Are we able to delete?\r
275                 // ie are all columns blank?\r
276                 boolean deleteAllowed = false;\r
277                for (int s = 0; s < sg.getSize(); s++)\r
278                {\r
279                  SequenceI seq = sg.getSequenceAt(s);\r
280                  for (int j=res; j<lastres; j++)\r
281                  {\r
282                    if(seq.getSequence().length()<=j)\r
283                      continue;\r
284 \r
285                    if(!jalview.util.Comparison.isGap(seq.getSequence().charAt(j)))\r
286                    {\r
287                      // Not a gap, block edit not valid\r
288                      res=j+1;\r
289                      deleteAllowed = false;\r
290                      continue;\r
291                    }\r
292                    deleteAllowed = true;\r
293                  }\r
294                }\r
295 \r
296                if(!deleteAllowed)\r
297                {\r
298                  lastres = -1;\r
299                  return;\r
300                }\r
301 \r
302                sg.setEndRes(sg.getEndRes() - (lastres-res));\r
303              }\r
304 \r
305 \r
306              for (int i = 0; i < sg.getSize(); i++)\r
307              {\r
308                SequenceI s = sg.getSequenceAt(i);\r
309                int k = av.alignment.findIndex(s);\r
310 \r
311                // drag to right\r
312                if (dragRight)\r
313                  for (int j = lastres; j < res; j++)\r
314                    insertChar(j, k);\r
315 \r
316                // drag to left\r
317                else\r
318                {\r
319                  for (int j = res; j < lastres; j++)\r
320                  {\r
321                    if(s.getLength()>j)\r
322                      deleteChar(res, k);\r
323                  }\r
324                }\r
325              }\r
326          }\r
327          else /////Editing a single sequence///////////\r
328          {\r
329            if (res < av.getAlignment().getWidth() && res > lastres)\r
330            {\r
331              // dragging to the right\r
332              for (int j = lastres; j < res; j++)\r
333                insertChar(j, startseq);\r
334            }\r
335            else if (res < av.getAlignment().getWidth() && res < lastres)\r
336            {\r
337              // dragging to the left\r
338              for (int j = lastres; j > res; j--)\r
339              {\r
340                if( jalview.util.Comparison.isGap(\r
341                  av.alignment.getSequenceAt(startseq).getSequence().charAt(res)))\r
342 \r
343                deleteChar(res, startseq);\r
344                else\r
345                {\r
346 \r
347                  break;\r
348                }\r
349              }\r
350            }\r
351 \r
352          }\r
353      }\r
354 \r
355      endEdit = res;\r
356      lastres = res;\r
357       seqCanvas.repaint();\r
358   }\r
359 \r
360   public void drawChars(int seqstart, int seqend, int start) {\r
361     seqCanvas.drawPanel(seqCanvas.gg, start,av.getEndRes(),seqstart,seqend,av.getStartRes(),av.getStartSeq(),0);\r
362     seqCanvas.repaint();\r
363   }\r
364 \r
365   public void insertChar(int j, int seq)\r
366   {\r
367     av.alignment.getSequenceAt(seq).insertCharAt(j, av.getGapCharacter());\r
368     seqEditOccurred=seq;\r
369   }\r
370 \r
371   public void deleteChar(int j, int seq)\r
372   {\r
373 \r
374     av.alignment.getSequenceAt(seq).deleteCharAt(j);\r
375     seqEditOccurred=seq;\r
376     av.alignment.getWidth();\r
377     repaint();\r
378   }\r
379 \r
380 \r
381   void editOccurred(int i)\r
382   {\r
383     if(endEdit==startEdit)\r
384     {\r
385       ap.alignFrame.historyList.pop();\r
386       ap.alignFrame.updateEditMenuBar();\r
387     }\r
388 \r
389     av.updateConservation();\r
390     av.updateConsensus();\r
391 \r
392     // Y O Y CLUSTALX\r
393     ColourSchemeI cs = av.getGlobalColourScheme();\r
394     if(cs instanceof ConservationColourScheme)\r
395     {\r
396       ConservationColourScheme ccs =  (ConservationColourScheme) cs;\r
397       if(ccs.cs instanceof ClustalxColourScheme)\r
398      {\r
399         jalview.analysis.Conservation c = new jalview.analysis.Conservation\r
400             ("All",\r
401             ResidueProperties.propHash, 3,\r
402             av.alignment.getSequences(), 0,\r
403             av.alignment.getWidth() - 1);\r
404         c.calculate();\r
405         c.verdict(false, av.ConsPercGaps);\r
406 \r
407         ClustalxColourScheme cxs = (ClustalxColourScheme)ccs.cs;\r
408         cxs.resetClustalX(av.alignment.getSequences(),  av.alignment.getWidth());\r
409         ccs = new ConservationColourScheme(c, cxs);\r
410         av.setGlobalColourScheme(ccs);\r
411      }\r
412     }\r
413 \r
414     if(cs instanceof ClustalxColourScheme)\r
415     {\r
416       ((ClustalxColourScheme)cs).resetClustalX(av.alignment.getSequences(),\r
417                                     av.alignment.getWidth());\r
418       av.setGlobalColourScheme(cs);\r
419     }\r
420 \r
421 \r
422   }\r
423 \r
424 //////////////////////////////////////////\r
425 /////Everything below this is for defining the boundary of the rubberband\r
426 //////////////////////////////////////////\r
427   int oldSeq = -1;\r
428   public void doMousePressedDefineMode(MouseEvent evt)\r
429   {\r
430     int res = evt.getX()/av.getCharWidth() + av.getStartRes();\r
431     int seq = evt.getY()/av.getCharHeight() + av.getStartSeq();\r
432     oldSeq = seq;\r
433 \r
434     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);\r
435 \r
436     if(sequence==null || res>sequence.getLength())\r
437       return;\r
438 \r
439     stretchGroup = av.getSelectionGroup();\r
440 \r
441     if(stretchGroup == null)\r
442      {\r
443        stretchGroup = av.alignment.findGroup( sequence );\r
444        if(stretchGroup!=null && res>stretchGroup.getStartRes() && res<stretchGroup.getEndRes())\r
445          av.setSelectionGroup(stretchGroup);\r
446        else\r
447          stretchGroup = null;\r
448      }\r
449 \r
450     else if(!stretchGroup.sequences.contains(sequence)\r
451             || stretchGroup.getStartRes()>res\r
452             || stretchGroup.getEndRes()<res)\r
453      {\r
454        stretchGroup = null;\r
455 \r
456        SequenceGroup[] allGroups = av.alignment.findAllGroups( sequence );\r
457 \r
458        if (allGroups != null)\r
459          for (int i = 0; i < allGroups.length; i++)\r
460            if (allGroups[i].getStartRes() <= res &&\r
461                allGroups[i].getEndRes() >= res)\r
462            {\r
463              stretchGroup = allGroups[i];\r
464              av.setSelectionGroup(stretchGroup);\r
465              break;\r
466            }\r
467      }\r
468 \r
469     if(stretchGroup==null)\r
470     {\r
471       // define a new group here\r
472       SequenceGroup sg = new SequenceGroup();\r
473       sg.setStartRes(res);\r
474       sg.setEndRes(res);\r
475       sg.addSequence( sequence );\r
476       av.setSelectionGroup( sg );\r
477       stretchGroup = sg;\r
478 \r
479       if(av.getConservationSelected())\r
480         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(), "Background");\r
481       if(av.getAbovePIDThreshold())\r
482         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(), "Background");\r
483 \r
484     }\r
485 \r
486     // DETECT RIGHT MOUSE BUTTON IN AWT\r
487     else if( ( evt.getModifiers() & InputEvent .BUTTON3_MASK ) == InputEvent.BUTTON3_MASK )\r
488     {\r
489       APopupMenu popup = new APopupMenu(ap, null);\r
490       this.add(popup);\r
491       popup.show(this, evt.getX(), evt.getY());\r
492     }\r
493 \r
494 \r
495     if(stretchGroup!=null && stretchGroup.getEndRes()==res)\r
496       // Edit end res position of selected group\r
497       changeEndRes = true;\r
498 \r
499    else if(stretchGroup!=null && stretchGroup.getStartRes()==res)\r
500       // Edit end res position of selected group\r
501       changeStartRes = true;\r
502 \r
503   }\r
504 \r
505   boolean changeEndSeq = false;\r
506   boolean changeStartSeq = false;\r
507   boolean changeEndRes = false;\r
508   boolean changeStartRes = false;\r
509   SequenceGroup stretchGroup = null;\r
510 \r
511   public void doMouseReleasedDefineMode(MouseEvent evt)\r
512   {\r
513     mouseDragging = false;\r
514 \r
515     if(stretchGroup==null)\r
516       return;\r
517 \r
518     if(stretchGroup.cs instanceof ClustalxColourScheme)\r
519     {\r
520       stretchGroup.cs = new ClustalxColourScheme(stretchGroup.sequences, av.alignment.getWidth());\r
521       seqCanvas.repaint();\r
522     }\r
523 \r
524     else if(stretchGroup.cs instanceof ConservationColourScheme)\r
525     {\r
526        ConservationColourScheme ccs = (ConservationColourScheme)stretchGroup.cs;\r
527        stretchGroup.cs = ccs;\r
528        SliderPanel.setConservationSlider(ap, stretchGroup.cs, stretchGroup.getName()) ;\r
529 \r
530        seqCanvas.repaint();\r
531     }\r
532     else\r
533     {\r
534       if(stretchGroup.cs !=null)\r
535       {\r
536         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs, stretchGroup.getName());\r
537       }\r
538 \r
539     }\r
540 \r
541 \r
542     changeEndRes = false;\r
543     changeStartRes = false;\r
544     stretchGroup = null;\r
545     ap.repaint();\r
546   }\r
547 \r
548 \r
549   boolean remove = false;\r
550   public void doMouseDraggedDefineMode(MouseEvent evt)\r
551   {\r
552     int res = evt.getX()/av.getCharWidth() + av.getStartRes();\r
553     int y = evt.getY()/av.getCharHeight() + av.getStartSeq();\r
554 \r
555     if(stretchGroup==null)\r
556       return;\r
557 \r
558     if(res>av.alignment.getWidth())\r
559       res = av.alignment.getWidth()-1;\r
560 \r
561 \r
562     if(stretchGroup.getEndRes()==res)\r
563       // Edit end res position of selected group\r
564       changeEndRes = true;\r
565 \r
566     else if(stretchGroup.getStartRes()==res)\r
567       // Edit start res position of selected group\r
568       changeStartRes = true;\r
569 \r
570 \r
571     if(res<av.getStartRes())\r
572       res = av.getStartRes();\r
573     else if(res>av.getEndRes())\r
574       res = av.getEndRes();\r
575 \r
576     if(changeEndRes)\r
577     {\r
578       if(res>stretchGroup.getStartRes()-1)\r
579         stretchGroup.setEndRes( res );\r
580     }\r
581     else if(changeStartRes)\r
582     {\r
583       if(res<stretchGroup.getEndRes()+1)\r
584         stretchGroup.setStartRes( res );\r
585     }\r
586 \r
587     int dragDirection = 0;\r
588     if (y > oldSeq)\r
589       dragDirection = 1;\r
590     else if (y < oldSeq)\r
591       dragDirection = -1;\r
592 \r
593     while (y != oldSeq && oldSeq>0 && y<av.alignment.getHeight())\r
594     {\r
595       // This routine ensures we don't skip any sequences, as the\r
596       // selection is quite slow.\r
597       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
598 \r
599       oldSeq += dragDirection;\r
600       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
601 \r
602       if (stretchGroup.sequences.contains(nextSeq))\r
603       {\r
604         stretchGroup.deleteSequence(seq);\r
605         stretchGroup.deleteSequence(nextSeq);\r
606       }\r
607       else\r
608       {\r
609        if(seq!=null)\r
610         stretchGroup.addSequence(seq);\r
611         stretchGroup.addSequence(nextSeq);\r
612       }\r
613     }\r
614     oldSeq = y;\r
615     mouseDragging = true;\r
616     if(scrollThread!=null)\r
617       scrollThread.setEvent(evt);\r
618 \r
619     seqCanvas.repaint();\r
620   }\r
621 \r
622   public void doMouseEnteredDefineMode(MouseEvent e)\r
623   {\r
624     if (scrollThread != null)\r
625       scrollThread.running = false;\r
626   }\r
627 \r
628   public void doMouseExitedDefineMode(MouseEvent e)\r
629   {\r
630     if (av.getWrapAlignment())\r
631       return;\r
632 \r
633     if(mouseDragging)\r
634       scrollThread = new ScrollThread();\r
635 \r
636   }\r
637   // this class allows scrolling off the bottom of the visible alignment\r
638   class ScrollThread extends Thread\r
639   {\r
640     MouseEvent evt;\r
641     boolean running = false;\r
642     public ScrollThread()\r
643     {\r
644       start();\r
645     }\r
646 \r
647     public void setEvent(MouseEvent e)\r
648     {\r
649       evt = e;\r
650     }\r
651 \r
652     public void stopScrolling()\r
653     {\r
654       running = false;\r
655     }\r
656 \r
657     public void run()\r
658     {\r
659       running = true;\r
660       while (running)\r
661       {\r
662         if(evt!=null)\r
663         {\r
664 \r
665           if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)\r
666             running = ap.scrollUp(true);\r
667 \r
668           if (mouseDragging && evt.getY() >= getSize().height &&\r
669               av.alignment.getHeight() > av.getEndSeq())\r
670             running = ap.scrollUp(false);\r
671 \r
672           if (mouseDragging && evt.getX() < 0)\r
673             running = ap.scrollRight(true);\r
674 \r
675           else if (mouseDragging && evt.getX() >= getSize().width)\r
676             running = ap.scrollRight(false);\r
677         }\r
678 \r
679         try\r
680         {\r
681           Thread.sleep(75);\r
682         }\r
683         catch (Exception ex)\r
684         {}\r
685       }\r
686     }\r
687 }\r
688 \r
689 \r
690 \r
691 }\r
692 \r
693 \r
694 \r
695 \r