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