2a252630ef63fd8d5dedefb4a4a23c63d774ceff
[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.util.*;\r
23 \r
24 import java.awt.*;\r
25 import java.awt.event.*;\r
26 \r
27 import jalview.datamodel.*;\r
28 import jalview.schemes.*;\r
29 \r
30 public class SeqPanel\r
31     extends Panel implements MouseMotionListener, MouseListener\r
32 {\r
33 \r
34   public SeqCanvas seqCanvas;\r
35   public AlignmentPanel ap;\r
36 \r
37   protected int lastres;\r
38   protected int startseq;\r
39   int startEdit = -1;\r
40   int endEdit = -1;\r
41 \r
42   protected AlignViewport av;\r
43 \r
44   // if character is inserted or deleted, we will need to recalculate the conservation\r
45   int seqEditOccurred = -1;\r
46 \r
47   ScrollThread scrollThread = null;\r
48   boolean mouseDragging = false;\r
49 \r
50   boolean editingSeqs = false;\r
51   boolean groupEditing = false;\r
52 \r
53   public SeqPanel(AlignViewport avp, AlignmentPanel p)\r
54   {\r
55     this.av = avp;\r
56 \r
57     seqCanvas = new SeqCanvas(avp);\r
58     setLayout(new BorderLayout());\r
59     add(seqCanvas);\r
60 \r
61     ap = p;\r
62 \r
63     seqCanvas.addMouseMotionListener(this);\r
64     seqCanvas.addMouseListener(this);\r
65 \r
66     seqCanvas.repaint();\r
67   }\r
68 \r
69 \r
70      public void mousePressed(MouseEvent evt)\r
71      {\r
72        if (evt.isShiftDown() || evt.isAltDown() || evt.isControlDown())\r
73        {\r
74          if (evt.isAltDown() || evt.isControlDown())\r
75          {\r
76            groupEditing = true;\r
77          }\r
78 \r
79          editingSeqs = true;\r
80          doMousePressed(evt);\r
81        }\r
82        else\r
83        {\r
84          doMousePressedDefineMode(evt);\r
85        }\r
86      }\r
87 \r
88      public void mouseClicked(MouseEvent evt){}\r
89 \r
90 \r
91   public void mouseReleased(MouseEvent evt)\r
92   {\r
93     if (!editingSeqs)\r
94     {\r
95       doMouseReleasedDefineMode(evt);\r
96       return;\r
97     }\r
98 \r
99 \r
100     if (seqEditOccurred > -1)\r
101     {\r
102       editOccurred(seqEditOccurred);\r
103     }\r
104 \r
105     startseq = -1;\r
106     lastres = -1;\r
107     seqEditOccurred = -1;\r
108     editingSeqs = false;\r
109     groupEditing = false;\r
110     ap.repaint();\r
111   }\r
112 \r
113   int startWrapBlock=-1;\r
114   int wrappedBlock=-1;\r
115   int findRes(MouseEvent evt)\r
116  {\r
117    int res = 0;\r
118    int x = evt.getX();\r
119 \r
120   if (av.wrapAlignment)\r
121   {\r
122 \r
123     int hgap = av.charHeight;\r
124     if (av.scaleAboveWrapped)\r
125       hgap += av.charHeight;\r
126 \r
127     int cHeight = av.getAlignment().getHeight() * av.charHeight\r
128         + hgap + seqCanvas.getAnnotationHeight();\r
129 \r
130       int y = evt.getY();\r
131       y -= hgap;\r
132       x -= seqCanvas.LABEL_WEST;\r
133 \r
134 \r
135       int cwidth = seqCanvas.getWrappedCanvasWidth(getSize().width);\r
136 \r
137       wrappedBlock = y / cHeight;\r
138       wrappedBlock += av.getStartRes() / cwidth;\r
139 \r
140       res = wrappedBlock * cwidth + x / av.getCharWidth();\r
141 \r
142   }\r
143   else\r
144   {\r
145       res = (x / av.getCharWidth()) + av.getStartRes();\r
146   }\r
147 \r
148   return res;\r
149 \r
150  }\r
151 \r
152  int findSeq(MouseEvent evt)\r
153  {\r
154 \r
155    int seq = 0;\r
156    int y = evt.getY();\r
157 \r
158    if (av.wrapAlignment)\r
159    {\r
160      int hgap = av.charHeight;\r
161      if (av.scaleAboveWrapped)\r
162        hgap += av.charHeight;\r
163 \r
164      int cHeight = av.getAlignment().getHeight() * av.charHeight\r
165          + hgap + seqCanvas.getAnnotationHeight();\r
166 \r
167        y -= hgap;\r
168 \r
169      seq = ( (y % cHeight) / av.getCharHeight());\r
170    }\r
171    else\r
172    {\r
173      seq = (y / av.getCharHeight()) + av.getStartSeq();\r
174    }\r
175 \r
176    return seq;\r
177  }\r
178 \r
179 \r
180   public void doMousePressed(MouseEvent evt)\r
181   {\r
182     ap.alignFrame.addHistoryItem(new HistoryItem(\r
183         "Edit Sequence", av.alignment, HistoryItem.EDIT));\r
184 \r
185     int seq = findSeq(evt);\r
186     int res = findRes(evt);\r
187 \r
188     if (seq < av.getAlignment().getHeight() &&\r
189         res < av.getAlignment().getSequenceAt(seq).getLength())\r
190     {\r
191       //char resstr = align.getSequenceAt(seq).getSequence().charAt(res);\r
192       // Find the residue's position in the sequence (res is the position\r
193       // in the alignment\r
194 \r
195       startseq = seq;\r
196       lastres = res;\r
197     }\r
198     else\r
199     {\r
200       startseq = -1;\r
201       lastres = -1;\r
202     }\r
203 \r
204     return;\r
205   }\r
206 \r
207   public void mouseMoved(MouseEvent evt)\r
208   {\r
209     int res = findRes(evt);\r
210     int seq = findSeq(evt);\r
211 \r
212     if (seq >= av.getAlignment().getHeight() || seq<0 || res<0)\r
213     {\r
214       return;\r
215     }\r
216 \r
217     SequenceI sequence = av.getAlignment().getSequenceAt(seq);\r
218     if (res > sequence.getLength())\r
219     {\r
220       return;\r
221     }\r
222 \r
223     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: " +\r
224             sequence.getName());\r
225 \r
226     Object obj = null;\r
227     if (av.alignment.isNucleotide())\r
228     {\r
229       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res) +\r
230           "");\r
231       if(obj!=null)\r
232         text.append(" Nucleotide: ");\r
233     }\r
234     else\r
235     {\r
236       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");\r
237       if(obj!=null)\r
238         text.append("  Residue: ");\r
239     }\r
240 \r
241     if (obj != null)\r
242     {\r
243 \r
244       if (obj != "")\r
245       {\r
246         text.append(obj + " (" +\r
247                     av.getAlignment().getSequenceAt(seq).findPosition(res) + ")");\r
248       }\r
249     }\r
250 \r
251     if(seqCanvas.pdbCanvas!=null && sequence==seqCanvas.pdbCanvas.sequence)\r
252     {\r
253       seqCanvas.pdbCanvas.highlightRes(sequence.findPosition(res));\r
254     }\r
255 \r
256 \r
257     // use aa to see if the mouse pointer is on a\r
258     if (av.showSequenceFeatures && sequence.getSequenceFeatures()!=null)\r
259     {\r
260       Vector features = sequence.getSequenceFeatures();\r
261       Enumeration e = features.elements();\r
262       boolean first = true;\r
263       while (e.hasMoreElements())\r
264       {\r
265         SequenceFeature sf = (SequenceFeature) e.nextElement();\r
266         if (sf.getBegin() <= sequence.findPosition(res) &&\r
267             sf.getEnd() >= sequence.findPosition(res))\r
268         {\r
269           if(first)\r
270           {\r
271             text.append(" Sequence Feature: ");\r
272             first = false;\r
273           }\r
274 \r
275           if(sf.getDescription()!=null)\r
276             text.append(sf.getDescription());\r
277 \r
278           if (sf.getStatus()!=null && sf.getStatus().length() > 0)\r
279           {\r
280             text.append(" (" + sf.getStatus() + ")");\r
281           }\r
282           text.append("; ");\r
283         }\r
284 \r
285       }\r
286     }\r
287 \r
288      ap.alignFrame.statusBar.setText(text.toString());\r
289 \r
290   }\r
291 \r
292   public void mouseDragged(MouseEvent evt)\r
293   {\r
294     if (!editingSeqs)\r
295     {\r
296       doMouseDraggedDefineMode(evt);\r
297       return;\r
298     }\r
299 \r
300     // If we're dragging we're editing\r
301     int res = findRes(evt);\r
302     if (res < 0)\r
303     {\r
304       res = 0;\r
305     }\r
306 \r
307     if (lastres == -1 || lastres == res)\r
308     {\r
309       return;\r
310     }\r
311 \r
312     boolean dragRight = true;\r
313     if (res < av.getAlignment().getWidth() && res < lastres)\r
314     {\r
315       dragRight = false;\r
316     }\r
317 \r
318     if (res != lastres)\r
319     {\r
320       // Group editing\r
321       if (groupEditing)\r
322       {\r
323         SequenceGroup sg = av.getSelectionGroup();\r
324         if (sg == null)\r
325         {\r
326           lastres = -1;\r
327           return;\r
328         }\r
329 \r
330         // drag to right\r
331         if (dragRight)\r
332         {\r
333           sg.setEndRes(sg.getEndRes() + (res - lastres));\r
334         }\r
335 \r
336         // drag to left\r
337         else\r
338         {\r
339           /// Are we able to delete?\r
340           // ie are all columns blank?\r
341           boolean deleteAllowed = false;\r
342           for (int s = 0; s < sg.getSize(); s++)\r
343           {\r
344             SequenceI seq = sg.getSequenceAt(s);\r
345             for (int j = res; j < lastres; j++)\r
346             {\r
347               if (seq.getSequence().length() <= j)\r
348               {\r
349                 continue;\r
350               }\r
351 \r
352               if (!jalview.util.Comparison.isGap(seq.getSequence().charAt(j)))\r
353               {\r
354                 // Not a gap, block edit not valid\r
355                 res = j + 1;\r
356                 deleteAllowed = false;\r
357                 continue;\r
358               }\r
359               deleteAllowed = true;\r
360             }\r
361           }\r
362 \r
363           if (!deleteAllowed)\r
364           {\r
365             lastres = -1;\r
366             return;\r
367           }\r
368 \r
369           sg.setEndRes(sg.getEndRes() - (lastres - res));\r
370         }\r
371 \r
372         for (int i = 0; i < sg.getSize(); i++)\r
373         {\r
374           SequenceI s = sg.getSequenceAt(i);\r
375           int k = av.alignment.findIndex(s);\r
376 \r
377           // drag to right\r
378           if (dragRight)\r
379           {\r
380             for (int j = lastres; j < res; j++)\r
381             {\r
382               insertChar(j, k);\r
383             }\r
384           }\r
385 \r
386           // drag to left\r
387           else\r
388           {\r
389             for (int j = res; j < lastres; j++)\r
390             {\r
391               if (s.getLength() > j)\r
392               {\r
393                 deleteChar(res, k);\r
394               }\r
395             }\r
396           }\r
397         }\r
398       }\r
399       else /////Editing a single sequence///////////\r
400       {\r
401         if (res < av.getAlignment().getWidth() && res > lastres)\r
402         {\r
403           // dragging to the right\r
404           for (int j = lastres; j < res; j++)\r
405           {\r
406             insertChar(j, startseq);\r
407           }\r
408         }\r
409         else if (res < av.getAlignment().getWidth() && res < lastres)\r
410         {\r
411           // dragging to the left\r
412           for (int j = lastres; j > res; j--)\r
413           {\r
414             if (jalview.util.Comparison.isGap(\r
415                 av.alignment.getSequenceAt(startseq).getSequence().charAt(res)))\r
416             {\r
417 \r
418               deleteChar(res, startseq);\r
419             }\r
420             else\r
421             {\r
422 \r
423               break;\r
424             }\r
425           }\r
426         }\r
427 \r
428       }\r
429     }\r
430 \r
431     endEdit = res;\r
432     lastres = res;\r
433     seqCanvas.repaint();\r
434   }\r
435 \r
436   public void drawChars(int seqstart, int seqend, int start)\r
437   {\r
438     seqCanvas.drawPanel(seqCanvas.gg, start, av.getEndRes(), seqstart, seqend,\r
439                         av.getStartRes(), av.getStartSeq(), 0);\r
440     seqCanvas.repaint();\r
441   }\r
442 \r
443   public void insertChar(int j, int seq)\r
444   {\r
445     av.alignment.getSequenceAt(seq).insertCharAt(j, av.getGapCharacter());\r
446     seqEditOccurred = seq;\r
447   }\r
448 \r
449   public void deleteChar(int j, int seq)\r
450   {\r
451 \r
452     av.alignment.getSequenceAt(seq).deleteCharAt(j);\r
453     seqEditOccurred = seq;\r
454     av.alignment.getWidth();\r
455     repaint();\r
456   }\r
457 \r
458   void editOccurred(int i)\r
459   {\r
460     if (endEdit == startEdit)\r
461     {\r
462         ap.alignFrame.historyList.pop();\r
463         ap.alignFrame.updateEditMenuBar();\r
464     }\r
465 \r
466     av.firePropertyChange("alignment", null,av.getAlignment().getSequences());\r
467   }\r
468 \r
469 //////////////////////////////////////////\r
470 /////Everything below this is for defining the boundary of the rubberband\r
471 //////////////////////////////////////////\r
472   int oldSeq = -1;\r
473   public void doMousePressedDefineMode(MouseEvent evt)\r
474   {\r
475     int res = findRes(evt);\r
476     int seq = findSeq(evt);\r
477     oldSeq = seq;\r
478     startWrapBlock=wrappedBlock;\r
479 \r
480     if(seq==-1)\r
481       return;\r
482 \r
483     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);\r
484 \r
485     if (sequence == null || res > sequence.getLength())\r
486     {\r
487       return;\r
488     }\r
489 \r
490     stretchGroup = av.getSelectionGroup();\r
491 \r
492     if (stretchGroup == null)\r
493     {\r
494       stretchGroup = av.alignment.findGroup(sequence);\r
495       if (stretchGroup != null && res > stretchGroup.getStartRes() &&\r
496           res < stretchGroup.getEndRes())\r
497       {\r
498         av.setSelectionGroup(stretchGroup);\r
499       }\r
500       else\r
501       {\r
502         stretchGroup = null;\r
503       }\r
504     }\r
505 \r
506     else if (!stretchGroup.sequences.contains(sequence)\r
507              || stretchGroup.getStartRes() > res\r
508              || stretchGroup.getEndRes() < res)\r
509     {\r
510       stretchGroup = null;\r
511 \r
512       SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence);\r
513 \r
514       if (allGroups != null)\r
515       {\r
516         for (int i = 0; i < allGroups.length; i++)\r
517         {\r
518           if (allGroups[i].getStartRes() <= res &&\r
519               allGroups[i].getEndRes() >= res)\r
520           {\r
521             stretchGroup = allGroups[i];\r
522             av.setSelectionGroup(stretchGroup);\r
523             break;\r
524           }\r
525         }\r
526       }\r
527     }\r
528 \r
529     if (stretchGroup == null)\r
530     {\r
531       // define a new group here\r
532       SequenceGroup sg = new SequenceGroup();\r
533       sg.setStartRes(res);\r
534       sg.setEndRes(res);\r
535       sg.addSequence(sequence, false);\r
536       av.setSelectionGroup(sg);\r
537       stretchGroup = sg;\r
538 \r
539       if (av.getConservationSelected())\r
540       {\r
541         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),\r
542                                           "Background");\r
543       }\r
544       if (av.getAbovePIDThreshold())\r
545       {\r
546         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),\r
547                                        "Background");\r
548       }\r
549 \r
550     }\r
551 \r
552     // DETECT RIGHT MOUSE BUTTON IN AWT\r
553     else if ( (evt.getModifiers() & InputEvent.BUTTON3_MASK) ==\r
554              InputEvent.BUTTON3_MASK)\r
555     {\r
556       APopupMenu popup = new APopupMenu(ap, null, null);\r
557       this.add(popup);\r
558       popup.show(this, evt.getX(), evt.getY());\r
559     }\r
560 \r
561     if (stretchGroup != null && stretchGroup.getEndRes() == res)\r
562     {\r
563       // Edit end res position of selected group\r
564       changeEndRes = true;\r
565     }\r
566 \r
567     else if (stretchGroup != null && stretchGroup.getStartRes() == res)\r
568     {\r
569       // Edit end res position of selected group\r
570       changeStartRes = true;\r
571     }\r
572 \r
573   }\r
574 \r
575   boolean changeEndSeq = false;\r
576   boolean changeStartSeq = false;\r
577   boolean changeEndRes = false;\r
578   boolean changeStartRes = false;\r
579   SequenceGroup stretchGroup = null;\r
580 \r
581   public void doMouseReleasedDefineMode(MouseEvent evt)\r
582   {\r
583     if(mouseDragging)\r
584      {\r
585        stretchGroup.recalcConservation();\r
586        mouseDragging = false;\r
587      }\r
588 \r
589     if (stretchGroup == null)\r
590     {\r
591       return;\r
592     }\r
593 \r
594     if(stretchGroup.cs!=null)\r
595     {\r
596       if (stretchGroup.cs.conservationApplied())\r
597       {\r
598         SliderPanel.setConservationSlider(ap, stretchGroup.cs,\r
599                                           stretchGroup.getName());\r
600       }\r
601       else\r
602       {\r
603         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,\r
604                                        stretchGroup.getName());\r
605       }\r
606     }\r
607     changeEndRes = false;\r
608     changeStartRes = false;\r
609     stretchGroup = null;\r
610     seqCanvas.repaint();\r
611     ap.repaint();\r
612   }\r
613 \r
614   boolean remove = false;\r
615   public void doMouseDraggedDefineMode(MouseEvent evt)\r
616   {\r
617     int res = findRes(evt);\r
618     int y = findSeq(evt);\r
619 \r
620     if(wrappedBlock!=startWrapBlock)\r
621         return;\r
622 \r
623     if(y>=av.alignment.getHeight())\r
624       y = av.alignment.getHeight()-1;\r
625 \r
626     if (stretchGroup == null)\r
627     {\r
628       return;\r
629     }\r
630 \r
631     if (res > av.alignment.getWidth())\r
632     {\r
633       res = av.alignment.getWidth() - 1;\r
634     }\r
635 \r
636     if (stretchGroup.getEndRes() == res)\r
637     {\r
638       // Edit end res position of selected group\r
639       changeEndRes = true;\r
640     }\r
641 \r
642     else if (stretchGroup.getStartRes() == res)\r
643     {\r
644       // Edit start res position of selected group\r
645       changeStartRes = true;\r
646     }\r
647 \r
648     if (res < av.getStartRes())\r
649     {\r
650       res = av.getStartRes();\r
651     }\r
652     else if (res > av.getEndRes() && !av.getWrapAlignment())\r
653     {\r
654       res = av.getEndRes();\r
655     }\r
656 \r
657     if (changeEndRes)\r
658     {\r
659       if (res > stretchGroup.getStartRes() - 1)\r
660       {\r
661         stretchGroup.setEndRes(res);\r
662       }\r
663     }\r
664     else if (changeStartRes)\r
665     {\r
666       if (res < stretchGroup.getEndRes() + 1)\r
667       {\r
668         stretchGroup.setStartRes(res);\r
669       }\r
670     }\r
671 \r
672     int dragDirection = 0;\r
673     if (y > oldSeq)\r
674     {\r
675       dragDirection = 1;\r
676     }\r
677     else if (y < oldSeq)\r
678     {\r
679       dragDirection = -1;\r
680     }\r
681 \r
682     while (y != oldSeq && oldSeq > 0 && y < av.alignment.getHeight())\r
683     {\r
684       // This routine ensures we don't skip any sequences, as the\r
685       // selection is quite slow.\r
686       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
687 \r
688       oldSeq += dragDirection;\r
689       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
690 \r
691       if (stretchGroup.sequences.contains(nextSeq))\r
692       {\r
693         stretchGroup.deleteSequence(seq, false);\r
694       }\r
695       else\r
696       {\r
697         if (seq != null)\r
698         {\r
699           stretchGroup.addSequence(seq, false);\r
700         }\r
701         stretchGroup.addSequence(nextSeq, false);\r
702       }\r
703     }\r
704     oldSeq = y;\r
705     mouseDragging = true;\r
706     if (scrollThread != null)\r
707     {\r
708       scrollThread.setEvent(evt);\r
709     }\r
710 \r
711     seqCanvas.repaint();\r
712   }\r
713 \r
714   public void mouseEntered(MouseEvent e)\r
715   {\r
716     if (editingSeqs && scrollThread != null)\r
717     {\r
718       scrollThread.running = false;\r
719     }\r
720   }\r
721 \r
722   public void mouseExited(MouseEvent e)\r
723   {\r
724     if (mouseDragging)\r
725     {\r
726       scrollThread = new ScrollThread();\r
727     }\r
728   }\r
729 \r
730   // this class allows scrolling off the bottom of the visible alignment\r
731   class ScrollThread\r
732       extends Thread\r
733   {\r
734     MouseEvent evt;\r
735     boolean running = false;\r
736     public ScrollThread()\r
737     {\r
738       start();\r
739     }\r
740 \r
741     public void setEvent(MouseEvent e)\r
742     {\r
743       evt = e;\r
744     }\r
745 \r
746     public void stopScrolling()\r
747     {\r
748       running = false;\r
749     }\r
750 \r
751     public void run()\r
752     {\r
753       running = true;\r
754       while (running)\r
755       {\r
756         if (evt != null)\r
757         {\r
758 \r
759           if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)\r
760           {\r
761             running = ap.scrollUp(true);\r
762           }\r
763 \r
764           if (mouseDragging && evt.getY() >= getSize().height &&\r
765               av.alignment.getHeight() > av.getEndSeq())\r
766           {\r
767             running = ap.scrollUp(false);\r
768           }\r
769 \r
770           if (mouseDragging && evt.getX() < 0)\r
771           {\r
772             running = ap.scrollRight(true);\r
773           }\r
774 \r
775           else if (mouseDragging && evt.getX() >= getSize().width)\r
776           {\r
777             running = ap.scrollRight(false);\r
778           }\r
779         }\r
780 \r
781         try\r
782         {\r
783           Thread.sleep(75);\r
784         }\r
785         catch (Exception ex)\r
786         {}\r
787       }\r
788     }\r
789   }\r
790 \r
791 }\r