Keyboard editing
[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 package jalview.gui;\r
20 \r
21 import jalview.datamodel.*;\r
22 \r
23 import jalview.schemes.*;\r
24 \r
25 import java.awt.*;\r
26 import java.awt.event.*;\r
27 \r
28 import javax.swing.*;\r
29 \r
30 \r
31 /**\r
32  * DOCUMENT ME!\r
33  *\r
34  * @author $author$\r
35  * @version $Revision$\r
36  */\r
37 public class SeqPanel extends JPanel implements MouseListener,\r
38     MouseMotionListener, MouseWheelListener\r
39 \r
40 {\r
41     /** DOCUMENT ME!! */\r
42     public SeqCanvas seqCanvas;\r
43 \r
44     /** DOCUMENT ME!! */\r
45     public AlignmentPanel ap;\r
46     protected int lastres;\r
47     protected int startseq;\r
48     protected AlignViewport av;\r
49 \r
50     // if character is inserted or deleted, we will need to recalculate the conservation\r
51     boolean seqEditOccurred = false;\r
52     ScrollThread scrollThread = null;\r
53     boolean mouseDragging = false;\r
54     boolean editingSeqs = false;\r
55     boolean groupEditing = false;\r
56 \r
57     //////////////////////////////////////////\r
58     /////Everything below this is for defining the boundary of the rubberband\r
59     //////////////////////////////////////////\r
60     int oldSeq = -1;\r
61     boolean changeEndSeq = false;\r
62     boolean changeStartSeq = false;\r
63     boolean changeEndRes = false;\r
64     boolean changeStartRes = false;\r
65     SequenceGroup stretchGroup = null;\r
66     boolean remove = false;\r
67 \r
68     boolean mouseWheelPressed = false;\r
69     StringBuffer keyboardNo1;\r
70     StringBuffer keyboardNo2;\r
71 \r
72     /**\r
73      * Creates a new SeqPanel object.\r
74      *\r
75      * @param avp DOCUMENT ME!\r
76      * @param p DOCUMENT ME!\r
77      */\r
78     public SeqPanel(AlignViewport avp, AlignmentPanel p)\r
79     {\r
80         ToolTipManager.sharedInstance().registerComponent(this);\r
81         ToolTipManager.sharedInstance().setInitialDelay(0);\r
82         ToolTipManager.sharedInstance().setDismissDelay(10000);\r
83         this.av = avp;\r
84         setBackground(Color.white);\r
85 \r
86         seqCanvas = new SeqCanvas(avp);\r
87         setLayout(new BorderLayout());\r
88         add(seqCanvas, BorderLayout.CENTER);\r
89 \r
90         ap = p;\r
91 \r
92         if(!av.isDataset())\r
93         {\r
94           addMouseMotionListener(this);\r
95           addMouseListener(this);\r
96           addMouseWheelListener(this);\r
97         }\r
98     }\r
99 \r
100     int startWrapBlock=-1;\r
101     int wrappedBlock=-1;\r
102     int findRes(MouseEvent evt)\r
103    {\r
104      int res = 0;\r
105      int x = evt.getX();\r
106 \r
107     if (av.wrapAlignment)\r
108     {\r
109 \r
110       int hgap = av.charHeight;\r
111       if (av.scaleAboveWrapped)\r
112         hgap += av.charHeight;\r
113 \r
114       int cHeight = av.getAlignment().getHeight() * av.charHeight\r
115           + hgap + seqCanvas.getAnnotationHeight();\r
116 \r
117         int y = evt.getY();\r
118         y -= hgap;\r
119         x -= seqCanvas.LABEL_WEST;\r
120 \r
121 \r
122         int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());\r
123 \r
124         wrappedBlock = y / cHeight;\r
125         wrappedBlock += av.getStartRes() / cwidth;\r
126 \r
127         res = wrappedBlock * cwidth + x / av.getCharWidth();\r
128 \r
129     }\r
130     else\r
131     {\r
132         res = (x / av.getCharWidth()) + av.getStartRes();\r
133     }\r
134 \r
135     if(av.hasHiddenColumns)\r
136           res = av.getColumnSelection().adjustForHiddenColumns(res);\r
137 \r
138     return res;\r
139 \r
140    }\r
141 \r
142    int findSeq(MouseEvent evt)\r
143    {\r
144 \r
145      int seq = 0;\r
146      int y = evt.getY();\r
147 \r
148      if (av.wrapAlignment)\r
149      {\r
150        int hgap = av.charHeight;\r
151        if (av.scaleAboveWrapped)\r
152          hgap += av.charHeight;\r
153 \r
154        int cHeight = av.getAlignment().getHeight() * av.charHeight\r
155            + hgap + seqCanvas.getAnnotationHeight();\r
156 \r
157          y -= hgap;\r
158 \r
159        seq = ( (y % cHeight) / av.getCharHeight());\r
160      }\r
161      else\r
162      {\r
163        seq = (y / av.getCharHeight()) + av.getStartSeq();\r
164      }\r
165 \r
166      return seq;\r
167    }\r
168 \r
169    void endEditing()\r
170    {\r
171      startseq = -1;\r
172      lastres = -1;\r
173      seqEditOccurred = false;\r
174      editingSeqs = false;\r
175      groupEditing = false;\r
176      keyboardNo1 = null;\r
177      keyboardNo2 = null;\r
178     }\r
179 \r
180     void setCursorRow()\r
181     {\r
182       seqCanvas.cursorY = getKeyboardNo(keyboardNo1)-1;\r
183       scrollToVisible();\r
184     }\r
185 \r
186     void setCursorColumn()\r
187     {\r
188       seqCanvas.cursorX = getKeyboardNo(keyboardNo1)-1;\r
189       scrollToVisible();\r
190     }\r
191 \r
192     void setCursorRowAndColumn()\r
193     {\r
194       if(keyboardNo2==null)\r
195       {\r
196         keyboardNo2 = new StringBuffer();\r
197       }\r
198       else\r
199       {\r
200         seqCanvas.cursorX = getKeyboardNo(keyboardNo1) - 1;\r
201         seqCanvas.cursorY = getKeyboardNo(keyboardNo2) - 1;\r
202         scrollToVisible();\r
203       }\r
204     }\r
205 \r
206     void setCursorPosition()\r
207     {\r
208       SequenceI sequence =\r
209           (Sequence) av.getAlignment().getSequenceAt(seqCanvas.cursorY);\r
210 \r
211       seqCanvas.cursorX = sequence.findIndex(\r
212           getKeyboardNo(keyboardNo1)-1\r
213           );\r
214       scrollToVisible();\r
215     }\r
216 \r
217     void moveCursor(int dx, int dy)\r
218     {\r
219       seqCanvas.cursorX += dx;\r
220       seqCanvas.cursorY += dy;\r
221       scrollToVisible();\r
222     }\r
223 \r
224     void scrollToVisible()\r
225     {\r
226       if (seqCanvas.cursorX < 0)\r
227         seqCanvas.cursorX = 0;\r
228       else if (seqCanvas.cursorX > av.alignment.getWidth() - 1)\r
229         seqCanvas.cursorX = av.alignment.getWidth() - 1;\r
230 \r
231       if (seqCanvas.cursorY < 0)\r
232         seqCanvas.cursorY = 0;\r
233       else if (seqCanvas.cursorY > av.alignment.getHeight() - 1)\r
234         seqCanvas.cursorY = av.alignment.getHeight() - 1;\r
235 \r
236 \r
237       endEditing();\r
238       while (seqCanvas.cursorY < av.startSeq)\r
239       {\r
240         ap.scrollUp(true);\r
241       }\r
242       while (seqCanvas.cursorY + 1 > av.endSeq)\r
243       {\r
244         ap.scrollUp(false);\r
245       }\r
246       while (seqCanvas.cursorX < av.startRes)\r
247       {\r
248         if(!ap.scrollRight(false))\r
249           break;\r
250       }\r
251       while (seqCanvas.cursorX > av.endRes)\r
252       {\r
253         if(!ap.scrollRight(true))\r
254           break;\r
255       }\r
256 \r
257       setStatusMessage(av.alignment.getSequenceAt(seqCanvas.cursorY),\r
258                        seqCanvas.cursorX, seqCanvas.cursorY);\r
259 \r
260       seqCanvas.repaint();\r
261     }\r
262 \r
263     void setSelectionAreaAtCursor(boolean topLeft)\r
264     {\r
265       SequenceI sequence =\r
266           (Sequence) av.getAlignment().getSequenceAt(seqCanvas.cursorY);\r
267 \r
268       if(av.getSelectionGroup()!=null)\r
269       {\r
270         SequenceGroup sg = av.selectionGroup;\r
271         //Find the top and bottom of this group\r
272         int min = av.alignment.getHeight(), max = 0;\r
273         for(int i=0; i<sg.getSize(); i++)\r
274         {\r
275           int index = av.alignment.findIndex( sg.getSequenceAt(i) );\r
276           if(index > max)\r
277             max = index;\r
278           if(index < min)\r
279             min = index;\r
280         }\r
281 \r
282         max ++;\r
283 \r
284         if(topLeft)\r
285         {\r
286           sg.setStartRes(seqCanvas.cursorX);\r
287           if(sg.getEndRes()<seqCanvas.cursorX)\r
288             sg.setEndRes(seqCanvas.cursorX);\r
289 \r
290           min = seqCanvas.cursorY;\r
291         }\r
292         else\r
293         {\r
294           sg.setEndRes(seqCanvas.cursorX);\r
295           if(sg.getStartRes()>seqCanvas.cursorX)\r
296             sg.setStartRes(seqCanvas.cursorX);\r
297 \r
298           max = seqCanvas.cursorY+1;\r
299         }\r
300 \r
301         if(min>max)\r
302         {\r
303           // Only the user can do this\r
304           av.setSelectionGroup(null);\r
305         }\r
306         else\r
307         {\r
308           // Now add any sequences between min and max\r
309           sg.sequences.clear();\r
310           for (int i = min; i < max; i++)\r
311           {\r
312             sg.addSequence(av.alignment.getSequenceAt(i), false);\r
313           }\r
314         }\r
315       }\r
316 \r
317       if (av.getSelectionGroup() == null)\r
318       {\r
319         SequenceGroup sg = new SequenceGroup();\r
320         sg.setStartRes(seqCanvas.cursorX);\r
321         sg.setEndRes(seqCanvas.cursorX);\r
322         sg.addSequence(sequence, false);\r
323         av.setSelectionGroup(sg);\r
324       }\r
325 \r
326 \r
327       ap.repaint();\r
328     }\r
329 \r
330     void insertGapAtCursor(boolean group)\r
331     {\r
332       ap.alignFrame.addHistoryItem(new HistoryItem("Edit Sequence",\r
333                                                    av.alignment, HistoryItem.EDIT));\r
334       groupEditing = group;\r
335       startseq = seqCanvas.cursorY;\r
336       lastres = seqCanvas.cursorX;\r
337       editSequence(true, seqCanvas.cursorX+getKeyboardNo(keyboardNo1));\r
338       editOccurred();\r
339     }\r
340 \r
341     void deleteGapAtCursor(boolean group)\r
342     {\r
343       ap.alignFrame.addHistoryItem(new HistoryItem("Edit Sequence",\r
344                                                    av.alignment, HistoryItem.EDIT));\r
345       groupEditing = group;\r
346       startseq = seqCanvas.cursorY;\r
347       lastres = seqCanvas.cursorX+getKeyboardNo(keyboardNo1);\r
348       editSequence(false, seqCanvas.cursorX);\r
349       editOccurred();\r
350     }\r
351 \r
352     void numberPressed(char value)\r
353     {\r
354       if(keyboardNo1==null)\r
355         keyboardNo1 = new StringBuffer();\r
356 \r
357       if(keyboardNo2!=null)\r
358         keyboardNo2.append(value);\r
359       else\r
360         keyboardNo1.append(value);\r
361     }\r
362 \r
363     int getKeyboardNo(StringBuffer kb)\r
364     {\r
365       if(kb==null)\r
366         return 1;\r
367       else\r
368         return Integer.parseInt(kb.toString());\r
369     }\r
370 \r
371 \r
372     /**\r
373      * DOCUMENT ME!\r
374      *\r
375      * @param evt DOCUMENT ME!\r
376      */\r
377     public void mouseReleased(MouseEvent evt)\r
378     {\r
379       mouseDragging = false;\r
380       mouseWheelPressed = false;\r
381 \r
382       if (!editingSeqs)\r
383       {\r
384          doMouseReleasedDefineMode(evt);\r
385          return;\r
386       }\r
387 \r
388        editOccurred();\r
389 \r
390        endEditing();\r
391        ap.repaint();\r
392     }\r
393 \r
394 \r
395 \r
396     /**\r
397      * DOCUMENT ME!\r
398      *\r
399      * @param evt DOCUMENT ME!\r
400      */\r
401     public void mousePressed(MouseEvent evt)\r
402     {\r
403       if (javax.swing.SwingUtilities.isMiddleMouseButton(evt))\r
404       {\r
405         mouseWheelPressed = true;\r
406         return;\r
407       }\r
408 \r
409       if (evt.isShiftDown() || evt.isAltDown() ||\r
410           evt.isControlDown())\r
411       {\r
412         if (evt.isAltDown() || evt.isControlDown())\r
413         {\r
414           groupEditing = true;\r
415         }\r
416         editingSeqs = true;\r
417       }\r
418       else\r
419       {\r
420         doMousePressedDefineMode(evt);\r
421         return;\r
422       }\r
423 \r
424 \r
425 \r
426       int seq = findSeq(evt);\r
427       int res = findRes(evt);\r
428 \r
429       if(seq<0 || res<0)\r
430         return;\r
431 \r
432       ap.alignFrame.addHistoryItem(new HistoryItem("Edit Sequence",\r
433                                                          av.alignment, HistoryItem.EDIT));\r
434 \r
435         if ((seq < av.getAlignment().getHeight()) &&\r
436                 (res < av.getAlignment().getSequenceAt(seq).getLength()))\r
437         {\r
438             startseq = seq;\r
439             lastres = res;\r
440         }\r
441         else\r
442         {\r
443             startseq = -1;\r
444             lastres = -1;\r
445         }\r
446 \r
447         return;\r
448     }\r
449 \r
450     /**\r
451      * DOCUMENT ME!\r
452      *\r
453      * @param evt DOCUMENT ME!\r
454      */\r
455     public void mouseMoved(MouseEvent evt)\r
456     {\r
457       if (editingSeqs)\r
458       {\r
459        // This is because MacOSX creates a mouseMoved\r
460        // If control is down, other platforms will not.\r
461        mouseDragged(evt);\r
462       }\r
463 \r
464       int res = findRes(evt);\r
465       int seq = findSeq(evt);\r
466 \r
467 \r
468       if(res<0 || seq<0 || seq >= av.getAlignment().getHeight())\r
469             return;\r
470 \r
471       SequenceI sequence = av.getAlignment().getSequenceAt(seq);\r
472 \r
473       if (res > sequence.getLength())\r
474       {\r
475         return;\r
476       }\r
477 \r
478       if(seqCanvas.pdbCanvas!=null && sequence==seqCanvas.pdbCanvas.sequence)\r
479       {\r
480         seqCanvas.pdbCanvas.highlightRes(sequence.findPosition(res));\r
481       }\r
482 \r
483       setStatusMessage(sequence, res, seq);\r
484 \r
485         // use aa to see if the mouse pointer is on a\r
486         if (av.showSequenceFeatures)\r
487         {\r
488             SequenceFeature [] features = sequence.getDatasetSequence().getSequenceFeatures();\r
489             if(features!=null)\r
490             {\r
491               StringBuffer sbuffer = new StringBuffer("<html>");\r
492 \r
493               for (int i = 0; i < features.length; i++)\r
494               {\r
495 \r
496                 if ( (features[i].getBegin() <= sequence.findPosition(res)) &&\r
497                     (features[i].getEnd() >= sequence.findPosition(res)))\r
498                 {\r
499                   if(av.featuresDisplayed==null\r
500                     || !av.featuresDisplayed.containsKey(features[i].getType()))\r
501                   continue;\r
502 \r
503 \r
504                   if (features[i].getType().equals("disulfide bond"))\r
505                   {\r
506                     if (features[i].getBegin() == sequence.findPosition(res)\r
507                         || features[i].getEnd() == sequence.findPosition(res))\r
508                     {\r
509                       if (sbuffer.length() > 6)\r
510                         sbuffer.append("<br>");\r
511                       sbuffer.append("disulfide bond " + features[i].getBegin() + ":" +\r
512                                      features[i].getEnd());\r
513                     }\r
514                   }\r
515                   else\r
516                   {\r
517                     if (sbuffer.length() > 6)\r
518                       sbuffer.append("<br>");\r
519                     if(features[i].featureGroup!=null)\r
520                       sbuffer.append(features[i].featureGroup+";");\r
521 \r
522                     sbuffer.append(features[i].getType());\r
523 \r
524                     if (features[i].getDescription() != null\r
525                         && !features[i].description.equals(features[i].getType()))\r
526                       sbuffer.append("; " + features[i].getDescription());\r
527 \r
528                     if (features[i].getStatus() != null && features[i].getStatus().length()>0)\r
529                     {\r
530                       sbuffer.append("; (" + features[i].getStatus() + ")");\r
531                     }\r
532                   }\r
533                 }\r
534 \r
535               }\r
536 \r
537               sbuffer.append("</html>");\r
538               if(sbuffer.length()==13) // <html></html>\r
539                 setToolTipText("");\r
540               else\r
541                setToolTipText(sbuffer.toString());\r
542             }\r
543             else\r
544               setToolTipText("");\r
545         }\r
546     }\r
547 \r
548     void setStatusMessage(SequenceI sequence, int res, int seq)\r
549     {\r
550       StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: " +\r
551                                            sequence.getName());\r
552 \r
553       Object obj = null;\r
554       if (av.alignment.isNucleotide())\r
555       {\r
556         obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res) +\r
557                                                    "");\r
558         if (obj != null)\r
559           text.append(" Nucleotide: ");\r
560       }\r
561       else\r
562       {\r
563         obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");\r
564         if (obj != null)\r
565           text.append("  Residue: ");\r
566       }\r
567 \r
568       if (obj != null)\r
569       {\r
570 \r
571         if (obj != "")\r
572         {\r
573           text.append(obj + " (" + sequence.findPosition(res) +\r
574                       ")");\r
575         }\r
576       }\r
577       ap.alignFrame.statusBar.setText(text.toString());\r
578 \r
579     }\r
580 \r
581     /**\r
582      * DOCUMENT ME!\r
583      *\r
584      * @param evt DOCUMENT ME!\r
585      */\r
586     public void mouseDragged(MouseEvent evt)\r
587     {\r
588       if (!editingSeqs)\r
589       {\r
590         doMouseDraggedDefineMode(evt);\r
591         return;\r
592       }\r
593 \r
594         int res = findRes(evt);\r
595 \r
596         if (res < 0)\r
597         {\r
598             res = 0;\r
599         }\r
600 \r
601         if ((lastres == -1) || (lastres == res))\r
602         {\r
603             return;\r
604         }\r
605 \r
606         if ( (res < av.getAlignment().getWidth()) && (res < lastres))\r
607         {\r
608           // dragLeft, delete gap\r
609           editSequence(false, res);\r
610         }\r
611         else\r
612           editSequence(true, res);\r
613 \r
614         mouseDragging = true;\r
615         if(scrollThread!=null)\r
616           scrollThread.setEvent(evt);\r
617 \r
618     }\r
619 \r
620     synchronized void editSequence(boolean insertGap, int startres)\r
621     {\r
622       int fixedLeft = -1;\r
623       int fixedRight = -1;\r
624       boolean fixedColumns = false;\r
625       SequenceGroup sg = av.getSelectionGroup();\r
626 \r
627       if(groupEditing && sg==null)\r
628         return;\r
629 \r
630         if (!groupEditing && av.hasHiddenRows)\r
631         {\r
632           //This needs to check all the sequences in a group edit,m\r
633           // not just the startseq\r
634           if (av.alignment.getSequenceAt(startseq).getHiddenSequences() != null)\r
635           {\r
636             groupEditing = true;\r
637           }\r
638         }\r
639 \r
640         SequenceI seq = av.alignment.getSequenceAt(startseq);\r
641         StringBuffer message = new StringBuffer();\r
642         if (groupEditing)\r
643            message.append("Edit group:");\r
644         else\r
645             message.append("Edit sequence: "+seq.getName());\r
646 \r
647        if(insertGap)\r
648          message.append(" insert ");\r
649        else\r
650          message.append(" delete ");\r
651 \r
652        message.append(Math.abs(startres-lastres)+" gaps.");\r
653        ap.alignFrame.statusBar.setText(message.toString());\r
654 \r
655 \r
656         //Are we editing within a selection group?\r
657         if (groupEditing\r
658             || (sg != null && sg.sequences.contains(seq)))\r
659         {\r
660           fixedColumns = true;\r
661 \r
662           fixedLeft = sg.getStartRes();\r
663           fixedRight = sg.getEndRes();\r
664 \r
665           if (   (startres < fixedLeft && lastres >= fixedLeft)\r
666               || (startres >= fixedLeft && lastres < fixedLeft)\r
667               || (startres > fixedRight && lastres <=fixedRight)\r
668               || (startres <= fixedRight && lastres > fixedRight))\r
669           {\r
670             endEditing();\r
671             return;\r
672           }\r
673 \r
674           if (fixedLeft > startres)\r
675           {\r
676             fixedRight = fixedLeft - 1;\r
677             fixedLeft = 0;\r
678           }\r
679           else if (fixedRight < startres)\r
680           {\r
681             fixedLeft = fixedRight;\r
682             fixedRight = -1;\r
683           }\r
684         }\r
685 \r
686 \r
687 \r
688         if(av.hasHiddenColumns)\r
689         {\r
690           fixedColumns = true;\r
691           int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);\r
692           int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);\r
693 \r
694           if(    ( insertGap && startres>y1 && lastres<y1)\r
695               || (!insertGap && startres<y2 && lastres>y2) )\r
696           {\r
697             endEditing();\r
698             return;\r
699           }\r
700 \r
701           if(fixedRight<y2 && fixedRight==-1 && y2!=startres)\r
702             fixedRight = y2 -1;\r
703           if(y1>fixedLeft && fixedLeft==-1)\r
704             fixedLeft = y1;\r
705         }\r
706 \r
707         if (groupEditing)\r
708         {\r
709 \r
710           /*if (av.hasHiddenRows)\r
711           {\r
712             //sg might be null as the user may only see 1 sequence\r
713             if (sg == null)\r
714             {\r
715               sg = new SequenceGroup();\r
716               sg.addSequence(av.alignment.getSequenceAt(startseq), false);\r
717             }\r
718 \r
719             SequenceGroup tmp = new SequenceGroup();\r
720 \r
721             //Do any of the sequences have hidden associates?\r
722             for (int s = 0; s < sg.getSize(); s++)\r
723             {\r
724               seq = sg.getSequenceAt(s);\r
725               tmp.addSequence(seq, false);\r
726               if (seq.getHiddenSequences() != null)\r
727               {\r
728                 for (int h = 0; h < seq.getHiddenSequences().getSize(); h++)\r
729                   tmp.addSequence(seq.getHiddenSequences().getSequenceAt(h),\r
730                                   false);\r
731               }\r
732             }\r
733 \r
734             sg = tmp;\r
735           }*/\r
736           // int blankColumn = -1;\r
737 \r
738 \r
739           // drag to right\r
740           if (insertGap)\r
741           {\r
742               //If the user has selected the whole sequence, and is dragging to\r
743               // the right, we can still extend the alignment and selectionGroup\r
744               if(sg.getStartRes() == 0 && sg.getEndRes() + 1 == av.alignment.getWidth())\r
745               {\r
746                 sg.setEndRes(av.alignment.getWidth() + startres - lastres);\r
747                 fixedRight = sg.getEndRes();\r
748               }\r
749 \r
750             // Is it valid with fixed columns??\r
751             // Find the next gap before the end\r
752             // of the visible region boundary\r
753             boolean blank = false;\r
754             for (fixedRight = fixedRight;\r
755                  fixedRight > lastres;\r
756                  fixedRight--)\r
757             {\r
758               blank = true;\r
759               for (int s = 0; s < sg.getSize(); s++)\r
760               {\r
761                 seq = sg.getSequenceAt(s);\r
762                 for (int j = 0; j < startres - lastres; j++)\r
763                 {\r
764                   if (!jalview.util.Comparison.isGap(\r
765                       seq.getCharAt(fixedRight - j)))\r
766                   {\r
767                     blank = false;\r
768                     break;\r
769                   }\r
770                 }\r
771               }\r
772               if (blank)\r
773                 break;\r
774             }\r
775 \r
776             if (!blank)\r
777             {\r
778               if(sg.getSize() == av.alignment.getHeight())\r
779               {\r
780                 //We can still insert gaps if the selectionGroup\r
781                 //contains all the sequences\r
782                 sg.setEndRes(sg.getEndRes()+1+startres-lastres);\r
783                 fixedRight = av.alignment.getWidth()+startres-lastres;\r
784               }\r
785               else\r
786               {\r
787                 endEditing();\r
788                 return;\r
789               }\r
790             }\r
791           }\r
792 \r
793 \r
794           // drag to left\r
795           else if(!insertGap)\r
796           {\r
797             /// Are we able to delete?\r
798             // ie are all columns blank?\r
799 \r
800             for (int s = 0; s < sg.getSize(); s++)\r
801             {\r
802               seq = sg.getSequenceAt(s);\r
803 \r
804               for (int j = startres; j < lastres; j++)\r
805               {\r
806                 if (seq.getSequence().length() <= j)\r
807                 {\r
808                   continue;\r
809                 }\r
810 \r
811                 if (!jalview.util.Comparison.isGap(\r
812                     seq.getSequence().charAt(j)))\r
813                 {\r
814                   // Not a gap, block edit not valid\r
815                   endEditing();\r
816                   return;\r
817                 }\r
818               }\r
819             }\r
820           }\r
821 \r
822 \r
823           for (int i = 0; i < sg.getSize(); i++)\r
824           {\r
825             seq = sg.getSequenceAt(i);\r
826 \r
827             if (insertGap)\r
828             {\r
829               // dragging to the right\r
830               for (int j = lastres; j < startres; j++)\r
831               {\r
832                 if (fixedColumns && fixedRight != -1)\r
833                 {\r
834                   insertChar(j, seq, fixedRight);\r
835                 }\r
836                 else\r
837                   insertChar(j, seq);\r
838               }\r
839             }\r
840             else\r
841             {\r
842               // dragging to the left\r
843               for (int j = lastres; j > startres; j--)\r
844               {\r
845                 if (fixedColumns && fixedRight != -1)\r
846                 {\r
847                   deleteChar(startres, seq, fixedRight);\r
848                 }\r
849                 else\r
850                 {\r
851                   deleteChar(startres, seq);\r
852                 }\r
853               }\r
854             }\r
855           }\r
856         }\r
857         else /////Editing a single sequence///////////\r
858         {\r
859           if (insertGap)\r
860           {\r
861             // dragging to the right\r
862             for (int j = lastres; j < startres; j++)\r
863             {\r
864               if (fixedColumns && fixedRight != -1)\r
865               {\r
866                 if (sg.getStartRes() == 0\r
867                     && sg.getEndRes() + 1 == av.alignment.getWidth()\r
868                     && !jalview.util.Comparison.isGap(seq.getCharAt(fixedRight)))\r
869                 {\r
870                   //Single sequence edit, whole sequence selected,\r
871                   //extend the selection group\r
872                   sg.setEndRes(av.alignment.getWidth() -1 + startres - lastres);\r
873                   fixedColumns = false;\r
874                   insertChar(j, seq);\r
875                 }\r
876                 else\r
877                   insertChar(j, seq, fixedRight);\r
878               }\r
879               else\r
880                 insertChar(j, seq);\r
881             }\r
882           }\r
883           else\r
884           {\r
885             // dragging to the left\r
886             for (int j = lastres; j > startres; j--)\r
887             {\r
888               if (fixedColumns && fixedRight != -1)\r
889               {\r
890                 deleteChar(startres, seq, fixedRight);\r
891               }\r
892               else\r
893               {\r
894                 deleteChar(startres, seq);\r
895               }\r
896             }\r
897           }\r
898         }\r
899 \r
900         lastres = startres;\r
901         seqCanvas.repaint();\r
902     }\r
903 \r
904 \r
905     /**\r
906      * DOCUMENT ME!\r
907      *\r
908      * @param j DOCUMENT ME!\r
909      * @param seq DOCUMENT ME!\r
910      */\r
911     void insertChar(int j, SequenceI seq)\r
912     {\r
913         seq.insertCharAt(j, av.getGapCharacter());\r
914         seqEditOccurred = true;\r
915     }\r
916 \r
917     void insertChar(int j, SequenceI seq, int fixedColumn)\r
918     {\r
919       //Find the next gap before the end of the visible region boundary\r
920       //If lastCol > j, theres a boundary after the gap insertion\r
921       int blankColumn = fixedColumn;\r
922       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)\r
923       {\r
924         if (jalview.util.Comparison.isGap(seq.getCharAt(blankColumn)))\r
925         {\r
926           //Theres a space, so break and insert the gap\r
927           break;\r
928         }\r
929       }\r
930 \r
931       if (blankColumn <= j)\r
932       {\r
933         endEditing();\r
934         return;\r
935       }\r
936 \r
937       if (!jalview.util.Comparison.isGap(seq.getCharAt(blankColumn)))\r
938       {\r
939         //Just Checking\r
940         System.out.println("Tried removing residue (INSERT)"+seq.getCharAt(fixedColumn));\r
941         return;\r
942       }\r
943 \r
944       seq.deleteCharAt(blankColumn);\r
945       seq.insertCharAt(j, av.getGapCharacter());\r
946       seqEditOccurred = true;\r
947     }\r
948 \r
949     void deleteChar(int j, SequenceI seq, int fixedColumn)\r
950     {\r
951       if (!jalview.util.Comparison.isGap(seq.getCharAt(j)))\r
952       {\r
953         ap.alignFrame.statusBar.setText(\r
954             "End editing: Tried removing residue " + seq.getCharAt(j));\r
955         return;\r
956       }\r
957 \r
958       seq.deleteCharAt(j);\r
959       seq.insertCharAt(fixedColumn, av.getGapCharacter());\r
960       seqEditOccurred = true;\r
961     }\r
962 \r
963     /**\r
964      * DOCUMENT ME!\r
965      *\r
966      * @param j DOCUMENT ME!\r
967      * @param seq DOCUMENT ME!\r
968      */\r
969     void deleteChar(int j, SequenceI seq)\r
970     {\r
971       if (!jalview.util.Comparison.isGap(seq.getCharAt(j)))\r
972       {\r
973         ap.alignFrame.statusBar.setText(\r
974             "End editing: Tried removing residue " + seq.getCharAt(j));\r
975         return;\r
976       }\r
977 \r
978         seq.deleteCharAt(j);\r
979         seqEditOccurred = true;\r
980         seqCanvas.repaint();\r
981     }\r
982     /**\r
983      * DOCUMENT ME!\r
984      *\r
985      * @param e DOCUMENT ME!\r
986      */\r
987     public void mouseEntered(MouseEvent e)\r
988     {\r
989         if(oldSeq < 0)\r
990           oldSeq = 0;\r
991 \r
992         if (scrollThread != null)\r
993         {\r
994             scrollThread.running = false;\r
995             scrollThread = null;\r
996         }\r
997     }\r
998 \r
999     /**\r
1000      * DOCUMENT ME!\r
1001      *\r
1002      * @param e DOCUMENT ME!\r
1003      */\r
1004     public void mouseExited(MouseEvent e)\r
1005     {\r
1006         if (av.getWrapAlignment())\r
1007         {\r
1008             return;\r
1009         }\r
1010 \r
1011         if (mouseDragging)\r
1012         {\r
1013             scrollThread = new ScrollThread();\r
1014         }\r
1015     }\r
1016 \r
1017     public void mouseClicked(MouseEvent evt)\r
1018     {}\r
1019 \r
1020     public void mouseWheelMoved(MouseWheelEvent e)\r
1021     {\r
1022       e.consume();\r
1023       if (mouseWheelPressed)\r
1024       {\r
1025         Font font = av.getFont();\r
1026         int fontSize = font.getSize();\r
1027         if (e.getWheelRotation() > 0 && fontSize < 51)\r
1028           fontSize++;\r
1029         else if (fontSize > 1)\r
1030           fontSize--;\r
1031 \r
1032         av.setFont(new Font(font.getName(), font.getStyle(), fontSize));\r
1033         ap.fontChanged();\r
1034       }\r
1035       else\r
1036       {\r
1037         if (e.getWheelRotation() > 0)\r
1038           ap.scrollUp(false);\r
1039         else\r
1040           ap.scrollUp(true);\r
1041       }\r
1042 \r
1043     }\r
1044 \r
1045 \r
1046 \r
1047     /**\r
1048      * DOCUMENT ME!\r
1049      *\r
1050      * @param i DOCUMENT ME!\r
1051      */\r
1052     void editOccurred()\r
1053     {\r
1054       if (!seqEditOccurred)\r
1055       {\r
1056         ap.alignFrame.historyList.pop();\r
1057         ap.alignFrame.updateEditMenuBar();\r
1058       }\r
1059 \r
1060       endEditing();\r
1061 \r
1062       av.firePropertyChange("alignment", null,av.getAlignment().getSequences());\r
1063 \r
1064     }\r
1065 \r
1066     /**\r
1067      * DOCUMENT ME!\r
1068      *\r
1069      * @param evt DOCUMENT ME!\r
1070      */\r
1071     public void doMousePressedDefineMode(MouseEvent evt)\r
1072     {\r
1073       int res = findRes(evt);\r
1074       int seq = findSeq(evt);\r
1075       oldSeq = seq;\r
1076 \r
1077       startWrapBlock=wrappedBlock;\r
1078 \r
1079       if(av.wrapAlignment && seq>av.alignment.getHeight())\r
1080       {\r
1081           JOptionPane.showInternalMessageDialog(Desktop.desktop,\r
1082               "Cannot edit annotations in wrapped view.",\r
1083               "Wrapped view - no edit",\r
1084               JOptionPane.WARNING_MESSAGE);\r
1085         return;\r
1086       }\r
1087 \r
1088       if(seq<0 || res<0)\r
1089             return;\r
1090 \r
1091         SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);\r
1092 \r
1093         if ((sequence == null) || (res > sequence.getLength()))\r
1094         {\r
1095             return;\r
1096         }\r
1097 \r
1098         stretchGroup = av.getSelectionGroup();\r
1099 \r
1100         if (stretchGroup == null)\r
1101         {\r
1102             stretchGroup = av.alignment.findGroup(sequence);\r
1103 \r
1104             if ((stretchGroup != null) && (res > stretchGroup.getStartRes()) &&\r
1105                     (res < stretchGroup.getEndRes()))\r
1106             {\r
1107                 av.setSelectionGroup(stretchGroup);\r
1108             }\r
1109             else\r
1110             {\r
1111                 stretchGroup = null;\r
1112             }\r
1113         }\r
1114         else if (!stretchGroup.sequences.contains(sequence) ||\r
1115                 (stretchGroup.getStartRes() > res) ||\r
1116                 (stretchGroup.getEndRes() < res))\r
1117         {\r
1118             stretchGroup = null;\r
1119 \r
1120             SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence);\r
1121 \r
1122             if (allGroups != null)\r
1123             {\r
1124                 for (int i = 0; i < allGroups.length; i++)\r
1125                 {\r
1126                     if ((allGroups[i].getStartRes() <= res) &&\r
1127                             (allGroups[i].getEndRes() >= res))\r
1128                     {\r
1129                         stretchGroup = allGroups[i];\r
1130                         av.setSelectionGroup(stretchGroup);\r
1131 \r
1132                         break;\r
1133                     }\r
1134                 }\r
1135             }\r
1136         }\r
1137 \r
1138         if (av.cursorMode)\r
1139         {\r
1140           seqCanvas.cursorX = findRes(evt);\r
1141           seqCanvas.cursorY = findSeq(evt);\r
1142           seqCanvas.repaint();\r
1143           return;\r
1144         }\r
1145 \r
1146 \r
1147         if (stretchGroup == null)\r
1148         {\r
1149             // define a new group here\r
1150             SequenceGroup sg = new SequenceGroup();\r
1151             sg.setStartRes(res);\r
1152             sg.setEndRes(res);\r
1153             sg.addSequence(sequence, false);\r
1154             av.setSelectionGroup(sg);\r
1155             stretchGroup = sg;\r
1156 \r
1157             if (av.getConservationSelected())\r
1158             {\r
1159                 SliderPanel.setConservationSlider(ap,\r
1160                     av.getGlobalColourScheme(), "Background");\r
1161             }\r
1162 \r
1163             if (av.getAbovePIDThreshold())\r
1164             {\r
1165                 SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),\r
1166                     "Background");\r
1167             }\r
1168         }\r
1169         else if (javax.swing.SwingUtilities.isRightMouseButton(evt))\r
1170         {\r
1171             jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null);\r
1172             pop.show(this, evt.getX(), evt.getY());\r
1173 \r
1174             // edit the properties of existing group\r
1175         }\r
1176 \r
1177         if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))\r
1178         {\r
1179             // Edit end res position of selected group\r
1180             changeEndRes = true;\r
1181         }\r
1182         else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))\r
1183         {\r
1184             // Edit end res position of selected group\r
1185             changeStartRes = true;\r
1186         }\r
1187 \r
1188         stretchGroup.getWidth();\r
1189 \r
1190         seqCanvas.repaint();\r
1191     }\r
1192 \r
1193     /**\r
1194      * DOCUMENT ME!\r
1195      *\r
1196      * @param evt DOCUMENT ME!\r
1197      */\r
1198     public void doMouseReleasedDefineMode(MouseEvent evt)\r
1199     {\r
1200         if (stretchGroup == null)\r
1201         {\r
1202             return;\r
1203         }\r
1204 \r
1205 \r
1206         if(stretchGroup.cs!=null)\r
1207         {\r
1208           if (stretchGroup.cs instanceof ClustalxColourScheme)\r
1209           {\r
1210             ( (ClustalxColourScheme) stretchGroup.cs).resetClustalX(stretchGroup.\r
1211                 sequences,\r
1212                 stretchGroup.getWidth());\r
1213           }\r
1214 \r
1215           if (stretchGroup.cs.conservationApplied())\r
1216           {\r
1217             SliderPanel.setConservationSlider(ap, stretchGroup.cs,\r
1218                                               stretchGroup.getName());\r
1219             stretchGroup.recalcConservation();\r
1220           }\r
1221           else\r
1222           {\r
1223             SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,\r
1224                                            stretchGroup.getName());\r
1225           }\r
1226         }\r
1227         changeEndRes = false;\r
1228         changeStartRes = false;\r
1229         stretchGroup = null;\r
1230         PaintRefresher.Refresh(av.alignment);\r
1231     }\r
1232 \r
1233     /**\r
1234      * DOCUMENT ME!\r
1235      *\r
1236      * @param evt DOCUMENT ME!\r
1237      */\r
1238     public void doMouseDraggedDefineMode(MouseEvent evt)\r
1239     {\r
1240       int res = findRes(evt);\r
1241       int y = findSeq(evt);\r
1242 \r
1243       if(wrappedBlock!=startWrapBlock)\r
1244         return;\r
1245 \r
1246        if (stretchGroup == null)\r
1247        {\r
1248             return;\r
1249        }\r
1250 \r
1251 \r
1252         if(y > av.alignment.getHeight())\r
1253         {\r
1254           y = av.alignment.getHeight() -1;\r
1255         }\r
1256         if(res> av.alignment.getWidth())\r
1257         {\r
1258           res = av.alignment.getWidth()-1;\r
1259         }\r
1260 \r
1261         if (stretchGroup.getEndRes() == res)\r
1262         {\r
1263             // Edit end res position of selected group\r
1264             changeEndRes = true;\r
1265         }\r
1266         else if (stretchGroup.getStartRes() == res)\r
1267         {\r
1268             // Edit start res position of selected group\r
1269             changeStartRes = true;\r
1270         }\r
1271 \r
1272         if (res < av.getStartRes())\r
1273         {\r
1274             res = av.getStartRes();\r
1275         }\r
1276 \r
1277         if (changeEndRes)\r
1278         {\r
1279             if (res > (stretchGroup.getStartRes() - 1))\r
1280             {\r
1281                 stretchGroup.setEndRes(res);\r
1282             }\r
1283         }\r
1284         else if (changeStartRes)\r
1285         {\r
1286             if (res < (stretchGroup.getEndRes() + 1))\r
1287             {\r
1288                 stretchGroup.setStartRes(res);\r
1289             }\r
1290         }\r
1291 \r
1292         int dragDirection = 0;\r
1293 \r
1294         if (y > oldSeq)\r
1295         {\r
1296             dragDirection = 1;\r
1297         }\r
1298         else if (y < oldSeq)\r
1299         {\r
1300             dragDirection = -1;\r
1301         }\r
1302 \r
1303 \r
1304         while ((y != oldSeq) && (oldSeq > -1) && (y < av.alignment.getHeight()))\r
1305         {\r
1306             // This routine ensures we don't skip any sequences, as the\r
1307             // selection is quite slow.\r
1308             Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
1309 \r
1310             oldSeq += dragDirection;\r
1311 \r
1312             if(oldSeq<0)\r
1313               break;\r
1314 \r
1315             Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);\r
1316 \r
1317             if (stretchGroup.sequences.contains(nextSeq))\r
1318             {\r
1319                 stretchGroup.deleteSequence(seq, false);\r
1320             }\r
1321             else\r
1322             {\r
1323                 if (seq != null)\r
1324                 {\r
1325                     stretchGroup.addSequence(seq, false);\r
1326                 }\r
1327 \r
1328                 stretchGroup.addSequence(nextSeq, false);\r
1329             }\r
1330         }\r
1331 \r
1332         if(oldSeq < 0)\r
1333           oldSeq = -1;\r
1334 \r
1335         mouseDragging = true;\r
1336 \r
1337         if (scrollThread != null)\r
1338         {\r
1339             scrollThread.setEvent(evt);\r
1340         }\r
1341 \r
1342         seqCanvas.repaint();\r
1343     }\r
1344 \r
1345     void scrollCanvas(MouseEvent evt)\r
1346     {\r
1347       if(evt==null)\r
1348       {\r
1349         if(scrollThread!=null)\r
1350         {\r
1351           scrollThread.running = false;\r
1352           scrollThread = null;\r
1353         }\r
1354         mouseDragging = false;\r
1355       }\r
1356       else\r
1357       {\r
1358         if (scrollThread == null)\r
1359           scrollThread = new ScrollThread();\r
1360 \r
1361         mouseDragging = true;\r
1362         scrollThread.setEvent(evt);\r
1363       }\r
1364 \r
1365     }\r
1366 \r
1367 \r
1368     // this class allows scrolling off the bottom of the visible alignment\r
1369     class ScrollThread extends Thread\r
1370     {\r
1371         MouseEvent evt;\r
1372         boolean running = false;\r
1373 \r
1374         public ScrollThread()\r
1375         {\r
1376             start();\r
1377         }\r
1378 \r
1379         public void setEvent(MouseEvent e)\r
1380         {\r
1381             evt = e;\r
1382         }\r
1383 \r
1384         public void stopScrolling()\r
1385         {\r
1386             running = false;\r
1387         }\r
1388 \r
1389         public void run()\r
1390         {\r
1391             running = true;\r
1392 \r
1393             while (running)\r
1394             {\r
1395                 if (evt != null)\r
1396                 {\r
1397                     if (mouseDragging && (evt.getY() < 0) &&\r
1398                             (av.getStartSeq() > 0))\r
1399                     {\r
1400                         running = ap.scrollUp(true);\r
1401                     }\r
1402 \r
1403                     if (mouseDragging && (evt.getY() >= getHeight()) &&\r
1404                             (av.alignment.getHeight() > av.getEndSeq()))\r
1405                     {\r
1406                         running = ap.scrollUp(false);\r
1407                     }\r
1408 \r
1409                     if (mouseDragging && (evt.getX() < 0))\r
1410                     {\r
1411                         running = ap.scrollRight(false);\r
1412                     }\r
1413                     else if (mouseDragging && (evt.getX() >= getWidth()))\r
1414                     {\r
1415                         running = ap.scrollRight(true);\r
1416                     }\r
1417                 }\r
1418 \r
1419                 try\r
1420                 {\r
1421                     Thread.sleep(20);\r
1422                 }\r
1423                 catch (Exception ex)\r
1424                 {\r
1425                 }\r
1426             }\r
1427         }\r
1428     }\r
1429 }\r