939ba45476b08ff5bd96ad53f0d456e908949494
[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].getValue("status") != null)
805           {
806             String status = features[i].getValue("status").toString();
807             if (status.length() > 0)
808             {
809               tooltipText2.append("; (" + features[i].getValue("status")
810                       + ")");
811             }
812           }
813
814           if (features[i].links != null)
815           {
816             tooltipText2.append(" <img src=\"" + linkImageURL + "\">");
817           }
818
819         }
820       }
821     }
822   }
823
824   String lastTooltip;
825
826   /**
827    * Set status message in alignment panel
828    * 
829    * @param sequence
830    *                aligned sequence object
831    * @param res
832    *                alignment column
833    * @param seq
834    *                index of sequence in alignment
835    * @return position of res in sequence
836    */
837   int setStatusMessage(SequenceI sequence, int res, int seq)
838   {
839     int pos = -1;
840     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
841             + sequence.getName());
842
843     Object obj = null;
844     if (av.alignment.isNucleotide())
845     {
846       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
847               + "");
848       if (obj != null)
849       {
850         text.append(" Nucleotide: ");
851       }
852     }
853     else
854     {
855       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
856       if (obj != null)
857       {
858         text.append("  Residue: ");
859       }
860     }
861
862     if (obj != null)
863     {
864       pos = sequence.findPosition(res);
865       if (obj != "")
866       {
867         text.append(obj + " (" + pos + ")");
868       }
869     }
870     ap.alignFrame.statusBar.setText(text.toString());
871     return pos;
872   }
873
874   /**
875    * DOCUMENT ME!
876    * 
877    * @param evt
878    *                DOCUMENT ME!
879    */
880   public void mouseDragged(MouseEvent evt)
881   {
882     if (mouseWheelPressed)
883     {
884       int oldWidth = av.charWidth;
885
886       // Which is bigger, left-right or up-down?
887       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
888               .getX()
889               - lastMousePress.getX()))
890       {
891         int fontSize = av.font.getSize();
892
893         if (evt.getY() < lastMousePress.getY())
894         {
895           fontSize--;
896         }
897         else if (evt.getY() > lastMousePress.getY())
898         {
899           fontSize++;
900         }
901
902         if (fontSize < 1)
903         {
904           fontSize = 1;
905         }
906
907         av
908                 .setFont(new Font(av.font.getName(), av.font.getStyle(),
909                         fontSize));
910         av.charWidth = oldWidth;
911         ap.fontChanged();
912       }
913       else
914       {
915         if (evt.getX() < lastMousePress.getX() && av.charWidth > 1)
916         {
917           av.charWidth--;
918         }
919         else if (evt.getX() > lastMousePress.getX())
920         {
921           av.charWidth++;
922         }
923
924         ap.paintAlignment(false);
925       }
926
927       FontMetrics fm = getFontMetrics(av.getFont());
928       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
929
930       lastMousePress = evt.getPoint();
931
932       return;
933     }
934
935     if (!editingSeqs)
936     {
937       doMouseDraggedDefineMode(evt);
938       return;
939     }
940
941     int res = findRes(evt);
942
943     if (res < 0)
944     {
945       res = 0;
946     }
947
948     if ((lastres == -1) || (lastres == res))
949     {
950       return;
951     }
952
953     if ((res < av.getAlignment().getWidth()) && (res < lastres))
954     {
955       // dragLeft, delete gap
956       editSequence(false, res);
957     }
958     else
959     {
960       editSequence(true, res);
961     }
962
963     mouseDragging = true;
964     if (scrollThread != null)
965     {
966       scrollThread.setEvent(evt);
967     }
968   }
969
970   synchronized void editSequence(boolean insertGap, int startres)
971   {
972     int fixedLeft = -1;
973     int fixedRight = -1;
974     boolean fixedColumns = false;
975     SequenceGroup sg = av.getSelectionGroup();
976
977     SequenceI seq = av.alignment.getSequenceAt(startseq);
978
979     // No group, but the sequence may represent a group
980     if (!groupEditing && av.hasHiddenRows)
981     {
982       if (av.hiddenRepSequences != null
983               && av.hiddenRepSequences.containsKey(seq))
984       {
985         sg = (SequenceGroup) av.hiddenRepSequences.get(seq);
986         groupEditing = true;
987       }
988     }
989
990     StringBuffer message = new StringBuffer();
991     if (groupEditing)
992     {
993       message.append("Edit group:");
994       if (editCommand == null)
995       {
996         editCommand = new EditCommand("Edit Group");
997       }
998     }
999     else
1000     {
1001       message.append("Edit sequence: " + seq.getName());
1002       String label = seq.getName();
1003       if (label.length() > 10)
1004       {
1005         label = label.substring(0, 10);
1006       }
1007       if (editCommand == null)
1008       {
1009         editCommand = new EditCommand("Edit " + label);
1010       }
1011     }
1012
1013     if (insertGap)
1014     {
1015       message.append(" insert ");
1016     }
1017     else
1018     {
1019       message.append(" delete ");
1020     }
1021
1022     message.append(Math.abs(startres - lastres) + " gaps.");
1023     ap.alignFrame.statusBar.setText(message.toString());
1024
1025     // Are we editing within a selection group?
1026     if (groupEditing
1027             || (sg != null && sg.getSequences(av.hiddenRepSequences)
1028                     .contains(seq)))
1029     {
1030       fixedColumns = true;
1031
1032       // sg might be null as the user may only see 1 sequence,
1033       // but the sequence represents a group
1034       if (sg == null)
1035       {
1036         if (av.hiddenRepSequences == null
1037                 || !av.hiddenRepSequences.containsKey(seq))
1038         {
1039           endEditing();
1040           return;
1041         }
1042         sg = (SequenceGroup) av.hiddenRepSequences.get(seq);
1043       }
1044
1045       fixedLeft = sg.getStartRes();
1046       fixedRight = sg.getEndRes();
1047
1048       if ((startres < fixedLeft && lastres >= fixedLeft)
1049               || (startres >= fixedLeft && lastres < fixedLeft)
1050               || (startres > fixedRight && lastres <= fixedRight)
1051               || (startres <= fixedRight && lastres > fixedRight))
1052       {
1053         endEditing();
1054         return;
1055       }
1056
1057       if (fixedLeft > startres)
1058       {
1059         fixedRight = fixedLeft - 1;
1060         fixedLeft = 0;
1061       }
1062       else if (fixedRight < startres)
1063       {
1064         fixedLeft = fixedRight;
1065         fixedRight = -1;
1066       }
1067     }
1068
1069     if (av.hasHiddenColumns)
1070     {
1071       fixedColumns = true;
1072       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1073       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1074
1075       if ((insertGap && startres > y1 && lastres < y1)
1076               || (!insertGap && startres < y2 && lastres > y2))
1077       {
1078         endEditing();
1079         return;
1080       }
1081
1082       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1083       // Selection spans a hidden region
1084       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1085       {
1086         if (startres >= y2)
1087         {
1088           fixedLeft = y2;
1089         }
1090         else
1091         {
1092           fixedRight = y2 - 1;
1093         }
1094       }
1095     }
1096
1097     if (groupEditing)
1098     {
1099       Vector vseqs = sg.getSequences(av.hiddenRepSequences);
1100       int g, groupSize = vseqs.size();
1101       SequenceI[] groupSeqs = new SequenceI[groupSize];
1102       for (g = 0; g < groupSeqs.length; g++)
1103       {
1104         groupSeqs[g] = (SequenceI) vseqs.elementAt(g);
1105       }
1106
1107       // drag to right
1108       if (insertGap)
1109       {
1110         // If the user has selected the whole sequence, and is dragging to
1111         // the right, we can still extend the alignment and selectionGroup
1112         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1113                 && sg.getEndRes() == av.alignment.getWidth() - 1)
1114         {
1115           sg.setEndRes(av.alignment.getWidth() + startres - lastres);
1116           fixedRight = sg.getEndRes();
1117         }
1118
1119         // Is it valid with fixed columns??
1120         // Find the next gap before the end
1121         // of the visible region boundary
1122         boolean blank = false;
1123         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1124         {
1125           blank = true;
1126
1127           for (g = 0; g < groupSize; g++)
1128           {
1129             for (int j = 0; j < startres - lastres; j++)
1130             {
1131               if (!jalview.util.Comparison.isGap(groupSeqs[g]
1132                       .getCharAt(fixedRight - j)))
1133               {
1134                 blank = false;
1135                 break;
1136               }
1137             }
1138           }
1139           if (blank)
1140           {
1141             break;
1142           }
1143         }
1144
1145         if (!blank)
1146         {
1147           if (sg.getSize() == av.alignment.getHeight())
1148           {
1149             if ((av.hasHiddenColumns && startres < av.getColumnSelection()
1150                     .getHiddenBoundaryRight(startres)))
1151             {
1152               endEditing();
1153               return;
1154             }
1155
1156             int alWidth = av.alignment.getWidth();
1157             if (av.hasHiddenRows)
1158             {
1159               int hwidth = av.alignment.getHiddenSequences().getWidth();
1160               if (hwidth > alWidth)
1161               {
1162                 alWidth = hwidth;
1163               }
1164             }
1165             // We can still insert gaps if the selectionGroup
1166             // contains all the sequences
1167             sg.setEndRes(sg.getEndRes() + startres - lastres);
1168             fixedRight = alWidth + startres - lastres;
1169           }
1170           else
1171           {
1172             endEditing();
1173             return;
1174           }
1175         }
1176       }
1177
1178       // drag to left
1179       else if (!insertGap)
1180       {
1181         // / Are we able to delete?
1182         // ie are all columns blank?
1183
1184         for (g = 0; g < groupSize; g++)
1185         {
1186           for (int j = startres; j < lastres; j++)
1187           {
1188             if (groupSeqs[g].getLength() <= j)
1189             {
1190               continue;
1191             }
1192
1193             if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
1194             {
1195               // Not a gap, block edit not valid
1196               endEditing();
1197               return;
1198             }
1199           }
1200         }
1201       }
1202
1203       if (insertGap)
1204       {
1205         // dragging to the right
1206         if (fixedColumns && fixedRight != -1)
1207         {
1208           for (int j = lastres; j < startres; j++)
1209           {
1210             insertChar(j, groupSeqs, fixedRight);
1211           }
1212         }
1213         else
1214         {
1215           editCommand.appendEdit(EditCommand.INSERT_GAP, groupSeqs,
1216                   startres, startres - lastres, av.alignment, true);
1217         }
1218       }
1219       else
1220       {
1221         // dragging to the left
1222         if (fixedColumns && fixedRight != -1)
1223         {
1224           for (int j = lastres; j > startres; j--)
1225           {
1226             deleteChar(startres, groupSeqs, fixedRight);
1227           }
1228         }
1229         else
1230         {
1231           editCommand.appendEdit(EditCommand.DELETE_GAP, groupSeqs,
1232                   startres, lastres - startres, av.alignment, true);
1233         }
1234
1235       }
1236     }
1237     else
1238     // ///Editing a single sequence///////////
1239     {
1240       if (insertGap)
1241       {
1242         // dragging to the right
1243         if (fixedColumns && fixedRight != -1)
1244         {
1245           for (int j = lastres; j < startres; j++)
1246           {
1247             insertChar(j, new SequenceI[]
1248             { seq }, fixedRight);
1249           }
1250         }
1251         else
1252         {
1253           editCommand.appendEdit(EditCommand.INSERT_GAP, new SequenceI[]
1254           { seq }, lastres, startres - lastres, av.alignment, true);
1255         }
1256       }
1257       else
1258       {
1259         // dragging to the left
1260         if (fixedColumns && fixedRight != -1)
1261         {
1262           for (int j = lastres; j > startres; j--)
1263           {
1264             if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1265             {
1266               endEditing();
1267               break;
1268             }
1269             deleteChar(startres, new SequenceI[]
1270             { seq }, fixedRight);
1271           }
1272         }
1273         else
1274         {
1275           // could be a keyboard edit trying to delete none gaps
1276           int max = 0;
1277           for (int m = startres; m < lastres; m++)
1278           {
1279             if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1280             {
1281               break;
1282             }
1283             max++;
1284           }
1285
1286           if (max > 0)
1287           {
1288             editCommand.appendEdit(EditCommand.DELETE_GAP, new SequenceI[]
1289             { seq }, startres, max, av.alignment, true);
1290           }
1291         }
1292       }
1293     }
1294
1295     lastres = startres;
1296     seqCanvas.repaint();
1297   }
1298
1299   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1300   {
1301     int blankColumn = fixedColumn;
1302     for (int s = 0; s < seq.length; s++)
1303     {
1304       // Find the next gap before the end of the visible region boundary
1305       // If lastCol > j, theres a boundary after the gap insertion
1306
1307       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1308       {
1309         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1310         {
1311           // Theres a space, so break and insert the gap
1312           break;
1313         }
1314       }
1315
1316       if (blankColumn <= j)
1317       {
1318         blankColumn = fixedColumn;
1319         endEditing();
1320         return;
1321       }
1322     }
1323
1324     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, blankColumn, 1,
1325             av.alignment, true);
1326
1327     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, j, 1, av.alignment,
1328             true);
1329
1330   }
1331
1332   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1333   {
1334
1335     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, j, 1, av.alignment,
1336             true);
1337
1338     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, fixedColumn, 1,
1339             av.alignment, true);
1340   }
1341
1342   /**
1343    * DOCUMENT ME!
1344    * 
1345    * @param e
1346    *                DOCUMENT ME!
1347    */
1348   public void mouseEntered(MouseEvent e)
1349   {
1350     if (oldSeq < 0)
1351     {
1352       oldSeq = 0;
1353     }
1354
1355     if (scrollThread != null)
1356     {
1357       scrollThread.running = false;
1358       scrollThread = null;
1359     }
1360   }
1361
1362   /**
1363    * DOCUMENT ME!
1364    * 
1365    * @param e
1366    *                DOCUMENT ME!
1367    */
1368   public void mouseExited(MouseEvent e)
1369   {
1370     if (av.getWrapAlignment())
1371     {
1372       return;
1373     }
1374
1375     if (mouseDragging)
1376     {
1377       scrollThread = new ScrollThread();
1378     }
1379   }
1380
1381   public void mouseClicked(MouseEvent evt)
1382   {
1383     SequenceI sequence = av.alignment.getSequenceAt(findSeq(evt));
1384     if (evt.getClickCount() > 1)
1385     {
1386       if (av.getSelectionGroup().getSize() == 1
1387               && av.getSelectionGroup().getEndRes()
1388                       - av.getSelectionGroup().getStartRes() < 2)
1389       {
1390         av.setSelectionGroup(null);
1391       }
1392
1393       SequenceFeature[] features = findFeaturesAtRes(sequence
1394               .getDatasetSequence(), sequence.findPosition(findRes(evt)));
1395
1396       if (features != null && features.length > 0)
1397       {
1398         SearchResults highlight = new SearchResults();
1399         highlight.addResult(sequence, features[0].getBegin(), features[0]
1400                 .getEnd());
1401         seqCanvas.highlightSearchResults(highlight);
1402       }
1403       if (features != null && features.length > 0)
1404       {
1405         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
1406         { sequence }, features, false, ap);
1407
1408         seqCanvas.highlightSearchResults(null);
1409       }
1410     }
1411   }
1412
1413   public void mouseWheelMoved(MouseWheelEvent e)
1414   {
1415     e.consume();
1416     if (e.getWheelRotation() > 0)
1417     {
1418       ap.scrollUp(false);
1419     }
1420     else
1421     {
1422       ap.scrollUp(true);
1423     }
1424   }
1425
1426   /**
1427    * DOCUMENT ME!
1428    * 
1429    * @param evt
1430    *                DOCUMENT ME!
1431    */
1432   public void doMousePressedDefineMode(MouseEvent evt)
1433   {
1434     int res = findRes(evt);
1435     int seq = findSeq(evt);
1436     oldSeq = seq;
1437
1438     startWrapBlock = wrappedBlock;
1439
1440     if (av.wrapAlignment && seq > av.alignment.getHeight())
1441     {
1442       JOptionPane.showInternalMessageDialog(Desktop.desktop,
1443               "Cannot edit annotations in wrapped view.",
1444               "Wrapped view - no edit", JOptionPane.WARNING_MESSAGE);
1445       return;
1446     }
1447
1448     if (seq < 0 || res < 0)
1449     {
1450       return;
1451     }
1452
1453     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);
1454
1455     if ((sequence == null) || (res > sequence.getLength()))
1456     {
1457       return;
1458     }
1459
1460     stretchGroup = av.getSelectionGroup();
1461
1462     if (stretchGroup == null)
1463     {
1464       stretchGroup = av.alignment.findGroup(sequence);
1465
1466       if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
1467               && (res < stretchGroup.getEndRes()))
1468       {
1469         av.setSelectionGroup(stretchGroup);
1470       }
1471       else
1472       {
1473         stretchGroup = null;
1474       }
1475     }
1476     else if (!stretchGroup.getSequences(null).contains(sequence)
1477             || (stretchGroup.getStartRes() > res)
1478             || (stretchGroup.getEndRes() < res))
1479     {
1480       stretchGroup = null;
1481
1482       SequenceGroup[] allGroups = av.alignment.findAllGroups(sequence);
1483
1484       if (allGroups != null)
1485       {
1486         for (int i = 0; i < allGroups.length; i++)
1487         {
1488           if ((allGroups[i].getStartRes() <= res)
1489                   && (allGroups[i].getEndRes() >= res))
1490           {
1491             stretchGroup = allGroups[i];
1492             break;
1493           }
1494         }
1495       }
1496
1497       av.setSelectionGroup(stretchGroup);
1498
1499     }
1500
1501     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
1502     {
1503       SequenceFeature[] allFeatures = findFeaturesAtRes(sequence
1504               .getDatasetSequence(), sequence.findPosition(res));
1505       Vector links = new Vector();
1506       for (int i = 0; i < allFeatures.length; i++)
1507       {
1508         if (allFeatures[i].links != null)
1509         {
1510           for (int j = 0; j < allFeatures[i].links.size(); j++)
1511           {
1512             links.addElement(allFeatures[i].links.elementAt(j));
1513           }
1514         }
1515       }
1516
1517       jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
1518       pop.show(this, evt.getX(), evt.getY());
1519       return;
1520     }
1521
1522     if (av.cursorMode)
1523     {
1524       seqCanvas.cursorX = findRes(evt);
1525       seqCanvas.cursorY = findSeq(evt);
1526       seqCanvas.repaint();
1527       return;
1528     }
1529
1530     if (stretchGroup == null)
1531     {
1532       // Only if left mouse button do we want to change group sizes
1533
1534       // define a new group here
1535       SequenceGroup sg = new SequenceGroup();
1536       sg.setStartRes(res);
1537       sg.setEndRes(res);
1538       sg.addSequence(sequence, false);
1539       av.setSelectionGroup(sg);
1540       
1541       stretchGroup = sg;
1542
1543       if (av.getConservationSelected())
1544       {
1545         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1546                 "Background");
1547       }
1548
1549       if (av.getAbovePIDThreshold())
1550       {
1551         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1552                 "Background");
1553       }
1554       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1555       {
1556         // Edit end res position of selected group
1557         changeEndRes = true;
1558       }
1559       else if ((stretchGroup != null)
1560               && (stretchGroup.getStartRes() == res))
1561       {
1562         // Edit end res position of selected group
1563         changeStartRes = true;
1564       }
1565       stretchGroup.getWidth();
1566     }
1567
1568     seqCanvas.repaint();
1569   }
1570
1571   /**
1572    * DOCUMENT ME!
1573    * 
1574    * @param evt
1575    *                DOCUMENT ME!
1576    */
1577   public void doMouseReleasedDefineMode(MouseEvent evt)
1578   {
1579     if (stretchGroup == null)
1580     {
1581       return;
1582     }
1583
1584     if (stretchGroup.cs != null)
1585     {
1586       if (stretchGroup.cs instanceof ClustalxColourScheme)
1587       {
1588         ((ClustalxColourScheme) stretchGroup.cs).resetClustalX(stretchGroup
1589                 .getSequences(av.hiddenRepSequences), stretchGroup
1590                 .getWidth());
1591       }
1592
1593       if (stretchGroup.cs instanceof Blosum62ColourScheme
1594               || stretchGroup.cs instanceof PIDColourScheme
1595               || stretchGroup.cs.conservationApplied()
1596               || stretchGroup.cs.getThreshold() > 0)
1597       {
1598         stretchGroup.recalcConservation();
1599       }
1600
1601       if (stretchGroup.cs.conservationApplied())
1602       {
1603         SliderPanel.setConservationSlider(ap, stretchGroup.cs, stretchGroup
1604                 .getName());
1605       }
1606       else
1607       {
1608         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs, stretchGroup
1609                 .getName());
1610       }
1611       PaintRefresher.Refresh(this, av.getSequenceSetId());
1612       ap.paintAlignment(true);
1613     }
1614
1615     changeEndRes = false;
1616     changeStartRes = false;
1617     stretchGroup = null;
1618     av.sendSelection();
1619   }
1620
1621   /**
1622    * DOCUMENT ME!
1623    * 
1624    * @param evt
1625    *                DOCUMENT ME!
1626    */
1627   public void doMouseDraggedDefineMode(MouseEvent evt)
1628   {
1629     int res = findRes(evt);
1630     int y = findSeq(evt);
1631
1632     if (wrappedBlock != startWrapBlock)
1633     {
1634       return;
1635     }
1636
1637     if (stretchGroup == null)
1638     {
1639       return;
1640     }
1641
1642     if (res >= av.alignment.getWidth())
1643     {
1644       res = av.alignment.getWidth() - 1;
1645     }
1646
1647     if (stretchGroup.getEndRes() == res)
1648     {
1649       // Edit end res position of selected group
1650       changeEndRes = true;
1651     }
1652     else if (stretchGroup.getStartRes() == res)
1653     {
1654       // Edit start res position of selected group
1655       changeStartRes = true;
1656     }
1657
1658     if (res < av.getStartRes())
1659     {
1660       res = av.getStartRes();
1661     }
1662
1663     if (changeEndRes)
1664     {
1665       if (res > (stretchGroup.getStartRes() - 1))
1666       {
1667         stretchGroup.setEndRes(res);
1668       }
1669     }
1670     else if (changeStartRes)
1671     {
1672       if (res < (stretchGroup.getEndRes() + 1))
1673       {
1674         stretchGroup.setStartRes(res);
1675       }
1676     }
1677
1678     int dragDirection = 0;
1679
1680     if (y > oldSeq)
1681     {
1682       dragDirection = 1;
1683     }
1684     else if (y < oldSeq)
1685     {
1686       dragDirection = -1;
1687     }
1688
1689     while ((y != oldSeq) && (oldSeq > -1) && (y < av.alignment.getHeight()))
1690     {
1691       // This routine ensures we don't skip any sequences, as the
1692       // selection is quite slow.
1693       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1694
1695       oldSeq += dragDirection;
1696
1697       if (oldSeq < 0)
1698       {
1699         break;
1700       }
1701
1702       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1703
1704       if (stretchGroup.getSequences(null).contains(nextSeq))
1705       {
1706         stretchGroup.deleteSequence(seq, false);
1707       }
1708       else
1709       {
1710         if (seq != null)
1711         {
1712           stretchGroup.addSequence(seq, false);
1713         }
1714
1715         stretchGroup.addSequence(nextSeq, false);
1716       }
1717     }
1718
1719     if (oldSeq < 0)
1720     {
1721       oldSeq = -1;
1722     }
1723
1724     mouseDragging = true;
1725
1726     if (scrollThread != null)
1727     {
1728       scrollThread.setEvent(evt);
1729     }
1730
1731     seqCanvas.repaint();
1732   }
1733
1734   void scrollCanvas(MouseEvent evt)
1735   {
1736     if (evt == null)
1737     {
1738       if (scrollThread != null)
1739       {
1740         scrollThread.running = false;
1741         scrollThread = null;
1742       }
1743       mouseDragging = false;
1744     }
1745     else
1746     {
1747       if (scrollThread == null)
1748       {
1749         scrollThread = new ScrollThread();
1750       }
1751
1752       mouseDragging = true;
1753       scrollThread.setEvent(evt);
1754     }
1755
1756   }
1757
1758   // this class allows scrolling off the bottom of the visible alignment
1759   class ScrollThread extends Thread
1760   {
1761     MouseEvent evt;
1762
1763     boolean running = false;
1764
1765     public ScrollThread()
1766     {
1767       start();
1768     }
1769
1770     public void setEvent(MouseEvent e)
1771     {
1772       evt = e;
1773     }
1774
1775     public void stopScrolling()
1776     {
1777       running = false;
1778     }
1779
1780     public void run()
1781     {
1782       running = true;
1783
1784       while (running)
1785       {
1786         if (evt != null)
1787         {
1788           if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
1789           {
1790             running = ap.scrollUp(true);
1791           }
1792
1793           if (mouseDragging && (evt.getY() >= getHeight())
1794                   && (av.alignment.getHeight() > av.getEndSeq()))
1795           {
1796             running = ap.scrollUp(false);
1797           }
1798
1799           if (mouseDragging && (evt.getX() < 0))
1800           {
1801             running = ap.scrollRight(false);
1802           }
1803           else if (mouseDragging && (evt.getX() >= getWidth()))
1804           {
1805             running = ap.scrollRight(true);
1806           }
1807         }
1808
1809         try
1810         {
1811           Thread.sleep(20);
1812         } catch (Exception ex)
1813         {
1814         }
1815       }
1816     }
1817   }
1818   /**
1819    * modify current selection according to a received message.
1820    */
1821   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1822           SelectionSource source)
1823   {
1824     // TODO: fix this hack - source of messages is align viewport, but SeqPanel handles selection messages...
1825     if (av==source)
1826     {
1827       return;
1828     }
1829     // do we want to thread this ? (contention with seqsel and colsel locks, I suspect)
1830     boolean repaint=false;
1831     if (av.followSelection)
1832     {
1833       if (av.selectionGroup==null || !av.isSelectionGroupChanged())
1834       {
1835         SequenceGroup sgroup = (seqsel!=null) ? seqsel.intersect(av.alignment, (av.hasHiddenRows) ? av.hiddenRepSequences : null)
1836                   : null;
1837         if (sgroup!=null && sgroup.getSize()>0)
1838         {
1839           av.setSelectionGroup(sgroup);
1840         } else {
1841           av.setSelectionGroup(null);
1842         }
1843         repaint = av.isSelectionGroupChanged();
1844       }
1845       if (av.colSel==null || !av.isColSelChanged())
1846       {
1847         // Check to see if the current selection is from a previous message
1848         if (colsel==null || colsel.size()==0)
1849         {
1850           av.colSel.clear();
1851         } else {
1852           av.colSel = new ColumnSelection(colsel);
1853         } 
1854         repaint |= av.isColSelChanged();
1855       }
1856     }
1857     if (repaint)
1858     {
1859       // probably finessing with multiple redraws here
1860       PaintRefresher.Refresh(this, av.getSequenceSetId());
1861       // ap.paintAlignment(false);
1862     }
1863   }
1864 }