/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2012-2015 the original author or authors.
*/
package org.assertj.swing.core;
import static org.assertj.core.util.Preconditions.checkNotNull;
import static org.assertj.swing.exception.ActionFailedException.actionFailure;
import static org.assertj.swing.timing.Pause.pause;
import static org.assertj.swing.util.Platform.isMacintosh;
import static org.assertj.swing.util.Platform.isWindows;
import static org.assertj.swing.util.TimeoutWatch.startWatchWithTimeoutOf;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import javax.annotation.Nonnull;
import org.assertj.swing.annotation.RunsInEDT;
import org.assertj.swing.util.TimeoutWatch;
/**
* Simulates a user performing drag-and-drop.
*
* @author Alex Ruiz
*/
public class ComponentDragAndDrop {
private final Robot robot;
/**
* Creates a new {@link ComponentDragAndDrop}.
*
* @param robot the robot to use to simulate user input.
*/
public ComponentDragAndDrop(@Nonnull Robot robot) {
this.robot = robot;
}
/** Number of pixels traversed before a drag starts. */
public static final int DRAG_THRESHOLD = isWindows() || isMacintosh() ? 10 : 16;
/**
* Performs a drag action at the given location.
*
* @param target the target AWT or Swing {@code Component}.
* @param where the point where to start the drag action.
*/
@RunsInEDT
public void drag(@Nonnull Component target, @Nonnull Point where) {
robot.pressMouse(target, where, robot.settings().dragButton());
int dragDelay = settings().dragDelay();
if (dragDelay > delayBetweenEvents()) {
pause(dragDelay);
}
mouseMove(target, where.x, where.y);
robot.waitForIdle();
}
private void mouseMove(@Nonnull Component target, int x, int y) {
if (isWindows() || isMacintosh()) {
mouseMoveOnWindowsAndMacintosh(target, x, y);
return;
}
mouseMove(target, point(x + DRAG_THRESHOLD / 2, y + DRAG_THRESHOLD / 2),
point(x + DRAG_THRESHOLD, y + DRAG_THRESHOLD), point(x + DRAG_THRESHOLD / 2, y + DRAG_THRESHOLD / 2),
point(x, y));
}
@RunsInEDT
private void mouseMoveOnWindowsAndMacintosh(@Nonnull Component target, int x, int y) {
Dimension size = target.getSize();
int dx = distance(x, size.width);
int dy = distance(y, size.height);
if (dx == 0 && dy == 0) {
dx = DRAG_THRESHOLD;
}
mouseMove(target, point(x + dx / 4, y + dy / 4), point(x + dx / 2, y + dy / 2), point(x + dx, y + dy),
point(x + dx + 1, y + dy));
}
private int distance(int coordinate, int dimension) {
return coordinate + DRAG_THRESHOLD < dimension ? DRAG_THRESHOLD : 0;
}
private @Nonnull Point point(int x, int y) {
return new Point(x, y);
}
/**
* <p>
* Ends a drag operation, releasing the mouse button over the given target location.
* </p>
*
* <p>
* This method is tuned for native drag/drop operations, so if you get odd behavior, you might try using a simple
* {@link Robot#moveMouse(Component, int, int)} and {@link Robot#releaseMouseButtons()}.
*
* @param target the target AWT or Swing {@code Component}.
* @param where the point where the drag operation ends.
* @throws org.assertj.swing.exception.ActionFailedException if there is no drag action in effect.
*/
@RunsInEDT
public void drop(@Nonnull Component target, @Nonnull Point where) {
dragOver(target, where);
TimeoutWatch watch = startWatchWithTimeoutOf(settings().eventPostingDelay() * 4);
while (!robot.isDragging()) {
if (watch.isTimeOut()) {
throw actionFailure("There is no drag in effect");
}
pause();
}
int dropDelay = settings().dropDelay();
int delayBetweenEvents = delayBetweenEvents();
if (dropDelay > delayBetweenEvents) {
pause(dropDelay - delayBetweenEvents);
}
robot.releaseMouseButtons();
robot.waitForIdle();
}
private int delayBetweenEvents() {
return settings().delayBetweenEvents();
}
private @Nonnull Settings settings() {
return robot.settings();
}
/**
* Move the mouse appropriately to get from the source to the destination. Enter/exit events will be generated where
* appropriate.
*
* @param target the target AWT or Swing {@code Component}.
* @param where the point to drag over.
*/
public void dragOver(@Nonnull Component target, @Nonnull Point where) {
dragOver(target, where.x, where.y);
}
private void dragOver(@Nonnull Component target, int x, int y) {
robot.moveMouse(target, x - 4, y);
robot.moveMouse(target, x, y);
}
private void mouseMove(@Nonnull Component target, @Nonnull Point... points) {
for (Point p : points) {
checkNotNull(p);
robot.moveMouse(target, p.x, p.y);
}
}
}