Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.appletgui

File SeqPanel.java

 

Coverage histogram

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

Code metrics

424
680
49
2
1,968
1,518
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.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 System.out.println("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, 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() && startres < av.getAlignment()
1199    .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 System.out.println("Selection message: alignviewport av SeqSetId="
1820    + av.getSequenceSetId() + " ViewId=" + av.getViewId()
1821    + " 's alignment is NULL! returning immediatly.");
1822  0 return;
1823    }
1824  0 sgroup = seqsel.intersect(av.getAlignment(),
1825  0 (av.hasHiddenRows()) ? av.getHiddenRepSequences() : null);
1826  0 if ((sgroup != null && sgroup.getSize() > 0))
1827    {
1828  0 copycolsel = true;
1829    }
1830    }
1831  0 if (sgroup != null && sgroup.getSize() > 0)
1832    {
1833  0 av.setSelectionGroup(sgroup);
1834    }
1835    else
1836    {
1837  0 av.setSelectionGroup(null);
1838    }
1839  0 repaint = av.isSelectionGroupChanged(true);
1840    }
1841  0 if (copycolsel && (av.getColumnSelection() == null
1842    || !av.isColSelChanged(true)))
1843    {
1844    // the current selection is unset or from a previous message
1845    // so import the new colsel.
1846  0 if (colsel == null || colsel.isEmpty())
1847    {
1848  0 if (av.getColumnSelection() != null)
1849    {
1850  0 av.getColumnSelection().clear();
1851    }
1852    }
1853    else
1854    {
1855    // TODO: shift colSel according to the intersecting sequences
1856  0 if (av.getColumnSelection() == null)
1857    {
1858  0 av.setColumnSelection(new ColumnSelection(colsel));
1859    }
1860    else
1861    {
1862  0 av.getColumnSelection().setElementsFrom(colsel,
1863    av.getAlignment().getHiddenColumns());
1864    }
1865    }
1866  0 repaint |= av.isColSelChanged(true);
1867    }
1868  0 if (copycolsel && av.hasHiddenColumns()
1869    && (av.getColumnSelection() == null))
1870    {
1871  0 System.err.println("Bad things");
1872    }
1873  0 if (repaint)
1874    {
1875  0 ap.scalePanelHolder.repaint();
1876  0 ap.repaint();
1877    }
1878    }
1879   
1880    /**
1881    * scroll to the given row/column - or nearest visible location
1882    *
1883    * @param row
1884    * @param column
1885    */
 
1886  0 toggle public void scrollTo(int row, int column)
1887    {
1888   
1889  0 row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
1890  0 column = column < 0 ? ap.av.getRanges().getStartRes() : column;
1891  0 ap.scrollTo(column, column, row, true, true);
1892    }
1893   
1894    /**
1895    * scroll to the given row - or nearest visible location
1896    *
1897    * @param row
1898    */
 
1899  0 toggle public void scrollToRow(int row)
1900    {
1901   
1902  0 row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
1903  0 ap.scrollTo(ap.av.getRanges().getStartRes(),
1904    ap.av.getRanges().getStartRes(), row, true, true);
1905    }
1906   
1907    /**
1908    * scroll to the given column - or nearest visible location
1909    *
1910    * @param column
1911    */
 
1912  0 toggle public void scrollToColumn(int column)
1913    {
1914   
1915  0 column = column < 0 ? ap.av.getRanges().getStartRes() : column;
1916  0 ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true,
1917    true);
1918    }
1919   
1920    /**
1921    * If this panel is a cdna/protein translation view of the selection source,
1922    * tries to map the source selection to a local one, and returns true. Else
1923    * returns false.
1924    *
1925    * @param seqsel
1926    * @param colsel
1927    * @param source
1928    */
 
1929  0 toggle protected boolean selectionFromTranslation(SequenceGroup seqsel,
1930    ColumnSelection colsel, HiddenColumns hidden,
1931    SelectionSource source)
1932    {
1933  0 if (!(source instanceof AlignViewportI))
1934    {
1935  0 return false;
1936    }
1937  0 final AlignViewportI sourceAv = (AlignViewportI) source;
1938  0 if (sourceAv.getCodingComplement() != av
1939    && av.getCodingComplement() != sourceAv)
1940    {
1941  0 return false;
1942    }
1943   
1944    /*
1945    * Map sequence selection
1946    */
1947  0 SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
1948  0 av.setSelectionGroup(sg);
1949  0 av.isSelectionGroupChanged(true);
1950   
1951    /*
1952    * Map column selection
1953    */
1954    // ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, sourceAv,
1955    // av);
1956  0 ColumnSelection cs = new ColumnSelection();
1957  0 HiddenColumns hs = new HiddenColumns();
1958  0 MappingUtils.mapColumnSelection(colsel, hidden, sourceAv, av, cs, hs);
1959  0 av.setColumnSelection(cs);
1960  0 av.getAlignment().setHiddenColumns(hs);
1961   
1962  0 ap.scalePanelHolder.repaint();
1963  0 ap.repaint();
1964   
1965  0 return true;
1966    }
1967   
1968    }