Merge branch 'features/r2_11_2_alphafold/JAL-2349_JAL-3855' into develop
[jalview.git] / test / jalview / gui / SeqPanelTest.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertNull;
25 import static org.testng.Assert.assertTrue;
26
27 import java.awt.EventQueue;
28 import java.awt.FontMetrics;
29 import java.awt.event.MouseEvent;
30 import java.lang.reflect.InvocationTargetException;
31
32 import javax.swing.JLabel;
33
34 import org.testng.annotations.AfterMethod;
35 import org.testng.annotations.BeforeClass;
36 import org.testng.annotations.Test;
37
38 import jalview.api.AlignViewportI;
39 import jalview.bin.Cache;
40 import jalview.bin.Jalview;
41 import jalview.commands.EditCommand;
42 import jalview.commands.EditCommand.Action;
43 import jalview.commands.EditCommand.Edit;
44 import jalview.datamodel.Alignment;
45 import jalview.datamodel.AlignmentAnnotation;
46 import jalview.datamodel.AlignmentI;
47 import jalview.datamodel.SearchResults;
48 import jalview.datamodel.SearchResultsI;
49 import jalview.datamodel.Sequence;
50 import jalview.datamodel.SequenceI;
51 import jalview.gui.SeqPanel.MousePos;
52 import jalview.io.DataSourceType;
53 import jalview.io.FileLoader;
54 import jalview.util.MessageManager;
55 import jalview.viewmodel.ViewportRanges;
56 import junit.extensions.PA;
57
58 public class SeqPanelTest
59 {
60   AlignFrame af;
61
62   @BeforeClass(alwaysRun = true)
63   public void setUpJvOptionPane()
64   {
65     JvOptionPane.setInteractiveMode(false);
66     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
67   }
68
69   @Test(groups = "Functional")
70   public void testSetStatusReturnsNearestResiduePosition()
71   {
72     SequenceI seq1 = new Sequence("Seq1", "AACDE");
73     SequenceI seq2 = new Sequence("Seq2", "AA--E");
74     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
75     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
76             al.getHeight());
77     AlignmentI visAl = alignFrame.getViewport().getAlignment();
78
79     // Test either side of gap
80     assertEquals(alignFrame.alignPanel.getSeqPanel()
81             .setStatusMessage(visAl.getSequenceAt(1), 1, 1), 2);
82     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
83             "Sequence 2 ID: Seq2 Residue: ALA (2)");
84     assertEquals(alignFrame.alignPanel.getSeqPanel()
85             .setStatusMessage(visAl.getSequenceAt(1), 4, 1), 3);
86     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
87             "Sequence 2 ID: Seq2 Residue: GLU (3)");
88     // no status message at a gap, returns next residue position to the right
89     assertEquals(alignFrame.alignPanel.getSeqPanel()
90             .setStatusMessage(visAl.getSequenceAt(1), 2, 1), 3);
91     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
92             "Sequence 2 ID: Seq2");
93     assertEquals(alignFrame.alignPanel.getSeqPanel()
94             .setStatusMessage(visAl.getSequenceAt(1), 3, 1), 3);
95     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
96             "Sequence 2 ID: Seq2");
97   }
98
99   @Test(groups = "Functional")
100   public void testAmbiguousAminoAcidGetsStatusMessage()
101   {
102     SequenceI seq1 = new Sequence("Seq1", "ABCDE");
103     SequenceI seq2 = new Sequence("Seq2", "AB--E");
104     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
105     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
106             al.getHeight());
107     AlignmentI visAl = alignFrame.getViewport().getAlignment();
108
109     assertEquals(alignFrame.alignPanel.getSeqPanel()
110             .setStatusMessage(visAl.getSequenceAt(1), 1, 1), 2);
111     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
112             "Sequence 2 ID: Seq2 Residue: B (2)");
113   }
114
115   @Test(groups = "Functional")
116   public void testGetEditStatusMessage()
117   {
118     assertNull(SeqPanel.getEditStatusMessage(null));
119
120     EditCommand edit = new EditCommand(); // empty
121     assertNull(SeqPanel.getEditStatusMessage(edit));
122
123     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
124
125     // 1 gap
126     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-'));
127     String expected = MessageManager.formatMessage("label.insert_gap", "1");
128     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
129
130     // 3 more gaps makes +4
131     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 3, '-'));
132     expected = MessageManager.formatMessage("label.insert_gaps", "4");
133     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
134
135     // 2 deletes makes + 2
136     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
137     expected = MessageManager.formatMessage("label.insert_gaps", "2");
138     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
139
140     // 2 more deletes makes 0 - no text
141     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
142     assertNull(SeqPanel.getEditStatusMessage(edit));
143
144     // 1 more delete makes 1 delete
145     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
146     expected = MessageManager.formatMessage("label.delete_gap", "1");
147     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
148
149     // 1 more delete makes 2 deletes
150     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
151     expected = MessageManager.formatMessage("label.delete_gaps", "2");
152     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
153   }
154
155   /**
156    * Tests that simulate 'locked editing', where an inserted gap is balanced by
157    * a gap deletion in the selection group, and vice versa
158    */
159   @Test(groups = "Functional")
160   public void testGetEditStatusMessage_lockedEditing()
161   {
162     EditCommand edit = new EditCommand(); // empty
163     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
164
165     // 1 gap inserted, balanced by 1 delete
166     Edit e1 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-');
167     edit.addEdit(e1);
168     Edit e2 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 1, '-');
169     e2.setSystemGenerated(true);
170     edit.addEdit(e2);
171     String expected = MessageManager.formatMessage("label.insert_gap", "1");
172     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
173
174     // 2 more gaps makes +3
175     Edit e3 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
176     edit.addEdit(e3);
177     Edit e4 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 2, '-');
178     e4.setSystemGenerated(true);
179     edit.addEdit(e4);
180     expected = MessageManager.formatMessage("label.insert_gaps", "3");
181     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
182
183     // 2 deletes makes + 1
184     Edit e5 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
185     edit.addEdit(e5);
186     Edit e6 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
187     e6.setSystemGenerated(true);
188     edit.addEdit(e6);
189     expected = MessageManager.formatMessage("label.insert_gap", "1");
190     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
191
192     // 1 more delete makes 0 - no text
193     Edit e7 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
194     edit.addEdit(e7);
195     Edit e8 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
196     e8.setSystemGenerated(true);
197     edit.addEdit(e8);
198     expected = MessageManager.formatMessage("label.insert_gaps", "2");
199     assertNull(SeqPanel.getEditStatusMessage(edit));
200
201     // 1 more delete makes 1 delete
202     Edit e9 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
203     edit.addEdit(e9);
204     Edit e10 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
205     e10.setSystemGenerated(true);
206     edit.addEdit(e10);
207     expected = MessageManager.formatMessage("label.delete_gap", "1");
208     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
209
210     // 2 more deletes makes 3 deletes
211     Edit e11 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
212     edit.addEdit(e11);
213     Edit e12 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
214     e12.setSystemGenerated(true);
215     edit.addEdit(e12);
216     expected = MessageManager.formatMessage("label.delete_gaps", "3");
217     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
218   }
219
220   public void testFindMousePosition_unwrapped()
221   {
222     String seqData = ">Seq1\nAACDE\n>Seq2\nAA--E\n";
223     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(seqData,
224             DataSourceType.PASTE);
225     AlignViewportI av = alignFrame.getViewport();
226     av.setShowAnnotation(true);
227     av.setWrapAlignment(false);
228     final int charHeight = av.getCharHeight();
229     final int charWidth = av.getCharWidth();
230     // sanity checks:
231     assertTrue(charHeight > 0);
232     assertTrue(charWidth > 0);
233     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
234
235     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
236     int x = 0;
237     int y = 0;
238
239     /*
240      * mouse at top left of unwrapped panel
241      */
242     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
243             x, y, 0, 0, 0, false, 0);
244     MousePos pos = testee.findMousePosition(evt);
245     assertEquals(pos.column, 0);
246     assertEquals(pos.seqIndex, 0);
247     assertEquals(pos.annotationIndex, -1);
248   }
249
250   @AfterMethod(alwaysRun = true)
251   public void tearDown()
252   {
253     Desktop.instance.closeAll_actionPerformed(null);
254   }
255
256   @Test(groups = "Functional")
257   public void testFindMousePosition_wrapped_annotations()
258   {
259     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
260     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
261     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
262             "examples/uniref50.fa", DataSourceType.FILE);
263     AlignViewportI av = alignFrame.getViewport();
264     av.setScaleAboveWrapped(false);
265     av.setScaleLeftWrapped(false);
266     av.setScaleRightWrapped(false);
267
268     alignFrame.alignPanel.updateLayout();
269
270     final int charHeight = av.getCharHeight();
271     final int charWidth = av.getCharWidth();
272     final int alignmentHeight = av.getAlignment().getHeight();
273
274     // sanity checks:
275     assertTrue(charHeight > 0);
276     assertTrue(charWidth > 0);
277     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
278
279     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
280     int x = 0;
281     int y = 0;
282
283     /*
284      * mouse at top left of wrapped panel; there is a gap of charHeight
285      * above the alignment
286      */
287     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
288             x, y, 0, 0, 0, false, 0);
289     MousePos pos = testee.findMousePosition(evt);
290     assertEquals(pos.column, 0);
291     assertEquals(pos.seqIndex, -1); // above sequences
292     assertEquals(pos.annotationIndex, -1);
293
294     /*
295      * cursor at bottom of gap above
296      */
297     y = charHeight - 1;
298     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
299             0, false, 0);
300     pos = testee.findMousePosition(evt);
301     assertEquals(pos.seqIndex, -1);
302     assertEquals(pos.annotationIndex, -1);
303
304     /*
305      * cursor over top of first sequence
306      */
307     y = charHeight;
308     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
309             0, false, 0);
310     pos = testee.findMousePosition(evt);
311     assertEquals(pos.seqIndex, 0);
312     assertEquals(pos.annotationIndex, -1);
313
314     /*
315      * cursor at bottom of first sequence
316      */
317     y = 2 * charHeight - 1;
318     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
319             0, false, 0);
320     pos = testee.findMousePosition(evt);
321     assertEquals(pos.seqIndex, 0);
322     assertEquals(pos.annotationIndex, -1);
323
324     /*
325      * cursor at top of second sequence
326      */
327     y = 2 * charHeight;
328     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
329             0, false, 0);
330     pos = testee.findMousePosition(evt);
331     assertEquals(pos.seqIndex, 1);
332     assertEquals(pos.annotationIndex, -1);
333
334     /*
335      * cursor at bottom of second sequence
336      */
337     y = 3 * charHeight - 1;
338     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
339             0, false, 0);
340     pos = testee.findMousePosition(evt);
341     assertEquals(pos.seqIndex, 1);
342     assertEquals(pos.annotationIndex, -1);
343
344     /*
345      * cursor at bottom of last sequence
346      */
347     y = charHeight * (1 + alignmentHeight) - 1;
348     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
349             0, false, 0);
350     pos = testee.findMousePosition(evt);
351     assertEquals(pos.seqIndex, alignmentHeight - 1);
352     assertEquals(pos.annotationIndex, -1);
353
354     /*
355      * cursor below sequences, in 3-pixel gap above annotations
356      * method reports index of nearest sequence above
357      */
358     y += 1;
359     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
360             0, false, 0);
361     pos = testee.findMousePosition(evt);
362     assertEquals(pos.seqIndex, alignmentHeight - 1);
363     assertEquals(pos.annotationIndex, -1);
364
365     /*
366      * cursor still in the gap above annotations, now at the bottom of it
367      */
368     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
369     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
370             0, false, 0);
371     pos = testee.findMousePosition(evt);
372     assertEquals(pos.seqIndex, alignmentHeight - 1);
373     assertEquals(pos.annotationIndex, -1);
374
375     AlignmentAnnotation[] annotationRows = av.getAlignment()
376             .getAlignmentAnnotation();
377     for (int n = 0; n < annotationRows.length; n++)
378     {
379       /*
380        * cursor at the top of the n'th annotation  
381        */
382       y += 1;
383       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
384               0, 0, false, 0);
385       pos = testee.findMousePosition(evt);
386       assertEquals(pos.seqIndex, alignmentHeight - 1);
387       assertEquals(pos.annotationIndex, n); // over n'th annotation
388
389       /*
390        * cursor at the bottom of the n'th annotation  
391        */
392       y += annotationRows[n].height - 1;
393       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
394               0, 0, false, 0);
395       pos = testee.findMousePosition(evt);
396       assertEquals(pos.seqIndex, alignmentHeight - 1);
397       assertEquals(pos.annotationIndex, n);
398     }
399
400     /*
401      * cursor in gap between wrapped widths  
402      */
403     y += 1;
404     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
405             0, false, 0);
406     pos = testee.findMousePosition(evt);
407     assertEquals(pos.seqIndex, -1);
408     assertEquals(pos.annotationIndex, -1);
409
410     /*
411      * cursor at bottom of gap between wrapped widths  
412      */
413     y += charHeight - 1;
414     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
415             0, false, 0);
416     pos = testee.findMousePosition(evt);
417     assertEquals(pos.seqIndex, -1);
418     assertEquals(pos.annotationIndex, -1);
419
420     /*
421      * cursor at top of first sequence, second wrapped width  
422      */
423     y += 1;
424     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
425             0, false, 0);
426     pos = testee.findMousePosition(evt);
427     assertEquals(pos.seqIndex, 0);
428     assertEquals(pos.annotationIndex, -1);
429   }
430
431   @Test(groups = "Functional")
432   public void testFindMousePosition_wrapped_scaleAbove()
433   {
434     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
435     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
436     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
437             "examples/uniref50.fa", DataSourceType.FILE);
438     AlignViewportI av = alignFrame.getViewport();
439     av.setScaleAboveWrapped(true);
440     av.setScaleLeftWrapped(false);
441     av.setScaleRightWrapped(false);
442     alignFrame.alignPanel.updateLayout();
443
444     final int charHeight = av.getCharHeight();
445     final int charWidth = av.getCharWidth();
446     final int alignmentHeight = av.getAlignment().getHeight();
447
448     // sanity checks:
449     assertTrue(charHeight > 0);
450     assertTrue(charWidth > 0);
451     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
452
453     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
454     int x = 0;
455     int y = 0;
456
457     /*
458      * mouse at top left of wrapped panel; there is a gap of charHeight
459      * above the alignment
460      */
461     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
462             x, y, 0, 0, 0, false, 0);
463     MousePos pos = testee.findMousePosition(evt);
464     assertEquals(pos.column, 0);
465     assertEquals(pos.seqIndex, -1); // above sequences
466     assertEquals(pos.annotationIndex, -1);
467
468     /*
469      * cursor at bottom of gap above
470      * two charHeights including scale panel
471      */
472     y = 2 * charHeight - 1;
473     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
474             0, false, 0);
475     pos = testee.findMousePosition(evt);
476     assertEquals(pos.seqIndex, -1);
477     assertEquals(pos.annotationIndex, -1);
478
479     /*
480      * cursor over top of first sequence
481      */
482     y += 1;
483     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
484             0, false, 0);
485     pos = testee.findMousePosition(evt);
486     assertEquals(pos.seqIndex, 0);
487     assertEquals(pos.annotationIndex, -1);
488
489     /*
490      * cursor at bottom of first sequence
491      */
492     y += charHeight - 1;
493     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
494             0, false, 0);
495     pos = testee.findMousePosition(evt);
496     assertEquals(pos.seqIndex, 0);
497     assertEquals(pos.annotationIndex, -1);
498
499     /*
500      * cursor at top of second sequence
501      */
502     y += 1;
503     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
504             0, false, 0);
505     pos = testee.findMousePosition(evt);
506     assertEquals(pos.seqIndex, 1);
507     assertEquals(pos.annotationIndex, -1);
508
509     /*
510      * cursor at bottom of second sequence
511      */
512     y += charHeight - 1;
513     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
514             0, false, 0);
515     pos = testee.findMousePosition(evt);
516     assertEquals(pos.seqIndex, 1);
517     assertEquals(pos.annotationIndex, -1);
518
519     /*
520      * cursor at bottom of last sequence
521      * (scale + gap + sequences)
522      */
523     y = charHeight * (2 + alignmentHeight) - 1;
524     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
525             0, false, 0);
526     pos = testee.findMousePosition(evt);
527     assertEquals(pos.seqIndex, alignmentHeight - 1);
528     assertEquals(pos.annotationIndex, -1);
529
530     /*
531      * cursor below sequences, in 3-pixel gap above annotations
532      */
533     y += 1;
534     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
535             0, false, 0);
536     pos = testee.findMousePosition(evt);
537     assertEquals(pos.seqIndex, alignmentHeight - 1);
538     assertEquals(pos.annotationIndex, -1);
539
540     /*
541      * cursor still in the gap above annotations, now at the bottom of it
542      * method reports index of nearest sequence above  
543      */
544     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
545     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
546             0, false, 0);
547     pos = testee.findMousePosition(evt);
548     assertEquals(pos.seqIndex, alignmentHeight - 1);
549     assertEquals(pos.annotationIndex, -1);
550
551     AlignmentAnnotation[] annotationRows = av.getAlignment()
552             .getAlignmentAnnotation();
553     for (int n = 0; n < annotationRows.length; n++)
554     {
555       /*
556        * cursor at the top of the n'th annotation  
557        */
558       y += 1;
559       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
560               0, 0, false, 0);
561       pos = testee.findMousePosition(evt);
562       assertEquals(pos.seqIndex, alignmentHeight - 1);
563       assertEquals(pos.annotationIndex, n); // over n'th annotation
564
565       /*
566        * cursor at the bottom of the n'th annotation  
567        */
568       y += annotationRows[n].height - 1;
569       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
570               0, 0, false, 0);
571       pos = testee.findMousePosition(evt);
572       SeqCanvas sc = testee.seqCanvas;
573       assertEquals(pos.seqIndex, alignmentHeight - 1,
574               String.format("%s n=%d y=%d %d, %d, %d, %d",
575                       annotationRows[n].label, n, y, sc.getWidth(),
576                       sc.getHeight(), sc.wrappedRepeatHeightPx,
577                       sc.wrappedSpaceAboveAlignment));
578       assertEquals(pos.annotationIndex, n);
579     }
580
581     /*
582      * cursor in gap between wrapped widths  
583      */
584     y += 1;
585     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
586             0, false, 0);
587     pos = testee.findMousePosition(evt);
588     assertEquals(pos.seqIndex, -1);
589     assertEquals(pos.annotationIndex, -1);
590
591     /*
592      * cursor at bottom of gap between wrapped widths  
593      */
594     y += charHeight - 1;
595     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
596             0, false, 0);
597     pos = testee.findMousePosition(evt);
598     assertEquals(pos.seqIndex, -1);
599     assertEquals(pos.annotationIndex, -1);
600
601     /*
602      * cursor at top of scale, second wrapped width  
603      */
604     y += 1;
605     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
606             0, false, 0);
607     pos = testee.findMousePosition(evt);
608     assertEquals(pos.seqIndex, -1);
609     assertEquals(pos.annotationIndex, -1);
610
611     /*
612      * cursor at bottom of scale, second wrapped width  
613      */
614     y += charHeight - 1;
615     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
616             0, false, 0);
617     pos = testee.findMousePosition(evt);
618     assertEquals(pos.seqIndex, -1);
619     assertEquals(pos.annotationIndex, -1);
620
621     /*
622      * cursor at top of first sequence, second wrapped width  
623      */
624     y += 1;
625     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
626             0, false, 0);
627     pos = testee.findMousePosition(evt);
628     assertEquals(pos.seqIndex, 0);
629     assertEquals(pos.annotationIndex, -1);
630   }
631
632   @Test(groups = "Functional")
633   public void testFindMousePosition_wrapped_noAnnotations()
634   {
635     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
636     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
637     Cache.applicationProperties.setProperty("FONT_SIZE", "10");
638     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
639             "examples/uniref50.fa", DataSourceType.FILE);
640     AlignViewportI av = alignFrame.getViewport();
641     av.setScaleAboveWrapped(false);
642     av.setScaleLeftWrapped(false);
643     av.setScaleRightWrapped(false);
644     alignFrame.alignPanel.updateLayout();
645
646     final int charHeight = av.getCharHeight();
647     final int charWidth = av.getCharWidth();
648     final int alignmentHeight = av.getAlignment().getHeight();
649
650     // sanity checks:
651     assertTrue(charHeight > 0);
652     assertTrue(charWidth > 0);
653     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
654
655     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
656     int x = 0;
657     int y = 0;
658
659     /*
660      * mouse at top left of wrapped panel; there is a gap of charHeight
661      * above the alignment
662      */
663     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
664             x, y, 0, 0, 0, false, 0);
665     MousePos pos = testee.findMousePosition(evt);
666     assertEquals(pos.column, 0);
667     assertEquals(pos.seqIndex, -1); // above sequences
668     assertEquals(pos.annotationIndex, -1);
669
670     /*
671      * cursor over top of first sequence
672      */
673     y = charHeight;
674     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
675             0, false, 0);
676     pos = testee.findMousePosition(evt);
677     assertEquals(pos.seqIndex, 0);
678     assertEquals(pos.annotationIndex, -1);
679
680     /*
681      * cursor at bottom of last sequence
682      */
683     y = charHeight * (1 + alignmentHeight) - 1;
684     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
685             0, false, 0);
686     pos = testee.findMousePosition(evt);
687     assertEquals(pos.seqIndex, alignmentHeight - 1);
688     assertEquals(pos.annotationIndex, -1);
689
690     /*
691      * cursor below sequences, at top of charHeight gap between widths
692      */
693     y += 1;
694     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
695             0, false, 0);
696     pos = testee.findMousePosition(evt);
697     assertEquals(pos.seqIndex, -1);
698     assertEquals(pos.annotationIndex, -1);
699
700     /*
701      * cursor below sequences, at top of charHeight gap between widths
702      */
703     y += charHeight - 1;
704     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
705             0, false, 0);
706     pos = testee.findMousePosition(evt);
707     assertEquals(pos.seqIndex, -1);
708     assertEquals(pos.annotationIndex, -1);
709
710     /*
711      * cursor at the top of the first sequence, second width  
712      */
713     y += 1;
714     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
715             0, false, 0);
716     pos = testee.findMousePosition(evt);
717     assertEquals(pos.seqIndex, 0);
718     assertEquals(pos.annotationIndex, -1);
719   }
720
721   @Test(groups = "Functional")
722   public void testFindColumn_unwrapped()
723   {
724     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "false");
725     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
726             "examples/uniref50.fa", DataSourceType.FILE);
727     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
728     int x = 0;
729     final int charWidth = alignFrame.getViewport().getCharWidth();
730     assertTrue(charWidth > 0); // sanity check
731     ViewportRanges ranges = alignFrame.getViewport().getRanges();
732     assertEquals(ranges.getStartRes(), 0);
733
734     /*
735      * mouse at top left of unwrapped panel
736      */
737     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
738             x, 0, 0, 0, 0, false, 0);
739     assertEquals(testee.findColumn(evt), 0);
740
741     /*
742      * not quite one charWidth across
743      */
744     x = charWidth - 1;
745     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
746             0, false, 0);
747     assertEquals(testee.findColumn(evt), 0);
748
749     /*
750      * one charWidth across
751      */
752     x = charWidth;
753     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
754             0, false, 0);
755     assertEquals(testee.findColumn(evt), 1);
756
757     /*
758      * two charWidths across
759      */
760     x = 2 * charWidth;
761     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
762             0, false, 0);
763     assertEquals(testee.findColumn(evt), 2);
764
765     /*
766      * limited to last column of seqcanvas
767      */
768     x = 20000;
769     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
770             0, false, 0);
771     SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas;
772     int w = seqCanvas.getWidth();
773     // limited to number of whole columns, base 0,
774     // and to end of visible range
775     int expected = w / charWidth;
776     expected = Math.min(expected, ranges.getEndRes());
777     assertEquals(testee.findColumn(evt), expected);
778
779     /*
780      * hide columns 5-10 (base 1)
781      */
782     alignFrame.getViewport().hideColumns(4, 9);
783     x = 5 * charWidth + 2;
784     // x is in 6th visible column, absolute column 12, or 11 base 0
785     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
786             0, false, 0);
787     assertEquals(testee.findColumn(evt), 11);
788   }
789
790   @Test(groups = "Functional")
791   public void testFindColumn_wrapped()
792   {
793     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
794     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
795             "examples/uniref50.fa", DataSourceType.FILE);
796     AlignViewport av = alignFrame.getViewport();
797     av.setScaleAboveWrapped(false);
798     av.setScaleLeftWrapped(false);
799     av.setScaleRightWrapped(false);
800     alignFrame.alignPanel.updateLayout();
801     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
802     int x = 0;
803     final int charWidth = av.getCharWidth();
804     assertTrue(charWidth > 0); // sanity check
805     assertEquals(av.getRanges().getStartRes(), 0);
806
807     /*
808      * mouse at top left of wrapped panel, no West (left) scale
809      */
810     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
811             x, 0, 0, 0, 0, false, 0);
812     assertEquals(testee.findColumn(evt), 0);
813
814     /*
815      * not quite one charWidth across
816      */
817     x = charWidth - 1;
818     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
819             0, false, 0);
820     assertEquals(testee.findColumn(evt), 0);
821
822     /*
823      * one charWidth across
824      */
825     x = charWidth;
826     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
827             0, false, 0);
828     assertEquals(testee.findColumn(evt), 1);
829
830     /*
831      * x over scale left (before drawn columns) results in -1
832      */
833     av.setScaleLeftWrapped(true);
834     alignFrame.alignPanel.updateLayout();
835     SeqCanvas seqCanvas = testee.seqCanvas;
836     int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest");
837     assertTrue(labelWidth > 0);
838     x = labelWidth - 1;
839     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
840             0, false, 0);
841     assertEquals(testee.findColumn(evt), -1);
842
843     x = labelWidth;
844     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
845             0, false, 0);
846     assertEquals(testee.findColumn(evt), 0);
847
848     /*
849      * x over right edge of last residue (including scale left)
850      */
851     int residuesWide = av.getRanges().getViewportWidth();
852     assertTrue(residuesWide > 0);
853     x = labelWidth + charWidth * residuesWide - 1;
854     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
855             0, false, 0);
856     assertEquals(testee.findColumn(evt), residuesWide - 1);
857
858     /*
859      * x over scale right (beyond drawn columns) results in -1
860      */
861     av.setScaleRightWrapped(true);
862     alignFrame.alignPanel.updateLayout();
863     labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast");
864     assertTrue(labelWidth > 0);
865     int residuesWide2 = av.getRanges().getViewportWidth();
866     assertTrue(residuesWide2 > 0);
867     assertTrue(residuesWide2 < residuesWide); // available width reduced
868     x += 1; // just over left edge of scale right
869     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
870             0, false, 0);
871     assertEquals(testee.findColumn(evt), -1);
872
873     // todo add startRes offset, hidden columns
874
875   }
876
877   @BeforeClass(alwaysRun = true)
878   public static void setUpBeforeClass() throws Exception
879   {
880     /*
881      * use read-only test properties file
882      */
883     Cache.loadProperties("test/jalview/io/testProps.jvprops");
884     Jalview.main(new String[] { "-nonews" });
885   }
886
887   /**
888    * waits for Swing event dispatch queue to empty
889    */
890   synchronized void waitForSwing()
891   {
892     try
893     {
894       EventQueue.invokeAndWait(new Runnable()
895       {
896         @Override
897         public void run()
898         {
899         }
900       });
901     } catch (InterruptedException | InvocationTargetException e)
902     {
903       e.printStackTrace();
904     }
905   }
906
907   @Test(groups = "Functional")
908   public void testFindMousePosition_wrapped_scales_longSequence()
909   {
910     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
911     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
912     Cache.applicationProperties.setProperty("FONT_SIZE", "14");
913     Cache.applicationProperties.setProperty("FONT_NAME", "SansSerif");
914     Cache.applicationProperties.setProperty("FONT_STYLE", "0");
915     // sequence of 50 bases, doubled 10 times, = 51200 bases
916     String dna = "ATGGCCATTGGGCCCAAATTTCCCAAAGGGTTTCCCTGAGGTCAGTCAGA";
917     for (int i = 0; i < 10; i++)
918     {
919       dna += dna;
920     }
921     assertEquals(dna.length(), 51200);
922     AlignFrame alignFrame = new FileLoader()
923             .LoadFileWaitTillLoaded("dna "+dna, DataSourceType.PASTE);
924     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
925     AlignViewport av = alignFrame.getViewport();
926     av.setScaleAboveWrapped(true);
927     av.setScaleLeftWrapped(true);
928     av.setScaleRightWrapped(true);
929     alignFrame.alignPanel.updateLayout();
930
931     try
932     {
933       Thread.sleep(200);
934     } catch (InterruptedException e)
935     {
936     }
937
938     final int charHeight = av.getCharHeight();
939     final int charWidth = av.getCharWidth();
940     assertEquals(charHeight, 17);
941     assertEquals(charWidth, 12);
942
943     FontMetrics fm = testee.getFontMetrics(av.getFont());
944     int labelWidth = fm.stringWidth("00000") + charWidth;
945     assertEquals(labelWidth, 57); // 5 x 9 + charWidth
946     assertEquals(testee.seqCanvas.getLabelWidthWest(), labelWidth);
947
948     int x = 0;
949     int y = 0;
950
951     /*
952      * mouse at top left of wrapped panel; there is a gap of 2 * charHeight
953      * above the alignment
954      */
955     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
956             x, y, 0, 0, 0, false, 0);
957     MousePos pos = testee.findMousePosition(evt);
958     assertEquals(pos.column, -1); // over scale left, not an alignment column
959     assertEquals(pos.seqIndex, -1); // above sequences
960     assertEquals(pos.annotationIndex, -1);
961
962     /*
963      * cursor over scale above first sequence
964      */
965     y += charHeight;
966     x = labelWidth;
967     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
968             0, false, 0);
969     pos = testee.findMousePosition(evt);
970     assertEquals(pos.seqIndex, -1);
971     assertEquals(pos.column, 0);
972     assertEquals(pos.annotationIndex, -1);
973
974     /*
975      * cursor over scale left of first sequence
976      */
977     y += charHeight;
978     x = 0;
979     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
980             0, false, 0);
981     pos = testee.findMousePosition(evt);
982     assertEquals(pos.seqIndex, 0);
983     assertEquals(pos.column, -1);
984     assertEquals(pos.annotationIndex, -1);
985
986     /*
987      * cursor over start of first sequence
988      */
989     x = labelWidth;
990     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
991             0, false, 0);
992     pos = testee.findMousePosition(evt);
993     assertEquals(pos.seqIndex, 0);
994     assertEquals(pos.column, 0);
995     assertEquals(pos.annotationIndex, -1);
996
997     /*
998      * move one character right, to bottom pixel of same row
999      */
1000     x += charWidth;
1001     y += charHeight - 1;
1002     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1003             0, false, 0);
1004     pos = testee.findMousePosition(evt);
1005     assertEquals(pos.seqIndex, 0);
1006     assertEquals(pos.column, 1);
1007
1008     /*
1009      * move down one pixel - now in the no man's land between rows
1010      */
1011     y += 1;
1012     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1013             0, false, 0);
1014     pos = testee.findMousePosition(evt);
1015     assertEquals(pos.seqIndex, -1);
1016     assertEquals(pos.column, 1);
1017
1018     /*
1019      * move down two char heights less one pixel - still in the no man's land
1020      * (scale above + spacer line)
1021      */
1022     y += (2 * charHeight - 1);
1023     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1024             0, false, 0);
1025     pos = testee.findMousePosition(evt);
1026     assertEquals(pos.seqIndex, -1);
1027     assertEquals(pos.column, 1);
1028
1029     /*
1030      * move down one more pixel - now on the next row of the sequence
1031      */
1032     y += 1;
1033     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1034             0, false, 0);
1035     pos = testee.findMousePosition(evt);
1036     assertEquals(pos.seqIndex, 0);
1037     assertEquals(pos.column, 1 + av.getWrappedWidth());
1038
1039     /*
1040      * scroll to near the end of the sequence
1041      */
1042     SearchResultsI sr = new SearchResults();
1043     int scrollTo = dna.length() - 1000;
1044     sr.addResult(av.getAlignment().getSequenceAt(0), scrollTo, scrollTo);
1045     alignFrame.alignPanel.scrollToPosition(sr);
1046
1047     /*
1048      * place the mouse on the first column of the 6th sequence, and
1049      * verify that (computed) findMousePosition matches (actual) ViewportRanges
1050      */
1051     x = labelWidth;
1052     y = 17 * charHeight; // 17 = 6 times two header rows and 5 sequence rows
1053     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1054             0, false, 0);
1055     pos = testee.findMousePosition(evt);
1056     assertEquals(pos.seqIndex, 0);
1057     int expected = av.getRanges().getStartRes() + 5 * av.getWrappedWidth();
1058     assertEquals(pos.column, expected);
1059   }
1060 }