aeab5cd4b134e63031f1a8a03c02e63c3b8b971b
[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     System.out.println("BH testing");
64     Jalview.setSynchronous(true);
65     JvOptionPane.setInteractiveMode(false);
66     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
67   }
68   @Test(groups = "Functional")
69   public void testSetStatusReturnsNearestResiduePosition()
70   {
71     SequenceI seq1 = new Sequence("Seq1", "AACDE");
72     SequenceI seq2 = new Sequence("Seq2", "AA--E");
73     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
74     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
75             al.getHeight());
76     AlignmentI visAl = alignFrame.getViewport().getAlignment();
77
78     // Test either side of gap
79     assertEquals(
80             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
81                     visAl.getSequenceAt(1), 1, 1), 2);
82     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
83             "Sequence 2 ID: Seq2 Residue: ALA (2)");
84     assertEquals(
85             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
86                     visAl.getSequenceAt(1), 4, 1), 3);
87     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
88             "Sequence 2 ID: Seq2 Residue: GLU (3)");
89     // no status message at a gap, returns next residue position to the right
90     assertEquals(
91             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
92                     visAl.getSequenceAt(1), 2, 1), 3);
93     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
94             "Sequence 2 ID: Seq2");
95     assertEquals(
96             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
97                     visAl.getSequenceAt(1), 3, 1), 3);
98     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
99             "Sequence 2 ID: Seq2");
100   }
101
102   @Test(groups = "Functional")
103   public void testAmbiguousAminoAcidGetsStatusMessage()
104   {
105     SequenceI seq1 = new Sequence("Seq1", "ABCDE");
106     SequenceI seq2 = new Sequence("Seq2", "AB--E");
107     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
108     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
109             al.getHeight());
110     AlignmentI visAl = alignFrame.getViewport().getAlignment();
111
112     assertEquals(
113             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
114                     visAl.getSequenceAt(1), 1, 1), 2);
115     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
116             "Sequence 2 ID: Seq2 Residue: B (2)");
117   }
118
119   @Test(groups = "Functional")
120   public void testGetEditStatusMessage()
121   {
122     assertNull(SeqPanel.getEditStatusMessage(null));
123
124     EditCommand edit = new EditCommand(); // empty
125     assertNull(SeqPanel.getEditStatusMessage(edit));
126
127     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
128     
129     // 1 gap
130     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-'));
131     String expected = MessageManager.formatMessage("label.insert_gap", "1");
132     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
133
134     // 3 more gaps makes +4
135     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 3, '-'));
136     expected = MessageManager.formatMessage("label.insert_gaps", "4");
137     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
138
139     // 2 deletes makes + 2
140     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
141     expected = MessageManager.formatMessage("label.insert_gaps", "2");
142     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
143
144     // 2 more deletes makes 0 - no text
145     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
146     assertNull(SeqPanel.getEditStatusMessage(edit));
147
148     // 1 more delete makes 1 delete
149     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
150     expected = MessageManager.formatMessage("label.delete_gap", "1");
151     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
152
153     // 1 more delete makes 2 deletes
154     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
155     expected = MessageManager.formatMessage("label.delete_gaps", "2");
156     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
157   }
158
159   /**
160    * Tests that simulate 'locked editing', where an inserted gap is balanced by
161    * a gap deletion in the selection group, and vice versa
162    */
163   @Test(groups = "Functional")
164   public void testGetEditStatusMessage_lockedEditing()
165   {
166     EditCommand edit = new EditCommand(); // empty
167     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
168     
169     // 1 gap inserted, balanced by 1 delete
170     Edit e1 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-');
171     edit.addEdit(e1);
172     Edit e2 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 1, '-');
173     e2.setSystemGenerated(true);
174     edit.addEdit(e2);
175     String expected = MessageManager.formatMessage("label.insert_gap", "1");
176     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
177   
178     // 2 more gaps makes +3
179     Edit e3 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
180     edit.addEdit(e3);
181     Edit e4 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 2, '-');
182     e4.setSystemGenerated(true);
183     edit.addEdit(e4);
184     expected = MessageManager.formatMessage("label.insert_gaps", "3");
185     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
186   
187     // 2 deletes makes + 1
188     Edit e5 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
189     edit.addEdit(e5);
190     Edit e6 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
191     e6.setSystemGenerated(true);
192     edit.addEdit(e6);
193     expected = MessageManager.formatMessage("label.insert_gap", "1");
194     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
195   
196     // 1 more delete makes 0 - no text
197     Edit e7 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
198     edit.addEdit(e7);
199     Edit e8 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
200     e8.setSystemGenerated(true);
201     edit.addEdit(e8);
202     expected = MessageManager.formatMessage("label.insert_gaps", "2");
203     assertNull(SeqPanel.getEditStatusMessage(edit));
204   
205     // 1 more delete makes 1 delete
206     Edit e9 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
207     edit.addEdit(e9);
208     Edit e10 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
209     e10.setSystemGenerated(true);
210     edit.addEdit(e10);
211     expected = MessageManager.formatMessage("label.delete_gap", "1");
212     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
213   
214     // 2 more deletes makes 3 deletes
215     Edit e11 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
216     edit.addEdit(e11);
217     Edit e12 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
218     e12.setSystemGenerated(true);
219     edit.addEdit(e12);
220     expected = MessageManager.formatMessage("label.delete_gaps", "3");
221     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
222   }
223
224   public void testFindMousePosition_unwrapped()
225   {
226     String seqData = ">Seq1\nAACDE\n>Seq2\nAA--E\n";
227     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(seqData,
228             DataSourceType.PASTE);
229     AlignViewportI av = alignFrame.getViewport();
230     av.setShowAnnotation(true);
231     av.setWrapAlignment(false);
232     final int charHeight = av.getCharHeight();
233     final int charWidth = av.getCharWidth();
234     // sanity checks:
235     assertTrue(charHeight > 0);
236     assertTrue(charWidth > 0);
237     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
238
239     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
240     int x = 0;
241     int y = 0;
242
243     /*
244      * mouse at top left of unwrapped panel
245      */
246     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
247             0, 0, 0, false, 0);
248     MousePos pos = testee.findMousePosition(evt);
249     assertEquals(pos.column, 0);
250     assertEquals(pos.seqIndex, 0);
251     assertEquals(pos.annotationIndex, -1);
252   }
253
254   @AfterMethod(alwaysRun = true)
255   public void tearDown()
256   {
257     Desktop.getInstance().closeAll_actionPerformed(null);
258   }
259
260   @Test(groups = "Functional")
261   public void testFindMousePosition_wrapped_annotations()
262   {
263     Cache.setPropertyNoSave("SHOW_ANNOTATIONS", "true");
264     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
265     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
266             "examples/uniref50.fa", DataSourceType.FILE);
267     AlignViewportI av = alignFrame.getViewport();
268     av.setScaleAboveWrapped(false);
269     av.setScaleLeftWrapped(false);
270     av.setScaleRightWrapped(false);
271
272     alignFrame.alignPanel.updateLayout();
273
274     final int charHeight = av.getCharHeight();
275     final int charWidth = av.getCharWidth();
276     final int alignmentHeight = av.getAlignment().getHeight();
277     
278     // sanity checks:
279     assertTrue(charHeight > 0);
280     assertTrue(charWidth > 0);
281     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
282   
283     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
284     int x = 0;
285     int y = 0;
286   
287     /*
288      * mouse at top left of wrapped panel; there is a gap of charHeight
289      * above the alignment
290      */
291     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
292             0, 0, 0, false, 0);
293     MousePos pos = testee.findMousePosition(evt);
294     assertEquals(pos.column, 0);
295     assertEquals(pos.seqIndex, -1); // above sequences
296     assertEquals(pos.annotationIndex, -1);
297
298     /*
299      * cursor at bottom of gap above
300      */
301     y = charHeight - 1;
302     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
303             false, 0);
304     pos = testee.findMousePosition(evt);
305     assertEquals(pos.seqIndex, -1);
306     assertEquals(pos.annotationIndex, -1);
307
308     /*
309      * cursor over top of first sequence
310      */
311     y = charHeight;
312     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
313             false, 0);
314     pos = testee.findMousePosition(evt);
315     assertEquals(pos.seqIndex, 0);
316     assertEquals(pos.annotationIndex, -1);
317
318     /*
319      * cursor at bottom of first sequence
320      */
321     y = 2 * charHeight - 1;
322     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
323             false, 0);
324     pos = testee.findMousePosition(evt);
325     assertEquals(pos.seqIndex, 0);
326     assertEquals(pos.annotationIndex, -1);
327
328     /*
329      * cursor at top of second sequence
330      */
331     y = 2 * charHeight;
332     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
333             false, 0);
334     pos = testee.findMousePosition(evt);
335     assertEquals(pos.seqIndex, 1);
336     assertEquals(pos.annotationIndex, -1);
337
338     /*
339      * cursor at bottom of second sequence
340      */
341     y = 3 * charHeight - 1;
342     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
343             false, 0);
344     pos = testee.findMousePosition(evt);
345     assertEquals(pos.seqIndex, 1);
346     assertEquals(pos.annotationIndex, -1);
347
348     /*
349      * cursor at bottom of last sequence
350      */
351     y = charHeight * (1 + alignmentHeight) - 1;
352     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
353             false, 0);
354     pos = testee.findMousePosition(evt);
355     assertEquals(pos.seqIndex, alignmentHeight - 1);
356     assertEquals(pos.annotationIndex, -1);
357
358     /*
359      * cursor below sequences, in 3-pixel gap above annotations
360      * method reports index of nearest sequence above
361      */
362     y += 1;
363     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
364             false, 0);
365     pos = testee.findMousePosition(evt);
366     assertEquals(pos.seqIndex, alignmentHeight - 1);
367     assertEquals(pos.annotationIndex, -1);
368
369     /*
370      * cursor still in the gap above annotations, now at the bottom of it
371      */
372     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
373     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
374             false, 0);
375     pos = testee.findMousePosition(evt);
376     assertEquals(pos.seqIndex, alignmentHeight - 1);
377     assertEquals(pos.annotationIndex, -1);
378
379     AlignmentAnnotation[] annotationRows = av.getAlignment()
380             .getAlignmentAnnotation();
381     for (int n = 0; n < annotationRows.length; n++)
382     {
383       /*
384        * cursor at the top of the n'th annotation  
385        */
386       y += 1;
387       evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
388               false, 0);
389       pos = testee.findMousePosition(evt);
390       assertEquals(pos.seqIndex, alignmentHeight - 1);
391       assertEquals(pos.annotationIndex, n); // over n'th annotation
392
393       /*
394        * cursor at the bottom of the n'th annotation  
395        */
396       y += annotationRows[n].height - 1;
397       evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
398               false, 0);
399       pos = testee.findMousePosition(evt);
400       assertEquals(pos.seqIndex, alignmentHeight - 1);
401       assertEquals(pos.annotationIndex, n);
402     }
403
404     /*
405      * cursor in gap between wrapped widths  
406      */
407     y += 1;
408     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
409             false, 0);
410     pos = testee.findMousePosition(evt);
411     assertEquals(pos.seqIndex, -1);
412     assertEquals(pos.annotationIndex, -1);
413
414     /*
415      * cursor at bottom of gap between wrapped widths  
416      */
417     y += charHeight - 1;
418     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
419             false, 0);
420     pos = testee.findMousePosition(evt);
421     assertEquals(pos.seqIndex, -1);
422     assertEquals(pos.annotationIndex, -1);
423
424     /*
425      * cursor at top of first sequence, second wrapped width  
426      */
427     y += 1;
428     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
429             false, 0);
430     pos = testee.findMousePosition(evt);
431     assertEquals(pos.seqIndex, 0);
432     assertEquals(pos.annotationIndex, -1);
433   }
434
435   @Test(groups = "Functional")
436   public void testFindMousePosition_wrapped_scaleAbove()
437   {
438     Cache.setPropertyNoSave("SHOW_ANNOTATIONS", "true");
439     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
440     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
441             "examples/uniref50.fa", DataSourceType.FILE);
442     AlignViewportI av = alignFrame.getViewport();
443     av.setScaleAboveWrapped(true);
444     av.setScaleLeftWrapped(false);
445     av.setScaleRightWrapped(false);
446     alignFrame.alignPanel.updateLayout();
447
448     final int charHeight = av.getCharHeight();
449     final int charWidth = av.getCharWidth();
450     final int alignmentHeight = av.getAlignment().getHeight();
451     
452     // sanity checks:
453     assertTrue(charHeight > 0);
454     assertTrue(charWidth > 0);
455     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
456   
457     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
458     int x = 0;
459     int y = 0;
460   
461     /*
462      * mouse at top left of wrapped panel; there is a gap of charHeight
463      * above the alignment
464      */
465     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
466             0, 0, 0, false, 0);
467     MousePos pos = testee.findMousePosition(evt);
468     assertEquals(pos.column, 0);
469     assertEquals(pos.seqIndex, -1); // above sequences
470     assertEquals(pos.annotationIndex, -1);
471   
472     /*
473      * cursor at bottom of gap above
474      * two charHeights including scale panel
475      */
476     y = 2 * charHeight - 1;
477     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
478             false, 0);
479     pos = testee.findMousePosition(evt);
480     assertEquals(pos.seqIndex, -1);
481     assertEquals(pos.annotationIndex, -1);
482   
483     /*
484      * cursor over top of first sequence
485      */
486     y += 1;
487     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
488             false, 0);
489     pos = testee.findMousePosition(evt);
490     assertEquals(pos.seqIndex, 0);
491     assertEquals(pos.annotationIndex, -1);
492   
493     /*
494      * cursor at bottom of first sequence
495      */
496     y += charHeight - 1;
497     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
498             false, 0);
499     pos = testee.findMousePosition(evt);
500     assertEquals(pos.seqIndex, 0);
501     assertEquals(pos.annotationIndex, -1);
502   
503     /*
504      * cursor at top of second sequence
505      */
506     y += 1;
507     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
508             false, 0);
509     pos = testee.findMousePosition(evt);
510     assertEquals(pos.seqIndex, 1);
511     assertEquals(pos.annotationIndex, -1);
512   
513     /*
514      * cursor at bottom of second sequence
515      */
516     y += charHeight - 1;
517     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
518             false, 0);
519     pos = testee.findMousePosition(evt);
520     assertEquals(pos.seqIndex, 1);
521     assertEquals(pos.annotationIndex, -1);
522   
523     /*
524      * cursor at bottom of last sequence
525      * (scale + gap + sequences)
526      */
527     y = charHeight * (2 + alignmentHeight) - 1;
528     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
529             false, 0);
530     pos = testee.findMousePosition(evt);
531     assertEquals(pos.seqIndex, alignmentHeight - 1);
532     assertEquals(pos.annotationIndex, -1);
533   
534     /*
535      * cursor below sequences, in 3-pixel gap above annotations
536      */
537     y += 1;
538     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
539             false, 0);
540     pos = testee.findMousePosition(evt);
541     assertEquals(pos.seqIndex, alignmentHeight - 1);
542     assertEquals(pos.annotationIndex, -1);
543   
544     /*
545      * cursor still in the gap above annotations, now at the bottom of it
546      * method reports index of nearest sequence above  
547      */
548     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
549     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
550             false, 0);
551     pos = testee.findMousePosition(evt);
552     assertEquals(pos.seqIndex, alignmentHeight - 1);
553     assertEquals(pos.annotationIndex, -1);
554   
555     AlignmentAnnotation[] annotationRows = av.getAlignment().getAlignmentAnnotation();
556     for (int n = 0; n < annotationRows.length; n++)
557     {
558       /*
559        * cursor at the top of the n'th annotation  
560        */
561       y += 1;
562       evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
563               false, 0);
564       pos = testee.findMousePosition(evt);
565       assertEquals(pos.seqIndex, alignmentHeight - 1);
566       assertEquals(pos.annotationIndex, n); // over n'th annotation
567
568       /*
569        * cursor at the bottom of the n'th annotation  
570        */
571       y += annotationRows[n].height - 1;
572       evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
573               false, 0);
574       pos = testee.findMousePosition(evt);
575       assertEquals(pos.seqIndex, alignmentHeight - 1);
576       assertEquals(pos.annotationIndex, n);
577     }
578   
579     /*
580      * cursor in gap between wrapped widths  
581      */
582     y += 1;
583     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
584             false, 0);
585     pos = testee.findMousePosition(evt);
586     assertEquals(pos.seqIndex, -1);
587     assertEquals(pos.annotationIndex, -1);
588   
589     /*
590      * cursor at bottom of gap between wrapped widths  
591      */
592     y += charHeight - 1;
593     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
594             false, 0);
595     pos = testee.findMousePosition(evt);
596     assertEquals(pos.seqIndex, -1);
597     assertEquals(pos.annotationIndex, -1);
598   
599     /*
600      * cursor at top of scale, second wrapped width  
601      */
602     y += 1;
603     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
604             false, 0);
605     pos = testee.findMousePosition(evt);
606     assertEquals(pos.seqIndex, -1);
607     assertEquals(pos.annotationIndex, -1);
608
609     /*
610      * cursor at bottom of scale, second wrapped width  
611      */
612     y += charHeight - 1;
613     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
614             false, 0);
615     pos = testee.findMousePosition(evt);
616     assertEquals(pos.seqIndex, -1);
617     assertEquals(pos.annotationIndex, -1);
618
619     /*
620      * cursor at top of first sequence, second wrapped width  
621      */
622     y += 1;
623     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
624             false, 0);
625     pos = testee.findMousePosition(evt);
626     assertEquals(pos.seqIndex, 0);
627     assertEquals(pos.annotationIndex, -1);
628   }
629
630   @Test(groups = "Functional")
631   public void testFindMousePosition_wrapped_noAnnotations()
632   {
633     Cache.setPropertyNoSave("SHOW_ANNOTATIONS", "false");
634     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
635     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
636             "examples/uniref50.fa", DataSourceType.FILE);
637     AlignViewportI av = alignFrame.getViewport();
638     av.setScaleAboveWrapped(false);
639     av.setScaleLeftWrapped(false);
640     av.setScaleRightWrapped(false);
641     alignFrame.alignPanel.updateLayout();
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);
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.setPropertyNoSave("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      * mouse at top left of unwrapped panel
731      */
732     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
733             0, 0, 0, false, 0);
734     assertEquals(testee.findColumn(evt), 0);
735     
736     /*
737      * not quite one charWidth across
738      */
739     x = charWidth-1;
740     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
741             0, 0, 0, false, 0);
742     assertEquals(testee.findColumn(evt), 0);
743
744     /*
745      * one charWidth across
746      */
747     x = charWidth;
748     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
749             false, 0);
750     assertEquals(testee.findColumn(evt), 1);
751
752     /*
753      * two charWidths across
754      */
755     x = 2 * charWidth;
756     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
757             false, 0);
758     assertEquals(testee.findColumn(evt), 2);
759
760     /*
761      * limited to last column of seqcanvas
762      */
763     x = 20000;
764     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
765             false, 0);
766     SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas;
767     int w = seqCanvas.getWidth();
768     // limited to number of whole columns, base 0
769
770     int expected = w / charWidth;
771     expected = Math.min(
772             alignFrame.getViewport().getRanges().getEndRes(),
773             expected);
774     int col = testee.findColumn(evt);
775     assertEquals(col, expected);
776
777     /*
778      * hide columns 5-10 (base 1)
779      */
780     alignFrame.getViewport().hideColumns(4, 9);
781     x = 5 * charWidth + 2;
782     // x is in 6th visible column, absolute column 12, or 11 base 0
783     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
784             false, 0);
785     col = testee.findColumn(evt);
786     assertEquals(col, 11);
787   }
788
789   @Test(groups = "Functional")
790   public void testFindColumn_wrapped()
791   {
792     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
793     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
794             "examples/uniref50.fa", DataSourceType.FILE);
795     AlignViewport av = alignFrame.getViewport();
796     av.setScaleAboveWrapped(false);
797     av.setScaleLeftWrapped(false);
798     av.setScaleRightWrapped(false);
799     alignFrame.alignPanel.updateLayout();
800     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
801     int x = 0;
802     final int charWidth = av.getCharWidth();
803     assertTrue(charWidth > 0); // sanity check
804     assertEquals(av.getRanges().getStartRes(), 0);
805   
806     /*
807      * mouse at top left of wrapped panel, no West (left) scale
808      */
809     MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
810             0, 0, 0, false, 0);
811     assertEquals(testee.findColumn(evt), 0);
812     
813     /*
814      * not quite one charWidth across
815      */
816     x = charWidth-1;
817     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
818             0, 0, 0, false, 0);
819     assertEquals(testee.findColumn(evt), 0);
820   
821     /*
822      * one charWidth across
823      */
824     x = charWidth;
825     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
826             false, 0);
827     assertEquals(testee.findColumn(evt), 1);
828
829     /*
830      * x over scale left (before drawn columns) results in -1
831      */
832     av.setScaleLeftWrapped(true);
833     alignFrame.alignPanel.updateLayout();
834     SeqCanvas seqCanvas = testee.seqCanvas;
835     int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest");
836     assertTrue(labelWidth > 0);
837     x = labelWidth - 1;
838     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
839             false, 0);
840     assertEquals(testee.findColumn(evt), -1);
841
842     x = labelWidth;
843     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
844             false, 0);
845     assertEquals(testee.findColumn(evt), 0);
846
847     /*
848      * x over right edge of last residue (including scale left)
849      */
850     int residuesWide = av.getRanges().getViewportWidth();
851     assertTrue(residuesWide > 0);
852     x = labelWidth + charWidth * residuesWide - 1;
853     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
854             false, 0);
855     assertEquals(testee.findColumn(evt), residuesWide - 1);
856
857     /*
858      * x over scale right (beyond drawn columns) results in -1
859      */
860     av.setScaleRightWrapped(true);
861     alignFrame.alignPanel.updateLayout();
862     labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast");
863     assertTrue(labelWidth > 0);
864     int residuesWide2 = av.getRanges().getViewportWidth();
865     assertTrue(residuesWide2 > 0);
866     assertTrue(residuesWide2 < residuesWide); // available width reduced
867     x += 1; // just over left edge of scale right
868     evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
869             false, 0);
870     assertEquals(testee.findColumn(evt), -1);
871     
872     // todo add startRes offset, hidden columns
873
874   }
875   @BeforeClass(alwaysRun = true)
876   public static void setUpBeforeClass() throws Exception
877   {
878     /*
879      * use read-only test properties file
880      */
881     Cache.loadProperties("test/jalview/io/testProps.jvprops");
882     Jalview.main(new String[] { "-nonews" });
883   }
884
885   /**
886    * waits for Swing event dispatch queue to empty
887    */
888   synchronized void waitForSwing()
889   {
890     try
891     {
892       EventQueue.invokeAndWait(new Runnable()
893       {
894         @Override
895         public void run()
896         {
897         }
898       });
899     } catch (InterruptedException | InvocationTargetException e)
900     {
901       e.printStackTrace();
902     }
903   }
904 }