d5776be51d508c855f2938b515d3c5f8154d153d
[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.util.Platform;
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 public class SeqCanvasTest
42 {
43   @BeforeClass(alwaysRun = true)
44   public void setUp()
45   {
46     Cache.initLogger();
47     Jalview.setSynchronous(true);
48   }
49
50   /**
51    * Test the method that computes wrapped width in residues, height of wrapped
52    * widths in pixels, and the number of widths visible
53    */
54   @Test(groups = "Functional")
55   public void testCalculateWrappedGeometry_noAnnotations()
56   {
57     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
58             "examples/uniref50.fa", DataSourceType.FILE);
59     AlignViewport av = af.getViewport();
60     AlignmentI al = av.getAlignment();
61     assertEquals(al.getWidth(), 157);
62     assertEquals(al.getHeight(), 15);
63     av.getRanges().setStartEndSeq(0, 14);
64
65     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
66
67     av.setWrapAlignment(true);
68     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
69     int charHeight = av.getCharHeight();
70     int charWidth = av.getCharWidth();
71     Assert.assertTrue(charHeight == 17 && charWidth == 12
72             || charHeight == 19 && charWidth == 11,
73             "char height/width " + charHeight + "/" + charWidth);
74     //
75     // assertEquals(charHeight, Platform.isMac() ? 17 : 19);
76     // assertEquals(charWidth, Platform.isMac() ? 12 : 11);
77
78     /*
79      * first with scales above, left, right
80      */
81     av.setShowAnnotation(false);
82     av.setScaleAboveWrapped(true);
83     av.setScaleLeftWrapped(true);
84     av.setScaleRightWrapped(true);
85     FontMetrics fm = testee.getFontMetrics(av.getFont());
86     int labelWidth = fm.stringWidth("000") + charWidth;
87     // BH 2020.03.22 It is not really necessary to be this detailed. Different 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     assertEquals(wrappedWidth, Platform.isMac() ? 24 : 25); // 2px not enough
226     canvasWidth += 1;
227     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
228             canvasHeight);
229     assertEquals(wrappedWidth, 25); // 3px is enough
230
231     /*
232      * turn off scales left and right, make width exactly 157 columns
233      */
234     av.setScaleLeftWrapped(false);
235     canvasWidth = al.getWidth() * charWidth;
236     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
237     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
238   }
239
240   /**
241    * Test the method that computes wrapped width in residues, height of wrapped
242    * widths in pixels, and the number of widths visible
243    */
244   @Test(groups = "Functional")
245   public void testCalculateWrappedGeometry_withAnnotations()
246   {
247     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
248             "examples/uniref50.fa", DataSourceType.FILE);
249     AlignViewport av = af.getViewport();
250     AlignmentI al = av.getAlignment();
251     assertEquals(al.getWidth(), 157);
252     assertEquals(al.getHeight(), 15);
253   
254     av.setWrapAlignment(true);
255     av.getRanges().setStartEndSeq(0, 14);
256     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
257     int charHeight = av.getCharHeight();
258     int charWidth = av.getCharWidth();
259
260     Assert.assertTrue(
261             charHeight == 17 && charWidth == 12
262                     || charHeight == 19 && charWidth == 11,
263             "char height/width " + charHeight + "/" + charWidth);
264     // assertEquals(charHeight, Platform.isMac() ? 17 : 19);
265     // assertEquals(charWidth, Platform.isMac() ? 12 : 11);
266
267     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
268   
269     /*
270      * first with scales above, left, right
271      */
272     av.setShowAnnotation(true);
273     av.setScaleAboveWrapped(true);
274     av.setScaleLeftWrapped(true);
275     av.setScaleRightWrapped(true);
276
277     FontMetrics fm = testee.getFontMetrics(av.getFont());
278     int labelWidth = fm.stringWidth("000") + charWidth;
279     assertEquals(labelWidth,
280             Platform.isMac() ? 3 * 9 + charWidth : 3 * 8 + charWidth);
281
282     int annotationHeight = testee.getAnnotationHeight();
283
284     /*
285      * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
286      * take the whole multiple of character widths
287      */
288     int canvasWidth = 400;
289     int canvasHeight = 300;
290     int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
291     int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
292     assertEquals(wrappedWidth, residueColumns);
293     assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
294     assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
295     assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
296             2 * charHeight);
297     int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
298     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight())
299             + SeqCanvas.SEQS_ANNOTATION_GAP + annotationHeight);
300     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
301   
302     /*
303      * repeat height is 17 * (2 + 15) = 289 + 3 + annotationHeight = 510
304      * make canvas height 2 of these plus 3 charHeights 
305      * so just enough to draw 2 widths, gap + scale + the first sequence of a third
306      */
307     canvasHeight = charHeight * (17 * 2 + 3)
308             + 2 * (annotationHeight + SeqCanvas.SEQS_ANNOTATION_GAP);
309     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
310     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
311   
312     /*
313      * reduce canvas height by 1 pixel - should not be enough height
314      * to draw 3 widths
315      */
316     canvasHeight -= 1;
317     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
318     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
319   
320     /*
321      * turn off scale above - can now fit in 2 and a bit widths
322      */
323     av.setScaleAboveWrapped(false);
324     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
325     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
326   
327     /*
328      * reduce height to enough for 2 widths and not quite a third
329      * i.e. two repeating heights + spacer + sequence - 1 pixel
330      */
331     canvasHeight = charHeight * (16 * 2 + 2)
332             + 2 * (annotationHeight + SeqCanvas.SEQS_ANNOTATION_GAP) - 1;
333     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
334     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
335
336     /*
337      * add 1 pixel to height - should now get 3 widths drawn
338      */
339     canvasHeight += 1;
340     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
341     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
342   }
343
344   /**
345    * Test simulates loading an unwrapped alignment, shrinking it vertically so
346    * not all sequences are visible, then changing to wrapped mode. The ranges
347    * endSeq should be unchanged, but the vertical repeat height should include
348    * all sequences.
349    */
350   @Test(groups = "Functional")
351   public void testCalculateWrappedGeometry_fromScrolled()
352   {
353     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
354             "examples/uniref50.fa", DataSourceType.FILE);
355     AlignViewport av = af.getViewport();
356     AlignmentI al = av.getAlignment();
357     assertEquals(al.getWidth(), 157);
358     assertEquals(al.getHeight(), 15);
359     av.getRanges().setStartEndSeq(0, 3);
360     av.setShowAnnotation(false);
361     av.setScaleAboveWrapped(true);
362
363     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
364     av.setWrapAlignment(true);
365     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
366     int charHeight = av.getCharHeight();
367     int charWidth = av.getCharWidth();
368     // Windows h=19, w=11; Mac (and Linux?) 17,11
369     Assert.assertTrue(charHeight == 17 && charWidth == 12
370             || charHeight == 19 && charWidth == 11,
371             "char height/width " + charHeight + "/" + charWidth);
372     int canvasWidth = 400;
373     int canvasHeight = 300;
374     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
375
376     assertEquals(av.getRanges().getEndSeq(), 3); // unchanged
377     int repeatingHeight = (int) PA.getValue(testee,
378             "wrappedRepeatHeightPx");
379     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
380   }
381 }