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