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