6f5fa2e56543f322e45f7a891c3cf8b304744937
[jalview.git] / test / jalview / gui / SeqCanvasTest.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
25 import jalview.bin.Cache;
26 import jalview.bin.Jalview;
27 import jalview.datamodel.AlignmentI;
28 import jalview.io.DataSourceType;
29 import jalview.io.FileLoader;
30 import jalview.viewmodel.ViewportRanges;
31
32 import java.awt.Font;
33 import java.awt.FontMetrics;
34
35 import org.testng.Assert;
36 import org.testng.annotations.BeforeClass;
37 import org.testng.annotations.Test;
38
39 import junit.extensions.PA;
40
41 @Test(singleThreaded = true)
42 public class SeqCanvasTest
43 {
44   @BeforeClass(alwaysRun = true)
45   public void setUp()
46   {
47     Thread.currentThread().setName("SeqCanvasTest Setup " + ++nTest);
48
49     Cache.initLogger();
50     Jalview.setSynchronous(true);
51   }
52
53   /**
54    * Test the method that computes wrapped width in residues, height of wrapped
55    * widths in pixels, and the number of widths visible
56    */
57   @Test(groups = "Functional")
58   public void testCalculateWrappedGeometry_noAnnotations()
59   {
60     Thread.currentThread().setName("SeqCanvasTest noAnn " + ++nTest);
61     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
62             "examples/uniref50.fa", DataSourceType.FILE);
63     AlignViewport av = af.getViewport();
64     AlignmentI al = av.getAlignment();
65     assertEquals(al.getWidth(), 157);
66     assertEquals(al.getHeight(), 15);
67     av.getRanges().setStartEndSeq(0, 14);
68
69     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
70
71     av.setWrapAlignment(true);
72     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
73     int charHeight = av.getCharHeight();
74     int charWidth = av.getCharWidth();
75     Assert.assertTrue(charHeight == 17 && charWidth == 12
76             || charHeight == 19 && charWidth == 11,
77             "char height/width " + charHeight + "/" + charWidth);
78     //
79     // assertEquals(charHeight, Platform.isMac() ? 17 : 19);
80     // assertEquals(charWidth, Platform.isMac() ? 12 : 11);
81
82     /*
83      * first with scales above, left, right
84      */
85     av.setShowAnnotation(false);
86     av.setScaleAboveWrapped(true);
87     av.setScaleLeftWrapped(true);
88     av.setScaleRightWrapped(true);
89     FontMetrics fm = testee.getFontMetrics(av.getFont());
90     int labelWidth = fm.stringWidth("000") + charWidth;
91     // BH 2020.03.22 It is not really necessary to be this detailed. Different
92     // OS-based UIs will
93     // always have slightly different parameters. StringgWidths are not
94     // necessarily linear sums of the letters involved.
95     // for example, the calculation for JavaScript is a float that has to be
96     // rounded.
97     // ..............................mac................PC................linux?
98     Assert.assertTrue(
99             labelWidth == 39 || labelWidth == 35 || labelWidth == 36);// 3 * 9 +
100                                                                       // charWidth
101                                                                       // ||
102                                                                       // labelWidth
103                                                                       // == 3 *
104                                                                       // 8 +
105                                                                       // charWidth,
106                                                                       // "labelWidth
107                                                                       // 36 or
108                                                                       // 39");
109
110     /*
111      * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
112      * take the whole multiple of character widths
113      */
114     int canvasWidth = 400;
115     int canvasHeight = 300;
116     int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
117     int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
118     assertEquals(wrappedWidth, residueColumns);
119     assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
120     assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
121     assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
122             2 * charHeight);
123     int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
124     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
125     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
126
127     /*
128      * repeat height is 17 * (2 + 15) = 289
129      * make canvas height 2 * 289 + 3 * charHeight so just enough to
130      * draw 2 widths and the first sequence of a third
131      */
132     canvasHeight = charHeight * (17 * 2 + 3);
133     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
134     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
135
136     /*
137      * reduce canvas height by 1 pixel 
138      * - should not be enough height to draw 3 widths
139      */
140     canvasHeight -= 1;
141     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
142     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
143
144     /*
145      * turn off scale above - can now fit in 2 and a bit widths
146      */
147     av.setScaleAboveWrapped(false);
148     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
149     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
150
151     /*
152      * reduce height to enough for 2 widths and not quite a third
153      * i.e. two repeating heights + spacer + sequence - 1 pixel
154      */
155     canvasHeight = charHeight * (16 * 2 + 2) - 1;
156     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
157     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
158
159     /*
160      * make canvas width enough for scales and 20 residues
161      */
162     canvasWidth = 2 * labelWidth + 20 * charWidth;
163     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
164             canvasHeight);
165     assertEquals(wrappedWidth, 20);
166
167     /*
168      * reduce width by 1 pixel - rounds down to 19 residues
169      */
170     canvasWidth -= 1;
171     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
172             canvasHeight);
173     assertEquals(wrappedWidth, 19);
174
175     /*
176      * turn off West scale - adds labelWidth (39) to available for residues
177      * which with the 11 remainder makes 50 which is 4 more charWidths rem 2
178      */
179     av.setScaleLeftWrapped(false);
180     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
181             canvasHeight);
182     assertEquals(wrappedWidth, 23);
183
184     /*
185      * add 10 pixels to width to fit in another whole residue column
186      */
187     canvasWidth += 9;
188     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
189             canvasHeight);
190     assertEquals(wrappedWidth, 23);
191     canvasWidth += 1;
192     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
193             canvasHeight);
194     assertEquals(wrappedWidth, 24);
195
196     /*
197      * turn off East scale to gain 39 more pixels (3 columns remainder 3)
198      */
199     av.setScaleRightWrapped(false);
200     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
201             canvasHeight);
202     assertEquals(wrappedWidth, 27);
203
204     /*
205      * add 9 pixels to width to gain a residue column
206      */
207     canvasWidth += 8;
208     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
209             canvasHeight);
210     assertEquals(wrappedWidth, 27); // 8px not enough
211     canvasWidth += 1;
212     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
213             canvasHeight);
214     assertEquals(wrappedWidth, 28); // 9px is enough
215
216     /*
217      * now West but not East scale - lose 39 pixels or 4 columns
218      */
219     av.setScaleLeftWrapped(true);
220     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
221             canvasHeight);
222     assertEquals(wrappedWidth, 24);
223
224     /*
225      * adding 3 pixels to width regains one column
226      */
227     canvasWidth += 2;
228     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
229             canvasHeight);
230     Assert.assertTrue(wrappedWidth == 24 || wrappedWidth == 25,
231             "WrappedWidth [" + wrappedWidth + "] should be 24 or 25"); // 2px
232                                                                        // not
233                                                                        // enough
234     canvasWidth += 1;
235     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
236             canvasHeight);
237     assertEquals(wrappedWidth, 25); // 3px is enough
238
239     /*
240      * turn off scales left and right, make width exactly 157 columns
241      */
242     av.setScaleLeftWrapped(false);
243     canvasWidth = al.getWidth() * charWidth;
244     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
245     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
246   }
247
248   /**
249    * Test the method that computes wrapped width in residues, height of wrapped
250    * widths in pixels, and the number of widths visible
251    */
252   @Test(groups = "Functional")
253   public void testCalculateWrappedGeometry_withAnnotations()
254   {
255     Thread.currentThread().setName("SeqCanvasTest wAnn " + ++nTest);
256     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
257             "examples/uniref50.fa", DataSourceType.FILE);
258     AlignViewport av = af.getViewport();
259     AlignmentI al = av.getAlignment();
260     assertEquals(al.getWidth(), 157);
261     assertEquals(al.getHeight(), 15);
262   
263     av.setWrapAlignment(true);
264     av.getRanges().setStartEndSeq(0, 14);
265     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
266     int charHeight = av.getCharHeight();
267     int charWidth = av.getCharWidth();
268
269     Assert.assertTrue(
270             charHeight == 17 && charWidth == 12
271                     || charHeight == 19 && charWidth == 11,
272             "char height/width " + charHeight + "/" + charWidth);
273     // assertEquals(charHeight, Platform.isMac() ? 17 : 19);
274     // assertEquals(charWidth, Platform.isMac() ? 12 : 11);
275
276     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
277   
278     /*
279      * first with scales above, left, right
280      */
281     av.setShowAnnotation(true);
282     av.setScaleAboveWrapped(true);
283     av.setScaleLeftWrapped(true);
284     av.setScaleRightWrapped(true);
285
286     FontMetrics fm = testee.getFontMetrics(av.getFont());
287     int labelWidth = fm.stringWidth("000") + charWidth;
288     // BH 2020.03.22 It is not really necessary to be this detailed. Different
289     // OS-based UIs will
290     // always have slightly different parameters. StringgWidths are not
291     // necessarily linear sums of the letters involved.
292     // for example, the calculation for JavaScript is a float that has to be
293     // rounded.
294     // ..............................mac................PC................linux?
295     Assert.assertTrue(
296             labelWidth == 39 || labelWidth == 35 || labelWidth == 36);// 3 * 9 +
297                                                                       // charWidth
298                                                                       // ||
299                                                                       // labelWidth
300                                                                       // == 3 *
301                                                                       // 8 +
302                                                                       // charWidth,
303                                                                       // "labelWidth
304                                                                       // 36 or
305                                                                       // 39");
306     // int labelWidth = fm.stringWidth("000") + charWidth;
307     // assertEquals(labelWidth,
308     // Platform.isMac() ? 3 * 9 + charWidth : 3 * 8 + charWidth);
309
310     int annotationHeight = testee.getAnnotationHeight();
311
312     /*
313      * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
314      * take the whole multiple of character widths
315      */
316     int canvasWidth = 400;
317     int canvasHeight = 300;
318     int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
319     int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
320     assertEquals(wrappedWidth, residueColumns);
321     assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
322     assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
323     assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
324             2 * charHeight);
325     int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
326     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight())
327             + SeqCanvas.SEQS_ANNOTATION_GAP + annotationHeight);
328     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
329   
330     /*
331      * repeat height is 17 * (2 + 15) = 289 + 3 + annotationHeight = 510
332      * make canvas height 2 of these plus 3 charHeights 
333      * so just enough to draw 2 widths, gap + scale + the first sequence of a third
334      */
335     canvasHeight = charHeight * (17 * 2 + 3)
336             + 2 * (annotationHeight + SeqCanvas.SEQS_ANNOTATION_GAP);
337     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
338     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
339   
340     /*
341      * reduce canvas height by 1 pixel - should not be enough height
342      * to draw 3 widths
343      */
344     canvasHeight -= 1;
345     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
346     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
347   
348     /*
349      * turn off scale above - can now fit in 2 and a bit widths
350      */
351     av.setScaleAboveWrapped(false);
352     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
353     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
354   
355     /*
356      * reduce height to enough for 2 widths and not quite a third
357      * i.e. two repeating heights + spacer + sequence - 1 pixel
358      */
359     canvasHeight = charHeight * (16 * 2 + 2)
360             + 2 * (annotationHeight + SeqCanvas.SEQS_ANNOTATION_GAP) - 1;
361     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
362     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
363
364     /*
365      * add 1 pixel to height - should now get 3 widths drawn
366      */
367     canvasHeight += 1;
368     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
369     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
370   }
371
372   private static int nTest = 0;
373   /**
374    * Test simulates loading an unwrapped alignment, shrinking it vertically so
375    * not all sequences are visible, then changing to wrapped mode. The ranges
376    * endSeq should be unchanged, but the vertical repeat height should include
377    * all sequences.
378    */
379   @Test(groups = "Functional")
380   public void testCalculateWrappedGeometry_fromScrolled()
381   {
382     Thread.currentThread().setName("SeqCanvasTest fromScrolled " + ++nTest);
383     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
384             "examples/uniref50.fa", DataSourceType.FILE);
385     AlignViewport av = af.getViewport();
386     AlignmentI al = av.getAlignment();
387     assertEquals(al.getWidth(), 157);
388     assertEquals(al.getHeight(), 15);
389     av.getRanges().setStartEndSeq(0, 3);
390     String s = "";
391     s += " SC1 " + av.getRanges() + " " + ViewportRanges.sTest;
392     av.setShowAnnotation(false);
393     av.setScaleAboveWrapped(true);
394     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
395     av.setWrapAlignment(true);
396     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
397     int charHeight = av.getCharHeight();
398     int charWidth = av.getCharWidth();
399     // Windows h=19, w=11; Mac (and Linux?) 17,11
400     Assert.assertTrue(charHeight == 17 && charWidth == 12
401             || charHeight == 19 && charWidth == 11,
402             "char height/width " + charHeight + "/" + charWidth);
403     int canvasWidth = 400;
404     int canvasHeight = 300;
405     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
406     int repeatingHeight = (int) PA.getValue(testee,
407             "wrappedRepeatHeightPx");
408     assertEquals(av.getRanges().getEndSeq(), 0, "endSeq should be 3 " + s); // unchanged
409     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
410   }
411 }