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