/*
* Copyright (c) 2010-2016, Sikuli.org, sikulix.com
* Released under the MIT License.
*
*/
package org.sikuli.script;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.sikuli.android.ADBDevice;
import org.sikuli.android.ADBScreen;
import org.sikuli.basics.Debug;
import org.sikuli.basics.Settings;
import org.sikuli.util.ScreenHighlighter;
/**
* A Region is a rectengular area and lies always completely inside its parent screen
*
*/
public class Region {
static RunTime runTime = RunTime.get();
private static String me = "Region: ";
private static int lvl = 3;
private static void log(int level, String message, Object... args) {
Debug.logx(level, me + message, args);
}
//<editor-fold desc="housekeeping">
/**
* The Screen containing the Region
*/
private IScreen scr;
protected boolean otherScreen = false;
/**
* The ScreenHighlighter for this Region
*/
private ScreenHighlighter overlay = null;
/**
* X-coordinate of the Region
*/
public int x;
/**
* Y-coordinate of the Region
*/
public int y;
/**
* Width of the Region
*/
public int w;
/**
* Height of the Region
*/
public int h;
/**
* Setting, how to react if an image is not found {@link FindFailed}
*/
private FindFailedResponse findFailedResponse = FindFailed.defaultFindFailedResponse;
private Object findFailedHandler = FindFailed.getFindFailedHandler();
private Object imageMissingHandler = FindFailed.getImageMissingHandler();
/**
* Setting {@link Settings}, if exception is thrown if an image is not found
*/
private boolean throwException = Settings.ThrowException;
/**
* Default time to wait for an image {@link Settings}
*/
private double autoWaitTimeout = Settings.AutoWaitTimeout;
private float waitScanRate = Settings.WaitScanRate;
/**
* Flag, if an observer is running on this region {@link Settings}
*/
private boolean observing = false;
private float observeScanRate = Settings.ObserveScanRate;
private int repeatWaitTime = Settings.RepeatWaitTime;
/**
* The {@link Observer} Singleton instance
*/
private Observer regionObserver = null;
/**
* The last found {@link Match} in the Region
*/
private Match lastMatch = null;
/**
* The last found {@link Match}es in the Region
*/
private Iterator<Match> lastMatches = null;
private long lastSearchTime = -1;
private long lastFindTime = -1;
private boolean isScreenUnion = false;
private boolean isVirtual = false;
private long lastSearchTimeRepeat = -1;
/**
* the area constants for use with get()
*/
public static final int NW = 300, NORTH_WEST = NW, TL = NW;
public static final int NM = 301, NORTH_MID = NM, TM = NM;
public static final int NE = 302, NORTH_EAST = NE, TR = NE;
public static final int EM = 312, EAST_MID = EM, RM = EM;
public static final int SE = 322, SOUTH_EAST = SE, BR = SE;
public static final int SM = 321, SOUTH_MID = SM, BM = SM;
public static final int SW = 320, SOUTH_WEST = SW, BL = SW;
public static final int WM = 310, WEST_MID = WM, LM = WM;
public static final int MM = 311, MIDDLE = MM, M3 = MM;
public static final int TT = 200;
public static final int RR = 201;
public static final int BB = 211;
public static final int LL = 210;
public static final int NH = 202, NORTH = NH, TH = NH;
public static final int EH = 221, EAST = EH, RH = EH;
public static final int SH = 212, SOUTH = SH, BH = SH;
public static final int WH = 220, WEST = WH, LH = WH;
public static final int MV = 441, MID_VERTICAL = MV, CV = MV;
public static final int MH = 414, MID_HORIZONTAL = MH, CH = MH;
public static final int M2 = 444, MIDDLE_BIG = M2, C2 = M2;
public static final int EN = NE, EAST_NORTH = NE, RT = TR;
public static final int ES = SE, EAST_SOUTH = SE, RB = BR;
public static final int WN = NW, WEST_NORTH = NW, LT = TL;
public static final int WS = SW, WEST_SOUTH = SW, LB = BL;
/**
* to support a raster over the region
*/
private int rows;
private int cols = 0;
private int rowH = 0;
private int colW = 0;
private int rowHd = 0;
private int colWd = 0;
/**
* {@inheritDoc}
*
* @return the description
*/
@Override
public String toString() {
String scrText = getScreen() == null ? "?" :
"" + (-1 == getScreen().getID() ? "Union" : "" + getScreen().getID());
return String.format("R[%d,%d %dx%d]@S(%s) E:%s, T:%.1f",
x, y, w, h, scrText,
throwException ? "Y" : "N", autoWaitTimeout);
}
/**
* INTERNAL USE ONLY
*
* @return text
*/
public String getIDString() {
return "NonLocal";
}
/**
*
* @return a compact description
*/
public String toStringShort() {
if (isOtherScreen()) {
return String.format("%s, %dx%d", getScreen().getIDString(), w, h);
} else {
String scrText = getScreen() == null ? "?" :
"" + (-1 == getScreen().getID() ? "Union" : getScreen().getID());
return String.format("R[%d,%d %dx%d]@S(%s)", x, y, w, h, scrText);
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="OFF: Specials for scripting environment">
/*
public Object __enter__() {
Debug.error("Region: with(__enter__): Trying to make it a Jython Region for with: usage");
IScriptRunner runner = Settings.getScriptRunner("jython", null, null);
if (runner != null) {
Object[] jyreg = new Object[]{this};
if (runner.doSomethingSpecial("createRegionForWith", jyreg)) {
if (jyreg[0] != null) {
return jyreg[0];
}
}
}
Debug.error("Region: with(__enter__): Sorry, not possible");
return null;
}
public void __exit__(Object type, Object value, Object traceback) {
Debug.error("Region: with(__exit__): Sorry, not a Jython Region and not posssible!");
}
*/
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Initialization">
/**
* INTERNAL USE
*
* @param iscr screen
*/
public void initScreen(IScreen iscr) {
// check given screen first
Rectangle rect, screenRect;
IScreen screen, screenOn;
if (iscr != null) {
if (iscr.isOtherScreen()) {
if (x < 0) {
w = w + x;
x = 0;
}
if (y < 0) {
h = h + y;
y = 0;
}
this.scr = iscr;
this.otherScreen = true;
return;
}
if (iscr.getID() > -1) {
rect = regionOnScreen(iscr);
if (rect != null) {
x = rect.x;
y = rect.y;
w = rect.width;
h = rect.height;
this.scr = iscr;
return;
}
} else {
// is ScreenUnion
return;
}
}
// check all possible screens if no screen was given or the region is not on given screen
// crop to the screen with the largest intersection
screenRect = new Rectangle(0, 0, 0, 0);
screenOn = null;
if (scr == null || !scr.isOtherScreen()) {
for (int i = 0; i < Screen.getNumberScreens(); i++) {
screen = Screen.getScreen(i);
rect = regionOnScreen(screen);
if (rect != null) {
if (rect.width * rect.height > screenRect.width * screenRect.height) {
screenRect = rect;
screenOn = screen;
}
}
}
} else {
rect = regionOnScreen(scr);
if (rect != null) {
if (rect.width * rect.height > screenRect.width * screenRect.height) {
screenRect = rect;
screenOn = scr;
}
}
}
if (screenOn != null) {
x = screenRect.x;
y = screenRect.y;
w = screenRect.width;
h = screenRect.height;
this.scr = screenOn;
} else {
// no screen found
this.scr = null;
Debug.error("Region(%d,%d,%d,%d) outside any screen - subsequent actions might not work as expected", x, y, w, h);
}
}
private Location checkAndSetRemote(Location loc) {
if (!isOtherScreen()) {
return loc;
}
return loc.setOtherScreen(scr);
}
/**
* INTERNAL USE - EXPERIMENTAL if true: this region is not bound to any screen
*
* @param rect rectangle
* @return the current state
*/
public static Region virtual(Rectangle rect) {
Region reg = new Region();
reg.x = rect.x;
reg.y = rect.y;
reg.w = rect.width;
reg.h = rect.height;
reg.setVirtual(true);
reg.scr = Screen.getPrimaryScreen();
return reg;
}
/**
* INTERNAL USE - EXPERIMENTAL if true: this region is not bound to any screen
*
* @return the current state
*/
public boolean isVirtual() {
return isVirtual;
}
/**
* INTERNAL USE - EXPERIMENTAL
*
* @param state if true: this region is not bound to any screen
*/
public void setVirtual(boolean state) {
isVirtual = state;
}
/**
* INTERNAL USE: checks wether this region belongs to a non-Desktop screen
*
* @return true/false
*/
public boolean isOtherScreen() {
return otherScreen;
}
/**
* INTERNAL USE: flags this region as belonging to a non-Desktop screen
*/
public void setOtherScreen() {
otherScreen = true;
}
/**
* INTERNAL USE: flags this region as belonging to a non-Desktop screen
*
* @param aScreen screen
*/
public void setOtherScreen(IScreen aScreen) {
scr = aScreen;
setOtherScreen();
}
/**
* Checks if the Screen contains the Region.
*
* @param screen The Screen in which the Region might be
* @return True, if the Region is on the Screen. False if the Region is not inside the Screen
*/
protected Rectangle regionOnScreen(IScreen screen) {
if (screen == null) {
return null;
}
// get intersection of Region and Screen
Rectangle rect = screen.getRect().intersection(getRect());
// no Intersection, Region is not on the Screen
if (rect.isEmpty()) {
return null;
}
return rect;
}
/**
* Check wether thie Region is contained by any of the available screens
*
* @return true if yes, false otherwise
*/
public boolean isValid() {
if (this instanceof Screen) {
return true;
}
return scr != null && w != 0 && h != 0;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Constructors to be used with Jython">
/**
* Create a region with the provided coordinate / size and screen
*
* @param X X position
* @param Y Y position
* @param W width
* @param H heigth
* @param screenNumber The number of the screen containing the Region
*/
public Region(int X, int Y, int W, int H, int screenNumber) {
this(X, Y, W, H, Screen.getScreen(screenNumber));
this.rows = 0;
}
/**
* Create a region with the provided coordinate / size and screen
*
* @param X X position
* @param Y Y position
* @param W width
* @param H heigth
* @param parentScreen the screen containing the Region
*/
public Region(int X, int Y, int W, int H, IScreen parentScreen) {
this.rows = 0;
this.x = X;
this.y = Y;
this.w = W > 1 ? W : 1;
this.h = H > 1 ? H : 1;
initScreen(parentScreen);
}
/**
* Convenience: a minimal Region to be used as a Point (backport from Version 2)<br>
* is always on primary screen
* @param X
* @param Y
*/
public Region (int X, int Y) {
this(X, Y, 1, 1, null);
}
/**
* Create a region with the provided coordinate / size
*
* @param X X position
* @param Y Y position
* @param W width
* @param H heigth
*/
public Region(int X, int Y, int W, int H) {
this(X, Y, W, H, null);
this.rows = 0;
log(lvl, "init: (%d, %d, %d, %d)", X, Y, W, H);
}
/**
* Create a region from a Rectangle
*
* @param r the Rectangle
*/
public Region(Rectangle r) {
this(r.x, r.y, r.width, r.height, null);
this.rows = 0;
}
/**
* Create a new region from another region<br>including the region's settings
*
* @param r the region
*/
public Region(Region r) {
init(r);
}
private void init(Region r) {
if (!r.isValid()) {
return;
}
x = r.x;
y = r.y;
w = r.w;
h = r.h;
scr = r.getScreen();
otherScreen = r.isOtherScreen();
rows = 0;
autoWaitTimeout = r.autoWaitTimeout;
findFailedResponse = r.findFailedResponse;
throwException = r.throwException;
waitScanRate = r.waitScanRate;
observeScanRate = r.observeScanRate;
repeatWaitTime = r.repeatWaitTime;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Quasi-Constructors to be used in Java">
/**
* internal use only, used for new Screen objects to get the Region behavior
*/
protected Region() {
this.rows = 0;
}
/**
* internal use only, used for new Screen objects to get the Region behavior
*/
protected Region(boolean isScreenUnion) {
this.isScreenUnion = isScreenUnion;
this.rows = 0;
}
/**
* Create a region with the provided top left corner and size
*
* @param X top left X position
* @param Y top left Y position
* @param W width
* @param H heigth
* @return then new region
*/
public static Region create(int X, int Y, int W, int H) {
return Region.create(X, Y, W, H, null);
}
/**
* Create a region with the provided top left corner and size
*
* @param X top left X position
* @param Y top left Y position
* @param W width
* @param H heigth
* @param scr the source screen
* @return the new region
*/
private static Region create(int X, int Y, int W, int H, IScreen scr) {
return new Region(X, Y, W, H, scr);
}
/**
* Create a region with the provided top left corner and size
*
* @param loc top left corner
* @param w width
* @param h height
* @return then new region
*/
public static Region create(Location loc, int w, int h) {
int _x = loc.x;
int _y = loc.y;
IScreen s = loc.getScreen();
if (s == null) {
_x = _y = 0;
s = Screen.getPrimaryScreen();
}
return Region.create(_x, _y, w, h, s);
}
/**
* Flag for the {@link #create(Location, int, int, int, int)} method. Sets the Location to be on the left corner of
* the new Region.
*/
public final static int CREATE_X_DIRECTION_LEFT = 0;
/**
* Flag for the {@link #create(Location, int, int, int, int)} method. Sets the Location to be on the right corner of
* the new Region.
*/
public final static int CREATE_X_DIRECTION_RIGHT = 1;
/**
* Flag for the {@link #create(Location, int, int, int, int)} method. Sets the Location to be on the top corner of the
* new Region.
*/
public final static int CREATE_Y_DIRECTION_TOP = 0;
/**
* Flag for the {@link #create(Location, int, int, int, int)} method. Sets the Location to be on the bottom corner of
* the new Region.
*/
public final static int CREATE_Y_DIRECTION_BOTTOM = 1;
/**
* create a region with a corner at the given point<br>as specified with x y<br> 0 0 top left<br> 0 1 bottom left<br>
* 1 0 top right<br> 1 1 bottom right<br>
*
* @param loc the refence point
* @param create_x_direction == 0 is left side !=0 is right side
* @param create_y_direction == 0 is top side !=0 is bottom side
* @param w the width
* @param h the height
* @return the new region
*/
public static Region create(Location loc, int create_x_direction, int create_y_direction, int w, int h) {
int _x = loc.x;
int _y = loc.y;
IScreen s = loc.getScreen();
if (s == null) {
_x = _y = 0;
s = Screen.getPrimaryScreen();
}
int X;
int Y;
int W = w;
int H = h;
if (create_x_direction == CREATE_X_DIRECTION_LEFT) {
if (create_y_direction == CREATE_Y_DIRECTION_TOP) {
X = _x;
Y = _y;
} else {
X = _x;
Y = _y - h;
}
} else {
if (create_y_direction == CREATE_Y_DIRECTION_TOP) {
X = _x - w;
Y = _y;
} else {
X = _x - w;
Y = _y - h;
}
}
return Region.create(X, Y, W, H, s);
}
/**
* create a region with a corner at the given point<br>as specified with x y<br> 0 0 top left<br> 0 1 bottom left<br>
* 1 0 top right<br> 1 1 bottom right<br>same as the corresponding create method, here to be naming compatible with
* class Location
*
* @param loc the refence point
* @param x ==0 is left side !=0 is right side
* @param y ==0 is top side !=0 is bottom side
* @param w the width
* @param h the height
* @return the new region
*/
public static Region grow(Location loc, int x, int y, int w, int h) {
return Region.create(loc, x, y, w, h);
}
/**
* Create a region from a Rectangle
*
* @param r the Rectangle
* @return the new region
*/
public static Region create(Rectangle r) {
return Region.create(r.x, r.y, r.width, r.height, null);
}
/**
* Create a region from a Rectangle on a given Screen
*
* @param r the Rectangle
* @param parentScreen the new parent screen
* @return the new region
*/
protected static Region create(Rectangle r, IScreen parentScreen) {
return Region.create(r.x, r.y, r.width, r.height, parentScreen);
}
/**
* Create a region from another region<br>including the region's settings
*
* @param r the region
* @return then new region
*/
public static Region create(Region r) {
Region reg = Region.create(r.x, r.y, r.w, r.h, r.getScreen());
reg.autoWaitTimeout = r.autoWaitTimeout;
reg.findFailedResponse = r.findFailedResponse;
reg.throwException = r.throwException;
return reg;
}
/**
* create a region with the given point as center and the given size
*
* @param loc the center point
* @param w the width
* @param h the height
* @return the new region
*/
public static Region grow(Location loc, int w, int h) {
int _x = loc.x;
int _y = loc.y;
IScreen s = loc.getScreen();
if (s == null) {
_x = _y = 0;
s = Screen.getPrimaryScreen();
}
int X = _x - (int) w / 2;
int Y = _y - (int) h / 2;
return Region.create(X, Y, w, h, s);
}
/**
* create a minimal region at given point with size 1 x 1
*
* @param loc the point
* @return the new region
*/
public static Region grow(Location loc) {
int _x = loc.x;
int _y = loc.y;
IScreen s = loc.getScreen();
if (s == null) {
_x = _y = 0;
s = Screen.getPrimaryScreen();
}
return Region.create(_x, _y, 1, 1, s);
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="handle coordinates">
/**
* check if current region contains given point
*
* @param point Point
* @return true/false
*/
public boolean contains(Location point) {
return getRect().contains(point.x, point.y);
}
/**
* check if mouse pointer is inside current region
*
* @return true/false
*/
public boolean containsMouse() {
return contains(Mouse.at());
}
/**
* new region with same offset to current screen's top left on given screen
*
* @param scrID number of screen
* @return new region
*/
public Region copyTo(int scrID) {
return copyTo(Screen.getScreen(scrID));
}
/**
* new region with same offset to current screen's top left on given screen
*
* @param screen new parent screen
* @return new region
*/
public Region copyTo(IScreen screen) {
Location o = new Location(getScreen().getBounds().getLocation());
Location n = new Location(screen.getBounds().getLocation());
return Region.create(n.x + x - o.x, n.y + y - o.y, w, h, screen);
}
/**
* used in Observer.callChangeObserving, Finder.next to adjust region relative coordinates of matches to screen
* coordinates
*
* @param m
* @return the modified match
*/
protected Match toGlobalCoord(Match m) {
m.x += x;
m.y += y;
return m;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="handle Settings">
//TODO should be possible to reset to current global value resetXXX()
/**
* true - (initial setting) should throw exception FindFailed if findX unsuccessful in this region<br> false - do not
* abort script on FindFailed (might leed to null pointer exceptions later)
*
* @param flag true/false
*/
public void setThrowException(boolean flag) {
throwException = flag;
if (throwException) {
findFailedResponse = FindFailedResponse.ABORT;
} else {
findFailedResponse = FindFailedResponse.SKIP;
}
}
/**
* current setting for this region (see setThrowException)
*
* @return true/false
*/
public boolean getThrowException() {
return throwException;
}
/**
* the time in seconds a find operation should wait for the appearence of the target in this region<br> initial value
* is the global AutoWaitTimeout setting at time of Region creation
*
* @param sec seconds
*/
public void setAutoWaitTimeout(double sec) {
autoWaitTimeout = sec;
}
/**
* current setting for this region (see setAutoWaitTimeout)
*
* @return value of seconds
*/
public double getAutoWaitTimeout() {
return autoWaitTimeout;
}
/**
* FindFailedResponse.<br>
* ABORT - (initial value) abort script on FindFailed (= setThrowException(true) )<br>
* SKIP - ignore FindFailed (same as setThrowException(false) )<br>
* PROMPT - display prompt on FindFailed to let user decide how to proceed<br>
* RETRY - continue to wait for appearence after FindFailed (caution: endless loop)
*
* @param response the FindFailedResponse
*/
public void setFindFailedResponse(FindFailedResponse response) {
findFailedResponse = response;
}
public void setFindFailedHandler(Object handler) {
findFailedHandler = setHandler(handler, ObserveEvent.Type.FINDFAILED);
log(lvl, "Setting FindFailedHandler");
}
public void setImageMissingHandler(Object handler) {
imageMissingHandler = setHandler(handler, ObserveEvent.Type.MISSING);
log(lvl, "Setting ImageMissingHandler");
}
private Object setHandler(Object handler, ObserveEvent.Type type) {
findFailedResponse = FindFailedResponse.HANDLE;
if (handler != null && (handler.getClass().getName().contains("org.python")
|| handler.getClass().getName().contains("org.jruby"))) {
handler = new ObserverCallBack(handler, type);
} else {
((ObserverCallBack) handler).setType(type);
}
return handler;
}
/**
*
* @return the current setting (see setFindFailedResponse)
*/
public FindFailedResponse getFindFailedResponse() {
return findFailedResponse;
}
/**
*
* @return the regions current WaitScanRate
*/
public float getWaitScanRate() {
return waitScanRate;
}
/**
* set the regions individual WaitScanRate
*
* @param waitScanRate decimal number
*/
public void setWaitScanRate(float waitScanRate) {
this.waitScanRate = waitScanRate;
}
/**
*
* @return the regions current ObserveScanRate
*/
public float getObserveScanRate() {
return observeScanRate;
}
/**
* set the regions individual ObserveScanRate
*
* @param observeScanRate decimal number
*/
public void setObserveScanRate(float observeScanRate) {
this.observeScanRate = observeScanRate;
}
/**
* INTERNAL USE: Observe
*
* @return the regions current RepeatWaitTime time in seconds
*/
public int getRepeatWaitTime() {
return repeatWaitTime;
}
/**
* INTERNAL USE: Observe set the regions individual WaitForVanish
*
* @param time in seconds
*/
public void setRepeatWaitTime(int time) {
repeatWaitTime = time;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="getters / setters / modificators">
/**
*
* @return the Screen object containing the region
*/
public IScreen getScreen() {
return scr;
}
// to avoid NPE for Regions being outside any screen
private IRobot getRobotForRegion() {
if (getScreen() == null || isScreenUnion) {
return Screen.getPrimaryScreen().getRobot();
}
return getScreen().getRobot();
}
/**
*
* @return the screen, that contains the top left corner of the region. Returns primary screen if outside of any
* screen.
* @deprecated Only for compatibility, to get the screen containing this region, use {@link #getScreen()}
*/
@Deprecated
public IScreen getScreenContaining() {
return getScreen();
}
/**
* Sets a new Screen for this region.
*
* @param scr the containing screen object
* @return the region itself
*/
protected Region setScreen(IScreen scr) {
initScreen(scr);
return this;
}
/**
* Sets a new Screen for this region.
*
* @param id the containing screen object's id
* @return the region itself
*/
protected Region setScreen(int id) {
return setScreen(Screen.getScreen(id));
}
/**
* synonym for showMonitors
*/
public void showScreens() {
Screen.showMonitors();
}
/**
* synonym for resetMonitors
*/
public void resetScreens() {
Screen.resetMonitors();
}
// ************************************************
/**
*
* @return the center pixel location of the region
*/
public Location getCenter() {
return checkAndSetRemote(new Location(getX() + getW() / 2, getY() + getH() / 2));
}
/**
* convenience method
*
* @return the region's center
*/
public Location getTarget() {
return getCenter();
}
/**
* Moves the region to the area, whose center is the given location
*
* @param loc the location which is the new center of the region
* @return the region itself
*/
public Region setCenter(Location loc) {
Location c = getCenter();
x = x - c.x + loc.x;
y = y - c.y + loc.y;
initScreen(null);
return this;
}
/**
*
* @return top left corner Location
*/
public Location getTopLeft() {
return checkAndSetRemote(new Location(x, y));
}
/**
* Moves the region to the area, whose top left corner is the given location
*
* @param loc the location which is the new top left point of the region
* @return the region itself
*/
public Region setTopLeft(Location loc) {
return setLocation(loc);
}
/**
*
* @return top right corner Location
*/
public Location getTopRight() {
return checkAndSetRemote(new Location(x + w - 1, y));
}
/**
* Moves the region to the area, whose top right corner is the given location
*
* @param loc the location which is the new top right point of the region
* @return the region itself
*/
public Region setTopRight(Location loc) {
Location c = getTopRight();
x = x - c.x + loc.x;
y = y - c.y + loc.y;
initScreen(null);
return this;
}
/**
*
* @return bottom left corner Location
*/
public Location getBottomLeft() {
return checkAndSetRemote(new Location(x, y + h - 1));
}
/**
* Moves the region to the area, whose bottom left corner is the given location
*
* @param loc the location which is the new bottom left point of the region
* @return the region itself
*/
public Region setBottomLeft(Location loc) {
Location c = getBottomLeft();
x = x - c.x + loc.x;
y = y - c.y + loc.y;
initScreen(null);
return this;
}
/**
*
* @return bottom right corner Location
*/
public Location getBottomRight() {
return checkAndSetRemote(new Location(x + w - 1, y + h - 1));
}
/**
* Moves the region to the area, whose bottom right corner is the given location
*
* @param loc the location which is the new bottom right point of the region
* @return the region itself
*/
public Region setBottomRight(Location loc) {
Location c = getBottomRight();
x = x - c.x + loc.x;
y = y - c.y + loc.y;
initScreen(null);
return this;
}
// ************************************************
/**
*
* @return x of top left corner
*/
public int getX() {
return x;
}
/**
*
* @return y of top left corner
*/
public int getY() {
return y;
}
/**
*
* @return width of region
*/
public int getW() {
return w;
}
/**
*
* @return height of region
*/
public int getH() {
return h;
}
/**
*
* @param X new x position of top left corner
*/
public void setX(int X) {
x = X;
initScreen(null);
}
/**
*
* @param Y new y position of top left corner
*/
public void setY(int Y) {
y = Y;
initScreen(null);
}
/**
*
* @param W new width
*/
public void setW(int W) {
w = W > 1 ? W : 1;
initScreen(null);
}
/**
*
* @param H new height
*/
public void setH(int H) {
h = H > 1 ? H : 1;
initScreen(null);
}
// ************************************************
/**
*
* @param W new width
* @param H new height
* @return the region itself
*/
public Region setSize(int W, int H) {
w = W > 1 ? W : 1;
h = H > 1 ? H : 1;
initScreen(null);
return this;
}
/**
*
* @return the AWT Rectangle of the region
*/
public Rectangle getRect() {
return new Rectangle(x, y, w, h);
}
/**
* set the regions position/size<br>this might move the region even to another screen
*
* @param r the AWT Rectangle to use for position/size
* @return the region itself
*/
public Region setRect(Rectangle r) {
return setRect(r.x, r.y, r.width, r.height);
}
/**
* set the regions position/size<br>this might move the region even to another screen
*
* @param X new x of top left corner
* @param Y new y of top left corner
* @param W new width
* @param H new height
* @return the region itself
*/
public Region setRect(int X, int Y, int W, int H) {
x = X;
y = Y;
w = W > 1 ? W : 1;
h = H > 1 ? H : 1;
initScreen(null);
return this;
}
/**
* set the regions position/size<br>this might move the region even to another screen
*
* @param r the region to use for position/size
* @return the region itself
*/
public Region setRect(Region r) {
return setRect(r.x, r.y, r.w, r.h);
}
// ****************************************************
/**
* resets this region (usually a Screen object) to the coordinates of the containing screen
*
* Because of the wanted side effect for the containing screen, this should only be used with screen objects. For
* Region objects use setRect() instead.
*/
public void setROI() {
setROI(getScreen().getBounds());
}
/**
* resets this region to the given location, and size <br> this might move the region even to another screen
*
* <br>Because of the wanted side effect for the containing screen, this should only be used with screen objects.
* <br>For Region objects use setRect() instead.
*
* @param X new x
* @param Y new y
* @param W new width
* @param H new height
*/
public void setROI(int X, int Y, int W, int H) {
x = X;
y = Y;
w = W > 1 ? W : 1;
h = H > 1 ? H : 1;
initScreen(null);
}
/**
* resets this region to the given rectangle <br> this might move the region even to another screen
*
* <br>Because of the wanted side effect for the containing screen, this should only be used with screen objects.
* <br>For Region objects use setRect() instead.
*
* @param r AWT Rectangle
*/
public void setROI(Rectangle r) {
setROI(r.x, r.y, r.width, r.height);
}
/**
* resets this region to the given region <br> this might move the region even to another screen
*
* <br>Because of the wanted side effect for the containing screen, this should only be used with screen objects.
* <br>For Region objects use setRect() instead.
*
* @param reg Region
*/
public void setROI(Region reg) {
setROI(reg.getX(), reg.getY(), reg.getW(), reg.getH());
}
/**
* A function only for backward compatibility - Only makes sense with Screen objects
*
* @return the Region being the current ROI of the containing Screen
*/
public Region getROI() {
IScreen screen = getScreen();
Rectangle screenRect = screen.getRect();
return new Region(screenRect.x, screenRect.y, screenRect.width, screenRect.height, screen);
}
// ****************************************************
/**
*
* @return the region itself
* @deprecated only for backward compatibility
*/
@Deprecated
public Region inside() {
return this;
}
/**
* set the regions position<br>this might move the region even to another screen
*
* @param loc new top left corner
* @return the region itself
* @deprecated to be like AWT Rectangle API use setLocation()
*/
@Deprecated
public Region moveTo(Location loc) {
return setLocation(loc);
}
/**
* set the regions position<br>this might move the region even to another screen
*
* @param loc new top left corner
* @return the region itself
*/
public Region setLocation(Location loc) {
x = loc.x;
y = loc.y;
initScreen(null);
return this;
}
/**
* set the regions position/size<br>this might move the region even to another screen
*
* @param r Region
* @return the region itself
* @deprecated to be like AWT Rectangle API use setRect() instead
*/
@Deprecated
public Region morphTo(Region r) {
return setRect(r);
}
/**
* resize the region using the given padding values<br>might be negative
*
* @param l padding on left side
* @param r padding on right side
* @param t padding at top side
* @param b padding at bottom side
* @return the region itself
*/
public Region add(int l, int r, int t, int b) {
x = x - l;
y = y - t;
w = w + l + r;
if (w < 1) {
w = 1;
}
h = h + t + b;
if (h < 1) {
h = 1;
}
initScreen(null);
return this;
}
/**
* extend the region, so it contains the given region<br>but only the part inside the current screen
*
* @param r the region to include
* @return the region itself
*/
public Region add(Region r) {
Rectangle rect = getRect();
rect.add(r.getRect());
setRect(rect);
initScreen(null);
return this;
}
/**
* extend the region, so it contains the given point<br>but only the part inside the current screen
*
* @param loc the point to include
* @return the region itself
*/
public Region add(Location loc) {
Rectangle rect = getRect();
rect.add(loc.x, loc.y);
setRect(rect);
initScreen(null);
return this;
}
//</editor-fold>
//<editor-fold desc="lastMatch">
// ************************************************
/**
* a find operation saves its match on success in the used region object<br>unchanged if not successful
*
* @return the Match object from last successful find in this region
*/
public Match getLastMatch() {
return lastMatch;
}
// ************************************************
/**
* a searchAll operation saves its matches on success in the used region object<br>unchanged if not successful
*
* @return a Match-Iterator of matches from last successful searchAll in this region
*/
public Iterator<Match> getLastMatches() {
return lastMatches;
}
//</editor-fold>
//<editor-fold desc="save capture to file">
public String saveScreenCapture() {
return getScreen().capture(this).save();
}
public String saveScreenCapture(String path) {
return getScreen().capture(this).save(path);
}
public String saveScreenCapture(String path, String name) {
return getScreen().capture(this).save(path, name);
}
// ************************************************
/**
* get the last image taken on this regions screen
*
* @return the stored ScreenImage
*/
public ScreenImage getLastScreenImage() {
return getScreen().getLastScreenImageFromScreen();
}
/**
* stores the lastScreenImage in the current bundle path with a created unique name
*
* @return the absolute file name
* @throws java.io.IOException if not possible
*/
public String getLastScreenImageFile() throws IOException {
return getScreen().getLastScreenImageFile(ImagePath.getBundlePath(), null);
}
/**
* stores the lastScreenImage in the current bundle path with the given name
*
* @param name file name (.png is added if not there)
* @return the absolute file name
* @throws java.io.IOException if not possible
*/
public String getLastScreenImageFile(String name) throws IOException {
return getScreen().getLastScreenImageFromScreen().getFile(ImagePath.getBundlePath(), name);
}
/**
* stores the lastScreenImage in the given path with the given name
*
* @param path path to use
* @param name file name (.png is added if not there)
* @return the absolute file name
* @throws java.io.IOException if not possible
*/
public String getLastScreenImageFile(String path, String name) throws IOException {
return getScreen().getLastScreenImageFromScreen().getFile(path, name);
}
public void saveLastScreenImage() {
ScreenImage simg = getScreen().getLastScreenImageFromScreen();
if (simg != null) {
simg.saveLastScreenImage(runTime.fSikulixStore);
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="spatial operators - new regions">
/**
* check if current region contains given region
*
* @param region the other Region
* @return true/false
*/
public boolean contains(Region region) {
return getRect().contains(region.getRect());
}
/**
* create a Location object, that can be used as an offset taking the width and hight of this Region
*
* @return a new Location object with width and height as x and y
*/
public Location asOffset() {
return new Location(w, h);
}
/**
* create region with same size at top left corner offset
*
* @param loc use its x and y to set the offset
* @return the new region
*/
public Region offset(Location loc) {
return Region.create(x + loc.x, y + loc.y, w, h, scr);
}
/**
* create region with same size at top left corner offset
*
* @param x horizontal offset
* @param y vertical offset
* @return the new region
*/
public Region offset(int x, int y) {
return Region.create(this.x + x, this.y + y, w, h, scr);
}
/**
* create a region enlarged Settings.DefaultPadding pixels on each side
*
* @return the new region
* @deprecated to be like AWT Rectangle API use grow() instead
*/
@Deprecated
public Region nearby() {
return grow(Settings.DefaultPadding, Settings.DefaultPadding);
}
/**
* create a region enlarged range pixels on each side
*
* @param range the margin to be added around
* @return the new region
* @deprecated to be like AWT Rectangle API use grow() instaed
*/
@Deprecated
public Region nearby(int range) {
return grow(range, range);
}
/**
* create a region enlarged n pixels on each side (n = Settings.DefaultPadding = 50 default)
*
* @return the new region
*/
public Region grow() {
return grow(Settings.DefaultPadding, Settings.DefaultPadding);
}
/**
* create a region enlarged range pixels on each side
*
* @param range the margin to be added around
* @return the new region
*/
public Region grow(int range) {
return grow(range, range);
}
/**
* create a region enlarged w pixels on left and right side and h pixels at top and bottom
*
* @param w pixels horizontally
* @param h pixels vertically
* @return the new region
*/
public Region grow(int w, int h) {
Rectangle r = getRect();
r.grow(w, h);
return Region.create(r.x, r.y, r.width, r.height, scr);
}
/**
* create a region enlarged l pixels on left and r pixels right side and t pixels at top side and b pixels a bottom
* side. negative values go inside (shrink)
*
* @param l add to the left
* @param r add to right
* @param t add above
* @param b add beneath
* @return the new region
*/
public Region grow(int l, int r, int t, int b) {
return Region.create(x - l, y - t, w + l + r, h + t + b, scr);
}
/**
* point middle on right edge
*
* @return point middle on right edge
*/
public Location rightAt() {
return rightAt(0);
}
/**
* positive offset goes to the right. might be off current screen
*
* @param offset pixels
* @return point with given offset horizontally to middle point on right edge
*/
public Location rightAt(int offset) {
return checkAndSetRemote(new Location(x + w + offset, y + h / 2));
}
/**
* create a region right of the right side with same height. the new region extends to the right screen border<br>
* use grow() to include the current region
*
* @return the new region
*/
public Region right() {
int distToRightScreenBorder = getScreen().getX() + getScreen().getW() - (getX() + getW());
return right(distToRightScreenBorder);
}
/**
* create a region right of the right side with same height and given width. negative width creates the right part
* with width inside the region<br>
* use grow() to include the current region
*
* @param width pixels
* @return the new region
*/
public Region right(int width) {
int _x;
if (width < 0) {
_x = x + w + width;
} else {
_x = x + w;
}
return Region.create(_x, y, Math.abs(width), h, scr);
}
/**
*
* @return point middle on left edge
*/
public Location leftAt() {
return leftAt(0);
}
/**
* negative offset goes to the left <br>might be off current screen
*
* @param offset pixels
* @return point with given offset horizontally to middle point on left edge
*/
public Location leftAt(int offset) {
return checkAndSetRemote(new Location(x + offset, y + h / 2));
}
/**
* create a region left of the left side with same height<br> the new region extends to the left screen border<br> use
* grow() to include the current region
*
* @return the new region
*/
public Region left() {
int distToLeftScreenBorder = getX() - getScreen().getX();
return left(distToLeftScreenBorder);
}
/**
* create a region left of the left side with same height and given width<br>
* negative width creates the left part with width inside the region use grow() to include the current region <br>
*
* @param width pixels
* @return the new region
*/
public Region left(int width) {
int _x;
if (width < 0) {
_x = x;
} else {
_x = x - width;
}
return Region.create(getScreen().getBounds().intersection(new Rectangle(_x, y, Math.abs(width), h)), scr);
}
/**
*
* @return point middle on top edge
*/
public Location aboveAt() {
return aboveAt(0);
}
/**
* negative offset goes towards top of screen <br>might be off current screen
*
* @param offset pixels
* @return point with given offset vertically to middle point on top edge
*/
public Location aboveAt(int offset) {
return checkAndSetRemote(new Location(x + w / 2, y + offset));
}
/**
* create a region above the top side with same width<br> the new region extends to the top screen border<br> use
* grow() to include the current region
*
* @return the new region
*/
public Region above() {
int distToAboveScreenBorder = getY() - getScreen().getY();
return above(distToAboveScreenBorder);
}
/**
* create a region above the top side with same width and given height<br>
* negative height creates the top part with height inside the region use grow() to include the current region
*
* @param height pixels
* @return the new region
*/
public Region above(int height) {
int _y;
if (height < 0) {
_y = y;
} else {
_y = y - height;
}
return Region.create(getScreen().getBounds().intersection(new Rectangle(x, _y, w, Math.abs(height))), scr);
}
/**
*
* @return point middle on bottom edge
*/
public Location belowAt() {
return belowAt(0);
}
/**
* positive offset goes towards bottom of screen <br>might be off current screen
*
* @param offset pixels
* @return point with given offset vertically to middle point on bottom edge
*/
public Location belowAt(int offset) {
return checkAndSetRemote(new Location(x + w / 2, y + h - offset));
}
/**
* create a region below the bottom side with same width<br> the new region extends to the bottom screen border<br>
* use grow() to include the current region
*
* @return the new region
*/
public Region below() {
int distToBelowScreenBorder = getScreen().getY() + getScreen().getH() - (getY() + getH());
return below(distToBelowScreenBorder);
}
/**
* create a region below the bottom side with same width and given height<br>
* negative height creates the bottom part with height inside the region use grow() to include the current region
*
* @param height pixels
* @return the new region
*/
public Region below(int height) {
int _y;
if (height < 0) {
_y = y + h + height;
} else {
_y = y + h;
}
return Region.create(x, _y, w, Math.abs(height), scr);
}
/**
* create a new region containing both regions
*
* @param ur region to unite with
* @return the new region
*/
public Region union(Region ur) {
Rectangle r = getRect().union(ur.getRect());
return Region.create(r.x, r.y, r.width, r.height, scr);
}
/**
* create a region that is the intersection of the given regions
*
* @param ir the region to intersect with like AWT Rectangle API
* @return the new region
*/
public Region intersection(Region ir) {
Rectangle r = getRect().intersection(ir.getRect());
return Region.create(r.x, r.y, r.width, r.height, scr);
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="parts of a Region">
/**
* select the specified part of the region.
*
* <br>Constants for the top parts of a region (Usage: Region.CONSTANT)<br>
* shown in brackets: possible shortcuts for the part constant<br>
* NORTH (NH, TH) - upper half <br>
* NORTH_WEST (NW, TL) - left third in upper third <br>
* NORTH_MID (NM, TM) - middle third in upper third <br>
* NORTH_EAST (NE, TR) - right third in upper third <br>
* ... similar for the other directions: <br>
* right side: EAST (Ex, Rx)<br>
* bottom part: SOUTH (Sx, Bx) <br>
* left side: WEST (Wx, Lx)<br>
* <br>
* specials for quartered:<br>
* TT top left quarter<br>
* RR top right quarter<br>
* BB bottom right quarter<br>
* LL bottom left quarter<br>
* <br>
* specials for the center parts:<br>
* MID_VERTICAL (MV, CV) half of width vertically centered <br>
* MID_HORIZONTAL (MH, CH) half of height horizontally centered <br>
* MID_BIG (M2, C2) half of width / half of height centered <br>
* MID_THIRD (MM, CC) third of width / third of height centered <br>
* <br>
* Based on the scheme behind these constants there is another possible usage:<br>
* specify part as e 3 digit integer where the digits xyz have the following meaning<br>
* 1st x: use a raster of x rows and x columns<br>
* 2nd y: the row number of the wanted cell<br>
* 3rd z: the column number of the wanted cell<br>
* y and z are counting from 0<br>
* valid numbers: 200 up to 999 (< 200 are invalid and return the region itself) <br>
* example: get(522) will use a raster of 5 rows and 5 columns and return the cell in the middle<br>
* special cases:<br>
* if either y or z are == or > x: returns the respective row or column<br>
* example: get(525) will use a raster of 5 rows and 5 columns and return the row in the middle<br>
* <br>
* internally this is based on {@link #setRaster(int, int) setRaster} and {@link #getCell(int, int) getCell} <br>
* <br>
* If you need only one row in one column with x rows or only one column in one row with x columns you can use
* {@link #getRow(int, int) getRow} or {@link #getCol(int, int) getCol}
*
* @param part the part to get (Region.PART long or short)
* @return new region
*/
public Region get(int part) {
return Region.create(getRectangle(getRect(), part));
}
protected static Rectangle getRectangle(Rectangle rect, int part) {
if (part < 200 || part > 999) {
return rect;
}
Region r = Region.create(rect);
int pTyp = (int) (part / 100);
int pPos = part - pTyp * 100;
int pRow = (int) (pPos / 10);
int pCol = pPos - pRow * 10;
r.setRaster(pTyp, pTyp);
if (pTyp == 3) {
// NW = 300, NORTH_WEST = NW;
// NM = 301, NORTH_MID = NM;
// NE = 302, NORTH_EAST = NE;
// EM = 312, EAST_MID = EM;
// SE = 322, SOUTH_EAST = SE;
// SM = 321, SOUTH_MID = SM;
// SW = 320, SOUTH_WEST = SW;
// WM = 310, WEST_MID = WM;
// MM = 311, MIDDLE = MM, M3 = MM;
return r.getCell(pRow, pCol).getRect();
}
if (pTyp == 2) {
// NH = 202, NORTH = NH;
// EH = 221, EAST = EH;
// SH = 212, SOUTH = SH;
// WH = 220, WEST = WH;
if (pRow > 1) {
return r.getCol(pCol).getRect();
} else if (pCol > 1) {
return r.getRow(pRow).getRect();
}
return r.getCell(pRow, pCol).getRect();
}
if (pTyp == 4) {
// MV = 441, MID_VERTICAL = MV;
// MH = 414, MID_HORIZONTAL = MH;
// M2 = 444, MIDDLE_BIG = M2;
if (pRow > 3) {
if (pCol > 3) {
return r.getCell(1, 1).union(r.getCell(2, 2)).getRect();
}
return r.getCell(0, 1).union(r.getCell(3, 2)).getRect();
} else if (pCol > 3) {
return r.getCell(1, 0).union(r.getCell(2, 3)).getRect();
}
return r.getCell(pRow, pCol).getRect();
}
return rect;
}
/**
* store info: this region is divided vertically into n even rows <br>
* a preparation for using getRow()
*
* @param n number of rows
* @return the top row
*/
public Region setRows(int n) {
return setRaster(n, 0);
}
/**
* store info: this region is divided horizontally into n even columns <br>
* a preparation for using getCol()
*
* @param n number of columns
* @return the leftmost column
*/
public Region setCols(int n) {
return setRaster(0, n);
}
/**
*
* @return the number of rows or null
*/
public int getRows() {
return rows;
}
/**
*
* @return the row height or 0
*/
public int getRowH() {
return rowH;
}
/**
*
* @return the number of columns or 0
*/
public int getCols() {
return cols;
}
/**
*
* @return the columnwidth or 0
*/
public int getColW() {
return colW;
}
/**
* Can be used to check, wether the Region currently has a valid raster
*
* @return true if it has a valid raster (either getCols or getRows or both would return > 0) false otherwise
*/
public boolean isRasterValid() {
return (rows > 0 || cols > 0);
}
/**
* store info: this region is divided into a raster of even cells <br>
* a preparation for using getCell()<br>
*
* @param r number of rows
* @param c number of columns
* @return the topleft cell
*/
public Region setRaster(int r, int c) {
rows = Math.max(r, h);
cols = Math.max(c, w);
if (r > 0) {
rowH = (int) (h / r);
rowHd = h - r * rowH;
}
if (c > 0) {
colW = (int) (w / c);
colWd = w - c * colW;
}
return getCell(0, 0);
}
/**
* get the specified row counting from 0, if rows or raster are setup negative counts reverse from the end (last = -1)
* values outside range are 0 or last respectively
*
* @param r row number
* @return the row as new region or the region itself, if no rows are setup
*/
public Region getRow(int r) {
if (rows == 0) {
return this;
}
if (r < 0) {
r = rows + r;
}
r = Math.max(0, r);
r = Math.min(r, rows - 1);
return Region.create(x, y + r * rowH, w, rowH);
}
public Region getRow(int r, int n) {
return this;
}
/**
* get the specified column counting from 0, if columns or raster are setup negative counts reverse from the end (last
* = -1) values outside range are 0 or last respectively
*
* @param c column number
* @return the column as new region or the region itself, if no columns are setup
*/
public Region getCol(int c) {
if (cols == 0) {
return this;
}
if (c < 0) {
c = cols + c;
}
c = Math.max(0, c);
c = Math.min(c, cols - 1);
return Region.create(x + c * colW, y, colW, h);
}
/**
* divide the region in n columns and select column c as new Region
*
* @param c the column to select counting from 0 or negative to count from the end
* @param n how many columns to devide in
* @return the selected part or the region itself, if parameters are invalid
*/
public Region getCol(int c, int n) {
Region r = new Region(this);
r.setCols(n);
return r.getCol(c);
}
/**
* get the specified cell counting from (0, 0), if a raster is setup <br>
* negative counts reverse from the end (last = -1) values outside range are 0 or last respectively
*
* @param r row number
* @param c column number
* @return the cell as new region or the region itself, if no raster is setup
*/
public Region getCell(int r, int c) {
if (rows == 0) {
return getCol(c);
}
if (cols == 0) {
return getRow(r);
}
if (rows == 0 && cols == 0) {
return this;
}
if (r < 0) {
r = rows - r;
}
if (c < 0) {
c = cols - c;
}
r = Math.max(0, r);
r = Math.min(r, rows - 1);
c = Math.max(0, c);
c = Math.min(c, cols - 1);
return Region.create(x + c * colW, y + r * rowH, colW, rowH);
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="highlight">
protected void updateSelf() {
if (overlay != null) {
highlight(false, null);
highlight(true, null);
}
}
protected Region silentHighlight(boolean onOff) {
if (onOff && overlay == null) {
return doHighlight(true, null, true);
}
if (!onOff && overlay != null) {
return doHighlight(false, null, true);
}
return this;
}
/**
* Toggle the regions Highlight visibility (red frame)
*
* @return the region itself
*/
public Region highlight() {
// Pass true if overlay is null, false otherwise
highlight(overlay == null, null);
return this;
}
/**
* Toggle the regions Highlight visibility (frame of specified color)<br>
* allowed color specifications for frame color: <br>
* - a color name out of: black, blue, cyan, gray, green, magenta, orange, pink, red, white, yellow (lowercase and
* uppercase can be mixed, internally transformed to all uppercase) <br>
* - these colornames exactly written: lightGray, LIGHT_GRAY, darkGray and DARK_GRAY <br>
* - a hex value like in HTML: #XXXXXX (max 6 hex digits) - an RGB specification as: #rrrgggbbb where rrr, ggg, bbb
* are integer values in range 0 - 255 padded with leading zeros if needed (hence exactly 9 digits)
*
* @param color Color of frame
* @return the region itself
*/
public Region highlight(String color) {
// Pass true if overlay is null, false otherwise
highlight(overlay == null, color);
return this;
}
/**
* Sets the regions Highlighting border
*
* @param toEnable set overlay enabled or disabled
* @param color Color of frame (see method highlight(color))
*/
private Region highlight(boolean toEnable, String color) {
return doHighlight(toEnable, color, false);
}
private Region doHighlight(boolean toEnable, String color, boolean silent) {
if (isOtherScreen()) {
return this;
}
if (!silent) {
Debug.action("toggle highlight " + toString() + ": " + toEnable
+ (color != null ? " color: " + color : ""));
}
if (toEnable) {
overlay = new ScreenHighlighter(getScreen(), color);
overlay.setWaitAfter(silent);
overlay.highlight(this);
} else {
if (overlay != null) {
overlay.close();
overlay = null;
}
}
return this;
}
/**
* show the regions Highlight for the given time in seconds (red frame) if 0 - use the global Settings.SlowMotionDelay
*
* @param secs time in seconds
* @return the region itself
*/
public Region highlight(float secs) {
return highlight(secs, null);
}
/**
* show the regions Highlight for the given time in seconds (frame of specified color) if 0 - use the global
* Settings.SlowMotionDelay
*
* @param secs time in seconds
* @param color Color of frame (see method highlight(color))
* @return the region itself
*/
public Region highlight(float secs, String color) {
if (getScreen() == null || isOtherScreen() || isScreenUnion) {
Debug.error("highlight: not possible for %s", getScreen());
return this;
}
if (secs < 0.1) {
return highlight((int) secs, color);
}
Debug.action("highlight " + toStringShort() + " for " + secs + " secs"
+ (color != null ? " color: " + color : ""));
ScreenHighlighter _overlay = new ScreenHighlighter(getScreen(), color);
_overlay.highlight(this, secs);
return this;
}
/**
* hack to implement the getLastMatch() convenience 0 means same as highlight() < 0 same as highlight(secs) if
* available the last match is highlighted
*
* @param secs seconds
* @return this region
*/
public Region highlight(int secs) {
return highlight(secs, null);
}
/**
* Show highlight in selected color
*
* @param secs time in seconds
* @param color Color of frame (see method highlight(color))
* @return this region
*/
public Region highlight(int secs, String color) {
if (getScreen() == null || isOtherScreen() || isScreenUnion) {
Debug.error("highlight: not possible for %s", getScreen());
return this;
}
if (secs > 0) {
return highlight((float) secs, color);
}
if (lastMatch != null) {
if (secs < 0) {
return lastMatch.highlight((float) -secs, color);
}
return lastMatch.highlight(Settings.DefaultHighlightTime, color);
}
return this;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="find public methods">
/**
* WARNING: wait(long timeout) is taken by Java Object as final. This method catches any interruptedExceptions
*
* @param timeout The time to wait
*/
public void wait(double timeout) {
try {
Thread.sleep((long) (timeout * 1000L));
} catch (InterruptedException e) {
}
}
/**
* return false to skip <br>
* return true to try again <br>
* throw FindFailed to abort
*
* @param img Handles a failed find action
*/
private <PSI> Boolean handleFindFailed(PSI target, Image img, boolean isExists) {
log(lvl, "handleFindFailed: %s", target);
Boolean state = null;
ObserveEvent evt = null;
FindFailedResponse response = findFailedResponse;
if (FindFailedResponse.HANDLE.equals(response)) {
ObserveEvent.Type type = ObserveEvent.Type.FINDFAILED;
if (findFailedHandler != null && ((ObserverCallBack) findFailedHandler).getType().equals(type)) {
log(lvl, "handleFindFailed: Response.HANDLE: calling handler");
evt = new ObserveEvent("", type, target, img, this, 0);
((ObserverCallBack) findFailedHandler).findfailed(evt);
response = evt.getResponse();
}
}
if (FindFailedResponse.ABORT.equals(response)) {
state = null;
if (isExists) {
state = false;
}
} else if (FindFailedResponse.SKIP.equals(response)) {
state = false;
} else if (FindFailedResponse.RETRY.equals(response)) {
state = true;
}
if (FindFailedResponse.PROMPT.equals(response)) {
response = handleFindFailedShowDialog(img, false);
} else {
return state;
}
if (FindFailedResponse.ABORT.equals(response)) {
state = null;
} else if (FindFailedResponse.SKIP.equals(response)) {
// TODO HACK to allow recapture on FindFailed PROMPT
if (img.backup()) {
img.delete();
state = handleImageMissing(img, true);
if (state == null || !state) {
if (!img.restore()) {
state = null;
} else {
img.get();
}
}
}
} else if (FindFailedResponse.RETRY.equals(response)) {
state = true;
}
return state;
}
private Boolean handleImageMissing(Image img, boolean recap) {
log(lvl, "handleImageMissing: %s", img.getName());
ObserveEvent evt = null;
FindFailedResponse response = findFailedResponse;
if (FindFailedResponse.HANDLE.equals(response)) {
ObserveEvent.Type type = ObserveEvent.Type.MISSING;
if (imageMissingHandler != null && ((ObserverCallBack) imageMissingHandler).getType().equals(type)) {
log(lvl, "handleImageMissing: Response.HANDLE: calling handler");
evt = new ObserveEvent("", type, null, img, this, 0);
((ObserverCallBack) imageMissingHandler).missing(evt);
response = evt.getResponse();
} else {
response = FindFailedResponse.PROMPT;
}
}
if (FindFailedResponse.PROMPT.equals(response)) {
log(lvl, "handleImageMissing: Response.PROMPT");
response = handleFindFailedShowDialog(img, true);
}
if (findFailedResponse.RETRY.equals(response)) {
log(lvl, "handleImageMissing: Response.RETRY: %s", (recap?"recapture ":"capture missing "));
getRobotForRegion().delay(500);
ScreenImage simg = getScreen().userCapture(
(recap?"recapture ":"capture missing ") + img.getName());
if (simg != null) {
String path = ImagePath.getBundlePath();
if (path == null) {
log(-1, "handleImageMissing: no bundle path - aborting");
return null;
}
simg.getFile(path, img.getImageName());
Image.set(img);
if (img.isValid()) {
log(lvl, "handleImageMissing: %scaptured: %s", (recap?"re":""), img);
Image.setIDEshouldReload(img);
return true;
}
}
return null;
} else if (findFailedResponse.ABORT.equals(response)) {
log(lvl, "handleImageMissing: Response.ABORT: aborting");
return null;
}
log(lvl, "handleImageMissing: skip requested on %s", (recap?"recapture ":"capture missing "));
return false;
}
private FindFailedResponse handleFindFailedShowDialog(Image img, boolean shouldCapture) {
log(lvl, "handleFindFailedShowDialog: requested %s", (shouldCapture?"(with capture)":""));
FindFailedResponse response;
FindFailedDialog fd = new FindFailedDialog(img, shouldCapture);
fd.setVisible(true);
response = fd.getResponse();
fd.dispose();
wait(0.5);
log(lvl, "handleFindFailedShowDialog: answer is %s", response);
return response;
}
/**
* finds the given Pattern, String or Image in the region and returns the best match. If AutoWaitTimeout is set, this
* is equivalent to wait(). Otherwise only one search attempt will be done.
*
* @param <PSI> Pattern, String or Image
* @param target A search criteria
* @return If found, the element. null otherwise
* @throws FindFailed if the Find operation failed
*/
public <PSI> Match find(PSI target) throws FindFailed {
if (autoWaitTimeout > 0) {
return wait(target, autoWaitTimeout);
}
lastMatch = null;
Image img = Image.getImageFromTarget(target);
Boolean response = true;
if (!img.isText() && !img.isValid() && img.hasIOException()) {
response = handleImageMissing(img, false);
if (response == null) {
runTime.abortScripting("Find: Abort:", "ImageMissing: " + target.toString());
}
}
String targetStr = img.getName();
while (null != response && response) {
log(lvl, "find: waiting 0 secs for %s to appear in %s", targetStr, this.toStringShort());
lastMatch = doFind(target, img, null);
if (lastMatch != null) {
lastMatch.setImage(img);
if (isOtherScreen()) {
lastMatch.setOtherScreen();
} else if (img != null) {
img.setLastSeen(lastMatch.getRect(), lastMatch.getScore());
}
log(lvl, "find: %s appeared (%s)", targetStr, lastMatch);
break;
}
log(lvl, "find: %s did not appear [%d msec]", targetStr, new Date().getTime() - lastFindTime);
if (null == lastMatch) {
response = handleFindFailed(target, img, false);
}
}
if (null == response) {
throw new FindFailed(FindFailed.createdefault(this, img));
}
return lastMatch;
}
/**
* Check if target exists (with the default autoWaitTimeout)
*
* @param <PSI> Pattern, String or Image
* @param target Pattern, String or Image
* @return the match (null if not found or image file missing)
*/
public <PSI> Match exists(PSI target) {
return exists(target, autoWaitTimeout);
}
/**
* Check if target exists with a specified timeout<br>
* timout = 0: returns immediately after first search
*
* @param <PSI> Pattern, String or Image
* @param target The target to search for
* @param timeout Timeout in seconds
* @return the match (null if not found or image file missing)
*/
public <PSI> Match exists(PSI target, double timeout) {
lastMatch = null;
String shouldAbort = "";
RepeatableFind rf = new RepeatableFind(target, null);
Image img = rf._image;
Boolean response = true;
if (!img.isText() && !img.isValid() && img.hasIOException()) {
response = handleImageMissing(img, false);
if (response == null) {
runTime.abortScripting("Exists: Abort:", "ImageMissing: " + target.toString());
}
}
String targetStr = img.getName();
while (null != response && response) {
log(lvl, "exists: waiting %.1f secs for %s to appear in %s", timeout, targetStr, this.toStringShort());
if (rf.repeat(timeout)) {
lastMatch = rf.getMatch();
lastMatch.setImage(img);
if (isOtherScreen()) {
lastMatch.setOtherScreen();
} else if (img != null) {
img.setLastSeen(lastMatch.getRect(), lastMatch.getScore());
}
log(lvl, "exists: %s has appeared (%s)", targetStr, lastMatch);
return lastMatch;
} else {
response = handleFindFailed(target, img, true);
if (null == response) {
shouldAbort = FindFailed.createdefault(this, img);
} else if (response) {
if (img.isRecaptured()) {
rf = new RepeatableFind(target, img);
}
continue;
}
break;
}
}
if (!shouldAbort.isEmpty()) {
runTime.abortScripting("Exists: Abort:", "FindFailed: " + shouldAbort);
} else {
log(lvl, "exists: %s did not appear [%d msec]", targetStr, new Date().getTime() - lastFindTime);
}
return null;
}
/**
* finds all occurences of the given Pattern, String or Image in the region and returns an Iterator of Matches.
*
*
* @param <PSI> Pattern, String or Image
* @param target A search criteria
* @return All elements matching
* @throws FindFailed if the Find operation failed
*/
public <PSI> Iterator<Match> findAll(PSI target) throws FindFailed {
lastMatches = null;
RepeatableFindAll rf = new RepeatableFindAll(target, null);
Image img = rf._image;
String targetStr = img.getName();
Boolean response = true;
if (!img.isValid() && img.hasIOException()) {
response = handleImageMissing(img, false);
if (response == null) {
runTime.abortScripting("FindAll: Abort:", "ImageMissing: " + target.toString());
}
}
while (null != response && response) {
log(lvl, "findAll: waiting %.1f secs for (multiple) %s to appear in %s",
autoWaitTimeout, targetStr, this.toStringShort());
if (autoWaitTimeout > 0) {
rf.repeat(autoWaitTimeout);
lastMatches = rf.getMatches();
} else {
lastMatches = doFindAll(target, null);
}
if (lastMatches != null) {
log(lvl, "findAll: %s has appeared", targetStr);
break;
} else {
log(lvl, "findAll: %s did not appear", targetStr);
response = handleFindFailed(target, img, false);
}
}
if (null == response) {
throw new FindFailed(FindFailed.createdefault(this, img));
}
return lastMatches;
}
public <PSI> Match[] findAllByRow(PSI target) {
Match[] matches = new Match[0];
List<Match> mList = findAllCollect(target);
if (mList.isEmpty()) {
return null;
}
Collections.sort(mList, new Comparator<Match>() {
@Override
public int compare(Match m1, Match m2) {
if (m1.y == m2.y) {
return m1.x - m2.x;
}
return m1.y - m2.y;
}
});
return mList.toArray(matches);
}
public <PSI> Match[] findAllByColumn(PSI target) {
Match[] matches = new Match[0];
List<Match> mList = findAllCollect(target);
if (mList.isEmpty()) {
return null;
}
Collections.sort(mList, new Comparator<Match>() {
@Override
public int compare(Match m1, Match m2) {
if (m1.x == m2.x) {
return m1.y - m2.y;
}
return m1.x - m2.x;
}
});
return mList.toArray(matches);
}
private <PSI> List<Match> findAllCollect(PSI target) {
Iterator<Match> mIter = null;
try {
mIter = findAll(target);
} catch (Exception ex) {
Debug.error("findAllByRow: %s", ex.getMessage());
return null;
}
List<Match> mList = new ArrayList<Match>();
while (mIter.hasNext()) {
mList.add(mIter.next());
}
return mList;
}
public Match findBest(Object... args) {
Debug.log(lvl, "findBest: enter");
Match mResult = null;
List<Match> mList = findAnyCollect(args);
if (mList != null) {
Collections.sort(mList, new Comparator<Match>() {
@Override
public int compare(Match m1, Match m2) {
double ms = m2.getScore() - m1.getScore();
if (ms < 0) {
return -1;
} else if (ms > 0) {
return 1;
}
return 0;
}
});
mResult = mList.get(0);
}
return mResult;
}
private List<Match> findAnyCollect(Object... args) {
if (args == null) {
return null;
}
List<Match> mList = new ArrayList<Match>();
Match[] mArray = new Match[args.length];
SubFindRun[] theSubs = new SubFindRun[args.length];
int nobj = 0;
ScreenImage base = getScreen().capture(this);
for (Object obj : args) {
mArray[nobj] = null;
if (obj instanceof Pattern || obj instanceof String || obj instanceof Image) {
theSubs[nobj] = new SubFindRun(mArray, nobj, base, obj, this);
new Thread(theSubs[nobj]).start();
}
nobj++;
}
Debug.log(lvl, "findAnyCollect: waiting for SubFindRuns");
nobj = 0;
boolean all = false;
while (!all) {
all = true;
for (SubFindRun sub : theSubs) {
all &= sub.hasFinished();
}
}
Debug.log(lvl, "findAnyCollect: SubFindRuns finished");
nobj = 0;
for (Match match : mArray) {
if (match != null) {
match.setIndex(nobj);
mList.add(match);
} else {
}
nobj++;
}
return mList;
}
private class SubFindRun implements Runnable {
Match[] mArray;
ScreenImage base;
Object target;
Region reg;
boolean finished = false;
int subN;
public SubFindRun(Match[] pMArray, int pSubN,
ScreenImage pBase, Object pTarget, Region pReg) {
subN = pSubN;
base = pBase;
target = pTarget;
reg = pReg;
mArray = pMArray;
}
@Override
public void run() {
try {
mArray[subN] = reg.findInImage(base, target);
} catch (Exception ex) {
log(-1, "findAnyCollect: image file not found:\n", target);
}
hasFinished(true);
}
public boolean hasFinished() {
return hasFinished(false);
}
public synchronized boolean hasFinished(boolean state) {
if (state) {
finished = true;
}
return finished;
}
}
private Match findInImage(ScreenImage base, Object target) throws IOException {
Finder finder = null;
Match match = null;
boolean findingText = false;
Image img = null;
if (target instanceof String) {
if (((String) target).startsWith("\t") && ((String) target).endsWith("\t")) {
findingText = true;
} else {
img = Image.create((String) target);
if (img.isValid()) {
finder = doCheckLastSeenAndCreateFinder(base, img, 0.0, null);
if (!finder.hasNext()) {
runFinder(finder, img);
}
} else if (img.isText()) {
findingText = true;
} else {
throw new IOException("Region: findInImage: Image not loadable: " + target.toString());
}
}
if (findingText) {
if (TextRecognizer.getInstance() != null) {
log(lvl, "findInImage: Switching to TextSearch");
finder = new Finder(getScreen().capture(x, y, w, h), this);
finder.findText((String) target);
}
}
} else if (target instanceof Pattern) {
if (((Pattern) target).isValid()) {
img = ((Pattern) target).getImage();
finder = doCheckLastSeenAndCreateFinder(base, img, 0.0, (Pattern) target);
if (!finder.hasNext()) {
runFinder(finder, target);
}
} else {
throw new IOException("Region: findInImage: Image not loadable: " + target.toString());
}
} else if (target instanceof Image) {
if (((Image) target).isValid()) {
img = ((Image) target);
finder = doCheckLastSeenAndCreateFinder(base, img, 0.0, null);
if (!finder.hasNext()) {
runFinder(finder, img);
}
} else {
throw new IOException("Region: findInImage: Image not loadable: " + target.toString());
}
} else {
log(-1, "findInImage: invalid parameter: %s", target);
return null;
}
if (finder.hasNext()) {
match = finder.next();
match.setImage(img);
img.setLastSeen(match.getRect(), match.getScore());
}
return match;
}
/**
* Waits for the Pattern, String or Image to appear until the AutoWaitTimeout value is exceeded.
*
* @param <PSI> Pattern, String or Image
* @param target The target to search for
* @return The found Match
* @throws FindFailed if the Find operation finally failed
*/
public <PSI> Match wait(PSI target) throws FindFailed {
if (target instanceof Float || target instanceof Double) {
wait(0.0 + ((Double) target));
return null;
}
return wait(target, autoWaitTimeout);
}
/**
* Waits for the Pattern, String or Image to appear or timeout (in second) is passed
*
* @param <PSI> Pattern, String or Image
* @param target The target to search for
* @param timeout Timeout in seconds
* @return The found Match
* @throws FindFailed if the Find operation finally failed
*/
public <PSI> Match wait(PSI target, double timeout) throws FindFailed {
lastMatch = null;
String shouldAbort = "";
RepeatableFind rf = new RepeatableFind(target, null);
Image img = rf._image;
String targetStr = img.getName();
Boolean response = true;
if (!img.isText() && !img.isValid() && img.hasIOException()) {
response = handleImageMissing(img, false);
if (response == null) {
runTime.abortScripting("Wait: Abort:", "ImageMissing: " + target.toString());
}
}
while (null != response && response) {
log(lvl, "wait: waiting %.1f secs for %s to appear in %s", timeout, targetStr, this.toStringShort());
if (rf.repeat(timeout)) {
lastMatch = rf.getMatch();
lastMatch.setImage(img);
if (isOtherScreen()) {
lastMatch.setOtherScreen();
} else if (img != null) {
img.setLastSeen(lastMatch.getRect(), lastMatch.getScore());
}
log(lvl, "wait: %s appeared (%s)", targetStr, lastMatch);
return lastMatch;
} else {
response = handleFindFailed(target, img, false);
if (null == response) {
shouldAbort = FindFailed.createdefault(this, img);
break;
} else if (response) {
if (img.isRecaptured()) {
rf = new RepeatableFind(target, img);
}
continue;
}
break;
}
}
log(lvl, "wait: %s did not appear [%d msec]", targetStr, new Date().getTime() - lastFindTime);
if (!shouldAbort.isEmpty()) {
throw new FindFailed(shouldAbort);
}
return lastMatch;
}
/**
* waits until target vanishes or timeout (in seconds) is passed (AutoWaitTimeout)
*
* @param <PSI> Pattern, String or Image
* @param target The target to wait for it to vanish
* @return true if the target vanishes, otherwise returns false.
*/
public <PSI> boolean waitVanish(PSI target) {
return waitVanish(target, autoWaitTimeout);
}
/**
* waits until target vanishes or timeout (in seconds) is passed
*
* @param <PSI> Pattern, String or Image
* @param target Pattern, String or Image
* @param timeout time in seconds
* @return true if target vanishes, false otherwise and if imagefile is missing.
*/
public <PSI> boolean waitVanish(PSI target, double timeout) {
RepeatableVanish rv = new RepeatableVanish(target);
Image img = rv._image;
String targetStr = img.getName();
Boolean response = true;
if (!img.isValid() && img.hasIOException()) {
response = handleImageMissing(img, false);
}
if (null != response && response) {
log(lvl, "waiting for " + targetStr + " to vanish within %.1f secs", timeout);
if (rv.repeat(timeout)) {
log(lvl, "%s vanished", targetStr);
return true;
}
log(lvl, "%s did not vanish before timeout", targetStr);
return false;
}
return false;
}
//TODO 1.2.0 Region.compare as time optimized Region.exists
/**
* time optimized Region.exists, when image-size == region-size<br>
* 1.1.x: just using exists(img, 0), sizes not checked
*
* @param img image file name
* @return the match or null if not equal
*/
public Match compare(String img) {
return compare(Image.create(img));
}
/**
* time optimized Region.exists, when image-size == region-size<br>
* 1.1.x: just using exists(img, 0), sizes not checked
*
* @param img Image object
* @return the match or null if not equal
*/
public Match compare(Image img) {
return exists(img, 0);
}
/**
* Use findText() instead of find() in cases where the given string could be misinterpreted as an image filename
*
* @param text text
* @param timeout time
* @return the matched region containing the text
* @throws org.sikuli.script.FindFailed if not found
*/
public Match findText(String text, double timeout) throws FindFailed {
// the leading/trailing tab is used to internally switch to text search directly
return wait("\t" + text + "\t", timeout);
}
/**
* Use findText() instead of find() in cases where the given string could be misinterpreted as an image filename
*
* @param text text
* @return the matched region containing the text
* @throws org.sikuli.script.FindFailed if not found
*/
public Match findText(String text) throws FindFailed {
return findText(text, autoWaitTimeout);
}
/**
* Use findAllText() instead of findAll() in cases where the given string could be misinterpreted as an image filename
*
* @param text text
* @return the matched region containing the text
* @throws org.sikuli.script.FindFailed if not found
*/
public Iterator<Match> findAllText(String text) throws FindFailed {
// the leading/trailing tab is used to internally switch to text search directly
return findAll("\t" + text + "\t");
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="find internal methods">
/**
* Match doFind( Pattern/String/Image ) finds the given pattern on the screen and returns the best match without
* waiting.
*/
private <PSI> Match doFind(PSI ptn, Image img, RepeatableFind repeating) {
Finder f = null;
Match m = null;
IScreen s = null;
boolean findingText = false;
ScreenImage simg;
double findTimeout = autoWaitTimeout;
String someText = "";
if (repeating != null) {
findTimeout = repeating.getFindTimeOut();
}
if (repeating != null && repeating._finder != null) {
simg = getScreen().capture(this);
f = repeating._finder;
f.setScreenImage(simg);
f.setRepeating();
if (Settings.FindProfiling) {
Debug.logp("[FindProfiling] Region.doFind repeat: %d msec",
new Date().getTime() - lastSearchTimeRepeat);
}
lastSearchTime = (new Date()).getTime();
f.findRepeat();
} else {
s = getScreen();
lastFindTime = (new Date()).getTime();
if (ptn instanceof String) {
if (((String) ptn).startsWith("\t") && ((String) ptn).endsWith("\t")) {
findingText = true;
someText = ((String) ptn).replaceAll("\\t", "");
} else {
if (img.isValid()) {
lastSearchTime = (new Date()).getTime();
f = checkLastSeenAndCreateFinder(img, findTimeout, null);
if (!f.hasNext()) {
runFinder(f, img);
}
} else if (img.isText()) {
findingText = true;
someText = img.getText();
}
}
if (findingText) {
log(lvl, "doFind: Switching to TextSearch");
if (TextRecognizer.getInstance() != null) {
f = new Finder(getScreen().capture(x, y, w, h), this);
lastSearchTime = (new Date()).getTime();
f.findText(someText);
}
}
} else if (ptn instanceof Pattern) {
if (img.isValid()) {
lastSearchTime = (new Date()).getTime();
f = checkLastSeenAndCreateFinder(img, findTimeout, (Pattern) ptn);
if (!f.hasNext()) {
runFinder(f, ptn);
}
}
} else if (ptn instanceof Image) {
if (img.isValid()) {
lastSearchTime = (new Date()).getTime();
f = checkLastSeenAndCreateFinder(img, findTimeout, null);
if (!f.hasNext()) {
runFinder(f, img);
}
}
} else {
runTime.abortScripting("aborting script at:",
String.format("find, wait, exists: invalid parameter: %s", ptn));
}
if (repeating != null) {
repeating._finder = f;
repeating._image = img;
}
}
if (f != null) {
lastSearchTimeRepeat = lastSearchTime;
lastSearchTime = (new Date()).getTime() - lastSearchTime;
if (f.hasNext()) {
lastFindTime = (new Date()).getTime() - lastFindTime;
m = f.next();
m.setTimes(lastFindTime, lastSearchTime);
if (Settings.FindProfiling) {
Debug.logp("[FindProfiling] Region.doFind final: %d msec", lastSearchTime);
}
}
}
return m;
}
private void runFinder(Finder f, Object target) {
if (Debug.shouldHighlight()) {
if (this.scr.getW() > w + 20 && this.scr.getH() > h + 20) {
highlight(2, "#000255000");
}
}
if (target instanceof Image) {
f.find((Image) target);
} else if (target instanceof Pattern) {
f.find((Pattern) target);
}
}
private Finder checkLastSeenAndCreateFinder(Image img, double findTimeout, Pattern ptn) {
return doCheckLastSeenAndCreateFinder(null, img, findTimeout, ptn);
}
private Finder doCheckLastSeenAndCreateFinder(ScreenImage base, Image img, double findTimeout, Pattern ptn) {
if (base == null) {
base = getScreen().capture(this);
}
boolean shouldCheckLastSeen = false;
float score = 0;
if (!Settings.UseImageFinder && Settings.CheckLastSeen && null != img.getLastSeen()) {
score = (float) (img.getLastSeenScore() - 0.01);
if (ptn != null) {
if (!(ptn.getSimilar() > score)) {
shouldCheckLastSeen = true;
}
}
}
if (shouldCheckLastSeen) {
Region r = Region.create(img.getLastSeen());
if (this.contains(r)) {
Finder f = new Finder(base.getSub(r.getRect()), r);
if (Debug.shouldHighlight()) {
if (this.scr.getW() > w + 10 && this.scr.getH() > h + 10) {
highlight(2, "#000255000");
}
}
if (ptn == null) {
f.find(new Pattern(img).similar(score));
} else {
f.find(new Pattern(ptn).similar(score));
}
if (f.hasNext()) {
log(lvl, "checkLastSeen: still there");
return f;
}
log(lvl, "checkLastSeen: not there");
}
}
if (Settings.UseImageFinder) {
ImageFinder f = new ImageFinder(this);
f.setFindTimeout(findTimeout);
return f;
} else {
return new Finder(base, this);
}
}
/**
* Match findAllNow( Pattern/String/Image ) finds all the given pattern on the screen and returns the best matches
* without waiting.
*/
private <PSI> Iterator<Match> doFindAll(PSI ptn, RepeatableFindAll repeating) {
boolean findingText = false;
Finder f;
ScreenImage simg = getScreen().capture(x, y, w, h);
if (repeating != null && repeating._finder != null) {
f = repeating._finder;
f.setScreenImage(simg);
f.setRepeating();
f.findAllRepeat();
} else {
f = new Finder(simg, this);
Image img = null;
if (ptn instanceof String) {
if (((String) ptn).startsWith("\t") && ((String) ptn).endsWith("\t")) {
findingText = true;
} else {
img = Image.create((String) ptn);
if (img.isValid()) {
f.findAll(img);
} else if (img.isText()) {
findingText = true;
}
}
if (findingText) {
if (TextRecognizer.getInstance() != null) {
log(lvl, "doFindAll: Switching to TextSearch");
f.findAllText((String) ptn);
}
}
} else if (ptn instanceof Pattern) {
if (((Pattern) ptn).isValid()) {
img = ((Pattern) ptn).getImage();
f.findAll((Pattern) ptn);
}
} else if (ptn instanceof Image) {
if (((Image) ptn).isValid()) {
img = ((Image) ptn);
f.findAll((Image) ptn);
}
} else {
log(-1, "doFind: invalid parameter: %s", ptn);
Sikulix.terminate(999);
}
if (repeating != null) {
repeating._finder = f;
repeating._image = img;
}
}
if (f.hasNext()) {
return f;
}
return null;
}
// Repeatable Find ////////////////////////////////
private abstract class Repeatable {
private double findTimeout;
abstract void run();
abstract boolean ifSuccessful();
double getFindTimeOut() {
return findTimeout;
}
// return TRUE if successful before timeout
// return FALSE if otherwise
// throws Exception if any unexpected error occurs
boolean repeat(double timeout) {
findTimeout = timeout;
int MaxTimePerScan = (int) (1000.0 / waitScanRate);
int timeoutMilli = (int) (timeout * 1000);
long begin_t = (new Date()).getTime();
do {
long before_find = (new Date()).getTime();
run();
if (ifSuccessful()) {
return true;
} else if (timeoutMilli < MaxTimePerScan || Settings.UseImageFinder) {
// instant return on first search failed if timeout very small or 0
// or when using new ImageFinder
return false;
}
long after_find = (new Date()).getTime();
if (after_find - before_find < MaxTimePerScan) {
getRobotForRegion().delay((int) (MaxTimePerScan - (after_find - before_find)));
} else {
getRobotForRegion().delay(10);
}
} while (begin_t + timeout * 1000 > (new Date()).getTime());
return false;
}
}
private class RepeatableFind extends Repeatable {
Object _target;
Match _match = null;
Finder _finder = null;
Image _image = null;
public <PSI> RepeatableFind(PSI target, Image img) {
_target = target;
if (img == null) {
_image = Image.getImageFromTarget(target);
} else {
_image = img;
}
}
public Match getMatch() {
if (_finder != null) {
_finder.destroy();
}
return (_match == null) ? _match : new Match(_match);
}
@Override
public void run() {
_match = doFind(_target, _image, this);
}
@Override
boolean ifSuccessful() {
return _match != null;
}
}
private class RepeatableVanish extends RepeatableFind {
public <PSI> RepeatableVanish(PSI target) {
super(target, null);
}
@Override
boolean ifSuccessful() {
return _match == null;
}
}
private class RepeatableFindAll extends Repeatable {
Object _target;
Iterator<Match> _matches = null;
Finder _finder = null;
Image _image = null;
public <PSI> RepeatableFindAll(PSI target, Image img) {
_target = target;
if (img == null) {
_image = Image.getImageFromTarget(target);
} else {
_image = img;
}
}
public Iterator<Match> getMatches() {
return _matches;
}
@Override
public void run() {
_matches = doFindAll(_target, this);
}
@Override
boolean ifSuccessful() {
return _matches != null;
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Find internal support">
// private <PatternStringRegionMatch> Region getRegionFromTarget(PatternStringRegionMatch target) throws FindFailed {
// if (target instanceof Pattern || target instanceof String || target instanceof Image) {
// Match m = find(target);
// if (m != null) {
// return m.setScreen(scr);
// }
// return null;
// }
// if (target instanceof Region) {
// return ((Region) target).setScreen(scr);
// }
// return null;
// }
protected <PSIMRL> Location getLocationFromTarget(PSIMRL target) throws FindFailed {
if (target instanceof Pattern || target instanceof String || target instanceof Image) {
Match m = find(target);
if (m != null) {
if (isOtherScreen()) {
return m.getTarget().setOtherScreen(scr);
} else {
return m.getTarget();
}
}
return null;
}
if (target instanceof Match) {
return ((Match) target).getTarget();
}
if (target instanceof Region) {
return ((Region) target).getCenter();
}
if (target instanceof Location) {
return new Location((Location) target);
}
return null;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Observing">
protected Observer getObserver() {
if (regionObserver == null) {
regionObserver = new Observer(this);
}
return regionObserver;
}
/**
* evaluate if at least one event observer is defined for this region (the observer need not be running)
*
* @return true, if the region has an observer with event observers
*/
public boolean hasObserver() {
if (regionObserver != null) {
return regionObserver.hasObservers();
}
return false;
}
/**
*
* @return true if an observer is running for this region
*/
public boolean isObserving() {
return observing;
}
/**
*
* @return true if any events have happened for this region, false otherwise
*/
public boolean hasEvents() {
return Observing.hasEvents(this);
}
/**
* the region's events are removed from the list
*
* @return the region's happened events as array if any (size might be 0)
*/
public ObserveEvent[] getEvents() {
return Observing.getEvents(this);
}
/**
* the event is removed from the list
*
* @param name event's name
* @return the named event if happened otherwise null
*/
public ObserveEvent getEvent(String name) {
return Observing.getEvent(name);
}
/**
* set the observer with the given name inactive (not checked while observing)
*
* @param name observers name
*/
public void setInactive(String name) {
if (!hasObserver()) {
return;
}
Observing.setActive(name, false);
}
/**
* set the observer with the given name active (checked while observing)
*
* @param name observers name
*/
public void setActive(String name) {
if (!hasObserver()) {
return;
}
Observing.setActive(name, true);
}
/**
* a subsequently started observer in this region should wait for target and notify the given observer about this
* event<br>
* for details about the observe event handler: {@link ObserverCallBack}<br>
* for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br>
*
* @param <PSI> Pattern, String or Image
* @param target Pattern, String or Image
* @param observer ObserverCallBack
* @return the event's name
*/
public <PSI> String onAppear(PSI target, Object observer) {
return onEvent(target, observer, ObserveEvent.Type.APPEAR);
}
/**
* a subsequently started observer in this region should wait for target success and details about the event can be
* obtained using @{link Observing}<br>
* for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br>
*
* @param <PSI> Pattern, String or Image
* @param target Pattern, String or Image
* @return the event's name
*/
public <PSI> String onAppear(PSI target) {
return onEvent(target, null, ObserveEvent.Type.APPEAR);
}
private <PSIC> String onEvent(PSIC targetThreshhold, Object observer, ObserveEvent.Type obsType) {
if (observer != null && (observer.getClass().getName().contains("org.python")
|| observer.getClass().getName().contains("org.jruby"))) {
observer = new ObserverCallBack(observer, obsType);
}
if (! (targetThreshhold instanceof Integer)) {
Image img = Image.getImageFromTarget(targetThreshhold);
Boolean response = true;
if (!img.isValid() && img.hasIOException()) {
response = handleImageMissing(img, false);
if (response == null) {
runTime.abortScripting("onEvent(" + obsType.name() + "): Abort:",
"ImageMissing: " + targetThreshhold.toString());
}
}
}
String name = Observing.add(this, (ObserverCallBack) observer, obsType, targetThreshhold);
log(lvl, "%s: observer %s %s: %s with: %s", toStringShort(), obsType,
(observer == null ? "" : " with callback"), name, targetThreshhold);
return name;
}
/**
* a subsequently started observer in this region should wait for the target to vanish and notify the given observer
* about this event<br>
* for details about the observe event handler: {@link ObserverCallBack}<br>
* for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br>
*
* @param <PSI> Pattern, String or Image
* @param target Pattern, String or Image
* @param observer ObserverCallBack
* @return the event's name
*/
public <PSI> String onVanish(PSI target, Object observer) {
return onEvent(target, observer, ObserveEvent.Type.VANISH);
}
/**
* a subsequently started observer in this region should wait for the target to vanish success and details about the
* event can be obtained using @{link Observing}<br>
* for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br>
*
* @param <PSI> Pattern, String or Image
* @param target Pattern, String or Image
* @return the event's name
*/
public <PSI> String onVanish(PSI target) {
return onEvent(target, null, ObserveEvent.Type.VANISH);
}
/**
* a subsequently started observer in this region should wait for changes in the region and notify the given observer
* about this event for details about the observe event handler: {@link ObserverCallBack} for details about
* APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
*
* @param threshold minimum size of changes (rectangle threshhold x threshold)
* @param observer ObserverCallBack
* @return the event's name
*/
public String onChange(Integer threshold, Object observer) {
return onEvent((threshold > 0 ? threshold : Settings.ObserveMinChangedPixels),
observer, ObserveEvent.Type.CHANGE);
}
/**
* a subsequently started observer in this region should wait for changes in the region success and details about the
* event can be obtained using @{link Observing}<br>
* for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
*
* @param threshold minimum size of changes (rectangle threshhold x threshold)
* @return the event's name
*/
public String onChange(Integer threshold) {
return onEvent((threshold > 0 ? threshold : Settings.ObserveMinChangedPixels),
null, ObserveEvent.Type.CHANGE);
}
/**
* a subsequently started observer in this region should wait for changes in the region and notify the given observer
* about this event <br>
* minimum size of changes used: Settings.ObserveMinChangedPixels for details about the observe event handler:
* {@link ObserverCallBack} for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
*
* @param observer ObserverCallBack
* @return the event's name
*/
public String onChange(Object observer) {
return onEvent(Settings.ObserveMinChangedPixels, observer, ObserveEvent.Type.CHANGE);
}
/**
* a subsequently started observer in this region should wait for changes in the region success and details about the
* event can be obtained using @{link Observing}<br>
* minimum size of changes used: Settings.ObserveMinChangedPixels for details about APPEAR/VANISH/CHANGE events:
* {@link ObserveEvent}
*
* @return the event's name
*/
public String onChange() {
return onEvent(Settings.ObserveMinChangedPixels, null, ObserveEvent.Type.CHANGE);
}
//<editor-fold defaultstate="collapsed" desc="obsolete">
// /**
// *INTERNAL USE ONLY: for use with scripting API bridges
// * @param <PSI> Pattern, String or Image
// * @param target Pattern, String or Image
// * @param observer ObserverCallBack
// * @return the event's name
// */
// public <PSI> String onAppearJ(PSI target, Object observer) {
// return onEvent(target, observer, ObserveEvent.Type.APPEAR);
// }
//
// /**
// *INTERNAL USE ONLY: for use with scripting API bridges
// * @param <PSI> Pattern, String or Image
// * @param target Pattern, String or Image
// * @param observer ObserverCallBack
// * @return the event's name
// */
// public <PSI> String onVanishJ(PSI target, Object observer) {
// return onEvent(target, observer, ObserveEvent.Type.VANISH);
// }
//
// /**
// *INTERNAL USE ONLY: for use with scripting API bridges
// * @param threshold min pixel size - 0 = ObserveMinChangedPixels
// * @param observer ObserverCallBack
// * @return the event's name
// */
// public String onChangeJ(int threshold, Object observer) {
// return onEvent( (threshold > 0 ? threshold : Settings.ObserveMinChangedPixels),
// observer, ObserveEvent.Type.CHANGE);
// }
//
//</editor-fold>
public String onChangeDo(Integer threshold, Object observer) {
String name = Observing.add(this, (ObserverCallBack) observer, ObserveEvent.Type.CHANGE, threshold);
log(lvl, "%s: onChange%s: %s minSize: %d", toStringShort(),
(observer == null ? "" : " with callback"), name, threshold);
return name;
}
/**
* start an observer in this region that runs forever (use stopObserving() in handler) for details about the observe
* event handler: {@link ObserverCallBack} for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
*
* @return false if not possible, true if events have happened
*/
public boolean observe() {
return observe(Float.POSITIVE_INFINITY);
}
/**
* start an observer in this region for the given time for details about the observe event handler:
* {@link ObserverCallBack} for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
*
* @param secs time in seconds the observer should run
* @return false if not possible, true if events have happened
*/
public boolean observe(double secs) {
return observeDo(secs);
}
/**
* INTERNAL USE ONLY: for use with scripting API bridges
*
* @param secs time in seconds the observer should run
* @return false if not possible, true if events have happened
*/
public boolean observeInLine(double secs) {
return observeDo(secs);
}
private boolean observeDo(double secs) {
if (regionObserver == null) {
Debug.error("Region: observe: Nothing to observe (Region might be invalid): " + this.toStringShort());
return false;
}
if (observing) {
Debug.error("Region: observe: already running for this region. Only one allowed!");
return false;
}
log(lvl, "observe: starting in " + this.toStringShort() + " for " + secs + " seconds");
int MaxTimePerScan = (int) (1000.0 / observeScanRate);
long begin_t = (new Date()).getTime();
long stop_t;
if (secs > Long.MAX_VALUE) {
stop_t = Long.MAX_VALUE;
} else {
stop_t = begin_t + (long) (secs * 1000);
}
regionObserver.initialize();
observing = true;
Observing.addRunningObserver(this);
while (observing && stop_t > (new Date()).getTime()) {
long before_find = (new Date()).getTime();
ScreenImage simg = getScreen().capture(x, y, w, h);
if (!regionObserver.update(simg)) {
observing = false;
break;
}
if (!observing) {
break;
}
long after_find = (new Date()).getTime();
try {
if (after_find - before_find < MaxTimePerScan) {
Thread.sleep((int) (MaxTimePerScan - (after_find - before_find)));
}
} catch (Exception e) {
}
}
boolean observeSuccess = false;
if (observing) {
observing = false;
log(lvl, "observe: stopped due to timeout in "
+ this.toStringShort() + " for " + secs + " seconds");
} else {
log(lvl, "observe: ended successfully: " + this.toStringShort());
observeSuccess = Observing.hasEvents(this);
}
return observeSuccess;
}
/**
* start an observer in this region for the given time that runs in background for details about the observe event
* handler: {@link ObserverCallBack} for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
*
* @param secs time in seconds the observer should run
* @return false if not possible, true otherwise
*/
public boolean observeInBackground(double secs) {
if (observing) {
Debug.error("Region: observeInBackground: already running for this region. Only one allowed!");
return false;
}
Thread observeThread = new Thread(new ObserverThread(secs));
observeThread.start();
log(lvl, "observeInBackground now running");
return true;
}
private class ObserverThread implements Runnable {
private double time;
ObserverThread(double time) {
this.time = time;
}
@Override
public void run() {
observeDo(time);
}
}
/**
* stops a running observer
*/
public void stopObserver() {
log(lvl, "observe: request to stop observer for " + this.toStringShort());
observing = false;
}
/**
* stops a running observer printing an info message
*
* @param message text
*/
public void stopObserver(String message) {
Debug.info(message);
stopObserver();
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Mouse actions - clicking">
protected Location checkMatch() {
if (lastMatch != null) {
return lastMatch.getTarget();
}
return getTarget();
}
/**
* move the mouse pointer to region's last successful match <br>use center if no lastMatch <br>
* if region is a match: move to targetOffset <br>same as mouseMove
*
* @return 1 if possible, 0 otherwise
*/
public int hover() {
try { // needed to cut throw chain for FindFailed
return hover(checkMatch());
} catch (FindFailed ex) {
}
return 0;
}
/**
* move the mouse pointer to the given target location<br> same as mouseMove<br> Pattern or Filename - do a find
* before and use the match<br> Region - position at center<br> Match - position at match's targetOffset<br> Location
* - position at that point<br>
*
* @param <PFRML> to search: Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int hover(PFRML target) throws FindFailed {
log(lvl, "hover: " + target);
return mouseMove(target);
}
/**
* left click at the region's last successful match <br>use center if no lastMatch <br>if region is a match: click
* targetOffset
*
* @return 1 if possible, 0 otherwise
*/
public int click() {
try { // needed to cut throw chain for FindFailed
return click(checkMatch(), 0);
} catch (FindFailed ex) {
return 0;
}
}
/**
* left click at the given target location<br> Pattern or Filename - do a find before and use the match<br> Region -
* position at center<br> Match - position at match's targetOffset<br>
* Location - position at that point<br>
*
* @param <PFRML> to search: Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int click(PFRML target) throws FindFailed {
return click(target, 0);
}
/**
* left click at the given target location<br> holding down the given modifier keys<br>
* Pattern or Filename - do a find before and use the match<br> Region - position at center<br>
* Match - position at match's targetOffset<br> Location - position at that point<br>
*
* @param <PFRML> to search: Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @param modifiers the value of the resulting bitmask (see KeyModifier)
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int click(PFRML target, Integer modifiers) throws FindFailed {
Location loc = getLocationFromTarget(target);
int ret = Mouse.click(loc, InputEvent.BUTTON1_MASK, modifiers, false, this);
//TODO SikuliActionManager.getInstance().clickTarget(this, target, _lastScreenImage, _lastMatch);
return ret;
}
/**
* double click at the region's last successful match <br>use center if no lastMatch <br>if region is a match: click
* targetOffset
*
* @return 1 if possible, 0 otherwise
*/
public int doubleClick() {
try { // needed to cut throw chain for FindFailed
return doubleClick(checkMatch(), 0);
} catch (FindFailed ex) {
return 0;
}
}
/**
* double click at the given target location<br> Pattern or Filename - do a find before and use the match<br> Region -
* position at center<br> Match - position at match's targetOffset<br>
* Location - position at that point<br>
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int doubleClick(PFRML target) throws FindFailed {
return doubleClick(target, 0);
}
/**
* double click at the given target location<br> holding down the given modifier keys<br>
* Pattern or Filename - do a find before and use the match<br> Region - position at center<br > Match - position at
* match's targetOffset<br> Location - position at that point<br>
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @param modifiers the value of the resulting bitmask (see KeyModifier)
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int doubleClick(PFRML target, Integer modifiers) throws FindFailed {
Location loc = getLocationFromTarget(target);
int ret = Mouse.click(loc, InputEvent.BUTTON1_MASK, modifiers, true, this);
//TODO SikuliActionManager.getInstance().doubleClickTarget(this, target, _lastScreenImage, _lastMatch);
return ret;
}
/**
* right click at the region's last successful match <br>use center if no lastMatch <br>if region is a match: click
* targetOffset
*
* @return 1 if possible, 0 otherwise
*/
public int rightClick() {
try { // needed to cut throw chain for FindFailed
return rightClick(checkMatch(), 0);
} catch (FindFailed ex) {
return 0;
}
}
/**
* right click at the given target location<br> Pattern or Filename - do a find before and use the match<br> Region -
* position at center<br> Match - position at match's targetOffset<br > Location - position at that point<br>
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int rightClick(PFRML target) throws FindFailed {
return rightClick(target, 0);
}
/**
* right click at the given target location<br> holding down the given modifier keys<br>
* Pattern or Filename - do a find before and use the match<br> Region - position at center<br > Match - position at
* match's targetOffset<br> Location - position at that point<br>
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @param modifiers the value of the resulting bitmask (see KeyModifier)
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int rightClick(PFRML target, Integer modifiers) throws FindFailed {
Location loc = getLocationFromTarget(target);
int ret = Mouse.click(loc, InputEvent.BUTTON3_MASK, modifiers, false, this);
//TODO SikuliActionManager.getInstance().rightClickTarget(this, target, _lastScreenImage, _lastMatch);
return ret;
}
/**
* time in milliseconds to delay between button down/up at next click only (max 1000)
*
* @param millisecs value
*/
public void delayClick(int millisecs) {
Settings.ClickDelay = millisecs;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Mouse actions - drag & drop">
/**
* Drag from region's last match and drop at given target <br>applying Settings.DelayAfterDrag and DelayBeforeDrop
* <br> using left mouse button
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed if the Find operation failed
*/
public <PFRML> int dragDrop(PFRML target) throws FindFailed {
return dragDrop(lastMatch, target);
}
/**
* Drag from a position and drop to another using left mouse button<br>applying Settings.DelayAfterDrag and
* DelayBeforeDrop
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param t1 source position
* @param t2 destination position
* @return 1 if possible, 0 otherwise
* @throws FindFailed if the Find operation failed
*/
public <PFRML> int dragDrop(PFRML t1, PFRML t2) throws FindFailed {
Location loc1 = getLocationFromTarget(t1);
Location loc2 = getLocationFromTarget(t2);
int retVal = 0;
if (loc1 != null && loc2 != null) {
IRobot r1 = loc1.getRobotForPoint("drag");
IRobot r2 = loc2.getRobotForPoint("drop");
if (r1 != null && r2 != null) {
Mouse.use(this);
r1.smoothMove(loc1);
r1.delay((int) (Settings.DelayBeforeMouseDown * 1000));
r1.mouseDown(InputEvent.BUTTON1_MASK);
double DelayBeforeDrag = Settings.DelayBeforeDrag;
if (DelayBeforeDrag < 0.0) {
DelayBeforeDrag = Settings.DelayAfterDrag;
}
r1.delay((int) (DelayBeforeDrag * 1000));
r2.smoothMove(loc2);
r2.delay((int) (Settings.DelayBeforeDrop * 1000));
r2.mouseUp(InputEvent.BUTTON1_MASK);
Mouse.let(this);
retVal = 1;
}
}
Settings.DelayBeforeMouseDown = Settings.DelayValue;
Settings.DelayAfterDrag = Settings.DelayValue;
Settings.DelayBeforeDrag = -Settings.DelayValue;
Settings.DelayBeforeDrop = Settings.DelayValue;
return retVal;
}
/**
* Prepare a drag action: move mouse to given target <br>press and hold left mouse button <br >wait
* Settings.DelayAfterDrag
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed if not found
*/
public <PFRML> int drag(PFRML target) throws FindFailed {
Location loc = getLocationFromTarget(target);
int retVal = 0;
if (loc != null) {
IRobot r = loc.getRobotForPoint("drag");
if (r != null) {
Mouse.use(this);
r.smoothMove(loc);
r.delay((int) (Settings.DelayBeforeMouseDown * 1000));
r.mouseDown(InputEvent.BUTTON1_MASK);
double DelayBeforeDrag = Settings.DelayBeforeDrag;
if (DelayBeforeDrag < 0.0) {
DelayBeforeDrag = Settings.DelayAfterDrag;
}
r.delay((int) (DelayBeforeDrag * 1000));
r.waitForIdle();
Mouse.let(this);
retVal = 1;
}
}
Settings.DelayBeforeMouseDown = Settings.DelayValue;
Settings.DelayAfterDrag = Settings.DelayValue;
Settings.DelayBeforeDrag = -Settings.DelayValue;
Settings.DelayBeforeDrop = Settings.DelayValue;
return retVal;
}
/**
* finalize a drag action with a drop: move mouse to given target <br>
* wait Settings.DelayBeforeDrop <br>
* before releasing the left mouse button
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed if not found
*/
public <PFRML> int dropAt(PFRML target) throws FindFailed {
Location loc = getLocationFromTarget(target);
int retVal = 0;
if (loc != null) {
IRobot r = loc.getRobotForPoint("drag");
if (r != null) {
Mouse.use(this);
r.smoothMove(loc);
r.delay((int) (Settings.DelayBeforeDrop * 1000));
r.mouseUp(InputEvent.BUTTON1_MASK);
r.waitForIdle();
Mouse.let(this);
retVal = 1;
}
}
Settings.DelayBeforeMouseDown = Settings.DelayValue;
Settings.DelayAfterDrag = Settings.DelayValue;
Settings.DelayBeforeDrag = -Settings.DelayValue;
Settings.DelayBeforeDrop = Settings.DelayValue;
return retVal;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Mouse actions - low level + Wheel">
/**
* press and hold the specified buttons - use + to combine Button.LEFT left mouse button Button.MIDDLE middle mouse
* button Button.RIGHT right mouse button
*
* @param buttons spec
*/
public void mouseDown(int buttons) {
Mouse.down(buttons, this);
}
/**
* release all currently held buttons
*/
public void mouseUp() {
Mouse.up(0, this);
}
/**
* release the specified mouse buttons (see mouseDown) if buttons==0, all currently held buttons are released
*
* @param buttons spec
*/
public void mouseUp(int buttons) {
Mouse.up(buttons, this);
}
/**
* move the mouse pointer to the region's last successful match<br>same as hover<br>
*
* @return 1 if possible, 0 otherwise
*/
public int mouseMove() {
if (lastMatch != null) {
try {
return mouseMove(lastMatch);
} catch (FindFailed ex) {
return 0;
}
}
return 0;
}
/**
* move the mouse pointer to the given target location<br> same as hover<br> Pattern or Filename - do a find before
* and use the match<br> Region - position at center<br> Match - position at match's targetOffset<br>
* Location - position at that point<br>
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @return 1 if possible, 0 otherwise
* @throws FindFailed for Pattern or Filename
*/
public <PFRML> int mouseMove(PFRML target) throws FindFailed {
Location loc = getLocationFromTarget(target);
return Mouse.move(loc, this);
}
/**
* move the mouse from the current position to the offset position given by the parameters
*
* @param xoff horizontal offset (< 0 left, > 0 right)
* @param yoff vertical offset (< 0 up, > 0 down)
* @return 1 if possible, 0 otherwise
*/
public int mouseMove(int xoff, int yoff) {
try {
return mouseMove(Mouse.at().offset(xoff, yoff));
} catch (Exception ex) {
return 0;
}
}
/**
* Move the wheel at the current mouse position<br> the given steps in the given direction: <br >Button.WHEEL_DOWN,
* Button.WHEEL_UP
*
* @param direction to move the wheel
* @param steps the number of steps
* @return 1 in any case
*/
public int wheel(int direction, int steps) {
Mouse.wheel(direction, steps, this);
return 1;
}
/**
* move the mouse pointer to the given target location<br> and move the wheel the given steps in the given direction:
* <br>Button.WHEEL_DOWN, Button.WHEEL_UP
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location target
* @param target Pattern, Filename, Text, Region, Match or Location
* @param direction to move the wheel
* @param steps the number of steps
* @return 1 if possible, 0 otherwise
* @throws FindFailed if the Find operation failed
*/
public <PFRML> int wheel(PFRML target, int direction, int steps) throws FindFailed {
return wheel(target, direction, steps, Mouse.WHEEL_STEP_DELAY);
}
/**
* move the mouse pointer to the given target location<br> and move the wheel the given steps in the given direction:
* <br>Button.WHEEL_DOWN, Button.WHEEL_UP
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location target
* @param target Pattern, Filename, Text, Region, Match or Location
* @param direction to move the wheel
* @param steps the number of steps
* @param stepDelay number of miliseconds to wait when incrementing the step value
* @return 1 if possible, 0 otherwise
* @throws FindFailed if the Find operation failed
*/
public <PFRML> int wheel(PFRML target, int direction, int steps, int stepDelay) throws FindFailed {
Location loc = getLocationFromTarget(target);
if (loc != null) {
Mouse.use(this);
Mouse.keep(this);
Mouse.move(loc, this);
Mouse.wheel(direction, steps, this, stepDelay);
Mouse.let(this);
return 1;
}
return 0;
}
/**
*
* @return current location of mouse pointer
* @deprecated use {@link Mouse#at()} instead
*/
@Deprecated
public static Location atMouse() {
return Mouse.at();
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Keyboard actions + paste">
/**
* press and hold the given key use a constant from java.awt.event.KeyEvent which might be special in the current
* machine/system environment
*
* @param keycode Java KeyCode
*/
public void keyDown(int keycode) {
getRobotForRegion().keyDown(keycode);
}
/**
* press and hold the given keys including modifier keys <br>use the key constants defined in class Key, <br>which
* only provides a subset of a US-QWERTY PC keyboard layout <br>might be mixed with simple characters
* <br>use + to concatenate Key constants
*
* @param keys valid keys
*/
public void keyDown(String keys) {
getRobotForRegion().keyDown(keys);
}
/**
* release all currently pressed keys
*/
public void keyUp() {
getRobotForRegion().keyUp();
}
/**
* release the given keys (see keyDown(keycode) )
*
* @param keycode Java KeyCode
*/
public void keyUp(int keycode) {
getRobotForRegion().keyUp(keycode);
}
/**
* release the given keys (see keyDown(keys) )
*
* @param keys valid keys
*/
public void keyUp(String keys) {
getRobotForRegion().keyUp(keys);
}
/**
* Compact alternative for type() with more options <br>
* - special keys and options are coded as #XN. or #X+ or #X- <br>
* where X is a refrence for a special key and N is an optional repeat factor <br>
* A modifier key as #X. modifies the next following key<br>
* the trailing . ends the special key, the + (press and hold) or - (release) does the same, <br>
* but signals press-and-hold or release additionally.<br>
* except #W / #w all special keys are not case-sensitive<br>
* a #wn. inserts a wait of n millisecs or n secs if n less than 60 <br>
* a #Wn. sets the type delay for the following keys (must be > 60 and denotes millisecs) - otherwise taken as
* normal wait<br>
* Example: wait 2 secs then type CMD/CTRL - N then wait 1 sec then type DOWN 3 times<br>
* Windows/Linux: write("#w2.#C.n#W1.#d3.")<br>
* Mac: write("#w2.#M.n#W1.#D3.")<br>
* for more details about the special key codes and examples consult the docs <br>
*
* @param text a coded text interpreted as a series of key actions (press/hold/release)
* @return 0 for success 1 otherwise
*/
public int write(String text) {
Debug.info("Write: " + text);
char c;
String token, tokenSave;
String modifier = "";
int k;
IRobot robot = getRobotForRegion();
int pause = 20 + (Settings.TypeDelay > 1 ? 1000 : (int) (Settings.TypeDelay * 1000));
Settings.TypeDelay = 0.0;
robot.typeStarts();
for (int i = 0; i < text.length(); i++) {
log(lvl + 1, "write: (%d) %s", i, text.substring(i));
c = text.charAt(i);
token = null;
boolean isModifier = false;
if (c == '#') {
if (text.charAt(i + 1) == '#') {
log(lvl, "write at: %d: %s", i, c);
i += 1;
continue;
}
if (text.charAt(i + 2) == '+' || text.charAt(i + 2) == '-') {
token = text.substring(i, i + 3);
isModifier = true;
} else if (-1 < (k = text.indexOf('.', i))) {
if (k > -1) {
token = text.substring(i, k + 1);
if (token.length() > Key.keyMaxLength || token.substring(1).contains("#")) {
token = null;
}
}
}
}
Integer key = -1;
if (token == null) {
log(lvl + 1, "write: %d: %s", i, c);
} else {
log(lvl + 1, "write: token at %d: %s", i, token);
int repeat = 0;
if (token.toUpperCase().startsWith("#W")) {
if (token.length() > 3) {
i += token.length() - 1;
int t = 0;
try {
t = Integer.parseInt(token.substring(2, token.length() - 1));
} catch (NumberFormatException ex) {
}
if ((token.startsWith("#w") && t > 60)) {
pause = 20 + (t > 1000 ? 1000 : t);
log(lvl + 1, "write: type delay: " + t);
} else {
log(lvl + 1, "write: wait: " + t);
robot.delay((t < 60 ? t * 1000 : t));
}
continue;
}
}
tokenSave = token;
token = token.substring(0, 2).toUpperCase() + ".";
if (Key.isRepeatable(token)) {
try {
repeat = Integer.parseInt(tokenSave.substring(2, tokenSave.length() - 1));
} catch (NumberFormatException ex) {
token = tokenSave;
}
} else if (tokenSave.length() == 3 && Key.isModifier(tokenSave.toUpperCase())) {
i += tokenSave.length() - 1;
modifier += tokenSave.substring(1, 2).toUpperCase();
continue;
} else {
token = tokenSave;
}
if (-1 < (key = Key.toJavaKeyCodeFromText(token))) {
if (repeat > 0) {
log(lvl + 1, "write: %s Repeating: %d", token, repeat);
} else {
log(lvl + 1, "write: %s", tokenSave);
repeat = 1;
}
i += tokenSave.length() - 1;
if (isModifier) {
if (tokenSave.endsWith("+")) {
robot.keyDown(key);
} else {
robot.keyUp(key);
}
continue;
}
if (repeat > 1) {
for (int n = 0; n < repeat; n++) {
robot.typeKey(key.intValue());
}
continue;
}
}
}
if (!modifier.isEmpty()) {
log(lvl + 1, "write: modifier + " + modifier);
for (int n = 0; n < modifier.length(); n++) {
robot.keyDown(Key.toJavaKeyCodeFromText(String.format("#%s.", modifier.substring(n, n + 1))));
}
}
if (key > -1) {
robot.typeKey(key.intValue());
} else {
robot.typeChar(c, IRobot.KeyMode.PRESS_RELEASE);
}
if (!modifier.isEmpty()) {
log(lvl + 1, "write: modifier - " + modifier);
for (int n = 0; n < modifier.length(); n++) {
robot.keyUp(Key.toJavaKeyCodeFromText(String.format("#%s.", modifier.substring(n, n + 1))));
}
}
robot.delay(pause);
modifier = "";
}
robot.typeEnds();
robot.waitForIdle();
return 0;
}
/**
* enters the given text one character/key after another using keyDown/keyUp
* <br>about the usable Key constants see keyDown(keys) <br>Class Key only provides a subset of a US-QWERTY PC
* keyboard layout<br>the text is entered at the current position of the focus/carret
*
* @param text containing characters and/or Key constants
* @return 1 if possible, 0 otherwise
*/
public int type(String text) {
try {
return keyin(null, text, 0);
} catch (FindFailed ex) {
return 0;
}
}
/**
* enters the given text one character/key after another using keyDown/keyUp<br>while holding down the given modifier
* keys <br>about the usable Key constants see keyDown(keys) <br>Class Key only provides a subset of a US-QWERTY PC
* keyboard layout<br>the text is entered at the current position of the focus/carret
*
* @param text containing characters and/or Key constants
* @param modifiers constants according to class KeyModifiers
* @return 1 if possible, 0 otherwise
*/
public int type(String text, int modifiers) {
try {
return keyin(null, text, modifiers);
} catch (FindFailed findFailed) {
return 0;
}
}
/**
* enters the given text one character/key after another using
*
* keyDown/keyUp<br>while holding down the given modifier keys <br>about the usable Key constants see keyDown(keys)
* <br>Class Key only provides a subset of a US-QWERTY PC keyboard layout<br>the text is entered at the current
* position of the focus/carret
*
*
* @param text containing characters and/or Key constants
* @param modifiers constants according to class Key - combine using +
* @return 1 if possible, 0 otherwise
*/
public int type(String text, String modifiers) {
String target = null;
int modifiersNew = Key.convertModifiers(modifiers);
if (modifiersNew == 0) {
target = text;
text = modifiers;
}
try {
return keyin(target, text, modifiersNew);
} catch (FindFailed findFailed) {
return 0;
}
}
/**
* first does a click(target) at the given target position to gain focus/carret <br>enters the given text one
* character/key after another using keyDown/keyUp <br>about the usable Key constants see keyDown(keys)
* <br>Class Key only provides a subset of a US-QWERTY PC keyboard layout
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @param text containing characters and/or Key constants
* @return 1 if possible, 0 otherwise
* @throws FindFailed if not found
*/
public <PFRML> int type(PFRML target, String text) throws FindFailed {
return keyin(target, text, 0);
}
/**
* first does a click(target) at the given target position to gain focus/carret <br>enters the given text one
* character/key after another using keyDown/keyUp <br>while holding down the given modifier keys<br>about the usable
* Key constants see keyDown(keys) <br>Class Key only provides a subset of a US-QWERTY PC keyboard layout
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @param text containing characters and/or Key constants
* @param modifiers constants according to class KeyModifiers
* @return 1 if possible, 0 otherwise
* @throws FindFailed if not found
*/
public <PFRML> int type(PFRML target, String text, int modifiers) throws FindFailed {
return keyin(target, text, modifiers);
}
/**
* first does a click(target) at the given target position to gain focus/carret <br>enters the given text one
* character/key after another using keyDown/keyUp <br>while holding down the given modifier keys<br>about the usable
* Key constants see keyDown(keys) <br>Class Key only provides a subset of a US-QWERTY PC keyboard layout
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location
* @param target Pattern, Filename, Text, Region, Match or Location
* @param text containing characters and/or Key constants
* @param modifiers constants according to class Key - combine using +
* @return 1 if possible, 0 otherwise
* @throws FindFailed if not found
*/
public <PFRML> int type(PFRML target, String text, String modifiers) throws FindFailed {
int modifiersNew = Key.convertModifiers(modifiers);
return keyin(target, text, modifiersNew);
}
private <PFRML> int keyin(PFRML target, String text, int modifiers)
throws FindFailed {
if (target != null && 0 == click(target, 0)) {
return 0;
}
Debug profiler = Debug.startTimer("Region.type");
if (text != null && !"".equals(text)) {
String showText = "";
for (int i = 0; i < text.length(); i++) {
showText += Key.toJavaKeyCodeText(text.charAt(i));
}
String modText = "";
String modWindows = null;
if ((modifiers & KeyModifier.WIN) != 0) {
modifiers -= KeyModifier.WIN;
modifiers |= KeyModifier.META;
log(lvl, "Key.WIN as modifier");
modWindows = "Windows";
}
if (modifiers != 0) {
modText = String.format("( %s ) ", KeyEvent.getKeyModifiersText(modifiers));
if (modWindows != null) {
modText = modText.replace("Meta", modWindows);
}
}
Debug.action("%s TYPE \"%s\"", modText, showText);
log(lvl, "%s TYPE \"%s\"", modText, showText);
profiler.lap("before getting Robot");
IRobot r = getRobotForRegion();
int pause = 20 + (Settings.TypeDelay > 1 ? 1000 : (int) (Settings.TypeDelay * 1000));
Settings.TypeDelay = 0.0;
profiler.lap("before typing");
r.typeStarts();
for (int i = 0; i < text.length(); i++) {
r.pressModifiers(modifiers);
r.typeChar(text.charAt(i), IRobot.KeyMode.PRESS_RELEASE);
r.releaseModifiers(modifiers);
r.delay(pause);
}
r.typeEnds();
profiler.lap("after typing, before waitForIdle");
r.waitForIdle();
profiler.end();
return 1;
}
return 0;
}
/**
* time in milliseconds to delay between each character at next type only (max 1000)
*
* @param millisecs value
*/
public void delayType(int millisecs) {
Settings.TypeDelay = millisecs;
}
/**
* pastes the text at the current position of the focus/carret <br>using the clipboard and strg/ctrl/cmd-v (paste
* keyboard shortcut)
*
* @param text a string, which might contain unicode characters
* @return 0 if possible, 1 otherwise
*/
public int paste(String text) {
try {
return paste(null, text);
} catch (FindFailed ex) {
return 1;
}
}
/**
* first does a click(target) at the given target position to gain focus/carret <br> and then pastes the text <br>
* using the clipboard and strg/ctrl/cmd-v (paste keyboard shortcut)
*
* @param <PFRML> Pattern, Filename, Text, Region, Match or Location target
* @param target Pattern, Filename, Text, Region, Match or Location
* @param text a string, which might contain unicode characters
* @return 0 if possible, 1 otherwise
* @throws FindFailed if not found
*/
public <PFRML> int paste(PFRML target, String text) throws FindFailed {
if (target != null) {
click(target, 0);
}
if (text != null) {
App.setClipboard(text);
int mod = Key.getHotkeyModifier();
IRobot r = getRobotForRegion();
r.keyDown(mod);
r.keyDown(KeyEvent.VK_V);
r.keyUp(KeyEvent.VK_V);
r.keyUp(mod);
return 0;
}
return 1;
}
//</editor-fold>
//<editor-fold desc="Mobile actions (Android)">
private ADBDevice adbDevice = null;
private ADBScreen adbScreen = null;
private boolean isAndroid() {
if (isOtherScreen()) {
IScreen scr = getScreen();
if (scr instanceof ADBScreen) {
adbScreen = (ADBScreen) scr;
adbDevice = adbScreen.getDevice();
return true;
}
}
return false;
}
/**
* EXPERIMENTAL: for Android over ADB
*
* @param <PFRML> Pattern, String, Image, Match, Region or Location
* @param target PFRML
* @throws FindFailed image not found
*/
public <PFRML> void aTap(PFRML target) throws FindFailed {
if (isAndroid() && adbDevice != null) {
Location loc = getLocationFromTarget(target);
if (loc != null) {
adbDevice.tap(loc.x, loc.y);
RunTime.pause(adbScreen.waitAfterAction);
}
}
}
/**
* EXPERIMENTAL: for Android over ADB
*
* @param text text
*/
public void aInput(String text) {
if (isAndroid() && adbDevice != null) {
adbDevice.input(text);
}
}
/**
* EXPERIMENTAL: for Android over ADB
*
* @param key key
*/
public void aKey(int key) {
if (isAndroid() && adbDevice != null) {
adbDevice.inputKeyEvent(key);
}
}
/**
* EXPERIMENTAL: for Android over ADB
*
* @param <PFRML> Pattern, String, Image, Match, Region or Location
* @param from PFRML
* @param to PFRML
* @throws FindFailed image not found
*/
public <PFRML> void aSwipe(PFRML from, PFRML to) throws FindFailed {
if (isAndroid() && adbDevice != null) {
Location locFrom = getLocationFromTarget(from);
Location locTo = getLocationFromTarget(to);
if (locFrom != null && locTo != null) {
adbDevice.swipe(locFrom.x, locFrom.y, locTo.x, locTo.y);
RunTime.pause(adbScreen.waitAfterAction);
}
}
}
/**
* EXPERIMENTAL: for Android over ADB
*/
public void aSwipeUp() {
int midX = (int) (w/2);
int swipeStep = (int) (h/5);
try {
aSwipe(new Location(midX, h - swipeStep), new Location(midX, swipeStep));
} catch (FindFailed findFailed) {
}
}
/**
* EXPERIMENTAL: for Android over ADB
*/
public void aSwipeDown() {
int midX = (int) (w/2);
int swipeStep = (int) (h/5);
try {
aSwipe(new Location(midX, swipeStep), new Location(midX, h - swipeStep));
} catch (FindFailed findFailed) {
}
}
/**
* EXPERIMENTAL: for Android over ADB
*/
public void aSwipeLeft() {
int midY = (int) (h/2);
int swipeStep = (int) (w/5);
try {
aSwipe(new Location(w - swipeStep, midY), new Location(swipeStep, midY));
} catch (FindFailed findFailed) {
}
}
/**
* EXPERIMENTAL: for Android over ADB
*/
public void aSwipeRight() {
int midY = (int) (h/2);
int swipeStep = (int) (w/5);
try {
aSwipe(new Location(swipeStep, midY), new Location(w - swipeStep, midY));
} catch (FindFailed findFailed) {
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="OCR - read text from Screen">
/**
* STILL EXPERIMENTAL: tries to read the text in this region<br> might contain misread characters, NL characters and
* other stuff, when interpreting contained grafics as text<br>
* Best results: one line of text with no grafics in the line
*
* @return the text read (utf8 encoded)
*/
public String text() {
if (Settings.OcrTextRead) {
ScreenImage simg = getScreen().capture(x, y, w, h);
TextRecognizer tr = TextRecognizer.getInstance();
if (tr == null) {
Debug.error("text: text recognition is now switched off");
return "--- no text ---";
}
String textRead = tr.recognize(simg);
log(lvl, "text: #(" + textRead + ")#");
return textRead;
}
Debug.error("text: text recognition is currently switched off");
return "--- no text ---";
}
/**
* VERY EXPERIMENTAL: returns a list of matches, that represent single words, that have been found in this region<br>
* the match's x,y,w,h the region of the word<br> Match.getText() returns the word (utf8) at this match<br>
* Match.getScore() returns a value between 0 ... 1, that represents some OCR-confidence value<br > (the higher, the
* better the OCR engine thinks the result is)
*
* @return a list of matches
*/
public List<Match> listText() {
if (Settings.OcrTextRead) {
ScreenImage simg = getScreen().capture(x, y, w, h);
TextRecognizer tr = TextRecognizer.getInstance();
if (tr == null) {
Debug.error("text: text recognition is now switched off");
return null;
}
log(lvl, "listText: scanning %s", this);
return tr.listText(simg, this);
}
Debug.error("text: text recognition is currently switched off");
return null;
}
//</editor-fold>
}