/******************************************************************************* * Copyright (c) 2008, 2016 Ketan Padegaonkar and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Ketan Padegaonkar - initial API and implementation * Lorenzo Bettini - (Bug 426869) mark new methods with since annotation * Patrick Tasse - Improve SWTBot menu API and implementation (Bug 479091) * Aparna Argade(Cadence Design Systems, Inc.) - Bug 489179 *******************************************************************************/ package org.eclipse.swtbot.swt.finder.widgets; import static org.eclipse.swtbot.swt.finder.waits.Conditions.widgetIsEnabled; import java.awt.AWTException; import java.awt.Robot; import java.awt.event.InputEvent; import java.util.List; import org.apache.log4j.Logger; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.util.Geometry; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Widget; import org.eclipse.swtbot.swt.finder.SWTBot; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.finders.ContextMenuHelper; import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; import org.eclipse.swtbot.swt.finder.keyboard.Keyboard; import org.eclipse.swtbot.swt.finder.keyboard.KeyboardFactory; import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes; import org.eclipse.swtbot.swt.finder.matchers.WithId; import org.eclipse.swtbot.swt.finder.results.ArrayResult; import org.eclipse.swtbot.swt.finder.results.BoolResult; import org.eclipse.swtbot.swt.finder.results.IntResult; import org.eclipse.swtbot.swt.finder.results.ListResult; import org.eclipse.swtbot.swt.finder.results.Result; import org.eclipse.swtbot.swt.finder.results.StringResult; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.eclipse.swtbot.swt.finder.results.WidgetResult; import org.eclipse.swtbot.swt.finder.utils.MessageFormat; import org.eclipse.swtbot.swt.finder.utils.SWTBotEvents; import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; import org.eclipse.swtbot.swt.finder.utils.SWTUtils; import org.eclipse.swtbot.swt.finder.utils.Traverse; import org.eclipse.swtbot.swt.finder.utils.WidgetTextDescription; import org.eclipse.swtbot.swt.finder.utils.internal.Assert; import org.eclipse.swtbot.swt.finder.waits.Conditions; import org.eclipse.swtbot.swt.finder.waits.WaitForObjectCondition; import org.hamcrest.SelfDescribing; import org.hamcrest.StringDescription; /** * Helper to find SWT {@link Widget}s and perform operations on them. * * @author Ketan Padegaonkar <KetanPadegaonkar [at] gmail [dot] com> * @author Joshua Gosse <jlgosse [at] ca [dot] ibm [dot] com> * @author Lorenzo Bettini - (Bug 426869) mark new methods with since annotation * @version $Id$ */ public abstract class AbstractSWTBot<T extends Widget> { /** The logger. */ protected final Logger log; /** With great power comes great responsibility, use carefully. */ public final T widget; /** With great power comes great responsibility, use carefully. */ public final Display display; /** The description of the widget. */ protected final SelfDescribing description; /** The keyboard to use to type on the widget. */ private Keyboard keyboard; /** * Constructs a new instance with the given widget. * * @param w the widget. * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. */ public AbstractSWTBot(T w) throws WidgetNotFoundException { this(w, new WidgetTextDescription(w)); } /** * Constructs a new instance with the given widget. * * @param w the widget. * @param description the description of the widget, this will be reported by {@link #toString()} * @throws WidgetNotFoundException if the widget is <code>null</code> or widget has been disposed. */ public AbstractSWTBot(T w, SelfDescribing description) throws WidgetNotFoundException { if (w == null) throw new WidgetNotFoundException("The widget was null."); //$NON-NLS-1$ this.widget = w; if (description == null) this.description = new WidgetTextDescription(w); else this.description = description; if (w.isDisposed()) throw new WidgetNotFoundException("The widget {" + description + "} was disposed." + SWTUtils.toString(w)); //$NON-NLS-1$ //$NON-NLS-2$ display = w.getDisplay(); log = Logger.getLogger(getClass()); } /** * Sends a non-blocking notification of the specified type to the widget. * * @param eventType the event type. * @see Widget#notifyListeners(int, Event) */ protected void notify(final int eventType) { notify(eventType, createEvent()); } /** * Sends a non-blocking notification of the specified type to the {@link #widget}. * * @param eventType the type of event. * @param createEvent the event to be sent to the {@link #widget}. */ protected void notify(final int eventType, final Event createEvent) { notify(eventType, createEvent, widget); } /** * Sends a non-blocking notification of the specified type to the widget. * * @param eventType the type of event. * @param createEvent the event to be sent to the {@link #widget}. * @param widget the widget to send the event to. */ protected void notify(final int eventType, final Event createEvent, final Widget widget) { createEvent.type = eventType; final Object[] result = syncExec(new ArrayResult<Object>() { public Object[] run() { return new Object[] { SWTBotEvents.toString(createEvent), AbstractSWTBot.this.toString() }; } }); log.trace(MessageFormat.format("Enquing event {0} on {1}", result)); //$NON-NLS-1$ asyncExec(new VoidResult() { public void run() { if ((widget == null) || widget.isDisposed()) { log.trace(MessageFormat.format("Not notifying {0} is null or has been disposed", AbstractSWTBot.this)); //$NON-NLS-1$ return; } if (!isEnabledInternal()) { log.warn(MessageFormat.format("Widget is not enabled: {0}", AbstractSWTBot.this)); //$NON-NLS-1$ return; } log.trace(MessageFormat.format("Sending event {0} to {1}", result)); //$NON-NLS-1$ widget.notifyListeners(eventType, createEvent); log.debug(MessageFormat.format("Sent event {0} to {1}", result)); //$NON-NLS-1$ } }); UIThreadRunnable.syncExec(new VoidResult() { public void run() { // do nothing, just wait for sync. } }); long playbackDelay = SWTBotPreferences.PLAYBACK_DELAY; if (playbackDelay > 0) sleep(playbackDelay); } /** * Sleeps for millis milliseconds. Delegate to {@link SWTUtils#sleep(long)} * * @param millis the time in milli seconds */ protected static void sleep(long millis) { SWTUtils.sleep(millis); } /** * Creates an event. * * @return an event that encapsulates {@link #widget} and {@link #display}. Subclasses may override to set other * event properties. */ protected Event createEvent() { Event event = new Event(); event.time = (int) System.currentTimeMillis(); event.widget = widget; event.display = display; return event; } /** * Create a mouse event * * @param x the x co-ordinate of the mouse event. * @param y the y co-ordinate of the mouse event. * @param button the mouse button that was clicked. * @param stateMask the state of the keyboard modifier keys. * @param count the number of times the mouse was clicked. * @return an event that encapsulates {@link #widget} and {@link #display} * @since 1.2 */ protected Event createMouseEvent(int x, int y, int button, int stateMask, int count) { Event event = new Event(); event.time = (int) System.currentTimeMillis(); event.widget = widget; event.display = display; event.x = x; event.y = y; event.button = button; event.stateMask = stateMask; event.count = count; return event; } /** * Create a selection event with a particular state mask * * @param stateMask the state of the keyboard modifier keys. */ protected Event createSelectionEvent(int stateMask) { Event event = createEvent(); event.stateMask = stateMask; return event; } /** * Click on the table at given coordinates * * @param x the x co-ordinate of the click * @param y the y co-ordinate of the click * @since 2.0 */ protected void clickXY(int x, int y) { log.debug(MessageFormat.format("Clicking on {0}", this)); //$NON-NLS-1$ notify(SWT.MouseEnter); notify(SWT.MouseMove); notify(SWT.Activate); notify(SWT.FocusIn); notify(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.NONE, 1)); notify(SWT.MouseUp, createMouseEvent(x, y, 1, SWT.BUTTON1, 1)); notify(SWT.Selection, createSelectionEvent(SWT.BUTTON1)); notify(SWT.MouseHover); notify(SWT.MouseMove); notify(SWT.MouseExit); notify(SWT.Deactivate); notify(SWT.FocusOut); log.debug(MessageFormat.format("Clicked on {0}", this)); //$NON-NLS-1$ } /** * Right click on the widget at given coordinates * * @param x the x co-ordinate of the click * @param y the y co-ordinate of the click * @since 2.0 */ private void rightClickXY(int x, int y) { log.debug(MessageFormat.format("Right clicking on {0}", this)); //$NON-NLS-1$ notify(SWT.MouseEnter); notify(SWT.MouseMove); notify(SWT.Activate); notify(SWT.FocusIn); notify(SWT.MouseDown, createMouseEvent(x, y, 3, SWT.NONE, 1)); notify(SWT.MouseUp, createMouseEvent(x, y, 3, SWT.BUTTON3, 1)); notify(SWT.Selection, createSelectionEvent(SWT.BUTTON3)); notify(SWT.MouseHover); notify(SWT.MouseMove); notify(SWT.MouseExit); notify(SWT.Deactivate); notify(SWT.FocusOut); log.debug(MessageFormat.format("Right clicked on {0}", this)); //$NON-NLS-1$ } /** * Double-click on the table at given coordinates * * @param x the x co-ordinate of the click * @param y the y co-ordinate of the click * @since 2.0 */ protected void doubleClickXY(int x, int y) { log.debug(MessageFormat.format("Double-clicking on {0}", widget)); //$NON-NLS-1$ notify(SWT.MouseEnter); notify(SWT.MouseMove); notify(SWT.Activate); notify(SWT.FocusIn); notify(SWT.MouseDown, createMouseEvent(x, y, 1, SWT.NONE, 1)); notify(SWT.MouseUp, createMouseEvent(x, y, 1, SWT.BUTTON1, 1)); notify(SWT.Selection, createSelectionEvent(SWT.BUTTON1)); notify(SWT.MouseDoubleClick, createMouseEvent(x, y, 1, SWT.BUTTON1, 2)); notify(SWT.MouseHover); notify(SWT.MouseMove); notify(SWT.MouseExit); notify(SWT.Deactivate); notify(SWT.FocusOut); log.debug(MessageFormat.format("Double-clicked on {0}", widget)); //$NON-NLS-1$ } @Override public String toString() { return StringDescription.toString(description); } // /** // * Finds a menu matching the current {@link Matcher}. // * // * @param matcher the matcher used to find menus. // * @return all menus that match the matcher. // */ // protected List findMenus(Matcher<?> matcher) { // return finder.findMenus(matcher); // } // /** // * Finds the menu on the main menu bar matching the given information. // * // * @param menuName the name of the menu. // * @param matcher the matcher used to find the menu. // * @return the first menuItem that matches the matcher // * @throws WidgetNotFoundException if the widget is not found. // */ // protected Widget findMenu(Matcher<?> matcher, String menuName) throws WidgetNotFoundException { // return findMenu(getMenuMatcher(menuName), 0); // } // /** // * Gets the menu matcher for the given name. // * // * @param menuName the name of the menuitem that the matcher must match. // * @return {@link WidgetMatcherFactory#menuMatcher(String)} // */ // protected Matcher getMenuMatcher(String menuName) { // return WidgetMatcherFactory.menuMatcher(menuName); // } // /** // * Finds the menu on the main menu bar matching the given information. // * // * @param matcher the matcher used to find the menu. // * @param index the index in the list of the menu items that match the matcher. // * @return the index(th) menuItem that matches the matcher // * @throws WidgetNotFoundException if the widget is not found. // */ // protected Widget findMenu(Matcher<?> matcher, int index) throws WidgetNotFoundException { // List findMenus = findMenus(matcher); // if (!findMenus.isEmpty()) // return (MenuItem) findMenus.get(index); // throw new WidgetNotFoundException("Could not find menu using matcher " + matcher); // } /** * Gets the text of this object's widget. * * @return the text on the widget. */ public String getText() { return SWTUtils.getText(widget); } /** * Gets the value of {@link Widget#getData(String))} for the key {@link SWTBotPreferences#DEFAULT_KEY} of this * object's widget. * * @return the id that SWTBot may use to search this widget. * @see WithId */ public String getId() { return syncExec(new StringResult() { public String run() { return (String) widget.getData(SWTBotPreferences.DEFAULT_KEY); } }); } /** * Gets the tooltip of this object's widget. * * @return the tooltip on the widget. * @since 1.0 */ public String getToolTipText() { return syncExec(new StringResult() { public String run() { return SWTUtils.getToolTipText(widget); } }); } /** * Check if this widget has a style attribute. * * @param w the widget. * @param style the style bits, one of the constants in {@link SWT}. * @return <code>true</code> if style is set on the widget. */ protected boolean hasStyle(Widget w, int style) { return SWTUtils.hasStyle(w, style); } /** * Gets the context menu of this widget. * <p> * The context menu is invoked at the center of this widget. * * @return the context menu. * @throws WidgetNotFoundException if the widget is not found. * @since 2.4 */ public SWTBotRootMenu contextMenu() throws WidgetNotFoundException { if (widget instanceof Control) { return contextMenu((Control) widget); } throw new WidgetNotFoundException("Could not find context menu for widget: " + widget); //$NON-NLS-1$ } /** * Gets the context menu of the given control. * <p> * The context menu is invoked at the center of this widget. * * @param control the control. * @return the context menu. * @throws WidgetNotFoundException if the widget is not found. * @since 2.4 */ protected SWTBotRootMenu contextMenu(final Control control) throws WidgetNotFoundException { ContextMenuHelper.notifyMenuDetect(control, widget); WaitForObjectCondition<Menu> waitForMenu = Conditions.waitForPopupMenu(control); new SWTBot().waitUntilWidgetAppears(waitForMenu); return new SWTBotRootMenu(waitForMenu.get(0)); } /** * Gets the context menu item matching the given text. It will attempt to * find the menu item recursively in each of the sub-menus that are found. * <p> * This is equivalent to calling contextMenu().menu(text, true, 0); * * @param text the text on the context menu item. * @return the context menu item that has the given text. * @throws WidgetNotFoundException if the widget is not found. */ public SWTBotMenu contextMenu(final String text) throws WidgetNotFoundException { return contextMenu().menu(text, true, 0); } /** * Gets the context menu item matching the given text on the given control. * It will attempt to find the menu item recursively in each of the * sub-menus that are found. * <p> * This is equivalent to calling contextMenu(control).menu(text, true, 0); * * @param control the control. * @param text the text on the context menu item. * @return the context menu item that has the given text. * @throws WidgetNotFoundException if the widget is not found. * @since 2.0 */ protected SWTBotMenu contextMenu(final Control control, final String text) { return contextMenu(control).menu(text, true, 0); } /** * Gets if the object's widget is enabled. * * @return <code>true</code> if the widget is enabled. * @see Control#isEnabled() */ public boolean isEnabled() { if (widget instanceof Control) return syncExec(new BoolResult() { public Boolean run() { return isEnabledInternal(); } }); return false; } /** * Gets if the widget is enabled. * <p> * This method is not thread safe, and must be called from the UI thread. * </p> * * @return <code>true</code> if the widget is enabled. * @since 1.0 */ protected boolean isEnabledInternal() { try { return ((Boolean) SWTUtils.invokeMethod(widget, "isEnabled")).booleanValue(); //$NON-NLS-1$ } catch (Exception e) { return true; } } /** * Invokes {@link ArrayResult#run()} on the UI thread. * * @param toExecute the object to be invoked in the UI thread. * @return the array returned by toExecute. */ protected <E> E[] syncExec(ArrayResult<E> toExecute) { return UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link VoidResult#run()} on the UI thread. * * @param toExecute the object to be invoked in the UI thread. */ protected void syncExec(VoidResult toExecute) { UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link ListResult#run()} on the UI thread. * * @param toExecute the object to be invoked in the UI thread. * @return the list returned by toExecute */ protected <E> List<E> syncExec(ListResult<E> toExecute) { return UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link BoolResult#run()} synchronously on the UI thread. * * @param toExecute the object to be invoked in the UI thread. * @return the boolean returned by toExecute */ protected boolean syncExec(BoolResult toExecute) { return UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link BoolResult#run()} synchronously on the UI thread. * * @param toExecute the object to be invoked in the UI thread. * @return the boolean returned by toExecute */ protected String syncExec(StringResult toExecute) { return UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link Result#run()} synchronously on the UI thread. * * @param toExecute the object to be invoked in the UI thread. * @return the boolean returned by toExecute */ protected <E> E syncExec(Result<E> toExecute) { return UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link WidgetResult#run()} synchronously on the UI thread. * * @param toExecute the object to be invoked in the UI thread. * @return the Widget returned by toExecute */ protected T syncExec(WidgetResult<T> toExecute) { return UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link IntResult#run()} synchronously on the UI thread. * * @param toExecute the object to be invoked in the UI thread. * @return the integer returned by toExecute */ protected int syncExec(IntResult toExecute) { return UIThreadRunnable.syncExec(display, toExecute); } /** * Invokes {@link BoolResult#run()} asynchronously on the UI thread. * * @param toExecute the object to be invoked in the UI thread. */ protected void asyncExec(VoidResult toExecute) { UIThreadRunnable.asyncExec(display, toExecute); } /** * Gets the foreground color of the widget. * * @return the foreground color on the widget, or <code>null</code> if the widget is not an instance of * {@link Control}. * @since 1.0 */ public Color foregroundColor() { return syncExec(new Result<Color>() { public Color run() { if (widget instanceof Control) return ((Control) widget).getForeground(); return null; } }); } /** * Gets the background color of the widget. * * @return the background color on the widget, or <code>null</code> if the widget is not an instance of * {@link Control}. * @since 1.0 */ public Color backgroundColor() { return syncExec(new Result<Color>() { public Color run() { if (widget instanceof Control) return ((Control) widget).getBackground(); return null; } }); } /** * Check if the widget is enabled, throws if the widget is disabled. * * @since 1.3 */ protected void assertEnabled() { Assert.isTrue(isEnabled(), MessageFormat.format("Widget {0} is not enabled.", this)); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Wait until the widget is enabled. * * @since 2.0 */ protected void waitForEnabled() { new SWTBot().waitUntil(widgetIsEnabled(this)); } /** * Checks if the widget is visible. * * @return <code>true</code> if the widget is visible, <code>false</code> otherwise. * @since 1.0 */ public boolean isVisible() { return syncExec(new BoolResult() { public Boolean run() { if (widget instanceof Control) return ((Control) widget).isVisible(); return true; } }); } /** * Sets the focus on this control. * * @since 1.2 */ public void setFocus() { waitForEnabled(); log.debug(MessageFormat.format("Attempting to set focus on {0}", this)); syncExec(new VoidResult() { public void run() { if (widget instanceof Control) { Control control = (Control) widget; if (hasFocus(control)) return; control.getShell().forceActive(); control.getShell().forceFocus(); control.forceFocus(); } } private boolean hasFocus(Control control) { return control.isFocusControl(); } }); } /** * @param traverse the kind of traversal to perform. * @return <code>true</code> if the traversal succeeded. * @see Control#traverse(int) */ public boolean traverse(final Traverse traverse) { waitForEnabled(); setFocus(); if (!(widget instanceof Control)) throw new UnsupportedOperationException("Can only traverse widgets of type Control. You're traversing a widget of type: " //$NON-NLS-1$ + widget.getClass().getName()); return syncExec(new BoolResult() { public Boolean run() { return ((Control) widget).traverse(traverse.type); } }); } /** * @return <code>true</code> if this widget has focus. * @see Display#getFocusControl() */ public boolean isActive() { return syncExec(new BoolResult() { public Boolean run() { return display.getFocusControl() == widget; } }); } /** * Clicks on this widget. * * @return itself. */ protected AbstractSWTBot<T> click() { throw new UnsupportedOperationException("This operation is not supported by this widget."); } /** * Empty method stub, since it should be overridden by subclass#rightClick * * @return itself. */ protected AbstractSWTBot<T> rightClick() { throw new UnsupportedOperationException("This operation is not supported by this widget."); } /** * Perform a click action at the given coordinates * * @param x the x coordinate * @param y the y coordinate * @param post Whether or not {@link Display#post} should be used * @return itself. */ protected AbstractSWTBot<T> click(final int x, final int y, final boolean post) { if (post) { asyncExec(new VoidResult() { public void run() { moveMouse(x, y); mouseDown(x, y, 1); mouseUp(x, y, 1); } }); sleep(500); } else clickXY(x, y); return this; } /** * Perform a right-click action at the given coordinates * * @param x the x coordinate * @param y the y coordinate * @param post Whether or not {@link Display#post} should be used * @return itself. */ protected AbstractSWTBot<T> rightClick(final int x, final int y, final boolean post) { if (post) { syncExec(new VoidResult() { public void run() { moveMouse(x, y); mouseDown(x, y, 3); mouseUp(x, y, 3); } }); } else rightClickXY(x, y); return this; } /** * Post an SWT.MouseMove event * * @param x the x coordinate * @param y the y coordinate */ void moveMouse(final int x, final int y) { asyncExec(new VoidResult() { public void run() { Event event = createMouseEvent(x, y, 0, 0, 0); event.type = SWT.MouseMove; display.post(event); } }); } /** * Post an SWT.MouseDown event * * @param x the x coordinate * @param y the y coordinate * @param button the mouse button to be pressed */ private void mouseDown(final int x, final int y, final int button) { asyncExec(new VoidResult() { public void run() { Event event = createMouseEvent(x, y, button, 0, 0); event.type = SWT.MouseDown; display.post(event); } }); } /** * Post an SWT.MouseUp event. * * @param x the x coordinate * @param y the y coordinate * @param button the mouse button to be pressed */ private void mouseUp(final int x, final int y, final int button) { asyncExec(new VoidResult() { public void run() { Event event = createMouseEvent(x, y, button, 0, 0); event.type = SWT.MouseUp; display.post(event); } }); } /** * @return the absolute location of the widget relative to the display. */ protected Rectangle absoluteLocation() { throw new UnsupportedOperationException("This operation is not supported by this widget."); } /** * @return the keyboard to use to type on this widget. */ protected Keyboard keyboard() { if (keyboard == null) keyboard = KeyboardFactory.getDefaultKeyboard(widget, description); return keyboard; } /** * Presses the shortcut specified by the given keys. * * @param modificationKeys the combination of {@link SWT#ALT} | {@link SWT#CTRL} | {@link SWT#SHIFT} | * {@link SWT#COMMAND}. * @param c the character * @return the same instance * @see Keystrokes#toKeys(int, char) */ public AbstractSWTBot<T> pressShortcut(int modificationKeys, char c) { waitForEnabled(); setFocus(); keyboard().pressShortcut(modificationKeys, c); return this; } /** * Presses the shortcut specified by the given keys. * * @param modificationKeys the combination of {@link SWT#ALT} | {@link SWT#CTRL} | {@link SWT#SHIFT} | {@link SWT#COMMAND}. * @param keyCode the keyCode, these may be special keys like F1-F12, or navigation keys like HOME, PAGE_UP * @param c the character * @return the same instance * @see Keystrokes#toKeys(int, char) */ public AbstractSWTBot<T> pressShortcut(int modificationKeys, int keyCode, char c) { waitForEnabled(); setFocus(); keyboard().pressShortcut(modificationKeys, keyCode, c); return this; } /** * Presses the shortcut specified by the given keys. * * @param keys the keys to press * @return the same instance * @see Keyboard#pressShortcut(KeyStroke...) * @see Keystrokes */ public AbstractSWTBot<T> pressShortcut(KeyStroke... keys) { waitForEnabled(); setFocus(); keyboard().pressShortcut(keys); return this; } /** * @since 2.2 */ public void dragAndDrop(final AbstractSWTBot<? extends Widget> target) { final Rectangle sourceRect = absoluteLocation(); final Rectangle destRect = target.absoluteLocation(); dragAndDropPointToPoint(Geometry.centerPoint(sourceRect), Geometry.centerPoint(destRect)); } private void dragAndDropPointToPoint(final Point source, final Point dest) { try { final Robot robot = new Robot(); syncExec(new VoidResult() { public void run() { robot.mouseMove(source.x, source.y); robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseMove((source.x + 10), source.y); } }); waitForIdle(robot); syncExec(new VoidResult() { public void run() { robot.mouseMove((dest.x + 10), dest.y); robot.mouseMove(dest.x, dest.y); } }); waitForIdle(robot); syncExec(new VoidResult() { public void run() { robot.mouseRelease(InputEvent.BUTTON1_MASK); } }); waitForIdle(robot); } catch (final AWTException e) { log.error(e.getMessage(), e); throw new RuntimeException(e); } } private void waitForIdle(final Robot robot) { if (SWT.getPlatform().equals("gtk")) { robot.waitForIdle(); } } }