fa0e35282dd4a99fe8ee16cc22eed37b947c7e8a
[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
43 import java.awt.Event;
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, Event.MOUSE_MOVE, 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     alignFrame.alignPanel.paintAlignment(false, false);
270     waitForSwing(); // for Swing thread
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, Event.MOUSE_MOVE, 0L, 0, x, y,
290             0, 0, 0, false, 0);
291     MousePos pos = testee.findMousePosition(evt, "291");
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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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, Event.MOUSE_MOVE, 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.paintAlignment(false, false);
445     waitForSwing();
446
447     final int charHeight = av.getCharHeight();
448     final int charWidth = av.getCharWidth();
449     final int alignmentHeight = av.getAlignment().getHeight();
450     
451     // sanity checks:
452     assertTrue(charHeight > 0);
453     assertTrue(charWidth > 0);
454     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
455   
456     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
457     int x = 0;
458     int y = 0;
459   
460     /*
461      * mouse at top left of wrapped panel; there is a gap of charHeight
462      * above the alignment
463      */
464     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
465             0, 0, 0, false, 0);
466     MousePos pos = testee.findMousePosition(evt, "466");
467     assertEquals(pos.column, 0);
468     assertEquals(pos.seqIndex, -1); // above sequences
469     assertEquals(pos.annotationIndex, -1);
470   
471     /*
472      * cursor at bottom of gap above
473      * two charHeights including scale panel
474      */
475     y = 2 * charHeight - 1;
476     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
477             false, 0);
478     pos = testee.findMousePosition(evt);
479     assertEquals(pos.seqIndex, -1);
480     assertEquals(pos.annotationIndex, -1);
481   
482     /*
483      * cursor over top of first sequence
484      */
485     y += 1;
486     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
487             false, 0);
488     pos = testee.findMousePosition(evt);
489     assertEquals(pos.seqIndex, 0);
490     assertEquals(pos.annotationIndex, -1);
491   
492     /*
493      * cursor at bottom of first sequence
494      */
495     y += charHeight - 1;
496     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
497             false, 0);
498     pos = testee.findMousePosition(evt);
499     assertEquals(pos.seqIndex, 0);
500     assertEquals(pos.annotationIndex, -1);
501   
502     /*
503      * cursor at top of second sequence
504      */
505     y += 1;
506     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
507             false, 0);
508     pos = testee.findMousePosition(evt);
509     assertEquals(pos.seqIndex, 1);
510     assertEquals(pos.annotationIndex, -1);
511   
512     /*
513      * cursor at bottom of second sequence
514      */
515     y += charHeight - 1;
516     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
517             false, 0);
518     pos = testee.findMousePosition(evt);
519     assertEquals(pos.seqIndex, 1);
520     assertEquals(pos.annotationIndex, -1);
521   
522     /*
523      * cursor at bottom of last sequence
524      * (scale + gap + sequences)
525      */
526     y = charHeight * (2 + alignmentHeight) - 1;
527     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
528             false, 0);
529     pos = testee.findMousePosition(evt);
530     assertEquals(pos.seqIndex, alignmentHeight - 1);
531     assertEquals(pos.annotationIndex, -1);
532   
533     /*
534      * cursor below sequences, in 3-pixel gap above annotations
535      */
536     y += 1;
537     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
538             false, 0);
539     pos = testee.findMousePosition(evt);
540     assertEquals(pos.seqIndex, alignmentHeight - 1);
541     assertEquals(pos.annotationIndex, -1);
542   
543     /*
544      * cursor still in the gap above annotations, now at the bottom of it
545      * method reports index of nearest sequence above  
546      */
547     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
548     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
549             false, 0);
550     pos = testee.findMousePosition(evt);
551     assertEquals(pos.seqIndex, alignmentHeight - 1);
552     assertEquals(pos.annotationIndex, -1);
553   
554     AlignmentAnnotation[] annotationRows = av.getAlignment().getAlignmentAnnotation();
555     for (int n = 0; n < annotationRows.length; n++)
556     {
557       /*
558        * cursor at the top of the n'th annotation  
559        */
560       y += 1;
561       evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
562               false, 0);
563       pos = testee.findMousePosition(evt);
564       assertEquals(pos.seqIndex, alignmentHeight - 1);
565       assertEquals(pos.annotationIndex, n); // over n'th annotation
566
567       /*
568        * cursor at the bottom of the n'th annotation  
569        */
570       y += annotationRows[n].height - 1;
571       evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
572               false, 0);
573       pos = testee.findMousePosition(evt);
574       assertEquals(pos.seqIndex, alignmentHeight - 1);
575       assertEquals(pos.annotationIndex, n);
576     }
577   
578     /*
579      * cursor in gap between wrapped widths  
580      */
581     y += 1;
582     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
583             false, 0);
584     pos = testee.findMousePosition(evt);
585     assertEquals(pos.seqIndex, -1);
586     assertEquals(pos.annotationIndex, -1);
587   
588     /*
589      * cursor at bottom of gap between wrapped widths  
590      */
591     y += charHeight - 1;
592     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
593             false, 0);
594     pos = testee.findMousePosition(evt);
595     assertEquals(pos.seqIndex, -1);
596     assertEquals(pos.annotationIndex, -1);
597   
598     /*
599      * cursor at top of scale, second wrapped width  
600      */
601     y += 1;
602     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
603             false, 0);
604     pos = testee.findMousePosition(evt);
605     assertEquals(pos.seqIndex, -1);
606     assertEquals(pos.annotationIndex, -1);
607
608     /*
609      * cursor at bottom of scale, second wrapped width  
610      */
611     y += charHeight - 1;
612     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
613             false, 0);
614     pos = testee.findMousePosition(evt);
615     assertEquals(pos.seqIndex, -1);
616     assertEquals(pos.annotationIndex, -1);
617
618     /*
619      * cursor at top of first sequence, second wrapped width  
620      */
621     y += 1;
622     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
623             false, 0);
624     pos = testee.findMousePosition(evt);
625     assertEquals(pos.seqIndex, 0);
626     assertEquals(pos.annotationIndex, -1);
627   }
628
629   @Test(groups = "Functional")
630   public void testFindMousePosition_wrapped_noAnnotations()
631   {
632     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
633     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
634     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
635             "examples/uniref50.fa", DataSourceType.FILE);
636     AlignViewportI av = alignFrame.getViewport();
637     av.setScaleAboveWrapped(false);
638     av.setScaleLeftWrapped(false);
639     av.setScaleRightWrapped(false);
640     alignFrame.alignPanel.paintAlignment(false, false);
641     waitForSwing();
642
643     final int charHeight = av.getCharHeight();
644     final int charWidth = av.getCharWidth();
645     final int alignmentHeight = av.getAlignment().getHeight();
646     
647     // sanity checks:
648     assertTrue(charHeight > 0);
649     assertTrue(charWidth > 0);
650     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
651   
652     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
653     int x = 0;
654     int y = 0;
655   
656     /*
657      * mouse at top left of wrapped panel; there is a gap of charHeight
658      * above the alignment
659      */
660     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
661             0, 0, 0, false, 0);
662     MousePos pos = testee.findMousePosition(evt, "662");
663     assertEquals(pos.column, 0);
664     assertEquals(pos.seqIndex, -1); // above sequences
665     assertEquals(pos.annotationIndex, -1);
666   
667     /*
668      * cursor over top of first sequence
669      */
670     y = charHeight;
671     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
672             false, 0);
673     pos = testee.findMousePosition(evt);
674     assertEquals(pos.seqIndex, 0);
675     assertEquals(pos.annotationIndex, -1);
676
677     /*
678      * cursor at bottom of last sequence
679      */
680     y = charHeight * (1 + alignmentHeight) - 1;
681     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
682             false, 0);
683     pos = testee.findMousePosition(evt);
684     assertEquals(pos.seqIndex, alignmentHeight - 1);
685     assertEquals(pos.annotationIndex, -1);
686   
687     /*
688      * cursor below sequences, at top of charHeight gap between widths
689      */
690     y += 1;
691     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
692             false, 0);
693     pos = testee.findMousePosition(evt);
694     assertEquals(pos.seqIndex, -1);
695     assertEquals(pos.annotationIndex, -1);
696   
697     /*
698      * cursor below sequences, at top of charHeight gap between widths
699      */
700     y += charHeight - 1;
701     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
702             false, 0);
703     pos = testee.findMousePosition(evt);
704     assertEquals(pos.seqIndex, -1);
705     assertEquals(pos.annotationIndex, -1);
706   
707     /*
708      * cursor at the top of the first sequence, second width  
709      */
710     y += 1;
711     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
712             false, 0);
713     pos = testee.findMousePosition(evt);
714     assertEquals(pos.seqIndex, 0);
715     assertEquals(pos.annotationIndex, -1);
716   }
717
718   @Test(groups = "Functional")
719   public void testFindColumn_unwrapped()
720   {
721     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "false");
722     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
723             "examples/uniref50.fa", DataSourceType.FILE);
724     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
725     int x = 0;
726     final int charWidth = alignFrame.getViewport().getCharWidth();
727     assertTrue(charWidth > 0); // sanity check
728     assertEquals(alignFrame.getViewport().getRanges().getStartRes(), 0);
729
730     /*
731      * mouse at top left of unwrapped panel
732      */
733     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
734             0, 0, 0, false, 0);
735     assertEquals(testee.findColumn(evt), 0);
736     
737     /*
738      * not quite one charWidth across
739      */
740     x = charWidth-1;
741     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
742             0, 0, 0, false, 0);
743     assertEquals(testee.findColumn(evt), 0);
744
745     /*
746      * one charWidth across
747      */
748     x = charWidth;
749     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
750             false, 0);
751     assertEquals(testee.findColumn(evt), 1);
752
753     /*
754      * two charWidths across
755      */
756     x = 2 * charWidth;
757     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
758             false, 0);
759     assertEquals(testee.findColumn(evt), 2);
760
761     /*
762      * limited to last column of seqcanvas
763      */
764     x = 20000;
765     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
766             false, 0);
767     SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas;
768     int w = seqCanvas.getWidth();
769     // limited to number of whole columns, base 0
770     int expected = w / charWidth - 1;
771     assertEquals(testee.findColumn(evt), expected);
772
773     /*
774      * hide columns 5-10 (base 1)
775      */
776     alignFrame.getViewport().hideColumns(4, 9);
777     x = 5 * charWidth + 2;
778     // x is in 6th visible column, absolute column 12, or 11 base 0
779     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
780             false, 0);
781     assertEquals(testee.findColumn(evt), 11);
782   }
783
784   @Test(groups = "Functional")
785   public void testFindColumn_wrapped()
786   {
787     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
788     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
789             "examples/uniref50.fa", DataSourceType.FILE);
790     AlignViewport av = alignFrame.getViewport();
791     av.setScaleAboveWrapped(false);
792     av.setScaleLeftWrapped(false);
793     av.setScaleRightWrapped(false);
794     alignFrame.alignPanel.paintAlignment(false, false);
795     // need to wait for repaint to finish!
796     waitForSwing();
797     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
798     int x = 0;
799     final int charWidth = av.getCharWidth();
800     assertTrue(charWidth > 0); // sanity check
801     assertEquals(av.getRanges().getStartRes(), 0);
802   
803     /*
804      * mouse at top left of wrapped panel, no West (left) scale
805      */
806     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
807             0, 0, 0, false, 0);
808     assertEquals(testee.findColumn(evt, "808"), 0);
809     
810     /*
811      * not quite one charWidth across
812      */
813     x = charWidth-1;
814     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
815             0, 0, 0, false, 0);
816     assertEquals(testee.findColumn(evt), 0);
817   
818     /*
819      * one charWidth across
820      */
821     x = charWidth;
822     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
823             false, 0);
824     assertEquals(testee.findColumn(evt), 1);
825
826     /*
827      * x over scale left (before drawn columns) results in -1
828      */
829     av.setScaleLeftWrapped(true);
830     alignFrame.alignPanel.paintAlignment(false, false);
831     waitForSwing();
832     SeqCanvas seqCanvas = testee.seqCanvas;
833     int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest");
834     assertTrue(labelWidth > 0);
835     x = labelWidth - 1;
836     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
837             false, 0);
838     assertEquals(testee.findColumn(evt), -1);
839
840     x = labelWidth;
841     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
842             false, 0);
843     assertEquals(testee.findColumn(evt), 0);
844
845     /*
846      * x over right edge of last residue (including scale left)
847      */
848     int residuesWide = av.getRanges().getViewportWidth();
849     assertTrue(residuesWide > 0);
850     x = labelWidth + charWidth * residuesWide - 1;
851     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
852             false, 0);
853     assertEquals(testee.findColumn(evt), residuesWide - 1);
854
855     /*
856      * x over scale right (beyond drawn columns) results in -1
857      */
858     av.setScaleRightWrapped(true);
859     alignFrame.alignPanel.paintAlignment(false, false);
860     waitForSwing();
861     labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast");
862     assertTrue(labelWidth > 0);
863     int residuesWide2 = av.getRanges().getViewportWidth();
864     assertTrue(residuesWide2 > 0);
865     assertTrue(residuesWide2 < residuesWide); // available width reduced
866     x += 1; // just over left edge of scale right
867     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
868             false, 0);
869     assertEquals(testee.findColumn(evt), -1);
870     
871     // todo add startRes offset, hidden columns
872
873   }
874   @BeforeClass(alwaysRun = true)
875   public static void setUpBeforeClass() throws Exception
876   {
877     /*
878      * use read-only test properties file
879      */
880     Cache.loadProperties("test/jalview/io/testProps.jvprops");
881     Jalview.main(new String[] { "-nonews" });
882   }
883
884   /**
885    * waits for Swing event dispatch queue to empty
886    */
887   synchronized void waitForSwing()
888   {
889     try
890     {
891       EventQueue.invokeAndWait(new Runnable()
892       {
893         @Override
894         public void run()
895         {
896         }
897       });
898     } catch (InterruptedException | InvocationTargetException e)
899     {
900       e.printStackTrace();
901     }
902   }
903 }