package abbot.swt;
import java.awt.AWTException;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.HashMap;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
/**
* Provides the functionality of <code>java.awt.Robot</code> for use
* in SWT environments. Thus this class should be regarded as "equivalent to"
* <code>java.awt.Robot</code>, not <code>abbot.tester.awt.Robot</code>.
*
* @author Kevin Dale
* @version $Id: Robot.java,v 1.3 2006-10-20 13:40:22 alexander_smirnoff Exp $
*
**/
public class Robot /*extends java.awt.Robot*/{
public static final String copyright = "Licensed Materials -- Property of IBM\n"+
"(c) Copyright International Business Machines Corporation, 2003\nUS Government "+
"Users Restricted Rights - Use, duplication or disclosure restricted by GSA "+
"ADP Schedule Contract with IBM Corp.";
/** the # of modifiers- like SWT.CTRL, SWT.ALT, etc- that are currently defined */
private static final int MODIFIER_COUNT= 7;
/** mask used to identify if any mouse buttons were clicked */
private static final int BUTTON_MASK = (SWT.BUTTON1|SWT.BUTTON2|SWT.BUTTON3);
/** the display associated with this robot */
private Display displayProperty;
/** the java.awt.Robot that does all the leg-work for us */
private java.awt.Robot robot;
/** simple internal class for storing info about a keystroke **/
class CharCode{
public int keycode;
public boolean shift;
public char character;
public CharCode(char character, int keycode, int shift){
this.keycode = keycode;
this.shift = (shift==1);
this.character = character;
}
}
/** key mappings from chars to keycodes **/
private static int[][] mappings = {
{SWT.ESC, KeyEvent.VK_ESCAPE,0},
{SWT.DEL, KeyEvent.VK_DELETE,0},
{ ' ', KeyEvent.VK_SPACE, 0, },
{ '\t', KeyEvent.VK_TAB, 0, },
{ '~', KeyEvent.VK_BACK_QUOTE, 1, },
{ '`', KeyEvent.VK_BACK_QUOTE, 0, },
{ '!', KeyEvent.VK_1, 1, },
{ '@', KeyEvent.VK_2, 1, },
{ '#', KeyEvent.VK_3, 1, },
{ '$', KeyEvent.VK_4, 1, },
{ '%', KeyEvent.VK_5, 1, },
{ '^', KeyEvent.VK_6, 1, },
{ '&', KeyEvent.VK_7, 1, },
{ '*', KeyEvent.VK_8, 1, },
{ '(', KeyEvent.VK_9, 1, },
{ ')', KeyEvent.VK_0, 1, },
{ '-', (SWT.getPlatform().equals("gtk"))?KeyEvent.VK_UNDERSCORE:KeyEvent.VK_MINUS, 0, },
{ '_', (SWT.getPlatform().equals("gtk"))?KeyEvent.VK_UNDERSCORE:KeyEvent.VK_MINUS, 1, },
{ '=', KeyEvent.VK_EQUALS, 0, },
{ '+', KeyEvent.VK_EQUALS, 1, },
{ '[', KeyEvent.VK_OPEN_BRACKET, 0, },
{ '{', KeyEvent.VK_OPEN_BRACKET, 1, },
{ ']', KeyEvent.VK_CLOSE_BRACKET, 0, },
{ '}', KeyEvent.VK_CLOSE_BRACKET, 1, },
{ '|', KeyEvent.VK_BACK_SLASH, 1, },
{ ';', KeyEvent.VK_SEMICOLON, 0, },
{ ':', KeyEvent.VK_SEMICOLON, 1, },
{ ',', KeyEvent.VK_COMMA, 0, },
{ '<', KeyEvent.VK_COMMA, 1, },
{ '.', KeyEvent.VK_PERIOD, 0, },
{ '>', (SWT.getPlatform().equals("gtk"))?KeyEvent.VK_GREATER:KeyEvent.VK_PERIOD, 1, },
{ '/', KeyEvent.VK_SLASH, 0, },
{ '?', KeyEvent.VK_SLASH, 1, },
{ '\\', KeyEvent.VK_BACK_SLASH, 0, },
{ '|', KeyEvent.VK_BACK_SLASH, 1, },
{ '\'', KeyEvent.VK_QUOTE, 0, },
{ '"', KeyEvent.VK_QUOTE, 1, },
{ '\r', KeyEvent.VK_ENTER, 0},
{'\t', KeyEvent.VK_TAB,0}
};
/** a mapping from unicode characters (non-letter,non-digit) to keycodes for US keyboards **/
private static HashMap keycodes = new HashMap();
/**
* Constructs a <code>Robot</code> object in the coordinate system of the primary screen.
*
* @throws org.eclipse.swt.SWTException
*/
public Robot()throws SWTException{
//displayProperty = Display.getDefault();
try{
robot = new java.awt.Robot();
keycodes = new HashMap();
for(int i=0; i<mappings.length;i++){
keycodes.put(new Character((char)mappings[i][0]),new CharCode((char)mappings[i][0],mappings[i][1],mappings[i][2]));
}
}
catch(AWTException awte){
throw new SWTException("(Translated AWTException) "+awte.getMessage());
}
}
/**
* CURRENTLY THIS IS FUNCTIONALLY THE SAME AS THE NO-PARAM CONSTRUCTOR
*
* Creates a <code>Robot</code> for the given <code>Display</code>.
*
* @param display the <code>Display</code> associated with this robot
*
* @throws org.eclipse.swt.SWTException
*/
public Robot(Display display) throws SWTException {
this.displayProperty = display;
try{
robot = new java.awt.Robot();
keycodes = new HashMap();
for(int i=0; i<mappings.length;i++){
keycodes.put(new Character((char)mappings[i][0]),new CharCode((char)mappings[i][0],mappings[i][1],mappings[i][2]));
}
}
catch(AWTException awte){
throw new SWTException("(Translated AWTException) "+awte.getMessage());
}
}
/**
* Creates an image containing pixels read from the screen.
* <p>
* NOTE: Application code must explicitly invoke the <code>Image.dispose()</code>
* method to release the operating system resources managed by each instance
* when those instances are no longer required.
* </p>
*
* @param rect <code>Rectangle</code> to capture in screen coordinates
*
* @return the captured <code>Image</code>
*/
public synchronized Image createScreenCapture(Rectangle rect){
PaletteData pData = new PaletteData(255<<16, // red mask
255<<8, // green mask
255); // blue mask
ImageData iData = new ImageData(rect.width,rect.height,24,pData);
Color color;
for(int x=0; x<rect.width; x++){
for(int y=0; y<rect.height; y++){
color = getPixelColor(rect.x+x,rect.y+y);
iData.setPixel(x,y,color.getRed()<<16 |
color.getGreen()<<8 |
color.getBlue());
}
}
Image image = new Image(Display.getDefault(),iData);
return image;
}
/**
* Returns the color of a pixel at the given screen coordinates.
* <p>
* NOTE: Application code must explicitly invoke the <code>Color.dispose()</code>
* method to release the operating system resources managed by each instance
* when those instances are no longer required.
* </p>
*
* @param x X-position of pixel
* @param y Y-position of pixel
*
* @return color of the specified pixel
*/
public synchronized Color getPixelColor(int x, int y){
java.awt.Color awtColor = robot.getPixelColor(x,y);
return new Color(Display.getDefault(), awtColor.getRed(),
awtColor.getGreen(),
awtColor.getBlue());
}
/**
* Sleep for the specified amount of time, taking care to
* NOT sleep a thread that has a corresponding Display object and
* event loop associated with it.
*
* @param delay the number of milliseconds to sleep
*/
public static void delay(int ms){
trace("starting delay");
final Display display = Display.getCurrent();
if (display == null) {
// Not in UI thread, so just wait.
trace("not in UI thread, so sleeping...");
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
return;
}
final boolean[] continueWait = new boolean[] { true };
display.timerExec(ms, new Runnable() {
public void run() {
trace("turning flag off");
continueWait[0] = false;
// Makes sure we wake up and see the flag has been turned off.
trace("waking up ui");
display.asyncExec(null);
trace("done waking up ui");
}
});
while (continueWait[0]) {
if (!display.readAndDispatch())
display.sleep();
}
trace("ending delay");
}
/**
* Returns the number of milliseconds this <code>Robot</code> sleeps after
* generating an event.
*
*/
public synchronized int getAutoDelay(){
return robot.getAutoDelay();
}
/**
* Returns whether this <code>Robot</code> automatically invokes
* <code>waitForIdle</code> after generating an event.
*
* @return whether <code>waitForIdle</code> is automatically called
*/
public synchronized boolean isAutoWaitForIdle(){
return robot.isAutoWaitForIdle();
}
/**
* Rotates the scroll wheel on wheel-equipped mice.
*
* @param wheelAmt number of "notches" to move the mouse wheel
* Negative values indicate movement up/away from the user, positive values
* indicate movement down/towards the user.
*
*/
/* NOT IN JDK 1.3.1
public synchronized void mouseWheel(int wheelAmt){
robot.mouseWheel(wheelAmt);
}
*/
/**
* Sets the number of milliseconds this <code>Robot</code> sleeps after
* generating an event.
*
* @param ms time to sleep in milliseconds
*/
public synchronized void setAutoDelay(int ms){
robot.setAutoDelay(ms);
}
/**
* Sets whether this <code>Robot</code> automatically invokes <code>waitForIdle</code>
* after generating an event.
*
* @param isOn Whether <code>waitForIdle</code> is automatically invoked
*/
public synchronized void setAutoWaitForIdle(boolean isOn){
robot.setAutoWaitForIdle(isOn);
}
/**
* Returns a string representation of this <code>Robot</code>.
*
* @returns The <code>String</code> representation
*/
public synchronized String toString(){
String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
return getClass().getName() + "[ " + params + " ]";
}
/**
* Dispatches events from the OS's event queue until all events
* in the queue, prior to calling this method, have been handled.
*/
public synchronized void waitForIdle() {
if(displayProperty.getThread()==Thread.currentThread()){
while(displayProperty.readAndDispatch()) ;
}
else{
displayProperty.syncExec(new Runnable() {
public void run() {
while(displayProperty.readAndDispatch());
}
});
}
}
public /*synchronized*/ void waitForIdle(final Display display){
display.syncExec(new Runnable() {
public void run() {
while(display.readAndDispatch());
}
});
}
/**
* Presses all keys in a given accelerator. To type a character or digit,
* just use the unicode value. For example, <code> accelerator = 'k',
* accelerator = 'K', accelerator = '5' </code>. Note that this is case-
* sensitive, so the <code>SWT.SHIFT</code> key is implied if the character
* is uppercase or otherwise requires the <code>SWT.SHIFT</code> key.
*
* For non-character keys, use the keycodes defined in org.eclipse.swt.SWT.
* For example, to type F1, <code> accelerator = SWT.F1; </code>
*
* Note that an accelerator can contain multiple modifier-key masks- like
* <code>accelerator = SWT.CTRL | SWT.ALT | SWT.SHIFT;</code>- but at most
* one character or keycode. Also, mouse-button masks are ignored here.
*
* This method ignores characters that do not appear on a US keyboard.
*
* @param accelerator SWT accelerator containing the keys to be pressed
*
* @see org.eclipse.swt.SWT for more info on accelerators
*/
public synchronized void keyPress(int accelerator){
//int[] keys = SWTCodeToAWT(accelerator);
int[] keys = getVirtualKeycode(accelerator);
boolean shift = false;
for(int i=0; i<keys.length;i++){
//System.out.print(keys[i]+",");
if(keys[i]!=0 && keys[i]!=KeyEvent.VK_UNDEFINED){// make sure that this entry is not empty and not invalid
if(keys[i]==KeyEvent.VK_SHIFT && shift) ;// skip it; shift has already been pressed
else try{
// if(keys[i]==KeyEvent.VK_PERIOD)
// robot.keyPress(KeyEvent.VK_DECIMAL);
//else if(keys[i]==KeyEvent.VK_MINUS)
// robot.keyPress(KeyEvent.VK_SUBTRACT);
//else
robot.keyPress(keys[i]);
}
catch(IllegalArgumentException iae){
System.err.println("IllegalArgumentException: keystroke :"+keys[i]
+"\nAccelerator:"+accelerator
+"\nCast as a char: "+(char)accelerator);
}
if(keys[i]==KeyEvent.VK_SHIFT){
shift = true;
}
}
}
//System.out.println();
}
/**
* Releases all keys in a given accelerator.
*
* @param accelerator SWT accelerator containing the keys to be released
*
* This method ignores characters that do not appear on a US keyboard.
*
* @see org.eclipse.swt.SWT for more info on accelerators
* @see keyPress(int accelerator)
*/
public synchronized void keyRelease(int accelerator){
// int[] keys = SWTCodeToAWT(accelerator);
int[] keys = getVirtualKeycode(accelerator);
boolean shift = false;
for(int i=0; i<keys.length;i++){
if(keys[i]!=0){// make sure that this entry is not empty
if(keys[i]==KeyEvent.VK_SHIFT && shift) ;// skip it; shift has already been pressed
// else if(keys[i]==KeyEvent.VK_PERIOD)
// robot.keyRelease(KeyEvent.VK_DECIMAL);
// else if(keys[i]==KeyEvent.VK_MINUS)
// robot.keyRelease(KeyEvent.VK_SUBTRACT);
else
robot.keyRelease(keys[i]);
if(keys[i]==KeyEvent.VK_SHIFT){
shift = true;
}
}
}
}
/**
* Moves mouse pointer to given screen coordinates.
*
* @param x X-position
* @param y Y-position
*/
public synchronized void mouseMove(int x, int y){
robot.mouseMove(x,y);
}
/**
* Presses all mouse buttons contained in a given accelerator, which
* can include any/all of the following: <code> SWT.BUTTON1, SWT.BUTTON2, SWT.BUTTON3</code>.
*
* Note that this method will not generate any keystrokes, only mouse
* button presses.
*
* @param accelerator SWT accelerator containing the mouse buttons to be pressed
*
* @see org.eclipse.swt.SWT for more info on accelerators
* @see keyPress(int accelerator)
*/
public synchronized void mousePress(int accelerator){
accelerator &= BUTTON_MASK;
if((accelerator&SWT.BUTTON1)==SWT.BUTTON1)
robot.mousePress(InputEvent.BUTTON1_MASK);
if((accelerator&SWT.BUTTON2)==SWT.BUTTON2)
robot.mousePress(InputEvent.BUTTON2_MASK);
if((accelerator&SWT.BUTTON3)==SWT.BUTTON3)
robot.mousePress(InputEvent.BUTTON3_MASK);
}
/**
* Releases all mouse buttons contained in a given accelerator.
* <p>
* Note that this method will not release any keys, only mouse buttons.
* </p>
* @param accelerator SWT accelerator containing the mouse buttons to be released
*
* @see org.eclipse.swt.SWT for more infor on accelerators
* @see mousePress(int accelerator)
* @see keyPress(int accelerator)
*/
public synchronized void mouseRelease(int accelerator){
accelerator &= BUTTON_MASK;
if((accelerator&SWT.BUTTON1)==SWT.BUTTON1)
robot.mouseRelease(InputEvent.BUTTON1_MASK);
if((accelerator&SWT.BUTTON2)==SWT.BUTTON2)
robot.mouseRelease(InputEvent.BUTTON2_MASK);
if((accelerator&SWT.BUTTON3)==SWT.BUTTON3)
robot.mouseRelease(InputEvent.BUTTON3_MASK);
}
/* Converts a single accelerator code into multiple virtual keycodes.
* This ignores mouse buttons.
*/
private int[] getVirtualKeycode(int code){
//System.out.println(Integer.toBinaryString(code));
int[] res = new int[MODIFIER_COUNT+2];// one extra for the keycode/character(last pos) and VK_SHIFT may occupy more than one pos
Arrays.fill(res,0);
int idx = 0;
if((SWT.MODIFIER_MASK & code)!=0){//check for all modifier keys
if((code&SWT.ALT)==SWT.ALT) res[idx++]=KeyEvent.VK_ALT;
if((code&SWT.SHIFT)==SWT.SHIFT) res[idx++]=KeyEvent.VK_SHIFT;
if((code&SWT.CTRL)==SWT.CTRL) res[idx++]=KeyEvent.VK_CONTROL;
}
int keyCode = code & SWT.KEY_MASK;
if((SWT.KEYCODE_BIT & keyCode)!=0){// code contains a keycode- set the last array element
switch(keyCode){
case SWT.ARROW_UP: res[MODIFIER_COUNT+1]= KeyEvent.VK_UP; break;
case SWT.ARROW_DOWN: res[MODIFIER_COUNT+1]= KeyEvent.VK_DOWN; break;
case SWT.ARROW_LEFT: res[MODIFIER_COUNT+1]= KeyEvent.VK_LEFT; break;
case SWT.ARROW_RIGHT: res[MODIFIER_COUNT+1]= KeyEvent.VK_RIGHT; break;
case SWT.PAGE_UP: res[MODIFIER_COUNT+1]= KeyEvent.VK_PAGE_UP; break;
case SWT.PAGE_DOWN: res[MODIFIER_COUNT+1]= KeyEvent.VK_PAGE_DOWN; break;
case SWT.HOME: res[MODIFIER_COUNT+1]= KeyEvent.VK_HOME; break;
case SWT.END: res[MODIFIER_COUNT+1]= KeyEvent.VK_END; break;
case SWT.INSERT: res[MODIFIER_COUNT+1]= KeyEvent.VK_INSERT; break;
case SWT.F1: res[MODIFIER_COUNT+1]= KeyEvent.VK_F1; break;
case SWT.F2: res[MODIFIER_COUNT+1]= KeyEvent.VK_F2; break;
case SWT.F3: res[MODIFIER_COUNT+1]= KeyEvent.VK_F3; break;
case SWT.F4: res[MODIFIER_COUNT+1]= KeyEvent.VK_F4; break;
case SWT.F5: res[MODIFIER_COUNT+1]= KeyEvent.VK_F5; break;
case SWT.F6: res[MODIFIER_COUNT+1]= KeyEvent.VK_F6; break;
case SWT.F7: res[MODIFIER_COUNT+1]= KeyEvent.VK_F7; break;
case SWT.F8: res[MODIFIER_COUNT+1]= KeyEvent.VK_F8; break;
case SWT.F9: res[MODIFIER_COUNT+1]= KeyEvent.VK_F9; break;
case SWT.F10: res[MODIFIER_COUNT+1]= KeyEvent.VK_F10; break;
case SWT.F11: res[MODIFIER_COUNT+1]= KeyEvent.VK_F11; break;
case SWT.F12: res[MODIFIER_COUNT+1]= KeyEvent.VK_F12; break;
default: res[MODIFIER_COUNT+1]= KeyEvent.VK_UNDEFINED; break;
}
}
else{// code contains a unicode character or digit (or ESC or DEL)
// handle ESC or DEL
if(keyCode==SWT.DEL)
res[MODIFIER_COUNT+1] = KeyEvent.VK_DELETE;
else if(keyCode==SWT.ESC)
res[MODIFIER_COUNT+1] = KeyEvent.VK_ESCAPE;
// handle unicode chars
else{
if(Character.isDigit((char)keyCode)){ // digits
res[MODIFIER_COUNT+1] = (keyCode - (int)'0') + KeyEvent.VK_0;
}
else if( (keyCode>='a' &&keyCode<='z') || (keyCode>='A'&&keyCode<='Z')){ // letters 'a'-'z' and 'A' - 'Z'
if(Character.isUpperCase((char)keyCode)){
res[MODIFIER_COUNT] = KeyEvent.VK_SHIFT;
keyCode = (int)Character.toLowerCase((char)keyCode);
}
res[MODIFIER_COUNT+1] = (keyCode - (int)'a') + KeyEvent.VK_A;
}
else{ // all other chars on US keyboard
CharCode cc = (CharCode)keycodes.get(new Character((char) keyCode));
if(cc==null)
res[MODIFIER_COUNT+1] = KeyEvent.VK_UNDEFINED;
else{
if(cc.shift)
res[MODIFIER_COUNT] = KeyEvent.VK_SHIFT;
res[MODIFIER_COUNT+1] = cc.keycode;
}
}
}
}
return res;
}
/**
* Get the <code>Display</code> object with which this robot is
* synchronized.
*
* @return the <code>Display</code> associated with this <code>Robot</code>
*/
public Display getDisplay(){
return displayProperty;
}
/**
* Set the <code>Display</code> object with which this robot is
* synchronized.
*
* @param display the <code>Display</code> to associate with this <code>Robot</code>
*/
public void setDisplay(Display display){
this.displayProperty = display;
}
//!pq: a debug flag to turn on verbose delay timing info
private static final boolean DEBUG_DELAY_INFO = false;
private static void trace(String message) {
if (DEBUG_DELAY_INFO)
System.out.println(message);
}
}