8f63c525ede7dfb292755feff8ffb360bd658c9a
[jalview.git] / src / jalview / gui / SeqPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import javax.swing.*;
26
27 import jalview.commands.*;
28 import jalview.datamodel.*;
29 import jalview.schemes.*;
30 import jalview.structure.*;
31
32 /**
33  * DOCUMENT ME!
34  * 
35  * @author $author$
36  * @version $Revision$
37  */
38 public class SeqPanel extends JPanel implements MouseListener,
39         MouseMotionListener, MouseWheelListener, SequenceListener, SelectionListener
40
41 {
42   /** DOCUMENT ME!! */
43   public SeqCanvas seqCanvas;
44
45   /** DOCUMENT ME!! */
46   public AlignmentPanel ap;
47
48   protected int lastres;
49
50   protected int startseq;
51
52   protected AlignViewport av;
53
54   ScrollThread scrollThread = null;
55
56   boolean mouseDragging = false;
57
58   boolean editingSeqs = false;
59
60   boolean groupEditing = false;
61
62   // ////////////////////////////////////////
63   // ///Everything below this is for defining the boundary of the rubberband
64   // ////////////////////////////////////////
65   int oldSeq = -1;
66
67   boolean changeEndSeq = false;
68
69   boolean changeStartSeq = false;
70
71   boolean changeEndRes = false;
72
73   boolean changeStartRes = false;
74
75   SequenceGroup stretchGroup = null;
76
77   boolean remove = false;
78
79   Point lastMousePress;
80
81   boolean mouseWheelPressed = false;
82
83   StringBuffer keyboardNo1;
84
85   StringBuffer keyboardNo2;
86
87   java.net.URL linkImageURL;
88
89   StringBuffer tooltipText = new StringBuffer("<html>");
90
91   String tmpString;
92
93   EditCommand editCommand;
94
95   StructureSelectionManager ssm;
96
97   /**
98    * Creates a new SeqPanel object.
99    * 
100    * @param avp
101    *                DOCUMENT ME!
102    * @param p
103    *                DOCUMENT ME!
104    */
105   public SeqPanel(AlignViewport av, AlignmentPanel ap)
106   {
107     linkImageURL = getClass().getResource("/images/link.gif");
108     ToolTipManager.sharedInstance().registerComponent(this);
109     ToolTipManager.sharedInstance().setInitialDelay(0);
110     ToolTipManager.sharedInstance().setDismissDelay(10000);
111     this.av = av;
112     setBackground(Color.white);
113
114     seqCanvas = new SeqCanvas(ap);
115     setLayout(new BorderLayout());
116     add(seqCanvas, BorderLayout.CENTER);
117
118     this.ap = ap;
119
120     if (!av.isDataset())
121     {
122       addMouseMotionListener(this);
123       addMouseListener(this);
124       addMouseWheelListener(this);
125       ssm = StructureSelectionManager.getStructureSelectionManager();
126       ssm.addStructureViewerListener(this);
127       ssm.addSelectionListener(this);
128     }
129   }
130
131   int startWrapBlock = -1;
132
133   int wrappedBlock = -1;
134
135   int findRes(MouseEvent evt)
136   {
137     int res = 0;
138     int x = evt.getX();
139
140     if (av.wrapAlignment)
141     {
142
143       int hgap = av.charHeight;
144       if (av.scaleAboveWrapped)
145       {
146         hgap += av.charHeight;
147       }
148
149       int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
150               + seqCanvas.getAnnotationHeight();
151
152       int y = evt.getY();
153       y -= hgap;
154       x -= seqCanvas.LABEL_WEST;
155
156       int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
157       if (cwidth < 1)
158       {
159         return 0;
160       }
161
162       wrappedBlock = y / cHeight;
163       wrappedBlock += av.getStartRes() / cwidth;
164
165       res = wrappedBlock * cwidth + x / av.getCharWidth();
166
167     }
168     else
169     {
170       res = (x / av.getCharWidth()) + av.getStartRes();
171     }
172
173     if (av.hasHiddenColumns)
174     {
175       res = av.getColumnSelection().adjustForHiddenColumns(res);
176     }
177
178     return res;
179
180   }
181
182   int findSeq(MouseEvent evt)
183   {
184     int seq = 0;
185     int y = evt.getY();
186
187     if (av.wrapAlignment)
188     {
189       int hgap = av.charHeight;
190       if (av.scaleAboveWrapped)
191       {
192         hgap += av.charHeight;
193       }
194
195       int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
196               + seqCanvas.getAnnotationHeight();
197
198       y -= hgap;
199
200       seq = Math.min((y % cHeight) / av.getCharHeight(), av.alignment
201               .getHeight() - 1);
202     }
203     else
204     {
205       seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(),
206               av.alignment.getHeight() - 1);
207     }
208
209     return seq;
210   }
211
212   SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
213   {
214     Vector tmp = new Vector();
215     SequenceFeature[] features = sequence.getSequenceFeatures();
216     if (features != null)
217     {
218       for (int i = 0; i < features.length; i++)
219       {
220         if (av.featuresDisplayed == null
221                 || !av.featuresDisplayed.containsKey(features[i].getType()))
222         {
223           continue;
224         }
225
226         if (features[i].featureGroup != null
227                 && seqCanvas.fr.featureGroups != null
228                 && seqCanvas.fr.featureGroups
229                         .containsKey(features[i].featureGroup)
230                 && !((Boolean) seqCanvas.fr.featureGroups
231                         .get(features[i].featureGroup)).booleanValue())
232           continue;
233
234         if ((features[i].getBegin() <= res)
235                 && (features[i].getEnd() >= res))
236         {
237           tmp.addElement(features[i]);
238         }
239       }
240     }
241
242     features = new SequenceFeature[tmp.size()];
243     tmp.copyInto(features);
244
245     return features;
246   }
247
248   void endEditing()
249   {
250     if (editCommand != null && editCommand.getSize() > 0)
251     {
252       ap.alignFrame.addHistoryItem(editCommand);
253       av.firePropertyChange("alignment", null, av.getAlignment()
254               .getSequences());
255     }
256
257     startseq = -1;
258     lastres = -1;
259     editingSeqs = false;
260     groupEditing = false;
261     keyboardNo1 = null;
262     keyboardNo2 = null;
263     editCommand = null;
264   }
265
266   void setCursorRow()
267   {
268     seqCanvas.cursorY = getKeyboardNo1() - 1;
269     scrollToVisible();
270   }
271
272   void setCursorColumn()
273   {
274     seqCanvas.cursorX = getKeyboardNo1() - 1;
275     scrollToVisible();
276   }
277
278   void setCursorRowAndColumn()
279   {
280     if (keyboardNo2 == null)
281     {
282       keyboardNo2 = new StringBuffer();
283     }
284     else
285     {
286       seqCanvas.cursorX = getKeyboardNo1() - 1;
287       seqCanvas.cursorY = getKeyboardNo2() - 1;
288       scrollToVisible();
289     }
290   }
291
292   void setCursorPosition()
293   {
294     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(
295             seqCanvas.cursorY);
296
297     seqCanvas.cursorX = sequence.findIndex(getKeyboardNo1() - 1);
298     scrollToVisible();
299   }
300
301   void moveCursor(int dx, int dy)
302   {
303     seqCanvas.cursorX += dx;
304     seqCanvas.cursorY += dy;
305     if (av.hasHiddenColumns && !av.colSel.isVisible(seqCanvas.cursorX))
306     {
307       int original = seqCanvas.cursorX - dx;
308       int maxWidth = av.alignment.getWidth();
309
310       while (!av.colSel.isVisible(seqCanvas.cursorX)
311               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
312       {
313         seqCanvas.cursorX += dx;
314       }
315
316       if (seqCanvas.cursorX >= maxWidth
317               || !av.colSel.isVisible(seqCanvas.cursorX))
318       {
319         seqCanvas.cursorX = original;
320       }
321     }
322
323     scrollToVisible();
324   }
325
326   void scrollToVisible()
327   {
328     if (seqCanvas.cursorX < 0)
329     {
330       seqCanvas.cursorX = 0;
331     }
332     else if (seqCanvas.cursorX > av.alignment.getWidth() - 1)
333     {
334       seqCanvas.cursorX = av.alignment.getWidth() - 1;
335     }
336
337     if (seqCanvas.cursorY < 0)
338     {
339       seqCanvas.cursorY = 0;
340     }
341     else if (seqCanvas.cursorY > av.alignment.getHeight() - 1)
342     {
343       seqCanvas.cursorY = av.alignment.getHeight() - 1;
344     }
345
346     endEditing();
347     if (av.wrapAlignment)
348     {
349       ap.scrollToWrappedVisible(seqCanvas.cursorX);
350     }
351     else
352     {
353       while (seqCanvas.cursorY < av.startSeq)
354       {
355         ap.scrollUp(true);
356       }
357       while (seqCanvas.cursorY + 1 > av.endSeq)
358       {
359         ap.scrollUp(false);
360       }
361       if (!av.wrapAlignment)
362       {
363         while (seqCanvas.cursorX < av.colSel
364                 .adjustForHiddenColumns(av.startRes))
365         {
366           if (!ap.scrollRight(false))
367           {
368             break;
369           }
370         }
371         while (seqCanvas.cursorX > av.colSel
372                 .adjustForHiddenColumns(av.endRes))
373         {
374           if (!ap.scrollRight(true))
375           {
376             break;
377           }
378         }
379       }
380     }
381     setStatusMessage(av.alignment.getSequenceAt(seqCanvas.cursorY),
382             seqCanvas.cursorX, seqCanvas.cursorY);
383
384     seqCanvas.repaint();
385   }
386
387   void setSelectionAreaAtCursor(boolean topLeft)
388   {
389     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(
390             seqCanvas.cursorY);
391
392     if (av.getSelectionGroup() != null)
393     {
394       SequenceGroup sg = av.selectionGroup;
395       // Find the top and bottom of this group
396       int min = av.alignment.getHeight(), max = 0;
397       for (int i = 0; i < sg.getSize(); i++)
398       {
399         int index = av.alignment.findIndex(sg.getSequenceAt(i));
400         if (index > max)
401         {
402           max = index;
403         }
404         if (index < min)
405         {
406           min = index;
407         }
408       }
409
410       max++;
411
412       if (topLeft)
413       {
414         sg.setStartRes(seqCanvas.cursorX);
415         if (sg.getEndRes() < seqCanvas.cursorX)
416         {
417           sg.setEndRes(seqCanvas.cursorX);
418         }
419
420         min = seqCanvas.cursorY;
421       }
422       else
423       {
424         sg.setEndRes(seqCanvas.cursorX);
425         if (sg.getStartRes() > seqCanvas.cursorX)
426         {
427           sg.setStartRes(seqCanvas.cursorX);
428         }
429
430         max = seqCanvas.cursorY + 1;
431       }
432
433       if (min > max)
434       {
435         // Only the user can do this
436         av.setSelectionGroup(null);
437       }
438       else
439       {
440         // Now add any sequences between min and max
441         sg.getSequences(null).clear();
442         for (int i = min; i < max; i++)
443         {
444           sg.addSequence(av.alignment.getSequenceAt(i), false);
445         }
446       }
447     }
448
449     if (av.getSelectionGroup() == null)
450     {
451       SequenceGroup sg = new SequenceGroup();
452       sg.setStartRes(seqCanvas.cursorX);
453       sg.setEndRes(seqCanvas.cursorX);
454       sg.addSequence(sequence, false);
455       av.setSelectionGroup(sg);
456     }
457
458     ap.paintAlignment(false);
459     av.sendSelection();
460   }
461
462   void insertGapAtCursor(boolean group)
463   {
464     groupEditing = group;
465     startseq = seqCanvas.cursorY;
466     lastres = seqCanvas.cursorX;
467     editSequence(true, seqCanvas.cursorX + getKeyboardNo1());
468     endEditing();
469   }
470
471   void deleteGapAtCursor(boolean group)
472   {
473     groupEditing = group;
474     startseq = seqCanvas.cursorY;
475     lastres = seqCanvas.cursorX + getKeyboardNo1();
476     editSequence(false, seqCanvas.cursorX);
477     endEditing();
478   }
479
480   void numberPressed(char value)
481   {
482     if (keyboardNo1 == null)
483     {
484       keyboardNo1 = new StringBuffer();
485     }
486
487     if (keyboardNo2 != null)
488     {
489       keyboardNo2.append(value);
490     }
491     else
492     {
493       keyboardNo1.append(value);
494     }
495   }
496
497   int getKeyboardNo1()
498   {
499     if (keyboardNo1 == null)
500       return 1;
501     else
502     {
503       int value = Integer.parseInt(keyboardNo1.toString());
504       keyboardNo1 = null;
505       return value;
506     }
507   }
508
509   int getKeyboardNo2()
510   {
511     if (keyboardNo2 == null)
512       return 1;
513     else
514     {
515       int value = Integer.parseInt(keyboardNo2.toString());
516       keyboardNo2 = null;
517       return value;
518     }
519   }
520
521   /**
522    * DOCUMENT ME!
523    * 
524    * @param evt
525    *                DOCUMENT ME!
526    */
527   public void mouseReleased(MouseEvent evt)
528   {
529     mouseDragging = false;
530     mouseWheelPressed = false;
531
532     if (!editingSeqs)
533     {
534       doMouseReleasedDefineMode(evt);
535       return;
536     }
537
538     endEditing();
539   }
540
541   /**
542    * DOCUMENT ME!
543    * 
544    * @param evt
545    *                DOCUMENT ME!
546    */
547   public void mousePressed(MouseEvent evt)
548   {
549     lastMousePress = evt.getPoint();
550
551     if (javax.swing.SwingUtilities.isMiddleMouseButton(evt))
552     {
553       mouseWheelPressed = true;
554       return;
555     }
556
557     if (evt.isShiftDown() || evt.isAltDown() || evt.isControlDown())
558     {
559       if (evt.isAltDown() || evt.isControlDown())
560       {
561         groupEditing = true;
562       }
563       editingSeqs = true;
564     }
565     else
566     {
567       doMousePressedDefineMode(evt);
568       return;
569     }
570
571     int seq = findSeq(evt);
572     int res = findRes(evt);
573
574     if (seq < 0 || res < 0)
575     {
576       return;
577     }
578
579     if ((seq < av.getAlignment().getHeight())
580             && (res < av.getAlignment().getSequenceAt(seq).getLength()))
581     {
582       startseq = seq;
583       lastres = res;
584     }
585     else
586     {
587       startseq = -1;
588       lastres = -1;
589     }
590
591     return;
592   }
593
594   String lastMessage;
595
596   public void mouseOverSequence(SequenceI sequence, int index, int pos)
597   {
598     String tmp = sequence.hashCode() + " " + index + " " + pos;
599
600     if (lastMessage == null || !lastMessage.equals(tmp))
601     {
602       // System.err.println("mouseOver Sequence: "+tmp);
603       ssm.mouseOverSequence(sequence, index, pos);
604     }
605     lastMessage = tmp;
606   }
607
608   public void highlightSequence(SearchResults results)
609   {
610     if (av.followHighlight)
611     {
612       ap.scrollToPosition(results, false);
613     }
614     seqCanvas.highlightSearchResults(results);
615   }
616
617   public void updateColours(SequenceI seq, int index)
618   {
619     System.out.println("update the seqPanel colours");
620     // repaint();
621   }
622
623   /**
624    * DOCUMENT ME!
625    * 
626    * @param evt
627    *                DOCUMENT ME!
628    */
629   public void mouseMoved(MouseEvent evt)
630   {
631     if (editingSeqs)
632     {
633       // This is because MacOSX creates a mouseMoved
634       // If control is down, other platforms will not.
635       mouseDragged(evt);
636     }
637
638     int res = findRes(evt);
639     int seq = findSeq(evt);
640     int pos;
641     if (res < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
642     {
643       return;
644     }
645
646     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
647
648     if (res >= sequence.getLength())
649     {
650       return;
651     }
652
653     pos = setStatusMessage(sequence, res, seq);
654     if (ssm != null && pos > -1)
655       mouseOverSequence(sequence, res, pos);
656
657     tooltipText.setLength(6); // Cuts the buffer back to <html>
658
659     SequenceGroup[] groups = av.alignment.findAllGroups(sequence);
660     if (groups != null)
661     {
662       for (int g = 0; g < groups.length; g++)
663       {
664         if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
665         {
666           if (tooltipText.length() > 6)
667           {
668             tooltipText.append("<br>");
669           }
670
671           if (!groups[g].getName().startsWith("JTreeGroup")
672                   && !groups[g].getName().startsWith("JGroup"))
673           {
674             tooltipText.append(groups[g].getName());
675           }
676
677           if (groups[g].getDescription() != null)
678           {
679             tooltipText.append(": " + groups[g].getDescription());
680           }
681         }
682       }
683     }
684
685     // use aa to see if the mouse pointer is on a
686     if (av.showSequenceFeatures)
687     {
688       int rpos;
689       SequenceFeature[] features = findFeaturesAtRes(sequence
690               .getDatasetSequence(), rpos = sequence.findPosition(res));
691       appendFeatures(tooltipText, linkImageURL.toString(), rpos, features);
692     }
693     if (tooltipText.length() == 6) // <html></html>
694     {
695       setToolTipText(null);
696     }
697     else
698     {
699       tooltipText.append("</html>");
700       if (lastTooltip == null
701               || !lastTooltip.equals(tooltipText.toString()))
702         setToolTipText(tooltipText.toString());
703
704       lastTooltip = tooltipText.toString();
705     }
706
707   }
708   /**
709    * appends the features at rpos to the given stringbuffer ready for display in a tooltip
710    * @param tooltipText2
711    * @param linkImageURL
712    * @param rpos
713    * @param features
714    * TODO refactor to Jalview 'utilities' somehow. 
715    */
716   public static void appendFeatures(StringBuffer tooltipText2, String linkImageURL,
717           int rpos, SequenceFeature[] features)
718   {
719     String tmpString;
720     if (features != null)
721     {
722       for (int i = 0; i < features.length; i++)
723       {
724         if (features[i].getType().equals("disulfide bond"))
725         {
726           if (features[i].getBegin() == rpos
727                   || features[i].getEnd() == rpos)
728           {
729             if (tooltipText2.length() > 6)
730             {
731               tooltipText2.append("<br>");
732             }
733             tooltipText2.append("disulfide bond " + features[i].getBegin()
734                     + ":" + features[i].getEnd());
735             if (features[i].links != null)
736             {
737               tooltipText2.append(" <img src=\"" + linkImageURL + "\">");
738             }
739           }
740         }
741         else
742         {
743           if (tooltipText2.length() > 6)
744           {
745             tooltipText2.append("<br>");
746           }
747
748           tooltipText2.append(features[i].getType() + " ");
749           if (rpos!=0)
750           {
751             // we are marking a positional feature
752             tooltipText2.append(features[i].begin);
753           }
754           if (features[i].begin != features[i].end)
755           {
756             tooltipText2.append(" " + features[i].end);
757           }
758
759           if (features[i].getDescription() != null
760                   && !features[i].description.equals(features[i]
761                           .getType()))
762           {
763             tmpString = features[i].getDescription();
764             int startTag = tmpString.toUpperCase().indexOf("<HTML>");
765             if (startTag > -1)
766             {
767               tmpString = tmpString.substring(startTag + 6);
768             }
769             int endTag = tmpString.toUpperCase().indexOf("</BODY>");
770             if (endTag > -1)
771             {
772               tmpString = tmpString.substring(0, endTag);
773             }
774             endTag = tmpString.toUpperCase().indexOf("</HTML>");
775             if (endTag > -1)
776             {
777               tmpString = tmpString.substring(0, endTag);
778             }
779
780             if (startTag > -1)
781             {
782               tooltipText2.append("; " + tmpString);
783             }
784             else
785             {
786               if (tmpString.indexOf("<") > -1
787                       || tmpString.indexOf(">") > -1)
788               {
789                 // The description does not specify html is to
790                 // be used, so we must remove < > symbols
791                 tmpString = tmpString.replaceAll("<", "&lt;");
792                 tmpString = tmpString.replaceAll(">", "&gt;");
793
794                 tooltipText2.append("; ");
795                 tooltipText2.append(tmpString);
796
797               }
798               else
799               {
800                 tooltipText2.append("; " + tmpString);
801               }
802             }
803           }
804           if (features[i].getScore()!=Float.NaN)
805           {
806             tooltipText2.append(" Score="+features[i].getScore());
807           }
808           if (features[i].getValue("status") != null)
809           {
810             String status = features[i].getValue("status").toString();
811             if (status.length() > 0)
812             {
813               tooltipText2.append("; (" + features[i].getValue("status")
814                       + ")");
815             }
816           }
817
818           if (features[i].links != null)
819           {
820             tooltipText2.append(" <img src=\"" + linkImageURL + "\">");
821           }
822
823         }
824       }
825     }
826   }
827
828   String lastTooltip;
829
830   /**
831    * Set status message in alignment panel
832    * 
833    * @param sequence
834    *                aligned sequence object
835    * @param res
836    *                alignment column
837    * @param seq
838    *                index of sequence in alignment
839    * @return position of res in sequence
840    */
841   int setStatusMessage(SequenceI sequence, int res, int seq)
842   {
843     int pos = -1;
844     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
845             + sequence.getName());
846
847     Object obj = null;
848     if (av.alignment.isNucleotide())
849     {
850       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
851               + "");
852       if (obj != null)
853       {
854         text.append(" Nucleotide: ");
855       }
856     }
857     else
858     {
859       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
860       if (obj != null)
861       {
862         text.append("  Residue: ");
863       }
864     }
865
866     if (obj != null)
867     {
868       pos = sequence.findPosition(res);
869       if (obj != "")
870       {
871         text.append(obj + " (" + pos + ")");
872       }
873     }
874     ap.alignFrame.statusBar.setText(text.toString());
875     return pos;
876   }
877
878   /**
879    * DOCUMENT ME!
880    * 
881    * @param evt
882    *                DOCUMENT ME!
883    */
884   public void mouseDragged(MouseEvent evt)
885   {
886     if (mouseWheelPressed)
887     {
888       int oldWidth = av.charWidth;
889
890       // Which is bigger, left-right or up-down?
891       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
892               .getX()
893               - lastMousePress.getX()))
894       {
895         int fontSize = av.font.getSize();
896
897         if (evt.getY() < lastMousePress.getY())
898         {
899           fontSize--;
900         }
901         else if (evt.getY() > lastMousePress.getY())
902         {
903           fontSize++;
904         }
905
906         if (fontSize < 1)
907         {
908           fontSize = 1;
909         }
910
911         av
912                 .setFont(new Font(av.font.getName(), av.font.getStyle(),
913                         fontSize));
914         av.charWidth = oldWidth;
915         ap.fontChanged();
916       }
917       else
918       {
919         if (evt.getX() < lastMousePress.getX() && av.charWidth > 1)
920         {
921           av.charWidth--;
922         }
923         else if (evt.getX() > lastMousePress.getX())
924         {
925           av.charWidth++;
926         }
927
928         ap.paintAlignment(false);
929       }
930
931       FontMetrics fm = getFontMetrics(av.getFont());
932       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
933
934       lastMousePress = evt.getPoint();
935
936       return;
937     }
938
939     if (!editingSeqs)
940     {
941       doMouseDraggedDefineMode(evt);
942       return;
943     }
944
945     int res = findRes(evt);
946
947     if (res < 0)
948     {
949       res = 0;
950     }
951
952     if ((lastres == -1) || (lastres == res))
953     {
954       return;
955     }
956
957     if ((res < av.getAlignment().getWidth()) && (res < lastres))
958     {
959       // dragLeft, delete gap
960       editSequence(false, res);
961     }
962     else
963     {
964       editSequence(true, res);
965     }
966
967     mouseDragging = true;
968     if (scrollThread != null)
969     {
970       scrollThread.setEvent(evt);
971     }
972   }
973
974   synchronized void editSequence(boolean insertGap, int startres)
975   {
976     int fixedLeft = -1;
977     int fixedRight = -1;
978     boolean fixedColumns = false;
979     SequenceGroup sg = av.getSelectionGroup();
980
981     SequenceI seq = av.alignment.getSequenceAt(startseq);
982
983     // No group, but the sequence may represent a group
984     if (!groupEditing && av.hasHiddenRows)
985     {
986       if (av.hiddenRepSequences != null
987               && av.hiddenRepSequences.containsKey(seq))
988       {
989         sg = (SequenceGroup) av.hiddenRepSequences.get(seq);
990         groupEditing = true;
991       }
992     }
993
994     StringBuffer message = new StringBuffer();
995     if (groupEditing)
996     {
997       message.append("Edit group:");
998       if (editCommand == null)
999       {
1000         editCommand = new EditCommand("Edit Group");
1001       }
1002     }
1003     else
1004     {
1005       message.append("Edit sequence: " + seq.getName());
1006       String label = seq.getName();
1007       if (label.length() > 10)
1008       {
1009         label = label.substring(0, 10);
1010       }
1011       if (editCommand == null)
1012       {
1013         editCommand = new EditCommand("Edit " + label);
1014       }
1015     }
1016
1017     if (insertGap)
1018     {
1019       message.append(" insert ");
1020     }
1021     else
1022     {
1023       message.append(" delete ");
1024     }
1025
1026     message.append(Math.abs(startres - lastres) + " gaps.");
1027     ap.alignFrame.statusBar.setText(message.toString());
1028
1029     // Are we editing within a selection group?
1030     if (groupEditing
1031             || (sg != null && sg.getSequences(av.hiddenRepSequences)
1032                     .contains(seq)))
1033     {
1034       fixedColumns = true;
1035
1036       // sg might be null as the user may only see 1 sequence,
1037       // but the sequence represents a group
1038       if (sg == null)
1039       {
1040         if (av.hiddenRepSequences == null
1041                 || !av.hiddenRepSequences.containsKey(seq))
1042         {
1043           endEditing();
1044           return;
1045         }
1046         sg = (SequenceGroup) av.hiddenRepSequences.get(seq);
1047       }
1048
1049       fixedLeft = sg.getStartRes();
1050       fixedRight = sg.getEndRes();
1051
1052       if ((startres < fixedLeft && lastres >= fixedLeft)
1053               || (startres >= fixedLeft && lastres < fixedLeft)
1054               || (startres > fixedRight && lastres <= fixedRight)
1055               || (startres <= fixedRight && lastres > fixedRight))
1056       {
1057         endEditing();
1058         return;
1059       }
1060
1061       if (fixedLeft > startres)
1062       {
1063         fixedRight = fixedLeft - 1;
1064         fixedLeft = 0;
1065       }
1066       else if (fixedRight < startres)
1067       {
1068         fixedLeft = fixedRight;
1069         fixedRight = -1;
1070       }
1071     }
1072
1073     if (av.hasHiddenColumns)
1074     {
1075       fixedColumns = true;
1076       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1077       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1078
1079       if ((insertGap && startres > y1 && lastres < y1)
1080               || (!insertGap && startres < y2 && lastres > y2))
1081       {
1082         endEditing();
1083         return;
1084       }
1085
1086       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1087       // Selection spans a hidden region
1088       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1089       {
1090         if (startres >= y2)
1091         {
1092           fixedLeft = y2;
1093         }
1094         else
1095         {
1096           fixedRight = y2 - 1;
1097         }
1098       }
1099     }
1100
1101     if (groupEditing)
1102     {
1103       Vector vseqs = sg.getSequences(av.hiddenRepSequences);
1104       int g, groupSize = vseqs.size();
1105       SequenceI[] groupSeqs = new SequenceI[groupSize];
1106       for (g = 0; g < groupSeqs.length; g++)
1107       {
1108         groupSeqs[g] = (SequenceI) vseqs.elementAt(g);
1109       }
1110
1111       // drag to right
1112       if (insertGap)
1113       {
1114         // If the user has selected the whole sequence, and is dragging to
1115         // the right, we can still extend the alignment and selectionGroup
1116         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1117                 && sg.getEndRes() == av.alignment.getWidth() - 1)
1118         {
1119           sg.setEndRes(av.alignment.getWidth() + startres - lastres);
1120           fixedRight = sg.getEndRes();
1121         }
1122
1123         // Is it valid with fixed columns??
1124         // Find the next gap before the end
1125         // of the visible region boundary
1126         boolean blank = false;
1127         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1128         {
1129           blank = true;
1130
1131           for (g = 0; g < groupSize; g++)
1132           {
1133             for (int j = 0; j < startres - lastres; j++)
1134             {
1135               if (!jalview.util.Comparison.isGap(groupSeqs[g]
1136                       .getCharAt(fixedRight - j)))
1137               {
1138                 blank = false;
1139                 break;
1140               }
1141             }
1142           }
1143           if (blank)
1144           {
1145             break;
1146           }
1147         }
1148
1149         if (!blank)
1150         {
1151           if (sg.getSize() == av.alignment.getHeight())
1152           {
1153             if ((av.hasHiddenColumns && startres < av.getColumnSelection()
1154                     .getHiddenBoundaryRight(startres)))
1155             {
1156               endEditing();
1157               return;
1158             }
1159
1160             int alWidth = av.alignment.getWidth();
1161             if (av.hasHiddenRows)
1162             {
1163               int hwidth = av.alignment.getHiddenSequences().getWidth();
1164               if (hwidth > alWidth)
1165               {
1166                 alWidth = hwidth;
1167               }
1168             }
1169             // We can still insert gaps if the selectionGroup
1170             // contains all the sequences
1171             sg.setEndRes(sg.getEndRes() + startres - lastres);
1172             fixedRight = alWidth + startres - lastres;
1173           }
1174           else
1175           {
1176             endEditing();
1177             return;
1178           }
1179         }
1180       }
1181
1182       // drag to left
1183       else if (!insertGap)
1184       {
1185         // / Are we able to delete?
1186         // ie are all columns blank?
1187
1188         for (g = 0; g < groupSize; g++)
1189         {
1190           for (int j = startres; j < lastres; j++)
1191           {
1192             if (groupSeqs[g].getLength() <= j)
1193             {
1194               continue;
1195             }
1196
1197             if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
1198             {
1199               // Not a gap, block edit not valid
1200               endEditing();
1201               return;
1202             }
1203           }
1204         }
1205       }
1206
1207       if (insertGap)
1208       {
1209         // dragging to the right
1210         if (fixedColumns && fixedRight != -1)
1211         {
1212           for (int j = lastres; j < startres; j++)
1213           {
1214             insertChar(j, groupSeqs, fixedRight);
1215           }
1216         }
1217         else
1218         {
1219           editCommand.appendEdit(EditCommand.INSERT_GAP, groupSeqs,
1220                   startres, startres - lastres, av.alignment, true);
1221         }
1222       }
1223       else
1224       {
1225         // dragging to the left
1226         if (fixedColumns && fixedRight != -1)
1227         {
1228           for (int j = lastres; j > startres; j--)
1229           {
1230             deleteChar(startres, groupSeqs, fixedRight);
1231           }
1232         }
1233         else
1234         {
1235           editCommand.appendEdit(EditCommand.DELETE_GAP, groupSeqs,
1236                   startres, lastres - startres, av.alignment, true);
1237         }
1238
1239       }
1240     }
1241     else
1242     // ///Editing a single sequence///////////
1243     {
1244       if (insertGap)
1245       {
1246         // dragging to the right
1247         if (fixedColumns && fixedRight != -1)
1248         {
1249           for (int j = lastres; j < startres; j++)
1250           {
1251             insertChar(j, new SequenceI[]
1252             { seq }, fixedRight);
1253           }
1254         }
1255         else
1256         {
1257           editCommand.appendEdit(EditCommand.INSERT_GAP, new SequenceI[]
1258           { seq }, lastres, startres - lastres, av.alignment, true);
1259         }
1260       }
1261       else
1262       {
1263         // dragging to the left
1264         if (fixedColumns && fixedRight != -1)
1265         {
1266           for (int j = lastres; j > startres; j--)
1267           {
1268             if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1269             {
1270               endEditing();
1271               break;
1272             }
1273             deleteChar(startres, new SequenceI[]
1274             { seq }, fixedRight);
1275           }
1276         }
1277         else
1278         {
1279           // could be a keyboard edit trying to delete none gaps
1280           int max = 0;
1281           for (int m = startres; m < lastres; m++)
1282           {
1283             if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1284             {
1285               break;
1286             }
1287             max++;
1288           }
1289
1290           if (max > 0)
1291           {
1292             editCommand.appendEdit(EditCommand.DELETE_GAP, new SequenceI[]
1293             { seq }, startres, max, av.alignment, true);
1294           }
1295         }
1296       }
1297     }
1298
1299     lastres = startres;
1300     seqCanvas.repaint();
1301   }
1302
1303   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1304   {
1305     int blankColumn = fixedColumn;
1306     for (int s = 0; s < seq.length; s++)
1307     {
1308       // Find the next gap before the end of the visible region boundary
1309       // If lastCol > j, theres a boundary after the gap insertion
1310
1311       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1312       {
1313         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1314         {
1315           // Theres a space, so break and insert the gap
1316           break;
1317         }
1318       }
1319
1320       if (blankColumn <= j)
1321       {
1322         blankColumn = fixedColumn;
1323         endEditing();
1324         return;
1325       }
1326     }
1327
1328     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, blankColumn, 1,
1329             av.alignment, true);
1330
1331     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, j, 1, av.alignment,
1332             true);
1333
1334   }
1335
1336   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1337   {
1338
1339     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, j, 1, av.alignment,
1340             true);
1341
1342     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, fixedColumn, 1,
1343             av.alignment, true);
1344   }
1345
1346   /**
1347    * DOCUMENT ME!
1348    * 
1349    * @param e
1350    *                DOCUMENT ME!
1351    */
1352   public void mouseEntered(MouseEvent e)
1353   {
1354     if (oldSeq < 0)
1355     {
1356       oldSeq = 0;
1357     }
1358
1359     if (scrollThread != null)
1360     {
1361       scrollThread.running = false;
1362       scrollThread = null;
1363     }
1364   }
1365
1366   /**
1367    * DOCUMENT ME!
1368    * 
1369    * @param e
1370    *                DOCUMENT ME!
1371    */
1372   public void mouseExited(MouseEvent e)
1373   {
1374     if (av.getWrapAlignment())
1375     {
1376       return;
1377     }
1378
1379     if (mouseDragging)
1380     {
1381       scrollThread = new ScrollThread();
1382     }
1383   }
1384
1385   public void mouseClicked(MouseEvent evt)
1386   {
1387     SequenceI sequence = av.alignment.getSequenceAt(findSeq(evt));
1388     if (evt.getClickCount() > 1)
1389     {
1390       if (av.getSelectionGroup().getSize() == 1
1391               && av.getSelectionGroup().getEndRes()
1392                       - av.getSelectionGroup().getStartRes() < 2)
1393       {
1394         av.setSelectionGroup(null);
1395       }
1396
1397       SequenceFeature[] features = findFeaturesAtRes(sequence
1398               .getDatasetSequence(), sequence.findPosition(findRes(evt)));
1399
1400       if (features != null && features.length > 0)
1401       {
1402         SearchResults highlight = new SearchResults();
1403         highlight.addResult(sequence, features[0].getBegin(), features[0]
1404                 .getEnd());
1405         seqCanvas.highlightSearchResults(highlight);
1406       }
1407       if (features != null && features.length > 0)
1408       {
1409         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
1410         { sequence }, features, false, ap);
1411
1412         seqCanvas.highlightSearchResults(null);
1413       }
1414     }
1415   }
1416
1417   public void mouseWheelMoved(MouseWheelEvent e)
1418   {
1419     e.consume();
1420     if (e.getWheelRotation() > 0)
1421     {
1422       ap.scrollUp(false);
1423     }
1424     else
1425     {
1426       ap.scrollUp(true);
1427     }
1428   }
1429
1430   /**
1431    * DOCUMENT ME!
1432    * 
1433    * @param evt
1434    *                DOCUMENT ME!
1435    */
1436   public void doMousePressedDefineMode(MouseEvent evt)
1437   {
1438     int res = findRes(evt);
1439     int seq = findSeq(evt);
1440     oldSeq = seq;
1441
1442     startWrapBlock = wrappedBlock;
1443
1444     if (av.wrapAlignment && seq > av.alignment.getHeight())
1445     {
1446       JOptionPane.showInternalMessageDialog(Desktop.desktop,
1447               "Cannot edit annotations in wrapped view.",
1448               "Wrapped view - no edit", JOptionPane.WARNING_MESSAGE);
1449       return;
1450     }
1451
1452     if (seq < 0 || res < 0)
1453     {
1454       return;
1455     }
1456
1457     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);
1458
1459     if ((sequence == null) || (res > sequence.getLength()))
1460     {
1461       return;
1462     }
1463
1464     stretchGroup = av.getSelectionGroup();
1465
1466     if (stretchGroup == null)
1467     {
1468       stretchGroup = av.alignment.findGroup(sequence);
1469
1470       if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
1471               && (res < stretchGroup.getEndRes()))
1472       {
1473         av.setSelectionGroup(stretchGroup);
1474       }
1475       else
1476       {
1477         stretchGroup = null;
1478       }
1479     }
1480     else if (!stretchGroup.getSequences(null).contains(sequence)
1481             || (stretchGroup.getStartRes() > res)
1482             || (stretchGroup.getEndRes() < res))
1483     {
1484       stretchGroup = null;
1485
1486       SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence);
1487
1488       if (allGroups != null)
1489       {
1490         for (int i = 0; i < allGroups.length; i++)
1491         {
1492           if ((allGroups[i].getStartRes() <= res)
1493                   && (allGroups[i].getEndRes() >= res))
1494           {
1495             stretchGroup = allGroups[i];
1496             break;
1497           }
1498         }
1499       }
1500
1501       av.setSelectionGroup(stretchGroup);
1502
1503     }
1504
1505     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
1506     {
1507       SequenceFeature[] allFeatures = findFeaturesAtRes(sequence
1508               .getDatasetSequence(), sequence.findPosition(res));
1509       Vector links = new Vector();
1510       for (int i = 0; i < allFeatures.length; i++)
1511       {
1512         if (allFeatures[i].links != null)
1513         {
1514           for (int j = 0; j < allFeatures[i].links.size(); j++)
1515           {
1516             links.addElement(allFeatures[i].links.elementAt(j));
1517           }
1518         }
1519       }
1520
1521       jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
1522       pop.show(this, evt.getX(), evt.getY());
1523       return;
1524     }
1525
1526     if (av.cursorMode)
1527     {
1528       seqCanvas.cursorX = findRes(evt);
1529       seqCanvas.cursorY = findSeq(evt);
1530       seqCanvas.repaint();
1531       return;
1532     }
1533
1534     if (stretchGroup == null)
1535     {
1536       // Only if left mouse button do we want to change group sizes
1537
1538       // define a new group here
1539       SequenceGroup sg = new SequenceGroup();
1540       sg.setStartRes(res);
1541       sg.setEndRes(res);
1542       sg.addSequence(sequence, false);
1543       av.setSelectionGroup(sg);
1544       
1545       stretchGroup = sg;
1546
1547       if (av.getConservationSelected())
1548       {
1549         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1550                 "Background");
1551       }
1552
1553       if (av.getAbovePIDThreshold())
1554       {
1555         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1556                 "Background");
1557       }
1558       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1559       {
1560         // Edit end res position of selected group
1561         changeEndRes = true;
1562       }
1563       else if ((stretchGroup != null)
1564               && (stretchGroup.getStartRes() == res))
1565       {
1566         // Edit end res position of selected group
1567         changeStartRes = true;
1568       }
1569       stretchGroup.getWidth();
1570     }
1571
1572     seqCanvas.repaint();
1573   }
1574
1575   /**
1576    * DOCUMENT ME!
1577    * 
1578    * @param evt
1579    *                DOCUMENT ME!
1580    */
1581   public void doMouseReleasedDefineMode(MouseEvent evt)
1582   {
1583     if (stretchGroup == null)
1584     {
1585       return;
1586     }
1587
1588     if (stretchGroup.cs != null)
1589     {
1590       if (stretchGroup.cs instanceof ClustalxColourScheme)
1591       {
1592         ((ClustalxColourScheme) stretchGroup.cs).resetClustalX(stretchGroup
1593                 .getSequences(av.hiddenRepSequences), stretchGroup
1594                 .getWidth());
1595       }
1596
1597       if (stretchGroup.cs instanceof Blosum62ColourScheme
1598               || stretchGroup.cs instanceof PIDColourScheme
1599               || stretchGroup.cs.conservationApplied()
1600               || stretchGroup.cs.getThreshold() > 0)
1601       {
1602         stretchGroup.recalcConservation();
1603       }
1604
1605       if (stretchGroup.cs.conservationApplied())
1606       {
1607         SliderPanel.setConservationSlider(ap, stretchGroup.cs, stretchGroup
1608                 .getName());
1609       }
1610       else
1611       {
1612         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs, stretchGroup
1613                 .getName());
1614       }
1615       PaintRefresher.Refresh(this, av.getSequenceSetId());
1616       ap.paintAlignment(true);
1617     }
1618
1619     changeEndRes = false;
1620     changeStartRes = false;
1621     stretchGroup = null;
1622     av.sendSelection();
1623   }
1624
1625   /**
1626    * DOCUMENT ME!
1627    * 
1628    * @param evt
1629    *                DOCUMENT ME!
1630    */
1631   public void doMouseDraggedDefineMode(MouseEvent evt)
1632   {
1633     int res = findRes(evt);
1634     int y = findSeq(evt);
1635
1636     if (wrappedBlock != startWrapBlock)
1637     {
1638       return;
1639     }
1640
1641     if (stretchGroup == null)
1642     {
1643       return;
1644     }
1645
1646     if (res >= av.alignment.getWidth())
1647     {
1648       res = av.alignment.getWidth() - 1;
1649     }
1650
1651     if (stretchGroup.getEndRes() == res)
1652     {
1653       // Edit end res position of selected group
1654       changeEndRes = true;
1655     }
1656     else if (stretchGroup.getStartRes() == res)
1657     {
1658       // Edit start res position of selected group
1659       changeStartRes = true;
1660     }
1661
1662     if (res < av.getStartRes())
1663     {
1664       res = av.getStartRes();
1665     }
1666
1667     if (changeEndRes)
1668     {
1669       if (res > (stretchGroup.getStartRes() - 1))
1670       {
1671         stretchGroup.setEndRes(res);
1672       }
1673     }
1674     else if (changeStartRes)
1675     {
1676       if (res < (stretchGroup.getEndRes() + 1))
1677       {
1678         stretchGroup.setStartRes(res);
1679       }
1680     }
1681
1682     int dragDirection = 0;
1683
1684     if (y > oldSeq)
1685     {
1686       dragDirection = 1;
1687     }
1688     else if (y < oldSeq)
1689     {
1690       dragDirection = -1;
1691     }
1692
1693     while ((y != oldSeq) && (oldSeq > -1) && (y < av.alignment.getHeight()))
1694     {
1695       // This routine ensures we don't skip any sequences, as the
1696       // selection is quite slow.
1697       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1698
1699       oldSeq += dragDirection;
1700
1701       if (oldSeq < 0)
1702       {
1703         break;
1704       }
1705
1706       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1707
1708       if (stretchGroup.getSequences(null).contains(nextSeq))
1709       {
1710         stretchGroup.deleteSequence(seq, false);
1711       }
1712       else
1713       {
1714         if (seq != null)
1715         {
1716           stretchGroup.addSequence(seq, false);
1717         }
1718
1719         stretchGroup.addSequence(nextSeq, false);
1720       }
1721     }
1722
1723     if (oldSeq < 0)
1724     {
1725       oldSeq = -1;
1726     }
1727
1728     mouseDragging = true;
1729
1730     if (scrollThread != null)
1731     {
1732       scrollThread.setEvent(evt);
1733     }
1734
1735     seqCanvas.repaint();
1736   }
1737
1738   void scrollCanvas(MouseEvent evt)
1739   {
1740     if (evt == null)
1741     {
1742       if (scrollThread != null)
1743       {
1744         scrollThread.running = false;
1745         scrollThread = null;
1746       }
1747       mouseDragging = false;
1748     }
1749     else
1750     {
1751       if (scrollThread == null)
1752       {
1753         scrollThread = new ScrollThread();
1754       }
1755
1756       mouseDragging = true;
1757       scrollThread.setEvent(evt);
1758     }
1759
1760   }
1761
1762   // this class allows scrolling off the bottom of the visible alignment
1763   class ScrollThread extends Thread
1764   {
1765     MouseEvent evt;
1766
1767     boolean running = false;
1768
1769     public ScrollThread()
1770     {
1771       start();
1772     }
1773
1774     public void setEvent(MouseEvent e)
1775     {
1776       evt = e;
1777     }
1778
1779     public void stopScrolling()
1780     {
1781       running = false;
1782     }
1783
1784     public void run()
1785     {
1786       running = true;
1787
1788       while (running)
1789       {
1790         if (evt != null)
1791         {
1792           if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
1793           {
1794             running = ap.scrollUp(true);
1795           }
1796
1797           if (mouseDragging && (evt.getY() >= getHeight())
1798                   && (av.alignment.getHeight() > av.getEndSeq()))
1799           {
1800             running = ap.scrollUp(false);
1801           }
1802
1803           if (mouseDragging && (evt.getX() < 0))
1804           {
1805             running = ap.scrollRight(false);
1806           }
1807           else if (mouseDragging && (evt.getX() >= getWidth()))
1808           {
1809             running = ap.scrollRight(true);
1810           }
1811         }
1812
1813         try
1814         {
1815           Thread.sleep(20);
1816         } catch (Exception ex)
1817         {
1818         }
1819       }
1820     }
1821   }
1822   /**
1823    * modify current selection according to a received message.
1824    */
1825   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1826           SelectionSource source)
1827   {
1828     // TODO: fix this hack - source of messages is align viewport, but SeqPanel handles selection messages...
1829     if (av==source)
1830     {
1831       return;
1832     }
1833     // do we want to thread this ? (contention with seqsel and colsel locks, I suspect)
1834     boolean repaint=false;
1835     if (av.followSelection)
1836     {
1837       if (av.selectionGroup==null || !av.isSelectionGroupChanged())
1838       {
1839         SequenceGroup sgroup = (seqsel!=null) ? seqsel.intersect(av.alignment, (av.hasHiddenRows) ? av.hiddenRepSequences : null)
1840                   : null;
1841         if (sgroup!=null && sgroup.getSize()>0)
1842         {
1843           av.setSelectionGroup(sgroup);
1844         } else {
1845           av.setSelectionGroup(null);
1846         }
1847         repaint = av.isSelectionGroupChanged();
1848       }
1849       if (av.colSel==null || !av.isColSelChanged())
1850       {
1851         // Check to see if the current selection is from a previous message
1852         if (colsel==null || colsel.size()==0)
1853         {
1854           av.colSel.clear();
1855         } else {
1856           av.colSel = new ColumnSelection(colsel);
1857         } 
1858         repaint |= av.isColSelChanged();
1859       }
1860     }
1861     if (repaint)
1862     {
1863       // probably finessing with multiple redraws here
1864       PaintRefresher.Refresh(this, av.getSequenceSetId());
1865       // ap.paintAlignment(false);
1866     }
1867   }
1868 }