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