| /* | |
| * Copyright (c) 2007, 2023, 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. | |
| * | |
| * 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.Component; | |
| import java.awt.EventQueue; | |
| import java.awt.Point; | |
| import java.awt.Rectangle; | |
| import java.awt.event.InputEvent; | |
| import java.util.concurrent.atomic.AtomicReference; | |
| import java.lang.reflect.InvocationTargetException; | |
| import javax.swing.SwingUtilities; | |
| /** | |
| * JRobot is a wrapper around java.awt.Robot that provides some convenience | |
| * methods. | |
| * <p>When using jtreg you would include this class via something like: | |
| * <pre>{@code | |
| * @library ../../../regtesthelpers | |
| * @build JRobot | |
| * }</pre> | |
| */ | |
| public class JRobot extends java.awt.Robot { | |
| private static final int DEFAULT_DELAY = 550; | |
| private static final int INTERNAL_DELAY = 250; | |
| private int delay; | |
| private boolean delaysEnabled; | |
| protected JRobot(boolean enableDelays) throws AWTException { | |
| super(); | |
| delaysEnabled = enableDelays; | |
| setAutoWaitForIdle(enableDelays); | |
| if (enableDelays) { | |
| setAutoDelay(INTERNAL_DELAY); | |
| setDelay(DEFAULT_DELAY); | |
| } | |
| } | |
| /** | |
| * Return a JRobot. Delays are enabled by default. | |
| * @return a JRobot | |
| */ | |
| public static JRobot getRobot() { | |
| return getRobot(true); | |
| } | |
| /** | |
| * Create a JRobot. The parameter controls whether delays are enabled. | |
| * @param enableDelays controls whether delays are enabled. | |
| * @return a JRobot | |
| */ | |
| public static JRobot getRobot(boolean enableDelays) { | |
| JRobot robot = null; | |
| try { | |
| robot = new JRobot(enableDelays); | |
| } catch (AWTException e) { | |
| System.err.println("Coudn't create Robot, details below"); | |
| throw new Error(e); | |
| } | |
| return robot; | |
| } | |
| /** | |
| * Press and release a key. | |
| * @param keycode which key to press. For example, KeyEvent.VK_DOWN | |
| */ | |
| public void hitKey(int keycode) { | |
| keyPress(keycode); | |
| keyRelease(keycode); | |
| delay(); | |
| } | |
| /** | |
| * Press and release a key with modifiers. | |
| * @param keys keys to press. Keys are pressed in order they are passed as | |
| * parameters to this method. All keys except the last one are considered | |
| * modifiers. For example, to press Ctrl+Shift+T, call: | |
| * hitKey(KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT, KeyEvent.VK_T); | |
| */ | |
| public void hitKey(int... keys) { | |
| for (int i = 0; i < keys.length; i++) { | |
| keyPress(keys[i]); | |
| } | |
| for (int i = keys.length - 1; i >= 0; i--) { | |
| keyRelease(keys[i]); | |
| } | |
| delay(); | |
| } | |
| /** | |
| * Move mouse cursor to the center of the Component. | |
| * <p> | |
| * <b>Note:</b> This method is executed on EDT. | |
| * | |
| * @param c Component the mouse is placed over | |
| */ | |
| public void moveMouseTo(Component c) { | |
| Point p = getCenterPoint(c); | |
| mouseMove(p.x, p.y); | |
| delay(); | |
| } | |
| private static Point getCenterPoint(Component c) { | |
| AtomicReference<Point> result = new AtomicReference<>(); | |
| if (SwingUtilities.isEventDispatchThread()) { | |
| result.set(getCenterPointImpl(c)); | |
| } else { | |
| try { | |
| SwingUtilities.invokeAndWait(() -> result.set(getCenterPointImpl(c))); | |
| } catch (InterruptedException | InvocationTargetException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| return result.get(); | |
| } | |
| private static Point getCenterPointImpl(Component c) { | |
| return centerOf(new Rectangle(c.getLocationOnScreen(), c.getSize())); | |
| } | |
| /** | |
| * Move mouse smoothly from (x0, y0) to (x1, y1). | |
| */ | |
| public void glide(int x0, int y0, int x1, int y1) { | |
| float dmax = (float)Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0)); | |
| float dx = (x1 - x0) / dmax; | |
| float dy = (y1 - y0) / dmax; | |
| mouseMove(x0, y0); | |
| for (int i=1; i<=dmax; i++) { | |
| mouseMove((int)(x0 + dx*i), (int)(y0 + dy*i)); | |
| } | |
| delay(); | |
| } | |
| /** | |
| * Perform a mouse click, i.e. press and release mouse button(s). | |
| * @param buttons mouse button(s). | |
| * For example, MouseEvent.BUTTON1_MASK | |
| */ | |
| public void clickMouse(int buttons) { | |
| mousePress(buttons); | |
| mouseRelease(buttons); | |
| delay(); | |
| } | |
| /** | |
| * Perform a click with the first mouse button. | |
| */ | |
| public void clickMouse() { | |
| clickMouse(InputEvent.BUTTON1_DOWN_MASK); | |
| } | |
| /** | |
| * Click in the center of the given Component. | |
| * <p> | |
| * <b>Note:</b> This method is executed on EDT. | |
| * | |
| * @param c the Component to click on | |
| * @param buttons mouse button(s) | |
| */ | |
| public void clickMouseOn(Component c, int buttons) { | |
| moveMouseTo(c); | |
| clickMouse(buttons); | |
| } | |
| /** | |
| * Click the first mouse button in the center of the given Component | |
| * <p> | |
| * <b>Note:</b> This method is executed on EDT. | |
| * | |
| * @param c the Component to click on | |
| */ | |
| public void clickMouseOn(Component c) { | |
| clickMouseOn(c, InputEvent.BUTTON1_DOWN_MASK); | |
| } | |
| /** | |
| * Return whether delays are enabled | |
| * @return whether delays are enabled | |
| */ | |
| public boolean getDelaysEnabled() { | |
| return delaysEnabled; | |
| } | |
| /** | |
| * Delay execution by delay milliseconds | |
| */ | |
| public void delay() { | |
| delay(delay); | |
| } | |
| /** | |
| * Return the delay amount, in milliseconds | |
| */ | |
| public int getDelay() { | |
| return delay; | |
| } | |
| /** | |
| * Set the delay amount, in milliseconds | |
| */ | |
| public void setDelay(int delay) { | |
| this.delay = delay; | |
| } | |
| /** | |
| * Waits until all events currently on the event queue have been processed. | |
| * Does nothing if called on EDT | |
| */ | |
| public synchronized void waitForIdle() { | |
| if (!EventQueue.isDispatchThread()) { | |
| super.waitForIdle(); | |
| } | |
| } | |
| /** | |
| * Calculate the center of the Rectangle passed, and return them | |
| * in a Point object. | |
| * @param r a non-null Rectangle | |
| * @return a new Point object containing coordinates of r's center | |
| */ | |
| public static Point centerOf(Rectangle r) { | |
| return centerOf(r, new Point()); | |
| } | |
| /** | |
| * Calculate the center of the Rectangle passed, and store it in p. | |
| * @param r a non-null Rectangle | |
| * @param p a non-null Point that receives coordinates of r's center | |
| * @return p | |
| */ | |
| public static Point centerOf(Rectangle r, Point p) { | |
| p.x = r.x + r.width / 2; | |
| p.y = r.y + r.height / 2; | |
| return p; | |
| } | |
| /** | |
| * Convert a rectangle from coordinate system of Component c to | |
| * screen coordinate system. | |
| * <p> | |
| * <b>Note:</b> This method is executed on EDT. | |
| * | |
| * @param r a non-null Rectangle | |
| * @param c a Component whose coordinate system is used for conversion | |
| */ | |
| public void convertRectToScreen(Rectangle r, Component c) { | |
| AtomicReference<Point> p = new AtomicReference<>(); | |
| if (SwingUtilities.isEventDispatchThread()) { | |
| p.set(c.getLocationOnScreen()); | |
| } else { | |
| try { | |
| SwingUtilities.invokeAndWait(() -> p.set(c.getLocationOnScreen())); | |
| } catch (InterruptedException | InvocationTargetException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| r.setLocation(p.get()); | |
| } | |
| /** | |
| * Compares two rectangles pixel-by-pixel. | |
| * @param r0 the first area | |
| * @param r1 the second area | |
| * return true if all pixels in the two areas are identical | |
| */ | |
| public boolean compareRects(Rectangle r0, Rectangle r1) { | |
| int xShift = r1.x - r0.x; | |
| int yShift = r1.y - r0.y; | |
| for (int y = r0.y; y < r0.y + r0.height; y++) { | |
| for (int x = r0.x; x < r0.x + r0.width; x++) { | |
| if (!comparePixels(x, y, x + xShift, y + yShift)) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Compares colors of two points on the screen. | |
| * @param p0 the first point | |
| * @param p1 the second point | |
| * return true if the two points have the same color | |
| */ | |
| public boolean comparePixels(Point p0, Point p1) { | |
| return comparePixels(p0.x, p0.y, p1.x, p1.y); | |
| } | |
| /** | |
| * Compares colors of two points on the screen. | |
| * @param x0 the x coordinate of the first point | |
| * @param y0 the y coordinate of the first point | |
| * @param x1 the x coordinate of the second point | |
| * @param y1 the y coordinate of the second point | |
| * return true if the two points have the same color | |
| */ | |
| public boolean comparePixels(int x0, int y0, int x1, int y1) { | |
| return (getPixelColor(x0, y0).equals(getPixelColor(x1, y1))); | |
| } | |
| } |