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;
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;
38 import org.testng.Assert;
39 import org.testng.annotations.BeforeClass;
40 import org.testng.annotations.Test;
42 import junit.extensions.PA;
44 @Test(singleThreaded = true)
45 public class SeqCanvasTest
47 @BeforeClass(alwaysRun = true)
50 Thread.currentThread().setName("SeqCanvasTest Setup " + ++nTest);
52 Cache.loadProperties("test/jalview/io/testProps.jvprops");
54 Jalview.setSynchronous(true);
58 * Test the method that computes wrapped width in residues, height of wrapped
59 * widths in pixels, and the number of widths visible
61 @Test(groups = "Functional")
62 public void testCalculateWrappedGeometry_noAnnotations()
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);
73 SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
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);
83 // assertEquals(charHeight, Platform.isMac() ? 17 : 19);
84 // assertEquals(charWidth, Platform.isMac() ? 12 : 11);
87 * first with scales above, left, right
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
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
101 // ..............................mac................PC................linux?
103 labelWidth == 39 || labelWidth == 35 || labelWidth == 36);// 3 * 9 +
115 * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
116 * take the whole multiple of character widths
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"),
127 int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
128 assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
129 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
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
136 canvasHeight = charHeight * (17 * 2 + 3);
137 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
138 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
141 * reduce canvas height by 1 pixel
142 * - should not be enough height to draw 3 widths
145 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
146 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
149 * turn off scale above - can now fit in 2 and a bit widths
151 av.setScaleAboveWrapped(false);
152 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
153 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
156 * reduce height to enough for 2 widths and not quite a third
157 * i.e. two repeating heights + spacer + sequence - 1 pixel
159 canvasHeight = charHeight * (16 * 2 + 2) - 1;
160 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
161 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
164 * make canvas width enough for scales and 20 residues
166 canvasWidth = 2 * labelWidth + 20 * charWidth;
167 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
169 assertEquals(wrappedWidth, 20);
172 * reduce width by 1 pixel - rounds down to 19 residues
175 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
177 assertEquals(wrappedWidth, 19);
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
183 av.setScaleLeftWrapped(false);
184 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
186 assertEquals(wrappedWidth, 23);
189 * add 10 pixels to width to fit in another whole residue column
192 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
194 assertEquals(wrappedWidth, 23);
196 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
198 assertEquals(wrappedWidth, 24);
201 * turn off East scale to gain 39 more pixels (3 columns remainder 3)
203 av.setScaleRightWrapped(false);
204 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
206 assertEquals(wrappedWidth, 27);
209 * add 9 pixels to width to gain a residue column
212 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
214 assertEquals(wrappedWidth, 27); // 8px not enough
216 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
218 assertEquals(wrappedWidth, 28); // 9px is enough
221 * now West but not East scale - lose 39 pixels or 4 columns
223 av.setScaleLeftWrapped(true);
224 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
226 assertEquals(wrappedWidth, 24);
229 * adding 3 pixels to width regains one column
232 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
234 Assert.assertTrue(wrappedWidth == 24 || wrappedWidth == 25,
235 "WrappedWidth [" + wrappedWidth + "] should be 24 or 25"); // 2px
239 wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
241 assertEquals(wrappedWidth, 25); // 3px is enough
244 * turn off scales left and right, make width exactly 157 columns
246 av.setScaleLeftWrapped(false);
247 canvasWidth = al.getWidth() * charWidth;
248 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
249 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
253 * Test the method that computes wrapped width in residues, height of wrapped
254 * widths in pixels, and the number of widths visible
256 @Test(groups = "Functional")
257 public void testCalculateWrappedGeometry_withAnnotations()
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);
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();
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);
280 SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
283 * first with scales above, left, right
285 av.setShowAnnotation(true);
286 av.setScaleAboveWrapped(true);
287 av.setScaleLeftWrapped(true);
288 av.setScaleRightWrapped(true);
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
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
298 // ..............................mac................PC................linux?
300 labelWidth == 39 || labelWidth == 35 || labelWidth == 36);// 3 * 9 +
310 // int labelWidth = fm.stringWidth("000") + charWidth;
311 // assertEquals(labelWidth,
312 // Platform.isMac() ? 3 * 9 + charWidth : 3 * 8 + charWidth);
314 int annotationHeight = testee.getAnnotationHeight();
317 * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
318 * take the whole multiple of character widths
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"),
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);
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
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);
345 * reduce canvas height by 1 pixel - should not be enough height
349 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
350 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
353 * turn off scale above - can now fit in 2 and a bit widths
355 av.setScaleAboveWrapped(false);
356 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
357 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
360 * reduce height to enough for 2 widths and not quite a third
361 * i.e. two repeating heights + spacer + sequence - 1 pixel
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);
369 * add 1 pixel to height - should now get 3 widths drawn
372 testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
373 assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
376 private static int nTest = 0;
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
383 @Test(groups = "Functional")
384 public void testCalculateWrappedGeometry_fromScrolled()
386 // debugOut("12]", new Runnable()
394 // .toString(new NullPointerException().getStackTrace())
395 // .replace(',', '\n') + "\n\n";
397 // System.err.println(s);
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);
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()));
436 * Let me know when the output indicates the specified message.
441 private void debugOut(String msg, Runnable r)
443 int len = msg.length();
444 int c0 = msg.charAt(0);
445 boolean[] recording = new boolean[1];
446 System.setOut(new PrintStream(new OutputStream()
449 StringBuffer out = new StringBuffer();
452 public void write(int b) throws IOException
457 out.append((char) b);
458 if (out.length() == len)
460 if (out.toString().equals(msg))
464 recording[0] = false;
470 out.append((char) b);
477 // TODO Auto-generated method stub
481 private static void flushEvents()
483 ((sun.awt.SunToolkit) Toolkit.getDefaultToolkit()).flushPendingEvents();