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