701431bdca772ff86ba1fcb75882a4d0f463ac7b
[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     if (Desktop.instance != null)
254       Desktop.instance.closeAll_actionPerformed(null);
255   }
256
257   @Test(groups = "Functional")
258   public void testFindMousePosition_wrapped_annotations()
259   {
260     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
261     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
262     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
263             "examples/uniref50.fa", DataSourceType.FILE);
264     AlignViewportI av = alignFrame.getViewport();
265     av.setScaleAboveWrapped(false);
266     av.setScaleLeftWrapped(false);
267     av.setScaleRightWrapped(false);
268
269     alignFrame.alignPanel.updateLayout();
270
271     final int charHeight = av.getCharHeight();
272     final int charWidth = av.getCharWidth();
273     final int alignmentHeight = av.getAlignment().getHeight();
274
275     // sanity checks:
276     assertTrue(charHeight > 0);
277     assertTrue(charWidth > 0);
278     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
279
280     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
281     int x = 0;
282     int y = 0;
283
284     /*
285      * mouse at top left of wrapped panel; there is a gap of charHeight
286      * above the alignment
287      */
288     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
289             x, y, 0, 0, 0, false, 0);
290     MousePos pos = testee.findMousePosition(evt);
291     assertEquals(pos.column, 0);
292     assertEquals(pos.seqIndex, -1); // above sequences
293     assertEquals(pos.annotationIndex, -1);
294
295     /*
296      * cursor at bottom of gap above
297      */
298     y = charHeight - 1;
299     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
300             0, false, 0);
301     pos = testee.findMousePosition(evt);
302     assertEquals(pos.seqIndex, -1);
303     assertEquals(pos.annotationIndex, -1);
304
305     /*
306      * cursor over top of first sequence
307      */
308     y = charHeight;
309     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
310             0, false, 0);
311     pos = testee.findMousePosition(evt);
312     assertEquals(pos.seqIndex, 0);
313     assertEquals(pos.annotationIndex, -1);
314
315     /*
316      * cursor at bottom of first sequence
317      */
318     y = 2 * charHeight - 1;
319     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
320             0, false, 0);
321     pos = testee.findMousePosition(evt);
322     assertEquals(pos.seqIndex, 0);
323     assertEquals(pos.annotationIndex, -1);
324
325     /*
326      * cursor at top of second sequence
327      */
328     y = 2 * charHeight;
329     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
330             0, false, 0);
331     pos = testee.findMousePosition(evt);
332     assertEquals(pos.seqIndex, 1);
333     assertEquals(pos.annotationIndex, -1);
334
335     /*
336      * cursor at bottom of second sequence
337      */
338     y = 3 * charHeight - 1;
339     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
340             0, false, 0);
341     pos = testee.findMousePosition(evt);
342     assertEquals(pos.seqIndex, 1);
343     assertEquals(pos.annotationIndex, -1);
344
345     /*
346      * cursor at bottom of last sequence
347      */
348     y = charHeight * (1 + alignmentHeight) - 1;
349     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
350             0, false, 0);
351     pos = testee.findMousePosition(evt);
352     assertEquals(pos.seqIndex, alignmentHeight - 1);
353     assertEquals(pos.annotationIndex, -1);
354
355     /*
356      * cursor below sequences, in 3-pixel gap above annotations
357      * method reports index of nearest sequence above
358      */
359     y += 1;
360     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
361             0, false, 0);
362     pos = testee.findMousePosition(evt);
363     assertEquals(pos.seqIndex, alignmentHeight - 1);
364     assertEquals(pos.annotationIndex, -1);
365
366     /*
367      * cursor still in the gap above annotations, now at the bottom of it
368      */
369     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
370     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
371             0, false, 0);
372     pos = testee.findMousePosition(evt);
373     assertEquals(pos.seqIndex, alignmentHeight - 1);
374     assertEquals(pos.annotationIndex, -1);
375
376     AlignmentAnnotation[] annotationRows = av.getAlignment()
377             .getAlignmentAnnotation();
378     for (int n = 0; n < annotationRows.length; n++)
379     {
380       /*
381        * cursor at the top of the n'th annotation  
382        */
383       y += 1;
384       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
385               0, 0, false, 0);
386       pos = testee.findMousePosition(evt);
387       assertEquals(pos.seqIndex, alignmentHeight - 1);
388       assertEquals(pos.annotationIndex, n); // over n'th annotation
389
390       /*
391        * cursor at the bottom of the n'th annotation  
392        */
393       y += annotationRows[n].height - 1;
394       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
395               0, 0, false, 0);
396       pos = testee.findMousePosition(evt);
397       assertEquals(pos.seqIndex, alignmentHeight - 1);
398       assertEquals(pos.annotationIndex, n);
399     }
400
401     /*
402      * cursor in gap between wrapped widths  
403      */
404     y += 1;
405     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
406             0, false, 0);
407     pos = testee.findMousePosition(evt);
408     assertEquals(pos.seqIndex, -1);
409     assertEquals(pos.annotationIndex, -1);
410
411     /*
412      * cursor at bottom of gap between wrapped widths  
413      */
414     y += charHeight - 1;
415     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
416             0, false, 0);
417     pos = testee.findMousePosition(evt);
418     assertEquals(pos.seqIndex, -1);
419     assertEquals(pos.annotationIndex, -1);
420
421     /*
422      * cursor at top of first sequence, second wrapped width  
423      */
424     y += 1;
425     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
426             0, false, 0);
427     pos = testee.findMousePosition(evt);
428     assertEquals(pos.seqIndex, 0);
429     assertEquals(pos.annotationIndex, -1);
430   }
431
432   @Test(groups = "Functional")
433   public void testFindMousePosition_wrapped_scaleAbove()
434   {
435     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
436     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
437     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
438             "examples/uniref50.fa", DataSourceType.FILE);
439     AlignViewportI av = alignFrame.getViewport();
440     av.setScaleAboveWrapped(true);
441     av.setScaleLeftWrapped(false);
442     av.setScaleRightWrapped(false);
443     alignFrame.alignPanel.updateLayout();
444
445     final int charHeight = av.getCharHeight();
446     final int charWidth = av.getCharWidth();
447     final int alignmentHeight = av.getAlignment().getHeight();
448
449     // sanity checks:
450     assertTrue(charHeight > 0);
451     assertTrue(charWidth > 0);
452     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
453
454     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
455     int x = 0;
456     int y = 0;
457
458     /*
459      * mouse at top left of wrapped panel; there is a gap of charHeight
460      * above the alignment
461      */
462     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
463             x, y, 0, 0, 0, false, 0);
464     MousePos pos = testee.findMousePosition(evt);
465     assertEquals(pos.column, 0);
466     assertEquals(pos.seqIndex, -1); // above sequences
467     assertEquals(pos.annotationIndex, -1);
468
469     /*
470      * cursor at bottom of gap above
471      * two charHeights including scale panel
472      */
473     y = 2 * charHeight - 1;
474     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
475             0, false, 0);
476     pos = testee.findMousePosition(evt);
477     assertEquals(pos.seqIndex, -1);
478     assertEquals(pos.annotationIndex, -1);
479
480     /*
481      * cursor over top of first sequence
482      */
483     y += 1;
484     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
485             0, false, 0);
486     pos = testee.findMousePosition(evt);
487     assertEquals(pos.seqIndex, 0);
488     assertEquals(pos.annotationIndex, -1);
489
490     /*
491      * cursor at bottom of first sequence
492      */
493     y += charHeight - 1;
494     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
495             0, false, 0);
496     pos = testee.findMousePosition(evt);
497     assertEquals(pos.seqIndex, 0);
498     assertEquals(pos.annotationIndex, -1);
499
500     /*
501      * cursor at top of second sequence
502      */
503     y += 1;
504     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
505             0, false, 0);
506     pos = testee.findMousePosition(evt);
507     assertEquals(pos.seqIndex, 1);
508     assertEquals(pos.annotationIndex, -1);
509
510     /*
511      * cursor at bottom of second sequence
512      */
513     y += charHeight - 1;
514     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
515             0, false, 0);
516     pos = testee.findMousePosition(evt);
517     assertEquals(pos.seqIndex, 1);
518     assertEquals(pos.annotationIndex, -1);
519
520     /*
521      * cursor at bottom of last sequence
522      * (scale + gap + sequences)
523      */
524     y = charHeight * (2 + alignmentHeight) - 1;
525     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
526             0, false, 0);
527     pos = testee.findMousePosition(evt);
528     assertEquals(pos.seqIndex, alignmentHeight - 1);
529     assertEquals(pos.annotationIndex, -1);
530
531     /*
532      * cursor below sequences, in 3-pixel gap above annotations
533      */
534     y += 1;
535     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
536             0, false, 0);
537     pos = testee.findMousePosition(evt);
538     assertEquals(pos.seqIndex, alignmentHeight - 1);
539     assertEquals(pos.annotationIndex, -1);
540
541     /*
542      * cursor still in the gap above annotations, now at the bottom of it
543      * method reports index of nearest sequence above  
544      */
545     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
546     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
547             0, false, 0);
548     pos = testee.findMousePosition(evt);
549     assertEquals(pos.seqIndex, alignmentHeight - 1);
550     assertEquals(pos.annotationIndex, -1);
551
552     AlignmentAnnotation[] annotationRows = av.getAlignment()
553             .getAlignmentAnnotation();
554     for (int n = 0; n < annotationRows.length; n++)
555     {
556       /*
557        * cursor at the top of the n'th annotation  
558        */
559       y += 1;
560       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
561               0, 0, false, 0);
562       pos = testee.findMousePosition(evt);
563       assertEquals(pos.seqIndex, alignmentHeight - 1);
564       assertEquals(pos.annotationIndex, n); // over n'th annotation
565
566       /*
567        * cursor at the bottom of the n'th annotation  
568        */
569       y += annotationRows[n].height - 1;
570       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0,
571               0, 0, false, 0);
572       pos = testee.findMousePosition(evt);
573       SeqCanvas sc = testee.seqCanvas;
574       assertEquals(pos.seqIndex, alignmentHeight - 1,
575               String.format("%s n=%d y=%d %d, %d, %d, %d",
576                       annotationRows[n].label, n, y, sc.getWidth(),
577                       sc.getHeight(), sc.wrappedRepeatHeightPx,
578                       sc.wrappedSpaceAboveAlignment));
579       assertEquals(pos.annotationIndex, n);
580     }
581
582     /*
583      * cursor in gap between wrapped widths  
584      */
585     y += 1;
586     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
587             0, false, 0);
588     pos = testee.findMousePosition(evt);
589     assertEquals(pos.seqIndex, -1);
590     assertEquals(pos.annotationIndex, -1);
591
592     /*
593      * cursor at bottom of gap between wrapped widths  
594      */
595     y += charHeight - 1;
596     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
597             0, false, 0);
598     pos = testee.findMousePosition(evt);
599     assertEquals(pos.seqIndex, -1);
600     assertEquals(pos.annotationIndex, -1);
601
602     /*
603      * cursor at top of scale, second wrapped width  
604      */
605     y += 1;
606     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
607             0, false, 0);
608     pos = testee.findMousePosition(evt);
609     assertEquals(pos.seqIndex, -1);
610     assertEquals(pos.annotationIndex, -1);
611
612     /*
613      * cursor at bottom of scale, second wrapped width  
614      */
615     y += charHeight - 1;
616     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
617             0, false, 0);
618     pos = testee.findMousePosition(evt);
619     assertEquals(pos.seqIndex, -1);
620     assertEquals(pos.annotationIndex, -1);
621
622     /*
623      * cursor at top of first sequence, second wrapped width  
624      */
625     y += 1;
626     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
627             0, false, 0);
628     pos = testee.findMousePosition(evt);
629     assertEquals(pos.seqIndex, 0);
630     assertEquals(pos.annotationIndex, -1);
631   }
632
633   @Test(groups = "Functional")
634   public void testFindMousePosition_wrapped_noAnnotations()
635   {
636     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
637     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
638     Cache.applicationProperties.setProperty("FONT_SIZE", "10");
639     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
640             "examples/uniref50.fa", DataSourceType.FILE);
641     AlignViewportI av = alignFrame.getViewport();
642     av.setScaleAboveWrapped(false);
643     av.setScaleLeftWrapped(false);
644     av.setScaleRightWrapped(false);
645     alignFrame.alignPanel.updateLayout();
646
647     final int charHeight = av.getCharHeight();
648     final int charWidth = av.getCharWidth();
649     final int alignmentHeight = av.getAlignment().getHeight();
650
651     // sanity checks:
652     assertTrue(charHeight > 0);
653     assertTrue(charWidth > 0);
654     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
655
656     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
657     int x = 0;
658     int y = 0;
659
660     /*
661      * mouse at top left of wrapped panel; there is a gap of charHeight
662      * above the alignment
663      */
664     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
665             x, y, 0, 0, 0, false, 0);
666     MousePos pos = testee.findMousePosition(evt);
667     assertEquals(pos.column, 0);
668     assertEquals(pos.seqIndex, -1); // above sequences
669     assertEquals(pos.annotationIndex, -1);
670
671     /*
672      * cursor over top of first sequence
673      */
674     y = charHeight;
675     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
676             0, false, 0);
677     pos = testee.findMousePosition(evt);
678     assertEquals(pos.seqIndex, 0);
679     assertEquals(pos.annotationIndex, -1);
680
681     /*
682      * cursor at bottom of last sequence
683      */
684     y = charHeight * (1 + alignmentHeight) - 1;
685     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
686             0, false, 0);
687     pos = testee.findMousePosition(evt);
688     assertEquals(pos.seqIndex, alignmentHeight - 1);
689     assertEquals(pos.annotationIndex, -1);
690
691     /*
692      * cursor below sequences, at top of charHeight gap between widths
693      */
694     y += 1;
695     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
696             0, false, 0);
697     pos = testee.findMousePosition(evt);
698     assertEquals(pos.seqIndex, -1);
699     assertEquals(pos.annotationIndex, -1);
700
701     /*
702      * cursor below sequences, at top of charHeight gap between widths
703      */
704     y += charHeight - 1;
705     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
706             0, false, 0);
707     pos = testee.findMousePosition(evt);
708     assertEquals(pos.seqIndex, -1);
709     assertEquals(pos.annotationIndex, -1);
710
711     /*
712      * cursor at the top of the first sequence, second width  
713      */
714     y += 1;
715     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
716             0, false, 0);
717     pos = testee.findMousePosition(evt);
718     assertEquals(pos.seqIndex, 0);
719     assertEquals(pos.annotationIndex, -1);
720   }
721
722   @Test(groups = "Functional")
723   public void testFindColumn_unwrapped()
724   {
725     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "false");
726     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
727             "examples/uniref50.fa", DataSourceType.FILE);
728     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
729     int x = 0;
730     final int charWidth = alignFrame.getViewport().getCharWidth();
731     assertTrue(charWidth > 0); // sanity check
732     ViewportRanges ranges = alignFrame.getViewport().getRanges();
733     assertEquals(ranges.getStartRes(), 0);
734
735     /*
736      * mouse at top left of unwrapped panel
737      */
738     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
739             x, 0, 0, 0, 0, false, 0);
740     assertEquals(testee.findColumn(evt), 0);
741
742     /*
743      * not quite one charWidth across
744      */
745     x = charWidth - 1;
746     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
747             0, false, 0);
748     assertEquals(testee.findColumn(evt), 0);
749
750     /*
751      * one charWidth across
752      */
753     x = charWidth;
754     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
755             0, false, 0);
756     assertEquals(testee.findColumn(evt), 1);
757
758     /*
759      * two charWidths across
760      */
761     x = 2 * charWidth;
762     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
763             0, false, 0);
764     assertEquals(testee.findColumn(evt), 2);
765
766     /*
767      * limited to last column of seqcanvas
768      */
769     x = 20000;
770     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
771             0, false, 0);
772     SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas;
773     int w = seqCanvas.getWidth();
774     // limited to number of whole columns, base 0,
775     // and to end of visible range
776     int expected = w / charWidth;
777     expected = Math.min(expected, ranges.getEndRes());
778     assertEquals(testee.findColumn(evt), expected);
779
780     /*
781      * hide columns 5-10 (base 1)
782      */
783     alignFrame.getViewport().hideColumns(4, 9);
784     x = 5 * charWidth + 2;
785     // x is in 6th visible column, absolute column 12, or 11 base 0
786     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
787             0, false, 0);
788     assertEquals(testee.findColumn(evt), 11);
789   }
790
791   @Test(groups = "Functional")
792   public void testFindColumn_wrapped()
793   {
794     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
795     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
796             "examples/uniref50.fa", DataSourceType.FILE);
797     AlignViewport av = alignFrame.getViewport();
798     av.setScaleAboveWrapped(false);
799     av.setScaleLeftWrapped(false);
800     av.setScaleRightWrapped(false);
801     alignFrame.alignPanel.updateLayout();
802     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
803     int x = 0;
804     final int charWidth = av.getCharWidth();
805     assertTrue(charWidth > 0); // sanity check
806     assertEquals(av.getRanges().getStartRes(), 0);
807
808     /*
809      * mouse at top left of wrapped panel, no West (left) scale
810      */
811     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
812             x, 0, 0, 0, 0, false, 0);
813     assertEquals(testee.findColumn(evt), 0);
814
815     /*
816      * not quite one charWidth across
817      */
818     x = charWidth - 1;
819     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
820             0, false, 0);
821     assertEquals(testee.findColumn(evt), 0);
822
823     /*
824      * one charWidth across
825      */
826     x = charWidth;
827     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
828             0, false, 0);
829     assertEquals(testee.findColumn(evt), 1);
830
831     /*
832      * x over scale left (before drawn columns) results in -1
833      */
834     av.setScaleLeftWrapped(true);
835     alignFrame.alignPanel.updateLayout();
836     SeqCanvas seqCanvas = testee.seqCanvas;
837     int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest");
838     assertTrue(labelWidth > 0);
839     x = labelWidth - 1;
840     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
841             0, false, 0);
842     assertEquals(testee.findColumn(evt), -1);
843
844     x = labelWidth;
845     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
846             0, false, 0);
847     assertEquals(testee.findColumn(evt), 0);
848
849     /*
850      * x over right edge of last residue (including scale left)
851      */
852     int residuesWide = av.getRanges().getViewportWidth();
853     assertTrue(residuesWide > 0);
854     x = labelWidth + charWidth * residuesWide - 1;
855     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
856             0, false, 0);
857     assertEquals(testee.findColumn(evt), residuesWide - 1);
858
859     /*
860      * x over scale right (beyond drawn columns) results in -1
861      */
862     av.setScaleRightWrapped(true);
863     alignFrame.alignPanel.updateLayout();
864     labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast");
865     assertTrue(labelWidth > 0);
866     int residuesWide2 = av.getRanges().getViewportWidth();
867     assertTrue(residuesWide2 > 0);
868     assertTrue(residuesWide2 < residuesWide); // available width reduced
869     x += 1; // just over left edge of scale right
870     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0,
871             0, false, 0);
872     assertEquals(testee.findColumn(evt), -1);
873
874     // todo add startRes offset, hidden columns
875
876   }
877
878   @BeforeClass(alwaysRun = true)
879   public static void setUpBeforeClass() throws Exception
880   {
881     /*
882      * use read-only test properties file
883      */
884     Cache.loadProperties("test/jalview/io/testProps.jvprops");
885     Jalview.main(new String[] { "-nonews" });
886   }
887
888   /**
889    * waits for Swing event dispatch queue to empty
890    */
891   synchronized void waitForSwing()
892   {
893     try
894     {
895       EventQueue.invokeAndWait(new Runnable()
896       {
897         @Override
898         public void run()
899         {
900         }
901       });
902     } catch (InterruptedException | InvocationTargetException e)
903     {
904       e.printStackTrace();
905     }
906   }
907
908   @Test(groups = "Functional")
909   public void testFindMousePosition_wrapped_scales_longSequence()
910   {
911     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
912     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
913     Cache.applicationProperties.setProperty("FONT_SIZE", "14");
914     Cache.applicationProperties.setProperty("FONT_NAME", "SansSerif");
915     Cache.applicationProperties.setProperty("FONT_STYLE", "0");
916     // sequence of 50 bases, doubled 10 times, = 51200 bases
917     String dna = "ATGGCCATTGGGCCCAAATTTCCCAAAGGGTTTCCCTGAGGTCAGTCAGA";
918     for (int i = 0; i < 10; i++)
919     {
920       dna += dna;
921     }
922     assertEquals(dna.length(), 51200);
923     AlignFrame alignFrame = new FileLoader()
924             .LoadFileWaitTillLoaded("dna " + dna, DataSourceType.PASTE);
925     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
926     AlignViewport av = alignFrame.getViewport();
927     av.setScaleAboveWrapped(true);
928     av.setScaleLeftWrapped(true);
929     av.setScaleRightWrapped(true);
930     alignFrame.alignPanel.updateLayout();
931
932     try
933     {
934       Thread.sleep(200);
935     } catch (InterruptedException e)
936     {
937     }
938
939     final int charHeight = av.getCharHeight();
940     final int charWidth = av.getCharWidth();
941     assertEquals(charHeight, 17);
942     assertEquals(charWidth, 12);
943
944     FontMetrics fm = testee.getFontMetrics(av.getFont());
945     int labelWidth = fm.stringWidth("00000") + charWidth;
946     assertEquals(labelWidth, 57); // 5 x 9 + charWidth
947     assertEquals(testee.seqCanvas.getLabelWidthWest(), labelWidth);
948
949     int x = 0;
950     int y = 0;
951
952     /*
953      * mouse at top left of wrapped panel; there is a gap of 2 * charHeight
954      * above the alignment
955      */
956     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0,
957             x, y, 0, 0, 0, false, 0);
958     MousePos pos = testee.findMousePosition(evt);
959     assertEquals(pos.column, -1); // over scale left, not an alignment column
960     assertEquals(pos.seqIndex, -1); // above sequences
961     assertEquals(pos.annotationIndex, -1);
962
963     /*
964      * cursor over scale above first sequence
965      */
966     y += charHeight;
967     x = labelWidth;
968     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
969             0, false, 0);
970     pos = testee.findMousePosition(evt);
971     assertEquals(pos.seqIndex, -1);
972     assertEquals(pos.column, 0);
973     assertEquals(pos.annotationIndex, -1);
974
975     /*
976      * cursor over scale left of first sequence
977      */
978     y += charHeight;
979     x = 0;
980     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
981             0, false, 0);
982     pos = testee.findMousePosition(evt);
983     assertEquals(pos.seqIndex, 0);
984     assertEquals(pos.column, -1);
985     assertEquals(pos.annotationIndex, -1);
986
987     /*
988      * cursor over start of first sequence
989      */
990     x = labelWidth;
991     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
992             0, false, 0);
993     pos = testee.findMousePosition(evt);
994     assertEquals(pos.seqIndex, 0);
995     assertEquals(pos.column, 0);
996     assertEquals(pos.annotationIndex, -1);
997
998     /*
999      * move one character right, to bottom pixel of same row
1000      */
1001     x += charWidth;
1002     y += charHeight - 1;
1003     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1004             0, false, 0);
1005     pos = testee.findMousePosition(evt);
1006     assertEquals(pos.seqIndex, 0);
1007     assertEquals(pos.column, 1);
1008
1009     /*
1010      * move down one pixel - now in the no man's land between rows
1011      */
1012     y += 1;
1013     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1014             0, false, 0);
1015     pos = testee.findMousePosition(evt);
1016     assertEquals(pos.seqIndex, -1);
1017     assertEquals(pos.column, 1);
1018
1019     /*
1020      * move down two char heights less one pixel - still in the no man's land
1021      * (scale above + spacer line)
1022      */
1023     y += (2 * charHeight - 1);
1024     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1025             0, false, 0);
1026     pos = testee.findMousePosition(evt);
1027     assertEquals(pos.seqIndex, -1);
1028     assertEquals(pos.column, 1);
1029
1030     /*
1031      * move down one more pixel - now on the next row of the sequence
1032      */
1033     y += 1;
1034     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1035             0, false, 0);
1036     pos = testee.findMousePosition(evt);
1037     assertEquals(pos.seqIndex, 0);
1038     assertEquals(pos.column, 1 + av.getWrappedWidth());
1039
1040     /*
1041      * scroll to near the end of the sequence
1042      */
1043     SearchResultsI sr = new SearchResults();
1044     int scrollTo = dna.length() - 1000;
1045     sr.addResult(av.getAlignment().getSequenceAt(0), scrollTo, scrollTo);
1046     alignFrame.alignPanel.scrollToPosition(sr);
1047
1048     /*
1049      * place the mouse on the first column of the 6th sequence, and
1050      * verify that (computed) findMousePosition matches (actual) ViewportRanges
1051      */
1052     x = labelWidth;
1053     y = 17 * charHeight; // 17 = 6 times two header rows and 5 sequence rows
1054     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0,
1055             0, false, 0);
1056     pos = testee.findMousePosition(evt);
1057     assertEquals(pos.seqIndex, 0);
1058     int expected = av.getRanges().getStartRes() + 5 * av.getWrappedWidth();
1059     assertEquals(pos.column, expected);
1060   }
1061 }