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