/*******************************************************************************
* Copyright (c) 2014 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.Rectangle;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.Slider;
import javafx.util.StringConverter;
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.StepExecutionException;
import org.eclipse.jubula.rc.common.tester.adapter.interfaces.ISliderComponent;
import org.eclipse.jubula.rc.common.util.MatchUtil;
import org.eclipse.jubula.rc.javafx.driver.EventThreadQueuerJavaFXImpl;
import org.eclipse.jubula.toolkit.enums.ValueSets;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.javafx.scene.control.skin.SliderSkin;
/**
* Slider Adapter
*
* @author BREDEX GmbH
*/
public class SliderAdapter extends
JavaFXComponentAdapter<Slider> implements ISliderComponent {
/** logger */
private static final Logger LOG = LoggerFactory.getLogger(
SliderAdapter.class);
/**
* Creates an object with the adapted Slider.
*
* @param objectToAdapt
* this must be an object of the Type <code>Slider</code>
*/
public SliderAdapter(Slider objectToAdapt) {
super(objectToAdapt);
}
@Override
public String getPosition(String units) {
return EventThreadQueuerJavaFXImpl.invokeAndWait("getPosition", //$NON-NLS-1$
(Callable<String>) () -> {
double value;
double absValue = getRealComponent().getValue();
if (units.equalsIgnoreCase(
ValueSets.Measure.percent.rcValue())) {
value = 100 * absValue / getRealComponent().getMax();
} else {
StringConverter<Double> labelFormatter = getRealComponent()
.getLabelFormatter();
if (labelFormatter != null) {
return labelFormatter.toString(absValue);
}
value = absValue;
}
return String.valueOf(value);
});
}
/**
* tries to set the position via drag & drop
* @param units the units (percent or value)
* @param value the value
*/
private void setPositionViaDragAndDrop(String units, Double value) {
Slider slider = getRealComponent();
double min = slider.getMin();
double max = slider.getMax();
double currentRelPos = 100 * (slider.getValue() - min) / (max - min);
double futureRelPos = value;
if (ValueSets.Measure.value.rcValue().equalsIgnoreCase(units)) {
futureRelPos = 100 * (value - min) / (max - min);
}
final DragAndDropHelper dndHelper = DragAndDropHelper.getInstance();
int mouseButton = ValueSets.InteractionMode.primary.rcIntValue();
dndHelper.setMouseButton(mouseButton);
final IRobot<Rectangle> robot = getRobot();
boolean horizontal = slider.getOrientation().equals(
Orientation.HORIZONTAL);
if (horizontal) {
moveHorizontal(currentRelPos, mouseButton);
} else {
moveVertical(currentRelPos, mouseButton);
}
robot.mousePress(null, null, mouseButton);
try {
if (horizontal) {
// This is a workaround for problems which lead to leaving behind the button
moveHorizontal(futureRelPos - 2.0, mouseButton);
TimeUtil.delay(200);
moveHorizontal(futureRelPos, mouseButton);
TimeUtil.delay(200);
moveHorizontal(futureRelPos + 2.0, mouseButton);
TimeUtil.delay(200);
moveHorizontal(futureRelPos, mouseButton);
} else {
moveVertical(futureRelPos - 2.0, mouseButton);
TimeUtil.delay(200);
moveVertical(futureRelPos, mouseButton);
TimeUtil.delay(200);
moveVertical(futureRelPos + 2.0, mouseButton);
TimeUtil.delay(200);
moveVertical(futureRelPos, mouseButton);
}
TimeUtil.delay(200);
} finally {
getRobot().mouseRelease(null, null, mouseButton);
}
}
/**
*
* @param targetPos target position
* @param mouseButton mouse Button
*/
private void moveHorizontal(double targetPos, int mouseButton) {
getRobot().click(
getTrack(),
null,
ClickOptions.create().setClickCount(0)
.setMouseButton(mouseButton),
(int)Math.round(targetPos), false, 50, false);
}
/**
*
* @param targetPos target position
* @param mouseButton mouse Button
*/
private void moveVertical(double targetPos, int mouseButton) {
getRobot().click(
getTrack(),
null,
ClickOptions.create().setClickCount(0)
.setMouseButton(mouseButton),
50, false, 100 - (int)Math.round(targetPos), false);
}
/**
* @return the track of the slider
*/
private Node getTrack() {
SliderSkin skin = (SliderSkin)getRealComponent().getSkin();
try {
Field trackField = skin.getClass().getDeclaredField("track"); //$NON-NLS-1$
trackField.setAccessible(true);
Node track = (Node) trackField.get(skin);
return track;
} catch (NoSuchFieldException | SecurityException
| IllegalArgumentException | IllegalAccessException e) {
throw new StepExecutionException("Track not found", //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.NOT_FOUND));
}
}
/** {@inheritDoc} */
public void setPosition(String position, String operator,
String units) {
Slider slider = getRealComponent();
if (slider.isDisabled()) {
throw new StepExecutionException(
"The slider is not enabled", EventFactory //$NON-NLS-1$
.createActionError("The slider is not enabled")); //$NON-NLS-1$
}
Double value = null;
StringConverter<Double> labelFormatter = null;
try {
value = Double.valueOf(position);
} catch (NumberFormatException nfe) {
labelFormatter = slider.getLabelFormatter();
}
if (ValueSets.Operator.equals.rcValue().equalsIgnoreCase(operator)) {
if (labelFormatter != null) {
value = labelFormatter.fromString(position);
}
if (value == null) {
throwInvalidValueMessage();
}
} else {
// If the operator is not "equals" we have to iterate over the slider's
// values as good as possible and try to match the desired position.
// For that we jump from minor tick to minor tick.
double min = slider.getMin();
double max = slider.getMax();
double incr = slider.getMajorTickUnit()
/ (slider.getMinorTickCount() + 1);
MatchUtil matcher = MatchUtil.getInstance();
for (double val = min; val <= max; val += incr) {
String stringToMatch;
if (labelFormatter != null) {
stringToMatch = labelFormatter.toString(val);
} else {
stringToMatch = String.valueOf(val);
}
if (matcher.match(stringToMatch, position, operator)) {
value = val;
break;
}
}
}
try {
setPositionViaDragAndDrop(units, value);
} catch (StepExecutionException ste) {
setValueProgrammatically(units, value);
}
}
/** throws message that input was invalid */
private void throwInvalidValueMessage() {
throw new StepExecutionException("Invalid input for slider", //$NON-NLS-1$
EventFactory.createActionError(
TestErrorEvent.INVALID_INPUT));
}
/**
* @param units the units
* @param value the value
*/
private void setValueProgrammatically(String units, double value) {
double absValue;
Slider slider = getRealComponent();
if (units.equalsIgnoreCase(
ValueSets.Measure.percent.rcValue())) {
if (value < 0 || 100 < value) {
throwInvalidValueMessage();
}
absValue = slider.getMin() + value
* ((slider.getMax() - slider.getMin())) * 0.01;
} else {
absValue = value;
}
double closestPossibleValue = absValue;
double incr = slider.getMajorTickUnit()
/ (slider.getMinorTickCount() + 1);
double val = slider.getMin();
while (val < absValue) {
val += incr;
}
final double valueToSet;
if (!slider.snapToTicksProperty().get()) {
valueToSet = absValue;
} else {
valueToSet = (val - absValue) <= (absValue - (val - incr))
? val : val - incr;
}
EventThreadQueuerJavaFXImpl.invokeAndWait("setSliderValue", //$NON-NLS-1$
(Callable<Void>) () -> {
slider.setValue(valueToSet);
LOG.warn("Fallback - Value of slider was set programmatically for " //$NON-NLS-1$
+ getRealComponent());
return null;
});
}
}