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