JAL-4397 Adjustments for macOS HiDPI
[jalview.git] / test / jalview / gui / AlignmentPanelTest.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 import static org.testng.Assert.assertNotEquals;
25 import static org.testng.Assert.assertNotNull;
26 import static org.testng.Assert.assertTrue;
27
28 import java.awt.Container;
29 import java.awt.Dimension;
30 import java.awt.Font;
31 import java.awt.FontMetrics;
32 import java.awt.event.MouseEvent;
33 import java.lang.reflect.InvocationTargetException;
34
35 import javax.swing.SwingUtilities;
36
37 import org.testng.annotations.BeforeMethod;
38 import org.testng.annotations.Test;
39
40 import jalview.api.AlignViewportI;
41 import jalview.bin.Cache;
42 import jalview.bin.Jalview;
43 import jalview.datamodel.AlignmentAnnotation;
44 import jalview.datamodel.SequenceI;
45 import jalview.io.DataSourceType;
46 import jalview.io.FileLoader;
47 import jalview.util.Platform;
48 import jalview.viewmodel.ViewportRanges;
49
50 public class AlignmentPanelTest
51 {
52   AlignFrame af;
53
54   @BeforeMethod(alwaysRun = true)
55   public void setUp() throws InvocationTargetException, InterruptedException
56   {
57     Jalview.main(
58             new String[]
59             { "--nonews", "--props", "test/jalview/testProps.jvprops" });
60
61     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
62             Boolean.TRUE.toString());
63     af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa",
64             DataSourceType.FILE);
65
66     /*
67      * ensure the panel has been repainted and so ViewportRanges set
68      */
69     SwingUtilities.invokeAndWait(new Runnable()
70     {
71       @Override
72       public void run()
73       {
74         af.repaint();
75       }
76     });
77
78     /*
79      * wait for Consensus thread to complete
80      */
81     do
82     {
83       try
84       {
85         Thread.sleep(50);
86       } catch (InterruptedException x)
87       {
88       }
89     } while (af.getViewport().getCalcManager().isWorking());
90   }
91
92   /**
93    * Test side effect that end residue is set correctly by setScrollValues, with
94    * or without hidden columns
95    */
96   @Test(groups = "Functional")
97   public void testSetScrollValues()
98   {
99     ViewportRanges ranges = af.getViewport().getRanges();
100     af.alignPanel.setScrollValues(0, 0);
101
102     int oldres = ranges.getEndRes();
103     af.alignPanel.setScrollValues(-1, 5);
104
105     // setting -ve x value does not change residue
106     assertEquals(ranges.getEndRes(), oldres);
107
108     af.alignPanel.setScrollValues(0, 5);
109
110     // setting 0 as x value does not change residue
111     assertEquals(ranges.getEndRes(), oldres);
112
113     af.alignPanel.setScrollValues(5, 5);
114     // setting x value to 5 extends endRes by 5 residues
115     assertEquals(ranges.getEndRes(), oldres + 5);
116
117     // scroll to position after hidden columns sets endres to oldres (width) +
118     // position
119     int scrollpos = 60;
120     af.getViewport().hideColumns(30, 50);
121     af.alignPanel.setScrollValues(scrollpos, 5);
122     assertEquals(ranges.getEndRes(), oldres + scrollpos);
123
124     // scroll to position within hidden columns, still sets endres to oldres +
125     // position
126     // not sure if this is actually correct behaviour but this is what Jalview
127     // currently does
128     scrollpos = 40;
129     af.getViewport().showAllHiddenColumns();
130     af.getViewport().hideColumns(30, 50);
131     af.alignPanel.setScrollValues(scrollpos, 5);
132     assertEquals(ranges.getEndRes(), oldres + scrollpos);
133
134     // scroll to position within <width> distance of the end of the alignment
135     // endRes should be set to width of alignment - 1
136     scrollpos = 130;
137     af.getViewport().showAllHiddenColumns();
138     af.alignPanel.setScrollValues(scrollpos, 5);
139     assertEquals(ranges.getEndRes(),
140             af.getViewport().getAlignment().getWidth() - 1);
141
142     // now hide some columns, and scroll to position within <width>
143     // distance of the end of the alignment
144     // endRes should be set to width of alignment - 1 - the number of hidden
145     // columns
146     af.getViewport().hideColumns(30, 50);
147     af.alignPanel.setScrollValues(scrollpos, 5);
148     assertEquals(ranges.getEndRes(),
149             af.getViewport().getAlignment().getWidth() - 1 - 21); // 21 is the
150                                                                   // number of
151                                                                   // hidden
152                                                                   // columns
153   }
154
155   /**
156    * Test that update layout reverts to original (unwrapped) values for endRes
157    * when switching from wrapped back to unwrapped mode (JAL-2739)
158    */
159   @Test(groups = "Functional")
160   public void testUpdateLayout_endRes()
161   {
162     // get details of original alignment dimensions
163     ViewportRanges ranges = af.getViewport().getRanges();
164     int endres = ranges.getEndRes();
165
166     // wrap
167     af.alignPanel.getAlignViewport().setWrapAlignment(true);
168     af.alignPanel.updateLayout();
169
170     // endRes has changed
171     assertNotEquals(ranges.getEndRes(), endres);
172
173     // unwrap
174     af.alignPanel.getAlignViewport().setWrapAlignment(false);
175     af.alignPanel.updateLayout();
176
177     // endRes back to original value
178     assertEquals(ranges.getEndRes(), endres);
179   }
180
181   /**
182    * Test the variant of calculateIdWidth that only recomputes the width if it
183    * is not already saved in the viewport (initial value is -1)
184    */
185   @Test(groups = "Functional")
186   public void testCalculateIdWidth_noArgs()
187   {
188     AlignViewportI av = af.alignPanel.getAlignViewport();
189     av.setShowJVSuffix(true);
190     av.setFont(new Font("Courier", Font.PLAIN, 15), true);
191
192     av.setIdWidth(0);
193     Dimension d = af.alignPanel.calculateIdWidth();
194     assertEquals(d.width, 0);
195     assertEquals(d.height, 0);
196
197     av.setIdWidth(99);
198     d = af.alignPanel.calculateIdWidth();
199     assertEquals(d.width, 99);
200     assertEquals(d.height, 0);
201
202     /*
203      * note 4 pixels padding are added to the longest sequence name width
204      */
205     av.setIdWidth(-1); // force recalculation
206     d = af.alignPanel.calculateIdWidth();
207     assertEquals(d.width, 166); // 4 + pixel width of "Q93Z60_ARATH/1-118"
208     assertEquals(d.height, 12);
209     assertEquals(d.width, av.getIdWidth());
210   }
211
212   /**
213    * Test the variant of calculateIdWidth that computes the longest of any
214    * sequence name or annotation label width FIXME: JAL-4291: test needs
215    * updating for JAL-244 and JAL-4091
216    */
217   @Test(groups = "Functional", enabled = false)
218   public void testCalculateIdWidth_withMaxWidth()
219   {
220     AlignViewportI av = af.alignPanel.getAlignViewport();
221     av.setShowJVSuffix(true);
222     av.setFont(new Font("Courier", Font.PLAIN, 15), true);
223     av.setShowAnnotation(false);
224     av.setIdWidth(18);
225
226     FontMetrics fmfor = new Container()
227             .getFontMetrics(new Font(af.viewport.font.getName(),
228                     Font.ITALIC, af.viewport.font.getSize()));
229
230     /*
231      * note 4 pixels 'padding' are added to the longest seq name/annotation label
232      */
233     Dimension d = af.alignPanel.calculateIdWidth(2000);
234     // Assumption ID_WIDTH_PADDING == 4
235     int expwidth = 3 + fmfor.stringWidth("Conservation");
236
237     assertEquals(d.width, 166); // 4 + pixel width of "Q93Z60_ARATH/1-118"
238     assertEquals(d.height, 12); // fixed value (not used?)
239     assertEquals(av.getIdWidth(), expwidth); // not changed by this method
240
241     /*
242      * make the longest sequence name longer
243      */
244     SequenceI seq = af.viewport.getAlignment()
245             .findSequenceMatch("Q93Z60_ARATH")[0];
246     seq.setName(seq.getName() + "MMMMM");
247     d = af.alignPanel.calculateIdWidth(2000);
248     assertEquals(d.width, 211); // 4 + pixel width of "Q93Z60_ARATHMMMMM/1-118"
249     assertEquals(d.height, 12);
250     assertEquals(av.getIdWidth(), 18); // unchanged
251
252     /*
253      * make the longest annotation name even longer
254      * note this is checked even if annotations are not shown
255      */
256     AlignmentAnnotation aa = av.getAlignment().getAlignmentAnnotation()[0];
257     aa.label = "THIS IS A VERY LONG LABEL INDEED";
258     d = af.alignPanel.calculateIdWidth(2000);
259     // Assumption ID_WIDTH_PADDING == 3
260     expwidth = 3 + fmfor.stringWidth(aa.label);
261
262     assertEquals(d.width, expwidth); // 228 == ID_WIDTH_PADDING + pixel width of
263                                      // "THIS IS A VERY LONG LABEL INDEED"
264     assertEquals(d.height, 12);
265
266     /*
267      * override with maxwidth
268      * note the 4 pixels padding is added to this value
269      */
270     d = af.alignPanel.calculateIdWidth(213);
271     assertEquals(d.width, 217);
272     assertEquals(d.height, 12);
273   }
274
275   @Test(groups = { "Functional", "Not-bamboo" })
276   public void testGetVisibleWidth()
277   {
278     double scaling = jalview.gui.JvSwingUtilsTest.getScaling(af.alignPanel);
279     /*
280      * width for onscreen rendering is IDPanel width
281      */
282     int w = af.alignPanel.getVisibleIdWidth(true);
283     assertEquals(w, af.alignPanel.getIdPanel().getWidth());
284
285     // different scaling (1.0, 2.0) gives different results
286     int expectedWidth = scaling == 1.0 ? 112 : Platform.isMac() ? 115 : 107;
287     assertEquals(w, expectedWidth);
288
289     /*
290      * width for offscreen rendering is the same
291      * if no fixed id width is specified in preferences
292      */
293     Cache.setProperty("FIGURE_AUTOIDWIDTH", Boolean.FALSE.toString());
294     Cache.removeProperty("FIGURE_FIXEDIDWIDTH");
295     assertEquals(w, af.alignPanel.getVisibleIdWidth(false));
296
297     /*
298      * preference for fixed id width - note 4 pixels padding is added
299      */
300     Cache.setProperty("FIGURE_FIXEDIDWIDTH", "120");
301     assertEquals(124, af.alignPanel.getVisibleIdWidth(false));
302
303     /*
304      * preference for auto id width overrides fixed width
305      */
306     Cache.setProperty("FIGURE_AUTOIDWIDTH", Boolean.TRUE.toString());
307     w = af.alignPanel.getVisibleIdWidth(false);
308     // allow some leeway for different OS renderings
309     assertTrue(w > 105 && w < 120);
310     // different scaling (1.0, 2.0) gives different results
311     assertEquals(w, expectedWidth);
312   }
313
314   @Test(groups = { "Functional", "Not-bamboo" })
315   public void testresetIdWidth()
316   {
317     double scaling = jalview.gui.JvSwingUtilsTest.getScaling(af.alignPanel);
318     /*
319      * width for onscreen rendering is IDPanel width
320      */
321     int w = af.alignPanel.getVisibleIdWidth(true);
322     int actual = af.alignPanel.getIdPanel().getWidth();
323     assertEquals(w, actual);
324     // allow some leeway for different OS renderings
325     assertTrue(w > 105 && w < 120);
326     // different scaling (1.0, 2.0) gives different results
327     int expectedWidth = scaling == 1.0 ? 112 : Platform.isMac() ? 115 : 107;
328     assertEquals(w, expectedWidth);
329
330     // manually adjust
331     af.viewport.setIdWidth(200);
332     // fake mouse drag sets manuallyAdjusted to true (0,0 not moving mouse)
333     MouseEvent drag = new MouseEvent(af.alignPanel,
334             MouseEvent.MOUSE_DRAGGED, System.currentTimeMillis(), 0, 0, 0,
335             MouseEvent.BUTTON1, false);
336     af.alignPanel.idwidthAdjuster.mouseDragged(drag);
337     af.alignPanel.paintComponent(af.alignPanel.getGraphics());
338     w = af.alignPanel.calculateIdWidth().width;
339     assertTrue(
340             af.alignPanel.getIdPanel().getIdCanvas().isManuallyAdjusted());
341     actual = af.alignPanel.getIdPanel().getWidth();
342     assertEquals(w, actual);
343
344     af.viewport.setIdWidth(-1);
345     af.alignPanel.calculateIdWidth();
346     af.alignPanel.getIdPanel().getIdCanvas().setManuallyAdjusted(false);
347     w = af.alignPanel.calculateIdWidth().width;
348     af.alignPanel.paintComponent(af.alignPanel.getGraphics());
349
350     actual = af.alignPanel.getIdPanel().getWidth();
351     assertEquals(w, actual);
352
353     // setting a negative IdWidth and then running calculateIdWidth resets width
354     // to optimal id width
355     // allow some leeway for different OS renderings
356     assertTrue(w > 105 && w < 120);
357     // different scaling (1.0, 2.0) gives different results
358     assertEquals(w, expectedWidth);
359   }
360
361   @Test(groups = "Functional")
362   public void testSetOverviewTitle()
363   {
364     OverviewPanel ov1 = this.af.openOverviewPanel(true);
365     String alignFrameTitle = af.getTitle();
366     assertEquals(ov1.getTitle(), "Overview " + alignFrameTitle);
367
368     /*
369      * on New View, existing overview should get " Original" added to title
370      * and new view's overview should get " View 1" added
371      */
372     af.newView_actionPerformed(null);
373     assertEquals(ov1.getTitle(),
374             "Overview " + alignFrameTitle + " Original");
375     OverviewPanel ov2 = this.af.openOverviewPanel(true);
376     assertEquals(ov2.getTitle(), "Overview " + alignFrameTitle + " View 1");
377   }
378
379   @Test(groups = "Functional")
380   public void testSetOverviewTitle_automaticOverview()
381   {
382     Cache.setProperty("SHOW_OVERVIEW", "true");
383     AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
384             "examples/uniref50.fa", DataSourceType.FILE);
385     OverviewPanel ov1 = alignFrame.alignPanel.getOverviewPanel();
386     assertNotNull(ov1);
387     String alignFrameTitle = alignFrame.getTitle();
388     assertEquals(ov1.getTitle(), "Overview " + alignFrameTitle);
389
390     /*
391      * on New View, existing overview should get " Original" added to title
392      * and new view's automatic overview should have " View 1" added
393      */
394     alignFrame.newView_actionPerformed(null);
395     assertEquals(ov1.getTitle(),
396             "Overview " + alignFrameTitle + " Original");
397     OverviewPanel ov2 = alignFrame.alignPanel.getOverviewPanel();
398     assertNotNull(ov2);
399     assertEquals(ov2.getTitle(), "Overview " + alignFrameTitle + " View 1");
400   }
401 }