package abbot.tester.swt; import java.awt.Toolkit; import java.util.Arrays; import java.util.Hashtable; import java.util.LinkedList; import java.util.Stack; import java.util.StringTokenizer; import junit.framework.AssertionFailedError; 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.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Decorations; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; import abbot.Log; import abbot.Platform; import abbot.WaitTimedOutError; import abbot.script.Condition; import abbot.util.Properties; import abbot.util.Reflector; import com.windowtester.runtime.swt.internal.operation.SWTKeyOperation; /** * Provides a layer of abstraction for user input above that of * {@link abbot.swt.Robot} * * @author Kevin Dale * @version $Id: Robot.java,v 1.5 2008-04-28 17:44:07 pq Exp $ */ public class 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."; /** Use java.awt.Robot to generate events. */ protected static int EM_ROBOT = 0; /** Post events to the AWT event queue. */ protected static int EM_AWT = 1; /** Use programmatic control where possible (only partly implemented). */ protected static int EM_PROG = 2; public static final int BUTTON_MASK = ( SWT.BUTTON1 | SWT.BUTTON2 | SWT.BUTTON3); public static int MENU_DELAY = 300; // private static final int MENU_TIMEOUT = 2500 ; // private boolean threadFlag; private static final boolean popupOnButton2 = Platform.isMacintosh(); public static final int POPUP_MASK = popupOnButton2 ? SWT.BUTTON2 : SWT.BUTTON3; public static final String POPUP_MODIFIER = popupOnButton2 ? "BUTTON2_MASK" : "BUTTON3_MASK"; public static final boolean POPUP_ON_PRESS = !Platform.isWindows(); public static final int TERTIARY_MASK = popupOnButton2 ? SWT.BUTTON3 : SWT.BUTTON2; public static final String TERTIARY_MODIFIER = popupOnButton2 ? "BUTTON3_MASK" : "BUTTON2_MASK"; /** TODO Add private helper function AWTToSWTCode to fill in below*/ public static final int MENU_SHORTCUT_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); //public static final String MENU_SHORTCUT_MODIFIER = // getKeyModifiers(MENU_SHORTCUT_MASK); //public static final String MENU_SHORTCUT_KEYCODE = // getKeyCode(maskToKeyCode(MENU_SHORTCUT_MASK)); /** Return whether this is the tertiary button, considering primary to be * button1 and secondary to be the popup trigger button. */ public static boolean isTertiaryButton(int mods) { return ((mods & BUTTON_MASK) != SWT.BUTTON1) && ((mods & POPUP_MASK) == 0); } /** OS X (as of 1.3.1, v10.1.5) has incorrect location information for the menu bar when it is installed at the top of the screen. Indicate whether to adjust for that (no workaround yet). Recheck with Jaguar. */ protected static final boolean needMenuFix() { // Ideally we'd install a menu and check where it ended up return Platform.isOSX() && (Boolean.getBoolean("com.apple.macos.useScreenMenuBar") || Boolean.getBoolean("apple.laf.useScreenMenuBar")); } // variables used in different places, across threads; use of these vars // is restricted to synchronized methods only protected boolean boolT = false; protected int intT; protected Point pointT = new Point(0,0); protected Rectangle rectT = new Rectangle(0,0,0,0); protected Control controlT; protected Shell shellT; protected Object objT; protected Menu menuT; protected MenuItem itemT; /** Base delay setting. */ private static int defaultDelay = Properties.getProperty("abbot.robot.default_delay", 0, 60000, 30000); /** Delay before checking for idle. This allows the system a little time to put a native event onto the AWT event queue. */ private static int eventPostDelay = Properties.getProperty("abbot.robot.event_post_delay", 0, 1000, 100); /** Delay before failing to find a popup menu that should appear. */ // private static int popupDelay = // Properties.getProperty("abbot.robot.popup_delay", // 0, 60000, defaultDelay); /** Delay before failing to find a component that should be visible. */ protected static int componentDelay = Properties.getProperty("abbot.robot.component_delay", 0, 60000, defaultDelay); /** With decreased robot auto delay, OSX popup menus don't activate * properly. Indicate the minimum delay for proper operation (determined * experimentally). */ // private static final int subMenuDelay = Platform.isOSX() ? 100 : 0; /** How events are generated. */ private static int eventMode = EM_ROBOT; /** Current input state. This will either be that of the AWT event queue * or of the robot, depending on the dispatch mode. * Note that the robot state may be different from that seen by the AWT * event queue, since robot events may be as yet unprocessed. */ // AWT-STUFF //protected static InputState state = new InputState(); /** If we started a drag, this is the source. */ protected Widget dragSource = null; protected org.eclipse.swt.graphics.Point dragLocation = null; protected boolean inDragSource = false; protected boolean inDropTarget = false; // FIXME add one per graphics device? /** The robot used to generate events. */ private static abbot.swt.Robot robot = null; /** Suitable delay for most cases; tests have been run safely at this value. Should definitely be less than the double-click threshold. (The default value, zero, causes half the tests to fail on linux). FIXME need to find a value between 0 and 100 (100 is kinda slow). 30 works (almost) for w32/linux, but OSX 10.1.5 text input lags (50 is minimum). <p> As platforms are tested at 0 delay, adjust this value.<p> OSX test run time was reduced from 130s to 96s.<p> Not sure it's worth tracking down all the robot bugs and working around them. */ private static final int DEFAULT_DELAY = Platform.isOSX() || Platform.isLinux() || Platform.isWindows() ? 0 : 50; private static final int SLEEP_INTERVAL = 10; private static int autoDelay = DEFAULT_DELAY; public static int getAutoDelay() { return autoDelay; } public static int getDefaultWaitTimeout() { return defaultDelay; } static { String mode=System.getProperty("abbot.robot.mode", "robot"); autoDelay = Properties.getProperty("abbot.robot.auto_delay", -1, 60000, autoDelay); try { robot = new abbot.swt.Robot(); if (autoDelay != -1) { robot.setAutoDelay(autoDelay); } else { autoDelay = robot.getAutoDelay(); Log.warn("Using delay of " + autoDelay); } } catch(SWTException swte) { // no robot available, send AWT events robot = null; } if (mode.equals("awt") || robot == null) { eventMode = EM_AWT; } } /** * Move the mouse to the given location, in screen coordinates. * NOTE: in robot mode, you may need to invokethis with a little jitter. * There are some conditions where a single mouse move will not * generate the necessary enter event on a component (typically a * dialog with an OK button) before a mousePress. See also click(). */ public /*private*/ void mouseMove(int x, int y) { if (eventMode == EM_ROBOT) { Log.debug("ROBOT: Mouse move: (" + x + "," + y + ")"); robot.mouseMove(x, y); //waitForIdle(); } else { throw new AWTDependentCodeException("eventMode == EM_AWT."); } } /** Send a button press event. */ public void mousePress(int buttons) { if (eventMode == EM_ROBOT) { Log.debug("ROBOT: Mouse press: " + getAcceleratorMouseString(buttons)); robot.mousePress(buttons); //waitForIdle(); } else { throw new AWTDependentCodeException("eventMode == EM_AWT."); } } /** Send a button release event. */ /* public void mouseRelease() { mouseRelease(InputState.getButtons()); } */ /** Send a button release event. */ public void mouseRelease(int buttons) { if (eventMode == EM_ROBOT) { Log.debug("ROBOT: Mouse release: " + getAcceleratorMouseString(buttons)); robot.mouseRelease(buttons); //waitForIdle(); } else { throw new AWTDependentCodeException("eventMode == EM_AWT."); } } public static void waitForIdle(){ if (eventPostDelay > autoDelay) { delay(eventPostDelay - autoDelay); } robot.waitForIdle(); } // public static void waitForIdle(Display d){ // if (eventPostDelay > autoDelay) { // delay(eventPostDelay - autoDelay); // } // robot.waitForIdle(d); // } //!pq: public static /*synchronized*/ void waitForIdle(final Display display){ display.syncExec(new Runnable() { public void run() { while(display.readAndDispatch()); // $codepro.audit.disable } }); } // public static void waitForIdleAndCondition(final Display d, final Condition cond){ // if (eventPostDelay > autoDelay) { // delay(eventPostDelay - autoDelay); // } // d.syncExec(new Runnable(){ // public void run(){ // while(!cond.test()){ // if(!d.readAndDispatch()){ // d.sleep(); // } // } // while(d.readAndDispatch()); // } // }); // } public synchronized void mouseMove(final Widget w){ Robot.syncExec(w.getDisplay(),this, new Runnable(){ public void run(){ pointT = WidgetLocator.getLocation(w); } }); mouseMove(pointT.x,pointT.y); } public synchronized void mouseMove(final Widget w, int x, int y) { Robot.syncExec(w.getDisplay(),this, new Runnable(){ public void run(){ pointT = WidgetLocator.getLocation(w); } }); mouseMove(pointT.x+x,pointT.y+y); } public void activate(final Shell shell){ Robot.syncExec(shell.getDisplay(),null, new Runnable() { public void run() { shell.forceActive(); } }); //waitForIdle(); //mouseMove(shell); } public synchronized Widget findFocusOwner(final Display display){ if(display!=null){ Robot.syncExec(display,this, new Runnable(){ public void run(){ controlT = display.getFocusControl(); } }); return controlT; } else return null; } /** Move keyboard focus to the given component. */ public void focus(final Control c) { final Display display = c.getDisplay(); Robot.syncExec(display,null, new Runnable(){ public void run(){ c.forceFocus(); } }); //dan's fix: //mouseMove(c); // Robot.syncExec(display,this, new Runnable(){ // public void run(){ // controlT = display.getFocusControl(); // shellT = c.getShell(); // if(controlT!=c){ // Log.debug("ROBOT: Focus change"); // activate(shellT); // c.forceFocus(); // mouseMove(c); // } // } // }); // if(controlT!=c){ // Log.debug("ROBOT: Focus change"); // activate(shellT); // Robot.syncExec(display,this, new Runnable(){ // public void run(){ // c.forceFocus(); // } // }); // mouseMove(c); // } } /** Send a key press event. */ public void keyPress(int keycode) { if (eventMode == EM_ROBOT) { Log.debug("ROBOT: key press " + getAcceleratorKeyString(keycode)); robot.keyPress(keycode); } else { throw new AWTDependentCodeException("eventMode == EM_AWT."); } } /** Send a key release event. */ public void keyRelease(int keycode) { if (eventMode == EM_ROBOT) { Log.debug("ROBOT: key release " + getAcceleratorKeyString(keycode)); robot.keyRelease(keycode); } else { throw new AWTDependentCodeException("eventMode == EM_AWT."); } } /** Sleep for a little bit, measured in UI time. */ public static void sleep() { delay(SLEEP_INTERVAL); } /** Sleep the given duration of ms. */ public static void delay(int ms) { if (eventMode == EM_ROBOT) abbot.swt.Robot.delay(ms); else { try { Thread.sleep(ms); } catch(InterruptedException ie) { } } } // // ------------------------- TestSet1---------------------------------------- // -------------------------------------------------------------------------- /** Sample the color at the given point on the screen. */ public static Color sample(int x, int y) { return robot.getPixelColor(x, y); } /** Capture the contents of the given rectangle. */ /* NOTE: Text components (and maybe others with a custom cursor) will * capture the cursor. May want to move the cursor out of the component * bounds, although this might cause issues where the component is * responding visually to mouse movement. * Is this an OSX bug? */ public static Image capture(Rectangle bounds) { return robot.createScreenCapture(bounds); } /** Capture the contents of the given Widget, sans any border or * insets. * * [FROM abbot.tester.awt's implementation]: * This should only be used on components that do * not use a LAF UI, or the results will not be consistent across * platforms. */ public synchronized Image capture(final Widget w) { Display display = w.getDisplay(); Robot.syncExec(display,this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(w,false); } }); Log.debug("Component bounds " + rectT); return capture(rectT); } /** Capture the contents of the given Widget, optionally including the * border and/or insets. * * [FROM abbot.tester.awt's implementation]: * This should only be used on components that do * not use a LAF UI, or the results will not be consistent across * platforms. */ public synchronized Image capture(final Widget w, final boolean ignoreBorder) { Display display = w.getDisplay(); Robot.syncExec(display,this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(w,ignoreBorder); } }); Log.debug("Component bounds " + rectT); return capture(rectT); } // ------------------------------------------------------------- /** Returns the current event-generation mode. */ public static int getEventMode() { return eventMode; } /** Set the event-generation mode. */ static void setEventMode(int mode) { eventMode = mode; } public static int getEventPostDelay() { return eventPostDelay; } public static void setEventPostDelay(int delay) { eventPostDelay = Math.min(1000, Math.max(0, delay)); } /** Allow this to be adjusted, mostly for testing. */ public static void setAutoDelay(int ms) { ms = Math.min(60000, Math.max(0, ms)); if (eventMode == EM_ROBOT) robot.setAutoDelay(ms); autoDelay = ms; } /** Run the given action on the event dispatch thread. */ public static void invokeAction(Display display,Runnable action) { display.asyncExec(action); } // /** Run the given action on the event dispatch thread, but don't return // until it's been run. // */ // public static void invokeAndWait(Display display, Runnable action) { // display.syncExec(action); // } // private static final Runnable EMPTY_RUNNABLE = // new Runnable() { public void run() { } }; // Bug workaround support protected void jitter(Widget w, int x, int y) { mouseMove(w, (x > 0 ? x - 1 : x + 1), y); } // // Bug workaround support // private void jitter(int x, int y) { // mouseMove((x > 0 ? x - 1 : x + 1), y); // } /** Move the mouse appropriately to get from the source to the destination. Enter/exit events will be generated where appropriate. */ public void dragOver(Widget dst, int x, int y) { mouseMove(dst, (x > 1) ? x-1 : x+1, y); mouseMove(dst, x, y); } /** Begin a drag operation. */ public void drag(Widget src, int sx, int sy, int modifiers) { // FIXME make sure it's sufficient to be recognized as a drag // by the default drag gesture recognizer mousePress(src, sx, sy, modifiers); mouseMove(src, sx > 0 ? sx - 1 : sx + 1, sy); dragSource = src; dragLocation = new org.eclipse.swt.graphics.Point(sx, sy); inDragSource = true; } /** End a drag operation, releasing the mouse button over the given target location. */ public void drop(Widget target, int x, int y, int modifiers) { // All motion events are relative to the drag source if (dragSource == null) throw new ActionFailedException("There is no drag source"); inDropTarget = dragSource == target; dragOver(target, x, y); mouseRelease(modifiers); dragSource = null; dragLocation = null; inDragSource = inDropTarget = false; } /** * Type all the keys contained in this accelerator. * * Note that uppercase characters will be typed as uppercase, which * requires that the shift key be depressed. * * @see abbot.swt.Robot */ public void key(int accelerator) { robot.keyPress(accelerator); robot.keyRelease(accelerator); //waitForIdle(); } /** * Type the given character. Note that this sends the key to whatever * component currently has the focus. */ public void keyStroke(char ch) { key((int)ch); } /** Type the given string. */ public void keyString(String str) { char[] ch = str.toCharArray(); for (int i=0;i < ch.length;i++) { keyStroke(ch[i]); } } public synchronized void mousePress(final Widget w) { Display display = w.getDisplay(); Robot.syncExec(display,this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(w); } }); mousePress(w, rectT.width/2, rectT.height/2); } public synchronized void mousePress(final Widget w, int mask) { Display display = w.getDisplay(); Robot.syncExec(display,this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(w); } }); mousePress(w, rectT.width/2, rectT.height/2,mask); } public void mousePress(Widget w, int x, int y) { mousePress(w, x, y, SWT.BUTTON1); } /** Mouse down in the given part of the component. All other mousePress methods must eventually invoke this one. */ public void mousePress(Widget w, int x, int y, int mask) { if (eventMode == EM_ROBOT && hasRobotMotionBug()) jitter(w, x, y); else mouseMove(w, x, y); mousePress(mask); } /** Click in the center of the given component. This is not static b/c it * sometimes needs to be redefined (i.e. JComponent to scroll before * clicking). */ public synchronized void click(final Widget w) { Display display = w.getDisplay(); Robot.syncExec(display,this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(w); //System.out.println(rectT); } }); click(w, rectT.width/2, rectT.height/2); } public synchronized void click(final Widget w, int mask) { Display display = w.getDisplay(); Robot.syncExec(display,this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(w); } }); click(w, rectT.width/2, rectT.height/2,mask); } public void click(Widget w, int x, int y) { click(w, x, y, SWT.BUTTON1); } public void click(Widget w, int x, int y, int mask) { click(w, x, y, mask, 1); } /** Click in the given part of the component. All other click methods * must eventually invoke this one. */ public void click(Widget w, int x, int y, int mask, int count) { Log.debug("Click at (" + x + "," + y + ") on " + w); boolean shift = (mask & SWT.SHIFT) ==SWT.SHIFT; // FIXME handle other modifiers mask &= (SWT.BUTTON1 |SWT.BUTTON2 |SWT.BUTTON3); if (shift) keyPress(SWT.SHIFT); // Adjust the auto-delay to ensure we actually get a multiple click // In general clicks have to be less than 200ms apart, although the // actual setting is not readable by java that I'm aware of. int oldDelay = getAutoDelay(); if (oldDelay * 2 > 200) setAutoDelay(50); mousePress(w, x, y, mask); while (count-- > 1) { mouseRelease(mask); mousePress(mask); } mouseRelease(mask); setAutoDelay(oldDelay); if (shift) keyRelease(SWT.SHIFT); } public void selectPopupMenuItem(final MenuItem item, final int x, final int y){ Display display = item.getDisplay(); Robot.syncExec(display,null, new Runnable(){ public void run(){ Menu root = getRootMenu(item); if((root.getStyle()&SWT.POP_UP)==SWT.POP_UP){ //Log.debug("selectMenuItem: cannot select items in pop-up menu trees"); exposeMenuItem(item,MENU_DELAY,true, 0, 0); // waitForIdle(item.getDisplay()); // http://sourceforge.net/mailarchive/forum.php?thread_id=7226932&forum_id=36529 // thanks Joerg Weingarten delay(MENU_DELAY); } else{ exposeMenuItem(item,MENU_DELAY, false, x, y); int oldAccel = item.getAccelerator(); int accel; activate(item.getParent().getShell()); if(oldAccel==0){ accel=findUnusedAccelerator(item.getDisplay()); //System.out.println("Unused accelerator = "+Integer.toBinaryString(accel)); if(accel==-1){ Log.debug("selectMenuItem: could not find unused accelerator"); return; } item.setAccelerator(accel); //!pq: attenpt keyPress fix //key(accel); keyClick(accel); //waitForIdle(item.getDisplay()); item.setAccelerator(0); } else{ //!pq: attenpt keyPress fix //key(oldAccel); keyClick(oldAccel); //waitForIdle(item.getDisplay()); } } } }); } /** Find and select an SWT MenuItem based on its "name" property. * * @param parent a Composite container that contains the MenuItem * @param label the text of the MenuItem (and its name) */ public void selectMenuItemByText(Menu parent, String name){ Hashtable ht = hashMenuItemsByText(parent); org.eclipse.swt.widgets.MenuItem item = (org.eclipse.swt.widgets.MenuItem)ht.get(name); if(item==null) throw new WidgetMissingException("No MenuItem found with "+ "the text \""+name+"\"."); selectMenuItem(item); } /** * Select an SWT MenuItem. * * NOTE: no menus can be open when this method is called * * @param item The MenuItem to be selected */ public void selectMenuItem(final MenuItem item){ Display display = item.getDisplay(); Robot.syncExec(display,null, new Runnable(){ public void run(){ Menu root = getRootMenu(item); if((root.getStyle()&SWT.POP_UP)==SWT.POP_UP){ exposeMenuItem(item,MENU_DELAY,true, 0, 0); waitForIdle(item.getDisplay()); } else{ exposeMenuItem(item,MENU_DELAY, false, 0, 0); int oldAccel = item.getAccelerator(); int accel; activate(item.getParent().getShell()); if(oldAccel==0){ accel=findUnusedAccelerator(item.getDisplay()); //System.out.println("Unused accelerator = "+Integer.toBinaryString(accel)); if(accel==-1){ Log.debug("selectMenuItem: could not find unused accelerator"); return; } if(!item.isDisposed()) item.setAccelerator(accel); key(accel); waitForIdle(item.getDisplay()); item.setAccelerator(0); } else{ key(oldAccel); waitForIdle(item.getDisplay()); } } } }); } /** * Navigate the application's menus to expose a particular MenuItem. * * Thanks to J�rg Weingarten for * <a href="https://sourceforge.net/tracker/index.php?func=detail&aid=1196884&group_id=50939&atid=461492"> * this patch</a> * * @param item MenuItem to expose * @param delay the time in milliseconds to leave the menuItem exposed * @param click should the item be clicked once it is exposed (for selection of popups)? * * @return the depth of the MenuItem or -1 if failure */ // TODO: THIS METHOD IS PLATFORM-SPECIFIC // this method should ONLY be called within a display.syncExec() call private void exposeMenuItem(MenuItem item, final int delay, final boolean click, int x, int y){ int depth = 0; Stack itemStack = new Stack(); Menu parent; MenuItem parentItem; LinkedList keyStrokes = new LinkedList(); // create a sequence of menuItems to click-on that will get us to our item while(true){ parent = item.getParent(); parentItem = parent.getParentItem(); itemStack.push(item); // add the item to the sequence if(parentItem==null) break;// NO, should be if parentItem is visible // or menu is bar menu or popup item = parentItem; depth++; } // parent points to the top-level menu- a pop-up or menuBar menu this.activate(parent.getShell()); org.eclipse.swt.widgets.MenuItem[] items; int itemIdx=-1; boolean found=false; if(parent.getShell().getMenuBar()!=null&&parent.getShell().getMenuBar().equals(parent)){// is this menu the menubar for a shell? keyStrokes.add(new Integer(SWT.ALT)); //key(SWT.ALT); //waitForIdle(); items = parent.getItems(); for(int i=0; i<items.length;i++){ if(item.equals(items[i])){ itemIdx = i; found = true; break; } } if(found){//type RIGHT the appropriate # of times, then type down for(int i=0; i<itemIdx;i++){ keyStrokes.add(new Integer(SWT.ARROW_RIGHT)); //key(SWT.ARROW_RIGHT); } if(!itemStack.isEmpty()){ //this.keyPress(SWT.ARROW_DOWN); //this.keyRelease(SWT.ARROW_DOWN); } keyStrokes.add(new Integer(SWT.ARROW_DOWN)); //key(SWT.ARROW_DOWN); itemStack.pop(); // don't need the first menuItem } else{ Log.debug("enterMenuItem(): "+item+" not found."); return; } } else if((parent.getStyle()&SWT.POP_UP)==SWT.POP_UP){ // is this menu a popup? //Rectangle parentBounds = WidgetLocator.getBounds(parent.getParent()); //mouseMove(parent.getParent(),parentBounds.width/2,parentBounds.height/2); mouseMove(parent.getParent(),x,y); robot.waitForIdle(parent.getDisplay()); parent.setVisible(true); //key(SWT.ARROW_DOWN); keyStrokes.add(new Integer(SWT.ARROW_DOWN)); } // traverse this sequence found = false; itemIdx = -1; while(!itemStack.isEmpty()){ found = false; item = (org.eclipse.swt.widgets.MenuItem)(itemStack.pop()); parent = item.getParent(); items = parent.getItems(); itemIdx = 0; //System.err.println("PARENT: "+parent+"\nITEM: "+item.getText()); if(!parent.isEnabled()){ Log.debug("enterMenuItem(): "+parent+" is not enabled."); return; } int sepCount = 0; for(int i=0; i<items.length;i++) { if(item.equals(items[i])) { itemIdx = i - sepCount; found = true; break; } if (items[i].getText().equals("")) { sepCount++; } } if(found){//type down the appropriate # of times, then type right for(int i=0; i<itemIdx;i++){ keyStrokes.add(new Integer(SWT.ARROW_DOWN)); //key(SWT.ARROW_DOWN); } if(!itemStack.isEmpty()){ //key(SWT.ARROW_RIGHT); keyStrokes.add(new Integer(SWT.ARROW_RIGHT)); } } else{ Log.debug("enterMenuItem(): "+item+" not found."); return; } } final int[] keyStrokeAccels = new int[keyStrokes.size()]; for(int i=0; i<keyStrokes.size();i++) keyStrokeAccels[i] = ((Integer)keyStrokes.get(i)).intValue(); //!pq: attempting keyPress fixes... synchronized(this){ boolT = false; Thread enter = new Thread(){ public void run(){ for(int i=0; i<keyStrokeAccels.length;i++){ // robot.keyPress(keyStrokeAccels[i]); // robot.keyRelease(keyStrokeAccels[i]); keyClick(keyStrokeAccels[i]); } pause(delay); if(click){ // robot.keyPress(SWT.CR); // robot.keyRelease(SWT.CR); keyClick(SWT.CR); } else{ // robot.keyPress(SWT.ALT); // robot.keyRelease(SWT.ALT); keyClick(SWT.ALT); } System.out.println("setting to true..."); boolT = true; } }; enter.start(); try{ enter.join(1); } catch(InterruptedException ie){ Log.debug("unable to wait for keystrokes to enter menuItem"); } Display display = item.getDisplay(); while(!display.isDisposed() && !boolT) display.readAndDispatch(); } } /** * returns an array of all menu items contained in this menu and its submenus */ private LinkedList traverseMenuTree(Menu menu){ MenuItem[] items = menu.getItems(); LinkedList list = new LinkedList(); list.addAll(Arrays.asList(items)); for(int i=0; i<items.length;i++){ if(items[i].getMenu()!=null) list.addAll(traverseMenuTree(items[i].getMenu())); } return list; } /** * returns a hashtable of all menuitems contained in this menu and its submenus */ public synchronized Hashtable hashMenuItemsByText(final Menu menu){ final Hashtable ht = new Hashtable(); Robot.syncExec(menu.getDisplay(),this, new Runnable(){ public void run(){ LinkedList items = traverseMenuTree(menu); for(int i=0; i<items.size();i++){ itemT = (MenuItem)items.get(i); ht.put(itemT.getText(),itemT); } } }); return ht; } /** * returns the root menu that contains a given menuitem */ public synchronized Menu getRootMenu(final MenuItem item){ Display display = item.getDisplay(); Robot.syncExec(display,this, new Runnable(){ public void run(){ menuT = item.getParent(); itemT = menuT.getParentItem(); } }); return (itemT==null)? menuT : getRootMenu(itemT); } /** * Finds an accelerator that is currently unused by any MenuItem * that is a decendant of the current active shell, or -1 if none found. */ // must be called from the display thread or within a display.syncExec call // PLATFORM-DEPENDENT ( private int findUnusedAccelerator(Display display){ Shell active = display.getActiveShell(); LinkedList items = new LinkedList(); Menu popup = null; Menu bar = null; try{ popup =(active.getMenu()!=null)?active.getMenu():null; bar = active.getMenuBar(); } catch(NullPointerException ignored){} if(popup!=null) items.addAll(traverseMenuTree(popup)); if(bar!=null) items.addAll(traverseMenuTree(bar)); LinkedList accelerators = new LinkedList(); int x; for(int i=0; i< items.size();i++){ x = ((org.eclipse.swt.widgets.MenuItem)items.get(i)).getAccelerator(); if(x!=0) accelerators.add(new Integer(x)); } int testAccel; int usedAccel; int [] testMasks = {(SWT.ALT),(SWT.CTRL),(SWT.ALT|SWT.CTRL)}; boolean clear = false; for(int j=0; j< testMasks.length; j++){ for(char c = 'a'; c<'z';c++){ testAccel = (testMasks[j] | c); clear = true; for(int i=0; i<accelerators.size();i++){ usedAccel = ((Integer)accelerators.get(i)).intValue(); if(usedAccel==testAccel) clear = false; } if(clear) return testAccel; } } return -1; } /* TODO May need to implement some sort of helper method, isOnMenuBar(MenuItem item) */ /** Attempt to display a popup menu at center of the control. */ /* THESE METHODS WORK BUT SERVE NO PURPOSE- USE SELECTMENUITEM FOR * BOTH BAR MENUS AND POPUPS */ /* public synchronized Menu showPopupMenu(final Control invoker){ invoker.getDisplay().syncExec(new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(invoker); } }); return showPopupMenu(invoker,rectT.width/2, rectT.height/2); } public synchronized Menu showPopupMenu(final Control invoker, int x, int y){ final String where = " at (" + x + "," + y + ")"; Log.debug("Invoking popup " + where); click(invoker, x, y, POPUP_MASK); invoker.getDisplay().syncExec(new Runnable(){ public void run(){ Menu popup = invoker.getMenu(); if(popup == null ){ String msg1 = "No popup associated with "+ invoker; throw new WidgetMissingException(msg1); } else if(!popup.isVisible()){ String msg2 = "No popup responded to " + POPUP_MODIFIER + where + " on " + invoker; throw new WidgetMissingException(msg2); } menuT = popup; } }); return menuT; } */ /** TODO make close(Decorations) pretend to click on the close button */ /** Invoke the window close operation. */ public void close(final Decorations window){ Robot.syncExec(window.getDisplay(),null, new Runnable(){ public void run(){ window.dispose(); } }); } /** Move the given Decorations to the requested location. */ public synchronized void move(final Decorations window, int newx, int newy){ Robot.syncExec(window.getDisplay(),this, new Runnable(){ public void run(){ pointT = WidgetLocator.getLocation(window); } }); moveBy(window, newx - pointT.x, newy - pointT.y); } /** Move the given Decorations by the given amount. */ public synchronized void moveBy(final Decorations window, final int dx, final int dy){ Robot.syncExec(window.getDisplay(),this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(window); } }); mouseMove(window, rectT.width/2, 0); mouseMove(window, rectT.width/2+dx, dy); Robot.syncExec(window.getDisplay(),null, new Runnable(){ public void run(){ window.setLocation(new Point(rectT.x+dx, rectT.y+dy)); } }); mouseMove(window, rectT.width/2, 0); } /** Resize the given Decorations to the given size. */ public synchronized void resize(final Decorations window, int width, int height){ Robot.syncExec(window.getDisplay(),this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(window); } }); resizeBy(window, width - rectT.width, height - rectT.height); } /** Resize the given Decorations by the given amounts. */ public synchronized void resizeBy(final Decorations window, final int dx, final int dy){ Robot.syncExec(window.getDisplay(),this, new Runnable(){ public void run(){ rectT = WidgetLocator.getBounds(window); } }); // Fake like we're resizing mouseMove(window, rectT.width-1, rectT.height -1); mouseMove(window, rectT.width+dx-1,rectT.height+dy-1); Robot.syncExec(window.getDisplay(),null, new Runnable(){ public void run(){ window.setSize(rectT.width+dx, rectT.width+dy); } }); mouseMove(window, rectT.width-1, rectT.height-1); } /** Identify the coordinates of the iconify button where we can, returning * null if we can't. */ public synchronized Point getIconifyLocation(final Shell shell){ Robot.syncExec(shell.getDisplay(),this, new Runnable(){ public void run(){ int style = shell.getStyle(); if((style & SWT.MIN)== SWT.MIN){ if(Platform.isWindows()){ int xOffset = 50 + shell.getBorderWidth(); int yOffset = 12 + shell.getBorderWidth(); Rectangle bounds = WidgetLocator.getBounds(shell); pointT = new Point(bounds.width - xOffset, yOffset); } } pointT = null; } }); return pointT; } /** Identify the coordinates of the maximize button where possible, * returning null if not. */ public /*private*/ synchronized Point getMaximizeLocation(final Shell shell) { final Point loc = getIconifyLocation(shell); Robot.syncExec(shell.getDisplay(),this, new Runnable(){ public void run(){ intT = shell.getStyle(); } }); int style = intT; if(loc!=null && (style & SWT.MAX) == SWT.MAX){ if (Platform.isWindows()) { return new Point(loc.x + 17, loc.y); } } return null; } /** Iconify the given Shell. Don't support iconification of Dialogs at * this point (although maybe should). */ public void iconify(final Shell shell){ Point loc = getIconifyLocation(shell); if(loc!=null) mouseMove(shell,loc.x, loc.y); Robot.syncExec(shell.getDisplay(),null, new Runnable() { public void run(){ shell.setMinimized(true); } }); } public void deiconify(Shell shell) { normalize(shell); } public void normalize(final Shell shell) { Robot.syncExec(shell.getDisplay(),null, new Runnable() { public void run(){ shell.setMinimized(false); shell.setMaximized(false); } }); } /** Make the window full size */ public void maximize(final Shell shell){ Point loc = getMaximizeLocation(shell); if(loc!=null) mouseMove(shell,loc.x,loc.y); Robot.syncExec(shell.getDisplay(),null, new Runnable(){ public void run(){ shell.setMaximized(true); // if maximizing failed and we can resize, resize to fit the screen if(!shell.getMaximized()&& (shell.getStyle() & SWT.RESIZE)==SWT.RESIZE ){ Rectangle screen =shell.getDisplay().getBounds(); shell.setLocation(screen.x,screen.y); shell.setSize(screen.width,screen.height); } } }); } public static Class getCanonicalClass(Class refClass) { // Don't use classnames from anonymous inner classes... // Don't use classnames from platform LAF classes... while (refClass.getName().indexOf("$") != -1 || refClass.getName().startsWith("javax.swing.plaf") || refClass.getName().startsWith("com.apple.mrj")) refClass = refClass.getSuperclass(); return refClass; } /** TODO add toString methods that use the widget name property * to provide a more concise representation of the Widget. */ /** Return the numeric event ID corresponding to the given string. */ public static int getEventID(Class cls, String id) { return Reflector.getFieldValue(cls, id); } /** TODO MAYBE add the following methods that are useful to testers: * - getModifiers(String) OK * - getModifiers(int,boolean,boolean) OK * - getKeyModifiers(int) OK * - getMouseModifiers(int) OK * - getModifiers(Event) X * - getKeyCode(int) X * - getKeyCode(String) X */ /** Convert the string representation into the actual modifier mask. * NOTE: this ignores any character stored as a unicode char */ /** TODO Fix this so that it will parse out chars as well */ public static int getModifiers(String mods) { int value = 0; if (mods != null && !mods.equals("")) { StringTokenizer st = new StringTokenizer(mods, "| "); while (st.hasMoreTokens()) { String flag = st.nextToken(); if (POPUP_MODIFIER.equals(flag)) value |= POPUP_MASK; else if (TERTIARY_MODIFIER.equals(flag)) value |= TERTIARY_MASK; else if (!flag.equals("0") && flag.indexOf('\'')==-1) value |= Reflector.getFieldValue(SWT.class, flag); } } return value; } /** * Provides a String representation of the mouse modifiers in * the given accelerator. */ public String getAcceleratorMouseString(int accelerator){ return getAcceleratorString(accelerator,false,true); } /** * Provides a String representation of a given accelerator. */ public String getAcceleratorString(int accelerator, boolean key, boolean mouse){ String res = "{ "; int count = 0; if(mouse){ if((accelerator&SWT.BUTTON1)==SWT.BUTTON1){ if(count!=0) res+="| "; res+="SWT.BUTTON1 "; count++; } if((accelerator&SWT.BUTTON2)==SWT.BUTTON2){ if(count!=0) res+="| "; res+="SWT.BUTTON2 "; count++; } if((accelerator&SWT.BUTTON3)==SWT.BUTTON3){ if(count!=0) res+="| "; res+="SWT.BUTTON3 "; count++; } } if(key){ // first, check modifier keys if((accelerator&SWT.ALT)==SWT.ALT){ if(count!=0) res+="| "; res+="SWT.ALT "; count++; } if((accelerator&SWT.SHIFT)==SWT.SHIFT){ if(count!=0) res+="| "; res+="SWT.SHIFT "; count++; } if((accelerator&SWT.CTRL)==SWT.CTRL){ if(count!=0) res+="| "; res+="SWT.CTRL "; count++; } if((accelerator&SWT.COMMAND)==SWT.COMMAND){ if(count!=0) res+="| "; res+="SWT.COMMAND "; count++; } // now, look at the keystroke (if any) int keyCode = accelerator & SWT.KEY_MASK; if((SWT.KEYCODE_BIT & keyCode)!=0 && keyCode!=0){ //accelerator contains a keycode switch(keyCode){ case SWT.ARROW_UP: res+=(count!=0)?"| ":"";res+="SWT.ARROW_UP";count++;break; case SWT.ARROW_DOWN: res+=(count!=0)?"| ":"";res+="SWT.ARROW_DOWN";count++;break; case SWT.ARROW_LEFT: res+=(count!=0)?"| ":"";res+="SWT.ARROW_LEFT";count++;break; case SWT.ARROW_RIGHT: res+=(count!=0)?"| ":"";res+="SWT.ARROW_RIGHT";count++;break; case SWT.PAGE_UP: res+=(count!=0)?"| ":"";res+="SWT.PAGE_UP";count++;break; case SWT.PAGE_DOWN: res+=(count!=0)?"| ":"";res+="SWT.PAGE_DOWN";count++;break; case SWT.HOME: res+=(count!=0)?"| ":"";res+="SWT.HOME";count++;break; case SWT.END: res+=(count!=0)?"| ":"";res+="SWT.END";count++;break; case SWT.INSERT: res+=(count!=0)?"| ":"";res+="SWT.INSERT";count++;break; case SWT.F1: res+=(count!=0)?"| ":"";res+="SWT.F1";count++;break; case SWT.F2: res+=(count!=0)?"| ":"";res+="SWT.F2";count++;break; case SWT.F3: res+=(count!=0)?"| ":"";res+="SWT.F3";count++;break; case SWT.F4: res+=(count!=0)?"| ":"";res+="SWT.F4";count++;break; case SWT.F5: res+=(count!=0)?"| ":"";res+="SWT.F5";count++;break; case SWT.F6: res+=(count!=0)?"| ":"";res+="SWT.F6";count++;break; case SWT.F7: res+=(count!=0)?"| ":"";res+="SWT.F7";count++;break; case SWT.F8: res+=(count!=0)?"| ":"";res+="SWT.F8";count++;break; case SWT.F9: res+=(count!=0)?"| ":"";res+="SWT.F9";count++;break; case SWT.F10: res+=(count!=0)?"| ":"";res+="SWT.F10";count++;break; case SWT.F11: res+=(count!=0)?"| ":"";res+="SWT.F11";count++;break; case SWT.F12: res+=(count!=0)?"| ":"";res+="SWT.F12";count++;break; default: break; } } else if(keyCode!=0){//accelerator contains a unicode character if(count!=0) res+="| "; res+="'(char)keyCode'"; count++; } } res+="}"; return res; } /** * returns a String representation of the key modifiers * in the given accelerator */ public String getAcceleratorKeyString(int accelerator){ return getAcceleratorString(accelerator,true,false); } /** Is the given MenuItem on a popup? */ public static boolean isOnPopup(MenuItem item){ Menu parent = item.getParent(); return (parent.getStyle() & SWT.POP_UP)==SWT.POP_UP || (parent.getParentItem()!=null && isOnPopup(parent.getParentItem())); } /** Strip the package from the class name. */ public static String simpleClassName(Class cls) { String name = cls.getName(); int dot = name.lastIndexOf("."); return name.substring(dot+1, name.length()); } /** Wait for the given Condition to return true. The default timeout may * be changed by setting abbot.robot.default_delay. * @throws WaitTimedOutError if the default timeout (30s) is exceeded. */ public static void wait(Condition condition) { wait(condition, defaultDelay); } /** Wait for the given Condition to return true, waiting for timeout ms. * @throws WaitTimedOutError if the timeout is exceeded. */ public static void wait(Condition condition, long timeout) { wait(condition, timeout, SLEEP_INTERVAL); } /** Wait for the given Condition to return true, waiting for timeout ms, * polling at the given interval. * @throws WaitTimedOutError if the timeout is exceeded. */ public static void wait(Condition condition, long timeout, int interval) { long now = System.currentTimeMillis(); while (!condition.test()) { if (System.currentTimeMillis() - now > timeout) { StringBuffer sb = new StringBuffer("Timed out waiting for " + condition); // Display d = robot.getDisplay(); // if (d != null) { // final TestHierarchy h = new TestHierarchy(d); // if (h != null) { // final BasicFinder f = new BasicFinder(h); // if (f != null) { //// TODO: write this //// sb.append(f.printWidgetsToString()); // f.printWidgets(); // } // } // } throw new WaitTimedOutError(sb.toString()); } delay(interval); } } // private static java.util.ArrayList bugList = null; // private static boolean gotBug1Event = false; /** Check for all known robot-related bugs that will affect Abbot * operation. Returns a String for each bug detected on the current * system. */ /* public static String[] bugCheck(final Window window) { if (bugList == null) { bugList = new java.util.ArrayList(); if (Platform.isWindows() && Platform.getJavaVersionNumber() < Platform.JAVA_1_4) { Log.debug("Checking for w32 bugs"); final int x = window.getWidth() / 2; final int y = window.getHeight() / 2; final int mask = InputEvent.BUTTON2_MASK; MouseAdapter ma = new MouseAdapter() { public void mouseClicked(MouseEvent ev) { Log.debug("Got " + Robot.toString(ev)); gotBug1Event = true; // w32 acceleration bug if (ev.getSource() != window || ev.getX() != x || ev.getY() != y) { bugList.add(Strings.get("Bug1")); } // w32 mouse button mapping bug if ((ev.getModifiers() & mask) != mask) { bugList.add(Strings.get("Bug2")); } } }; window.addMouseListener(ma); Robot robot = new Robot(); robot.click(window, x, y, mask); robot.waitForIdle(); window.removeMouseListener(ma); window.toFront(); // Bogus acceleration may mean the event goes entirely // elsewhere if (!gotBug1Event) { bugList.add(0, Strings.get("Bug1")); } } } return (String[])bugList.toArray(new String[bugList.size()]); } */ /** Place the pointer in the center of the display */ public void resetPointer() { if (eventMode == EM_ROBOT) { Rectangle screen = robot.getDisplay().getBounds(); mouseMove(screen.width/2, screen.height/2); mouseMove(screen.width/2-1, screen.height/2-1); } } /** * 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 robot.getDisplay(); } /** * 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){ robot.setDisplay(display); } /** Provides a more concise representation of the component than the * default Component.toString(). */ // FIXME getTag has too much overhead to be calling this frequently public static String toString(Widget widget) { if (widget == null) return "(null)"; Class cls = widget.getClass(); WidgetTester wt = new WidgetTester(); String name = (String)wt.getData(widget,"name"); // WidgetFinder finder = DefaultWidgetFinder.getFinder(); // String name = finder.getWidgetName(widget); if (name == null) name = WidgetTester.getTag(widget); cls = getCanonicalClass(cls); String cname = simpleClassName(widget.getClass()); if (!cls.equals(widget.getClass())) cname += "/" + simpleClassName(cls); if (name == null) name = cname + " instance"; else name = "'" + name + "' (" + cname + ")"; return name; } // for setting flags inside a call to Display.asyncExec(Runnable) // [NOT USED CURRENTLY] public static class SyncFlag{ static Hashtable flags = new Hashtable(); public static synchronized void initFlag(Display display){ flags.put(display,new Boolean(false)); } public static synchronized void clearFlag(Display display){ flags.remove(display); } /* To be called inside of an syncExec/asyncExec call */ public static synchronized void setFlag(){ Display display = Display.findDisplay(Thread.currentThread()); flags.put(display, new Boolean(true)); } public static synchronized boolean getFlag(Display display){ return ((Boolean)flags.get(Thread.currentThread())).booleanValue(); } } boolean locked = false; public static void syncExec(Display dsply,Object obj, Runnable action){ try{ dsply.syncExec(action); //Logger.trace("Didn't catch a NullPointerException"); }catch(NullPointerException npe){ //Logger.trace("Caught a NullPointerException"); npe.printStackTrace(); /* Do nothing. When DWF.getRootShells() is called, * sometimes an NPE is thrown when Display.syncExec() is * called, despite the fact that the display wasn't null * before the call, nor after. * * This happens when we're checking displays that don't * matter anyway- eg, when we're waiting for a shell to * show that isn't opened yet. So its fine to just skip * it. */ } /* * The following hoping to get cleaner test failure handling. */ catch (SWTException swtEx) { if (swtEx.throwable instanceof AssertionFailedError) { throw (AssertionFailedError) swtEx.throwable; } else if (swtEx.getCause() instanceof AssertionFailedError) { throw (AssertionFailedError) swtEx.getCause(); } else { throw swtEx; } } //Synchronizer.getSynchronizer().syncExec(dsply,obj,action); //Synchronizer.getSynchronizer().syncExec(dsply,action); } /** * Executes the action given synchronously in the SWT thread and returns the * eventual result of the computation. * <p/> * This is a helper method for testers which query Widget properties.<br/> * @param display the display whichs Thread to use. * @param action the action to be run. * @return the result of the synchronously executed action. */ public static Object syncExec(Display display, RunnableWithResult action) { Robot.syncExec(display, null, action); return action.getResult(); } /** OS X (as of 1.3.1, v10.1.5), will sometimes send a click to the wrong component after a mouse move. This continues to be an issue in 1.4.1 <p> Linux x86 (1.3.1) has a similar problem, although it manifests it at different times (need a bug test case for this one). <p> Solaris and HPUX probably share code with the linux VM implementation, so the bug there is probably identical. <p> */ // FIXME add tests to determine presence of bug. public static boolean hasRobotMotionBug() { return Platform.isOSX() || (!Platform.isWindows() && Platform.JAVA_VERSION < Platform.JAVA_1_4) //&& Platform.getJavaVersionNumber() < Platform.JAVA_1_4) || Boolean.getBoolean("abbot.robot.need_jitter"); } //////////////////////////////////////////////////////////////////////////// // // Alternative keyClick actions // //////////////////////////////////////////////////////////////////////////// private void keyClick(final int keyCode) { new SWTKeyOperation().keyCode(keyCode).execute(); // keyDown(keyCode); // keyUp(keyCode); } // // private void keyDown(final int keyCode) { // trace("post key down " + keyCode); // Event event = new Event(); // event.type = SWT.KeyDown; // event.keyCode = keyCode; // new SWTPushEventOperation(event).execute(); // } // // private void keyUp(final int keyCode) { // trace("post key up " + keyCode); // Event event = new Event(); // event.type = SWT.KeyUp; // event.keyCode = keyCode; // new SWTPushEventOperation(event).execute(); // } public static void pause(int ms) { try { Thread.sleep(ms); } catch(InterruptedException ie) { } } //!pq: a debug flag to tunr on verbose key pressing timing info private static final boolean DEBUG_KEY_INFO = false; // private void trace(String message) { // if (DEBUG_KEY_INFO) // System.out.println(message); // } }