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