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