1 /*******************************************************************************
2  * Copyright (c) 2018 Red Hat and others. All rights reserved.
3  * The contents of this file are made available under the terms
4  * of the GNU Lesser General Public License (LGPL) Version 2.1 that
5  * accompanies this distribution (lgpl-v21.txt).  The LGPL is also
6  * available at http://www.gnu.org/licenses/lgpl.html.  If the version
7  * of the LGPL at http://www.gnu.org is different to the version of
8  * the LGPL accompanying this distribution and there is any conflict
9  * between the two license versions, the terms of the LGPL accompanying
10  * this distribution shall govern.
11  *
12  * Contributors:
13  *     Red Hat - initial API and implementation
14  *******************************************************************************/
15 package org.eclipse.swt.tests.gtk.snippets;
16 
17 import static org.junit.Assert.assertTrue;
18 
19 import java.util.concurrent.atomic.AtomicInteger;
20 
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.events.ControlEvent;
23 import org.eclipse.swt.events.ControlListener;
24 import org.eclipse.swt.graphics.Rectangle;
25 import org.eclipse.swt.widgets.Button;
26 import org.eclipse.swt.widgets.Control;
27 import org.eclipse.swt.widgets.Display;
28 import org.eclipse.swt.widgets.Shell;
29 import org.junit.After;
30 import org.junit.Before;
31 import org.junit.Test;
32 
33 
34 /**
35  * Title: Bug 465280 – [GTK3] OS.gtk_widget_get_allocation returns (0,0) for invisible controls
36  * How to run: These are jUnits. Select the class and run as jUnits.
37  * Bug description: getBounds is not working properly in combination with setVisible.
38  * Expected results: All tests should pass, but on Gtk3.8+ the "fails_*" tests fail.
39  * GTK version(s): GTK3.8+
40  *
41  * This is a snippet to validate upcomming bug submission. These will be used to make new jUnits later.
42  */
43 public class Bug497705_setBoundsAfterSetVisible {
44 
45 	boolean debugShowWidget = false; // true = see shell & widget.  False = tests run without interaction.
46 
47 	Display display;
48 	Shell shell ;
49 	private StringBuilder log;
50 	private int x;
51 	private int y;
52 	private int height;
53 	private int width;
54 	private boolean passed;
55 	private Rectangle bounds;
56 	Control testControl;
57 
58 	@Before
setUp()59 	public void setUp() {
60 		display = Display.getDefault();
61 		shell = new Shell(display);
62 		shell.setSize(400, 400);
63 		log = new StringBuilder("");
64 		x = 5;
65 		y = 10;
66 		height = 100;
67 		width = 200;
68 		passed = true;
69 		testControl = new Button(shell, SWT.PUSH);
70 	}
71 
72 	@Test
fails_test2_setBoundsAfterVisibility()73 	public void fails_test2_setBoundsAfterVisibility() { // Works on Gtk2, Fails on Gtk3.
74 		testControl.setVisible(false);
75 		testControl.setVisible(true);
76 
77 		testControl.setBounds(x, y, width, height);
78 
79 		bounds = testControl.getBounds();
80 		verifyBounds();
81 	}
82 	@Test
fails_test2b_setBoundsInvisibleWidgets()83 	public void fails_test2b_setBoundsInvisibleWidgets() { // Works on Gtk2, Fails on Gtk3.
84 		testControl.setVisible(false);
85 
86 		testControl.setBounds(x, y, width, height);
87 
88 		bounds = testControl.getBounds();
89 		verifyBounds();
90 	}
91 
92 
93 	@Test
fails_test3_setBoundsBetweenVisibility()94 	public void fails_test3_setBoundsBetweenVisibility() { // Works on Gtk2, Fails on Gtk3.
95 		testControl.setVisible(false);
96 		testControl.setBounds(x, y, width, height);
97 		testControl.setVisible(true);
98 
99 		bounds = testControl.getBounds();
100 		verifyBounds();
101 	}
102 
103 	@Test
fails_moveInnvisibleControl()104 	public void fails_moveInnvisibleControl() {
105 		testControl.setBounds(4, 4, 6, 6);
106 
107 		shell.open(); for (int i = 0; i < 500; i++) display.readAndDispatch();
108 		testControl.setVisible(false);
109 		shell.open(); for (int i = 0; i < 500; i++) display.readAndDispatch();
110 		testControl.setBounds(x, y, width, height);
111 		shell.open(); for (int i = 0; i < 500; i++) display.readAndDispatch();
112 		testControl.setVisible(true);
113 		shell.open(); for (int i = 0; i < 500; i++) display.readAndDispatch();
114 
115 		bounds = testControl.getBounds(); // Visually looks ok. (width/height), but programatically incorrect getBounds().
116 		verifyBounds();
117 	}
118 
119 	@Test
fails_unecessaryEvents()120 	public void fails_unecessaryEvents() { // Breaks on Gtk3.8 & onwards
121 		testControl.setVisible(false);
122 
123 		AtomicInteger resizeCount = new AtomicInteger(0);
124 		AtomicInteger moveCount = new AtomicInteger(0);
125 
126 		testControl.addControlListener(new ControlListener() {
127 			@Override
128 			public void controlResized(ControlEvent e) {
129 				resizeCount.incrementAndGet();
130 			}
131 			@Override
132 			public void controlMoved(ControlEvent e) {
133 				moveCount.incrementAndGet();
134 			}
135 		});
136 
137 		for (int i = 0; i < 10; i++) {
138 			testControl.setBounds(x, y, width, height); // Once bounds set, calling same bounds shouldn't trigger SWT.MOVE events.
139 		}
140 		if (resizeCount.get() != 1 || moveCount.get() != 1) {
141 			passed = false;
142 			log.append("\nERROR:\nExpected only one Resize and one Move event.\nActually received R/M:" + resizeCount.get() + "/" + moveCount.get());
143 		}
144 	}
145 
146 	@Test
works_test1_setBoundsBeforeVisibility()147 	public void works_test1_setBoundsBeforeVisibility () {
148 		// Note, here you can see that getBounds() does work for widgets that are set to be invisible, but
149 		// only if setBounds was called before setVisible(false).
150 		// The problem is inside setBounds, if it's called after setVisible(false), then getBounds() returns wrong output.
151 		testControl.setBounds(x, y, width, height);
152 
153 		testControl.setVisible(false);
154 //		testControl.setVisible(true);   // commenting or uncommenting this doesn't change anything.
155 
156 		bounds = testControl.getBounds();
157 		verifyBounds();
158 	}
159 
160 	@Test
works_zeroSize()161 	public void works_zeroSize() { // There has to be a mechanism by which we set with and height to 0.
162 		testControl.setBounds(x, y, width, height);
163 		x = y = width = height = 0;
164 		testControl.setBounds(x, y, width, height);
165 		bounds = testControl.getBounds();
166 		verifyBounds();
167 	}
168 
169 	@Test
works_drainingQueue()170 	public void works_drainingQueue() {
171 		testControl.setVisible(false);
172 		testControl.setVisible(true);
173 
174 		// doing readAndDispatch up *before* 'setBounds()' doesn't make a difference.
175 		testControl.setBounds(x, y, width, height);
176 
177 		// doing readAndDispatch *After* setBounds *many times* gives gtk time to update it's cache, and getBounds() returns correct coordinates.
178 		shell.open();
179 		for (int i = 0; i < 1000; i++)
180 			display.readAndDispatch();
181 
182 		bounds = testControl.getBounds();
183 		verifyBounds();
184 	}
185 
186 	@After
tearDown()187 	public void tearDown() {
188 		if (debugShowWidget) {
189 			if (!passed) System.err.println(log.toString());
190 
191 			shell.open();
192 			while (!shell.isDisposed()) {
193 				if (!display.readAndDispatch())
194 					display.sleep();
195 			}
196 			display.dispose();
197 		}
198 		assertTrue(log.toString(), passed);
199 	}
200 
verifyBounds()201 	private void verifyBounds() {
202 		if (bounds.x != x | bounds.y != y) {
203 			passed = false;
204 			log.append("\nERROR: x,y do not match. Expected:" + x + "/" + y + " Actual:" + bounds.x + "/" + bounds.y);
205 		}
206 		if (bounds.height != height | bounds.width != width) {
207 			passed = false;
208 			log.append("\nERROR: width,height do not match. Expected:" + width + "/" + height + " Actual:"
209 					+ bounds.width + "/" + bounds.height);
210 		}
211 	}
212 
213 }
214