Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractSWTBot |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$1 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$10 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$11 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$12 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$13 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$14 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$15 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$16 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$17 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$18 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$2 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$3 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$4 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$5 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$6 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$7 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$8 |
|
| 1.4929577464788732;1.493 | ||||
AbstractSWTBot$9 |
|
| 1.4929577464788732;1.493 |
1 | 1 | /******************************************************************************* |
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 | *******************************************************************************/ | |
11 | package org.eclipse.swtbot.swt.finder.widgets; | |
12 | ||
13 | import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType; | |
14 | import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.withMnemonic; | |
15 | import static org.eclipse.swtbot.swt.finder.waits.Conditions.widgetIsEnabled; | |
16 | import static org.hamcrest.Matchers.allOf; | |
17 | ||
18 | import java.util.List; | |
19 | ||
20 | import org.apache.log4j.Logger; | |
21 | import org.eclipse.jface.bindings.keys.KeyStroke; | |
22 | import org.eclipse.swt.SWT; | |
23 | import org.eclipse.swt.graphics.Color; | |
24 | import org.eclipse.swt.graphics.Rectangle; | |
25 | import org.eclipse.swt.widgets.Control; | |
26 | import org.eclipse.swt.widgets.Display; | |
27 | import org.eclipse.swt.widgets.Event; | |
28 | import org.eclipse.swt.widgets.MenuItem; | |
29 | import org.eclipse.swt.widgets.Widget; | |
30 | import org.eclipse.swtbot.swt.finder.SWTBot; | |
31 | import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; | |
32 | import org.eclipse.swtbot.swt.finder.finders.ContextMenuFinder; | |
33 | import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; | |
34 | import org.eclipse.swtbot.swt.finder.keyboard.Keyboard; | |
35 | import org.eclipse.swtbot.swt.finder.keyboard.KeyboardFactory; | |
36 | import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes; | |
37 | import org.eclipse.swtbot.swt.finder.matchers.WithId; | |
38 | import org.eclipse.swtbot.swt.finder.results.ArrayResult; | |
39 | import org.eclipse.swtbot.swt.finder.results.BoolResult; | |
40 | import org.eclipse.swtbot.swt.finder.results.IntResult; | |
41 | import org.eclipse.swtbot.swt.finder.results.ListResult; | |
42 | import org.eclipse.swtbot.swt.finder.results.Result; | |
43 | import org.eclipse.swtbot.swt.finder.results.StringResult; | |
44 | import org.eclipse.swtbot.swt.finder.results.VoidResult; | |
45 | import org.eclipse.swtbot.swt.finder.results.WidgetResult; | |
46 | import org.eclipse.swtbot.swt.finder.utils.MessageFormat; | |
47 | import org.eclipse.swtbot.swt.finder.utils.SWTBotEvents; | |
48 | import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; | |
49 | import org.eclipse.swtbot.swt.finder.utils.SWTUtils; | |
50 | import org.eclipse.swtbot.swt.finder.utils.Traverse; | |
51 | import org.eclipse.swtbot.swt.finder.utils.WidgetTextDescription; | |
52 | import org.eclipse.swtbot.swt.finder.utils.internal.Assert; | |
53 | import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; | |
54 | import org.hamcrest.Matcher; | |
55 | import org.hamcrest.SelfDescribing; | |
56 | import org.hamcrest.StringDescription; | |
57 | ||
58 | /** | |
59 | * Helper to find SWT {@link Widget}s and perform operations on them. | |
60 | * | |
61 | * @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com> | |
62 | * @author Joshua Gosse <jlgosse [at] ca [dot] ibm [dot] com> | |
63 | * @version $Id$ | |
64 | */ | |
65 | public abstract class AbstractSWTBot<T extends Widget> { | |
66 | ||
67 | /** The logger. */ | |
68 | protected final Logger log; | |
69 | /** With great power comes great responsibility, use carefully. */ | |
70 | public final T widget; | |
71 | /** With great power comes great responsibility, use carefully. */ | |
72 | public final Display display; | |
73 | /** The description of the widget. */ | |
74 | protected final SelfDescribing description; | |
75 | /** The keyboard to use to type on the widget. */ | |
76 | private Keyboard keyboard; | |
77 | ||
78 | /** | |
79 | * Constructs a new instance with the given widget. | |
80 | * | |
81 | * @param w the widget. | |
82 | * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. | |
83 | */ | |
84 | public AbstractSWTBot(T w) throws WidgetNotFoundException { | |
85 | 17 | this(w, new WidgetTextDescription(w)); |
86 | 15 | } |
87 | ||
88 | /** | |
89 | * Constructs a new instance with the given widget. | |
90 | * | |
91 | * @param w the widget. | |
92 | * @param description the description of the widget, this will be reported by {@link #toString()} | |
93 | * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. | |
94 | */ | |
95 | 1541 | public AbstractSWTBot(T w, SelfDescribing description) throws WidgetNotFoundException { |
96 | 1541 | if (w == null) |
97 | 1 | throw new WidgetNotFoundException("The widget was null."); //$NON-NLS-1$ |
98 | ||
99 | 1540 | this.widget = w; |
100 | 1540 | if (description == null) |
101 | 244 | this.description = new WidgetTextDescription(w); |
102 | else | |
103 | 1296 | this.description = description; |
104 | ||
105 | 1540 | if (w.isDisposed()) |
106 | 1 | throw new WidgetNotFoundException("The widget {" + description + "} was disposed." + SWTUtils.toString(w)); //$NON-NLS-1$ //$NON-NLS-2$ |
107 | ||
108 | 1539 | display = w.getDisplay(); |
109 | 1539 | log = Logger.getLogger(getClass()); |
110 | 1539 | } |
111 | ||
112 | /** | |
113 | * Sends a non-blocking notification of the specified type to the widget. | |
114 | * | |
115 | * @param eventType the event type. | |
116 | * @see Widget#notifyListeners(int, Event) | |
117 | */ | |
118 | protected void notify(final int eventType) { | |
119 | 5904 | notify(eventType, createEvent()); |
120 | 5904 | } |
121 | ||
122 | /** | |
123 | * Sends a non-blocking notification of the specified type to the {@link #widget}. | |
124 | * | |
125 | * @param eventType the type of event. | |
126 | * @param createEvent the event to be sent to the {@link #widget}. | |
127 | */ | |
128 | protected void notify(final int eventType, final Event createEvent) { | |
129 | 6013 | notify(eventType, createEvent, widget); |
130 | 6013 | } |
131 | ||
132 | /** | |
133 | * Sends a non-blocking notification of the specified type to the widget. | |
134 | * | |
135 | * @param eventType the type of event. | |
136 | * @param createEvent the event to be sent to the {@link #widget}. | |
137 | * @param widget the widget to send the event to. | |
138 | */ | |
139 | protected void notify(final int eventType, final Event createEvent, final Widget widget) { | |
140 | 6571 | createEvent.type = eventType; |
141 | 6571 | final Object[] result = syncExec(new ArrayResult<Object>() { |
142 | public Object[] run() { | |
143 | 6571 | return new Object[] { SWTBotEvents.toString(createEvent), AbstractSWTBot.this.toString() }; |
144 | } | |
145 | }); | |
146 | ||
147 | 6571 | log.trace(MessageFormat.format("Enquing event {0} on {1}", result)); //$NON-NLS-1$ |
148 | 6571 | asyncExec(new VoidResult() { |
149 | public void run() { | |
150 | 6571 | if ((widget == null) || widget.isDisposed()) { |
151 | 6 | log.trace(MessageFormat.format("Not notifying {0} is null or has been disposed", AbstractSWTBot.this)); //$NON-NLS-1$ |
152 | 6 | return; |
153 | } | |
154 | 6565 | if (!isEnabledInternal()) { |
155 | 0 | log.warn(MessageFormat.format("Widget is not enabled: {0}", AbstractSWTBot.this)); //$NON-NLS-1$ |
156 | 0 | return; |
157 | } | |
158 | 6565 | log.trace(MessageFormat.format("Sending event {0} to {1}", result)); //$NON-NLS-1$ |
159 | 6565 | widget.notifyListeners(eventType, createEvent); |
160 | 6565 | log.debug(MessageFormat.format("Sent event {0} to {1}", result)); //$NON-NLS-1$ |
161 | 6565 | } |
162 | }); | |
163 | ||
164 | 6571 | UIThreadRunnable.syncExec(new VoidResult() { |
165 | public void run() { | |
166 | // do nothing, just wait for sync. | |
167 | 6571 | } |
168 | }); | |
169 | ||
170 | 6571 | long playbackDelay = SWTBotPreferences.PLAYBACK_DELAY; |
171 | 6571 | if (playbackDelay > 0) |
172 | 0 | sleep(playbackDelay); |
173 | 6571 | } |
174 | ||
175 | /** | |
176 | * Sleeps for millis milliseconds. Delegate to {@link SWTUtils#sleep(long)} | |
177 | * | |
178 | * @param millis the time in milli seconds | |
179 | */ | |
180 | protected static void sleep(long millis) { | |
181 | 1 | SWTUtils.sleep(millis); |
182 | 1 | } |
183 | ||
184 | /** | |
185 | * Creates an event. | |
186 | * | |
187 | * @return an event that encapsulates {@link #widget} and {@link #display}. Subclasses may override to set other | |
188 | * event properties. | |
189 | */ | |
190 | protected Event createEvent() { | |
191 | 6485 | Event event = new Event(); |
192 | 6485 | event.time = (int) System.currentTimeMillis(); |
193 | 6485 | event.widget = widget; |
194 | 6485 | event.display = display; |
195 | 6485 | return event; |
196 | } | |
197 | ||
198 | /** | |
199 | * Create a mouse event | |
200 | * | |
201 | * @param x the x co-ordinate of the mouse event. | |
202 | * @param y the y co-ordinate of the mouse event. | |
203 | * @param button the mouse button that was clicked. | |
204 | * @param stateMask the state of the keyboard modifier keys. | |
205 | * @param count the number of times the mouse was clicked. | |
206 | * @return an event that encapsulates {@link #widget} and {@link #display} | |
207 | * @since 1.2 | |
208 | */ | |
209 | protected Event createMouseEvent(int x, int y, int button, int stateMask, int count) { | |
210 | 99 | Event event = new Event(); |
211 | 99 | event.time = (int) System.currentTimeMillis(); |
212 | 99 | event.widget = widget; |
213 | 99 | event.display = display; |
214 | 99 | event.x = x; |
215 | 99 | event.y = y; |
216 | 99 | event.button = button; |
217 | 99 | event.stateMask = stateMask; |
218 | 99 | event.count = count; |
219 | 99 | return event; |
220 | } | |
221 | ||
222 | /** | |
223 | * Create a selection event with a particular state mask | |
224 | * | |
225 | * @param stateMask the state of the keyboard modifier keys. | |
226 | */ | |
227 | protected Event createSelectionEvent(int stateMask) { | |
228 | 7 | Event event = createEvent(); |
229 | 7 | event.stateMask = stateMask; |
230 | 7 | return event; |
231 | } | |
232 | ||
233 | /** | |
234 | * Create a key event | |
235 | * | |
236 | * @param keyCode the key code of the key pressed | |
237 | * @param character the character representation of the key | |
238 | * @return an event that encapsulates {@link #widget} and {@link #display} | |
239 | */ | |
240 | private Event createKeyEvent(int keyCode, char character) { | |
241 | 0 | Event event = createEvent(); |
242 | 0 | event.keyCode = keyCode; |
243 | 0 | event.character = character; |
244 | 0 | return event; |
245 | } | |
246 | ||
247 | /** | |
248 | * Click on the table at given coordinates | |
249 | * | |
250 | * @param x the x co-ordinate of the click | |
251 | * @param y the y co-ordinate of the click | |
252 | * @since 2.0 | |
253 | */ | |
254 | protected void clickXY(int x, int y) { | |
255 | 2 | log.debug(MessageFormat.format("Clicking on {0}", this)); //$NON-NLS-1$ |
256 | 2 | notify(SWT.MouseEnter); |
257 | 2 | notify(SWT.MouseMove); |
258 | 2 | notify(SWT.Activate); |
259 | 2 | notify(SWT.FocusIn); |
260 | 2 | notify(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.NONE, 1)); |
261 | 2 | notify(SWT.MouseUp, createMouseEvent(x, y, 1, SWT.BUTTON1, 1)); |
262 | 2 | notify(SWT.Selection, createSelectionEvent(SWT.BUTTON1)); |
263 | 2 | notify(SWT.MouseHover); |
264 | 2 | notify(SWT.MouseMove); |
265 | 2 | notify(SWT.MouseExit); |
266 | 2 | notify(SWT.Deactivate); |
267 | 2 | notify(SWT.FocusOut); |
268 | 2 | log.debug(MessageFormat.format("Clicked on {0}", this)); //$NON-NLS-1$ |
269 | 2 | } |
270 | ||
271 | /** | |
272 | * Right click on the widget at given coordinates | |
273 | * | |
274 | * @param x the x co-ordinate of the click | |
275 | * @param y the y co-ordinate of the click | |
276 | * @since 2.0 | |
277 | */ | |
278 | private void rightClickXY(int x, int y) { | |
279 | 0 | log.debug(MessageFormat.format("Right clicking on {0}", this)); //$NON-NLS-1$ |
280 | 0 | notify(SWT.MouseEnter); |
281 | 0 | notify(SWT.MouseMove); |
282 | 0 | notify(SWT.Activate); |
283 | 0 | notify(SWT.FocusIn); |
284 | 0 | notify(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.BUTTON3, 1)); |
285 | 0 | notify(SWT.MouseUp); |
286 | 0 | notify(SWT.Selection, createSelectionEvent(SWT.BUTTON3)); |
287 | 0 | notify(SWT.MouseHover); |
288 | 0 | notify(SWT.MouseMove); |
289 | 0 | notify(SWT.MouseExit); |
290 | 0 | notify(SWT.Deactivate); |
291 | 0 | notify(SWT.FocusOut); |
292 | 0 | log.debug(MessageFormat.format("Right clicked on {0}", this)); //$NON-NLS-1$ |
293 | 0 | } |
294 | ||
295 | /** | |
296 | * Double-click on the table at given coordinates | |
297 | * | |
298 | * @param x the x co-ordinate of the click | |
299 | * @param y the y co-ordinate of the click | |
300 | * @since 2.0 | |
301 | */ | |
302 | protected void doubleClickXY(int x, int y) { | |
303 | 1 | log.debug(MessageFormat.format("Double-clicking on {0}", widget)); //$NON-NLS-1$ |
304 | 1 | notify(SWT.MouseEnter); |
305 | 1 | notify(SWT.MouseMove); |
306 | 1 | notify(SWT.Activate); |
307 | 1 | notify(SWT.FocusIn); |
308 | 1 | notify(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.NONE, 1)); |
309 | 1 | notify(SWT.MouseUp, createMouseEvent(x, y, 1, SWT.BUTTON1, 1)); |
310 | 1 | notify(SWT.Selection, createSelectionEvent(SWT.BUTTON1)); |
311 | 1 | notify(SWT.MouseDoubleClick, createMouseEvent(x, y, 1, SWT.BUTTON1, 2)); |
312 | 1 | notify(SWT.MouseHover); |
313 | 1 | notify(SWT.MouseMove); |
314 | 1 | notify(SWT.MouseExit); |
315 | 1 | notify(SWT.Deactivate); |
316 | 1 | notify(SWT.FocusOut); |
317 | 1 | log.debug(MessageFormat.format("Double-clicked on {0}", widget)); //$NON-NLS-1$ |
318 | 1 | } |
319 | ||
320 | @Override | |
321 | public String toString() { | |
322 | 6586 | return StringDescription.toString(description); |
323 | } | |
324 | ||
325 | // /** | |
326 | // * Finds a menu matching the current {@link Matcher}. | |
327 | // * | |
328 | // * @param matcher the matcher used to find menus. | |
329 | // * @return all menus that match the matcher. | |
330 | // */ | |
331 | // protected List findMenus(Matcher<?> matcher) { | |
332 | // return finder.findMenus(matcher); | |
333 | // } | |
334 | ||
335 | // /** | |
336 | // * Finds the menu on the main menu bar matching the given information. | |
337 | // * | |
338 | // * @param menuName the name of the menu. | |
339 | // * @param matcher the matcher used to find the menu. | |
340 | // * @return the first menuItem that matches the matcher | |
341 | // * @throws WidgetNotFoundException if the widget is not found. | |
342 | // */ | |
343 | // protected Widget findMenu(Matcher<?> matcher, String menuName) throws WidgetNotFoundException { | |
344 | // return findMenu(getMenuMatcher(menuName), 0); | |
345 | // } | |
346 | ||
347 | // /** | |
348 | // * Gets the menu matcher for the given name. | |
349 | // * | |
350 | // * @param menuName the name of the menuitem that the matcher must match. | |
351 | // * @return {@link WidgetMatcherFactory#menuMatcher(String)} | |
352 | // */ | |
353 | // protected Matcher getMenuMatcher(String menuName) { | |
354 | // return WidgetMatcherFactory.menuMatcher(menuName); | |
355 | // } | |
356 | ||
357 | // /** | |
358 | // * Finds the menu on the main menu bar matching the given information. | |
359 | // * | |
360 | // * @param matcher the matcher used to find the menu. | |
361 | // * @param index the index in the list of the menu items that match the matcher. | |
362 | // * @return the index(th) menuItem that matches the matcher | |
363 | // * @throws WidgetNotFoundException if the widget is not found. | |
364 | // */ | |
365 | // protected Widget findMenu(Matcher<?> matcher, int index) throws WidgetNotFoundException { | |
366 | // List findMenus = findMenus(matcher); | |
367 | // if (!findMenus.isEmpty()) | |
368 | // return (MenuItem) findMenus.get(index); | |
369 | // throw new WidgetNotFoundException("Could not find menu using matcher " + matcher); | |
370 | // } | |
371 | ||
372 | /** | |
373 | * Gets the text of this object's widget. | |
374 | * | |
375 | * @return the text on the widget. | |
376 | */ | |
377 | public String getText() { | |
378 | 101 | return SWTUtils.getText(widget); |
379 | } | |
380 | ||
381 | /** | |
382 | * Gets the value of {@link Widget#getData(String))} for the key {@link SWTBotPreferences#DEFAULT_KEY} of this | |
383 | * object's widget. | |
384 | * | |
385 | * @return the id that SWTBot may use to search this widget. | |
386 | * @see WithId | |
387 | */ | |
388 | public String getId() { | |
389 | 2 | return syncExec(new StringResult() { |
390 | public String run() { | |
391 | 2 | return (String) widget.getData(SWTBotPreferences.DEFAULT_KEY); |
392 | } | |
393 | }); | |
394 | } | |
395 | ||
396 | /** | |
397 | * Gets the tooltip of this object's widget. | |
398 | * | |
399 | * @return the tooltip on the widget. | |
400 | * @since 1.0 | |
401 | */ | |
402 | public String getToolTipText() { | |
403 | 1 | return syncExec(new StringResult() { |
404 | public String run() { | |
405 | 1 | return SWTUtils.getToolTipText(widget); |
406 | } | |
407 | }); | |
408 | } | |
409 | ||
410 | /** | |
411 | * Check if this widget has a style attribute. | |
412 | * | |
413 | * @param w the widget. | |
414 | * @param style the style bits, one of the constants in {@link SWT}. | |
415 | * @return <code>true</code> if style is set on the widget. | |
416 | */ | |
417 | protected boolean hasStyle(Widget w, int style) { | |
418 | 126 | return SWTUtils.hasStyle(w, style); |
419 | } | |
420 | ||
421 | /** | |
422 | * Gets the context menu matching the text. | |
423 | * | |
424 | * @param text the text on the context menu. | |
425 | * @return the menu that has the given text. | |
426 | * @throws WidgetNotFoundException if the widget is not found. | |
427 | */ | |
428 | public SWTBotMenu contextMenu(final String text) throws WidgetNotFoundException { | |
429 | 7 | if (widget instanceof Control) { |
430 | 7 | return contextMenu((Control) widget, text); |
431 | } | |
432 | 0 | throw new WidgetNotFoundException("Could not find menu: " + text); //$NON-NLS-1$ |
433 | } | |
434 | ||
435 | /** | |
436 | * Gets the context menu on the given control, matching the text. | |
437 | * | |
438 | * @param control the control | |
439 | * @param text the text on the context menu. | |
440 | * @return the menu that has the given text. | |
441 | * @throws WidgetNotFoundException if the widget is not found. | |
442 | * @since 2.0 | |
443 | */ | |
444 | @SuppressWarnings("unchecked") | |
445 | // varargs and generics doesn't mix well! | |
446 | protected SWTBotMenu contextMenu(final Control control, final String text) { | |
447 | 9 | Matcher<MenuItem> withMnemonic = withMnemonic(text); |
448 | 9 | final Matcher<MenuItem> matcher = allOf(widgetOfType(MenuItem.class), withMnemonic); |
449 | 9 | final ContextMenuFinder menuFinder = new ContextMenuFinder(control); |
450 | ||
451 | 9 | new SWTBot().waitUntil(new DefaultCondition() { |
452 | public String getFailureMessage() { | |
453 | 0 | return "Could not find context menu with text: " + text; //$NON-NLS-1$ |
454 | } | |
455 | ||
456 | public boolean test() throws Exception { | |
457 | 9 | return !menuFinder.findMenus(matcher).isEmpty(); |
458 | } | |
459 | }); | |
460 | 9 | return new SWTBotMenu(menuFinder.findMenus(matcher).get(0), matcher); |
461 | } | |
462 | ||
463 | /** | |
464 | * Gets if the object's widget is enabled. | |
465 | * | |
466 | * @return <code>true</code> if the widget is enabled. | |
467 | * @see Control#isEnabled() | |
468 | */ | |
469 | public boolean isEnabled() { | |
470 | 870 | if (widget instanceof Control) |
471 | 870 | return syncExec(new BoolResult() { |
472 | public Boolean run() { | |
473 | 870 | return isEnabledInternal(); |
474 | } | |
475 | }); | |
476 | 0 | return false; |
477 | } | |
478 | ||
479 | /** | |
480 | * Gets if the widget is enabled. | |
481 | * <p> | |
482 | * This method is not thread safe, and must be called from the UI thread. | |
483 | * </p> | |
484 | * | |
485 | * @return <code>true</code> if the widget is enabled. | |
486 | * @since 1.0 | |
487 | */ | |
488 | protected boolean isEnabledInternal() { | |
489 | try { | |
490 | 7435 | return ((Boolean) SWTUtils.invokeMethod(widget, "isEnabled")).booleanValue(); //$NON-NLS-1$ |
491 | 553 | } catch (Exception e) { |
492 | 553 | return true; |
493 | } | |
494 | } | |
495 | ||
496 | /** | |
497 | * Invokes {@link ArrayResult#run()} on the UI thread. | |
498 | * | |
499 | * @param toExecute the object to be invoked in the UI thread. | |
500 | * @return the array returned by toExecute. | |
501 | */ | |
502 | protected <T> T[] syncExec(ArrayResult<T> toExecute) { | |
503 | 6584 | return UIThreadRunnable.syncExec(display, toExecute); |
504 | } | |
505 | ||
506 | /** | |
507 | * Invokes {@link VoidResult#run()} on the UI thread. | |
508 | * | |
509 | * @param toExecute the object to be invoked in the UI thread. | |
510 | */ | |
511 | protected void syncExec(VoidResult toExecute) { | |
512 | 121 | UIThreadRunnable.syncExec(display, toExecute); |
513 | 121 | } |
514 | ||
515 | /** | |
516 | * Invokes {@link ListResult#run()} on the UI thread. | |
517 | * | |
518 | * @param toExecute the object to be invoked in the UI thread. | |
519 | * @return the list returned by toExecute | |
520 | */ | |
521 | protected <E> List<E> syncExec(ListResult<E> toExecute) { | |
522 | 40 | return UIThreadRunnable.syncExec(display, toExecute); |
523 | } | |
524 | ||
525 | /** | |
526 | * Invokes {@link BoolResult#run()} synchronously on the UI thread. | |
527 | * | |
528 | * @param toExecute the object to be invoked in the UI thread. | |
529 | * @return the boolean returned by toExecute | |
530 | */ | |
531 | protected boolean syncExec(BoolResult toExecute) { | |
532 | 2011 | return UIThreadRunnable.syncExec(display, toExecute); |
533 | } | |
534 | ||
535 | /** | |
536 | * Invokes {@link BoolResult#run()} synchronously on the UI thread. | |
537 | * | |
538 | * @param toExecute the object to be invoked in the UI thread. | |
539 | * @return the boolean returned by toExecute | |
540 | */ | |
541 | ||
542 | protected String syncExec(StringResult toExecute) { | |
543 | 39 | return UIThreadRunnable.syncExec(display, toExecute); |
544 | } | |
545 | ||
546 | /** | |
547 | * Invokes {@link Result#run()} synchronously on the UI thread. | |
548 | * | |
549 | * @param toExecute the object to be invoked in the UI thread. | |
550 | * @return the boolean returned by toExecute | |
551 | */ | |
552 | protected <T> T syncExec(Result<T> toExecute) { | |
553 | 461 | return UIThreadRunnable.syncExec(display, toExecute); |
554 | } | |
555 | ||
556 | /** | |
557 | * Invokes {@link WidgetResult#run()} synchronously on the UI thread. | |
558 | * | |
559 | * @param toExecute the object to be invoked in the UI thread. | |
560 | * @return the Widget returned by toExecute | |
561 | */ | |
562 | protected T syncExec(WidgetResult<T> toExecute) { | |
563 | 41 | return UIThreadRunnable.syncExec(display, toExecute); |
564 | } | |
565 | ||
566 | /** | |
567 | * Invokes {@link IntResult#run()} synchronously on the UI thread. | |
568 | * | |
569 | * @param toExecute the object to be invoked in the UI thread. | |
570 | * @return the integer returned by toExecute | |
571 | */ | |
572 | ||
573 | protected int syncExec(IntResult toExecute) { | |
574 | 183 | return UIThreadRunnable.syncExec(display, toExecute); |
575 | } | |
576 | ||
577 | /** | |
578 | * Invokes {@link BoolResult#run()} asynchronously on the UI thread. | |
579 | * | |
580 | * @param toExecute the object to be invoked in the UI thread. | |
581 | */ | |
582 | protected void asyncExec(VoidResult toExecute) { | |
583 | 7371 | UIThreadRunnable.asyncExec(display, toExecute); |
584 | 7371 | } |
585 | ||
586 | /** | |
587 | * Gets the foreground color of the widget. | |
588 | * | |
589 | * @return the foreground color on the widget, or <code>null</code> if the widget is not an instance of | |
590 | * {@link Control}. | |
591 | * @since 1.0 | |
592 | */ | |
593 | public Color foregroundColor() { | |
594 | 1 | return syncExec(new Result<Color>() { |
595 | public Color run() { | |
596 | 1 | if (widget instanceof Control) |
597 | 1 | return ((Control) widget).getForeground(); |
598 | 0 | return null; |
599 | } | |
600 | }); | |
601 | } | |
602 | ||
603 | /** | |
604 | * Gets the background color of the widget. | |
605 | * | |
606 | * @return the background color on the widget, or <code>null</code> if the widget is not an instance of | |
607 | * {@link Control}. | |
608 | * @since 1.0 | |
609 | */ | |
610 | public Color backgroundColor() { | |
611 | 1 | return syncExec(new Result<Color>() { |
612 | public Color run() { | |
613 | 1 | if (widget instanceof Control) |
614 | 1 | return ((Control) widget).getBackground(); |
615 | 0 | return null; |
616 | } | |
617 | }); | |
618 | } | |
619 | ||
620 | /** | |
621 | * Check if the widget is enabled, throws if the widget is disabled. | |
622 | * | |
623 | * @since 1.3 | |
624 | */ | |
625 | protected void assertEnabled() { | |
626 | 77 | Assert.isTrue(isEnabled(), MessageFormat.format("Widget {0} is not enabled.", this)); //$NON-NLS-1$ //$NON-NLS-2$ |
627 | 77 | } |
628 | ||
629 | /** | |
630 | * Wait until the widget is enabled. | |
631 | * | |
632 | * @since 2.0 | |
633 | */ | |
634 | protected void waitForEnabled() { | |
635 | 1051 | new SWTBot().waitUntil(widgetIsEnabled(this)); |
636 | 1051 | } |
637 | ||
638 | /** | |
639 | * Checks if the widget is visible. | |
640 | * | |
641 | * @return <code>true</code> if the widget is visible, <code>false</code> otherwise. | |
642 | * @since 1.0 | |
643 | */ | |
644 | public boolean isVisible() { | |
645 | 0 | return syncExec(new BoolResult() { |
646 | public Boolean run() { | |
647 | 0 | if (widget instanceof Control) |
648 | 0 | return ((Control) widget).isVisible(); |
649 | 0 | return true; |
650 | } | |
651 | }); | |
652 | } | |
653 | ||
654 | /** | |
655 | * Sets the focus on this control. | |
656 | * | |
657 | * @since 1.2 | |
658 | */ | |
659 | public void setFocus() { | |
660 | 47 | waitForEnabled(); |
661 | 47 | log.debug(MessageFormat.format("Attempting to set focus on {0}", this)); |
662 | 47 | syncExec(new VoidResult() { |
663 | public void run() { | |
664 | 47 | if (widget instanceof Control) { |
665 | 47 | Control control = (Control) widget; |
666 | 47 | if (hasFocus(control)) |
667 | 23 | return; |
668 | 24 | control.getShell().forceActive(); |
669 | 24 | control.getShell().forceFocus(); |
670 | 24 | control.forceFocus(); |
671 | } | |
672 | 24 | } |
673 | ||
674 | private boolean hasFocus(Control control) { | |
675 | 47 | return control.isFocusControl(); |
676 | } | |
677 | }); | |
678 | 47 | } |
679 | ||
680 | /** | |
681 | * @param traverse the kind of traversal to perform. | |
682 | * @return <code>true</code> if the traversal succeeded. | |
683 | * @see Control#traverse(int) | |
684 | */ | |
685 | public boolean traverse(final Traverse traverse) { | |
686 | 1 | waitForEnabled(); |
687 | 1 | setFocus(); |
688 | ||
689 | 1 | if (!(widget instanceof Control)) |
690 | 0 | throw new UnsupportedOperationException("Can only traverse widgets of type Control. You're traversing a widget of type: " //$NON-NLS-1$ |
691 | 0 | + widget.getClass().getName()); |
692 | ||
693 | 1 | return syncExec(new BoolResult() { |
694 | public Boolean run() { | |
695 | 1 | return ((Control) widget).traverse(traverse.type); |
696 | } | |
697 | }); | |
698 | } | |
699 | ||
700 | /** | |
701 | * @return <code>true</code> if this widget has focus. | |
702 | * @see Display#getFocusControl() | |
703 | */ | |
704 | public boolean isActive() { | |
705 | 4 | return syncExec(new BoolResult() { |
706 | public Boolean run() { | |
707 | 4 | return display.getFocusControl() == widget; |
708 | } | |
709 | }); | |
710 | } | |
711 | ||
712 | /** | |
713 | * Clicks on this widget. | |
714 | * | |
715 | * @return itself. | |
716 | */ | |
717 | protected AbstractSWTBot<T> click() { | |
718 | 0 | throw new UnsupportedOperationException("This operation is not supported by this widget."); |
719 | } | |
720 | ||
721 | /** | |
722 | * Empty method stub, since it should be overridden by subclass#rightClick | |
723 | * | |
724 | * @return itself. | |
725 | */ | |
726 | protected AbstractSWTBot<T> rightClick() { | |
727 | 0 | throw new UnsupportedOperationException("This operation is not supported by this widget."); |
728 | } | |
729 | ||
730 | /** | |
731 | * Perform a click action at the given coordinates | |
732 | * | |
733 | * @param x the x coordinate | |
734 | * @param y the y coordinate | |
735 | * @param post Whether or not {@link Display#post} should be used | |
736 | * @return itself. | |
737 | */ | |
738 | protected AbstractSWTBot<T> click(final int x, final int y, final boolean post) { | |
739 | 1 | if (post) { |
740 | 1 | asyncExec(new VoidResult() { |
741 | public void run() { | |
742 | 1 | moveMouse(x, y); |
743 | 1 | mouseDown(x, y, 1); |
744 | 1 | mouseUp(x, y, 1); |
745 | 1 | } |
746 | }); | |
747 | 1 | sleep(500); |
748 | } else | |
749 | 0 | clickXY(x, y); |
750 | 1 | return this; |
751 | } | |
752 | ||
753 | /** | |
754 | * Perform a right-click action at the given coordinates | |
755 | * | |
756 | * @param x the x coordinate | |
757 | * @param y the y coordinate | |
758 | * @param post Whether or not {@link Display#post} should be used | |
759 | * @return itself. | |
760 | */ | |
761 | protected AbstractSWTBot<T> rightClick(final int x, final int y, final boolean post) { | |
762 | 0 | if (post) { |
763 | 0 | syncExec(new VoidResult() { |
764 | public void run() { | |
765 | 0 | moveMouse(x, y); |
766 | 0 | mouseDown(x, y, 3); |
767 | 0 | mouseUp(x, y, 3); |
768 | 0 | } |
769 | }); | |
770 | } else | |
771 | 0 | rightClickXY(x, y); |
772 | 0 | return this; |
773 | } | |
774 | ||
775 | /** | |
776 | * Post an SWT.MouseMove event | |
777 | * | |
778 | * @param x the x coordinate | |
779 | * @param y the y coordinate | |
780 | */ | |
781 | void moveMouse(final int x, final int y) { | |
782 | 1 | asyncExec(new VoidResult() { |
783 | public void run() { | |
784 | 1 | Event event = createMouseEvent(x, y, 0, 0, 0); |
785 | 1 | event.type = SWT.MouseMove; |
786 | 1 | display.post(event); |
787 | 1 | } |
788 | }); | |
789 | 1 | } |
790 | ||
791 | /** | |
792 | * Post an SWT.MouseDown event | |
793 | * | |
794 | * @param x the x coordinate | |
795 | * @param y the y coordinate | |
796 | * @param button the mouse button to be pressed | |
797 | */ | |
798 | 1 | private void mouseDown(final int x, final int y, final int button) { |
799 | 1 | asyncExec(new VoidResult() { |
800 | public void run() { | |
801 | 1 | Event event = createMouseEvent(x, y, button, 0, 0); |
802 | 1 | event.type = SWT.MouseDown; |
803 | 1 | display.post(event); |
804 | 1 | } |
805 | }); | |
806 | 1 | } |
807 | ||
808 | /** | |
809 | * Post an SWT.MouseUp event. | |
810 | * | |
811 | * @param x the x coordinate | |
812 | * @param y the y coordinate | |
813 | * @param button the mouse button to be pressed | |
814 | */ | |
815 | 1 | private void mouseUp(final int x, final int y, final int button) { |
816 | 1 | asyncExec(new VoidResult() { |
817 | public void run() { | |
818 | 1 | Event event = createMouseEvent(x, y, button, 0, 0); |
819 | 1 | event.type = SWT.MouseUp; |
820 | 1 | display.post(event); |
821 | 1 | } |
822 | }); | |
823 | 1 | } |
824 | ||
825 | /** | |
826 | * @return the absolute location of the widget relative to the display. | |
827 | */ | |
828 | protected Rectangle absoluteLocation() { | |
829 | 0 | throw new UnsupportedOperationException("This operation is not supported by this widget."); |
830 | } | |
831 | ||
832 | /** | |
833 | * @return the keyboard to use to type on this widget. | |
834 | */ | |
835 | protected Keyboard keyboard() { | |
836 | 4 | if (keyboard == null) |
837 | 4 | keyboard = KeyboardFactory.getDefaultKeyboard(widget, description); |
838 | 4 | return keyboard; |
839 | } | |
840 | ||
841 | /** | |
842 | * Presses the shortcut specified by the given keys. | |
843 | * | |
844 | * @param modificationKeys the combination of {@link SWT#ALT} | {@link SWT#CTRL} | {@link SWT#SHIFT} | | |
845 | * {@link SWT#COMMAND}. | |
846 | * @param c the character | |
847 | * @return the same instance | |
848 | * @see Keystrokes#toKeys(int, char) | |
849 | */ | |
850 | public AbstractSWTBot<T> pressShortcut(int modificationKeys, char c) { | |
851 | 0 | waitForEnabled(); |
852 | 0 | setFocus(); |
853 | 0 | keyboard().pressShortcut(modificationKeys, c); |
854 | 0 | return this; |
855 | } | |
856 | ||
857 | /** | |
858 | * Presses the shortcut specified by the given keys. | |
859 | * | |
860 | * @param modificationKeys the combination of {@link SWT#ALT} | {@link SWT#CTRL} | {@link SWT#SHIFT} | {@link SWT#COMMAND}. | |
861 | * @param keyCode the keyCode, these may be special keys like F1-F12, or navigation keys like HOME, PAGE_UP | |
862 | * @param c the character | |
863 | * @return the same instance | |
864 | * @see Keystrokes#toKeys(int, char) | |
865 | */ | |
866 | public AbstractSWTBot<T> pressShortcut(int modificationKeys, int keyCode, char c) { | |
867 | 0 | waitForEnabled(); |
868 | 0 | setFocus(); |
869 | 0 | keyboard().pressShortcut(modificationKeys, keyCode, c); |
870 | 0 | return this; |
871 | } | |
872 | ||
873 | /** | |
874 | * Presses the shortcut specified by the given keys. | |
875 | * | |
876 | * @param keys the keys to press | |
877 | * @return the same instance | |
878 | * @see Keyboard#pressShortcut(KeyStroke...) | |
879 | * @see Keystrokes | |
880 | */ | |
881 | public AbstractSWTBot<T> pressShortcut(KeyStroke... keys) { | |
882 | 0 | waitForEnabled(); |
883 | 0 | setFocus(); |
884 | 0 | keyboard().pressShortcut(keys); |
885 | 0 | return this; |
886 | } | |
887 | } |