Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.appletgui

File SeqPanel.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
60% of files have more coverage

Code metrics

424
680
49
2
1,970
1,520
343
0.5
13.88
24.5
7

Classes

Class Line # Actions
SeqPanel 61 664 325
0.00%
SeqPanel.ScrollThread 1708 16 18
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

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