/*******************************************************************************
* Copyright (c) 2004, 2010 BREDEX GmbH.
* 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:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.rc.swt.driver;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Properties;
import java.util.Set;
import javax.swing.KeyStroke;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.Validate;
import org.eclipse.jubula.rc.common.AUTServer;
import org.eclipse.jubula.rc.common.driver.ClickOptions;
import org.eclipse.jubula.rc.common.driver.ClickOptions.ClickModifier;
import org.eclipse.jubula.rc.common.driver.IEventMatcher;
import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer;
import org.eclipse.jubula.rc.common.driver.IRobot;
import org.eclipse.jubula.rc.common.driver.IRobotEventConfirmer;
import org.eclipse.jubula.rc.common.driver.IRobotEventInterceptor;
import org.eclipse.jubula.rc.common.driver.IRobotFactory;
import org.eclipse.jubula.rc.common.driver.IRunnable;
import org.eclipse.jubula.rc.common.driver.InterceptorOptions;
import org.eclipse.jubula.rc.common.driver.MouseMovementStrategy;
import org.eclipse.jubula.rc.common.driver.RobotTiming;
import org.eclipse.jubula.rc.common.exception.OsNotSupportedException;
import org.eclipse.jubula.rc.common.exception.RobotException;
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
import org.eclipse.jubula.rc.common.tester.adapter.interfaces.IComponent;
import org.eclipse.jubula.rc.common.util.LocalScreenshotUtil;
import org.eclipse.jubula.rc.common.util.PropertyUtil;
import org.eclipse.jubula.rc.common.util.WorkaroundUtil;
import org.eclipse.jubula.rc.swt.SwtAUTServer;
import org.eclipse.jubula.rc.swt.tester.SwtApplicationTester;
import org.eclipse.jubula.rc.swt.utils.SwtKeyCodeConverter;
import org.eclipse.jubula.rc.swt.utils.SwtPointUtil;
import org.eclipse.jubula.rc.swt.utils.SwtUtils;
import org.eclipse.jubula.toolkit.enums.ValueSets;
import org.eclipse.jubula.toolkit.enums.ValueSets.InteractionMode;
import org.eclipse.jubula.tools.internal.i18n.I18n;
import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs;
import org.eclipse.jubula.tools.internal.objects.event.EventFactory;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
import org.eclipse.jubula.tools.internal.utils.EnvironmentUtils;
import org.eclipse.jubula.tools.internal.utils.TimeUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
/**
* <p>
* AWT/Swing implementation of the <code>IRobot</code> interface. It
* uses the {@link java.awt.Robot} to move the mouse and perform clicks. Any
* mouse move or click is intercepted and confirmed using the appropriate
* AWT/Swing implementations of
* {@link org.eclipse.jubula.rc.swt.driver.IRobotEventInterceptor}and
* {@link org.eclipse.jubula.rc.swt.driver.IRobotEventConfirmer}.
* </p>
*
* <p>
* The <code>click()</code> and <code>move()</code> implementations expect
* that the graphics component is of type {@link java.awt.Component}.
* </p>
*
* @author BREDEX GmbH
* @created 17.03.2005
*/
public class RobotSwtImpl implements IRobot<Rectangle> {
/** the logger */
private static AutServerLogger log = new AutServerLogger(
RobotSwtImpl.class);
/**
* Base class for key typers.
*
* @author BREDEX GmbH
* @created Nov 16, 2009
*/
private abstract static class AbstractKeyTyper {
/**
*
* @return an event that the key for this key typer has been pressed.
*/
public Event createKeyDownEvent() {
return createKeyEvent(SWT.KeyDown);
}
/**
*
* @return an event that the key for this key typer has been released.
*/
public Event createKeyUpEvent() {
return createKeyEvent(SWT.KeyUp);
}
/**
*
* @param eventType The type of event to create.
* @return an event of the given type with the key-specific information
* for this key typer.
*/
private Event createKeyEvent(int eventType) {
final Event ke = new Event();
ke.type = eventType;
ke.time = (int)System.currentTimeMillis();
assignKey(ke);
return ke;
}
/**
* Sets values of parameter(s) of the given event such that the event
* corresponds to the key represented by this key typer.
*
* @param keyEvent The event to modify.
*/
protected abstract void assignKey(Event keyEvent);
}
/**
* Provides a keycode-based key event.
*
* @author BREDEX GmbH
* @created Nov 19, 2009
*/
private static class KeyCodeTyper extends AbstractKeyTyper {
/** the keycode represented by this key typer */
private int m_keyCode;
/**
* Constructor
*
* @param keyCode The keycode for events generated by this key typer.
*/
public KeyCodeTyper(int keyCode) {
m_keyCode = keyCode;
}
/**
* {@inheritDoc}
*/
public void assignKey(Event keyEvent) {
keyEvent.keyCode = m_keyCode;
}
}
/**
* Provides a character-based key event.
*
* @author BREDEX GmbH
* @created Nov 19, 2009
*/
private static class KeyCharTyper extends AbstractKeyTyper {
/** the character represented by this key typer */
private char m_keyChar;
/**
* Constructor
*
* @param keyChar The character for events generated by this key typer.
*/
public KeyCharTyper(char keyChar) {
m_keyChar = keyChar;
}
/**
* {@inheritDoc}
*/
public void assignKey(Event keyEvent) {
keyEvent.character = m_keyChar;
}
}
/**
* <code>m_autServer</code>
*/
private SwtAUTServer m_autServer = null;
/**
* The AWT Robot instance.
*/
private SwtRobot m_robot;
/**
* The KeyboardHelper.
*/
private KeyboardHelper m_keyboardHelper = null;
/**
* The event interceptor.
*/
private IRobotEventInterceptor m_interceptor;
/** The event thread queuer. */
private IEventThreadQueuer m_queuer;
/**
* Scrolls a component to visible. The Scroller asumes that the component is
* embedded (directly or indirectly) into a <code>JScrollPane</code>(
* <code>JViewPort</code> more precisely). Hierarchies of scrollpanes are
* also supported. The default mechanismn of
* {@link JComponent#scrollRectToVisible(java.awt.Rectangle) is not used
* here because of limitations and bugs: <br>
*
* <code>JViewPort</code> overrides <code>scrollRectToVisible()</code>,
* but doesn't call <code>super.scrollRectToVisible()</code>, so that
* hierarchies of scrollpanes don't work. <br>
*
* <code>JTextField</code> interpretes <code>scrollRectToVisible()</code>
* in a different way by scrolling the containing text, not the component
* ifself. Again, <code>super.scrollRectToVisible()</code> is not called.
*/
private class Scroller {
/** The component to scroll to visible. */
private Control m_component;
/**
* @param component The component to scroll to visible.
*/
public Scroller(Control component) {
m_component = component;
}
/**
* Scrolls the component passed to the constructor to visible. Also
* scrolls the given rectangle (component-relative) to visible.
* <em>NOTE</em>: This changes the given rectangle in order to reflect
* its relative position within the component's client area.
*
* @param aRect
* The bounds of the component, or a constraint within
* a component.
*/
public void scrollRectToVisible(final Rectangle aRect) {
m_queuer.invokeAndWait("scrollToComponent", new IRunnable<Void>() { //$NON-NLS-1$
public Void run() throws StepExecutionException {
Point currentPoint = new Point(aRect.x, aRect.y);
if (m_component instanceof Scrollable) {
final Scrollable scrollable = (Scrollable)m_component;
currentPoint.x = scroll(
currentPoint.x, scrollable.getHorizontalBar());
currentPoint.y = scroll(
currentPoint.y, scrollable.getVerticalBar());
}
for (Control current = m_component;
current.getParent() != null;
current = current.getParent()) {
final Composite currentParent = current.getParent();
currentPoint.x = scroll(currentPoint.x,
currentParent.getHorizontalBar());
currentPoint.y = scroll(currentPoint.y,
currentParent.getVerticalBar());
currentPoint = current.getDisplay().map(
current, currentParent, currentPoint);
}
return null;
}
});
}
/**
*
* <b>Must be called from the GUI thread.</b>
*
* @param scrollTarget The target value to make visible by scrolling.
* @param scrollBar The scroll bar to use for performing the scrolling.
* May be <code>null</code>. If this is
* <code>null</code>, or if the component cannot be
* scrolled, then no scrolling will occur.
* @return the remaining distance to <code>scrollTarget</code>. This is
* equivalent to <code>scrollTarget -
* <amountScrolled></code>
*/
private int scroll(int scrollTarget, ScrollBar scrollBar) {
if (scrollBar != null && !scrollBar.isDisposed()) {
int oldScrollLoc = scrollBar.getSelection();
scrollBar.setSelection(scrollTarget);
Event selectionEvent = new Event();
selectionEvent.type = SWT.Selection;
selectionEvent.widget = scrollBar;
// SWT.None indicates the end of a mouse drag event
selectionEvent.detail = SWT.None;
selectionEvent.time =
(int)System.currentTimeMillis();
selectionEvent.display = scrollBar.getDisplay();
scrollBar.notifyListeners(SWT.Selection, selectionEvent);
return scrollTarget - (scrollBar.getSelection() - oldScrollLoc);
}
return scrollTarget;
}
}
/**
* Creates a new instance.
* @param factory The Robot factory instance.
* @throws RobotException If the SWT-Robot cannot be created.
*/
public RobotSwtImpl(IRobotFactory factory) throws RobotException {
try {
m_autServer = (SwtAUTServer)AUTServer.getInstance();
m_robot = new SwtRobot((m_autServer).getAutDisplay());
m_robot.setAutoWaitForIdle(false);
m_robot.setAutoDelay(0);
} catch (SWTException swte) {
log.error(swte);
m_robot = null;
throw new RobotException(swte);
} catch (SecurityException se) {
log.error(se);
m_robot = null;
throw new RobotException(se);
}
m_interceptor = factory.getRobotEventInterceptor();
m_queuer = factory.getEventThreadQueuer();
}
/**
*
* {@inheritDoc}
*/
public void setKeyboardLayout(Properties layout) {
m_keyboardHelper = new KeyboardHelper(layout);
}
/**
* Implementation of the mouse click. The mouse is moved into the graphics
* component by calling <code>moveImpl()</code> before performing the click.
* @param graphicsComponent The graphics component to click on
* @param constraints The constraints, may be <code>null</code>.
* @param clickOptions The click options
* @throws RobotException If the click delay is interupted or the event confirmation receives a timeout.
*/
private void clickImpl(Object graphicsComponent, Object constraints,
ClickOptions clickOptions) throws RobotException {
clickImpl(graphicsComponent, constraints, clickOptions, 50, false, 50,
false);
}
/**
* Implementation of the mouse click. The mouse is moved into the graphics
* component by calling <code>moveImpl()</code> before performing the click.
* @param graphicsComponent The graphics component to click on
* @param constraints The constraints, may be <code>null</code>.
* @param clickOptions The click options
* @param xPos xPos in component
* @param yPos yPos in component
* @param yAbsolute true if y-position should be absolute
* @param xAbsolute true if x-position should be absolute
* @throws RobotException If the click delay is interupted or the event confirmation receives a timeout.
*/
private void clickImpl(final Object graphicsComponent,
final Object constraints, final ClickOptions clickOptions,
final int xPos, final boolean xAbsolute, final int yPos,
final boolean yAbsolute)
throws RobotException {
Widget component = (Widget)graphicsComponent;
Rectangle constraintsRect = (Rectangle)constraints;
moveImpl(component, constraintsRect,
xPos, xAbsolute, yPos, yAbsolute, clickOptions);
clickImpl(component, clickOptions);
}
/**
* Logs information for a RobotException (usually a timeout).
*
* @param graphicsComponent The component being tested.
* @param re The <code>RobotException</code> to log.
* @param sb <code>StringBuffer</code> containing any desired initial
* content.
*/
private void logRobotException(final Object graphicsComponent,
RobotException re, final StringBuffer sb) {
// Get mouse coordinates
Point mouseCoords =
m_queuer.invokeAndWait("get mouse coordinates", //$NON-NLS-1$
new IRunnable<Point>() {
public Point run() throws StepExecutionException {
return m_autServer.getAutDisplay()
.getCursorLocation();
}
});
// Get component bounds
Rectangle compBounds = null;
if (graphicsComponent instanceof Widget) {
compBounds = m_queuer.invokeAndWait(
"getBounds", new IRunnable<Rectangle>() { //$NON-NLS-1$
public Rectangle run() throws StepExecutionException {
return SwtUtils.getWidgetBounds(
(Widget)graphicsComponent);
}
});
}
sb.append("Component: "); //$NON-NLS-1$
// Component value must be appended in the GUI thread. Otherwise
// we receive a "*Wrong Thread*" message rather than actual
// component information.
m_queuer.invokeAndWait(
"appendGraphicsComponent", new IRunnable<Void>() { //$NON-NLS-1$
public Void run() throws StepExecutionException {
sb.append(graphicsComponent);
// Return value not used
return null;
}
});
sb.append("\n"); //$NON-NLS-1$
sb.append("Bounds: "); //$NON-NLS-1$
sb.append(compBounds);
sb.append("\n"); ////$NON-NLS-1$
sb.append("Mouse position: "); //$NON-NLS-1$
sb.append(mouseCoords);
log.error(sb.toString(), re);
}
/**
* Performs a mouse move only if the mouse cursor is not currently within
* given constraints. In this case, the mouse is moved into the middle of
* the given constraint.
*
* @param constraint
* The rectangle to move to the center of, if necessary.
*/
public void preMove(final Rectangle constraint)
throws RobotException {
boolean isAlreadyInConstraints = m_queuer.invokeAndWait(
"isAlreadyInConstraints", new IRunnable<Boolean>() { //$NON-NLS-1$
public Boolean run() throws StepExecutionException {
return constraint.contains(
m_autServer.getAutDisplay().getCursorLocation());
}
});
if (!isAlreadyInConstraints) {
Point p = new Point(constraint.x + (constraint.width / 2),
constraint.y + (constraint.height / 2));
m_robot.mouseMove(p.x - 1, p.y - 1);
m_robot.mouseMove(p.x, p.y);
}
}
/**
* Checks if the mouse has to be moved on <code>p</code> or if the mouse
* pointer already resides on this location.
* Runs in the GUI-Thread
* @param p The point to move to
* @return <code>true</code> if the mouse pointer resides on a different point, otherwise <code>false</code>.
*/
private boolean isMouseMoveRequired(Point p) {
boolean result = false;
Point point = m_queuer.invokeAndWait("isMouseMoveRequired", //$NON-NLS-1$
new IRunnable<Point>() {
public Point run() throws StepExecutionException {
Display d = (m_autServer).getAutDisplay();
return d.getCursorLocation();
}
});
if (point != null) {
result = !point.equals(p);
result = true;
}
return result;
}
/**
* The mouse is moved into the center of the graphics component.
* @param graphicsComponent graphicsComponent The graphics component to move to.
*/
private void moveToCenter(final Object graphicsComponent) {
moveImpl((Widget)graphicsComponent, null,
50, false, 50, false, ClickOptions.create());
}
/**
* Implementation of the mouse move. The mouse is moved into the graphics
* component.
*
* @param graphicsComponent The component into which the mouse will be
* moved.
* @param constraints The rectangle to move to, relative to the location of
* the given component. May be <code>null</code>.
* If <code>null</code>, the mouse will be moved to the
* center of the given component.
* @param xPos xPos in component
* @param yPos yPos in component
* @param xAbsolute true if x-position should be absolute
* @param yAbsolute true if y-position should be absolute
* @param clickOptions Contains mouse movement strategy information.
* @throws StepExecutionException If the click delay is interupted or the event confirmation receives a timeout.
*/
private void moveImpl(final Widget graphicsComponent,
final Rectangle constraints, final int xPos,
final boolean xAbsolute, final int yPos, final boolean yAbsolute,
ClickOptions clickOptions) throws StepExecutionException {
if (clickOptions.isScrollToVisible()
&& graphicsComponent instanceof Control) {
ensureComponentVisible((Control)graphicsComponent, constraints);
}
Rectangle bounds = getBounds(graphicsComponent);
if (constraints != null) {
if (graphicsComponent instanceof Control) {
Point convertedLocation = convertLocation(constraints,
(Control)graphicsComponent);
bounds.x = convertedLocation.x;
bounds.y = convertedLocation.y;
} else {
bounds.x += constraints.x;
bounds.y += constraints.y;
}
bounds.height = constraints.height;
bounds.width = constraints.width;
}
Point pointToGo = SwtPointUtil.calculatePointToGo(
xPos, xAbsolute, yPos, yAbsolute, bounds);
if (isMouseMoveRequired(pointToGo)) {
if (log.isDebugEnabled()) {
log.debug("Moving mouse to: " + pointToGo); //$NON-NLS-1$
}
Point initialPoint = m_queuer.invokeAndWait("moveImpl", //$NON-NLS-1$
new IRunnable<Point>() {
public Point run() throws StepExecutionException {
Display d = m_autServer.getAutDisplay();
return d.getCursorLocation();
}
});
if (pointToGo != null && (pointToGo.x < 0 || pointToGo.y < 0)) {
throw new RobotException(
"Error occurred while attempting to move the mouse pointer.", //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.CLICKPOINT_OFFSCREEN,
new String[] {
String.valueOf(pointToGo.x),
String.valueOf(pointToGo.y)}));
}
java.awt.Point [] path =
MouseMovementStrategy.getMovementPath(
new java.awt.Point(initialPoint.x, initialPoint.y),
new java.awt.Point(pointToGo.x, pointToGo.y),
clickOptions.getStepMovement(),
clickOptions.getFirstHorizontal());
for (int i = 0; i < path.length; i++) {
m_robot.mouseMove(path[i].x, path[i].y);
}
logAndCorrectMousePosition(pointToGo);
}
}
/**
* Use SWT's mapping function, if possible, as it is more
* multi-platform than simply adding the x and y values.
* @param constraints the constraints
* @param graphicsComponent the graphics component
* @return the converted location
*/
private Point convertLocation(final Rectangle constraints,
final Control graphicsComponent) {
Point convertedLocation = m_queuer.invokeAndWait(
"toDisplay", new IRunnable<Point>() { //$NON-NLS-1$
public Point run() throws StepExecutionException {
return graphicsComponent.toDisplay(constraints.x,
constraints.y);
}
});
// adjust Y-location due to issue / bug
// http://eclip.se/353907
if (EnvironmentUtils.isMacOS()
&& graphicsComponent instanceof org.eclipse.swt.widgets.List) {
final org.eclipse.swt.widgets.List list =
(org.eclipse.swt.widgets.List)graphicsComponent;
Integer correctedYPos = m_queuer.invokeAndWait(
"correctedYPos", new IRunnable<Integer>() { //$NON-NLS-1$
public Integer run() throws StepExecutionException {
return list.getClientArea().y;
}
});
convertedLocation.y += correctedYPos.intValue();
}
return convertedLocation;
}
/**
* Checks whether the move was really successful. If not, a few workarounds
* are attempted in order to correct the situation. The workarounds are
* logged at the warn-level as they are used. If, after all workarounds
* have been applied, the mouse pointer is still not at the correct
* position, then an error is logged.
*
* @param pointToGo The point where the mouse pointer should currently be.
*/
private void logAndCorrectMousePosition(Point pointToGo) {
String runnableName = "moveImpl.getCursorPoint"; //$NON-NLS-1$
Point curPoint = m_queuer.invokeAndWait(runnableName,
new IRunnable<Point>() {
public Point run() throws StepExecutionException {
Display d = m_autServer.getAutDisplay();
return d.getCursorLocation();
}
});
if (!curPoint.equals(pointToGo)) {
log.warn("Current and end points not equal after mouse move. Waiting 1 second to see if a delay fixes the problem."); //$NON-NLS-1$
SwtRobot.delay(1000);
m_robot.waitForIdle();
curPoint = m_queuer.invokeAndWait(runnableName,
new IRunnable<Point>() {
public Point run() throws StepExecutionException {
Display d = m_autServer.getAutDisplay();
return d.getCursorLocation();
}
});
if (!curPoint.equals(pointToGo)) {
log.warn("Delay did not fix the problem. Trying to call mouse move one more time with an additional wait afterward."); //$NON-NLS-1$
m_robot.mouseMove(pointToGo.x, pointToGo.y);
SwtRobot.delay(1000);
m_robot.waitForIdle();
SwtRobot.delay(1000);
curPoint = m_queuer.invokeAndWait(runnableName,
new IRunnable<Point>() {
public Point run() throws StepExecutionException {
Display d = m_autServer.getAutDisplay();
return d.getCursorLocation();
}
});
if (!curPoint.equals(pointToGo)) {
log.error("Current and end points not equal after mouse move. The mouse pointer could not be correctly moved."); //$NON-NLS-1$
}
}
}
}
/**
* {@inheritDoc}
*/
public void click(Object graphicsComponent, Rectangle constraints)
throws RobotException {
click(graphicsComponent, constraints, ClickOptions.create());
}
/**
* {@inheritDoc}
* java.lang.Object, org.eclipse.jubula.rc.swt.robot.ClickOptions)
*/
public void click(Object graphicsComponent, Rectangle constraints,
ClickOptions clickOptions) throws RobotException {
clickImpl(graphicsComponent, constraints, clickOptions);
}
/**
* {@inheritDoc}
*/
public void move(final Object graphicsComponent,
final Rectangle constraints)
throws RobotException {
moveToCenter(graphicsComponent);
}
/**
* {@inheritDoc}
*/
public void type(final Object graphicsComponent, final char character)
throws RobotException {
Validate.notNull(graphicsComponent, "The graphic component must not be null"); //$NON-NLS-1$
// Workaround for issue http://eclip.se/342718
if (EnvironmentUtils.isMacOS()
&& Character.toLowerCase(character) == WorkaroundUtil.CHAR_B) {
SwtApplicationTester impClass = new SwtApplicationTester();
impClass.rcNativeInputText(String.valueOf(character));
return;
}
if (m_keyboardHelper == null) {
throw new StepExecutionException("No keyboard layout available.", //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.UNSUPPORTED_KEYBOARD_LAYOUT));
}
final KeyboardHelper.KeyStroke keyStroke =
m_keyboardHelper.getKeyStroke(character);
final Integer[] modifiers = keyStroke.getModifiers();
final char key = keyStroke.getChar();
final InterceptorOptions options = new InterceptorOptions(new long[]{
SWT.KeyUp});
// add confirmer
final IEventMatcher matcher = new DefaultSwtEventMatcher(SWT.KeyUp);
final IRobotEventConfirmer confirmer = m_interceptor.intercept(options);
final Boolean succeeded = m_queuer.invokeAndWait(
this.getClass().getName() + ".type", //$NON-NLS-1$
new IRunnable<Boolean>() {
public Boolean run() {
Boolean success = Boolean.TRUE;
try {
// press the modifier keys
for (int i = 0; i < modifiers.length; i++) {
final int mod = modifiers[i].intValue();
if (!postKeyPress(graphicsComponent, mod, '\0')) {
success = Boolean.FALSE;
break;
}
}
if (success.booleanValue()) {
if (!postKeyPress(graphicsComponent, 0, key)) {
success = Boolean.FALSE;
}
}
} finally {
if (!postKeyRelease(graphicsComponent, 0, key)) {
success = Boolean.FALSE;
}
// release the modifier keys
for (int i = 0; i < modifiers.length; i++) {
final int mod = modifiers[i].intValue();
if (!postKeyRelease(graphicsComponent, mod, '\0')) {
success = Boolean.FALSE;
}
}
}
return success;
}
});
if (!succeeded.booleanValue()) {
final String msg = "Failed to type character '" //$NON-NLS-1$
+ String.valueOf(character) + "' into component '" //$NON-NLS-1$
+ SwtUtils.toString((Widget)graphicsComponent) + "'"; //$NON-NLS-1$
if (log.isWarnEnabled()) {
log.warn(msg);
}
throw new RobotException(msg, EventFactory.createActionError(
TestErrorEvent.INPUT_FAILED));
}
// Workaround for bug http://eclip.se/342718
if (!(key == WorkaroundUtil.CHAR_9 && EnvironmentUtils.isMacOS())) {
confirmer.waitToConfirm(graphicsComponent, matcher);
} else {
TimeUtil.delay(50);
}
}
/**
* {@inheritDoc}
*/
public void type(Object graphicsComponent, String text)
throws RobotException {
if (text != null) {
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
type(graphicsComponent, ch);
}
}
}
/**
* {@inheritDoc}
*/
public void keyType(Object graphicsComponent, int keycode)
throws RobotException {
try {
InterceptorOptions options = new InterceptorOptions(new long[]{
SWT.KeyUp, SWT.KeyDown});
IRobotEventConfirmer confirmer = m_interceptor.intercept(options);
try {
m_robot.keyPress(keycode);
} finally {
m_robot.keyRelease(keycode);
}
confirmer.waitToConfirm(graphicsComponent, new KeySwtEventMatcher(
SWT.KeyUp));
} catch (IllegalArgumentException e) {
throw new RobotException(e);
}
}
/**
* {@inheritDoc}
*/
public void keyPress(Object graphicsComponent, int keycode)
throws RobotException {
// A direct "post" is necessary here (rather than using the AWT Robot)
// because we do not receive event confirmation for the KeyDown event
// on MacOS X Cocoa.
if (!post(new KeyCodeTyper(keycode).createKeyDownEvent())) {
final String msg = "Failed to post key event for keycode '" //$NON-NLS-1$
+ keycode + "'"; //$NON-NLS-1$
log.warn(msg);
throw new RobotException(
msg,
EventFactory.createActionError(
TestErrorEvent.INVALID_INPUT));
}
}
/**
* {@inheritDoc}
*/
public void keyRelease(Object graphicsComponent, int keycode)
throws RobotException {
// A direct "post" is necessary here (rather than using the AWT Robot)
// because we do not receive event confirmation for the KeyDown event
// on MacOS X Cocoa.
if (!post(new KeyCodeTyper(keycode).createKeyUpEvent())) {
final String msg = "Failed to post key event for keycode '" //$NON-NLS-1$
+ keycode + "'"; //$NON-NLS-1$
log.warn(msg);
throw new RobotException(
msg,
EventFactory.createActionError(
TestErrorEvent.INVALID_INPUT));
}
}
/**
*
* @param keyStroke The key stroke.
* @return a list of key typers capable of generating the necessary
* events to simulate the modifiers of the given key stroke.
*/
private List<AbstractKeyTyper> modifierKeyTypers(KeyStroke keyStroke) {
List<AbstractKeyTyper> l = new LinkedList<AbstractKeyTyper>();
int modifiers = keyStroke.getModifiers();
// this is jdk 1.3 - code.
// use ALT_DOWN_MASK instead etc. with jdk 1.4 !
if ((modifiers & InputEvent.ALT_MASK) != 0) {
l.add(new KeyCodeTyper(SWT.ALT));
}
if ((modifiers & InputEvent.CTRL_MASK) != 0) {
l.add(new KeyCodeTyper(SWT.CTRL));
}
if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
l.add(new KeyCodeTyper(SWT.SHIFT));
}
if ((modifiers & InputEvent.META_MASK) != 0) {
l.add(new KeyCodeTyper(SWT.COMMAND));
}
return l;
}
/**
*
* @param keyStrokeSpec String representing the key for which a
* key typer should be returned.
* @return a key typer capable of generating the necessary
* event to simulate the base key of the given key stroke
* spec.
*/
private static AbstractKeyTyper getBaseKeyTyper(String keyStrokeSpec) {
String [] specElements = keyStrokeSpec.split(" "); //$NON-NLS-1$
String baseKey = specElements[specElements.length - 1];
int code = SwtKeyCodeConverter.getKeyCode(baseKey);
if (code == -1) {
return new KeyCharTyper(
getOSSspecificSpecBaseCharacter(SwtKeyCodeConverter
.getKeyChar(baseKey).charValue()));
}
return new KeyCodeTyper(code);
}
/**
* {@inheritDoc}
*/
public void keyStroke(final String keyStrokeSpec) throws RobotException {
final KeyStroke keyStroke = getKeyStroke(keyStrokeSpec);
final List<AbstractKeyTyper> keyTyperList =
modifierKeyTypers(keyStroke);
keyTyperList.add(getBaseKeyTyper(keyStrokeSpec));
m_robot.setAutoWaitForIdle(true);
// first press all keys, then release all keys, but
// avoid to press and release any key twice (even if perhaps alt
// and meta should have the same keycode(??)
final Set<AbstractKeyTyper> alreadyDown =
new HashSet<AbstractKeyTyper>();
final ListIterator<AbstractKeyTyper> i = keyTyperList.listIterator();
final InterceptorOptions options = new InterceptorOptions(
new long[] { SWT.KeyUp });
try {
m_queuer.invokeAndWait(this.getClass().getName() + ".type", //$NON-NLS-1$
new IRunnable<Boolean>() {
public Boolean run() { // SYNCH THREAD START
boolean success = true;
while (i.hasNext()) {
AbstractKeyTyper keyTyper = i
.next();
if (log.isDebugEnabled()) {
log.debug("trying to press: " + keyTyper); //$NON-NLS-1$
}
if (!alreadyDown.contains(keyTyper)) {
if (log.isDebugEnabled()) {
log.debug("pressing: " + keyTyper); //$NON-NLS-1$
}
// post the event!
if (!post(keyTyper.createKeyDownEvent())) {
success = false;
}
alreadyDown.add(keyTyper);
}
}
if (!success) {
final String msg = "Failed to post keystroke '" //$NON-NLS-1$
+ keyStrokeSpec + "'"; //$NON-NLS-1$
if (log.isWarnEnabled()) {
log.warn(msg);
}
throw new RobotException(
msg,
EventFactory.createActionError(
TestErrorEvent.INVALID_INPUT));
}
return success;
}
});
} finally {
releaseKeys(options, alreadyDown, i);
}
}
/**
* Creates a KeyStroke of the given keyStrokeSpec
*
* @param keyStrokeSpec
* see {@link KeyStroke#getKeyStroke(String)} and
* {@link KeyStroke#getKeyStroke(Char)}
* @return a KeyStroke.
* @throws RobotException
* if no KeyStroke can be created.
*/
private KeyStroke getKeyStroke(String keyStrokeSpec) throws RobotException {
KeyStroke keyStroke;
if (keyStrokeSpec.length() == 1) {
char singeKeyStrokeSpecChar = keyStrokeSpec.charAt(0);
singeKeyStrokeSpecChar =
getOSSspecificSpecBaseCharacter(singeKeyStrokeSpecChar);
keyStroke = KeyStroke.getKeyStroke(singeKeyStrokeSpecChar);
} else {
int keyStrokeSpecSize = keyStrokeSpec.length();
char keySpec = keyStrokeSpec.charAt(keyStrokeSpecSize - 1);
// '�'.toUpperCase is "SS" we do not want that!
if ('�' != keySpec) {
keySpec = Character.toUpperCase(keySpec);
}
String modifiedKeyStrokeSpec = keyStrokeSpec.substring(0,
keyStrokeSpecSize - 1) + keySpec;
keyStroke = KeyStroke.getKeyStroke(modifiedKeyStrokeSpec);
}
if (keyStroke == null) {
final String msg = "Failed to post keystroke '" + keyStrokeSpec //$NON-NLS-1$
+ "'"; //$NON-NLS-1$
if (log.isWarnEnabled()) {
log.warn(msg);
}
throw new RobotException(msg, EventFactory.createActionError(
TestErrorEvent.INVALID_PARAM_VALUE));
}
return keyStroke;
}
/**
* @param character
* the character
* @return the "corrected" character, e.g. for Mac OS X it has to be in
* lower case
*/
private static char getOSSspecificSpecBaseCharacter(char character) {
if (EnvironmentUtils.isMacOS() && Character.isUpperCase(character)) {
return Character.toLowerCase(character);
}
return character;
}
/**
* Ensures that the passed component is visible
* @param component The component.
* @param bounds Optional bounds inside the component. If not <code>null</code>, the bounds are scrolled to visible.
* @throws RobotException If the component's screen location cannot be calculated.
*/
private void ensureComponentVisible(final Control component,
final Rectangle bounds) throws RobotException {
Rectangle rectangle = bounds;
if (rectangle == null) {
rectangle = m_queuer.invokeAndWait(
"getRelativeWidgetBounds", new IRunnable<Rectangle>() { //$NON-NLS-1$
public Rectangle run() throws StepExecutionException {
return SwtUtils.getRelativeWidgetBounds(component,
component);
}
});
}
if (log.isDebugEnabled()) {
log.debug("Scrolling rectangle to visible: " + rectangle); //$NON-NLS-1$
}
Scroller scroller = new Scroller(component);
scroller.scrollRectToVisible(rectangle);
}
/**
* {@inheritDoc}
*/
public void scrollToVisible(Object graphicsComponent, Rectangle constraints)
throws RobotException {
if (graphicsComponent instanceof Control) {
ensureComponentVisible((Control)graphicsComponent,
constraints);
}
}
/**
* {@inheritDoc}
*/
public void activateApplication(String method) throws RobotException {
try {
final Shell window = getActiveWindow();
if (window == null) {
throw new RobotException("No AUT window is available.", //$NON-NLS-1$
EventFactory.createImplClassErrorEvent());
}
WindowActivationMethod wam =
WindowActivationMethod.createWindowActivationMethod(
method, m_robot, m_queuer);
wam.activate(window);
// Verify that window was successfully activated
Shell activeWindow = m_queuer.invokeAndWait(
"getActiveWindow", new IRunnable<Shell>() { //$NON-NLS-1$
public Shell run() throws StepExecutionException {
return window.getDisplay().getActiveShell();
}
});
if (activeWindow != window) {
throw new StepExecutionException(
I18n.getString(
TestErrorEvent.WINDOW_ACTIVATION_FAILED, true),
EventFactory.createActionError(
TestErrorEvent.WINDOW_ACTIVATION_FAILED));
}
} catch (Exception exc) {
throw new RobotException(exc);
}
}
/**
* Guesses the active window. If windows exist but are not active, one of
* the existing windows will be returned.
* Returns <code>null</code> if no windows exist for the AUT.
* @return the active window
*/
public Shell getActiveWindow() {
return m_queuer.invokeAndWait(
"getActiveWindow", //$NON-NLS-1$
new IRunnable<Shell>() {
public Shell run() throws StepExecutionException {
Display autDisplay = m_autServer.getAutDisplay();
Shell activeShell = autDisplay.getActiveShell();
if (activeShell == null) {
Shell [] existingShells = autDisplay.getShells();
for (int i = 0; i < existingShells.length; i++) {
Shell shell = existingShells[i];
if (shell.isVisible()) {
activeShell = shell;
break;
}
}
if (activeShell == null && existingShells.length > 0) {
activeShell = existingShells[0];
}
}
return activeShell;
}
});
}
/**
* {@inheritDoc}
*/
public void keyToggle(Object obj, int key, boolean activated)
throws OsNotSupportedException {
if (activated && isActivated(key)
|| !activated && !isActivated(key)) {
return;
}
List<AbstractKeyTyper> keyTyperList =
new LinkedList<AbstractKeyTyper>();
keyTyperList.add(new KeyCodeTyper(key));
m_robot.setAutoWaitForIdle(true);
final Set<AbstractKeyTyper> alreadyDown =
new HashSet<AbstractKeyTyper>();
final ListIterator<AbstractKeyTyper> i = keyTyperList.listIterator();
final InterceptorOptions options = new InterceptorOptions(
new long[]{SWT.KeyUp});
try {
while (i.hasNext()) {
m_queuer.invokeAndWait(this.getClass().getName() + ".type", //$NON-NLS-1$
new IRunnable<Void>() {
public Void run() { // SYNCH THREAD START
AbstractKeyTyper keyTyper =
i.next();
if (log.isDebugEnabled()) {
log.debug("trying to press: " + keyTyper); //$NON-NLS-1$
}
if (!alreadyDown.contains(keyTyper)) {
if (log.isDebugEnabled()) {
log.debug("pressing: " + keyTyper); //$NON-NLS-1$
}
post(keyTyper.createKeyDownEvent());
alreadyDown.add(keyTyper);
}
return null;
}
}
);
TimeUtil.delay(20);
}
} catch (IllegalArgumentException e) {
throw new RobotException(e);
} finally {
releaseKeys(options, alreadyDown, i);
}
}
/**
* @param key the key
* @return true, if key is already activated
*/
private boolean isActivated(int key)
throws OsNotSupportedException {
if (SWT.getPlatform().equals("win32")) { //$NON-NLS-1$
try {
return isActivatedWIN(key);
} catch (Exception e) {
log.error(e);
}
} else if (SWT.getPlatform().equals("motif")) { //$NON-NLS-1$
return isActivatedMOTIF(key);
} else if (SWT.getPlatform().equals("gtk")) { //$NON-NLS-1$
return isActivatedGTK(key);
}
throw new OsNotSupportedException("Current os \"" //$NON-NLS-1$
+ SWT.getPlatform() + "\" is not supported.", //$NON-NLS-1$
MessageIDs.E_UNSUPPORTED_OS);
}
/**
* @param key the key to toggle
* @return true, if <b>windows</b> key is already locked
*/
private boolean isActivatedWIN(int key)
throws ClassNotFoundException, IllegalArgumentException,
SecurityException, IllegalAccessException, NoSuchFieldException,
NoSuchMethodException, InvocationTargetException {
// Use reflection so that this compiles on all platforms
String className = "org.eclipse.swt.internal.win32.OS"; //$NON-NLS-1$
Class clazz = Class.forName(className);
int osSpecificKey = -1;
switch (key) {
case SWT.NUM_LOCK :
osSpecificKey =
clazz.getDeclaredField("VK_NUMLOCK").getInt(null); //$NON-NLS-1$
break;
case SWT.CAPS_LOCK :
osSpecificKey =
clazz.getDeclaredField("VK_CAPITAL").getInt(null); //$NON-NLS-1$
break;
case SWT.SCROLL_LOCK :
osSpecificKey =
clazz.getDeclaredField("VK_SCROLL").getInt(null); //$NON-NLS-1$
break;
default :
break;
}
// Call org.eclipse.swt.internal.win32.OS.GetKeyState
String methodName = "GetKeyState"; //$NON-NLS-1$
Class [] params = new Class [] {
Integer.TYPE,
};
Object [] args = new Object [] {
new Integer(osSpecificKey),
};
Method method = clazz.getMethod(methodName, params);
short keyState = ((Short)method.invoke(clazz, args)).shortValue();
if (keyState == 1) {
return true;
}
return false;
}
/**
* @param key the key to toggle
* @return true, if <b>gtk</b> key is already locked
*/
private boolean isActivatedGTK(int key) {
switch (key) {
default:
}
throw new UnsupportedOperationException("OS.GetKeyState"); //$NON-NLS-1$
}
/**
* @param key the key to toggle
* @return true, if <b>motif</b> key is already locked
*/
private boolean isActivatedMOTIF(int key) {
switch (key) {
default:
}
throw new UnsupportedOperationException("OS.GetKeyState"); //$NON-NLS-1$
}
/**
* Gets the InputEvent-ButtonMask of the given mouse button number
* @param button the button number
* @return the InputEvent button mask
*/
private int getButtonMask(int button) {
if (button == InteractionMode.primary.rcIntValue()) {
return SWT.BUTTON1;
}
if (button == InteractionMode.tertiary.rcIntValue()) {
return SWT.BUTTON2;
}
if (button == InteractionMode.secondary.rcIntValue()) {
return SWT.BUTTON3;
}
throw new RobotException("unsupported mouse button", null); //$NON-NLS-1$
}
/**
* {@inheritDoc}
*/
public void clickAtCurrentPosition(Object graphicsComponent,
int clickCount, int button) {
clickImpl(graphicsComponent,
ClickOptions.create().setClickCount(clickCount)
.setMouseButton(button));
}
/**
* {@inheritDoc}
*/
public java.awt.Point getCurrentMousePosition() {
java.awt.Point currentPos = m_queuer.invokeAndWait(
"getCursorPosition", new IRunnable<java.awt.Point>() { //$NON-NLS-1$
public java.awt.Point run() throws StepExecutionException {
Display d = (m_autServer).getAutDisplay();
final Point cursorLocation = d.getCursorLocation();
return new java.awt.Point(cursorLocation.x,
cursorLocation.y);
}
});
return currentPos;
}
/**
*
* {@inheritDoc}
*/
public boolean isMouseInComponent(Object graphicsComponent) {
final Widget comp = (Widget)graphicsComponent;
final java.awt.Point currentMousePosition = getCurrentMousePosition();
final Point currMousePos = new Point(currentMousePosition.x,
currentMousePosition.y);
final Rectangle bounds = m_queuer.invokeAndWait(
"getBounds", new IRunnable<Rectangle>() { //$NON-NLS-1$
public Rectangle run() throws StepExecutionException {
return SwtUtils.getWidgetBounds(comp);
}
});
Point treeLocUpperLeft = new Point(bounds.x, bounds.y);
final Point treeLocLowerRight = new Point(
bounds.width + treeLocUpperLeft.x,
bounds.height + treeLocUpperLeft.y);
final boolean x1 = currMousePos.x >= treeLocUpperLeft.x;
final boolean x2 = currMousePos.x < treeLocLowerRight.x;
final boolean y1 = currMousePos.y >= treeLocUpperLeft.y;
final boolean y2 = currMousePos.y < treeLocLowerRight.y;
return x1 && x2 && y1 && y2;
}
/**
* @param options options
* @param alreadyDown set of pressed keys
* @param i ListIterator
*/
private void releaseKeys(final InterceptorOptions options,
final Set<AbstractKeyTyper> alreadyDown,
final ListIterator<AbstractKeyTyper> i) {
// Release all keys in reverse order.
final Set<AbstractKeyTyper> alreadyUp = new HashSet<AbstractKeyTyper>();
try {
while (i.hasPrevious()) {
IRobotEventConfirmer confirmer = m_interceptor
.intercept(options);
m_queuer.invokeAndWait(this.getClass().getName() + ".type", //$NON-NLS-1$
new IRunnable<Void>() {
public Void run() { // SYNCH THREAD START
AbstractKeyTyper keyTyper =
i.previous();
if (log.isDebugEnabled()) {
log.debug("trying to release: " + keyTyper); //$NON-NLS-1$
}
if (alreadyDown.contains(keyTyper)
&& !alreadyUp.contains(keyTyper)) {
if (log.isDebugEnabled()) {
log.debug("releasing: " + keyTyper); //$NON-NLS-1$
}
post(keyTyper.createKeyUpEvent());
alreadyUp.add(keyTyper);
}
return null;
}
}
);
confirmer.waitToConfirm(null,
new KeySwtEventMatcher(SWT.KeyUp));
}
} catch (RobotException e) {
log.error("error releasing keys", e); //$NON-NLS-1$
if (!i.hasPrevious()) {
throw e;
}
}
}
/**
* {@inheritDoc}
*/
public String getSystemModifierSpec() {
String keyStrokeSpec = ValueSets.Modifier.control.rcValue();
if (SWT.MOD1 == SWT.COMMAND) {
keyStrokeSpec = ValueSets.Modifier.meta.rcValue();
} else if (SWT.MOD1 == SWT.ALT) {
keyStrokeSpec = ValueSets.Modifier.alt.rcValue();
}
return keyStrokeSpec;
}
/**
* Gets the absolute bounds of the given Widget.
* This method runs in the GUI-Thread.
* @param component a Widget
* @return the bounds
* @see SwtUtils#getBounds(Control)
*/
private Rectangle getBounds(final Widget component) {
return m_queuer.invokeAndWait("getBounds", new IRunnable<Rectangle>() { //$NON-NLS-1$
public Rectangle run() throws StepExecutionException {
return SwtUtils.getWidgetBounds(component);
}
});
}
/**
* Posts a Key-Press-event with the given modifier and the given character.
* @param graphicsComponent a graphicsComponent.
* @param modifier The modifier (e.g. SWT.SHIFT, SWT.CTRL, etc.)
* @param character a character to press.
* @return true if the event could be posted false otherwise.
*/
private boolean postKeyPress(Object graphicsComponent, int modifier,
char character) {
final Event event = createUntypedEvent((Widget)graphicsComponent,
modifier, character);
event.type = SWT.KeyDown;
return post(event);
}
/**
* Posts a Key-Release-event with the given modifier and the given character.
* @param graphicsComponent a graphicsComponent.
* @param modifier The modifier (e.g. SWT.SHIFT, SWT.CTRL, etc.)
* @param character a character to release.
* @return true if the event could be posted false otherwise.
*/
private boolean postKeyRelease(Object graphicsComponent, int modifier,
char character) {
final Event event = createUntypedEvent((Widget)graphicsComponent,
modifier, character);
event.type = SWT.KeyUp;
return post(event);
}
/**
* Creates an Event with the given parameters without an {@link Event#type}.
* @param graphicsComponent a Widget
* @param modifier a modifier, e.g. a SWT-constant (SWT.CTRL, etc.)
* @param character a char value
* @return an Event.
*/
private Event createUntypedEvent(Widget graphicsComponent, int modifier,
char character) {
final Event event = new Event();
event.keyCode = modifier;
event.character = character;
event.widget = graphicsComponent;
return event;
}
/**
* Posts an {@link Event} on the UI input queue.
*
* @param event the {@link Event} to post
* @return true if the event could be posted false otherwise.
* @see Display#post(Event)
*/
private boolean post(Event event) {
try {
final Display display = m_autServer.getAutDisplay();
if (!display.post(event)) {
final String msg = "posting event failed: " + event; //$NON-NLS-1$
log.error(msg);
return false;
}
} catch (SWTError swte) {
log.error(swte);
throw new RobotException(swte);
}
return true;
}
/**
* {@inheritDoc}
*/
public void mousePress(final Object graphicsComponent,
Rectangle constraints, final int button) throws RobotException {
if (graphicsComponent != null) {
move(graphicsComponent, constraints);
}
RobotTiming.sleepPreClickDelay();
m_robot.mousePress(getButtonMask(button));
}
/**
* {@inheritDoc}
*/
public void mouseRelease(final Object graphicsComponent,
Rectangle constraints, final int button) throws RobotException {
if (graphicsComponent != null) {
move(graphicsComponent, constraints);
}
RobotTiming.sleepPreClickDelay();
m_robot.mouseRelease(getButtonMask(button));
}
/**
* Clicks at the current mouse position.
*
* @param graphicsComponent The component used for confirming the click.
* @param clickOptions Configuration for the click.
*/
private void clickImpl(Object graphicsComponent,
ClickOptions clickOptions) {
int buttonMask = getButtonMask(clickOptions.getMouseButton());
int clickCount = clickOptions.getClickCount();
int[] modifierMask = getModifierMask(clickOptions.getClickModifier());
if (clickCount > 0) {
final IEventMatcher matcher =
new ClickSwtEventMatcher(clickOptions);
IRobotEventConfirmer confirmer = null;
if (clickOptions.isConfirmClick()) {
final InterceptorOptions options = new InterceptorOptions(
new long[]{SWT.MouseUp, SWT.MouseDown});
confirmer = m_interceptor.intercept(options);
}
try {
pressModifier(modifierMask);
RobotTiming.sleepPreClickDelay();
for (int i = 0; i < clickCount; i++) {
m_robot.mousePress(buttonMask);
RobotTiming.sleepPostMouseDownDelay();
m_robot.mouseRelease(buttonMask);
RobotTiming.sleepPostMouseUpDelay();
}
if (confirmer != null) {
try {
confirmer.waitToConfirm(graphicsComponent, matcher);
} catch (RobotException re) {
StringBuffer sb = new StringBuffer(
"Robot exception occurred while clicking...\n"); //$NON-NLS-1$
logRobotException(graphicsComponent, re, sb);
throw re;
}
}
} finally {
releaseModifier(modifierMask);
}
}
}
/**
* @param clickModifier
* the click modifier to use for this click
* @return an array of modifiers to press before click and release after
* click
*/
private int[] getModifierMask(ClickModifier clickModifier) {
int[] modifier = new int[0];
if (clickModifier.hasModifiers(ClickModifier.M1)) {
modifier = ArrayUtils.add(modifier, SwtUtils
.getSystemDefaultModifier());
}
if (clickModifier.hasModifiers(ClickModifier.M2)) {
modifier = ArrayUtils.add(modifier, SwtUtils.getSystemModifier2());
}
if (clickModifier.hasModifiers(ClickModifier.M3)) {
modifier = ArrayUtils.add(modifier, SwtUtils.getSystemModifier3());
}
if (clickModifier.hasModifiers(ClickModifier.M4)) {
modifier = ArrayUtils.add(modifier, SwtUtils.getSystemModifier4());
}
return modifier;
}
/**
* @param modifierMask
* array of modifiers to press before click
*/
private void pressModifier(int[] modifierMask) {
for (int i = 0; i < modifierMask.length; i++) {
keyPress(null, modifierMask[i]);
}
}
/**
* @param modifierMask
* array of modifiers release after click
*/
private void releaseModifier(int[] modifierMask) {
for (int i = 0; i < modifierMask.length; i++) {
keyRelease(null, modifierMask[i]);
}
}
/**
* {@inheritDoc}
*/
public void click(Object graphicsComponent, Rectangle constraints,
ClickOptions clickOptions, int xPos, boolean xAbsolute,
int yPos, boolean yAbsolute) throws RobotException {
clickImpl(graphicsComponent, constraints, clickOptions,
xPos, xAbsolute, yPos, yAbsolute);
}
/**
* {@inheritDoc}
*/
public String getPropertyValue(Object graphicsComponent,
String propertyName) throws RobotException {
return PropertyUtil.getPropertyValue(graphicsComponent, propertyName);
}
/** {@inheritDoc} */
public BufferedImage createFullScreenCapture() {
return LocalScreenshotUtil.createFullScreenCapture();
}
/**
* {@inheritDoc}
*/
public void shakeMouse() {
m_queuer.invokeAndWait("shakeMouse", new IRunnable<Void>() { //$NON-NLS-1$
public Void run() throws StepExecutionException {
/**
* number of pixels by which a "mouse shake" offsets the mouse
* cursor
*/
final int mouseShakeOffset = 10;
java.awt.Point origin = AUTServer.getInstance().getRobot()
.getCurrentMousePosition();
SwtRobot lowLevelRobot = new SwtRobot(Display.getDefault());
lowLevelRobot.mouseMove(origin.x + mouseShakeOffset,
origin.y + mouseShakeOffset);
lowLevelRobot.mouseMove(origin.x - mouseShakeOffset,
origin.y - mouseShakeOffset);
lowLevelRobot.mouseMove(origin.x, origin.y);
if (!EnvironmentUtils.isWindowsOS()
&& !EnvironmentUtils.isMacOS()) {
boolean moreEvents = true;
while (moreEvents) {
moreEvents = Display.getDefault().readAndDispatch();
}
}
return null;
}
});
}
/** {@inheritDoc} */
public java.awt.Rectangle getComponentBounds(IComponent component) {
Rectangle swtRec = getBounds((Widget) component.getRealComponent());
java.awt.Rectangle rectangle = new java.awt.Rectangle(
swtRec.x, swtRec.y, swtRec.width, swtRec.height);
return rectangle;
}
}