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