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