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