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