Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
SWTUtils |
|
| 2.6363636363636362;2.636 | ||||
SWTUtils$1 |
|
| 2.6363636363636362;2.636 | ||||
SWTUtils$2 |
|
| 2.6363636363636362;2.636 | ||||
SWTUtils$3 |
|
| 2.6363636363636362;2.636 | ||||
SWTUtils$4 |
|
| 2.6363636363636362;2.636 | ||||
SWTUtils$5 |
|
| 2.6363636363636362;2.636 | ||||
SWTUtils$6 |
|
| 2.6363636363636362;2.636 |
1 | 52635 | /******************************************************************************* |
2 | * Copyright (c) 2008 Ketan Padegaonkar and others. | |
3 | * All rights reserved. This program and the accompanying materials | |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
7 | * | |
8 | * Contributors: | |
9 | * Ketan Padegaonkar - initial API and implementation | |
10 | * Hans Schwaebli - http://swtbot.org/bugzilla/show_bug.cgi?id=108 | |
11 | *******************************************************************************/ | |
12 | package org.eclipse.swtbot.swt.finder.utils; | |
13 | ||
14 | import java.io.File; | |
15 | import java.lang.reflect.InvocationTargetException; | |
16 | import java.lang.reflect.Method; | |
17 | import java.text.MessageFormat; | |
18 | ||
19 | import org.apache.log4j.Logger; | |
20 | import org.eclipse.swt.SWT; | |
21 | import org.eclipse.swt.graphics.GC; | |
22 | import org.eclipse.swt.graphics.Image; | |
23 | import org.eclipse.swt.graphics.ImageData; | |
24 | import org.eclipse.swt.graphics.ImageLoader; | |
25 | import org.eclipse.swt.graphics.Rectangle; | |
26 | import org.eclipse.swt.widgets.Control; | |
27 | import org.eclipse.swt.widgets.Display; | |
28 | import org.eclipse.swt.widgets.Shell; | |
29 | import org.eclipse.swt.widgets.Text; | |
30 | import org.eclipse.swt.widgets.Widget; | |
31 | import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; | |
32 | import org.eclipse.swtbot.swt.finder.results.BoolResult; | |
33 | import org.eclipse.swtbot.swt.finder.results.Result; | |
34 | import org.eclipse.swtbot.swt.finder.utils.internal.Assert; | |
35 | import org.eclipse.swtbot.swt.finder.utils.internal.NextWidgetFinder; | |
36 | import org.eclipse.swtbot.swt.finder.utils.internal.PreviousWidgetFinder; | |
37 | import org.eclipse.swtbot.swt.finder.utils.internal.ReflectionInvoker; | |
38 | import org.eclipse.swtbot.swt.finder.utils.internal.SiblingFinder; | |
39 | import org.eclipse.swtbot.swt.finder.utils.internal.WidgetIndexFinder; | |
40 | import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; | |
41 | import org.eclipse.swtbot.swt.finder.waits.ICondition; | |
42 | import org.eclipse.swtbot.swt.finder.widgets.TimeoutException; | |
43 | ||
44 | /** | |
45 | * @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com> | |
46 | * @version $Id$ | |
47 | */ | |
48 | 1 | public abstract class SWTUtils { |
49 | ||
50 | /** The logger. */ | |
51 | 1 | private static Logger log = Logger.getLogger(SWTUtils.class); |
52 | ||
53 | /** | |
54 | * The display used for the GUI. | |
55 | */ | |
56 | private static Display display; | |
57 | ||
58 | /** | |
59 | * @param w the widget | |
60 | * @return the siblings of the widget, or an empty array, if there are none. | |
61 | */ | |
62 | public static Widget[] siblings(final Widget w) { | |
63 | 17 | if ((w == null) || w.isDisposed()) |
64 | 0 | return new Widget[] {}; |
65 | 17 | return UIThreadRunnable.syncExec(w.getDisplay(), new SiblingFinder(w)); |
66 | } | |
67 | ||
68 | /** | |
69 | * Gets the index of the given widget in its current container. | |
70 | * | |
71 | * @param w the widget | |
72 | * @return -1 if the the widget is <code>null</code> or if the widget does not have a parent; a number greater than | |
73 | * or equal to zero indicating the index of the widget among its siblings | |
74 | */ | |
75 | public static int widgetIndex(final Widget w) { | |
76 | 1720 | if ((w == null) || w.isDisposed()) |
77 | 1 | return -1; |
78 | 1719 | return UIThreadRunnable.syncExec(w.getDisplay(), new WidgetIndexFinder(w)); |
79 | } | |
80 | ||
81 | /** | |
82 | * Gets the previous sibling of the passed in widget. | |
83 | * | |
84 | * @param w the widget | |
85 | * @return the previous sibling of w | |
86 | */ | |
87 | public static Widget previousWidget(final Widget w) { | |
88 | 668 | if ((w == null) || w.isDisposed()) |
89 | 0 | return null; |
90 | 668 | return UIThreadRunnable.syncExec(w.getDisplay(), new PreviousWidgetFinder(w)); |
91 | } | |
92 | ||
93 | /** | |
94 | * Gets the next sibling of this passed in widget. | |
95 | * | |
96 | * @param w the widget. | |
97 | * @return the sibling of the specified widget, or <code>null</code> if it has none. | |
98 | */ | |
99 | public static Widget nextWidget(Widget w) { | |
100 | 2 | if ((w == null) || w.isDisposed()) |
101 | 0 | return null; |
102 | 2 | return UIThreadRunnable.syncExec(w.getDisplay(), new NextWidgetFinder(w)); |
103 | } | |
104 | ||
105 | /** | |
106 | * Gets the text of the given object. | |
107 | * | |
108 | * @param obj the object which should be a widget. | |
109 | * @return the result of invocation of Widget#getText() | |
110 | */ | |
111 | public static String getText(final Object obj) { | |
112 | 1628 | if ((obj instanceof Widget) && !((Widget) obj).isDisposed()) { |
113 | 1628 | Widget widget = (Widget) obj; |
114 | 1628 | String text = UIThreadRunnable.syncExec(widget.getDisplay(), new ReflectionInvoker(obj, "getText")); //$NON-NLS-1$ |
115 | 1628 | text = text.replaceAll(Text.DELIMITER, "\n"); //$NON-NLS-1$ |
116 | 1628 | return text; |
117 | } | |
118 | 0 | return ""; //$NON-NLS-1$ |
119 | } | |
120 | ||
121 | /** | |
122 | * Gets the tooltip text for the given object. | |
123 | * | |
124 | * @param obj the object which should be a widget. | |
125 | * @return the result of invocation of Widget#getToolTipText() | |
126 | * @since 1.0 | |
127 | */ | |
128 | public static String getToolTipText(final Object obj) { | |
129 | 1 | if ((obj instanceof Widget) && !((Widget) obj).isDisposed()) { |
130 | 1 | Widget widget = (Widget) obj; |
131 | 1 | return UIThreadRunnable.syncExec(widget.getDisplay(), new ReflectionInvoker(obj, "getToolTipText")); //$NON-NLS-1$ |
132 | } | |
133 | 0 | return ""; //$NON-NLS-1$ |
134 | } | |
135 | ||
136 | /** | |
137 | * Converts the given widget to a string. | |
138 | * | |
139 | * @param w the widget. | |
140 | * @return the string representation of the widget. | |
141 | */ | |
142 | public static String toString(final Widget w) { | |
143 | 947 | if ((w == null) || w.isDisposed()) |
144 | 2 | return ""; //$NON-NLS-1$ |
145 | 945 | return toString(w.getDisplay(), w); |
146 | } | |
147 | ||
148 | /** | |
149 | * Convers the display and object to a string. | |
150 | * | |
151 | * @param display the display on which the object should be evaluated. | |
152 | * @param o the object to evaluate. | |
153 | * @return the string representation of the object when evaluated in the display thread. | |
154 | */ | |
155 | public static String toString(Display display, final Object o) { | |
156 | 945 | return ClassUtils.simpleClassName(o) + " {" + trimToLength(getText(o), 20) + "}"; //$NON-NLS-1$ //$NON-NLS-2$ |
157 | ||
158 | } | |
159 | ||
160 | /** | |
161 | * Trims the string to a given length, adds an ellipsis("...") if the string is trimmed. | |
162 | * | |
163 | * @param result The string to limit. | |
164 | * @param maxLength The length to limit it to. | |
165 | * @return The resulting string. | |
166 | */ | |
167 | private static String trimToLength(String result, int maxLength) { | |
168 | 945 | if (result.length() > maxLength) |
169 | 29 | result = result.substring(0, maxLength) + "..."; //$NON-NLS-1$ |
170 | 945 | return result; |
171 | } | |
172 | ||
173 | /** | |
174 | * Checks if the widget has the given style. | |
175 | * | |
176 | * @param w the widget. | |
177 | * @param style the style. | |
178 | * @return <code>true</code> if the widget has the specified style bit set. Otherwise <code>false</code>. | |
179 | */ | |
180 | public static boolean hasStyle(final Widget w, final int style) { | |
181 | 2617 | if ((w == null) || w.isDisposed()) |
182 | 1 | return false; |
183 | 2616 | if (style == SWT.NONE) |
184 | 1 | return true; |
185 | 2615 | return UIThreadRunnable.syncExec(w.getDisplay(), new BoolResult() { |
186 | public Boolean run() { | |
187 | 2615 | return (w.getStyle() & style) != 0; |
188 | } | |
189 | }); | |
190 | } | |
191 | ||
192 | /** | |
193 | * Sleeps for the given number of milliseconds. | |
194 | * | |
195 | * @param millis the time in milliseconds to sleep. | |
196 | */ | |
197 | public static void sleep(long millis) { | |
198 | try { | |
199 | 260 | Thread.sleep(millis); |
200 | 0 | } catch (InterruptedException e) { |
201 | 0 | throw new RuntimeException("Could not sleep", e); //$NON-NLS-1$ |
202 | } | |
203 | 260 | } |
204 | ||
205 | /** | |
206 | * Gets all the thread in the VM. | |
207 | * | |
208 | * @return all the threads in the VM. | |
209 | */ | |
210 | public static Thread[] allThreads() { | |
211 | 1 | ThreadGroup threadGroup = primaryThreadGroup(); |
212 | ||
213 | 1 | Thread[] threads = new Thread[64]; |
214 | 1 | int enumerate = threadGroup.enumerate(threads, true); |
215 | ||
216 | 1 | Thread[] result = new Thread[enumerate]; |
217 | 1 | System.arraycopy(threads, 0, result, 0, enumerate); |
218 | ||
219 | 1 | return result; |
220 | } | |
221 | ||
222 | /** | |
223 | * Gets the primary thread group. | |
224 | * | |
225 | * @return the top level thread group. | |
226 | */ | |
227 | public static ThreadGroup primaryThreadGroup() { | |
228 | 1 | ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); |
229 | 3 | while (threadGroup.getParent() != null) |
230 | 1 | threadGroup = threadGroup.getParent(); |
231 | 1 | return threadGroup; |
232 | } | |
233 | ||
234 | /** | |
235 | * Caches the display for later use. | |
236 | * | |
237 | * @return the display. | |
238 | */ | |
239 | public static Display display() { | |
240 | 18675 | if ((display == null) || display.isDisposed()) { |
241 | 1 | display = null; |
242 | 1 | Thread[] allThreads = allThreads(); |
243 | 8 | for (Thread thread : allThreads) { |
244 | 7 | Display d = Display.findDisplay(thread); |
245 | 7 | if (d != null) |
246 | 1 | display = d; |
247 | } | |
248 | 1 | if (display == null) |
249 | 0 | throw new IllegalStateException("Could not find a display"); //$NON-NLS-1$ |
250 | } | |
251 | 18675 | return display; |
252 | } | |
253 | ||
254 | /** | |
255 | * Checks if the widget text is an empty string. | |
256 | * | |
257 | * @param w the widget | |
258 | * @return <code>true</code> if the widget does not have any text set on it. Othrewise <code>false</code>. | |
259 | */ | |
260 | // TODO recommend changing the name to isEmptyText since null isn't being check and if getText returned a null an | |
261 | // exception would be thrown. | |
262 | public static boolean isEmptyOrNullText(Widget w) { | |
263 | 0 | return getText(w).trim().equals(""); //$NON-NLS-1$ |
264 | } | |
265 | ||
266 | /** | |
267 | * Invokes the specified methodName on the object, and returns the result, or <code>null</code> if the method | |
268 | * returns void. | |
269 | * | |
270 | * @param object the object | |
271 | * @param methodName the method name | |
272 | * @return the result of invoking the method on the object | |
273 | * @throws NoSuchMethodException if the method methodName does not exist. | |
274 | * @throws IllegalAccessException if the java access control does not allow invocation. | |
275 | * @throws InvocationTargetException if the method methodName throws an exception. | |
276 | * @see Method#invoke(Object, Object[]) | |
277 | * @since 1.0 | |
278 | */ | |
279 | public static Object invokeMethod(final Object object, String methodName) throws NoSuchMethodException, IllegalAccessException, | |
280 | InvocationTargetException { | |
281 | 53293 | final Method method = object.getClass().getMethod(methodName, new Class[0]); |
282 | 52649 | Widget widget = null; |
283 | final Object result; | |
284 | 52649 | if (object instanceof Widget) { |
285 | 52635 | widget = (Widget) object; |
286 | 52635 | result = UIThreadRunnable.syncExec(widget.getDisplay(), new Result<Object>() { |
287 | public Object run() { | |
288 | try { | |
289 | 52635 | return method.invoke(object, new Object[0]); |
290 | 0 | } catch (Exception niceTry) { |
291 | } | |
292 | 0 | return null; |
293 | } | |
294 | }); | |
295 | } else | |
296 | 14 | result = method.invoke(object, new Object[0]); |
297 | ||
298 | 52649 | return result; |
299 | } | |
300 | ||
301 | /** | |
302 | * This captures a screen shot and saves it to the given file. | |
303 | * | |
304 | * @param fileName the filename to save screenshot to. | |
305 | * @return <code>true</code> if the screenshot was created and saved, <code>false</code> otherwise. | |
306 | * @since 1.0 | |
307 | */ | |
308 | public static boolean captureScreenshot(final String fileName) { | |
309 | 1 | new ImageFormatConverter().imageTypeOf(fileName.substring(fileName.lastIndexOf('.') + 1)); |
310 | 1 | return UIThreadRunnable.syncExec(new BoolResult() { |
311 | public Boolean run() { | |
312 | 1 | return captureScreenshotInternal(fileName); |
313 | } | |
314 | }); | |
315 | } | |
316 | ||
317 | /** | |
318 | * This captures a screen shot of a widget and saves it to the given file. | |
319 | * | |
320 | * @param fileName the filename to save screenshot to. | |
321 | * @param control the control | |
322 | * @return <code>true</code> if the screenshot was created and saved, <code>false</code> otherwise. | |
323 | * @since 2.0 | |
324 | */ | |
325 | public static boolean captureScreenshot(final String fileName, final Control control) { | |
326 | 0 | new ImageFormatConverter().imageTypeOf(fileName.substring(fileName.lastIndexOf('.') + 1)); |
327 | 0 | return UIThreadRunnable.syncExec(new BoolResult() { |
328 | public Boolean run() { | |
329 | 0 | if (control instanceof Shell) |
330 | 0 | return captureScreenshotInternal(fileName, control.getBounds()); |
331 | 0 | Display display = control.getDisplay(); |
332 | 0 | Rectangle bounds = control.getBounds(); |
333 | 0 | Rectangle mappedToDisplay = display.map(control.getParent(), null, bounds); |
334 | ||
335 | 0 | return captureScreenshotInternal(fileName, mappedToDisplay); |
336 | } | |
337 | }); | |
338 | } | |
339 | ||
340 | /** | |
341 | * This captures a screen shot of an area and saves it to the given file. | |
342 | * | |
343 | * @param fileName the filename to save screenshot to. | |
344 | * @param bounds the area to capture. | |
345 | * @return <code>true</code> if the screenshot was created and saved, <code>false</code> otherwise. | |
346 | * @since 2.0 | |
347 | */ | |
348 | public static boolean captureScreenshot(final String fileName, final Rectangle bounds) { | |
349 | 0 | new ImageFormatConverter().imageTypeOf(fileName.substring(fileName.lastIndexOf('.') + 1)); |
350 | 0 | return UIThreadRunnable.syncExec(new BoolResult() { |
351 | public Boolean run() { | |
352 | 0 | return captureScreenshotInternal(fileName, bounds); |
353 | } | |
354 | }); | |
355 | } | |
356 | ||
357 | /** | |
358 | * Captures a screen shot. Used internally. | |
359 | * <p> | |
360 | * NOTE: This method is not thread safe. Clients must ensure that they do invoke this from a UI thread. | |
361 | * </p> | |
362 | * | |
363 | * @param fileName the filename to save screenshot to. | |
364 | * @return <code>true</code> if the screenshot was created and saved, <code>false</code> otherwise. | |
365 | */ | |
366 | 1 | private static boolean captureScreenshotInternal(final String fileName) { |
367 | 1 | return captureScreenshotInternal(fileName, display.getBounds()); |
368 | } | |
369 | ||
370 | /** | |
371 | * Captures a screen shot. Used internally. | |
372 | * <p> | |
373 | * NOTE: This method is not thread safe. Clients must ensure that they do invoke this from a UI thread. | |
374 | * </p> | |
375 | * | |
376 | * @param fileName the filename to save screenshot to. | |
377 | * @param bounds the area relative to the display that should be captured. | |
378 | * @return <code>true</code> if the screenshot was created and saved, <code>false</code> otherwise. | |
379 | */ | |
380 | 0 | private static boolean captureScreenshotInternal(final String fileName, Rectangle bounds) { |
381 | 1 | GC gc = new GC(display); |
382 | 1 | Image image = null; |
383 | 1 | File file = new File(fileName); |
384 | 1 | File parentDir = file.getParentFile(); |
385 | 1 | if (parentDir != null) |
386 | 1 | parentDir.mkdirs(); |
387 | try { | |
388 | 1 | log.debug(MessageFormat.format("Capturing screenshot ''{0}''", fileName)); //$NON-NLS-1$ |
389 | ||
390 | 1 | image = new Image(display, bounds.width, bounds.height); |
391 | 1 | gc.copyArea(image, bounds.x, bounds.y); |
392 | 1 | ImageLoader imageLoader = new ImageLoader(); |
393 | 1 | imageLoader.data = new ImageData[] { image.getImageData() }; |
394 | 1 | imageLoader.save(fileName, new ImageFormatConverter().imageTypeOf(fileName.substring(fileName.lastIndexOf('.') + 1))); |
395 | 1 | return true; |
396 | 0 | } catch (Exception e) { |
397 | 0 | log.warn("Could not capture screenshot: " + fileName + "'", e); //$NON-NLS-1$ //$NON-NLS-2$ |
398 | 0 | File brokenImage = file.getAbsoluteFile(); |
399 | 0 | if (brokenImage.exists()) { |
400 | try { | |
401 | 0 | log.trace(MessageFormat.format("Broken screenshot set to be deleted on exit: {0}", fileName)); //$NON-NLS-1$ |
402 | 0 | brokenImage.deleteOnExit(); |
403 | 0 | } catch (Exception ex) { |
404 | 0 | log.info(MessageFormat.format("Could not set broken screenshot to be deleted on exit: {0}", fileName), ex); //$NON-NLS-1$ |
405 | } | |
406 | } | |
407 | 0 | return false; |
408 | 0 | } finally { |
409 | 1 | gc.dispose(); |
410 | 1 | if (image != null) { |
411 | 1 | image.dispose(); |
412 | } | |
413 | 0 | } |
414 | } | |
415 | ||
416 | /** | |
417 | * Waits until a display appears. | |
418 | * | |
419 | * @throws TimeoutException if the condition does not evaluate to true after {@link SWTBotPreferences#TIMEOUT} | |
420 | * milliseconds. | |
421 | */ | |
422 | public static void waitForDisplayToAppear() { | |
423 | 0 | waitForDisplayToAppear(SWTBotPreferences.TIMEOUT); |
424 | 0 | } |
425 | ||
426 | /** | |
427 | * Waits until a display appears. | |
428 | * | |
429 | * @param timeout the timeout in ms. | |
430 | * @throws TimeoutException if the condition does not evaluate to true after {@code timeout}ms milliseconds. | |
431 | */ | |
432 | public static void waitForDisplayToAppear(long timeout) { | |
433 | 0 | waitUntil(new DefaultCondition() { |
434 | ||
435 | public String getFailureMessage() { | |
436 | 0 | return "Could not find a display"; //$NON-NLS-1$ |
437 | } | |
438 | ||
439 | public boolean test() throws Exception { | |
440 | 0 | return SWTUtils.display() != null; |
441 | } | |
442 | ||
443 | 0 | }, timeout, 500); |
444 | 0 | } |
445 | ||
446 | private static void waitUntil(ICondition condition, long timeout, long interval) throws TimeoutException { | |
447 | 0 | Assert.isTrue(interval >= 0, "interval value is negative"); //$NON-NLS-1$ |
448 | 0 | Assert.isTrue(timeout >= 0, "timeout value is negative"); //$NON-NLS-1$ |
449 | 0 | long limit = System.currentTimeMillis() + timeout; |
450 | while (true) { | |
451 | try { | |
452 | 0 | if (condition.test()) |
453 | 0 | return; |
454 | 0 | } catch (Throwable e) { |
455 | // do nothing | |
456 | } | |
457 | 0 | sleep(interval); |
458 | 0 | if (System.currentTimeMillis() > limit) |
459 | 0 | throw new TimeoutException("Timeout after: " + timeout + " ms.: " + condition.getFailureMessage()); //$NON-NLS-1$ //$NON-NLS-2$ |
460 | } | |
461 | } | |
462 | ||
463 | /** | |
464 | * Return true if the current thread is the UI thread. | |
465 | * | |
466 | * @param display the display | |
467 | * @return <code>true</code> if the current thread is the UI thread, <code>false</code> otherwise. | |
468 | */ | |
469 | public static boolean isUIThread(Display display) { | |
470 | 94262 | return display.getThread() == Thread.currentThread(); |
471 | } | |
472 | ||
473 | /** | |
474 | * Return true if the current thread is the UI thread. | |
475 | * | |
476 | * @return <code>true</code> if this instance is running in the UI thread, <code>false</code> otherwise. | |
477 | */ | |
478 | public static boolean isUIThread() { | |
479 | 0 | return isUIThread(display); |
480 | } | |
481 | } |