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