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