Feature text mouse over modified
[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       int index = 0;\r
261       sequence.getSequenceFeatures();\r
262       boolean first = true;\r
263       while (index < sequence.getSequenceFeatures().length)\r
264       {\r
265         SequenceFeature sf = sequence.getSequenceFeatures()[index];\r
266         if (sf.getBegin() <= sequence.findPosition(res) &&\r
267             sf.getEnd() >= sequence.findPosition(res))\r
268         {\r
269           if(!av.featuresDisplayed.containsKey(sf.getType()))\r
270            {\r
271              index++;\r
272              continue;\r
273            }\r
274 \r
275           if(first)\r
276           {\r
277             text.append(" Sequence Feature:");\r
278             first = false;\r
279           }\r
280 \r
281           text.append(" "+sf.getType());\r
282 \r
283           if(sf.getDescription()!=null)\r
284             text.append(" "+sf.getDescription());\r
285 \r
286           if (sf.getStatus()!=null && sf.getStatus().length() > 0)\r
287           {\r
288             text.append(" (" + sf.getStatus() + ")");\r
289           }\r
290           text.append("; ");\r
291         }\r
292 \r
293         index++;\r
294 \r
295       }\r
296     }\r
297 \r
298      ap.alignFrame.statusBar.setText(text.toString());\r
299 \r
300   }\r
301 \r
302   public void mouseDragged(MouseEvent evt)\r
303   {\r
304     if (!editingSeqs)\r
305     {\r
306       doMouseDraggedDefineMode(evt);\r
307       return;\r
308     }\r
309 \r
310     // If we're dragging we're editing\r
311     int res = findRes(evt);\r
312     if (res < 0)\r
313     {\r
314       res = 0;\r
315     }\r
316 \r
317     if (lastres == -1 || lastres == res)\r
318     {\r
319       return;\r
320     }\r
321 \r
322     boolean dragRight = true;\r
323     if (res < av.getAlignment().getWidth() && res < lastres)\r
324     {\r
325       dragRight = false;\r
326     }\r
327 \r
328     if (res != lastres)\r
329     {\r
330       // Group editing\r
331       if (groupEditing)\r
332       {\r
333         SequenceGroup sg = av.getSelectionGroup();\r
334         if (sg == null)\r
335         {\r
336           lastres = -1;\r
337           return;\r
338         }\r
339 \r
340         // drag to right\r
341         if (dragRight)\r
342         {\r
343           sg.setEndRes(sg.getEndRes() + (res - lastres));\r
344         }\r
345 \r
346         // drag to left\r
347         else\r
348         {\r
349           /// Are we able to delete?\r
350           // ie are all columns blank?\r
351           boolean deleteAllowed = false;\r
352           for (int s = 0; s < sg.getSize(); s++)\r
353           {\r
354             SequenceI seq = sg.getSequenceAt(s);\r
355             for (int j = res; j < lastres; j++)\r
356             {\r
357               if (seq.getSequence().length() <= j)\r
358               {\r
359                 continue;\r
360               }\r
361 \r
362               if (!jalview.util.Comparison.isGap(seq.getSequence().charAt(j)))\r
363               {\r
364                 // Not a gap, block edit not valid\r
365                 res = j + 1;\r
366                 deleteAllowed = false;\r
367                 continue;\r
368               }\r
369               deleteAllowed = true;\r
370             }\r
371           }\r
372 \r
373           if (!deleteAllowed)\r
374           {\r
375             lastres = -1;\r
376             return;\r
377           }\r
378 \r
379           sg.setEndRes(sg.getEndRes() - (lastres - res));\r
380         }\r
381 \r
382         for (int i = 0; i < sg.getSize(); i++)\r
383         {\r
384           SequenceI s = sg.getSequenceAt(i);\r
385           int k = av.alignment.findIndex(s);\r
386 \r
387           // drag to right\r
388           if (dragRight)\r
389           {\r
390             for (int j = lastres; j < res; j++)\r
391             {\r
392               insertChar(j, k);\r
393             }\r
394           }\r
395 \r
396           // drag to left\r
397           else\r
398           {\r
399             for (int j = res; j < lastres; j++)\r
400             {\r
401               if (s.getLength() > j)\r
402               {\r
403                 deleteChar(res, k);\r
404               }\r
405             }\r
406           }\r
407         }\r
408       }\r
409       else /////Editing a single sequence///////////\r
410       {\r
411         if (res < av.getAlignment().getWidth() && res > lastres)\r
412         {\r
413           // dragging to the right\r
414           for (int j = lastres; j < res; j++)\r
415           {\r
416             insertChar(j, startseq);\r
417           }\r
418         }\r
419         else if (res < av.getAlignment().getWidth() && res < lastres)\r
420         {\r
421           // dragging to the left\r
422           for (int j = lastres; j > res; j--)\r
423           {\r
424             if (jalview.util.Comparison.isGap(\r
425                 av.alignment.getSequenceAt(startseq).getSequence().charAt(res)))\r
426             {\r
427 \r
428               deleteChar(res, startseq);\r
429             }\r
430             else\r
431             {\r
432 \r
433               break;\r
434             }\r
435           }\r
436         }\r
437 \r
438       }\r
439     }\r
440 \r
441     endEdit = res;\r
442     lastres = res;\r
443     seqCanvas.repaint();\r
444   }\r
445 \r
446   public void drawChars(int seqstart, int seqend, int start)\r
447   {\r
448     seqCanvas.drawPanel(seqCanvas.gg, start, av.getEndRes(), seqstart, seqend,  0);\r
449     seqCanvas.repaint();\r
450   }\r
451 \r
452   public void insertChar(int j, int seq)\r
453   {\r
454     av.alignment.getSequenceAt(seq).insertCharAt(j, av.getGapCharacter());\r
455     seqEditOccurred = seq;\r
456   }\r
457 \r
458   public void deleteChar(int j, int seq)\r
459   {\r
460 \r
461     av.alignment.getSequenceAt(seq).deleteCharAt(j);\r
462     seqEditOccurred = seq;\r
463     av.alignment.getWidth();\r
464     repaint();\r
465   }\r
466 \r
467   void editOccurred(int i)\r
468   {\r
469     if (endEdit == startEdit)\r
470     {\r
471         ap.alignFrame.historyList.pop();\r
472         ap.alignFrame.updateEditMenuBar();\r
473     }\r
474 \r
475     av.firePropertyChange("alignment", null,av.getAlignment().getSequences());\r
476   }\r
477 \r
478 //////////////////////////////////////////\r
479 /////Everything below this is for defining the boundary of the rubberband\r
480 //////////////////////////////////////////\r
481   int oldSeq = -1;\r
482   public void doMousePressedDefineMode(MouseEvent evt)\r
483   {\r
484     int res = findRes(evt);\r
485     int seq = findSeq(evt);\r
486     oldSeq = seq;\r
487     startWrapBlock=wrappedBlock;\r
488 \r
489     if(seq==-1)\r
490       return;\r
491 \r
492     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);\r
493 \r
494     if (sequence == null || res > sequence.getLength())\r
495     {\r
496       return;\r
497     }\r
498 \r
499     stretchGroup = av.getSelectionGroup();\r
500 \r
501     if (stretchGroup == null)\r
502     {\r
503       stretchGroup = av.alignment.findGroup(sequence);\r
504       if (stretchGroup != null && res > stretchGroup.getStartRes() &&\r
505           res < stretchGroup.getEndRes())\r
506       {\r
507         av.setSelectionGroup(stretchGroup);\r
508       }\r
509       else\r
510       {\r
511         stretchGroup = null;\r
512       }\r
513     }\r
514 \r
515     else if (!stretchGroup.sequences.contains(sequence)\r
516              || stretchGroup.getStartRes() > res\r
517              || stretchGroup.getEndRes() < res)\r
518     {\r
519       stretchGroup = null;\r
520 \r
521       SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence);\r
522 \r
523       if (allGroups != null)\r
524       {\r
525         for (int i = 0; i < allGroups.length; i++)\r
526         {\r
527           if (allGroups[i].getStartRes() <= res &&\r
528               allGroups[i].getEndRes() >= res)\r
529           {\r
530             stretchGroup = allGroups[i];\r
531             av.setSelectionGroup(stretchGroup);\r
532             break;\r
533           }\r
534         }\r
535       }\r
536     }\r
537 \r
538     if (stretchGroup == null)\r
539     {\r
540       // define a new group here\r
541       SequenceGroup sg = new SequenceGroup();\r
542       sg.setStartRes(res);\r
543       sg.setEndRes(res);\r
544       sg.addSequence(sequence, false);\r
545       av.setSelectionGroup(sg);\r
546       stretchGroup = sg;\r
547 \r
548       if (av.getConservationSelected())\r
549       {\r
550         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),\r
551                                           "Background");\r
552       }\r
553       if (av.getAbovePIDThreshold())\r
554       {\r
555         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),\r
556                                        "Background");\r
557       }\r
558 \r
559     }\r
560 \r
561     // DETECT RIGHT MOUSE BUTTON IN AWT\r
562     else if ( (evt.getModifiers() & InputEvent.BUTTON3_MASK) ==\r
563              InputEvent.BUTTON3_MASK)\r
564     {\r
565       APopupMenu popup = new APopupMenu(ap, null, null);\r
566       this.add(popup);\r
567       popup.show(this, evt.getX(), evt.getY());\r
568     }\r
569 \r
570     if (stretchGroup != null && stretchGroup.getEndRes() == res)\r
571     {\r
572       // Edit end res position of selected group\r
573       changeEndRes = true;\r
574     }\r
575 \r
576     else if (stretchGroup != null && stretchGroup.getStartRes() == res)\r
577     {\r
578       // Edit end res position of selected group\r
579       changeStartRes = true;\r
580     }\r
581 \r
582   }\r
583 \r
584   boolean changeEndSeq = false;\r
585   boolean changeStartSeq = false;\r
586   boolean changeEndRes = false;\r
587   boolean changeStartRes = false;\r
588   SequenceGroup stretchGroup = null;\r
589 \r
590   public void doMouseReleasedDefineMode(MouseEvent evt)\r
591   {\r
592     if(mouseDragging)\r
593      {\r
594        stretchGroup.recalcConservation();\r
595        mouseDragging = false;\r
596      }\r
597 \r
598     if (stretchGroup == null)\r
599     {\r
600       return;\r
601     }\r
602 \r
603     if(stretchGroup.cs!=null)\r
604     {\r
605       if (stretchGroup.cs.conservationApplied())\r
606       {\r
607         SliderPanel.setConservationSlider(ap, stretchGroup.cs,\r
608                                           stretchGroup.getName());\r
609       }\r
610       else\r
611       {\r
612         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,\r
613                                        stretchGroup.getName());\r
614       }\r
615     }\r
616     changeEndRes = false;\r
617     changeStartRes = false;\r
618     stretchGroup = null;\r
619     seqCanvas.repaint();\r
620     ap.repaint();\r
621   }\r
622 \r
623   boolean remove = false;\r
624   public void doMouseDraggedDefineMode(MouseEvent evt)\r
625   {\r
626     int res = findRes(evt);\r
627     int y = findSeq(evt);\r
628 \r
629     if(wrappedBlock!=startWrapBlock)\r
630         return;\r
631 \r
632     if(y>=av.alignment.getHeight())\r
633       y = av.alignment.getHeight()-1;\r
634 \r
635     if (stretchGroup == null)\r
636     {\r
637       return;\r
638     }\r
639 \r
640     if (res > av.alignment.getWidth())\r
641     {\r
642       res = av.alignment.getWidth() - 1;\r
643     }\r
644 \r
645     if (stretchGroup.getEndRes() == res)\r
646     {\r
647       // Edit end res position of selected group\r
648       changeEndRes = true;\r
649     }\r
650 \r
651     else if (stretchGroup.getStartRes() == res)\r
652     {\r
653       // Edit start res position of selected group\r
654       changeStartRes = true;\r
655     }\r
656 \r
657     if (res < av.getStartRes())\r
658     {\r
659       res = av.getStartRes();\r
660     }\r
661     else if (res > av.getEndRes() && !av.getWrapAlignment())\r
662     {\r
663       res = av.getEndRes();\r
664     }\r
665 \r
666     if (changeEndRes)\r
667     {\r
668       if (res > stretchGroup.getStartRes() - 1)\r
669       {\r
670         stretchGroup.setEndRes(res);\r
671       }\r
672     }\r
673     else if (changeStartRes)\r
674     {\r
675       if (res < stretchGroup.getEndRes() + 1)\r
676       {\r
677         stretchGroup.setStartRes(res);\r
678       }\r
679     }\r
680 \r
681     int dragDirection = 0;\r
682     if (y > oldSeq)\r
683     {\r
684       dragDirection = 1;\r
685     }\r
686     else if (y < oldSeq)\r
687     {\r
688       dragDirection = -1;\r
689     }\r
690 \r
691     while (y != oldSeq && oldSeq > 0 && y < av.alignment.getHeight())\r
692     {\r
693       // This routine ensures we don't skip any sequences, as the\r
694       // selection is quite slow.\r
695       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
696 \r
697       oldSeq += dragDirection;\r
698       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
699 \r
700       if (stretchGroup.sequences.contains(nextSeq))\r
701       {\r
702         stretchGroup.deleteSequence(seq, false);\r
703       }\r
704       else\r
705       {\r
706         if (seq != null)\r
707         {\r
708           stretchGroup.addSequence(seq, false);\r
709         }\r
710         stretchGroup.addSequence(nextSeq, false);\r
711       }\r
712     }\r
713     oldSeq = y;\r
714     mouseDragging = true;\r
715     if (scrollThread != null)\r
716     {\r
717       scrollThread.setEvent(evt);\r
718     }\r
719 \r
720     seqCanvas.repaint();\r
721   }\r
722 \r
723   public void mouseEntered(MouseEvent e)\r
724   {\r
725     if (editingSeqs && scrollThread != null)\r
726     {\r
727       scrollThread.running = false;\r
728     }\r
729   }\r
730 \r
731   public void mouseExited(MouseEvent e)\r
732   {\r
733     if (mouseDragging)\r
734     {\r
735       scrollThread = new ScrollThread();\r
736     }\r
737   }\r
738 \r
739   // this class allows scrolling off the bottom of the visible alignment\r
740   class ScrollThread\r
741       extends Thread\r
742   {\r
743     MouseEvent evt;\r
744     boolean running = false;\r
745     public ScrollThread()\r
746     {\r
747       start();\r
748     }\r
749 \r
750     public void setEvent(MouseEvent e)\r
751     {\r
752       evt = e;\r
753     }\r
754 \r
755     public void stopScrolling()\r
756     {\r
757       running = false;\r
758     }\r
759 \r
760     public void run()\r
761     {\r
762       running = true;\r
763       while (running)\r
764       {\r
765         if (evt != null)\r
766         {\r
767 \r
768           if (mouseDragging && evt.getY() < 0 && av.getStartSeq() > 0)\r
769           {\r
770             running = ap.scrollUp(true);\r
771           }\r
772 \r
773           if (mouseDragging && evt.getY() >= getSize().height &&\r
774               av.alignment.getHeight() > av.getEndSeq())\r
775           {\r
776             running = ap.scrollUp(false);\r
777           }\r
778 \r
779           if (mouseDragging && evt.getX() < 0)\r
780           {\r
781             running = ap.scrollRight(true);\r
782           }\r
783 \r
784           else if (mouseDragging && evt.getX() >= getSize().width)\r
785           {\r
786             running = ap.scrollRight(false);\r
787           }\r
788         }\r
789 \r
790         try\r
791         {\r
792           Thread.sleep(75);\r
793         }\r
794         catch (Exception ex)\r
795         {}\r
796       }\r
797     }\r
798   }\r
799 \r
800 }\r