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