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