(JAL-969) fix logic bug introduced during refactoring
[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.getAlignment().getHeight() - 1);
208     }
209     else
210     {
211       seq = Math.min((y / av.getCharHeight()) + av.getStartSeq(),
212               av.getAlignment().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.getColumnSelection().isVisible(seqCanvas.cursorX))
312     {
313       int original = seqCanvas.cursorX - dx;
314       int maxWidth = av.getAlignment().getWidth();
315
316       while (!av.getColumnSelection().isVisible(seqCanvas.cursorX)
317               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
318       {
319         seqCanvas.cursorX += dx;
320       }
321
322       if (seqCanvas.cursorX >= maxWidth
323               || !av.getColumnSelection().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.getAlignment().getWidth() - 1)
339     {
340       seqCanvas.cursorX = av.getAlignment().getWidth() - 1;
341     }
342
343     if (seqCanvas.cursorY < 0)
344     {
345       seqCanvas.cursorY = 0;
346     }
347     else if (seqCanvas.cursorY > av.getAlignment().getHeight() - 1)
348     {
349       seqCanvas.cursorY = av.getAlignment().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.getColumnSelection()
370                 .adjustForHiddenColumns(av.startRes))
371         {
372           if (!ap.scrollRight(false))
373           {
374             break;
375           }
376         }
377         while (seqCanvas.cursorX > av.getColumnSelection()
378                 .adjustForHiddenColumns(av.endRes))
379         {
380           if (!ap.scrollRight(true))
381           {
382             break;
383           }
384         }
385       }
386     }
387     setStatusMessage(av.getAlignment().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.getSelectionGroup();
401       // Find the top and bottom of this group
402       int min = av.getAlignment().getHeight(), max = 0;
403       for (int i = 0; i < sg.getSize(); i++)
404       {
405         int index = av.getAlignment().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.getAlignment().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.getAlignment().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.getAlignment().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.getAlignment().getSequenceAt(startseq);
1047
1048     // No group, but the sequence may represent a group
1049     if (!groupEditing && av.hasHiddenRows())
1050     {
1051       if (av.isHiddenRepSequence(seq))
1052       {
1053         sg = av.getRepresentedSequences(seq);
1054         groupEditing = true;
1055       }
1056     }
1057
1058     StringBuffer message = new StringBuffer();
1059     if (groupEditing)
1060     {
1061       message.append("Edit group:");
1062       if (editCommand == null)
1063       {
1064         editCommand = new EditCommand("Edit Group");
1065       }
1066     }
1067     else
1068     {
1069       message.append("Edit sequence: " + seq.getName());
1070       String label = seq.getName();
1071       if (label.length() > 10)
1072       {
1073         label = label.substring(0, 10);
1074       }
1075       if (editCommand == null)
1076       {
1077         editCommand = new EditCommand("Edit " + label);
1078       }
1079     }
1080
1081     if (insertGap)
1082     {
1083       message.append(" insert ");
1084     }
1085     else
1086     {
1087       message.append(" delete ");
1088     }
1089
1090     message.append(Math.abs(startres - lastres) + " gaps.");
1091     ap.alignFrame.statusBar.setText(message.toString());
1092
1093     // Are we editing within a selection group?
1094     if (groupEditing
1095             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
1096                     .contains(seq)))
1097     {
1098       fixedColumns = true;
1099
1100       // sg might be null as the user may only see 1 sequence,
1101       // but the sequence represents a group
1102       if (sg == null)
1103       {
1104         if (!av.isHiddenRepSequence(seq))
1105         {
1106           endEditing();
1107           return;
1108         }
1109         sg = av.getRepresentedSequences(seq);
1110       }
1111
1112       fixedLeft = sg.getStartRes();
1113       fixedRight = sg.getEndRes();
1114
1115       if ((startres < fixedLeft && lastres >= fixedLeft)
1116               || (startres >= fixedLeft && lastres < fixedLeft)
1117               || (startres > fixedRight && lastres <= fixedRight)
1118               || (startres <= fixedRight && lastres > fixedRight))
1119       {
1120         endEditing();
1121         return;
1122       }
1123
1124       if (fixedLeft > startres)
1125       {
1126         fixedRight = fixedLeft - 1;
1127         fixedLeft = 0;
1128       }
1129       else if (fixedRight < startres)
1130       {
1131         fixedLeft = fixedRight;
1132         fixedRight = -1;
1133       }
1134     }
1135
1136     if (av.hasHiddenColumns())
1137     {
1138       fixedColumns = true;
1139       int y1 = av.getColumnSelection().getHiddenBoundaryLeft(startres);
1140       int y2 = av.getColumnSelection().getHiddenBoundaryRight(startres);
1141
1142       if ((insertGap && startres > y1 && lastres < y1)
1143               || (!insertGap && startres < y2 && lastres > y2))
1144       {
1145         endEditing();
1146         return;
1147       }
1148
1149       // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
1150       // Selection spans a hidden region
1151       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
1152       {
1153         if (startres >= y2)
1154         {
1155           fixedLeft = y2;
1156         }
1157         else
1158         {
1159           fixedRight = y2 - 1;
1160         }
1161       }
1162     }
1163
1164     if (groupEditing)
1165     {
1166       Vector vseqs = sg.getSequences(av.getHiddenRepSequences());
1167       int g, groupSize = vseqs.size();
1168       SequenceI[] groupSeqs = new SequenceI[groupSize];
1169       for (g = 0; g < groupSeqs.length; g++)
1170       {
1171         groupSeqs[g] = (SequenceI) vseqs.elementAt(g);
1172       }
1173
1174       // drag to right
1175       if (insertGap)
1176       {
1177         // If the user has selected the whole sequence, and is dragging to
1178         // the right, we can still extend the alignment and selectionGroup
1179         if (sg.getStartRes() == 0 && sg.getEndRes() == fixedRight
1180                 && sg.getEndRes() == av.getAlignment().getWidth() - 1)
1181         {
1182           sg.setEndRes(av.getAlignment().getWidth() + startres - lastres);
1183           fixedRight = sg.getEndRes();
1184         }
1185
1186         // Is it valid with fixed columns??
1187         // Find the next gap before the end
1188         // of the visible region boundary
1189         boolean blank = false;
1190         for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
1191         {
1192           blank = true;
1193
1194           for (g = 0; g < groupSize; g++)
1195           {
1196             for (int j = 0; j < startres - lastres; j++)
1197             {
1198               if (!jalview.util.Comparison.isGap(groupSeqs[g]
1199                       .getCharAt(fixedRight - j)))
1200               {
1201                 blank = false;
1202                 break;
1203               }
1204             }
1205           }
1206           if (blank)
1207           {
1208             break;
1209           }
1210         }
1211
1212         if (!blank)
1213         {
1214           if (sg.getSize() == av.getAlignment().getHeight())
1215           {
1216             if ((av.hasHiddenColumns() && startres < av.getColumnSelection()
1217                     .getHiddenBoundaryRight(startres)))
1218             {
1219               endEditing();
1220               return;
1221             }
1222
1223             int alWidth = av.getAlignment().getWidth();
1224             if (av.hasHiddenRows())
1225             {
1226               int hwidth = av.getAlignment().getHiddenSequences().getWidth();
1227               if (hwidth > alWidth)
1228               {
1229                 alWidth = hwidth;
1230               }
1231             }
1232             // We can still insert gaps if the selectionGroup
1233             // contains all the sequences
1234             sg.setEndRes(sg.getEndRes() + startres - lastres);
1235             fixedRight = alWidth + startres - lastres;
1236           }
1237           else
1238           {
1239             endEditing();
1240             return;
1241           }
1242         }
1243       }
1244
1245       // drag to left
1246       else if (!insertGap)
1247       {
1248         // / Are we able to delete?
1249         // ie are all columns blank?
1250
1251         for (g = 0; g < groupSize; g++)
1252         {
1253           for (int j = startres; j < lastres; j++)
1254           {
1255             if (groupSeqs[g].getLength() <= j)
1256             {
1257               continue;
1258             }
1259
1260             if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
1261             {
1262               // Not a gap, block edit not valid
1263               endEditing();
1264               return;
1265             }
1266           }
1267         }
1268       }
1269
1270       if (insertGap)
1271       {
1272         // dragging to the right
1273         if (fixedColumns && fixedRight != -1)
1274         {
1275           for (int j = lastres; j < startres; j++)
1276           {
1277             insertChar(j, groupSeqs, fixedRight);
1278           }
1279         }
1280         else
1281         {
1282           editCommand.appendEdit(EditCommand.INSERT_GAP, groupSeqs,
1283                   startres, startres - lastres, av.getAlignment(), true);
1284         }
1285       }
1286       else
1287       {
1288         // dragging to the left
1289         if (fixedColumns && fixedRight != -1)
1290         {
1291           for (int j = lastres; j > startres; j--)
1292           {
1293             deleteChar(startres, groupSeqs, fixedRight);
1294           }
1295         }
1296         else
1297         {
1298           editCommand.appendEdit(EditCommand.DELETE_GAP, groupSeqs,
1299                   startres, lastres - startres, av.getAlignment(), true);
1300         }
1301
1302       }
1303     }
1304     else
1305     // ///Editing a single sequence///////////
1306     {
1307       if (insertGap)
1308       {
1309         // dragging to the right
1310         if (fixedColumns && fixedRight != -1)
1311         {
1312           for (int j = lastres; j < startres; j++)
1313           {
1314             insertChar(j, new SequenceI[]
1315             { seq }, fixedRight);
1316           }
1317         }
1318         else
1319         {
1320           editCommand.appendEdit(EditCommand.INSERT_GAP, new SequenceI[]
1321           { seq }, lastres, startres - lastres, av.getAlignment(), true);
1322          }
1323       }
1324       else
1325       {
1326         if(!editSeq){
1327         // dragging to the left
1328         if (fixedColumns && fixedRight != -1)
1329         {
1330           for (int j = lastres; j > startres; j--)
1331           {
1332             if (!jalview.util.Comparison.isGap(seq.getCharAt(startres)))
1333             {
1334               endEditing();
1335               break;
1336             }
1337             deleteChar(startres, new SequenceI[]
1338             { seq }, fixedRight);
1339           }
1340         }
1341         else
1342         {
1343           // could be a keyboard edit trying to delete none gaps
1344           int max = 0;
1345           for (int m = startres; m < lastres; m++)
1346           {
1347             if (!jalview.util.Comparison.isGap(seq.getCharAt(m)))
1348             {
1349               break;
1350             }
1351             max++;
1352           }
1353
1354           if (max > 0)
1355           {
1356             editCommand.appendEdit(EditCommand.DELETE_GAP, new SequenceI[]
1357             { seq }, startres, max, av.getAlignment(), true);
1358           }
1359         }
1360         }else{//insertGap==false AND editSeq==TRUE;
1361                 if (fixedColumns && fixedRight != -1)
1362             {
1363               for (int j = lastres; j < startres; j++)
1364               {
1365                 insertChar(j, new SequenceI[]
1366                 { seq }, fixedRight);
1367               }
1368               }
1369             else
1370             {
1371               editCommand.appendEdit(EditCommand.INSERT_NUC, new SequenceI[]
1372               { seq }, lastres, startres - lastres, av.getAlignment(), true);
1373              }
1374         }
1375       }
1376     }
1377
1378     lastres = startres;
1379     seqCanvas.repaint();
1380   }
1381
1382   void insertChar(int j, SequenceI[] seq, int fixedColumn)
1383   {
1384     int blankColumn = fixedColumn;
1385     for (int s = 0; s < seq.length; s++)
1386     {
1387       // Find the next gap before the end of the visible region boundary
1388       // If lastCol > j, theres a boundary after the gap insertion
1389
1390       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
1391       {
1392         if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
1393         {
1394           // Theres a space, so break and insert the gap
1395           break;
1396         }
1397       }
1398
1399       if (blankColumn <= j)
1400       {
1401         blankColumn = fixedColumn;
1402         endEditing();
1403         return;
1404       }
1405     }
1406
1407     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, blankColumn, 1,
1408             av.getAlignment(), true);
1409
1410     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, j, 1, av.getAlignment(),
1411             true);
1412
1413   }
1414
1415   void deleteChar(int j, SequenceI[] seq, int fixedColumn)
1416   {
1417
1418     editCommand.appendEdit(EditCommand.DELETE_GAP, seq, j, 1, av.getAlignment(),
1419             true);
1420
1421     editCommand.appendEdit(EditCommand.INSERT_GAP, seq, fixedColumn, 1,
1422             av.getAlignment(), true);
1423   }
1424
1425   /**
1426    * DOCUMENT ME!
1427    * 
1428    * @param e
1429    *          DOCUMENT ME!
1430    */
1431   public void mouseEntered(MouseEvent e)
1432   {
1433     if (oldSeq < 0)
1434     {
1435       oldSeq = 0;
1436     }
1437
1438     if (scrollThread != null)
1439     {
1440       scrollThread.running = false;
1441       scrollThread = null;
1442     }
1443   }
1444
1445   /**
1446    * DOCUMENT ME!
1447    * 
1448    * @param e
1449    *          DOCUMENT ME!
1450    */
1451   public void mouseExited(MouseEvent e)
1452   {
1453     if (av.getWrapAlignment())
1454     {
1455       return;
1456     }
1457
1458     if (mouseDragging)
1459     {
1460       scrollThread = new ScrollThread();
1461     }
1462   }
1463
1464   public void mouseClicked(MouseEvent evt)
1465   {
1466     SequenceGroup sg = null;
1467     SequenceI sequence = av.getAlignment().getSequenceAt(findSeq(evt));
1468     if (evt.getClickCount() > 1)
1469     {
1470       sg = av.getSelectionGroup();
1471       if (sg != null && sg.getSize() == 1
1472               && sg.getEndRes() - sg.getStartRes() < 2)
1473       {
1474         av.setSelectionGroup(null);
1475       }
1476
1477       SequenceFeature[] features = findFeaturesAtRes(
1478               sequence.getDatasetSequence(),
1479               sequence.findPosition(findRes(evt)));
1480
1481       if (features != null && features.length > 0)
1482       {
1483         SearchResults highlight = new SearchResults();
1484         highlight.addResult(sequence, features[0].getBegin(),
1485                 features[0].getEnd());
1486         seqCanvas.highlightSearchResults(highlight);
1487       }
1488       if (features != null && features.length > 0)
1489       {
1490         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
1491         { sequence }, features, false, ap);
1492
1493         seqCanvas.highlightSearchResults(null);
1494       }
1495     }
1496   }
1497
1498   public void mouseWheelMoved(MouseWheelEvent e)
1499   {
1500     e.consume();
1501     if (e.getWheelRotation() > 0)
1502     {
1503       ap.scrollUp(false);
1504     }
1505     else
1506     {
1507       ap.scrollUp(true);
1508     }
1509     // TODO Update tooltip for new position.
1510   }
1511
1512   /**
1513    * DOCUMENT ME!
1514    * 
1515    * @param evt
1516    *          DOCUMENT ME!
1517    */
1518   public void doMousePressedDefineMode(MouseEvent evt)
1519   {
1520     int res = findRes(evt);
1521     int seq = findSeq(evt);
1522     oldSeq = seq;
1523
1524     startWrapBlock = wrappedBlock;
1525
1526     if (av.wrapAlignment && seq > av.getAlignment().getHeight())
1527     {
1528       JOptionPane.showInternalMessageDialog(Desktop.desktop,
1529               "Cannot edit annotations in wrapped view.",
1530               "Wrapped view - no edit", JOptionPane.WARNING_MESSAGE);
1531       return;
1532     }
1533
1534     if (seq < 0 || res < 0)
1535     {
1536       return;
1537     }
1538
1539     SequenceI sequence = (Sequence) av.getAlignment().getSequenceAt(seq);
1540
1541     if ((sequence == null) || (res > sequence.getLength()))
1542     {
1543       return;
1544     }
1545
1546     stretchGroup = av.getSelectionGroup();
1547
1548     if (stretchGroup == null)
1549     {
1550       stretchGroup = av.getAlignment().findGroup(sequence);
1551
1552       if ((stretchGroup != null) && (res > stretchGroup.getStartRes())
1553               && (res < stretchGroup.getEndRes()))
1554       {
1555         av.setSelectionGroup(stretchGroup);
1556       }
1557       else
1558       {
1559         stretchGroup = null;
1560       }
1561     }
1562     else if (!stretchGroup.getSequences(null).contains(sequence)
1563             || (stretchGroup.getStartRes() > res)
1564             || (stretchGroup.getEndRes() < res))
1565     {
1566       stretchGroup = null;
1567
1568       SequenceGroup[] allGroups = av.getAlignment().findAllGroups(sequence);
1569
1570       if (allGroups != null)
1571       {
1572         for (int i = 0; i < allGroups.length; i++)
1573         {
1574           if ((allGroups[i].getStartRes() <= res)
1575                   && (allGroups[i].getEndRes() >= res))
1576           {
1577             stretchGroup = allGroups[i];
1578             break;
1579           }
1580         }
1581       }
1582
1583       av.setSelectionGroup(stretchGroup);
1584
1585     }
1586
1587     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
1588     {
1589       SequenceFeature[] allFeatures = findFeaturesAtRes(
1590               sequence.getDatasetSequence(), sequence.findPosition(res));
1591       Vector links = new Vector();
1592       for (int i = 0; i < allFeatures.length; i++)
1593       {
1594         if (allFeatures[i].links != null)
1595         {
1596           for (int j = 0; j < allFeatures[i].links.size(); j++)
1597           {
1598             links.addElement(allFeatures[i].links.elementAt(j));
1599           }
1600         }
1601       }
1602
1603       jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
1604       pop.show(this, evt.getX(), evt.getY());
1605       return;
1606     }
1607
1608     if (av.cursorMode)
1609     {
1610       seqCanvas.cursorX = findRes(evt);
1611       seqCanvas.cursorY = findSeq(evt);
1612       seqCanvas.repaint();
1613       return;
1614     }
1615
1616     if (stretchGroup == null)
1617     {
1618       // Only if left mouse button do we want to change group sizes
1619
1620       // define a new group here
1621       SequenceGroup sg = new SequenceGroup();
1622       sg.setStartRes(res);
1623       sg.setEndRes(res);
1624       sg.addSequence(sequence, false);
1625       av.setSelectionGroup(sg);
1626
1627       stretchGroup = sg;
1628
1629       if (av.getConservationSelected())
1630       {
1631         SliderPanel.setConservationSlider(ap, av.getGlobalColourScheme(),
1632                 "Background");
1633       }
1634
1635       if (av.getAbovePIDThreshold())
1636       {
1637         SliderPanel.setPIDSliderSource(ap, av.getGlobalColourScheme(),
1638                 "Background");
1639       }
1640       if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
1641       {
1642         // Edit end res position of selected group
1643         changeEndRes = true;
1644       }
1645       else if ((stretchGroup != null)
1646               && (stretchGroup.getStartRes() == res))
1647       {
1648         // Edit end res position of selected group
1649         changeStartRes = true;
1650       }
1651       stretchGroup.getWidth();
1652     }
1653
1654     seqCanvas.repaint();
1655   }
1656
1657   /**
1658    * DOCUMENT ME!
1659    * 
1660    * @param evt
1661    *          DOCUMENT ME!
1662    */
1663   public void doMouseReleasedDefineMode(MouseEvent evt)
1664   {
1665     if (stretchGroup == null)
1666     {
1667       return;
1668     }
1669
1670     stretchGroup.recalcConservation(); // always do this - annotation has own
1671                                        // state
1672     if (stretchGroup.cs != null)
1673     {
1674       if (stretchGroup.cs instanceof ClustalxColourScheme)
1675       {
1676         ((ClustalxColourScheme) stretchGroup.cs).resetClustalX(
1677                 stretchGroup.getSequences(av.getHiddenRepSequences()),
1678                 stretchGroup.getWidth());
1679       }
1680
1681       if (stretchGroup.cs.conservationApplied())
1682       {
1683         SliderPanel.setConservationSlider(ap, stretchGroup.cs,
1684                 stretchGroup.getName());
1685       }
1686       else
1687       {
1688         SliderPanel.setPIDSliderSource(ap, stretchGroup.cs,
1689                 stretchGroup.getName());
1690       }
1691     }
1692     PaintRefresher.Refresh(this, av.getSequenceSetId());
1693     ap.paintAlignment(true);
1694
1695     changeEndRes = false;
1696     changeStartRes = false;
1697     stretchGroup = null;
1698     av.sendSelection();
1699   }
1700
1701   /**
1702    * DOCUMENT ME!
1703    * 
1704    * @param evt
1705    *          DOCUMENT ME!
1706    */
1707   public void doMouseDraggedDefineMode(MouseEvent evt)
1708   {
1709     int res = findRes(evt);
1710     int y = findSeq(evt);
1711
1712     if (wrappedBlock != startWrapBlock)
1713     {
1714       return;
1715     }
1716
1717     if (stretchGroup == null)
1718     {
1719       return;
1720     }
1721
1722     if (res >= av.getAlignment().getWidth())
1723     {
1724       res = av.getAlignment().getWidth() - 1;
1725     }
1726
1727     if (stretchGroup.getEndRes() == res)
1728     {
1729       // Edit end res position of selected group
1730       changeEndRes = true;
1731     }
1732     else if (stretchGroup.getStartRes() == res)
1733     {
1734       // Edit start res position of selected group
1735       changeStartRes = true;
1736     }
1737
1738     if (res < av.getStartRes())
1739     {
1740       res = av.getStartRes();
1741     }
1742
1743     if (changeEndRes)
1744     {
1745       if (res > (stretchGroup.getStartRes() - 1))
1746       {
1747         stretchGroup.setEndRes(res);
1748       }
1749     }
1750     else if (changeStartRes)
1751     {
1752       if (res < (stretchGroup.getEndRes() + 1))
1753       {
1754         stretchGroup.setStartRes(res);
1755       }
1756     }
1757
1758     int dragDirection = 0;
1759
1760     if (y > oldSeq)
1761     {
1762       dragDirection = 1;
1763     }
1764     else if (y < oldSeq)
1765     {
1766       dragDirection = -1;
1767     }
1768
1769     while ((y != oldSeq) && (oldSeq > -1) && (y < av.getAlignment().getHeight()))
1770     {
1771       // This routine ensures we don't skip any sequences, as the
1772       // selection is quite slow.
1773       Sequence seq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1774
1775       oldSeq += dragDirection;
1776
1777       if (oldSeq < 0)
1778       {
1779         break;
1780       }
1781
1782       Sequence nextSeq = (Sequence) av.getAlignment().getSequenceAt(oldSeq);
1783
1784       if (stretchGroup.getSequences(null).contains(nextSeq))
1785       {
1786         stretchGroup.deleteSequence(seq, false);
1787       }
1788       else
1789       {
1790         if (seq != null)
1791         {
1792           stretchGroup.addSequence(seq, false);
1793         }
1794
1795         stretchGroup.addSequence(nextSeq, false);
1796       }
1797     }
1798
1799     if (oldSeq < 0)
1800     {
1801       oldSeq = -1;
1802     }
1803
1804     mouseDragging = true;
1805
1806     if (scrollThread != null)
1807     {
1808       scrollThread.setEvent(evt);
1809     }
1810
1811     seqCanvas.repaint();
1812   }
1813
1814   void scrollCanvas(MouseEvent evt)
1815   {
1816     if (evt == null)
1817     {
1818       if (scrollThread != null)
1819       {
1820         scrollThread.running = false;
1821         scrollThread = null;
1822       }
1823       mouseDragging = false;
1824     }
1825     else
1826     {
1827       if (scrollThread == null)
1828       {
1829         scrollThread = new ScrollThread();
1830       }
1831
1832       mouseDragging = true;
1833       scrollThread.setEvent(evt);
1834     }
1835
1836   }
1837
1838   // this class allows scrolling off the bottom of the visible alignment
1839   class ScrollThread extends Thread
1840   {
1841     MouseEvent evt;
1842
1843     boolean running = false;
1844
1845     public ScrollThread()
1846     {
1847       start();
1848     }
1849
1850     public void setEvent(MouseEvent e)
1851     {
1852       evt = e;
1853     }
1854
1855     public void stopScrolling()
1856     {
1857       running = false;
1858     }
1859
1860     public void run()
1861     {
1862       running = true;
1863
1864       while (running)
1865       {
1866         if (evt != null)
1867         {
1868           if (mouseDragging && (evt.getY() < 0) && (av.getStartSeq() > 0))
1869           {
1870             running = ap.scrollUp(true);
1871           }
1872
1873           if (mouseDragging && (evt.getY() >= getHeight())
1874                   && (av.getAlignment().getHeight() > av.getEndSeq()))
1875           {
1876             running = ap.scrollUp(false);
1877           }
1878
1879           if (mouseDragging && (evt.getX() < 0))
1880           {
1881             running = ap.scrollRight(false);
1882           }
1883           else if (mouseDragging && (evt.getX() >= getWidth()))
1884           {
1885             running = ap.scrollRight(true);
1886           }
1887         }
1888
1889         try
1890         {
1891           Thread.sleep(20);
1892         } catch (Exception ex)
1893         {
1894         }
1895       }
1896     }
1897   }
1898
1899   /**
1900    * modify current selection according to a received message.
1901    */
1902   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
1903           SelectionSource source)
1904   {
1905     // TODO: fix this hack - source of messages is align viewport, but SeqPanel
1906     // handles selection messages...
1907     // TODO: extend config options to allow user to control if selections may be
1908     // shared between viewports.
1909     if (av == source
1910             || !av.followSelection
1911             || (av.isSelectionGroupChanged(false) || av.isColSelChanged(false))
1912             || (source instanceof AlignViewport && ((AlignViewport) source)
1913                     .getSequenceSetId().equals(av.getSequenceSetId())))
1914     {
1915       return;
1916     }
1917     // do we want to thread this ? (contention with seqsel and colsel locks, I
1918     // suspect)
1919     // rules are: colsel is copied if there is a real intersection between
1920     // sequence selection
1921     boolean repaint = false, copycolsel = true;
1922     // if (!av.isSelectionGroupChanged(false))
1923     {
1924       SequenceGroup sgroup = null;
1925       if (seqsel != null && seqsel.getSize()>0)
1926       {
1927         if (av.getAlignment() == null)
1928         {
1929           jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
1930                   + av.getSequenceSetId() + " ViewId=" + av.getViewId()
1931                   + " 's alignment is NULL! returning immediatly.");
1932           return;
1933         }
1934         sgroup = seqsel.intersect(av.getAlignment(),
1935                 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1936         if ((sgroup == null || sgroup.getSize() == 0)
1937                 || (colsel == null || colsel.size() == 0))
1938         {
1939           // don't copy columns if the region didn't intersect.
1940           copycolsel = false;
1941         }
1942       }
1943       if (sgroup != null && sgroup.getSize() > 0)
1944       {
1945         av.setSelectionGroup(sgroup);
1946       }
1947       else
1948       {
1949         av.setSelectionGroup(null);
1950       }
1951       av.isSelectionGroupChanged(true);
1952       repaint = true;
1953     }
1954     if (copycolsel)
1955     {
1956       // the current selection is unset or from a previous message
1957       // so import the new colsel.
1958       if (colsel == null || colsel.size() == 0)
1959       {
1960         if (av.getColumnSelection() != null)
1961         {
1962           av.getColumnSelection().clear();
1963           repaint=true;
1964         }
1965       }
1966       else
1967       {
1968         // TODO: shift colSel according to the intersecting sequences
1969         if (av.getColumnSelection() == null)
1970         {
1971           av.setColumnSelection(new ColumnSelection(colsel));
1972         }
1973         else
1974         {
1975           av.getColumnSelection().setElementsFrom(colsel);
1976         }
1977       }
1978       av.isColSelChanged(true);
1979       repaint = true;
1980     }
1981     if (copycolsel && av.hasHiddenColumns()
1982             && (av.getColumnSelection() == null || av.getColumnSelection().getHiddenColumns() == null))
1983     {
1984       System.err.println("Bad things");
1985     }
1986     if (repaint)
1987     {
1988       // probably finessing with multiple redraws here
1989       PaintRefresher.Refresh(this, av.getSequenceSetId());
1990       // ap.paintAlignment(false);
1991     }
1992   }
1993 }