2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import static org.testng.Assert.assertEquals;
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;
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;
39 import org.testng.Assert;
40 import org.testng.annotations.BeforeClass;
41 import org.testng.annotations.Test;
43 import junit.extensions.PA;
45 @Test(singleThreaded = true)
46 public class SeqCanvasTest
48 @BeforeClass(alwaysRun = true)
51 Thread.currentThread().setName("SeqCanvasTest Setup " + ++nTest);
53 Cache.loadProperties("test/jalview/io/testProps.jvprops");
55 Jalview.setSynchronous(true);
59 * Test the method that computes wrapped width in residues, height of wrapped
60 * widths in pixels, and the number of widths visible
62 @Test(groups = "Functional")
63 public void testCalculateWrappedGeometry_noAnnotations()
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);
74 SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
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);
84 // assertEquals(charHeight, Platform.isMac() ? 17 : 19);
85 // assertEquals(charWidth, Platform.isMac() ? 12 : 11);
88 * first with scales above, left, right
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
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
102 // ..............................mac................PC................linux?
104 labelWidth == 39 || labelWidth == 35 || labelWidth == 36);// 3 * 9 +
116 * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
117 * take the whole multiple of character widths
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"),
128 int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
129 assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
130 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
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
137 canvasHeight = charHeight * (17 * 2 + 3);
138 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
139 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
142 * reduce canvas height by 1 pixel
143 * - should not be enough height to draw 3 widths
146 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
147 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
150 * turn off scale above - can now fit in 2 and a bit widths
152 av.setScaleAboveWrapped(false);
153 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
154 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
157 * reduce height to enough for 2 widths and not quite a third
158 * i.e. two repeating heights + spacer + sequence - 1 pixel
160 canvasHeight = charHeight * (16 * 2 + 2) - 1;
161 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
162 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
165 * make canvas width enough for scales and 20 residues
167 canvasWidth = 2 * labelWidth + 20 * charWidth;
168 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
170 assertEquals(wrappedWidth, 20);
173 * reduce width by 1 pixel - rounds down to 19 residues
176 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
178 assertEquals(wrappedWidth, 19);
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
184 av.setScaleLeftWrapped(false);
185 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
187 assertEquals(wrappedWidth, 23);
190 * add 10 pixels to width to fit in another whole residue column
193 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
195 assertEquals(wrappedWidth, 23);
197 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
199 assertEquals(wrappedWidth, 24);
202 * turn off East scale to gain 39 more pixels (3 columns remainder 3)
204 av.setScaleRightWrapped(false);
205 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
207 assertEquals(wrappedWidth, 27);
210 * add 9 pixels to width to gain a residue column
213 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
215 assertEquals(wrappedWidth, 27); // 8px not enough
217 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
219 assertEquals(wrappedWidth, 28); // 9px is enough
222 * now West but not East scale - lose 39 pixels or 4 columns
224 av.setScaleLeftWrapped(true);
225 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
227 assertEquals(wrappedWidth, 24);
230 * adding 3 pixels to width regains one column
233 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
235 Assert.assertTrue(wrappedWidth == 24 || wrappedWidth == 25,
236 "WrappedWidth [" + wrappedWidth + "] should be 24 or 25"); // 2px
240 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
242 assertEquals(wrappedWidth, 25); // 3px is enough
245 * turn off scales left and right, make width exactly 157 columns
247 av.setScaleLeftWrapped(false);
248 canvasWidth = al.getWidth() * charWidth;
249 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
250 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
254 * Test the method that computes wrapped width in residues, height of wrapped
255 * widths in pixels, and the number of widths visible
257 @Test(groups = "Functional")
258 public void testCalculateWrappedGeometry_withAnnotations()
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);
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();
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);
281 SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
284 * first with scales above, left, right
286 av.setShowAnnotation(true);
287 av.setScaleAboveWrapped(true);
288 av.setScaleLeftWrapped(true);
289 av.setScaleRightWrapped(true);
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
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
299 // ..............................mac................PC................linux?
301 labelWidth == 39 || labelWidth == 35 || labelWidth == 36);// 3 * 9 +
311 // int labelWidth = fm.stringWidth("000") + charWidth;
312 // assertEquals(labelWidth,
313 // Platform.isMac() ? 3 * 9 + charWidth : 3 * 8 + charWidth);
315 int annotationHeight = testee.getAnnotationHeight();
318 * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
319 * take the whole multiple of character widths
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"),
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);
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
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);
346 * reduce canvas height by 1 pixel - should not be enough height
350 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
351 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
354 * turn off scale above - can now fit in 2 and a bit widths
356 av.setScaleAboveWrapped(false);
357 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
358 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
361 * reduce height to enough for 2 widths and not quite a third
362 * i.e. two repeating heights + spacer + sequence - 1 pixel
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);
370 * add 1 pixel to height - should now get 3 widths drawn
373 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
374 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
377 private static int nTest = 0;
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
384 @Test(groups = "Functional")
385 public void testCalculateWrappedGeometry_fromScrolled()
387 // debugOut("12]", new Runnable()
395 // .toString(new NullPointerException().getStackTrace())
396 // .replace(',', '\n') + "\n\n";
398 // System.err.println(s);
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);
414 s += "flushing events";
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()));
440 * Let me know when the output indicates the specified message.
445 private void debugOut(String msg, Runnable r)
447 int len = msg.length();
448 int c0 = msg.charAt(0);
449 boolean[] recording = new boolean[1];
450 System.setOut(new PrintStream(new OutputStream()
453 StringBuffer out = new StringBuffer();
456 public void write(int b) throws IOException
461 out.append((char) b);
462 if (out.length() == len)
464 if (out.toString().equals(msg))
468 recording[0] = false;
474 out.append((char) b);
481 // TODO Auto-generated method stub
485 private static void flushEvents()
487 ((sun.awt.SunToolkit) Toolkit.getDefaultToolkit()).flushPendingEvents();