jdk/test/lib/testlibrary/ExtendedRobot.java
author dermashov
Thu, 30 Oct 2014 15:44:37 +0300
changeset 27506 41ccac32e143
parent 25108 cbf21a81a738
child 35302 e4d2275861c3
permissions -rw-r--r--
8056911: Remove internal API usage from ExtendedRobot class Reviewed-by: serb

/*
 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.GraphicsDevice;
import java.awt.Toolkit;
import java.awt.Point;
import java.awt.MouseInfo;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;

/**
 * ExtendedRobot is a subclass of {@link java.awt.Robot}. It provides some convenience methods that are
 * ought to be moved to {@link java.awt.Robot} class.
 * <p>
 * ExtendedRobot uses delay {@link #getSyncDelay()} to make syncing threads with {@link #waitForIdle()}
 * more stable. This delay can be set once on creating object and could not be changed throughout object
 * lifecycle. Constructor reads vm integer property {@code java.awt.robotdelay} and sets the delay value
 * equal to the property value. If the property was not set 500 milliseconds default value is used.
 * <p>
 * When using jtreg you would include this class via something like:
 * <pre>
 * {@literal @}library ../../../../lib/testlibrary
 * {@literal @}build ExtendedRobot
 * </pre>
 *
 * @author      Dmitriy Ermashov
 * @since       1.9
 */

public class ExtendedRobot extends Robot {

    private static int DEFAULT_SPEED = 20;       // Speed for mouse glide and click
    private static int DEFAULT_SYNC_DELAY = 500; // Default Additional delay for waitForIdle()
    private static int DEFAULT_STEP_LENGTH = 2;  // Step length (in pixels) for mouse glide

    private final int syncDelay = DEFAULT_SYNC_DELAY;

    //TODO: uncomment three lines below after moving functionality to java.awt.Robot
    //{
    //    syncDelay = AccessController.doPrivileged(new GetIntegerAction("java.awt.robotdelay", DEFAULT_SYNC_DELAY));
    //}

    /**
     * Constructs an ExtendedRobot object in the coordinate system of the primary screen.
     *
     * @throws  AWTException if the platform configuration does not allow low-level input
     *          control. This exception is always thrown when
     *          GraphicsEnvironment.isHeadless() returns true
     * @throws  SecurityException if {@code createRobot} permission is not granted
     *
     * @see     java.awt.GraphicsEnvironment#isHeadless
     * @see     SecurityManager#checkPermission
     * @see     java.awt.AWTPermission
     */
    public ExtendedRobot() throws AWTException {
        super();
    }

    /**
     * Creates an ExtendedRobot for the given screen device. Coordinates passed
     * to ExtendedRobot method calls like mouseMove and createScreenCapture will
     * be interpreted as being in the same coordinate system as the specified screen.
     * Note that depending on the platform configuration, multiple screens may either:
     * <ul>
     * <li>share the same coordinate system to form a combined virtual screen</li>
     * <li>use different coordinate systems to act as independent screens</li>
     * </ul>
     * This constructor is meant for the latter case.
     * <p>
     * If screen devices are reconfigured such that the coordinate system is
     * affected, the behavior of existing ExtendedRobot objects is undefined.
     *
     * @param   screen  A screen GraphicsDevice indicating the coordinate
     *                  system the Robot will operate in.
     * @throws  AWTException if the platform configuration does not allow low-level input
     *          control. This exception is always thrown when
     *          GraphicsEnvironment.isHeadless() returns true.
     * @throws  IllegalArgumentException if {@code screen} is not a screen
     *          GraphicsDevice.
     * @throws  SecurityException if {@code createRobot} permission is not granted
     *
     * @see     java.awt.GraphicsEnvironment#isHeadless
     * @see     GraphicsDevice
     * @see     SecurityManager#checkPermission
     * @see     java.awt.AWTPermission
     */
    public ExtendedRobot(GraphicsDevice screen) throws AWTException {
        super(screen);
    }

    /**
     * Returns delay length for {@link #waitForIdle()} method
     *
     * @return  Current delay value
     *
     * @see     #waitForIdle()
     */
    public int getSyncDelay(){ return this.syncDelay; }

    /**
     * Clicks mouse button(s) by calling {@link java.awt.Robot#mousePress(int)} and
     * {@link java.awt.Robot#mouseRelease(int)} methods
     *
     *
     * @param   buttons The button mask; a combination of one or more mouse button masks.
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button and support for extended mouse buttons is
     *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button that does not exist on the mouse and support for extended
     *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
     *          by Java
     *
     * @see     #mousePress(int)
     * @see     #mouseRelease(int)
     * @see     InputEvent#getMaskForButton(int)
     * @see     Toolkit#areExtraMouseButtonsEnabled()
     * @see     java.awt.event.MouseEvent
     */
    public void click(int buttons) {
        mousePress(buttons);
        waitForIdle(DEFAULT_SPEED);
        mouseRelease(buttons);
        waitForIdle();
    }

    /**
     * Clicks mouse button 1
     *
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button and support for extended mouse buttons is
     *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button that does not exist on the mouse and support for extended
     *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
     *          by Java
     *
     * @see     #click(int)
     */
    public void click() {
        click(InputEvent.BUTTON1_DOWN_MASK);
    }

    /**
     * Waits until all events currently on the event queue have been processed with given
     * delay after syncing threads. It uses more advanced method of synchronizing threads
     * unlike {@link java.awt.Robot#waitForIdle()}
     *
     * @param   delayValue  Additional delay length in milliseconds to wait until thread
     *                      sync been completed
     * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
     *          dispatching thread
     */
    public synchronized void waitForIdle(int delayValue) {
        super.waitForIdle();
        delay(delayValue);
    }

    /**
     * Waits until all events currently on the event queue have been processed with delay
     * {@link #getSyncDelay()} after syncing threads. It uses more advanced method of
     * synchronizing threads unlike {@link java.awt.Robot#waitForIdle()}
     *
     * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
     *          dispatching thread
     *
     * @see     #waitForIdle(int)
     */
    @Override
    public synchronized void waitForIdle() {
        waitForIdle(syncDelay);
    }

    /**
     * Move the mouse in multiple steps from where it is
     * now to the destination coordinates.
     *
     * @param   x   Destination point x coordinate
     * @param   y   Destination point y coordinate
     *
     * @see     #glide(int, int, int, int)
     */
    public void glide(int x, int y) {
        Point p = MouseInfo.getPointerInfo().getLocation();
        glide(p.x, p.y, x, y);
    }

    /**
     * Move the mouse in multiple steps from where it is
     * now to the destination point.
     *
     * @param   dest    Destination point
     *
     * @see     #glide(int, int)
     */
    public void glide(Point dest) {
        glide(dest.x, dest.y);
    }

    /**
     * Move the mouse in multiple steps from source coordinates
     * to the destination coordinates.
     *
     * @param   fromX   Source point x coordinate
     * @param   fromY   Source point y coordinate
     * @param   toX     Destination point x coordinate
     * @param   toY     Destination point y coordinate
     *
     * @see     #glide(int, int, int, int, int, int)
     */
    public void glide(int fromX, int fromY, int toX, int toY) {
        glide(fromX, fromY, toX, toY, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
    }

    /**
     * Move the mouse in multiple steps from source point to the
     * destination point with default speed and step length.
     *
     * @param   src     Source point
     * @param   dest    Destination point
     *
     * @see     #glide(int, int, int, int, int, int)
     */
    public void glide(Point src, Point dest) {
        glide(src.x, src.y, dest.x, dest.y, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
    }

    /**
     * Move the mouse in multiple steps from source point to the
     * destination point with given speed and step length.
     *
     * @param   srcX        Source point x cordinate
     * @param   srcY        Source point y cordinate
     * @param   destX       Destination point x cordinate
     * @param   destY       Destination point y cordinate
     * @param   stepLength  Approximate length of one step
     * @param   speed       Delay between steps.
     *
     * @see     #mouseMove(int, int)
     * @see     #delay(int)
     */
     public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int speed) {
        int stepNum;
        double tDx, tDy;
        double dx, dy, ds;
        double x, y;

        dx = (destX - srcX);
        dy = (destY - srcY);
        ds = Math.sqrt(dx*dx + dy*dy);

        tDx = dx / ds * stepLength;
        tDy = dy / ds * stepLength;

        int stepsCount = (int) ds / stepLength;

        // Walk the mouse to the destination one step at a time
        mouseMove(srcX, srcY);

        for (x = srcX, y = srcY, stepNum = 0;
             stepNum < stepsCount;
             stepNum++) {
            x += tDx;
            y += tDy;
            mouseMove((int)x, (int)y);
            delay(speed);
        }

        // Ensure the mouse moves to the right destination.
        // The steps may have led the mouse to a slightly wrong place.
        mouseMove(destX, destY);
    }

    /**
     * Moves mouse pointer to given screen coordinates.
     *
     * @param   position    Target position
     *
     * @see     java.awt.Robot#mouseMove(int, int)
     */
    public synchronized void mouseMove(Point position) {
        mouseMove(position.x, position.y);
    }


    /**
     * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
     * The method successively moves mouse cursor to point with coordinates
     * ({@code fromX}, {@code fromY}), presses mouse button 1, drag mouse to
     * point with coordinates ({@code toX}, {@code toY}) and releases mouse
     * button 1 at last.
     *
     * @param   fromX   Source point x coordinate
     * @param   fromY   Source point y coordinate
     * @param   toX     Destination point x coordinate
     * @param   toY     Destination point y coordinate
     *
     * @see     #mousePress(int)
     * @see     #glide(int, int, int, int)
     */
    public void dragAndDrop(int fromX, int fromY, int toX, int toY){
        mouseMove(fromX, fromY);
        mousePress(InputEvent.BUTTON1_DOWN_MASK);
        waitForIdle();
        glide(toX, toY);
        mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
        waitForIdle();
    }

    /**
     * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
     * The method successively moves mouse cursor to point {@code from},
     * presses mouse button 1, drag mouse to point {@code to} and releases
     * mouse button 1 at last.
     *
     * @param   from    Source point
     * @param   to      Destination point
     *
     * @see     #mousePress(int)
     * @see     #glide(int, int, int, int)
     * @see     #dragAndDrop(int, int, int, int)
     */
    public void dragAndDrop(Point from, Point to){
        dragAndDrop(from.x, from.y, to.x, to.y);
    }

    /**
     * Successively presses and releases a given key.
     * <p>
     * Key codes that have more than one physical key associated with them
     * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
     * left or right shift key) will map to the left key.
     *
     * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
     * @throws  IllegalArgumentException if {@code keycode} is not
     *          a valid key
     *
     * @see     java.awt.Robot#keyPress(int)
     * @see     java.awt.Robot#keyRelease(int)
     * @see     java.awt.event.KeyEvent
     */
    public void type(int keycode) {
        keyPress(keycode);
        waitForIdle(DEFAULT_SPEED);
        keyRelease(keycode);
        waitForIdle(DEFAULT_SPEED);
    }

    /**
     * Types given character
     *
     * @param   c   Character to be typed (e.g. {@code 'a'})
     *
     * @see     #type(int)
     * @see     java.awt.event.KeyEvent
     */
    public void type(char c) {
        type(KeyEvent.getExtendedKeyCodeForChar(c));
    }

    /**
     * Types given array of characters one by one
     *
     * @param   symbols Array of characters to be typed
     *
     * @see     #type(char)
     */
    public void type(char[] symbols) {
        for (int i = 0; i < symbols.length; i++) {
            type(symbols[i]);
        }
    }

    /**
     * Types given string
     *
     * @param   s   String to be typed
     *
     * @see     #type(char[])
     */
    public void type(String s) {
        type(s.toCharArray());
    }
}