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