1. Project Clover database Fri Dec 6 2024 13:47:14 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,971
1,521
343
0.5
13.88
24.5
7

Classes

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