33bbeecbb895babf49d5401ebac941285ac74b7f
[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 import jalview.api.AlignViewportI;
38 import jalview.bin.Cache;
39 import jalview.bin.Jalview;
40 import jalview.commands.EditCommand;
41 import jalview.commands.EditCommand.Action;
42 import jalview.commands.EditCommand.Edit;
43 import jalview.datamodel.Alignment;
44 import jalview.datamodel.AlignmentAnnotation;
45 import jalview.datamodel.AlignmentI;
46 import jalview.datamodel.SearchResults;
47 import jalview.datamodel.SearchResultsI;
48 import jalview.datamodel.Sequence;
49 import jalview.datamodel.SequenceI;
50 import jalview.gui.SeqPanel.MousePos;
51 import jalview.io.DataSourceType;
52 import jalview.io.FileLoader;
53 import jalview.util.MessageManager;
54 import jalview.viewmodel.ViewportRanges;
55
56
57 import junit.extensions.PA;
58
59 public class SeqPanelTest
60 {
61   AlignFrame af;
62
63   @BeforeClass(alwaysRun = true)
64   public void setUpJvOptionPane()
65   {
66     JvOptionPane.setInteractiveMode(false);
67     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
68   }
69   @Test(groups = "Functional")
70   public void testSetStatusReturnsNearestResiduePosition()
71   {
72     SequenceI seq1 = new Sequence("Seq1", "AACDE");
73     SequenceI seq2 = new Sequence("Seq2", "AA--E");
74     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
75     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
76             al.getHeight());
77     AlignmentI visAl = alignFrame.getViewport().getAlignment();
78
79     // Test either side of gap
80     assertEquals(
81             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
82                     visAl.getSequenceAt(1), 1, 1), 2);
83     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
84             "Sequence 2 ID: Seq2 Residue: ALA (2)");
85     assertEquals(
86             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
87                     visAl.getSequenceAt(1), 4, 1), 3);
88     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
89             "Sequence 2 ID: Seq2 Residue: GLU (3)");
90     // no status message at a gap, returns next residue position to the right
91     assertEquals(
92             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
93                     visAl.getSequenceAt(1), 2, 1), 3);
94     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
95             "Sequence 2 ID: Seq2");
96     assertEquals(
97             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
98                     visAl.getSequenceAt(1), 3, 1), 3);
99     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
100             "Sequence 2 ID: Seq2");
101   }
102
103   @Test(groups = "Functional")
104   public void testAmbiguousAminoAcidGetsStatusMessage()
105   {
106     SequenceI seq1 = new Sequence("Seq1", "ABCDE");
107     SequenceI seq2 = new Sequence("Seq2", "AB--E");
108     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
109     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
110             al.getHeight());
111     AlignmentI visAl = alignFrame.getViewport().getAlignment();
112
113     assertEquals(
114             alignFrame.alignPanel.getSeqPanel().setStatusMessage(
115                     visAl.getSequenceAt(1), 1, 1), 2);
116     assertEquals(((JLabel) PA.getValue(alignFrame, "statusBar")).getText(),
117             "Sequence 2 ID: Seq2 Residue: B (2)");
118   }
119
120   @Test(groups = "Functional")
121   public void testGetEditStatusMessage()
122   {
123     assertNull(SeqPanel.getEditStatusMessage(null));
124
125     EditCommand edit = new EditCommand(); // empty
126     assertNull(SeqPanel.getEditStatusMessage(edit));
127
128     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
129     
130     // 1 gap
131     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-'));
132     String expected = MessageManager.formatMessage("label.insert_gap", "1");
133     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
134
135     // 3 more gaps makes +4
136     edit.addEdit(edit.new Edit(Action.INSERT_GAP, seqs, 1, 3, '-'));
137     expected = MessageManager.formatMessage("label.insert_gaps", "4");
138     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
139
140     // 2 deletes makes + 2
141     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
142     expected = MessageManager.formatMessage("label.insert_gaps", "2");
143     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
144
145     // 2 more deletes makes 0 - no text
146     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-'));
147     assertNull(SeqPanel.getEditStatusMessage(edit));
148
149     // 1 more delete makes 1 delete
150     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
151     expected = MessageManager.formatMessage("label.delete_gap", "1");
152     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
153
154     // 1 more delete makes 2 deletes
155     edit.addEdit(edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-'));
156     expected = MessageManager.formatMessage("label.delete_gaps", "2");
157     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
158   }
159
160   /**
161    * Tests that simulate 'locked editing', where an inserted gap is balanced by
162    * a gap deletion in the selection group, and vice versa
163    */
164   @Test(groups = "Functional")
165   public void testGetEditStatusMessage_lockedEditing()
166   {
167     EditCommand edit = new EditCommand(); // empty
168     SequenceI[] seqs = new SequenceI[] { new Sequence("a", "b") };
169     
170     // 1 gap inserted, balanced by 1 delete
171     Edit e1 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 1, '-');
172     edit.addEdit(e1);
173     Edit e2 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 1, '-');
174     e2.setSystemGenerated(true);
175     edit.addEdit(e2);
176     String expected = MessageManager.formatMessage("label.insert_gap", "1");
177     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
178   
179     // 2 more gaps makes +3
180     Edit e3 = edit.new Edit(Action.INSERT_GAP, seqs, 1, 2, '-');
181     edit.addEdit(e3);
182     Edit e4 = edit.new Edit(Action.DELETE_GAP, seqs, 5, 2, '-');
183     e4.setSystemGenerated(true);
184     edit.addEdit(e4);
185     expected = MessageManager.formatMessage("label.insert_gaps", "3");
186     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
187   
188     // 2 deletes makes + 1
189     Edit e5 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
190     edit.addEdit(e5);
191     Edit e6 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
192     e6.setSystemGenerated(true);
193     edit.addEdit(e6);
194     expected = MessageManager.formatMessage("label.insert_gap", "1");
195     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
196   
197     // 1 more delete makes 0 - no text
198     Edit e7 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
199     edit.addEdit(e7);
200     Edit e8 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
201     e8.setSystemGenerated(true);
202     edit.addEdit(e8);
203     expected = MessageManager.formatMessage("label.insert_gaps", "2");
204     assertNull(SeqPanel.getEditStatusMessage(edit));
205   
206     // 1 more delete makes 1 delete
207     Edit e9 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 1, '-');
208     edit.addEdit(e9);
209     Edit e10 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 1, '-');
210     e10.setSystemGenerated(true);
211     edit.addEdit(e10);
212     expected = MessageManager.formatMessage("label.delete_gap", "1");
213     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
214   
215     // 2 more deletes makes 3 deletes
216     Edit e11 = edit.new Edit(Action.DELETE_GAP, seqs, 1, 2, '-');
217     edit.addEdit(e11);
218     Edit e12 = edit.new Edit(Action.INSERT_GAP, seqs, 5, 2, '-');
219     e12.setSystemGenerated(true);
220     edit.addEdit(e12);
221     expected = MessageManager.formatMessage("label.delete_gaps", "3");
222     assertEquals(SeqPanel.getEditStatusMessage(edit), expected);
223   }
224
225   public void testFindMousePosition_unwrapped()
226   {
227     String seqData = ">Seq1\nAACDE\n>Seq2\nAA--E\n";
228     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(seqData,
229             DataSourceType.PASTE);
230     AlignViewportI av = alignFrame.getViewport();
231     av.setShowAnnotation(true);
232     av.setWrapAlignment(false);
233     final int charHeight = av.getCharHeight();
234     final int charWidth = av.getCharWidth();
235     // sanity checks:
236     assertTrue(charHeight > 0);
237     assertTrue(charWidth > 0);
238     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
239
240     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
241     int x = 0;
242     int y = 0;
243
244     /*
245      * mouse at top left of unwrapped panel
246      */
247     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
248             0, 0, 0, false, 0);
249     MousePos pos = testee.findMousePosition(evt);
250     assertEquals(pos.column, 0);
251     assertEquals(pos.seqIndex, 0);
252     assertEquals(pos.annotationIndex, -1);
253   }
254
255   @AfterMethod(alwaysRun = true)
256   public void tearDown()
257   {
258     Desktop.instance.closeAll_actionPerformed(null);
259   }
260
261   @Test(groups = "Functional")
262   public void testFindMousePosition_wrapped_annotations()
263   {
264     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
265     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
266     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
267             "examples/uniref50.fa", DataSourceType.FILE);
268     AlignViewportI av = alignFrame.getViewport();
269     av.setScaleAboveWrapped(false);
270     av.setScaleLeftWrapped(false);
271     av.setScaleRightWrapped(false);
272
273     alignFrame.alignPanel.updateLayout();
274
275     final int charHeight = av.getCharHeight();
276     final int charWidth = av.getCharWidth();
277     final int alignmentHeight = av.getAlignment().getHeight();
278     
279     // sanity checks:
280     assertTrue(charHeight > 0);
281     assertTrue(charWidth > 0);
282     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
283   
284     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
285     int x = 0;
286     int y = 0;
287   
288     /*
289      * mouse at top left of wrapped panel; there is a gap of charHeight
290      * above the alignment
291      */
292     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
293             0, 0, 0, false, 0);
294     MousePos pos = testee.findMousePosition(evt);
295     assertEquals(pos.column, 0);
296     assertEquals(pos.seqIndex, -1); // above sequences
297     assertEquals(pos.annotationIndex, -1);
298
299     /*
300      * cursor at bottom of gap above
301      */
302     y = charHeight - 1;
303     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
304             false, 0);
305     pos = testee.findMousePosition(evt);
306     assertEquals(pos.seqIndex, -1);
307     assertEquals(pos.annotationIndex, -1);
308
309     /*
310      * cursor over top of first sequence
311      */
312     y = charHeight;
313     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
314             false, 0);
315     pos = testee.findMousePosition(evt);
316     assertEquals(pos.seqIndex, 0);
317     assertEquals(pos.annotationIndex, -1);
318
319     /*
320      * cursor at bottom of first sequence
321      */
322     y = 2 * charHeight - 1;
323     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
324             false, 0);
325     pos = testee.findMousePosition(evt);
326     assertEquals(pos.seqIndex, 0);
327     assertEquals(pos.annotationIndex, -1);
328
329     /*
330      * cursor at top of second sequence
331      */
332     y = 2 * charHeight;
333     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
334             false, 0);
335     pos = testee.findMousePosition(evt);
336     assertEquals(pos.seqIndex, 1);
337     assertEquals(pos.annotationIndex, -1);
338
339     /*
340      * cursor at bottom of second sequence
341      */
342     y = 3 * charHeight - 1;
343     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
344             false, 0);
345     pos = testee.findMousePosition(evt);
346     assertEquals(pos.seqIndex, 1);
347     assertEquals(pos.annotationIndex, -1);
348
349     /*
350      * cursor at bottom of last sequence
351      */
352     y = charHeight * (1 + alignmentHeight) - 1;
353     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
354             false, 0);
355     pos = testee.findMousePosition(evt);
356     assertEquals(pos.seqIndex, alignmentHeight - 1);
357     assertEquals(pos.annotationIndex, -1);
358
359     /*
360      * cursor below sequences, in 3-pixel gap above annotations
361      * method reports index of nearest sequence above
362      */
363     y += 1;
364     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
365             false, 0);
366     pos = testee.findMousePosition(evt);
367     assertEquals(pos.seqIndex, alignmentHeight - 1);
368     assertEquals(pos.annotationIndex, -1);
369
370     /*
371      * cursor still in the gap above annotations, now at the bottom of it
372      */
373     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
374     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
375             false, 0);
376     pos = testee.findMousePosition(evt);
377     assertEquals(pos.seqIndex, alignmentHeight - 1);
378     assertEquals(pos.annotationIndex, -1);
379
380     AlignmentAnnotation[] annotationRows = av.getAlignment()
381             .getAlignmentAnnotation();
382     for (int n = 0; n < annotationRows.length; n++)
383     {
384       /*
385        * cursor at the top of the n'th annotation  
386        */
387       y += 1;
388       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
389               false, 0);
390       pos = testee.findMousePosition(evt);
391       assertEquals(pos.seqIndex, alignmentHeight - 1);
392       assertEquals(pos.annotationIndex, n); // over n'th annotation
393
394       /*
395        * cursor at the bottom of the n'th annotation  
396        */
397       y += annotationRows[n].height - 1;
398       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
399               false, 0);
400       pos = testee.findMousePosition(evt);
401       assertEquals(pos.seqIndex, alignmentHeight - 1);
402       assertEquals(pos.annotationIndex, n);
403     }
404
405     /*
406      * cursor in gap between wrapped widths  
407      */
408     y += 1;
409     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
410             false, 0);
411     pos = testee.findMousePosition(evt);
412     assertEquals(pos.seqIndex, -1);
413     assertEquals(pos.annotationIndex, -1);
414
415     /*
416      * cursor at bottom of gap between wrapped widths  
417      */
418     y += charHeight - 1;
419     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
420             false, 0);
421     pos = testee.findMousePosition(evt);
422     assertEquals(pos.seqIndex, -1);
423     assertEquals(pos.annotationIndex, -1);
424
425     /*
426      * cursor at top of first sequence, second wrapped width  
427      */
428     y += 1;
429     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
430             false, 0);
431     pos = testee.findMousePosition(evt);
432     assertEquals(pos.seqIndex, 0);
433     assertEquals(pos.annotationIndex, -1);
434   }
435
436   @Test(groups = "Functional")
437   public void testFindMousePosition_wrapped_scaleAbove()
438   {
439     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
440     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
441     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
442             "examples/uniref50.fa", DataSourceType.FILE);
443     AlignViewportI av = alignFrame.getViewport();
444     av.setScaleAboveWrapped(true);
445     av.setScaleLeftWrapped(false);
446     av.setScaleRightWrapped(false);
447     alignFrame.alignPanel.updateLayout();
448
449     final int charHeight = av.getCharHeight();
450     final int charWidth = av.getCharWidth();
451     final int alignmentHeight = av.getAlignment().getHeight();
452     
453     // sanity checks:
454     assertTrue(charHeight > 0);
455     assertTrue(charWidth > 0);
456     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
457   
458     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
459     int x = 0;
460     int y = 0;
461   
462     /*
463      * mouse at top left of wrapped panel; there is a gap of charHeight
464      * above the alignment
465      */
466     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
467             0, 0, 0, false, 0);
468     MousePos pos = testee.findMousePosition(evt);
469     assertEquals(pos.column, 0);
470     assertEquals(pos.seqIndex, -1); // above sequences
471     assertEquals(pos.annotationIndex, -1);
472   
473     /*
474      * cursor at bottom of gap above
475      * two charHeights including scale panel
476      */
477     y = 2 * charHeight - 1;
478     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
479             false, 0);
480     pos = testee.findMousePosition(evt);
481     assertEquals(pos.seqIndex, -1);
482     assertEquals(pos.annotationIndex, -1);
483   
484     /*
485      * cursor over top of first sequence
486      */
487     y += 1;
488     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
489             false, 0);
490     pos = testee.findMousePosition(evt);
491     assertEquals(pos.seqIndex, 0);
492     assertEquals(pos.annotationIndex, -1);
493   
494     /*
495      * cursor at bottom of first sequence
496      */
497     y += charHeight - 1;
498     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
499             false, 0);
500     pos = testee.findMousePosition(evt);
501     assertEquals(pos.seqIndex, 0);
502     assertEquals(pos.annotationIndex, -1);
503   
504     /*
505      * cursor at top of second sequence
506      */
507     y += 1;
508     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
509             false, 0);
510     pos = testee.findMousePosition(evt);
511     assertEquals(pos.seqIndex, 1);
512     assertEquals(pos.annotationIndex, -1);
513   
514     /*
515      * cursor at bottom of second sequence
516      */
517     y += charHeight - 1;
518     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
519             false, 0);
520     pos = testee.findMousePosition(evt);
521     assertEquals(pos.seqIndex, 1);
522     assertEquals(pos.annotationIndex, -1);
523   
524     /*
525      * cursor at bottom of last sequence
526      * (scale + gap + sequences)
527      */
528     y = charHeight * (2 + alignmentHeight) - 1;
529     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
530             false, 0);
531     pos = testee.findMousePosition(evt);
532     assertEquals(pos.seqIndex, alignmentHeight - 1);
533     assertEquals(pos.annotationIndex, -1);
534   
535     /*
536      * cursor below sequences, in 3-pixel gap above annotations
537      */
538     y += 1;
539     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
540             false, 0);
541     pos = testee.findMousePosition(evt);
542     assertEquals(pos.seqIndex, alignmentHeight - 1);
543     assertEquals(pos.annotationIndex, -1);
544   
545     /*
546      * cursor still in the gap above annotations, now at the bottom of it
547      * method reports index of nearest sequence above  
548      */
549     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
550     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
551             false, 0);
552     pos = testee.findMousePosition(evt);
553     assertEquals(pos.seqIndex, alignmentHeight - 1);
554     assertEquals(pos.annotationIndex, -1);
555   
556     AlignmentAnnotation[] annotationRows = av.getAlignment().getAlignmentAnnotation();
557     for (int n = 0; n < annotationRows.length; n++)
558     {
559       /*
560        * cursor at the top of the n'th annotation  
561        */
562       y += 1;
563       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
564               false, 0);
565       pos = testee.findMousePosition(evt);
566       assertEquals(pos.seqIndex, alignmentHeight - 1);
567       assertEquals(pos.annotationIndex, n); // over n'th annotation
568
569       /*
570        * cursor at the bottom of the n'th annotation  
571        */
572       y += annotationRows[n].height - 1;
573       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
574               false, 0);
575       pos = testee.findMousePosition(evt);
576       SeqCanvas sc = testee.seqCanvas;
577       assertEquals(pos.seqIndex, alignmentHeight - 1,
578               String.format("%s n=%d y=%d %d, %d, %d, %d",
579                       annotationRows[n].label, n, y, sc.getWidth(),
580                       sc.getHeight(), sc.wrappedRepeatHeightPx,
581                       sc.wrappedSpaceAboveAlignment));
582       assertEquals(pos.annotationIndex, n);
583     }
584   
585     /*
586      * cursor in gap between wrapped widths  
587      */
588     y += 1;
589     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
590             false, 0);
591     pos = testee.findMousePosition(evt);
592     assertEquals(pos.seqIndex, -1);
593     assertEquals(pos.annotationIndex, -1);
594   
595     /*
596      * cursor at bottom of gap between wrapped widths  
597      */
598     y += charHeight - 1;
599     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
600             false, 0);
601     pos = testee.findMousePosition(evt);
602     assertEquals(pos.seqIndex, -1);
603     assertEquals(pos.annotationIndex, -1);
604   
605     /*
606      * cursor at top of scale, second wrapped width  
607      */
608     y += 1;
609     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
610             false, 0);
611     pos = testee.findMousePosition(evt);
612     assertEquals(pos.seqIndex, -1);
613     assertEquals(pos.annotationIndex, -1);
614
615     /*
616      * cursor at bottom of scale, second wrapped width  
617      */
618     y += charHeight - 1;
619     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
620             false, 0);
621     pos = testee.findMousePosition(evt);
622     assertEquals(pos.seqIndex, -1);
623     assertEquals(pos.annotationIndex, -1);
624
625     /*
626      * cursor at top of first sequence, second wrapped width  
627      */
628     y += 1;
629     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
630             false, 0);
631     pos = testee.findMousePosition(evt);
632     assertEquals(pos.seqIndex, 0);
633     assertEquals(pos.annotationIndex, -1);
634   }
635
636   @Test(groups = "Functional")
637   public void testFindMousePosition_wrapped_noAnnotations()
638   {
639     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
640     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
641     Cache.applicationProperties.setProperty("FONT_SIZE", "10");
642     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
643             "examples/uniref50.fa", DataSourceType.FILE);
644     AlignViewportI av = alignFrame.getViewport();
645     av.setScaleAboveWrapped(false);
646     av.setScaleLeftWrapped(false);
647     av.setScaleRightWrapped(false);
648     alignFrame.alignPanel.updateLayout();
649
650     final int charHeight = av.getCharHeight();
651     final int charWidth = av.getCharWidth();
652     final int alignmentHeight = av.getAlignment().getHeight();
653     
654     // sanity checks:
655     assertTrue(charHeight > 0);
656     assertTrue(charWidth > 0);
657     assertTrue(alignFrame.alignPanel.getSeqPanel().getWidth() > 0);
658   
659     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
660     int x = 0;
661     int y = 0;
662   
663     /*
664      * mouse at top left of wrapped panel; there is a gap of charHeight
665      * above the alignment
666      */
667     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
668             0, 0, 0, false, 0);
669     MousePos pos = testee.findMousePosition(evt);
670     assertEquals(pos.column, 0);
671     assertEquals(pos.seqIndex, -1); // above sequences
672     assertEquals(pos.annotationIndex, -1);
673   
674     /*
675      * cursor over top of first sequence
676      */
677     y = charHeight;
678     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
679             false, 0);
680     pos = testee.findMousePosition(evt);
681     assertEquals(pos.seqIndex, 0);
682     assertEquals(pos.annotationIndex, -1);
683
684     /*
685      * cursor at bottom of last sequence
686      */
687     y = charHeight * (1 + alignmentHeight) - 1;
688     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
689             false, 0);
690     pos = testee.findMousePosition(evt);
691     assertEquals(pos.seqIndex, alignmentHeight - 1);
692     assertEquals(pos.annotationIndex, -1);
693   
694     /*
695      * cursor below sequences, at top of charHeight gap between widths
696      */
697     y += 1;
698     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
699             false, 0);
700     pos = testee.findMousePosition(evt);
701     assertEquals(pos.seqIndex, -1);
702     assertEquals(pos.annotationIndex, -1);
703   
704     /*
705      * cursor below sequences, at top of charHeight gap between widths
706      */
707     y += charHeight - 1;
708     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
709             false, 0);
710     pos = testee.findMousePosition(evt);
711     assertEquals(pos.seqIndex, -1);
712     assertEquals(pos.annotationIndex, -1);
713   
714     /*
715      * cursor at the top of the first sequence, second width  
716      */
717     y += 1;
718     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
719             false, 0);
720     pos = testee.findMousePosition(evt);
721     assertEquals(pos.seqIndex, 0);
722     assertEquals(pos.annotationIndex, -1);
723   }
724
725   @Test(groups = "Functional")
726   public void testFindColumn_unwrapped()
727   {
728     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "false");
729     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
730             "examples/uniref50.fa", DataSourceType.FILE);
731     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
732     int x = 0;
733     final int charWidth = alignFrame.getViewport().getCharWidth();
734     assertTrue(charWidth > 0); // sanity check
735     ViewportRanges ranges = alignFrame.getViewport().getRanges();
736     assertEquals(ranges.getStartRes(), 0);
737
738     /*
739      * mouse at top left of unwrapped panel
740      */
741     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
742             0, 0, 0, false, 0);
743     assertEquals(testee.findColumn(evt), 0);
744     
745     /*
746      * not quite one charWidth across
747      */
748     x = charWidth-1;
749     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
750             0, 0, 0, false, 0);
751     assertEquals(testee.findColumn(evt), 0);
752
753     /*
754      * one charWidth across
755      */
756     x = charWidth;
757     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
758             false, 0);
759     assertEquals(testee.findColumn(evt), 1);
760
761     /*
762      * two charWidths across
763      */
764     x = 2 * charWidth;
765     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
766             false, 0);
767     assertEquals(testee.findColumn(evt), 2);
768
769     /*
770      * limited to last column of seqcanvas
771      */
772     x = 20000;
773     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
774             false, 0);
775     SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas;
776     int w = seqCanvas.getWidth();
777     // limited to number of whole columns, base 0,
778     // and to end of visible range
779     int expected = w / charWidth;
780     expected = Math.min(expected, ranges.getEndRes());
781     assertEquals(testee.findColumn(evt), expected);
782
783     /*
784      * hide columns 5-10 (base 1)
785      */
786     alignFrame.getViewport().hideColumns(4, 9);
787     x = 5 * charWidth + 2;
788     // x is in 6th visible column, absolute column 12, or 11 base 0
789     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
790             false, 0);
791     assertEquals(testee.findColumn(evt), 11);
792   }
793
794   @Test(groups = "Functional")
795   public void testFindColumn_wrapped()
796   {
797     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
798     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
799             "examples/uniref50.fa", DataSourceType.FILE);
800     AlignViewport av = alignFrame.getViewport();
801     av.setScaleAboveWrapped(false);
802     av.setScaleLeftWrapped(false);
803     av.setScaleRightWrapped(false);
804     alignFrame.alignPanel.updateLayout();
805     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
806     int x = 0;
807     final int charWidth = av.getCharWidth();
808     assertTrue(charWidth > 0); // sanity check
809     assertEquals(av.getRanges().getStartRes(), 0);
810   
811     /*
812      * mouse at top left of wrapped panel, no West (left) scale
813      */
814     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
815             0, 0, 0, false, 0);
816     assertEquals(testee.findColumn(evt), 0);
817     
818     /*
819      * not quite one charWidth across
820      */
821     x = charWidth-1;
822     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
823             0, 0, 0, false, 0);
824     assertEquals(testee.findColumn(evt), 0);
825   
826     /*
827      * one charWidth across
828      */
829     x = charWidth;
830     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
831             false, 0);
832     assertEquals(testee.findColumn(evt), 1);
833
834     /*
835      * x over scale left (before drawn columns) results in -1
836      */
837     av.setScaleLeftWrapped(true);
838     alignFrame.alignPanel.updateLayout();
839     SeqCanvas seqCanvas = testee.seqCanvas;
840     int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest");
841     assertTrue(labelWidth > 0);
842     x = labelWidth - 1;
843     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
844             false, 0);
845     assertEquals(testee.findColumn(evt), -1);
846
847     x = labelWidth;
848     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
849             false, 0);
850     assertEquals(testee.findColumn(evt), 0);
851
852     /*
853      * x over right edge of last residue (including scale left)
854      */
855     int residuesWide = av.getRanges().getViewportWidth();
856     assertTrue(residuesWide > 0);
857     x = labelWidth + charWidth * residuesWide - 1;
858     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
859             false, 0);
860     assertEquals(testee.findColumn(evt), residuesWide - 1);
861
862     /*
863      * x over scale right (beyond drawn columns) results in -1
864      */
865     av.setScaleRightWrapped(true);
866     alignFrame.alignPanel.updateLayout();
867     labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast");
868     assertTrue(labelWidth > 0);
869     int residuesWide2 = av.getRanges().getViewportWidth();
870     assertTrue(residuesWide2 > 0);
871     assertTrue(residuesWide2 < residuesWide); // available width reduced
872     x += 1; // just over left edge of scale right
873     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
874             false, 0);
875     assertEquals(testee.findColumn(evt), -1);
876     
877     // todo add startRes offset, hidden columns
878
879   }
880   @BeforeClass(alwaysRun = true)
881   public static void setUpBeforeClass() throws Exception
882   {
883     /*
884      * use read-only test properties file
885      */
886     Cache.loadProperties("test/jalview/io/testProps.jvprops");
887     Jalview.main(new String[] { "-nonews" });
888   }
889
890   /**
891    * waits for Swing event dispatch queue to empty
892    */
893   synchronized void waitForSwing()
894   {
895     try
896     {
897       EventQueue.invokeAndWait(new Runnable()
898       {
899         @Override
900         public void run()
901         {
902         }
903       });
904     } catch (InterruptedException | InvocationTargetException e)
905     {
906       e.printStackTrace();
907     }
908   }
909   @Test(groups = "Functional")
910   public void testFindMousePosition_wrapped_scales_longSequence()
911   {
912     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
913     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
914     Cache.applicationProperties.setProperty("FONT_SIZE", "14");
915     Cache.applicationProperties.setProperty("FONT_NAME", "SansSerif");
916     Cache.applicationProperties.setProperty("FONT_STYLE", "0");
917     // sequence of 50 bases, doubled 10 times, = 51200 bases
918     String dna = "ATGGCCATTGGGCCCAAATTTCCCAAAGGGTTTCCCTGAGGTCAGTCAGA";
919     for (int i = 0 ; i < 10 ; i++)
920     {
921       dna += dna;
922     }
923     assertEquals(dna.length(), 51200);
924     AlignFrame alignFrame = new FileLoader()
925             .LoadFileWaitTillLoaded(dna, DataSourceType.PASTE);
926     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
927     AlignViewport av = alignFrame.getViewport();
928     av.setScaleAboveWrapped(true);
929     av.setScaleLeftWrapped(true);
930     av.setScaleRightWrapped(true);
931     alignFrame.alignPanel.updateLayout();
932
933     try
934     {
935       Thread.sleep(200);
936     } catch (InterruptedException e)
937     {
938     }
939   
940     final int charHeight = av.getCharHeight();
941     final int charWidth = av.getCharWidth();
942     assertEquals(charHeight, 17);
943     assertEquals(charWidth, 12);
944     
945     FontMetrics fm = testee.getFontMetrics(av.getFont());
946     int labelWidth = fm.stringWidth("00000") + charWidth;
947     assertEquals(labelWidth, 57); // 5 x 9 + charWidth
948     assertEquals(testee.seqCanvas.getLabelWidthWest(), labelWidth);
949
950     int x = 0;
951     int y = 0;
952   
953     /*
954      * mouse at top left of wrapped panel; there is a gap of 2 * charHeight
955      * above the alignment
956      */
957     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
958             0, 0, 0, false, 0);
959     MousePos pos = testee.findMousePosition(evt);
960     assertEquals(pos.column, -1); // over scale left, not an alignment column
961     assertEquals(pos.seqIndex, -1); // above sequences
962     assertEquals(pos.annotationIndex, -1);
963
964     /*
965      * cursor over scale above first sequence
966      */
967     y += charHeight;
968     x = labelWidth;
969     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
970             false, 0);
971     pos = testee.findMousePosition(evt);
972     assertEquals(pos.seqIndex, -1);
973     assertEquals(pos.column, 0);
974     assertEquals(pos.annotationIndex, -1);
975     
976     /*
977      * cursor over scale left of first sequence
978      */
979     y += charHeight;
980     x = 0;
981     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
982             false, 0);
983     pos = testee.findMousePosition(evt);
984     assertEquals(pos.seqIndex, 0);
985     assertEquals(pos.column, -1);
986     assertEquals(pos.annotationIndex, -1);
987   
988     /*
989      * cursor over start of first sequence
990      */
991     x = labelWidth;
992     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
993             false, 0);
994     pos = testee.findMousePosition(evt);
995     assertEquals(pos.seqIndex, 0);
996     assertEquals(pos.column, 0);
997     assertEquals(pos.annotationIndex, -1);
998   
999     /*
1000      * move one character right, to bottom pixel of same row
1001      */
1002     x += charWidth;
1003     y += charHeight - 1;
1004     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1005             false, 0);
1006     pos = testee.findMousePosition(evt);
1007     assertEquals(pos.seqIndex, 0);
1008     assertEquals(pos.column, 1);
1009     
1010     /*
1011      * move down one pixel - now in the no man's land between rows
1012      */
1013     y += 1;
1014     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1015             false, 0);
1016     pos = testee.findMousePosition(evt);
1017     assertEquals(pos.seqIndex, -1);
1018     assertEquals(pos.column, 1);
1019     
1020     /*
1021      * move down two char heights less one pixel - still in the no man's land
1022      * (scale above + spacer line)
1023      */
1024     y += (2 * charHeight - 1);
1025     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1026             false, 0);
1027     pos = testee.findMousePosition(evt);
1028     assertEquals(pos.seqIndex, -1);
1029     assertEquals(pos.column, 1);
1030     
1031     /*
1032      * move down one more pixel - now on the next row of the sequence
1033      */
1034     y += 1;
1035     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1036             false, 0);
1037     pos = testee.findMousePosition(evt);
1038     assertEquals(pos.seqIndex, 0);
1039     assertEquals(pos.column, 1 + av.getWrappedWidth());
1040     
1041     /*
1042      * scroll to near the end of the sequence
1043      */
1044     SearchResultsI sr = new SearchResults();
1045     int scrollTo = dna.length() - 1000;
1046     sr.addResult(av.getAlignment().getSequenceAt(0), scrollTo, scrollTo); 
1047     alignFrame.alignPanel.scrollToPosition(sr);
1048     
1049     /*
1050      * place the mouse on the first column of the 6th sequence, and
1051      * verify that (computed) findMousePosition matches (actual) ViewportRanges
1052      */
1053     x = labelWidth;
1054     y = 17 * charHeight; // 17 = 6 times two header rows and 5 sequence rows
1055     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
1056             false, 0);
1057     pos = testee.findMousePosition(evt);
1058     assertEquals(pos.seqIndex, 0);
1059     int expected = av.getRanges().getStartRes() + 5 * av.getWrappedWidth();
1060     assertEquals(pos.column, expected);
1061   }
1062 }