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