Merge branch 'bug/JAL-998_wrap-sequence-tooltip' into develop
[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();
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 (!groups[g].getName().startsWith("JTreeGroup")
733                   && !groups[g].getName().startsWith("JGroup"))
734           {
735             tooltipText.append(groups[g].getName());
736           }
737
738           if (groups[g].getDescription() != null)
739           {
740             tooltipText.append(": " + groups[g].getDescription());
741           }
742         }
743       }
744     }
745
746     // use aa to see if the mouse pointer is on a
747     if (av.showSequenceFeatures)
748     {
749       int rpos;
750       SequenceFeature[] features = findFeaturesAtRes(
751               sequence.getDatasetSequence(),
752               rpos = sequence.findPosition(res));
753       seqARep.appendFeatures(tooltipText, rpos, features,
754               this.ap.seqPanel.seqCanvas.fr.minmax);
755     }
756     if (tooltipText.length() == 6) // <html></html>
757     {
758       setToolTipText(null);
759       lastTooltip = null;
760     }
761     else
762     {
763       if (lastTooltip == null
764               || !lastTooltip.equals(tooltipText.toString()))
765       {
766         String formatedTooltipText = JvSwingUtils.wrapTooltip(true,
767                 tooltipText.toString());
768         // String formatedTooltipText = tooltipText.toString();
769         setToolTipText(formatedTooltipText);
770         lastTooltip = tooltipText.toString();
771       }
772
773     }
774
775   }
776
777   private Point lastp = null;
778
779   /*
780    * (non-Javadoc)
781    * 
782    * @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
783    */
784   public Point getToolTipLocation(MouseEvent event)
785   {
786     int x = event.getX(), w = getWidth();
787     int wdth = (w - x < 200) ? -(w / 2) : 5; // switch sides when tooltip is too
788     // close to edge
789     Point p = lastp;
790     if (!event.isShiftDown() || p == null)
791     {
792       p = (tooltipText != null && tooltipText.length() > 6) ? new Point(
793               event.getX() + wdth, event.getY() - 20) : null;
794     }
795     /*
796      * TODO: try to modify position region is not obcured by tooltip
797      */
798     return lastp = p;
799   }
800
801   String lastTooltip;
802
803   /**
804    * Set status message in alignment panel
805    * 
806    * @param sequence
807    *          aligned sequence object
808    * @param res
809    *          alignment column
810    * @param seq
811    *          index of sequence in alignment
812    * @return position of res in sequence
813    */
814   int setStatusMessage(SequenceI sequence, int res, int seq)
815   {
816     int pos = -1;
817     StringBuffer text = new StringBuffer("Sequence " + (seq + 1) + " ID: "
818             + sequence.getName());
819
820     Object obj = null;
821     if (av.getAlignment().isNucleotide())
822     {
823       obj = ResidueProperties.nucleotideName.get(sequence.getCharAt(res)
824               + "");
825       if (obj != null)
826       {
827         text.append(" Nucleotide: ");
828       }
829     }
830     else
831     {
832       obj = ResidueProperties.aa2Triplet.get(sequence.getCharAt(res) + "");
833       if (obj != null)
834       {
835         text.append("  Residue: ");
836       }
837     }
838
839     if (obj != null)
840     {
841       pos = sequence.findPosition(res);
842       if (obj != "")
843       {
844         text.append(obj + " (" + pos + ")");
845       }
846     }
847     ap.alignFrame.statusBar.setText(text.toString());
848     return pos;
849   }
850
851   /**
852    * DOCUMENT ME!
853    * 
854    * @param evt
855    *          DOCUMENT ME!
856    */
857   @Override
858   public void mouseDragged(MouseEvent evt)
859   {
860     if (mouseWheelPressed)
861     {
862       int oldWidth = av.charWidth;
863
864       // Which is bigger, left-right or up-down?
865       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
866               .getX() - lastMousePress.getX()))
867       {
868         int fontSize = av.font.getSize();
869
870         if (evt.getY() < lastMousePress.getY())
871         {
872           fontSize--;
873         }
874         else if (evt.getY() > lastMousePress.getY())
875         {
876           fontSize++;
877         }
878
879         if (fontSize < 1)
880         {
881           fontSize = 1;
882         }
883
884         av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
885         av.charWidth = oldWidth;
886         ap.fontChanged();
887       }
888       else
889       {
890         if (evt.getX() < lastMousePress.getX() && av.charWidth > 1)
891         {
892           av.charWidth--;
893         }
894         else if (evt.getX() > lastMousePress.getX())
895         {
896           av.charWidth++;
897         }
898
899         ap.paintAlignment(false);
900       }
901
902       FontMetrics fm = getFontMetrics(av.getFont());
903       av.validCharWidth = fm.charWidth('M') <= av.charWidth;
904
905       lastMousePress = evt.getPoint();
906
907       return;
908     }
909
910     if (!editingSeqs)
911     {
912       doMouseDraggedDefineMode(evt);
913       return;
914     }
915
916     int res = findRes(evt);
917
918     if (res < 0)
919     {
920       res = 0;
921     }
922
923     if ((lastres == -1) || (lastres == res))
924     {
925       return;
926     }
927
928     if ((res < av.getAlignment().getWidth()) && (res < lastres))
929     {
930       // dragLeft, delete gap
931       editSequence(false, false, res);
932     }
933     else
934     {
935       editSequence(true, false, res);
936     }
937
938     mouseDragging = true;
939     if (scrollThread != null)
940     {
941       scrollThread.setEvent(evt);
942     }
943   }
944
945   // TODO: Make it more clever than many booleans
946   synchronized void editSequence(boolean insertGap, boolean editSeq,
947           int startres)
948   {
949     int fixedLeft = -1;
950     int fixedRight = -1;
951     boolean fixedColumns = false;
952     SequenceGroup sg = av.getSelectionGroup();
953
954     SequenceI seq = av.getAlignment().getSequenceAt(startseq);
955
956     // No group, but the sequence may represent a group
957     if (!groupEditing && av.hasHiddenRows())
958     {
959       if (av.isHiddenRepSequence(seq))
960       {
961         sg = av.getRepresentedSequences(seq);
962         groupEditing = true;
963       }
964     }
965
966     StringBuffer message = new StringBuffer();
967     if (groupEditing)
968     {
969       message.append("Edit group:");
970       if (editCommand == null)
971       {
972         editCommand = new EditCommand(MessageManager.getString("action.edit_group"));
973       }
974     }
975     else
976     {
977       message.append("Edit sequence: " + seq.getName());
978       String label = seq.getName();
979       if (label.length() > 10)
980       {
981         label = label.substring(0, 10);
982       }
983       if (editCommand == null)
984       {
985         editCommand = new EditCommand(MessageManager.formatMessage("label.edit_params", new String[]{label}));
986       }
987     }
988
989     if (insertGap)
990     {
991       message.append(" insert ");
992     }
993     else
994     {
995       message.append(" delete ");
996     }
997
998     message.append(Math.abs(startres - lastres) + " gaps.");
999     ap.alignFrame.statusBar.setText(message.toString());
1000
1001     // Are we editing within a selection group?
1002     if (groupEditing
1003             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
1004                     .contains(seq)))
1005     {
1006       fixedColumns = true;
1007
1008       // sg might be null as the user may only see 1 sequence,
1009       // but the sequence represents a group
1010       if (sg == null)
1011       {
1012         if (!av.isHiddenRepSequence(seq))
1013         {
1014           endEditing();
1015           return;
1016         }
1017         sg = av.getRepresentedSequences(seq);
1018       }
1019
1020       fixedLeft = sg.getStartRes();
1021       fixedRight = sg.getEndRes();
1022
1023       if ((startres < fixedLeft && lastres >= fixedLeft)
1024               || (startres >= fixedLeft && lastres < fixedLeft)
1025               || (startres > fixedRight && lastres <= fixedRight)
1026               || (startres <= fixedRight && lastres > fixedRight))
1027       {
1028         endEditing();
1029         return;
1030       }
1031
1032       if (fixedLeft > startres)
1033       {
1034         fixedRight = fixedLeft - 1;
1035         fixedLeft = 0;
1036       }
1037       else if (fixedRight < startres)
1038       {
1039         fixedLeft = fixedRight;
1040         fixedRight = -1;
1041       }
1042     }
1043
1044     if (av.hasHiddenColumns())
1045     {
1046       fixedColumns = true;
1047       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1048       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1049
1050       if ((insertGap && startres > y1 && lastres < y1)
1051               || (!insertGap && startres < y2 && lastres > y2))
1052       {
1053         endEditing();
1054         return;
1055       }
1056
1057       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1058       // Selection spans a hidden region
1059       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1060       {
1061         if (startres >= y2)
1062         {
1063           fixedLeft = y2;
1064         }
1065         else
1066         {
1067           fixedRight = y2 - 1;
1068         }
1069       }
1070     }
1071
1072     if (groupEditing)
1073     {
1074       List<SequenceI> vseqs = sg.getSequences(av.getHiddenRepSequences());
1075       int g, groupSize = vseqs.size();
1076       SequenceI[] groupSeqs = new SequenceI[groupSize];
1077       for (g = 0; g < groupSeqs.length; g++)
1078       {
1079         groupSeqs[g] = vseqs.get(g);
1080       }
1081
1082       // drag to right
1083       if (insertGap)
1084       {
1085         // If the user has selected the whole sequence, and is dragging to
1086         // the right, we can still extend the alignment and selectionGroup
1087         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1088                 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1089         {
1090           sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
1091           fixedRight = sg.getEndRes();
1092         }
1093
1094         // Is it valid with fixed columns??
1095         // Find the next gap before the end
1096         // of the visible region boundary
1097         boolean blank = false;
1098         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1099         {
1100           blank = true;
1101
1102           for (g = 0; g < groupSize; g++)
1103           {
1104             for (int j = 0; j < startres - lastres; j++)
1105             {
1106               if (!jalview.util.Comparison.isGap(groupSeqs[g]
1107                       .getCharAt(fixedRight - j)))
1108               {
1109                 blank = false;
1110                 break;
1111               }
1112             }
1113           }
1114           if (blank)
1115           {
1116             break;
1117           }
1118         }
1119
1120         if (!blank)
1121         {
1122           if (sg.getSize() == av.getAlignment().getHeight())
1123           {
1124             if ((av.hasHiddenColumns() && startres < av
1125                     .getColumnSelection().getHiddenBoundaryRight(startres)))
1126             {
1127               endEditing();
1128               return;
1129             }
1130
1131             int alWidth = av.getAlignment().getWidth();
1132             if (av.hasHiddenRows())
1133             {
1134               int hwidth = av.getAlignment().getHiddenSequences()
1135                       .getWidth();
1136               if (hwidth > alWidth)
1137               {
1138                 alWidth = hwidth;
1139               }
1140             }
1141             // We can still insert gaps if the selectionGroup
1142             // contains all the sequences
1143             sg.setEndRes(sg.getEndRes() + startres - lastres);
1144             fixedRight = alWidth + startres - lastres;
1145           }
1146           else
1147           {
1148             endEditing();
1149             return;
1150           }
1151         }
1152       }
1153
1154       // drag to left
1155       else if (!insertGap)
1156       {
1157         // / Are we able to delete?
1158         // ie are all columns blank?
1159
1160         for (g = 0; g < groupSize; g++)
1161         {
1162           for (int j = startres; j < lastres; j++)
1163           {
1164             if (groupSeqs[g].getLength() <= j)
1165             {
1166               continue;
1167             }
1168
1169             if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
1170             {
1171               // Not a gap, block edit not valid
1172               endEditing();
1173               return;
1174             }
1175           }
1176         }
1177       }
1178
1179       if (insertGap)
1180       {
1181         // dragging to the right
1182         if (fixedColumns && fixedRight != -1)
1183         {
1184           for (int j = lastres; j < startres; j++)
1185           {
1186             insertChar(j, groupSeqs, fixedRight);
1187           }
1188         }
1189         else
1190         {
1191           editCommand.appendEdit(Action.INSERT_GAP, groupSeqs,
1192                   startres, startres - lastres, av.getAlignment(), true);
1193         }
1194       }
1195       else
1196       {
1197         // dragging to the left
1198         if (fixedColumns && fixedRight != -1)
1199         {
1200           for (int j = lastres; j > startres; j--)
1201           {
1202             deleteChar(startres, groupSeqs, fixedRight);
1203           }
1204         }
1205         else
1206         {
1207           editCommand.appendEdit(Action.DELETE_GAP, groupSeqs,
1208                   startres, lastres - startres, av.getAlignment(), true);
1209         }
1210
1211       }
1212     }
1213     else
1214     // ///Editing a single sequence///////////
1215     {
1216       if (insertGap)
1217       {
1218         // dragging to the right
1219         if (fixedColumns && fixedRight != -1)
1220         {
1221           for (int j = lastres; j < startres; j++)
1222           {
1223             insertChar(j, new SequenceI[]
1224             { seq }, fixedRight);
1225           }
1226         }
1227         else
1228         {
1229           editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[]
1230           { seq }, lastres, startres - lastres, av.getAlignment(), true);
1231         }
1232       }
1233       else
1234       {
1235         if (!editSeq)
1236         {
1237           // dragging to the left
1238           if (fixedColumns && fixedRight != -1)
1239           {
1240             for (int j = lastres; j > startres; j--)
1241             {
1242               if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1243               {
1244                 endEditing();
1245                 break;
1246               }
1247               deleteChar(startres, new SequenceI[]
1248               { seq }, fixedRight);
1249             }
1250           }
1251           else
1252           {
1253             // could be a keyboard edit trying to delete none gaps
1254             int max = 0;
1255             for (int m = startres; m < lastres; m++)
1256             {
1257               if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1258               {
1259                 break;
1260               }
1261               max++;
1262             }
1263
1264             if (max > 0)
1265             {
1266               editCommand.appendEdit(Action.DELETE_GAP,
1267                       new SequenceI[]
1268                       { seq }, startres, max, av.getAlignment(), true);
1269             }
1270           }
1271         }
1272         else
1273         {// insertGap==false AND editSeq==TRUE;
1274           if (fixedColumns && fixedRight != -1)
1275           {
1276             for (int j = lastres; j < startres; j++)
1277             {
1278               insertChar(j, new SequenceI[]
1279               { seq }, fixedRight);
1280             }
1281           }
1282           else
1283           {
1284             editCommand.appendEdit(Action.INSERT_NUC, new SequenceI[]
1285             { seq }, lastres, startres - lastres, av.getAlignment(), true);
1286           }
1287         }
1288       }
1289     }
1290
1291     lastres = startres;
1292     seqCanvas.repaint();
1293   }
1294
1295   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1296   {
1297     int blankColumn = fixedColumn;
1298     for (int s = 0; s < seq.length; s++)
1299     {
1300       // Find the next gap before the end of the visible region boundary
1301       // If lastCol > j, theres a boundary after the gap insertion
1302
1303       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1304       {
1305         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1306         {
1307           // Theres a space, so break and insert the gap
1308           break;
1309         }
1310       }
1311
1312       if (blankColumn <= j)
1313       {
1314         blankColumn = fixedColumn;
1315         endEditing();
1316         return;
1317       }
1318     }
1319
1320     editCommand.appendEdit(Action.DELETE_GAP, seq, blankColumn, 1,
1321             av.getAlignment(), true);
1322
1323     editCommand.appendEdit(Action.INSERT_GAP, seq, j, 1,
1324             av.getAlignment(), true);
1325
1326   }
1327
1328   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1329   {
1330
1331     editCommand.appendEdit(Action.DELETE_GAP, seq, j, 1,
1332             av.getAlignment(), true);
1333
1334     editCommand.appendEdit(Action.INSERT_GAP, seq, fixedColumn, 1,
1335             av.getAlignment(), true);
1336   }
1337
1338   /**
1339    * DOCUMENT ME!
1340    * 
1341    * @param e
1342    *          DOCUMENT ME!
1343    */
1344   @Override
1345   public void mouseEntered(MouseEvent e)
1346   {
1347     if (oldSeq < 0)
1348     {
1349       oldSeq = 0;
1350     }
1351
1352     if (scrollThread != null)
1353     {
1354       scrollThread.running = false;
1355       scrollThread = null;
1356     }
1357   }
1358
1359   /**
1360    * DOCUMENT ME!
1361    * 
1362    * @param e
1363    *          DOCUMENT ME!
1364    */
1365   @Override
1366   public void mouseExited(MouseEvent e)
1367   {
1368     if (av.getWrapAlignment())
1369     {
1370       return;
1371     }
1372
1373     if (mouseDragging)
1374     {
1375       scrollThread = new ScrollThread();
1376     }
1377   }
1378
1379   @Override
1380   public void mouseClicked(MouseEvent evt)
1381   {
1382     SequenceGroup sg = null;
1383     SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
1384     if (evt.getClickCount() > 1)
1385     {
1386       sg = av.getSelectionGroup();
1387       if (sg != null && sg.getSize() == 1
1388               && sg.getEndRes() - sg.getStartRes() < 2)
1389       {
1390         av.setSelectionGroup(null);
1391       }
1392
1393       SequenceFeature[] features = findFeaturesAtRes(
1394               sequence.getDatasetSequence(),
1395               sequence.findPosition(findRes(evt)));
1396
1397       if (features != null && features.length > 0)
1398       {
1399         SearchResults highlight = new SearchResults();
1400         highlight.addResult(sequence, features[0].getBegin(),
1401                 features[0].getEnd());
1402         seqCanvas.highlightSearchResults(highlight);
1403       }
1404       if (features != null && features.length > 0)
1405       {
1406         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
1407         { sequence }, features, false, ap);
1408
1409         seqCanvas.highlightSearchResults(null);
1410       }
1411     }
1412   }
1413
1414   @Override
1415   public void mouseWheelMoved(MouseWheelEvent e)
1416   {
1417     e.consume();
1418     if (e.getWheelRotation() > 0)
1419     {
1420       if (e.isShiftDown())
1421       {
1422         ap.scrollRight(true);
1423
1424       }
1425       else
1426       {
1427         ap.scrollUp(false);
1428       }
1429     }
1430     else
1431     {
1432       if (e.isShiftDown())
1433       {
1434         ap.scrollRight(false);
1435       }
1436       else
1437       {
1438         ap.scrollUp(true);
1439       }
1440     }
1441     // TODO Update tooltip for new position.
1442   }
1443
1444   /**
1445    * DOCUMENT ME!
1446    * 
1447    * @param evt
1448    *          DOCUMENT ME!
1449    */
1450   public void doMousePressedDefineMode(MouseEvent evt)
1451   {
1452     int res = findRes(evt);
1453     int seq = findSeq(evt);
1454     oldSeq = seq;
1455
1456     startWrapBlock = wrappedBlock;
1457
1458     if (av.wrapAlignment && seq > av.getAlignment().getHeight())
1459     {
1460       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
1461               .getString("label.cannot_edit_annotations_in_wrapped_view"),
1462               MessageManager.getString("label.wrapped_view_no_edit"),
1463               JOptionPane.WARNING_MESSAGE);
1464       return;
1465     }
1466
1467     if (seq < 0 || res < 0)
1468     {
1469       return;
1470     }
1471
1472     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
1473
1474     if ((sequence == null) || (res > sequence.getLength()))
1475     {
1476       return;
1477     }
1478
1479     stretchGroup = av.getSelectionGroup();
1480
1481     if (stretchGroup == null)
1482     {
1483       stretchGroup = av.getAlignment().findGroup(sequence);
1484
1485       if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
1486               && (res < stretchGroup.getEndRes()))
1487       {
1488         av.setSelectionGroup(stretchGroup);
1489       }
1490       else
1491       {
1492         stretchGroup = null;
1493       }
1494     }
1495     else if (!stretchGroup.getSequences(null).contains(sequence)
1496             || (stretchGroup.getStartRes() > res)
1497             || (stretchGroup.getEndRes() < res))
1498     {
1499       stretchGroup = null;
1500
1501       SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
1502
1503       if (allGroups != null)
1504       {
1505         for (int i = 0; i < allGroups.length; i++)
1506         {
1507           if ((allGroups[i].getStartRes() <= res)
1508                   && (allGroups[i].getEndRes() >= res))
1509           {
1510             stretchGroup = allGroups[i];
1511             break;
1512           }
1513         }
1514       }
1515
1516       av.setSelectionGroup(stretchGroup);
1517
1518     }
1519
1520     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
1521     {
1522       SequenceFeature[] allFeatures = findFeaturesAtRes(
1523               sequence.getDatasetSequence(), sequence.findPosition(res));
1524       Vector links = new Vector();
1525       for (int i = 0; i < allFeatures.length; i++)
1526       {
1527         if (allFeatures[i].links != null)
1528         {
1529           for (int j = 0; j < allFeatures[i].links.size(); j++)
1530           {
1531             links.addElement(allFeatures[i].links.elementAt(j));
1532           }
1533         }
1534       }
1535
1536       jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
1537       pop.show(this, evt.getX(), evt.getY());
1538       return;
1539     }
1540
1541     if (av.cursorMode)
1542     {
1543       seqCanvas.cursorX = findRes(evt);
1544       seqCanvas.cursorY = findSeq(evt);
1545       seqCanvas.repaint();
1546       return;
1547     }
1548
1549     if (stretchGroup == null)
1550     {
1551       // Only if left mouse button do we want to change group sizes
1552
1553       // define a new group here
1554       SequenceGroup sg = new SequenceGroup();
1555       sg.setStartRes(res);
1556       sg.setEndRes(res);
1557       sg.addSequence(sequence, false);
1558       av.setSelectionGroup(sg);
1559
1560       stretchGroup = sg;
1561
1562       if (av.getConservationSelected())
1563       {
1564         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1565                 "Background");
1566       }
1567
1568       if (av.getAbovePIDThreshold())
1569       {
1570         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1571                 "Background");
1572       }
1573       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1574       {
1575         // Edit end res position of selected group
1576         changeEndRes = true;
1577       }
1578       else if ((stretchGroup != null)
1579               && (stretchGroup.getStartRes() == res))
1580       {
1581         // Edit end res position of selected group
1582         changeStartRes = true;
1583       }
1584       stretchGroup.getWidth();
1585     }
1586
1587     seqCanvas.repaint();
1588   }
1589
1590   /**
1591    * DOCUMENT ME!
1592    * 
1593    * @param evt
1594    *          DOCUMENT ME!
1595    */
1596   public void doMouseReleasedDefineMode(MouseEvent evt)
1597   {
1598     if (stretchGroup == null)
1599     {
1600       return;
1601     }
1602
1603     stretchGroup.recalcConservation(); // always do this - annotation has own
1604                                        // state
1605     if (stretchGroup.cs != null)
1606     {
1607       stretchGroup.cs.alignmentChanged(stretchGroup,
1608               av.getHiddenRepSequences());
1609
1610       if (stretchGroup.cs.conservationApplied())
1611       {
1612         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1613                 stretchGroup.getName());
1614       }
1615       else
1616       {
1617         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1618                 stretchGroup.getName());
1619       }
1620     }
1621     PaintRefresher.Refresh(this, av.getSequenceSetId());
1622     ap.paintAlignment(true);
1623
1624     changeEndRes = false;
1625     changeStartRes = false;
1626     stretchGroup = null;
1627     av.sendSelection();
1628   }
1629
1630   /**
1631    * DOCUMENT ME!
1632    * 
1633    * @param evt
1634    *          DOCUMENT ME!
1635    */
1636   public void doMouseDraggedDefineMode(MouseEvent evt)
1637   {
1638     int res = findRes(evt);
1639     int y = findSeq(evt);
1640
1641     if (wrappedBlock != startWrapBlock)
1642     {
1643       return;
1644     }
1645
1646     if (stretchGroup == null)
1647     {
1648       return;
1649     }
1650
1651     if (res >= av.getAlignment().getWidth())
1652     {
1653       res = av.getAlignment().getWidth() - 1;
1654     }
1655
1656     if (stretchGroup.getEndRes() == res)
1657     {
1658       // Edit end res position of selected group
1659       changeEndRes = true;
1660     }
1661     else if (stretchGroup.getStartRes() == res)
1662     {
1663       // Edit start res position of selected group
1664       changeStartRes = true;
1665     }
1666
1667     if (res < av.getStartRes())
1668     {
1669       res = av.getStartRes();
1670     }
1671
1672     if (changeEndRes)
1673     {
1674       if (res > (stretchGroup.getStartRes() - 1))
1675       {
1676         stretchGroup.setEndRes(res);
1677       }
1678     }
1679     else if (changeStartRes)
1680     {
1681       if (res < (stretchGroup.getEndRes() + 1))
1682       {
1683         stretchGroup.setStartRes(res);
1684       }
1685     }
1686
1687     int dragDirection = 0;
1688
1689     if (y > oldSeq)
1690     {
1691       dragDirection = 1;
1692     }
1693     else if (y < oldSeq)
1694     {
1695       dragDirection = -1;
1696     }
1697
1698     while ((y != oldSeq) && (oldSeq > -1)
1699             && (y < av.getAlignment().getHeight()))
1700     {
1701       // This routine ensures we don't skip any sequences, as the
1702       // selection is quite slow.
1703       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1704
1705       oldSeq += dragDirection;
1706
1707       if (oldSeq < 0)
1708       {
1709         break;
1710       }
1711
1712       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1713
1714       if (stretchGroup.getSequences(null).contains(nextSeq))
1715       {
1716         stretchGroup.deleteSequence(seq, false);
1717       }
1718       else
1719       {
1720         if (seq != null)
1721         {
1722           stretchGroup.addSequence(seq, false);
1723         }
1724
1725         stretchGroup.addSequence(nextSeq, false);
1726       }
1727     }
1728
1729     if (oldSeq < 0)
1730     {
1731       oldSeq = -1;
1732     }
1733
1734     mouseDragging = true;
1735
1736     if (scrollThread != null)
1737     {
1738       scrollThread.setEvent(evt);
1739     }
1740
1741     seqCanvas.repaint();
1742   }
1743
1744   void scrollCanvas(MouseEvent evt)
1745   {
1746     if (evt == null)
1747     {
1748       if (scrollThread != null)
1749       {
1750         scrollThread.running = false;
1751         scrollThread = null;
1752       }
1753       mouseDragging = false;
1754     }
1755     else
1756     {
1757       if (scrollThread == null)
1758       {
1759         scrollThread = new ScrollThread();
1760       }
1761
1762       mouseDragging = true;
1763       scrollThread.setEvent(evt);
1764     }
1765
1766   }
1767
1768   // this class allows scrolling off the bottom of the visible alignment
1769   class ScrollThread extends Thread
1770   {
1771     MouseEvent evt;
1772
1773     boolean running = false;
1774
1775     public ScrollThread()
1776     {
1777       start();
1778     }
1779
1780     public void setEvent(MouseEvent e)
1781     {
1782       evt = e;
1783     }
1784
1785     public void stopScrolling()
1786     {
1787       running = false;
1788     }
1789
1790     @Override
1791     public void run()
1792     {
1793       running = true;
1794
1795       while (running)
1796       {
1797         if (evt != null)
1798         {
1799           if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
1800           {
1801             running = ap.scrollUp(true);
1802           }
1803
1804           if (mouseDragging && (evt.getY() >= getHeight())
1805                   && (av.getAlignment().getHeight() > av.getEndSeq()))
1806           {
1807             running = ap.scrollUp(false);
1808           }
1809
1810           if (mouseDragging && (evt.getX() < 0))
1811           {
1812             running = ap.scrollRight(false);
1813           }
1814           else if (mouseDragging && (evt.getX() >= getWidth()))
1815           {
1816             running = ap.scrollRight(true);
1817           }
1818         }
1819
1820         try
1821         {
1822           Thread.sleep(20);
1823         } catch (Exception ex)
1824         {
1825         }
1826       }
1827     }
1828   }
1829
1830   /**
1831    * modify current selection according to a received message.
1832    */
1833   @Override
1834   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1835           SelectionSource source)
1836   {
1837     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1838     // handles selection messages...
1839     // TODO: extend config options to allow user to control if selections may be
1840     // shared between viewports.
1841     if (av == source
1842             || !av.followSelection
1843             || (av.isSelectionGroupChanged(false) || av
1844                     .isColSelChanged(false))
1845             || (source instanceof AlignViewport && ((AlignViewport) source)
1846                     .getSequenceSetId().equals(av.getSequenceSetId())))
1847     {
1848       return;
1849     }
1850     // do we want to thread this ? (contention with seqsel and colsel locks, I
1851     // suspect)
1852     // rules are: colsel is copied if there is a real intersection between
1853     // sequence selection
1854     boolean repaint = false, copycolsel = true;
1855     // if (!av.isSelectionGroupChanged(false))
1856     {
1857       SequenceGroup sgroup = null;
1858       if (seqsel != null && seqsel.getSize() > 0)
1859       {
1860         if (av.getAlignment() == null)
1861         {
1862           jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
1863                   + av.getSequenceSetId() + " ViewId=" + av.getViewId()
1864                   + " 's alignment is NULL! returning immediatly.");
1865           return;
1866         }
1867         sgroup = seqsel.intersect(av.getAlignment(),
1868                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1869         if ((sgroup == null || sgroup.getSize() == 0)
1870                 || (colsel == null || colsel.size() == 0))
1871         {
1872           // don't copy columns if the region didn't intersect.
1873           copycolsel = false;
1874         }
1875       }
1876       if (sgroup != null && sgroup.getSize() > 0)
1877       {
1878         av.setSelectionGroup(sgroup);
1879       }
1880       else
1881       {
1882         av.setSelectionGroup(null);
1883       }
1884       av.isSelectionGroupChanged(true);
1885       repaint = true;
1886     }
1887     if (copycolsel)
1888     {
1889       // the current selection is unset or from a previous message
1890       // so import the new colsel.
1891       if (colsel == null || colsel.size() == 0)
1892       {
1893         if (av.getColumnSelection() != null)
1894         {
1895           av.getColumnSelection().clear();
1896           repaint = true;
1897         }
1898       }
1899       else
1900       {
1901         // TODO: shift colSel according to the intersecting sequences
1902         if (av.getColumnSelection() == null)
1903         {
1904           av.setColumnSelection(new ColumnSelection(colsel));
1905         }
1906         else
1907         {
1908           av.getColumnSelection().setElementsFrom(colsel);
1909         }
1910       }
1911       av.isColSelChanged(true);
1912       repaint = true;
1913     }
1914     if (copycolsel
1915             && av.hasHiddenColumns()
1916             && (av.getColumnSelection() == null || av.getColumnSelection()
1917                     .getHiddenColumns() == null))
1918     {
1919       System.err.println("Bad things");
1920     }
1921     if (repaint)
1922     {
1923       // probably finessing with multiple redraws here
1924       PaintRefresher.Refresh(this, av.getSequenceSetId());
1925       // ap.paintAlignment(false);
1926     }
1927   }
1928 }