/*******************************************************************************
* Copyright (c) 2013 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.javafx.tester.adapter;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.Callable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Control;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import org.eclipse.jubula.rc.common.driver.ClickOptions;
import org.eclipse.jubula.rc.common.driver.DragAndDropHelper;
import org.eclipse.jubula.rc.common.driver.IRobot;
import org.eclipse.jubula.rc.common.exception.RobotException;
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
import org.eclipse.jubula.rc.common.listener.EventLock;
import org.eclipse.jubula.rc.common.tester.AbstractMenuTester;
import org.eclipse.jubula.rc.common.tester.adapter.interfaces.IWidgetComponent;
import org.eclipse.jubula.rc.common.util.KeyStrokeUtil;
import org.eclipse.jubula.rc.javafx.driver.EventThreadQueuerJavaFXImpl;
import org.eclipse.jubula.rc.javafx.tester.MenuTester;
import org.eclipse.jubula.rc.javafx.tester.util.NodeBounds;
import org.eclipse.jubula.rc.javafx.tester.util.Rounding;
import org.eclipse.jubula.rc.javafx.tester.util.WindowsUtil;
import org.eclipse.jubula.toolkit.enums.ValueSets;
import org.eclipse.jubula.tools.internal.constants.TimeoutConstants;
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.TimeUtil;
/**
* Implements the interface for widgets and supports basic methods which are
* needed for nearly all JavaFX UI components.
*
* @param <T>
* type of the Component
*
* @author BREDEX GmbH
* @created 30.10.2013
*/
public class JavaFXComponentAdapter<T extends Node> extends
AbstractComponentAdapter<T> implements IWidgetComponent {
/**
* The Converter Map.
*/
private static Map<String, Integer> converterTable = null;
static {
converterTable = new HashMap<String, Integer>();
converterTable.put(ValueSets.Modifier.none.rcValue(), new Integer(-1));
converterTable.put(ValueSets.Modifier.shift.rcValue(), new Integer(
KeyEvent.VK_SHIFT));
converterTable.put(ValueSets.Modifier.control.rcValue(), new Integer(
KeyEvent.VK_CONTROL));
converterTable.put(ValueSets.Modifier.alt.rcValue(), new Integer(
KeyEvent.VK_ALT));
converterTable.put(ValueSets.Modifier.meta.rcValue(), new Integer(
KeyEvent.VK_META));
converterTable.put(ValueSets.Modifier.cmd.rcValue(), new Integer(
KeyEvent.VK_META));
converterTable.put(ValueSets.Modifier.mod.rcValue(), new Integer(
KeyEvent.VK_CONTROL));
}
/**
* Used to store the component into the adapter.
*
* @param objectToAdapt
* the object to adapt
*/
public JavaFXComponentAdapter(T objectToAdapt) {
super(objectToAdapt);
}
@Override
public boolean isShowing() {
boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"isShowing", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
return getRealComponent().isVisible();
}
});
return result;
}
@Override
public boolean isEnabled() {
boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"isEnabled", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
// because the logic in JavaFX
// is switched the return value is inverted
return !(getRealComponent().isDisabled());
}
});
return result;
}
@Override
public boolean hasFocus() {
boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"hasFocus", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
return getRealComponent().isFocused();
}
});
return result;
}
@Override
public String getPropteryValue(final String propertyname) {
Object prop = EventThreadQueuerJavaFXImpl.invokeAndWait("getProperty", //$NON-NLS-1$
new Callable<String>() {
@Override
public String call() throws Exception {
try {
return getRobot().getPropertyValue(
getRealComponent(), propertyname);
} catch (RobotException e) {
throw new StepExecutionException(
e.getMessage(),
EventFactory
.createActionError(TestErrorEvent.
PROPERTY_NOT_ACCESSABLE));
}
}
});
return String.valueOf(prop);
}
@Override
public AbstractMenuTester showPopup(int xPos, String xUnits, int yPos,
String yUnits, int button) throws StepExecutionException {
Node n = getRealComponent();
if (n instanceof Control && ((Control) n).getContextMenu() != null) {
return openPropertyContextMenu(xPos, xUnits, yPos, yUnits, button,
(Control)n);
}
return openContextMenu(xPos, xUnits, yPos, yUnits, button, n);
}
/**
* Opens the context menu of the given node and finds it with
* Window.impl_getWindows(). Use this method for components which are not a
* subclass of Control and therefore don't have the context menu property
*
* @param xPos
* what x position
* @param xUnits
* should x position be pixel or percent values
* @param yPos
* what y position
* @param yUnits
* should y position be pixel or percent values
* @param button
* MouseButton
* @param n
* the Node
* @return a MenuTester instance which references the context menu
*/
protected AbstractMenuTester openContextMenu(int xPos, String xUnits,
int yPos, String yUnits, int button, Node n) {
boolean isAbsoluteUnitsX = ValueSets.Unit.pixel.rcValue()
.equalsIgnoreCase(xUnits);
boolean isAbsoluteUnitsY = ValueSets.Unit.pixel.rcValue()
.equalsIgnoreCase(yUnits);
getRobot().click(
n,
null,
ClickOptions.create().setClickCount(1)
.setMouseButton(button), xPos, isAbsoluteUnitsX,
yPos, isAbsoluteUnitsY);
return EventThreadQueuerJavaFXImpl.invokeAndWait(
"showPopup", new Callable<MenuTester>() { //$NON-NLS-1$
@Override
public MenuTester call() throws Exception {
MenuTester menuTester = null;
Iterator<Window> iter = WindowsUtil.getWindowIterator();
ArrayList<ContextMenu> result = new ArrayList<>();
long timeout = TimeoutConstants.
SERVER_TIMEOUT_WAIT_FOR_POPUP;
long done = System.currentTimeMillis() + timeout;
long now;
do {
if (!iter.hasNext()) {
iter = WindowsUtil.getWindowIterator();
}
Window w = iter.next();
if (w instanceof ContextMenu
&& !result.contains(w)) {
result.add((ContextMenu) w);
}
now = System.currentTimeMillis();
timeout = done - now;
} while (timeout > 0
&& !(!iter.hasNext() && result.size() > 0));
if (result.size() == 1) {
ContextMenu cm = result.get(0);
menuTester = new MenuTester();
menuTester.setComponent(cm);
} else if (result.size() == 0) {
throw new StepExecutionException("No Context Menu was found", //$NON-NLS-1$
EventFactory
.createActionError(TestErrorEvent.
POPUP_NOT_FOUND));
} else if (result.size() > 1) {
throw new StepExecutionException("Multiple Context Menus were found", //$NON-NLS-1$
EventFactory.createActionError(TestErrorEvent.
UNSUPPORTED_OPERATION_IN_TOOLKIT_ERROR));
}
return menuTester;
}
});
}
/**
* Opens the context menu of the given control, via the context menu
* property.
*
* @param xPos
* what x position
* @param xUnits should x position be pixel or percent
* values
* @param yPos
* what y position
* @param yUnits should y position be pixel or percent
* values
* @param button
* MouseButton
* @param comp
* the control
* @return a MenuTester instance which references the context menu
*/
private AbstractMenuTester openPropertyContextMenu(int xPos, String xUnits,
int yPos, String yUnits, int button, Control comp) {
final EventLock event = new EventLock();
ContextMenu cotxMenu = comp.getContextMenu();
EventHandler<WindowEvent> filter = new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent e) {
synchronized (event) {
event.notifyAll();
}
}
};
cotxMenu.addEventFilter(WindowEvent.WINDOW_SHOWN, filter);
// RobotTiming.sleepPreShowPopupDelay();
boolean isAbsoluteUnitsX = ValueSets.Unit.pixel.rcValue()
.equalsIgnoreCase(xUnits);
boolean isAbsoluteUnitsY = ValueSets.Unit.pixel.rcValue()
.equalsIgnoreCase(yUnits);
getRobot().click(
comp,
null,
ClickOptions.create().setClickCount(1)
.setMouseButton(button), xPos, isAbsoluteUnitsX,
yPos, isAbsoluteUnitsY);
if (!comp.getContextMenu().isShowing()) {
try {
synchronized (event) {
event.wait(TimeoutConstants.
SERVER_TIMEOUT_WAIT_FOR_POPUP);
}
} catch (InterruptedException e) {
// ignore
} finally {
cotxMenu.removeEventFilter(WindowEvent.
WINDOW_SHOWN, filter);
}
}
if (comp.getContextMenu().isShowing()) {
MenuTester tester = new MenuTester();
tester.setComponent(cotxMenu);
return tester;
}
throw new StepExecutionException("Popup could not be opened", //$NON-NLS-1$
EventFactory
.createActionError(TestErrorEvent.POPUP_NOT_FOUND));
}
@Override
public AbstractMenuTester showPopup(int button) {
Point currentMousePosition = getRobot().getCurrentMousePosition();
Point2D mousePos = new Point2D(currentMousePosition.x,
currentMousePosition.y);
boolean widgetContainsCurrentPos =
EventThreadQueuerJavaFXImpl.invokeAndWait("showPopup", //$NON-NLS-1$
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return NodeBounds.checkIfContains(mousePos,
getRealComponent());
}
});
if (widgetContainsCurrentPos) {
Point2D local = EventThreadQueuerJavaFXImpl.invokeAndWait(
"showPopup", new Callable<Point2D>() { //$NON-NLS-1$
@Override
public Point2D call() throws Exception {
return getRealComponent().screenToLocal(mousePos);
}
});
return showPopup(Rounding.round(local.getX()),
ValueSets.Unit.pixel.rcValue(),
Rounding.round(local.getY()),
ValueSets.Unit.pixel.rcValue(), button);
}
return showPopup(50, ValueSets.Unit.percent.rcValue(), 50,
ValueSets.Unit.percent.rcValue(), button);
}
@Override
public void showToolTip(String text, int textSize, int timePerWord,
int windowWidth) {
StepExecutionException.throwUnsupportedAction();
}
@Override
public void rcDrag(int mouseButton, String modifier, int xPos,
String xUnits, int yPos, String yUnits) {
final DragAndDropHelper dndHelper = DragAndDropHelper.getInstance();
dndHelper.setMouseButton(mouseButton);
dndHelper.setModifier(modifier);
final IRobot robot = getRobot();
clickDirect(0, mouseButton, xPos, xUnits, yPos, yUnits);
pressOrReleaseModifiers(modifier, true);
robot.mousePress(null, null, mouseButton);
}
@Override
public void rcDrop(int xPos, String xUnits, int yPos, String yUnits,
int delayBeforeDrop) {
final DragAndDropHelper dndHelper = DragAndDropHelper.getInstance();
final String modifier = dndHelper.getModifier();
final int mouseButton = dndHelper.getMouseButton();
try {
clickDirect(0, mouseButton, xPos, xUnits, yPos, yUnits);
TimeUtil.delay(delayBeforeDrop);
} finally {
getRobot().mouseRelease(null, null, mouseButton);
pressOrReleaseModifiers(modifier, false);
}
}
/**
* Presses or releases the given modifier.
*
* @param modifier
* the modifier.
* @param press
* if true, the modifier will be pressed. if false, the modifier
* will be released.
*/
private void pressOrReleaseModifiers(String modifier, boolean press) {
final IRobot robot = getRobot();
final StringTokenizer modTok = new StringTokenizer(
KeyStrokeUtil.getModifierString(modifier), " "); //$NON-NLS-1$
while (modTok.hasMoreTokens()) {
final String mod = modTok.nextToken();
final int keyCode = getKeyCode(mod);
if (press) {
robot.keyPress(null, keyCode);
} else {
robot.keyRelease(null, keyCode);
}
}
}
/**
* clicks into the component.
*
* @param count
* amount of clicks
* @param button
* what mouse button should be used
* @param xPos
* what x position
* @param xUnits
* should x position be pixel or percent values
* @param yPos
* what y position
* @param yUnits
* should y position be pixel or percent values
* @throws StepExecutionException
* error
*/
private void clickDirect(int count, int button, int xPos, String xUnits,
int yPos, String yUnits) throws StepExecutionException {
getRobot().click(
getRealComponent(),
null,
ClickOptions.create().setClickCount(count)
.setMouseButton(button), xPos,
xUnits.equalsIgnoreCase(ValueSets.Unit.pixel.rcValue()),
yPos,
yUnits.equalsIgnoreCase(ValueSets.Unit.pixel.rcValue()));
}
@Override
public int getKeyCode(String key) {
if (key == null) {
throw new RobotException("Key is null!", //$NON-NLS-1$
EventFactory.createConfigErrorEvent());
}
final Integer keyCode = converterTable.get(key.toLowerCase());
if (keyCode == null) {
throw new RobotException("No KeyCode found for key '" + key + "'", //$NON-NLS-1$//$NON-NLS-2$
EventFactory.createConfigErrorEvent());
}
return keyCode.intValue();
}
@Override
public ReadOnlyObjectProperty<Window> getWindow() {
return getRealComponent().getScene().windowProperty();
}
}