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