| /******************************************************************************* |
| * 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(); |
| } |
| } |
| } |
| |