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