1 /*
   2  * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /**
  27  * JRobot is a wrapper around java.awt.Robot that provides some convenience
  28  * methods.
  29  * <p>When using jtreg you would include this class via something like:
  30  * <pre>
  31  * @library ../../../regtesthelpers
  32  * @build JRobot
  33  * </pre>
  34  *
  35  */
  36 import java.awt.AWTException;
  37 import java.awt.Component;
  38 import java.awt.Dimension;
  39 import java.awt.EventQueue;
  40 import java.awt.Point;
  41 import java.awt.Rectangle;
  42 import java.awt.event.InputEvent;
  43 import java.awt.event.KeyEvent;
  44 import javax.swing.SwingUtilities;
  45 
  46 public class JRobot extends java.awt.Robot {
  47     private static int DEFAULT_DELAY = 550;
  48     private static int INTERNAL_DELAY = 250;
  49 
  50     private int delay;
  51     private boolean delaysEnabled;
  52 
  53     protected JRobot(boolean enableDelays) throws AWTException {
  54         super();
  55         delaysEnabled = enableDelays;
  56         setAutoWaitForIdle(enableDelays);
  57         if (enableDelays) {
  58             setAutoDelay(INTERNAL_DELAY);
  59             setDelay(DEFAULT_DELAY);
  60         }
  61     }
  62 
  63     /**
  64      * Return a JRobot. Delays are enabled by default.
  65      * @return a JRobot
  66      */
  67     public static JRobot getRobot() {
  68         return getRobot(true);
  69     }
  70 
  71     /**
  72      * Create a JRobot. The parameter controls whether delays are enabled.
  73      * @param enableDelays controls whether delays are enabled.
  74      * @return a JRobot
  75      */
  76     public static JRobot getRobot(boolean enableDelays) {
  77         JRobot robot = null;
  78         try {
  79             robot = new JRobot(enableDelays);
  80         } catch (AWTException e) {
  81             System.err.println("Coudn't create Robot, details below");
  82             throw new Error(e);
  83         }
  84         return robot;
  85     }
  86 
  87     /**
  88      * Press and release a key.
  89      * @param keycode which key to press. For example, KeyEvent.VK_DOWN
  90      */
  91     public void hitKey(int keycode) {
  92         keyPress(keycode);
  93         keyRelease(keycode);
  94         delay();
  95     }
  96 
  97     /**
  98      * Press and release a key with modifiers.
  99      * @param keys keys to press. Keys are pressed in order they are passed as
 100      * parameters to this method. All keys except the last one are considered
 101      * modifiers. For example, to press Ctrl+Shift+T, call:
 102      * hitKey(KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT, KeyEvent.VK_T);
 103      */
 104     public void hitKey(int... keys) {
 105         for (int i = 0; i < keys.length; i++) {
 106             keyPress(keys[i]);
 107         }
 108 
 109         for (int i = keys.length - 1; i >= 0; i--) {
 110             keyRelease(keys[i]);
 111         }
 112         delay();
 113     }
 114 
 115     /**
 116      * Move mouse cursor to the center of the Component.
 117      * @param c Component the mouse is placed over
 118      */
 119     public void moveMouseTo(Component c) {
 120         Point p = c.getLocationOnScreen();
 121         Dimension size = c.getSize();
 122         p.x += size.width / 2;
 123         p.y += size.height / 2;
 124         mouseMove(p.x, p.y);
 125         delay();
 126     }
 127 
 128     /**
 129      * Move mouse smoothly from (x0, y0) to (x1, y1).
 130      */
 131     public void glide(int x0, int y0, int x1, int y1) {
 132         float dmax = (float)Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0));
 133         float dx = (x1 - x0) / dmax;
 134         float dy = (y1 - y0) / dmax;
 135 
 136         mouseMove(x0, y0);
 137         for (int i=1; i<=dmax; i++) {
 138             mouseMove((int)(x0 + dx*i), (int)(y0 + dy*i));
 139         }
 140         delay();
 141     }
 142 
 143     /**
 144      * Perform a mouse click, i.e. press and release mouse button(s).
 145      * @param buttons mouse button(s).
 146      *                For example, MouseEvent.BUTTON1_MASK
 147      */
 148     public void clickMouse(int buttons) {
 149         mousePress(buttons);
 150         mouseRelease(buttons);
 151         delay();
 152     }
 153 
 154     /**
 155      * Perform a click with the first mouse button.
 156      */
 157     public void clickMouse() {
 158         clickMouse(InputEvent.BUTTON1_MASK);
 159     }
 160 
 161     /**
 162      * Click in the center of the given Component
 163      * @param c the Component to click on
 164      * @param buttons mouse button(s).
 165      */
 166     public void clickMouseOn(Component c, int buttons) {
 167         moveMouseTo(c);
 168         clickMouse(buttons);
 169     }
 170 
 171     /**
 172      * Click the first mouse button in the center of the given Component
 173      * @param c the Component to click on
 174      */
 175     public void clickMouseOn(Component c) {
 176         clickMouseOn(c, InputEvent.BUTTON1_MASK);
 177     }
 178 
 179     /**
 180      * Return whether delays are enabled
 181      * @return whether delays are enabled
 182      */
 183     public boolean getDelaysEnabled() {
 184         return delaysEnabled;
 185     }
 186 
 187     /**
 188      * Delay execution by delay milliseconds
 189      */
 190     public void delay() {
 191         delay(delay);
 192     }
 193 
 194     /**
 195      * Return the delay amount, in milliseconds
 196      */
 197     public int getDelay() {
 198         return delay;
 199     }
 200 
 201     /**
 202      * Set the delay amount, in milliseconds
 203      */
 204     public void setDelay(int delay) {
 205         this.delay = delay;
 206     }
 207 
 208     /**
 209      * Waits until all events currently on the event queue have been processed.
 210      * Does nothing if called on EDT
 211      */
 212     public synchronized void waitForIdle() {
 213         if (!EventQueue.isDispatchThread()) {
 214             super.waitForIdle();
 215         }
 216     }
 217 
 218     /**
 219      * Calculate the center of the Rectangle passed, and return them
 220      * in a Point object.
 221      * @param r a non-null Rectangle
 222      * @return a new Point object containing coordinates of r's center
 223      */
 224     public Point centerOf(Rectangle r) {
 225         return new Point(r.x + r.width / 2, r.y + r.height / 2);
 226     }
 227 
 228     /**
 229      * Calculate the center of the Rectangle passed, and store it in p.
 230      * @param r a non-null Rectangle
 231      * @param p a non-null Point that receives coordinates of r's center
 232      * @return p
 233      */
 234     public Point centerOf(Rectangle r, Point p) {
 235         p.x = r.x + r.width / 2;
 236         p.y = r.y + r.height / 2;
 237         return p;
 238     }
 239 
 240     /**
 241      * Convert a rectangle from coordinate system of Component c to
 242      * screen coordinate system.
 243      * @param r a non-null Rectangle
 244      * @param c a Component whose coordinate system is used for conversion
 245      */
 246     public void convertRectToScreen(Rectangle r, Component c) {
 247         Point p = new Point(r.x, r.y);
 248         SwingUtilities.convertPointToScreen(p, c);
 249         r.x = p.x;
 250         r.y = p.y;
 251     }
 252 
 253     /**
 254      * Compares two rectangles pixel-by-pixel.
 255      * @param r0 the first area
 256      * @param r1 the second area
 257      * return true if all pixels in the two areas are identical
 258      */
 259     public boolean compareRects(Rectangle r0, Rectangle r1) {
 260         int xShift = r1.x - r0.x;
 261         int yShift = r1.y - r0.y;
 262 
 263         for (int y = r0.y; y < r0.y + r0.height; y++) {
 264             for (int x = r0.x; x < r0.x + r0.width; x++) {
 265                 if (!comparePixels(x, y, x + xShift, y + yShift)) {
 266                     return false;
 267                 }
 268             }
 269         }
 270         return true;
 271     }
 272 
 273     /**
 274      * Compares colors of two points on the screen.
 275      * @param p0 the first point
 276      * @param p1 the second point
 277      * return true if the two points have the same color
 278      */
 279     public boolean comparePixels(Point p0, Point p1) {
 280         return comparePixels(p0.x, p0.y, p1.x, p1.y);
 281     }
 282 
 283     /**
 284      * Compares colors of two points on the screen.
 285      * @param x0 the x coordinate of the first point
 286      * @param y0 the y coordinate of the first point
 287      * @param x1 the x coordinate of the second point
 288      * @param y1 the y coordinate of the second point
 289      * return true if the two points have the same color
 290      */
 291     public boolean comparePixels(int x0, int y0, int x1, int y1) {
 292         return (getPixelColor(x0, y0).equals(getPixelColor(x1, y1)));
 293     }
 294 }