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