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