JAL-1620 version bump and release notes
[jalview.git] / src / jalview / gui / SeqPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
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.commands.EditCommand.Action;
25 import jalview.datamodel.ColumnSelection;
26 import jalview.datamodel.SearchResults;
27 import jalview.datamodel.Sequence;
28 import jalview.datamodel.SequenceFeature;
29 import jalview.datamodel.SequenceGroup;
30 import jalview.datamodel.SequenceI;
31 import jalview.io.SequenceAnnotationReport;
32 import jalview.schemes.ResidueProperties;
33 import jalview.structure.SelectionListener;
34 import jalview.structure.SelectionSource;
35 import jalview.structure.SequenceListener;
36 import jalview.structure.StructureSelectionManager;
37 import jalview.util.MessageManager;
38
39 import java.awt.BorderLayout;
40 import java.awt.Color;
41 import java.awt.Font;
42 import java.awt.FontMetrics;
43 import java.awt.Point;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.awt.event.MouseWheelEvent;
48 import java.awt.event.MouseWheelListener;
49 import java.util.List;
50 import java.util.Vector;
51
52 import javax.swing.JOptionPane;
53 import javax.swing.JPanel;
54 import javax.swing.ToolTipManager;
55
56 /**
57  * DOCUMENT ME!
58  * 
59  * @author $author$
60  * @version $Revision: 1.130 $
61  */
62 public class SeqPanel extends JPanel implements MouseListener,
63         MouseMotionListener, MouseWheelListener, SequenceListener,
64         SelectionListener
65
66 {
67   /** DOCUMENT ME!! */
68   public SeqCanvas seqCanvas;
69
70   /** DOCUMENT ME!! */
71   public AlignmentPanel ap;
72
73   protected int lastres;
74
75   protected int startseq;
76
77   protected AlignViewport av;
78
79   ScrollThread scrollThread = null;
80
81   boolean mouseDragging = false;
82
83   boolean editingSeqs = false;
84
85   boolean groupEditing = false;
86
87   // ////////////////////////////////////////
88   // ///Everything below this is for defining the boundary of the rubberband
89   // ////////////////////////////////////////
90   int oldSeq = -1;
91
92   boolean changeEndSeq = false;
93
94   boolean changeStartSeq = false;
95
96   boolean changeEndRes = false;
97
98   boolean changeStartRes = false;
99
100   SequenceGroup stretchGroup = null;
101
102   boolean remove = false;
103
104   Point lastMousePress;
105
106   boolean mouseWheelPressed = false;
107
108   StringBuffer keyboardNo1;
109
110   StringBuffer keyboardNo2;
111
112   java.net.URL linkImageURL;
113
114   private final SequenceAnnotationReport seqARep;
115
116   StringBuffer tooltipText = new StringBuffer();
117
118   String tmpString;
119
120   EditCommand editCommand;
121
122   StructureSelectionManager ssm;
123
124   /**
125    * Creates a new SeqPanel object.
126    * 
127    * @param avp
128    *          DOCUMENT ME!
129    * @param p
130    *          DOCUMENT ME!
131    */
132   public SeqPanel(AlignViewport av, AlignmentPanel ap)
133   {
134     linkImageURL = getClass().getResource("/images/link.gif");
135     seqARep = new SequenceAnnotationReport(linkImageURL.toString());
136     ToolTipManager.sharedInstance().registerComponent(this);
137     ToolTipManager.sharedInstance().setInitialDelay(0);
138     ToolTipManager.sharedInstance().setDismissDelay(10000);
139     this.av = av;
140     setBackground(Color.white);
141
142     seqCanvas = new SeqCanvas(ap);
143     setLayout(new BorderLayout());
144     add(seqCanvas, BorderLayout.CENTER);
145
146     this.ap = ap;
147
148     if (!av.isDataset())
149     {
150       addMouseMotionListener(this);
151       addMouseListener(this);
152       addMouseWheelListener(this);
153       ssm = av.getStructureSelectionManager();
154       ssm.addStructureViewerListener(this);
155       ssm.addSelectionListener(this);
156     }
157   }
158
159   int startWrapBlock = -1;
160
161   int wrappedBlock = -1;
162
163   int findRes(MouseEvent evt)
164   {
165     int res = 0;
166     int x = evt.getX();
167
168     if (av.wrapAlignment)
169     {
170
171       int hgap = av.charHeight;
172       if (av.scaleAboveWrapped)
173       {
174         hgap += av.charHeight;
175       }
176
177       int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
178               + seqCanvas.getAnnotationHeight();
179
180       int y = evt.getY();
181       y -= hgap;
182       x -= seqCanvas.LABEL_WEST;
183
184       int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
185       if (cwidth < 1)
186       {
187         return 0;
188       }
189
190       wrappedBlock = y / cHeight;
191       wrappedBlock += av.getStartRes() / cwidth;
192
193       res = wrappedBlock * cwidth + x / av.getCharWidth();
194
195     }
196     else
197     {
198       if (x > seqCanvas.getWidth() + seqCanvas.getWidth())
199       {
200         // make sure we calculate relative to visible alignment, rather than
201         // right-hand gutter
202         x = seqCanvas.getX() + seqCanvas.getWidth();
203       }
204       res = (x / av.getCharWidth()) + av.getStartRes();
205     }
206
207     if (av.hasHiddenColumns())
208     {
209       res = av.getColumnSelection().adjustForHiddenColumns(res);
210     }
211
212     return res;
213
214   }
215
216   int findSeq(MouseEvent evt)
217   {
218     int seq = 0;
219     int y = evt.getY();
220
221     if (av.wrapAlignment)
222     {
223       int hgap = av.charHeight;
224       if (av.scaleAboveWrapped)
225       {
226         hgap += av.charHeight;
227       }
228
229       int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
230               + seqCanvas.getAnnotationHeight();
231
232       y -= hgap;
233
234       seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
235               .getHeight() - 1);
236     }
237     else
238     {
239       seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(), av
240               .getAlignment().getHeight() - 1);
241     }
242
243     return seq;
244   }
245
246   SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
247   {
248     Vector tmp = new Vector();
249     SequenceFeature[] features = sequence.getSequenceFeatures();
250     if (features != null)
251     {
252       for (int i = 0; i < features.length; i++)
253       {
254         if (av.featuresDisplayed == null
255                 || !av.featuresDisplayed.containsKey(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 (!groups[g].getName().startsWith("JTreeGroup")
732                   && !groups[g].getName().startsWith("JGroup"))
733           {
734             tooltipText.append(groups[g].getName());
735           }
736
737           if (groups[g].getDescription() != null)
738           {
739             tooltipText.append(": " + groups[g].getDescription());
740           }
741         }
742       }
743     }
744
745     // use aa to see if the mouse pointer is on a
746     if (av.showSequenceFeatures)
747     {
748       int rpos;
749       SequenceFeature[] features = findFeaturesAtRes(
750               sequence.getDatasetSequence(),
751               rpos = sequence.findPosition(res));
752       seqARep.appendFeatures(tooltipText, rpos, features,
753               this.ap.seqPanel.seqCanvas.fr.minmax);
754     }
755     if (tooltipText.length() == 6) // <html></html>
756     {
757       setToolTipText(null);
758       lastTooltip = null;
759     }
760     else
761     {
762       if (lastTooltip == null
763               || !lastTooltip.equals(tooltipText.toString()))
764       {
765         String formatedTooltipText = JvSwingUtils.wrapTooltip(true,
766                 tooltipText.toString());
767         // String formatedTooltipText = tooltipText.toString();
768         setToolTipText(formatedTooltipText);
769         lastTooltip = tooltipText.toString();
770       }
771
772     }
773
774   }
775
776   private Point lastp = null;
777
778   /*
779    * (non-Javadoc)
780    * 
781    * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
782    */
783   public Point getToolTipLocation(MouseEvent event)
784   {
785     int x = event.getX(), w = getWidth();
786     int wdth = (w - x < 200) ? -(w / 2) : 5; // switch sides when tooltip is too
787     // close to edge
788     Point p = lastp;
789     if (!event.isShiftDown() || p == null)
790     {
791       p = (tooltipText != null && tooltipText.length() > 6) ? new Point(
792               event.getX() + wdth, event.getY() - 20) : null;
793     }
794     /*
795      * TODO: try to modify position region is not obcured by tooltip
796      */
797     return lastp = p;
798   }
799
800   String lastTooltip;
801
802   /**
803    * Set status message in alignment panel
804    * 
805    * @param sequence
806    *          aligned sequence object
807    * @param res
808    *          alignment column
809    * @param seq
810    *          index of sequence in alignment
811    * @return position of res in sequence
812    */
813   int setStatusMessage(SequenceI sequence, int res, int seq)
814   {
815     int pos = -1;
816     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
817             + sequence.getName());
818
819     Object obj = null;
820     if (av.getAlignment().isNucleotide())
821     {
822       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
823               + "");
824       if (obj != null)
825       {
826         text.append(" Nucleotide: ");
827       }
828     }
829     else
830     {
831       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
832       if (obj != null)
833       {
834         text.append("  Residue: ");
835       }
836     }
837
838     if (obj != null)
839     {
840       pos = sequence.findPosition(res);
841       if (obj != "")
842       {
843         text.append(obj + " (" + pos + ")");
844       }
845     }
846     ap.alignFrame.statusBar.setText(text.toString());
847     return pos;
848   }
849
850   /**
851    * DOCUMENT ME!
852    * 
853    * @param evt
854    *          DOCUMENT ME!
855    */
856   @Override
857   public void mouseDragged(MouseEvent evt)
858   {
859     if (mouseWheelPressed)
860     {
861       int oldWidth = av.charWidth;
862
863       // Which is bigger, left-right or up-down?
864       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
865               .getX() - lastMousePress.getX()))
866       {
867         int fontSize = av.font.getSize();
868
869         if (evt.getY() < lastMousePress.getY())
870         {
871           fontSize--;
872         }
873         else if (evt.getY() > lastMousePress.getY())
874         {
875           fontSize++;
876         }
877
878         if (fontSize < 1)
879         {
880           fontSize = 1;
881         }
882
883         av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
884         av.charWidth = oldWidth;
885         ap.fontChanged();
886       }
887       else
888       {
889         if (evt.getX() < lastMousePress.getX() && av.charWidth > 1)
890         {
891           av.charWidth--;
892         }
893         else if (evt.getX() > lastMousePress.getX())
894         {
895           av.charWidth++;
896         }
897
898         ap.paintAlignment(false);
899       }
900
901       FontMetrics fm = getFontMetrics(av.getFont());
902       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
903
904       lastMousePress = evt.getPoint();
905
906       return;
907     }
908
909     if (!editingSeqs)
910     {
911       doMouseDraggedDefineMode(evt);
912       return;
913     }
914
915     int res = findRes(evt);
916
917     if (res < 0)
918     {
919       res = 0;
920     }
921
922     if ((lastres == -1) || (lastres == res))
923     {
924       return;
925     }
926
927     if ((res < av.getAlignment().getWidth()) && (res < lastres))
928     {
929       // dragLeft, delete gap
930       editSequence(false, false, res);
931     }
932     else
933     {
934       editSequence(true, false, res);
935     }
936
937     mouseDragging = true;
938     if (scrollThread != null)
939     {
940       scrollThread.setEvent(evt);
941     }
942   }
943
944   // TODO: Make it more clever than many booleans
945   synchronized void editSequence(boolean insertGap, boolean editSeq,
946           int startres)
947   {
948     int fixedLeft = -1;
949     int fixedRight = -1;
950     boolean fixedColumns = false;
951     SequenceGroup sg = av.getSelectionGroup();
952
953     SequenceI seq = av.getAlignment().getSequenceAt(startseq);
954
955     // No group, but the sequence may represent a group
956     if (!groupEditing && av.hasHiddenRows())
957     {
958       if (av.isHiddenRepSequence(seq))
959       {
960         sg = av.getRepresentedSequences(seq);
961         groupEditing = true;
962       }
963     }
964
965     StringBuffer message = new StringBuffer();
966     if (groupEditing)
967     {
968       message.append("Edit group:");
969       if (editCommand == null)
970       {
971         editCommand = new EditCommand(MessageManager.getString("action.edit_group"));
972       }
973     }
974     else
975     {
976       message.append("Edit sequence: " + seq.getName());
977       String label = seq.getName();
978       if (label.length() > 10)
979       {
980         label = label.substring(0, 10);
981       }
982       if (editCommand == null)
983       {
984         editCommand = new EditCommand(MessageManager.formatMessage("label.edit_params", new String[]{label}));
985       }
986     }
987
988     if (insertGap)
989     {
990       message.append(" insert ");
991     }
992     else
993     {
994       message.append(" delete ");
995     }
996
997     message.append(Math.abs(startres - lastres) + " gaps.");
998     ap.alignFrame.statusBar.setText(message.toString());
999
1000     // Are we editing within a selection group?
1001     if (groupEditing
1002             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
1003                     .contains(seq)))
1004     {
1005       fixedColumns = true;
1006
1007       // sg might be null as the user may only see 1 sequence,
1008       // but the sequence represents a group
1009       if (sg == null)
1010       {
1011         if (!av.isHiddenRepSequence(seq))
1012         {
1013           endEditing();
1014           return;
1015         }
1016         sg = av.getRepresentedSequences(seq);
1017       }
1018
1019       fixedLeft = sg.getStartRes();
1020       fixedRight = sg.getEndRes();
1021
1022       if ((startres < fixedLeft && lastres >= fixedLeft)
1023               || (startres >= fixedLeft && lastres < fixedLeft)
1024               || (startres > fixedRight && lastres <= fixedRight)
1025               || (startres <= fixedRight && lastres > fixedRight))
1026       {
1027         endEditing();
1028         return;
1029       }
1030
1031       if (fixedLeft > startres)
1032       {
1033         fixedRight = fixedLeft - 1;
1034         fixedLeft = 0;
1035       }
1036       else if (fixedRight < startres)
1037       {
1038         fixedLeft = fixedRight;
1039         fixedRight = -1;
1040       }
1041     }
1042
1043     if (av.hasHiddenColumns())
1044     {
1045       fixedColumns = true;
1046       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1047       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1048
1049       if ((insertGap && startres > y1 && lastres < y1)
1050               || (!insertGap && startres < y2 && lastres > y2))
1051       {
1052         endEditing();
1053         return;
1054       }
1055
1056       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1057       // Selection spans a hidden region
1058       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1059       {
1060         if (startres >= y2)
1061         {
1062           fixedLeft = y2;
1063         }
1064         else
1065         {
1066           fixedRight = y2 - 1;
1067         }
1068       }
1069     }
1070
1071     if (groupEditing)
1072     {
1073       List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1074       int g, groupSize = vseqs.size();
1075       SequenceI[] groupSeqs = new SequenceI[groupSize];
1076       for (g = 0; g < groupSeqs.length; g++)
1077       {
1078         groupSeqs[g] = vseqs.get(g);
1079       }
1080
1081       // drag to right
1082       if (insertGap)
1083       {
1084         // If the user has selected the whole sequence, and is dragging to
1085         // the right, we can still extend the alignment and selectionGroup
1086         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1087                 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1088         {
1089           sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
1090           fixedRight = sg.getEndRes();
1091         }
1092
1093         // Is it valid with fixed columns??
1094         // Find the next gap before the end
1095         // of the visible region boundary
1096         boolean blank = false;
1097         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1098         {
1099           blank = true;
1100
1101           for (g = 0; g < groupSize; g++)
1102           {
1103             for (int j = 0; j < startres - lastres; j++)
1104             {
1105               if (!jalview.util.Comparison.isGap(groupSeqs[g]
1106                       .getCharAt(fixedRight - j)))
1107               {
1108                 blank = false;
1109                 break;
1110               }
1111             }
1112           }
1113           if (blank)
1114           {
1115             break;
1116           }
1117         }
1118
1119         if (!blank)
1120         {
1121           if (sg.getSize() == av.getAlignment().getHeight())
1122           {
1123             if ((av.hasHiddenColumns() && startres < av
1124                     .getColumnSelection().getHiddenBoundaryRight(startres)))
1125             {
1126               endEditing();
1127               return;
1128             }
1129
1130             int alWidth = av.getAlignment().getWidth();
1131             if (av.hasHiddenRows())
1132             {
1133               int hwidth = av.getAlignment().getHiddenSequences()
1134                       .getWidth();
1135               if (hwidth > alWidth)
1136               {
1137                 alWidth = hwidth;
1138               }
1139             }
1140             // We can still insert gaps if the selectionGroup
1141             // contains all the sequences
1142             sg.setEndRes(sg.getEndRes() + startres - lastres);
1143             fixedRight = alWidth + startres - lastres;
1144           }
1145           else
1146           {
1147             endEditing();
1148             return;
1149           }
1150         }
1151       }
1152
1153       // drag to left
1154       else if (!insertGap)
1155       {
1156         // / Are we able to delete?
1157         // ie are all columns blank?
1158
1159         for (g = 0; g < groupSize; g++)
1160         {
1161           for (int j = startres; j < lastres; j++)
1162           {
1163             if (groupSeqs[g].getLength() <= j)
1164             {
1165               continue;
1166             }
1167
1168             if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
1169             {
1170               // Not a gap, block edit not valid
1171               endEditing();
1172               return;
1173             }
1174           }
1175         }
1176       }
1177
1178       if (insertGap)
1179       {
1180         // dragging to the right
1181         if (fixedColumns && fixedRight != -1)
1182         {
1183           for (int j = lastres; j < startres; j++)
1184           {
1185             insertChar(j, groupSeqs, fixedRight);
1186           }
1187         }
1188         else
1189         {
1190           editCommand.appendEdit(Action.INSERT_GAP, groupSeqs,
1191                   startres, startres - lastres, av.getAlignment(), true);
1192         }
1193       }
1194       else
1195       {
1196         // dragging to the left
1197         if (fixedColumns && fixedRight != -1)
1198         {
1199           for (int j = lastres; j > startres; j--)
1200           {
1201             deleteChar(startres, groupSeqs, fixedRight);
1202           }
1203         }
1204         else
1205         {
1206           editCommand.appendEdit(Action.DELETE_GAP, groupSeqs,
1207                   startres, lastres - startres, av.getAlignment(), true);
1208         }
1209
1210       }
1211     }
1212     else
1213     // ///Editing a single sequence///////////
1214     {
1215       if (insertGap)
1216       {
1217         // dragging to the right
1218         if (fixedColumns && fixedRight != -1)
1219         {
1220           for (int j = lastres; j < startres; j++)
1221           {
1222             insertChar(j, new SequenceI[]
1223             { seq }, fixedRight);
1224           }
1225         }
1226         else
1227         {
1228           editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[]
1229           { seq }, lastres, startres - lastres, av.getAlignment(), true);
1230         }
1231       }
1232       else
1233       {
1234         if (!editSeq)
1235         {
1236           // dragging to the left
1237           if (fixedColumns && fixedRight != -1)
1238           {
1239             for (int j = lastres; j > startres; j--)
1240             {
1241               if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1242               {
1243                 endEditing();
1244                 break;
1245               }
1246               deleteChar(startres, new SequenceI[]
1247               { seq }, fixedRight);
1248             }
1249           }
1250           else
1251           {
1252             // could be a keyboard edit trying to delete none gaps
1253             int max = 0;
1254             for (int m = startres; m < lastres; m++)
1255             {
1256               if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1257               {
1258                 break;
1259               }
1260               max++;
1261             }
1262
1263             if (max > 0)
1264             {
1265               editCommand.appendEdit(Action.DELETE_GAP,
1266                       new SequenceI[]
1267                       { seq }, startres, max, av.getAlignment(), true);
1268             }
1269           }
1270         }
1271         else
1272         {// insertGap==false AND editSeq==TRUE;
1273           if (fixedColumns && fixedRight != -1)
1274           {
1275             for (int j = lastres; j < startres; j++)
1276             {
1277               insertChar(j, new SequenceI[]
1278               { seq }, fixedRight);
1279             }
1280           }
1281           else
1282           {
1283             editCommand.appendEdit(Action.INSERT_NUC, new SequenceI[]
1284             { seq }, lastres, startres - lastres, av.getAlignment(), true);
1285           }
1286         }
1287       }
1288     }
1289
1290     lastres = startres;
1291     seqCanvas.repaint();
1292   }
1293
1294   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1295   {
1296     int blankColumn = fixedColumn;
1297     for (int s = 0; s < seq.length; s++)
1298     {
1299       // Find the next gap before the end of the visible region boundary
1300       // If lastCol > j, theres a boundary after the gap insertion
1301
1302       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1303       {
1304         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1305         {
1306           // Theres a space, so break and insert the gap
1307           break;
1308         }
1309       }
1310
1311       if (blankColumn <= j)
1312       {
1313         blankColumn = fixedColumn;
1314         endEditing();
1315         return;
1316       }
1317     }
1318
1319     editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
1320             av.getAlignment(), true);
1321
1322     editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1,
1323             av.getAlignment(), true);
1324
1325   }
1326
1327   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1328   {
1329
1330     editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1,
1331             av.getAlignment(), true);
1332
1333     editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
1334             av.getAlignment(), true);
1335   }
1336
1337   /**
1338    * DOCUMENT ME!
1339    * 
1340    * @param e
1341    *          DOCUMENT ME!
1342    */
1343   @Override
1344   public void mouseEntered(MouseEvent e)
1345   {
1346     if (oldSeq < 0)
1347     {
1348       oldSeq = 0;
1349     }
1350
1351     if (scrollThread != null)
1352     {
1353       scrollThread.running = false;
1354       scrollThread = null;
1355     }
1356   }
1357
1358   /**
1359    * DOCUMENT ME!
1360    * 
1361    * @param e
1362    *          DOCUMENT ME!
1363    */
1364   @Override
1365   public void mouseExited(MouseEvent e)
1366   {
1367     if (av.getWrapAlignment())
1368     {
1369       return;
1370     }
1371
1372     if (mouseDragging)
1373     {
1374       scrollThread = new ScrollThread();
1375     }
1376   }
1377
1378   @Override
1379   public void mouseClicked(MouseEvent evt)
1380   {
1381     SequenceGroup sg = null;
1382     SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
1383     if (evt.getClickCount() > 1)
1384     {
1385       sg = av.getSelectionGroup();
1386       if (sg != null && sg.getSize() == 1
1387               && sg.getEndRes() - sg.getStartRes() < 2)
1388       {
1389         av.setSelectionGroup(null);
1390       }
1391
1392       SequenceFeature[] features = findFeaturesAtRes(
1393               sequence.getDatasetSequence(),
1394               sequence.findPosition(findRes(evt)));
1395
1396       if (features != null && features.length > 0)
1397       {
1398         SearchResults highlight = new SearchResults();
1399         highlight.addResult(sequence, features[0].getBegin(),
1400                 features[0].getEnd());
1401         seqCanvas.highlightSearchResults(highlight);
1402       }
1403       if (features != null && features.length > 0)
1404       {
1405         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
1406         { sequence }, features, false, ap);
1407
1408         seqCanvas.highlightSearchResults(null);
1409       }
1410     }
1411   }
1412
1413   @Override
1414   public void mouseWheelMoved(MouseWheelEvent e)
1415   {
1416     e.consume();
1417     if (e.getWheelRotation() > 0)
1418     {
1419       if (e.isShiftDown())
1420       {
1421         ap.scrollRight(true);
1422
1423       }
1424       else
1425       {
1426         ap.scrollUp(false);
1427       }
1428     }
1429     else
1430     {
1431       if (e.isShiftDown())
1432       {
1433         ap.scrollRight(false);
1434       }
1435       else
1436       {
1437         ap.scrollUp(true);
1438       }
1439     }
1440     // TODO Update tooltip for new position.
1441   }
1442
1443   /**
1444    * DOCUMENT ME!
1445    * 
1446    * @param evt
1447    *          DOCUMENT ME!
1448    */
1449   public void doMousePressedDefineMode(MouseEvent evt)
1450   {
1451     int res = findRes(evt);
1452     int seq = findSeq(evt);
1453     oldSeq = seq;
1454
1455     startWrapBlock = wrappedBlock;
1456
1457     if (av.wrapAlignment && seq > av.getAlignment().getHeight())
1458     {
1459       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
1460               .getString("label.cannot_edit_annotations_in_wrapped_view"),
1461               MessageManager.getString("label.wrapped_view_no_edit"),
1462               JOptionPane.WARNING_MESSAGE);
1463       return;
1464     }
1465
1466     if (seq < 0 || res < 0)
1467     {
1468       return;
1469     }
1470
1471     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1472
1473     if ((sequence == null) || (res > sequence.getLength()))
1474     {
1475       return;
1476     }
1477
1478     stretchGroup = av.getSelectionGroup();
1479
1480     if (stretchGroup == null)
1481     {
1482       stretchGroup = av.getAlignment().findGroup(sequence);
1483
1484       if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
1485               && (res < stretchGroup.getEndRes()))
1486       {
1487         av.setSelectionGroup(stretchGroup);
1488       }
1489       else
1490       {
1491         stretchGroup = null;
1492       }
1493     }
1494     else if (!stretchGroup.getSequences(null).contains(sequence)
1495             || (stretchGroup.getStartRes() > res)
1496             || (stretchGroup.getEndRes() < res))
1497     {
1498       stretchGroup = null;
1499
1500       SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
1501
1502       if (allGroups != null)
1503       {
1504         for (int i = 0; i < allGroups.length; i++)
1505         {
1506           if ((allGroups[i].getStartRes() <= res)
1507                   && (allGroups[i].getEndRes() >= res))
1508           {
1509             stretchGroup = allGroups[i];
1510             break;
1511           }
1512         }
1513       }
1514
1515       av.setSelectionGroup(stretchGroup);
1516
1517     }
1518
1519     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
1520     {
1521       SequenceFeature[] allFeatures = findFeaturesAtRes(
1522               sequence.getDatasetSequence(), sequence.findPosition(res));
1523       Vector links = new Vector();
1524       for (int i = 0; i < allFeatures.length; i++)
1525       {
1526         if (allFeatures[i].links != null)
1527         {
1528           for (int j = 0; j < allFeatures[i].links.size(); j++)
1529           {
1530             links.addElement(allFeatures[i].links.elementAt(j));
1531           }
1532         }
1533       }
1534
1535       jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
1536       pop.show(this, evt.getX(), evt.getY());
1537       return;
1538     }
1539
1540     if (av.cursorMode)
1541     {
1542       seqCanvas.cursorX = findRes(evt);
1543       seqCanvas.cursorY = findSeq(evt);
1544       seqCanvas.repaint();
1545       return;
1546     }
1547
1548     if (stretchGroup == null)
1549     {
1550       // Only if left mouse button do we want to change group sizes
1551
1552       // define a new group here
1553       SequenceGroup sg = new SequenceGroup();
1554       sg.setStartRes(res);
1555       sg.setEndRes(res);
1556       sg.addSequence(sequence, false);
1557       av.setSelectionGroup(sg);
1558
1559       stretchGroup = sg;
1560
1561       if (av.getConservationSelected())
1562       {
1563         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1564                 "Background");
1565       }
1566
1567       if (av.getAbovePIDThreshold())
1568       {
1569         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1570                 "Background");
1571       }
1572       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1573       {
1574         // Edit end res position of selected group
1575         changeEndRes = true;
1576       }
1577       else if ((stretchGroup != null)
1578               && (stretchGroup.getStartRes() == res))
1579       {
1580         // Edit end res position of selected group
1581         changeStartRes = true;
1582       }
1583       stretchGroup.getWidth();
1584     }
1585
1586     seqCanvas.repaint();
1587   }
1588
1589   /**
1590    * DOCUMENT ME!
1591    * 
1592    * @param evt
1593    *          DOCUMENT ME!
1594    */
1595   public void doMouseReleasedDefineMode(MouseEvent evt)
1596   {
1597     if (stretchGroup == null)
1598     {
1599       return;
1600     }
1601
1602     stretchGroup.recalcConservation(); // always do this - annotation has own
1603                                        // state
1604     if (stretchGroup.cs != null)
1605     {
1606       stretchGroup.cs.alignmentChanged(stretchGroup,
1607               av.getHiddenRepSequences());
1608
1609       if (stretchGroup.cs.conservationApplied())
1610       {
1611         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1612                 stretchGroup.getName());
1613       }
1614       else
1615       {
1616         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1617                 stretchGroup.getName());
1618       }
1619     }
1620     PaintRefresher.Refresh(this, av.getSequenceSetId());
1621     ap.paintAlignment(true);
1622
1623     changeEndRes = false;
1624     changeStartRes = false;
1625     stretchGroup = null;
1626     av.sendSelection();
1627   }
1628
1629   /**
1630    * DOCUMENT ME!
1631    * 
1632    * @param evt
1633    *          DOCUMENT ME!
1634    */
1635   public void doMouseDraggedDefineMode(MouseEvent evt)
1636   {
1637     int res = findRes(evt);
1638     int y = findSeq(evt);
1639
1640     if (wrappedBlock != startWrapBlock)
1641     {
1642       return;
1643     }
1644
1645     if (stretchGroup == null)
1646     {
1647       return;
1648     }
1649
1650     if (res >= av.getAlignment().getWidth())
1651     {
1652       res = av.getAlignment().getWidth() - 1;
1653     }
1654
1655     if (stretchGroup.getEndRes() == res)
1656     {
1657       // Edit end res position of selected group
1658       changeEndRes = true;
1659     }
1660     else if (stretchGroup.getStartRes() == res)
1661     {
1662       // Edit start res position of selected group
1663       changeStartRes = true;
1664     }
1665
1666     if (res < av.getStartRes())
1667     {
1668       res = av.getStartRes();
1669     }
1670
1671     if (changeEndRes)
1672     {
1673       if (res > (stretchGroup.getStartRes() - 1))
1674       {
1675         stretchGroup.setEndRes(res);
1676       }
1677     }
1678     else if (changeStartRes)
1679     {
1680       if (res < (stretchGroup.getEndRes() + 1))
1681       {
1682         stretchGroup.setStartRes(res);
1683       }
1684     }
1685
1686     int dragDirection = 0;
1687
1688     if (y > oldSeq)
1689     {
1690       dragDirection = 1;
1691     }
1692     else if (y < oldSeq)
1693     {
1694       dragDirection = -1;
1695     }
1696
1697     while ((y != oldSeq) && (oldSeq > -1)
1698             && (y < av.getAlignment().getHeight()))
1699     {
1700       // This routine ensures we don't skip any sequences, as the
1701       // selection is quite slow.
1702       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1703
1704       oldSeq += dragDirection;
1705
1706       if (oldSeq < 0)
1707       {
1708         break;
1709       }
1710
1711       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1712
1713       if (stretchGroup.getSequences(null).contains(nextSeq))
1714       {
1715         stretchGroup.deleteSequence(seq, false);
1716       }
1717       else
1718       {
1719         if (seq != null)
1720         {
1721           stretchGroup.addSequence(seq, false);
1722         }
1723
1724         stretchGroup.addSequence(nextSeq, false);
1725       }
1726     }
1727
1728     if (oldSeq < 0)
1729     {
1730       oldSeq = -1;
1731     }
1732
1733     mouseDragging = true;
1734
1735     if (scrollThread != null)
1736     {
1737       scrollThread.setEvent(evt);
1738     }
1739
1740     seqCanvas.repaint();
1741   }
1742
1743   void scrollCanvas(MouseEvent evt)
1744   {
1745     if (evt == null)
1746     {
1747       if (scrollThread != null)
1748       {
1749         scrollThread.running = false;
1750         scrollThread = null;
1751       }
1752       mouseDragging = false;
1753     }
1754     else
1755     {
1756       if (scrollThread == null)
1757       {
1758         scrollThread = new ScrollThread();
1759       }
1760
1761       mouseDragging = true;
1762       scrollThread.setEvent(evt);
1763     }
1764
1765   }
1766
1767   // this class allows scrolling off the bottom of the visible alignment
1768   class ScrollThread extends Thread
1769   {
1770     MouseEvent evt;
1771
1772     boolean running = false;
1773
1774     public ScrollThread()
1775     {
1776       start();
1777     }
1778
1779     public void setEvent(MouseEvent e)
1780     {
1781       evt = e;
1782     }
1783
1784     public void stopScrolling()
1785     {
1786       running = false;
1787     }
1788
1789     @Override
1790     public void run()
1791     {
1792       running = true;
1793
1794       while (running)
1795       {
1796         if (evt != null)
1797         {
1798           if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
1799           {
1800             running = ap.scrollUp(true);
1801           }
1802
1803           if (mouseDragging && (evt.getY() >= getHeight())
1804                   && (av.getAlignment().getHeight() > av.getEndSeq()))
1805           {
1806             running = ap.scrollUp(false);
1807           }
1808
1809           if (mouseDragging && (evt.getX() < 0))
1810           {
1811             running = ap.scrollRight(false);
1812           }
1813           else if (mouseDragging && (evt.getX() >= getWidth()))
1814           {
1815             running = ap.scrollRight(true);
1816           }
1817         }
1818
1819         try
1820         {
1821           Thread.sleep(20);
1822         } catch (Exception ex)
1823         {
1824         }
1825       }
1826     }
1827   }
1828
1829   /**
1830    * modify current selection according to a received message.
1831    */
1832   @Override
1833   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1834           SelectionSource source)
1835   {
1836     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1837     // handles selection messages...
1838     // TODO: extend config options to allow user to control if selections may be
1839     // shared between viewports.
1840     if (av == source
1841             || !av.followSelection
1842             || (av.isSelectionGroupChanged(false) || av
1843                     .isColSelChanged(false))
1844             || (source instanceof AlignViewport && ((AlignViewport) source)
1845                     .getSequenceSetId().equals(av.getSequenceSetId())))
1846     {
1847       return;
1848     }
1849     // do we want to thread this ? (contention with seqsel and colsel locks, I
1850     // suspect)
1851     // rules are: colsel is copied if there is a real intersection between
1852     // sequence selection
1853     boolean repaint = false, copycolsel = true;
1854     // if (!av.isSelectionGroupChanged(false))
1855     {
1856       SequenceGroup sgroup = null;
1857       if (seqsel != null && seqsel.getSize() > 0)
1858       {
1859         if (av.getAlignment() == null)
1860         {
1861           jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
1862                   + av.getSequenceSetId() + " ViewId=" + av.getViewId()
1863                   + " 's alignment is NULL! returning immediatly.");
1864           return;
1865         }
1866         sgroup = seqsel.intersect(av.getAlignment(),
1867                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1868         if ((sgroup == null || sgroup.getSize() == 0)
1869                 || (colsel == null || colsel.size() == 0))
1870         {
1871           // don't copy columns if the region didn't intersect.
1872           copycolsel = false;
1873         }
1874       }
1875       if (sgroup != null && sgroup.getSize() > 0)
1876       {
1877         av.setSelectionGroup(sgroup);
1878       }
1879       else
1880       {
1881         av.setSelectionGroup(null);
1882       }
1883       av.isSelectionGroupChanged(true);
1884       repaint = true;
1885     }
1886     if (copycolsel)
1887     {
1888       // the current selection is unset or from a previous message
1889       // so import the new colsel.
1890       if (colsel == null || colsel.size() == 0)
1891       {
1892         if (av.getColumnSelection() != null)
1893         {
1894           av.getColumnSelection().clear();
1895           repaint = true;
1896         }
1897       }
1898       else
1899       {
1900         // TODO: shift colSel according to the intersecting sequences
1901         if (av.getColumnSelection() == null)
1902         {
1903           av.setColumnSelection(new ColumnSelection(colsel));
1904         }
1905         else
1906         {
1907           av.getColumnSelection().setElementsFrom(colsel);
1908         }
1909       }
1910       av.isColSelChanged(true);
1911       repaint = true;
1912     }
1913     if (copycolsel
1914             && av.hasHiddenColumns()
1915             && (av.getColumnSelection() == null || av.getColumnSelection()
1916                     .getHiddenColumns() == null))
1917     {
1918       System.err.println("Bad things");
1919     }
1920     if (repaint)
1921     {
1922       // probably finessing with multiple redraws here
1923       PaintRefresher.Refresh(this, av.getSequenceSetId());
1924       // ap.paintAlignment(false);
1925     }
1926   }
1927 }