/*******************************************************************************
* 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.swing.driver;
import java.awt.AWTError;
import java.awt.AWTEvent;
import java.awt.AWTException;
import java.awt.Component;
import java.awt.Container;
import java.awt.Event;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.Validate;
import org.eclipse.jubula.rc.common.driver.ClickOptions;
import org.eclipse.jubula.rc.common.driver.ClickOptions.ClickModifier;
import org.eclipse.jubula.rc.common.driver.DragAndDropHelper;
import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer;
import org.eclipse.jubula.rc.common.driver.IMouseMotionTracker;
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.KeyTyper;
import org.eclipse.jubula.rc.common.driver.MouseMovementStrategy;
import org.eclipse.jubula.rc.common.driver.RobotTiming;
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.PointUtil;
import org.eclipse.jubula.rc.common.util.PropertyUtil;
import org.eclipse.jubula.rc.swing.utils.SwingUtils;
import org.eclipse.jubula.toolkit.enums.ValueSets;
import org.eclipse.jubula.toolkit.enums.ValueSets.InteractionMode;
import org.eclipse.jubula.tools.internal.constants.TimingConstantsServer;
import org.eclipse.jubula.tools.internal.i18n.I18n;
import org.eclipse.jubula.tools.internal.objects.event.EventFactory;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
/**
* <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.swing.driver.IRobotEventInterceptor}and
* {@link org.eclipse.jubula.rc.swing.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}and the
* constraints object is <code>null</code> or of type
* {@link java.awt.Rectangle}.
* </p>
*
* @author BREDEX GmbH
* @created 17.03.2005
*/
public class RobotAwtImpl implements IRobot<Rectangle> {
/** the timeout to flush native events (only relevant for Java 7) */
private static final int FLUSH_TIMEOUT = 10000;
/** the logger */
private static AutServerLogger log =
new AutServerLogger(RobotAwtImpl.class);
/** max retries to get the location on screen */
private static final int MAX_RETRIES = 1;
/** ID of Metal Look and Feel */
private static final String METAL_LAF_ID = "Metal"; //$NON-NLS-1$
/** The AWT Robot instance. */
private Robot m_robot;
/** The toolkit utilities */
private EventFlusher m_eventFlusher;
/** The event interceptor. */
private IRobotEventInterceptor m_interceptor;
/** The mouse motion tracker. */
private IMouseMotionTracker m_mouseMotionTracker;
/** The event thread queuer. */
private IEventThreadQueuer m_queuer;
/**
* Scrolls a component to visible. The Scroller assumes 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 mechanism 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> interprets <code>scrollRectToVisible()</code>
* in a different way by scrolling the containing text, not the component
* itself. Again, <code>super.scrollRectToVisible()</code> is not called.
*/
private class Scroller {
/** The component to scroll to visible. */
private Component m_component;
/**
* @param component The component to scroll to visible.
*/
public Scroller(Component component) {
m_component = component;
}
/**
* Scrolls the component to visible.
* @param component The component.
* @param aRect The bounds.
*/
private void scrollRectToVisible(Component component, Rectangle aRect) {
Container parent;
int dx = component.getX();
int dy = component.getY();
for (parent = component.getParent(); (parent != null)
&& !(parent instanceof JComponent)
&& !(parent instanceof CellRendererPane); parent = parent
.getParent()) {
Rectangle bounds = parent.getBounds();
dx += bounds.x;
dy += bounds.y;
}
if ((parent != null) && !(parent instanceof CellRendererPane)) {
aRect.x += dx;
aRect.y += dy;
if (parent instanceof JComponent) {
((JComponent)parent).scrollRectToVisible(aRect);
Point p1 = getLocation(m_component, null);
Point p2 = parent.getLocationOnScreen();
aRect.x = p1.x - p2.x;
aRect.y = p1.y - p2.y;
}
}
if (parent != null) {
scrollRectToVisible(parent, aRect);
}
}
/**
* Scrolls the component passed to the constructor to visible.
* @param aRect The bounds of the component.
*/
public void scrollRectToVisible(Rectangle aRect) {
scrollRectToVisible(m_component, aRect);
}
}
/**
* Creates a new instance.
* @param factory The Robot factory instance.
* @throws RobotException If the AWT-Robot cannot be created.
*/
public RobotAwtImpl(IRobotFactory factory) throws RobotException {
// HERE init robot in the AWT Event Dispatch Thread on Linux ??? (see
// http://www.netbeans.org/issues/show_bug.cgi?id=37476)
try {
m_robot = new Robot();
m_robot.setAutoWaitForIdle(false);
m_robot.setAutoDelay(0);
} catch (AWTException awte) {
log.error(awte);
m_robot = null;
throw new RobotException(awte);
} catch (SecurityException se) {
log.error(se);
m_robot = null;
throw new RobotException(se);
}
m_interceptor = factory.getRobotEventInterceptor();
m_mouseMotionTracker = factory.getMouseMotionTracker();
m_queuer = factory.getEventThreadQueuer();
m_eventFlusher = new EventFlusher(m_robot, FLUSH_TIMEOUT);
}
/**
* Gets a location inside the component. If <code>offset</code> is
* <code>null</code>, it returns the middle of the component otherwise it
* adds the offset to the upper left corner.
* @param component the component to get the location for
* @param offset the offset
* @throws IllegalArgumentException if <code>component</code> is null
* @return the <b>global </b> coordinates of <code>component</code>
*/
private Point getLocation(Component component, final Point offset)
throws IllegalArgumentException {
Validate.notNull(component, "component must not be null"); //$NON-NLS-1$
final Component comp = component;
IRunnable<Point> runnable = new IRunnable<Point>() {
public Point run() {
Point pos = comp.getLocationOnScreen();
if (offset == null) {
pos.x += comp.getBounds().width / 2;
pos.y += comp.getBounds().height / 2;
} else {
pos.x += offset.x;
pos.y += offset.y;
}
return pos;
}
};
Point point = null;
StepExecutionException exc = null;
int retries = 0;
do {
try {
point = m_queuer.invokeAndWait("getLocation", runnable); //$NON-NLS-1$
} catch (StepExecutionException e) {
exc = e;
retries++;
log.error("getLocation failed - " + retries); //$NON-NLS-1$
try {
Thread.sleep(TimingConstantsServer
.GET_LOCATION_RETRY_DELAY);
} catch (InterruptedException e1) {
log.error(e1.getLocalizedMessage());
}
}
} while (point == null && retries < MAX_RETRIES);
if (point == null) {
throw exc;
}
return point;
}
/**
* 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, must be a
* <code>java.awt.Rectangle</code> or <code>null</code>.
* The constraints are <em>relative</em> to the
* location/origin of the <code>graphicsComponent</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 interrupted or the event confirmation receives a timeout.
*/
private void clickImpl(Object graphicsComponent, Rectangle constraints,
ClickOptions clickOptions, int xPos,
boolean xAbsolute, int yPos, boolean yAbsolute)
throws RobotException {
moveImpl(graphicsComponent, constraints, xPos, xAbsolute,
yPos, yAbsolute, clickOptions);
clickImpl(graphicsComponent, clickOptions);
}
/**
* 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) {
IRobotEventConfirmer confirmer = null;
if (clickOptions.isConfirmClick()) {
InterceptorOptions options = new InterceptorOptions(
new long[] { AWTEvent.MOUSE_EVENT_MASK });
confirmer = m_interceptor.intercept(options);
}
try {
pressModifier(modifierMask);
RobotTiming.sleepPreClickDelay();
for (int i = 0; i < clickCount; i++) {
m_robot.mousePress(buttonMask);
RobotTiming.sleepPostMouseDownDelay();
m_eventFlusher.flush();
m_robot.mouseRelease(buttonMask);
RobotTiming.sleepPostMouseUpDelay();
m_eventFlusher.flush();
}
if (confirmer != null) {
confirmer.waitToConfirm(graphicsComponent,
new ClickAwtEventMatcher(clickOptions));
}
} finally {
releaseModifier(modifierMask);
}
}
}
/**
* @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]);
}
}
/**
* @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,
SwingUtils.getSystemDefaultModifier());
}
if (clickModifier.hasModifiers(ClickModifier.M2)) {
modifier = ArrayUtils.add(modifier,
SwingUtils.getSystemModifier2());
}
if (clickModifier.hasModifiers(ClickModifier.M3)) {
modifier = ArrayUtils.add(modifier,
SwingUtils.getSystemModifier3());
}
if (clickModifier.hasModifiers(ClickModifier.M4)) {
modifier = ArrayUtils.add(modifier,
SwingUtils.getSystemModifier4());
}
return modifier;
}
/**
* Checks if the mouse has to be moved on <code>p</code> or if the mouse
* pointer already resides on this location.
* @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 = true;
Point point = m_mouseMotionTracker.getLastMousePointOnScreen();
if (point != null) {
result = !point.equals(p);
if (log.isDebugEnabled()) {
MouseEvent event = (MouseEvent)m_mouseMotionTracker
.getLastMouseMotionEvent();
if (event != null) {
log.debug("Last mouse motion event point: " //$NON-NLS-1$
+ event.getPoint());
}
log.debug("Last converted screen point : " + point); //$NON-NLS-1$
log.debug("Required screen point : " + p); //$NON-NLS-1$
log.debug("Mouse move required? : " + result); //$NON-NLS-1$
}
}
return result;
}
/**
* Implementation of the mouse move. The mouse is moved into the graphics component.
* @param graphicsComponent The component to move to
* @param constraints The more specific constraints. Use this, for example
* when you want the click point to be relative to a part
* of the component (e.g. tree node, table cell, etc)
* rather than the overall component itself. May be
* <code>null</code>.
* @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 The click options
* @throws StepExecutionException If the click delay is interrupted or the
* event confirmation receives a timeout.
*/
private void moveImpl(Object graphicsComponent,
final Rectangle constraints, final int xPos,
final boolean xAbsolute, final int yPos, final boolean yAbsolute,
ClickOptions clickOptions)
throws StepExecutionException {
if (clickOptions.isScrollToVisible()) {
ensureComponentVisible((Component)graphicsComponent, constraints);
m_eventFlusher.flush();
}
Component component = (Component)graphicsComponent;
Rectangle bounds = new Rectangle(
getLocation(component, new Point(0, 0)));
bounds.width = component.getWidth();
bounds.height = component.getHeight();
if (component instanceof Frame) {
Frame window = (Frame) component;
if (bounds.x < 0
&& checkExtendedState(window, Frame.MAXIMIZED_HORIZ)) {
bounds.width += 2 * bounds.x;
bounds.x = 0;
}
if (bounds.y < 0
&& checkExtendedState(window, Frame.MAXIMIZED_VERT)) {
bounds.height += 2 * bounds.y;
bounds.y = 0;
}
}
if (constraints != null) {
bounds.x += constraints.x;
bounds.y += constraints.y;
bounds.height = constraints.height;
bounds.width = constraints.width;
}
Point p = PointUtil.calculateAwtPointToGo(xPos, xAbsolute, yPos,
yAbsolute, bounds);
// Move if necessary
if (isMouseMoveRequired(p)) {
if (log.isDebugEnabled()) {
log.debug("Moving mouse to: " + p); //$NON-NLS-1$
}
IRobotEventConfirmer confirmer = null;
if (clickOptions.isConfirmClick()) {
InterceptorOptions options = new InterceptorOptions(new long[]{
AWTEvent.MOUSE_MOTION_EVENT_MASK});
confirmer = m_interceptor.intercept(options);
}
Point startpoint = m_mouseMotionTracker.getLastMousePointOnScreen();
if (startpoint == null) {
// If there is no starting point the center of the root component is used
Component root = SwingUtilities.getRoot(component);
Component c = (root != null) ? root : component;
startpoint = getLocation(c, null);
}
Point[] mouseMove = MouseMovementStrategy.
getMovementPath(startpoint, p,
clickOptions.getStepMovement(),
clickOptions.getFirstHorizontal());
for (int i = 0; i < mouseMove.length; i++) {
m_robot.mouseMove(mouseMove[i].x, mouseMove[i].y);
m_eventFlusher.flush();
}
if (confirmer != null) {
confirmer.waitToConfirm(component,
new MouseMovedAwtEventMatcher());
}
}
}
/**
*
* @param frame the <code>JFrame</code> to to check if it is maximized
* @param stateBits see <code>getExtendedState()</code>
* @return <code>true</code> if the specified maximize state is set.
*/
private boolean checkExtendedState(Frame frame, int stateBits) {
return (frame.getExtendedState() & stateBits)
== stateBits;
}
/**
* {@inheritDoc}
*/
public void click(Object graphicsComponent, Rectangle constraints)
throws RobotException {
click(graphicsComponent, constraints, ClickOptions.create());
}
/**
* {@inheritDoc}
*/
public void click(Object graphicsComponent, Rectangle constraints,
ClickOptions clickOptions) throws RobotException {
clickImpl(graphicsComponent, constraints, clickOptions,
50, false, 50, false);
}
/**
* 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 InputEvent.BUTTON1_MASK;
}
if (button == InteractionMode.tertiary.rcIntValue()) {
return InputEvent.BUTTON2_MASK;
}
if (button == InteractionMode.secondary.rcIntValue()) {
return InputEvent.BUTTON3_MASK;
}
throw new RobotException("unsupported mouse button", null); //$NON-NLS-1$
}
/**
* {@inheritDoc}
*/
public void clickAtCurrentPosition(Object graphicsComponent,
int clickCount, int button) {
ClickOptions clickOptions = new ClickOptions();
clickOptions.setClickCount(clickCount);
clickOptions.setMouseButton(button);
clickImpl(graphicsComponent, clickOptions);
}
/**
* {@inheritDoc}
*/
public void move(Object graphicsComponent, Rectangle constraints)
throws RobotException {
moveImpl(graphicsComponent, constraints, 50, false, 50,
false, ClickOptions.create());
}
/**
* {@inheritDoc}
*/
public void type(Object graphicsComponent, char character)
throws RobotException {
Validate.notNull(graphicsComponent, "The graphic component must not be null"); //$NON-NLS-1$
try {
int modifier = 0;
Component component = (Component)graphicsComponent;
KeyEvent ke = new KeyEvent(component, KeyEvent.KEY_PRESSED,
System.currentTimeMillis(), modifier, KeyEvent.VK_UNDEFINED,
character);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ke);
ke = new KeyEvent(component, KeyEvent.KEY_TYPED,
System.currentTimeMillis(), modifier, KeyEvent.VK_UNDEFINED,
character);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ke);
IRobotEventConfirmer confirmer = m_interceptor.intercept(
new InterceptorOptions(new long[]{AWTEvent.KEY_EVENT_MASK}));
ke = new KeyEvent(component, KeyEvent.KEY_RELEASED,
System.currentTimeMillis(), modifier, KeyEvent.VK_UNDEFINED,
character);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ke);
confirmer.waitToConfirm(component,
new DefaultAwtEventMatcher(KeyEvent.KEY_RELEASED));
} catch (AWTError awte) {
log.error(awte);
throw new RobotException(awte);
} catch (SecurityException se) {
log.error(se);
throw new RobotException(se);
}
}
/**
* {@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[]{
AWTEvent.KEY_EVENT_MASK});
IRobotEventConfirmer confirmer = m_interceptor.intercept(options);
try {
m_robot.keyPress(keycode);
m_eventFlusher.flush();
} finally {
m_robot.keyRelease(keycode);
m_eventFlusher.flush();
}
confirmer.waitToConfirm(graphicsComponent, new KeyAwtEventMatcher(
KeyEvent.KEY_RELEASED));
} catch (IllegalArgumentException e) {
throw new RobotException(e);
}
}
/**
* {@inheritDoc}
*/
public String getSystemModifierSpec() {
String keyStrokeSpec = ValueSets.Modifier.control.rcValue();
if (!(UIManager.getLookAndFeel().getID().equals(METAL_LAF_ID))) {
if (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
== Event.META_MASK) {
keyStrokeSpec = ValueSets.Modifier.meta.rcValue();
} else if (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
== Event.ALT_MASK) {
keyStrokeSpec = ValueSets.Modifier.alt.rcValue();
}
}
return keyStrokeSpec;
}
/**
* Implements the key press or release.
* @param graphicsComponent The component, may be <code>null</code>
* @param keyCode The key code
* @param press If <code>true</code>, the key is pressed, otherwise released
*/
private void keyPressReleaseImpl(Object graphicsComponent, int keyCode,
boolean press) {
InterceptorOptions options = new InterceptorOptions(new long[]{
AWTEvent.KEY_EVENT_MASK});
IRobotEventConfirmer confirmer = m_interceptor.intercept(options);
if (press) {
m_robot.keyPress(keyCode);
m_eventFlusher.flush();
} else {
m_robot.keyRelease(keyCode);
m_eventFlusher.flush();
}
confirmer.waitToConfirm(graphicsComponent, new KeyAwtEventMatcher(
press ? KeyEvent.KEY_PRESSED : KeyEvent.KEY_RELEASED));
}
/**
* {@inheritDoc}
*/
public void keyPress(Object graphicsComponent, int keycode)
throws RobotException {
keyPressReleaseImpl(graphicsComponent, keycode, true);
}
/**
* {@inheritDoc}
*/
public void keyRelease(Object graphicsComponent, int keycode)
throws RobotException {
keyPressReleaseImpl(graphicsComponent, keycode, false);
}
/**
* a method to turn the toggle keys caps-lock, num-lock and scroll-lock on and off.
* @param obj Component
* @param key to set key Event
* @param activated boolean
*/
public void keyToggle(Object obj, int key, boolean activated) {
Validate.notNull(obj, "The graphic component must not be null"); //$NON-NLS-1$
if ((activated && !((Component)obj).getToolkit().getLockingKeyState(
key)) || (!activated && ((Component)obj).getToolkit()
.getLockingKeyState(key))) {
keyPressReleaseImpl(null, key, true);
keyPressReleaseImpl(null, key, false);
}
}
/**
* {@inheritDoc}
*/
public void keyStroke(String keyStrokeSpec) throws RobotException {
try {
KeyTyper.getInstance().type(keyStrokeSpec, m_interceptor,
new DefaultAwtEventMatcher(KeyEvent.KEY_PRESSED),
new KeyReleasedEventMatcher());
} catch (AWTException e) {
throw new RobotException(e);
}
}
/**
* 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 Component component,
final Rectangle bounds) throws RobotException {
m_queuer.invokeAndWait("ensureVisible", new IRunnable<Void>() { //$NON-NLS-1$
public Void run() {
Rectangle rectangle = bounds != null ? new Rectangle(bounds)
: SwingUtilities.getLocalBounds(component);
if (log.isDebugEnabled()) {
log.debug("Scrolling rectangle to visible: " + rectangle); //$NON-NLS-1$
}
Scroller scroller = new Scroller(component);
scroller.scrollRectToVisible(rectangle);
return null;
}
});
}
/**
* {@inheritDoc}
*/
public void scrollToVisible(Object graphicsComponent, Rectangle constraints)
throws RobotException {
ensureComponentVisible((Component)graphicsComponent,
constraints);
}
/**
* {@inheritDoc}
*/
public void activateApplication(String method) throws RobotException {
try {
Window window = getActiveWindow();
if (window == null) {
return;
}
WindowActivationMethod wam =
WindowActivationMethod.createWindowActivationMethod(
method, m_robot, m_queuer);
wam.activate(window);
// Verify that window was successfully activated
Window activeWindow = m_queuer.invokeAndWait(
"getActiveWindow", //$NON-NLS-1$
new IRunnable<Window>() {
public Window run() throws StepExecutionException {
if (Frame.getFrames().length == 0) {
return null;
}
for (int i = 0; i < Frame.getFrames().length; ++i) {
Window curWindow = Frame.getFrames()[i];
while (curWindow.getOwner() != null) {
curWindow = curWindow.getOwner();
}
if (curWindow.isFocused()) {
return curWindow;
}
}
return null;
}
});
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);
}
}
/**
* @return The current mouse position as a Point
* {@inheritDoc}
*/
public Point getCurrentMousePosition() {
return m_mouseMotionTracker.getLastMousePointOnScreen();
}
/**
* Guesses the active window. Returns null if no active window is found.
* @return the active window
*/
private Window getActiveWindow() {
return m_queuer.invokeAndWait(
"getActiveWindow", //$NON-NLS-1$
new IRunnable<Window>() {
public Window run() throws StepExecutionException {
if (Frame.getFrames().length == 0) {
return null;
}
for (int i = 0; i < Frame.getFrames().length; ++i) {
Window window = Frame.getFrames()[i];
while (window.getOwner() != null) {
window = window.getOwner();
}
if (window.isVisible()) {
return window;
}
}
return null;
}
});
}
/**
*
* {@inheritDoc}
*/
public boolean isMouseInComponent(Object graphicsComponent) {
Component comp = (Component)graphicsComponent;
final Point currMousePos = getCurrentMousePosition();
if (currMousePos == null) {
return false;
}
final Point treeLocUpperLeft = comp.getLocationOnScreen();
final Rectangle bounds = comp.getBounds();
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;
}
/**
* Presses the given mouse button on the given component in the given
* constraints. <br>
* <b>Note:</b> Use only for Drag and Drop!
* To click with the mouse, use click-methods!
* @param graphicsComponent the component where to press the mouse button.
* If null, the mouse is pressed at the current location.
* @param constraints A constraints object used by the Robot implementation, may be <code>null</code>.
* @param button the mouse button which is to be pressed.
*/
public void mousePress(Object graphicsComponent, Rectangle constraints,
int button) {
DragAndDropHelper.getInstance().setDragMode(true);
if (graphicsComponent != null) {
move(graphicsComponent, constraints);
}
RobotTiming.sleepPreClickDelay();
m_robot.mousePress(getButtonMask(button));
m_eventFlusher.flush();
}
/**
* Releases the given mouse button on the given component in the given
* constraints. <br>
* <b>Note:</b> Use only for Drag and Drop!
* To click with the mouse, use click-methods!
* @param graphicsComponent The graphics component.
* If null, the mouse button is released at the current location.
* @param constraints A constraints object used by the Robot implementation, may be <code>null</code>.
* @param button the mouse button.
*/
public void mouseRelease(Object graphicsComponent, Rectangle constraints,
int button) throws RobotException {
if (graphicsComponent != null) {
move(graphicsComponent, constraints);
}
RobotTiming.sleepPreClickDelay();
m_robot.mouseRelease(getButtonMask(button));
m_eventFlusher.flush();
DragAndDropHelper.getInstance().setDragMode(false);
}
/**
* {@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() {
/** number of pixels by which a "mouse shake" offsets the mouse cursor */
final int mouseShakeOffset = 10;
Point origin = getCurrentMousePosition();
try {
m_robot.mouseMove(
origin.x + mouseShakeOffset,
origin.y + mouseShakeOffset);
m_robot.mouseMove(
origin.x - mouseShakeOffset,
origin.y - mouseShakeOffset);
} finally {
m_robot.mouseMove(origin.x, origin.y);
}
}
/** {@inheritDoc} */
public Rectangle getComponentBounds(IComponent component) {
Component realComponent = (Component) component.getRealComponent();
Point screenLocation = realComponent.getLocationOnScreen();
Rectangle rec = realComponent.getBounds();
rec.setLocation(screenLocation.x, screenLocation.y);
return rec;
}
}