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