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