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