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