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