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