b0fc116d31da3b72fc47efb9027e826f23e8faf8
[jalview.git] / test / jalview / gui / SeqPanelTest.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertNull;
25 import static org.testng.Assert.assertTrue;
26
27 import java.awt.EventQueue;
28 import java.awt.FontMetrics;
29 import java.awt.event.MouseEvent;
30 import java.lang.reflect.InvocationTargetException;
31
32 import javax.swing.JLabel;
33
34 import org.testng.annotations.AfterMethod;
35 import org.testng.annotations.BeforeClass;
36 import org.testng.annotations.Test;
37
38 import jalview.api.AlignViewportI;
39 import jalview.bin.Cache;
40 import jalview.bin.Jalview;
41 import jalview.commands.EditCommand;
42 import jalview.commands.EditCommand.Action;
43 import jalview.commands.EditCommand.Edit;
44 import jalview.datamodel.Alignment;
45 import jalview.datamodel.AlignmentAnnotation;
46 import jalview.datamodel.AlignmentI;
47 import jalview.datamodel.SearchResults;
48 import jalview.datamodel.SearchResultsI;
49 import jalview.datamodel.Sequence;
50 import jalview.datamodel.SequenceI;
51 import jalview.gui.SeqPanel.MousePos;
52 import jalview.io.DataSourceType;
53 import jalview.io.FileLoader;
54 import jalview.util.MessageManager;
55 import jalview.viewmodel.ViewportRanges;
56 import junit.extensions.PA;
57
58 public class SeqPanelTest
59 {
60   AlignFrame af;
61
62   @BeforeClass(alwaysRun = true)
63   public void setUpJvOptionPane()
64   {
65     JvOptionPane.setInteractiveMode(false);
66     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
67   }
68   @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, MouseEvent.MOUSE_MOVED, 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.instance.closeAll_actionPerformed(null);
258   }
259
260   @Test(groups = "Functional")
261   public void testFindMousePosition_wrapped_annotations()
262   {
263     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
264     Cache.applicationProperties.setProperty("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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
439     Cache.applicationProperties.setProperty("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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 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, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
573               false, 0);
574       pos = testee.findMousePosition(evt);
575       SeqCanvas sc = testee.seqCanvas;
576       assertEquals(pos.seqIndex, alignmentHeight - 1,
577               String.format("%s n=%d y=%d %d, %d, %d, %d",
578                       annotationRows[n].label, n, y, sc.getWidth(),
579                       sc.getHeight(), sc.wrappedRepeatHeightPx,
580                       sc.wrappedSpaceAboveAlignment));
581       assertEquals(pos.annotationIndex, n);
582     }
583   
584     /*
585      * cursor in gap between wrapped widths  
586      */
587     y += 1;
588     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
589             false, 0);
590     pos = testee.findMousePosition(evt);
591     assertEquals(pos.seqIndex, -1);
592     assertEquals(pos.annotationIndex, -1);
593   
594     /*
595      * cursor at bottom of gap between wrapped widths  
596      */
597     y += charHeight - 1;
598     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
599             false, 0);
600     pos = testee.findMousePosition(evt);
601     assertEquals(pos.seqIndex, -1);
602     assertEquals(pos.annotationIndex, -1);
603   
604     /*
605      * cursor at top of scale, second wrapped width  
606      */
607     y += 1;
608     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
609             false, 0);
610     pos = testee.findMousePosition(evt);
611     assertEquals(pos.seqIndex, -1);
612     assertEquals(pos.annotationIndex, -1);
613
614     /*
615      * cursor at bottom of scale, second wrapped width  
616      */
617     y += charHeight - 1;
618     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
619             false, 0);
620     pos = testee.findMousePosition(evt);
621     assertEquals(pos.seqIndex, -1);
622     assertEquals(pos.annotationIndex, -1);
623
624     /*
625      * cursor at top of first sequence, second wrapped width  
626      */
627     y += 1;
628     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
629             false, 0);
630     pos = testee.findMousePosition(evt);
631     assertEquals(pos.seqIndex, 0);
632     assertEquals(pos.annotationIndex, -1);
633   }
634
635   @Test(groups = "Functional")
636   public void testFindMousePosition_wrapped_noAnnotations()
637   {
638     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
639     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
640     Cache.applicationProperties.setProperty("FONT_SIZE", "10");
641     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
642             "examples/uniref50.fa", DataSourceType.FILE);
643     AlignViewportI av = alignFrame.getViewport();
644     av.setScaleAboveWrapped(false);
645     av.setScaleLeftWrapped(false);
646     av.setScaleRightWrapped(false);
647     alignFrame.alignPanel.updateLayout();
648
649     final int charHeight = av.getCharHeight();
650     final int charWidth = av.getCharWidth();
651     final int alignmentHeight = av.getAlignment().getHeight();
652     
653     // sanity checks:
654     assertTrue(charHeight > 0);
655     assertTrue(charWidth > 0);
656     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
657   
658     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
659     int x = 0;
660     int y = 0;
661   
662     /*
663      * mouse at top left of wrapped panel; there is a gap of charHeight
664      * above the alignment
665      */
666     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
667             0, 0, 0, false, 0);
668     MousePos pos = testee.findMousePosition(evt);
669     assertEquals(pos.column, 0);
670     assertEquals(pos.seqIndex, -1); // above sequences
671     assertEquals(pos.annotationIndex, -1);
672   
673     /*
674      * cursor over top of first sequence
675      */
676     y = charHeight;
677     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
678             false, 0);
679     pos = testee.findMousePosition(evt);
680     assertEquals(pos.seqIndex, 0);
681     assertEquals(pos.annotationIndex, -1);
682
683     /*
684      * cursor at bottom of last sequence
685      */
686     y = charHeight * (1 + alignmentHeight) - 1;
687     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
688             false, 0);
689     pos = testee.findMousePosition(evt);
690     assertEquals(pos.seqIndex, alignmentHeight - 1);
691     assertEquals(pos.annotationIndex, -1);
692   
693     /*
694      * cursor below sequences, at top of charHeight gap between widths
695      */
696     y += 1;
697     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
698             false, 0);
699     pos = testee.findMousePosition(evt);
700     assertEquals(pos.seqIndex, -1);
701     assertEquals(pos.annotationIndex, -1);
702   
703     /*
704      * cursor below sequences, at top of charHeight gap between widths
705      */
706     y += charHeight - 1;
707     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
708             false, 0);
709     pos = testee.findMousePosition(evt);
710     assertEquals(pos.seqIndex, -1);
711     assertEquals(pos.annotationIndex, -1);
712   
713     /*
714      * cursor at the top of the first sequence, second width  
715      */
716     y += 1;
717     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
718             false, 0);
719     pos = testee.findMousePosition(evt);
720     assertEquals(pos.seqIndex, 0);
721     assertEquals(pos.annotationIndex, -1);
722   }
723
724   @Test(groups = "Functional")
725   public void testFindColumn_unwrapped()
726   {
727     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "false");
728     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
729             "examples/uniref50.fa", DataSourceType.FILE);
730     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
731     int x = 0;
732     final int charWidth = alignFrame.getViewport().getCharWidth();
733     assertTrue(charWidth > 0); // sanity check
734     ViewportRanges ranges = alignFrame.getViewport().getRanges();
735     assertEquals(ranges.getStartRes(), 0);
736
737     /*
738      * mouse at top left of unwrapped panel
739      */
740     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
741             0, 0, 0, false, 0);
742     assertEquals(testee.findColumn(evt), 0);
743     
744     /*
745      * not quite one charWidth across
746      */
747     x = charWidth-1;
748     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
749             0, 0, 0, false, 0);
750     assertEquals(testee.findColumn(evt), 0);
751
752     /*
753      * one charWidth across
754      */
755     x = charWidth;
756     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
757             false, 0);
758     assertEquals(testee.findColumn(evt), 1);
759
760     /*
761      * two charWidths across
762      */
763     x = 2 * charWidth;
764     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
765             false, 0);
766     assertEquals(testee.findColumn(evt), 2);
767
768     /*
769      * limited to last column of seqcanvas
770      */
771     x = 20000;
772     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
773             false, 0);
774     SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas;
775     int w = seqCanvas.getWidth();
776     // limited to number of whole columns, base 0,
777     // and to end of visible range
778     int expected = w / charWidth;
779     expected = Math.min(expected, ranges.getEndRes());
780     assertEquals(testee.findColumn(evt), expected);
781
782     /*
783      * hide columns 5-10 (base 1)
784      */
785     alignFrame.getViewport().hideColumns(4, 9);
786     x = 5 * charWidth + 2;
787     // x is in 6th visible column, absolute column 12, or 11 base 0
788     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
789             false, 0);
790     assertEquals(testee.findColumn(evt), 11);
791   }
792
793   @Test(groups = "Functional")
794   public void testFindColumn_wrapped()
795   {
796     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
797     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
798             "examples/uniref50.fa", DataSourceType.FILE);
799     AlignViewport av = alignFrame.getViewport();
800     av.setScaleAboveWrapped(false);
801     av.setScaleLeftWrapped(false);
802     av.setScaleRightWrapped(false);
803     alignFrame.alignPanel.updateLayout();
804     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
805     int x = 0;
806     final int charWidth = av.getCharWidth();
807     assertTrue(charWidth > 0); // sanity check
808     assertEquals(av.getRanges().getStartRes(), 0);
809   
810     /*
811      * mouse at top left of wrapped panel, no West (left) scale
812      */
813     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
814             0, 0, 0, false, 0);
815     assertEquals(testee.findColumn(evt), 0);
816     
817     /*
818      * not quite one charWidth across
819      */
820     x = charWidth-1;
821     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
822             0, 0, 0, false, 0);
823     assertEquals(testee.findColumn(evt), 0);
824   
825     /*
826      * one charWidth across
827      */
828     x = charWidth;
829     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
830             false, 0);
831     assertEquals(testee.findColumn(evt), 1);
832
833     /*
834      * x over scale left (before drawn columns) results in -1
835      */
836     av.setScaleLeftWrapped(true);
837     alignFrame.alignPanel.updateLayout();
838     SeqCanvas seqCanvas = testee.seqCanvas;
839     int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest");
840     assertTrue(labelWidth > 0);
841     x = labelWidth - 1;
842     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
843             false, 0);
844     assertEquals(testee.findColumn(evt), -1);
845
846     x = labelWidth;
847     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
848             false, 0);
849     assertEquals(testee.findColumn(evt), 0);
850
851     /*
852      * x over right edge of last residue (including scale left)
853      */
854     int residuesWide = av.getRanges().getViewportWidth();
855     assertTrue(residuesWide > 0);
856     x = labelWidth + charWidth * residuesWide - 1;
857     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
858             false, 0);
859     assertEquals(testee.findColumn(evt), residuesWide - 1);
860
861     /*
862      * x over scale right (beyond drawn columns) results in -1
863      */
864     av.setScaleRightWrapped(true);
865     alignFrame.alignPanel.updateLayout();
866     labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast");
867     assertTrue(labelWidth > 0);
868     int residuesWide2 = av.getRanges().getViewportWidth();
869     assertTrue(residuesWide2 > 0);
870     assertTrue(residuesWide2 < residuesWide); // available width reduced
871     x += 1; // just over left edge of scale right
872     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
873             false, 0);
874     assertEquals(testee.findColumn(evt), -1);
875     
876     // todo add startRes offset, hidden columns
877
878   }
879   @BeforeClass(alwaysRun = true)
880   public static void setUpBeforeClass() throws Exception
881   {
882     /*
883      * use read-only test properties file
884      */
885     Cache.loadProperties("test/jalview/io/testProps.jvprops");
886     Jalview.main(new String[] { "-nonews" });
887   }
888
889   /**
890    * waits for Swing event dispatch queue to empty
891    */
892   synchronized void waitForSwing()
893   {
894     try
895     {
896       EventQueue.invokeAndWait(new Runnable()
897       {
898         @Override
899         public void run()
900         {
901         }
902       });
903     } catch (InterruptedException | InvocationTargetException e)
904     {
905       e.printStackTrace();
906     }
907   }
908   @Test(groups = "Functional")
909   public void testFindMousePosition_wrapped_scales_longSequence()
910   {
911     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
912     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
913     Cache.applicationProperties.setProperty("FONT_SIZE", "14");
914     Cache.applicationProperties.setProperty("FONT_NAME", "SansSerif");
915     Cache.applicationProperties.setProperty("FONT_STYLE", "0");
916     // sequence of 50 bases, doubled 10 times, = 51200 bases
917     String dna = "ATGGCCATTGGGCCCAAATTTCCCAAAGGGTTTCCCTGAGGTCAGTCAGA";
918     for (int i = 0 ; i < 10 ; i++)
919     {
920       dna += dna;
921     }
922     assertEquals(dna.length(), 51200);
923     AlignFrame alignFrame = new FileLoader()
924             .LoadFileWaitTillLoaded(dna, DataSourceType.PASTE);
925     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
926     AlignViewport av = alignFrame.getViewport();
927     av.setScaleAboveWrapped(true);
928     av.setScaleLeftWrapped(true);
929     av.setScaleRightWrapped(true);
930     alignFrame.alignPanel.updateLayout();
931
932     try
933     {
934       Thread.sleep(200);
935     } catch (InterruptedException e)
936     {
937     }
938   
939     final int charHeight = av.getCharHeight();
940     final int charWidth = av.getCharWidth();
941     assertEquals(charHeight, 17);
942     assertEquals(charWidth, 12);
943     
944     FontMetrics fm = testee.getFontMetrics(av.getFont());
945     int labelWidth = fm.stringWidth("00000") + charWidth;
946     assertEquals(labelWidth, 57); // 5 x 9 + charWidth
947     assertEquals(testee.seqCanvas.getLabelWidthWest(), labelWidth);
948
949     int x = 0;
950     int y = 0;
951   
952     /*
953      * mouse at top left of wrapped panel; there is a gap of 2 * charHeight
954      * above the alignment
955      */
956     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
957             0, 0, 0, false, 0);
958     MousePos pos = testee.findMousePosition(evt);
959     assertEquals(pos.column, -1); // over scale left, not an alignment column
960     assertEquals(pos.seqIndex, -1); // above sequences
961     assertEquals(pos.annotationIndex, -1);
962
963     /*
964      * cursor over scale above first sequence
965      */
966     y += charHeight;
967     x = labelWidth;
968     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
969             false, 0);
970     pos = testee.findMousePosition(evt);
971     assertEquals(pos.seqIndex, -1);
972     assertEquals(pos.column, 0);
973     assertEquals(pos.annotationIndex, -1);
974     
975     /*
976      * cursor over scale left of first sequence
977      */
978     y += charHeight;
979     x = 0;
980     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
981             false, 0);
982     pos = testee.findMousePosition(evt);
983     assertEquals(pos.seqIndex, 0);
984     assertEquals(pos.column, -1);
985     assertEquals(pos.annotationIndex, -1);
986   
987     /*
988      * cursor over start of first sequence
989      */
990     x = labelWidth;
991     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
992             false, 0);
993     pos = testee.findMousePosition(evt);
994     assertEquals(pos.seqIndex, 0);
995     assertEquals(pos.column, 0);
996     assertEquals(pos.annotationIndex, -1);
997   
998     /*
999      * move one character right, to bottom pixel of same row
1000      */
1001     x += charWidth;
1002     y += charHeight - 1;
1003     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1004             false, 0);
1005     pos = testee.findMousePosition(evt);
1006     assertEquals(pos.seqIndex, 0);
1007     assertEquals(pos.column, 1);
1008     
1009     /*
1010      * move down one pixel - now in the no man's land between rows
1011      */
1012     y += 1;
1013     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1014             false, 0);
1015     pos = testee.findMousePosition(evt);
1016     assertEquals(pos.seqIndex, -1);
1017     assertEquals(pos.column, 1);
1018     
1019     /*
1020      * move down two char heights less one pixel - still in the no man's land
1021      * (scale above + spacer line)
1022      */
1023     y += (2 * charHeight - 1);
1024     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1025             false, 0);
1026     pos = testee.findMousePosition(evt);
1027     assertEquals(pos.seqIndex, -1);
1028     assertEquals(pos.column, 1);
1029     
1030     /*
1031      * move down one more pixel - now on the next row of the sequence
1032      */
1033     y += 1;
1034     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1035             false, 0);
1036     pos = testee.findMousePosition(evt);
1037     assertEquals(pos.seqIndex, 0);
1038     assertEquals(pos.column, 1 + av.getWrappedWidth());
1039     
1040     /*
1041      * scroll to near the end of the sequence
1042      */
1043     SearchResultsI sr = new SearchResults();
1044     int scrollTo = dna.length() - 1000;
1045     sr.addResult(av.getAlignment().getSequenceAt(0), scrollTo, scrollTo); 
1046     alignFrame.alignPanel.scrollToPosition(sr);
1047     
1048     /*
1049      * place the mouse on the first column of the 6th sequence, and
1050      * verify that (computed) findMousePosition matches (actual) ViewportRanges
1051      */
1052     x = labelWidth;
1053     y = 17 * charHeight; // 17 = 6 times two header rows and 5 sequence rows
1054     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1055             false, 0);
1056     pos = testee.findMousePosition(evt);
1057     assertEquals(pos.seqIndex, 0);
1058     int expected = av.getRanges().getStartRes() + 5 * av.getWrappedWidth();
1059     assertEquals(pos.column, expected);
1060   }
1061 }