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