a03819d1763369b8e50ffe37dd2d8a8f289c0e8b
[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 jalview.api.AlignViewportI;
28 import jalview.bin.Cache;
29 import jalview.bin.Jalview;
30 import jalview.commands.EditCommand;
31 import jalview.commands.EditCommand.Action;
32 import jalview.commands.EditCommand.Edit;
33 import jalview.datamodel.Alignment;
34 import jalview.datamodel.AlignmentAnnotation;
35 import jalview.datamodel.AlignmentI;
36 import jalview.datamodel.Sequence;
37 import jalview.datamodel.SequenceI;
38 import jalview.gui.SeqPanel.MousePos;
39 import jalview.io.DataSourceType;
40 import jalview.io.FileLoader;
41 import jalview.util.MessageManager;
42 import jalview.viewmodel.ViewportRanges;
43
44 import java.awt.EventQueue;
45 import java.awt.event.MouseEvent;
46 import java.lang.reflect.InvocationTargetException;
47
48 import javax.swing.JLabel;
49
50 import org.testng.annotations.AfterMethod;
51 import org.testng.annotations.BeforeClass;
52 import org.testng.annotations.Test;
53
54 import junit.extensions.PA;
55
56 public class SeqPanelTest
57 {
58   AlignFrame af;
59
60   @BeforeClass(alwaysRun = true)
61   public void setUpJvOptionPane()
62   {
63     JvOptionPane.setInteractiveMode(false);
64     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
65   }
66   @Test(groups = "Functional")
67   public void testSetStatusReturnsNearestResiduePosition()
68   {
69     SequenceI seq1 = new Sequence("Seq1", "AACDE");
70     SequenceI seq2 = new Sequence("Seq2", "AA--E");
71     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
72     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
73             al.getHeight());
74     AlignmentI visAl = alignFrame.getViewport().getAlignment();
75
76     // Test either side of gap
77     assertEquals(
78             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
79                     visAl.getSequenceAt(1), 1, 1), 2);
80     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
81             "Sequence 2 ID: Seq2 Residue: ALA (2)");
82     assertEquals(
83             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
84                     visAl.getSequenceAt(1), 4, 1), 3);
85     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
86             "Sequence 2 ID: Seq2 Residue: GLU (3)");
87     // no status message at a gap, returns next residue position to the right
88     assertEquals(
89             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
90                     visAl.getSequenceAt(1), 2, 1), 3);
91     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
92             "Sequence 2 ID: Seq2");
93     assertEquals(
94             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
95                     visAl.getSequenceAt(1), 3, 1), 3);
96     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
97             "Sequence 2 ID: Seq2");
98   }
99
100   @Test(groups = "Functional")
101   public void testAmbiguousAminoAcidGetsStatusMessage()
102   {
103     SequenceI seq1 = new Sequence("Seq1", "ABCDE");
104     SequenceI seq2 = new Sequence("Seq2", "AB--E");
105     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
106     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
107             al.getHeight());
108     AlignmentI visAl = alignFrame.getViewport().getAlignment();
109
110     assertEquals(
111             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
112                     visAl.getSequenceAt(1), 1, 1), 2);
113     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
114             "Sequence 2 ID: Seq2 Residue: B (2)");
115   }
116
117   @Test(groups = "Functional")
118   public void testGetEditStatusMessage()
119   {
120     assertNull(SeqPanel.getEditStatusMessage(null));
121
122     EditCommand edit = new EditCommand(); // empty
123     assertNull(SeqPanel.getEditStatusMessage(edit));
124
125     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
126     
127     // 1 gap
128     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-'));
129     String expected = MessageManager.formatMessage("label.insert_gap", "1");
130     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
131
132     // 3 more gaps makes +4
133     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 3, '-'));
134     expected = MessageManager.formatMessage("label.insert_gaps", "4");
135     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
136
137     // 2 deletes makes + 2
138     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
139     expected = MessageManager.formatMessage("label.insert_gaps", "2");
140     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
141
142     // 2 more deletes makes 0 - no text
143     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
144     assertNull(SeqPanel.getEditStatusMessage(edit));
145
146     // 1 more delete makes 1 delete
147     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
148     expected = MessageManager.formatMessage("label.delete_gap", "1");
149     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
150
151     // 1 more delete makes 2 deletes
152     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
153     expected = MessageManager.formatMessage("label.delete_gaps", "2");
154     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
155   }
156
157   /**
158    * Tests that simulate 'locked editing', where an inserted gap is balanced by
159    * a gap deletion in the selection group, and vice versa
160    */
161   @Test(groups = "Functional")
162   public void testGetEditStatusMessage_lockedEditing()
163   {
164     EditCommand edit = new EditCommand(); // empty
165     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
166     
167     // 1 gap inserted, balanced by 1 delete
168     Edit e1 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-');
169     edit.addEdit(e1);
170     Edit e2 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 1, '-');
171     e2.setSystemGenerated(true);
172     edit.addEdit(e2);
173     String expected = MessageManager.formatMessage("label.insert_gap", "1");
174     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
175   
176     // 2 more gaps makes +3
177     Edit e3 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
178     edit.addEdit(e3);
179     Edit e4 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 2, '-');
180     e4.setSystemGenerated(true);
181     edit.addEdit(e4);
182     expected = MessageManager.formatMessage("label.insert_gaps", "3");
183     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
184   
185     // 2 deletes makes + 1
186     Edit e5 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
187     edit.addEdit(e5);
188     Edit e6 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
189     e6.setSystemGenerated(true);
190     edit.addEdit(e6);
191     expected = MessageManager.formatMessage("label.insert_gap", "1");
192     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
193   
194     // 1 more delete makes 0 - no text
195     Edit e7 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
196     edit.addEdit(e7);
197     Edit e8 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
198     e8.setSystemGenerated(true);
199     edit.addEdit(e8);
200     expected = MessageManager.formatMessage("label.insert_gaps", "2");
201     assertNull(SeqPanel.getEditStatusMessage(edit));
202   
203     // 1 more delete makes 1 delete
204     Edit e9 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
205     edit.addEdit(e9);
206     Edit e10 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
207     e10.setSystemGenerated(true);
208     edit.addEdit(e10);
209     expected = MessageManager.formatMessage("label.delete_gap", "1");
210     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
211   
212     // 2 more deletes makes 3 deletes
213     Edit e11 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
214     edit.addEdit(e11);
215     Edit e12 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
216     e12.setSystemGenerated(true);
217     edit.addEdit(e12);
218     expected = MessageManager.formatMessage("label.delete_gaps", "3");
219     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
220   }
221
222   public void testFindMousePosition_unwrapped()
223   {
224     String seqData = ">Seq1\nAACDE\n>Seq2\nAA--E\n";
225     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(seqData,
226             DataSourceType.PASTE);
227     AlignViewportI av = alignFrame.getViewport();
228     av.setShowAnnotation(true);
229     av.setWrapAlignment(false);
230     final int charHeight = av.getCharHeight();
231     final int charWidth = av.getCharWidth();
232     // sanity checks:
233     assertTrue(charHeight > 0);
234     assertTrue(charWidth > 0);
235     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
236
237     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
238     int x = 0;
239     int y = 0;
240
241     /*
242      * mouse at top left of unwrapped panel
243      */
244     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
245             0, 0, 0, false, 0);
246     MousePos pos = testee.findMousePosition(evt);
247     assertEquals(pos.column, 0);
248     assertEquals(pos.seqIndex, 0);
249     assertEquals(pos.annotationIndex, -1);
250   }
251
252   @AfterMethod(alwaysRun = true)
253   public void tearDown()
254   {
255     Desktop.instance.closeAll_actionPerformed(null);
256   }
257
258   @Test(groups = "Functional")
259   public void testFindMousePosition_wrapped_annotations()
260   {
261     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
262     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
263     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
264             "examples/uniref50.fa", DataSourceType.FILE);
265     AlignViewportI av = alignFrame.getViewport();
266     av.setScaleAboveWrapped(false);
267     av.setScaleLeftWrapped(false);
268     av.setScaleRightWrapped(false);
269
270     alignFrame.alignPanel.updateLayout();
271
272     final int charHeight = av.getCharHeight();
273     final int charWidth = av.getCharWidth();
274     final int alignmentHeight = av.getAlignment().getHeight();
275     
276     // sanity checks:
277     assertTrue(charHeight > 0);
278     assertTrue(charWidth > 0);
279     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
280   
281     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
282     int x = 0;
283     int y = 0;
284   
285     /*
286      * mouse at top left of wrapped panel; there is a gap of charHeight
287      * above the alignment
288      */
289     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
290             0, 0, 0, false, 0);
291     MousePos pos = testee.findMousePosition(evt);
292     assertEquals(pos.column, 0);
293     assertEquals(pos.seqIndex, -1); // above sequences
294     assertEquals(pos.annotationIndex, -1);
295
296     /*
297      * cursor at bottom of gap above
298      */
299     y = charHeight - 1;
300     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
301             false, 0);
302     pos = testee.findMousePosition(evt);
303     assertEquals(pos.seqIndex, -1);
304     assertEquals(pos.annotationIndex, -1);
305
306     /*
307      * cursor over top of first sequence
308      */
309     y = charHeight;
310     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
311             false, 0);
312     pos = testee.findMousePosition(evt);
313     assertEquals(pos.seqIndex, 0);
314     assertEquals(pos.annotationIndex, -1);
315
316     /*
317      * cursor at bottom of first sequence
318      */
319     y = 2 * charHeight - 1;
320     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
321             false, 0);
322     pos = testee.findMousePosition(evt);
323     assertEquals(pos.seqIndex, 0);
324     assertEquals(pos.annotationIndex, -1);
325
326     /*
327      * cursor at top of second sequence
328      */
329     y = 2 * charHeight;
330     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
331             false, 0);
332     pos = testee.findMousePosition(evt);
333     assertEquals(pos.seqIndex, 1);
334     assertEquals(pos.annotationIndex, -1);
335
336     /*
337      * cursor at bottom of second sequence
338      */
339     y = 3 * charHeight - 1;
340     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
341             false, 0);
342     pos = testee.findMousePosition(evt);
343     assertEquals(pos.seqIndex, 1);
344     assertEquals(pos.annotationIndex, -1);
345
346     /*
347      * cursor at bottom of last sequence
348      */
349     y = charHeight * (1 + alignmentHeight) - 1;
350     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
351             false, 0);
352     pos = testee.findMousePosition(evt);
353     assertEquals(pos.seqIndex, alignmentHeight - 1);
354     assertEquals(pos.annotationIndex, -1);
355
356     /*
357      * cursor below sequences, in 3-pixel gap above annotations
358      * method reports index of nearest sequence above
359      */
360     y += 1;
361     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
362             false, 0);
363     pos = testee.findMousePosition(evt);
364     assertEquals(pos.seqIndex, alignmentHeight - 1);
365     assertEquals(pos.annotationIndex, -1);
366
367     /*
368      * cursor still in the gap above annotations, now at the bottom of it
369      */
370     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
371     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
372             false, 0);
373     pos = testee.findMousePosition(evt);
374     assertEquals(pos.seqIndex, alignmentHeight - 1);
375     assertEquals(pos.annotationIndex, -1);
376
377     AlignmentAnnotation[] annotationRows = av.getAlignment()
378             .getAlignmentAnnotation();
379     for (int n = 0; n < annotationRows.length; n++)
380     {
381       /*
382        * cursor at the top of the n'th annotation  
383        */
384       y += 1;
385       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
386               false, 0);
387       pos = testee.findMousePosition(evt);
388       assertEquals(pos.seqIndex, alignmentHeight - 1);
389       assertEquals(pos.annotationIndex, n); // over n'th annotation
390
391       /*
392        * cursor at the bottom of the n'th annotation  
393        */
394       y += annotationRows[n].height - 1;
395       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
396               false, 0);
397       pos = testee.findMousePosition(evt);
398       assertEquals(pos.seqIndex, alignmentHeight - 1);
399       assertEquals(pos.annotationIndex, n);
400     }
401
402     /*
403      * cursor in gap between wrapped widths  
404      */
405     y += 1;
406     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
407             false, 0);
408     pos = testee.findMousePosition(evt);
409     assertEquals(pos.seqIndex, -1);
410     assertEquals(pos.annotationIndex, -1);
411
412     /*
413      * cursor at bottom of gap between wrapped widths  
414      */
415     y += charHeight - 1;
416     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
417             false, 0);
418     pos = testee.findMousePosition(evt);
419     assertEquals(pos.seqIndex, -1);
420     assertEquals(pos.annotationIndex, -1);
421
422     /*
423      * cursor at top of first sequence, second wrapped width  
424      */
425     y += 1;
426     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
427             false, 0);
428     pos = testee.findMousePosition(evt);
429     assertEquals(pos.seqIndex, 0);
430     assertEquals(pos.annotationIndex, -1);
431   }
432
433   @Test(groups = "Functional")
434   public void testFindMousePosition_wrapped_scaleAbove()
435   {
436     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
437     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
438     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
439             "examples/uniref50.fa", DataSourceType.FILE);
440     AlignViewportI av = alignFrame.getViewport();
441     av.setScaleAboveWrapped(true);
442     av.setScaleLeftWrapped(false);
443     av.setScaleRightWrapped(false);
444     alignFrame.alignPanel.updateLayout();
445
446     final int charHeight = av.getCharHeight();
447     final int charWidth = av.getCharWidth();
448     final int alignmentHeight = av.getAlignment().getHeight();
449     
450     // sanity checks:
451     assertTrue(charHeight > 0);
452     assertTrue(charWidth > 0);
453     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
454   
455     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
456     int x = 0;
457     int y = 0;
458   
459     /*
460      * mouse at top left of wrapped panel; there is a gap of charHeight
461      * above the alignment
462      */
463     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
464             0, 0, 0, false, 0);
465     MousePos pos = testee.findMousePosition(evt);
466     assertEquals(pos.column, 0);
467     assertEquals(pos.seqIndex, -1); // above sequences
468     assertEquals(pos.annotationIndex, -1);
469   
470     /*
471      * cursor at bottom of gap above
472      * two charHeights including scale panel
473      */
474     y = 2 * charHeight - 1;
475     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
476             false, 0);
477     pos = testee.findMousePosition(evt);
478     assertEquals(pos.seqIndex, -1);
479     assertEquals(pos.annotationIndex, -1);
480   
481     /*
482      * cursor over top of first sequence
483      */
484     y += 1;
485     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
486             false, 0);
487     pos = testee.findMousePosition(evt);
488     assertEquals(pos.seqIndex, 0);
489     assertEquals(pos.annotationIndex, -1);
490   
491     /*
492      * cursor at bottom of first sequence
493      */
494     y += charHeight - 1;
495     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
496             false, 0);
497     pos = testee.findMousePosition(evt);
498     assertEquals(pos.seqIndex, 0);
499     assertEquals(pos.annotationIndex, -1);
500   
501     /*
502      * cursor at top of second sequence
503      */
504     y += 1;
505     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
506             false, 0);
507     pos = testee.findMousePosition(evt);
508     assertEquals(pos.seqIndex, 1);
509     assertEquals(pos.annotationIndex, -1);
510   
511     /*
512      * cursor at bottom of second sequence
513      */
514     y += charHeight - 1;
515     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
516             false, 0);
517     pos = testee.findMousePosition(evt);
518     assertEquals(pos.seqIndex, 1);
519     assertEquals(pos.annotationIndex, -1);
520   
521     /*
522      * cursor at bottom of last sequence
523      * (scale + gap + sequences)
524      */
525     y = charHeight * (2 + alignmentHeight) - 1;
526     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
527             false, 0);
528     pos = testee.findMousePosition(evt);
529     assertEquals(pos.seqIndex, alignmentHeight - 1);
530     assertEquals(pos.annotationIndex, -1);
531   
532     /*
533      * cursor below sequences, in 3-pixel gap above annotations
534      */
535     y += 1;
536     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
537             false, 0);
538     pos = testee.findMousePosition(evt);
539     assertEquals(pos.seqIndex, alignmentHeight - 1);
540     assertEquals(pos.annotationIndex, -1);
541   
542     /*
543      * cursor still in the gap above annotations, now at the bottom of it
544      * method reports index of nearest sequence above  
545      */
546     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
547     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
548             false, 0);
549     pos = testee.findMousePosition(evt);
550     assertEquals(pos.seqIndex, alignmentHeight - 1);
551     assertEquals(pos.annotationIndex, -1);
552   
553     AlignmentAnnotation[] annotationRows = av.getAlignment().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, 0, 0,
561               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, 0, 0,
571               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, 0,
587             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, 0,
597             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, 0,
607             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, 0,
617             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, 0,
627             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     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, x, y,
664             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, 0,
675             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, 0,
685             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, 0,
695             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, 0,
705             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, 0,
715             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, x, 0,
738             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,
746             0, 0, 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, 0,
754             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, 0,
762             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, 0,
770             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, 0,
786             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, x, 0,
811             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,
819             0, 0, 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, 0,
827             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, 0,
840             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, 0,
845             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, 0,
855             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, 0,
870             false, 0);
871     assertEquals(testee.findColumn(evt), -1);
872     
873     // todo add startRes offset, hidden columns
874
875   }
876   @BeforeClass(alwaysRun = true)
877   public static void setUpBeforeClass() throws Exception
878   {
879     /*
880      * use read-only test properties file
881      */
882     Cache.loadProperties("test/jalview/io/testProps.jvprops");
883     Jalview.main(new String[] { "-nonews" });
884   }
885
886   /**
887    * waits for Swing event dispatch queue to empty
888    */
889   synchronized void waitForSwing()
890   {
891     try
892     {
893       EventQueue.invokeAndWait(new Runnable()
894       {
895         @Override
896         public void run()
897         {
898         }
899       });
900     } catch (InterruptedException | InvocationTargetException e)
901     {
902       e.printStackTrace();
903     }
904   }
905 }