Clover coverage report - clover
Coverage timestamp: Sat Oct 8 2005 22:54:17 EDT
file stats: LOC: 2,050   Methods: 131
NCLOC: 1,479   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
Robot.java 65.1% 76.1% 92.4% 74.4%
coverage coverage
 1   
 package abbot.tester;
 2   
 
 3   
 import java.applet.Applet;
 4   
 import java.awt.*;
 5   
 import java.awt.event.*;
 6   
 import java.awt.geom.Rectangle2D;
 7   
 import java.awt.image.BufferedImage;
 8   
 import java.lang.reflect.InvocationTargetException;
 9   
 import java.util.Collection;
 10   
 import java.util.Iterator;
 11   
 
 12   
 import javax.accessibility.AccessibleAction;
 13   
 import javax.swing.*;
 14   
 
 15   
 import abbot.*;
 16   
 import abbot.finder.*;
 17   
 import abbot.finder.ComponentFinder;
 18   
 import abbot.finder.matchers.*;
 19   
 import abbot.i18n.Strings;
 20   
 import abbot.script.*;
 21   
 import abbot.util.*;
 22   
 
 23   
 /** Provide a higher level of abstraction for user input (A Better Robot).
 24   
     The Robot's operation may be affected by the following properties:<br>
 25   
     <pre><code>abbot.robot.auto_delay</code></pre><br>
 26   
     Set this to a value representing the millisecond count in between
 27   
     generated events.  Usually just set to 100-200 if you want to slow down
 28   
     the playback to simulate actual user input.  The default is zero delay.<br>
 29   
     <pre><code>abbot.robot.mode</code></pre><br>
 30   
     Set this to either "robot" or "awt" to designate the desired mode of event
 31   
     generation.  "robot" uses java.awt.Robot to generate events, while "awt"
 32   
     stuffs events directly into the AWT event queue.<br>
 33   
     <pre><code>abbot.robot.event_post_delay</code></pre><br>
 34   
     This is the maximum number of ms it takes the system to post an AWT event
 35   
     in response to a Robot-generated event.
 36   
     <pre><code>abbot.robot.default_delay</code></pre><br>
 37   
     Base delay setting, acts as default value for the next two.
 38   
     <pre><code>abbot.robot.popup_delay</code></pre><br>
 39   
     Set this to the maximum time to wait for a menu to appear or be generated.
 40   
     <pre><code>abbot.robot.component_delay</code></pre><br>
 41   
     Set this to the maximum time to wait for a Component to become available.
 42   
     <p>
 43   
     NOTE: Only use event queue synchronization (e.g.
 44   
     {@link #invokeAndWait(Runnable)} or {@link #waitForIdle()} when a
 45   
     subsequent robot-level action is being applied to the results of a prior
 46   
     action (e.g. focus, deiconify, menu selection).  Otherwise, don't
 47   
     introduce a mandatory delay (e.g. use {@link #invokeLater(Runnable)}). 
 48   
     <p>
 49   
     NOTE: If a robot action isn't reproduced properly, you may need to
 50   
     introduce either additional events or extra delay.  Adding enforced delay
 51   
     for a given platform is usually preferable to generating additional events,
 52   
     so always try that first, but be sure to restrict it to the platform in 
 53   
     question.
 54   
     <p>
 55   
     NOTE: Robot actions should <b>never</b> be invoked on the event dispatch
 56   
     thread. 
 57   
  */
 58   
 
 59   
 public class Robot implements AWTConstants {
 60   
     /** Use java.awt.Robot to generate events. */
 61   
     public static int EM_ROBOT = 0;
 62   
     /** Post events to the AWT event queue. */
 63   
     public static int EM_AWT   = 1;
 64   
 
 65   
     private static final Toolkit toolkit = Toolkit.getDefaultToolkit();
 66   
     // Max robot delay, in ms
 67   
     private static final int MAX_DELAY = 60000;
 68   
     // TODO: verify this value for X11, etc.; ALT for w32, option for OSX
 69   
     public static final int MOUSELESS_MODIFIER_MASK = InputEvent.ALT_MASK;
 70   
     public static final String MOUSELESS_MODIFIER =
 71   
         AWT.getKeyModifiers(MOUSELESS_MODIFIER_MASK);
 72   
 
 73   
     /** OS X using screenMenuBar actually uses an AWT menu as the live
 74   
         component.  The JXXX components exist, but are not effectively
 75   
         active. 
 76   
     */
 77  64
     protected static final boolean useScreenMenuBar() {
 78   
         // Ideally we'd install a menu and check where it ended up, since the
 79   
         // property is read once at startup and ignored thereafter.
 80  64
         return Platform.isOSX()
 81   
             && (Boolean.getBoolean("com.apple.macos.useScreenMenuBar")
 82   
                 || Boolean.getBoolean("apple.laf.useScreenMenuBar"));
 83   
     }
 84   
 
 85   
     /** Base delay setting. */
 86   
     public static int defaultDelay =
 87   
         Properties.getProperty("abbot.robot.default_delay", 30000, 0, 60000);
 88   
 
 89   
     /** Delay before checking for idle.  This allows the system a little time
 90   
         to put a native event onto the AWT event queue. */
 91   
     private static int eventPostDelay =
 92   
         Properties.getProperty("abbot.robot.event_post_delay", 100, 0, 1000);
 93   
 
 94   
     protected static long IDLE_TIMEOUT =
 95   
         Integer.getInteger("abbot.robot.idle_timeout", 10000).intValue();
 96   
 
 97   
     /** Delay before failing to find a popup menu that should appear. */
 98   
     protected static int popupDelay =
 99   
         Properties.getProperty("abbot.robot.popup_delay",
 100   
                                defaultDelay, 0, 60000);
 101   
 
 102   
     /** Delay before failing to find a component that should be visible. */
 103   
     public static int componentDelay =
 104   
         Properties.getProperty("abbot.robot.component_delay",
 105   
                                defaultDelay, 0, 60000);
 106   
 
 107   
     /** With decreased robot auto delay, OSX popup menus don't activate
 108   
      * properly.  Indicate the minimum delay for proper operation (determined
 109   
      * experimentally). 
 110   
      */
 111  96
     private static final int subMenuDelay = Platform.isOSX() ? 100 : 0; 
 112   
 
 113   
     /** How events are generated. */
 114   
     private static int eventMode = EM_ROBOT;
 115   
     private static boolean verified = false;
 116   
     private static boolean serviceMode = false;
 117   
     
 118   
     // FIXME add one per graphics device?
 119   
     /** The robot used to generate events. */
 120   
     private static java.awt.Robot robot;
 121   
     private static WindowTracker tracker;
 122   
     /** Current input state.  This will either be that of the AWT event queue
 123   
      * or of the robot, depending on the dispatch mode. 
 124   
      * Note that the robot state may be different from that seen by the AWT
 125   
      * event queue, since robot events may be as yet unprocessed.
 126   
      */
 127   
     private static InputState state;
 128   
 
 129   
 
 130   
     /** Suitable inter-event delay for most cases; tests have been run safely
 131   
      * at this value.  Should definitely be less than the double-click
 132   
      * threshold.<p>  
 133   
     */
 134   
     private static final int DEFAULT_DELAY = getPreferredRobotAutoDelay();
 135   
     private static final int SLEEP_INTERVAL = 10;
 136   
 
 137   
     private static int autoDelay = DEFAULT_DELAY;
 138  724
     public static int getAutoDelay() { return autoDelay; }
 139   
     /** Returns a functioning instance of java.awt.Robot. If this method
 140   
      * returns null, it should be assumed that java.awt.Robot is unavailable
 141   
      * or non-functional on the current system.  
 142   
      */
 143  519
     public static java.awt.Robot getRobot() {
 144  519
         return serviceMode ? null : robot;
 145   
     }
 146   
 
 147   
     /** Return a singleton InputState object. */
 148  501
     public static InputState getState() {
 149  501
         return state;
 150   
     }
 151   
 
 152   
     static {
 153  96
         robot = createRobot();
 154  96
         tracker = WindowTracker.getTracker();
 155  96
         state = new InputState();
 156   
     }
 157   
 
 158  96
     private static java.awt.Robot createRobot() {
 159  96
         java.awt.Robot robot = null;
 160  96
         String mode = System.getProperty("abbot.robot.mode", "robot");
 161  96
         autoDelay = Properties.getProperty("abbot.robot.auto_delay",
 162   
                                            autoDelay, -1, 60000);
 163  96
         try {
 164   
             // Even if the robot doesn't work, we can still use it for some
 165   
             // things. 
 166  96
             robot = new java.awt.Robot();
 167  96
             if (autoDelay != -1) {
 168  96
                 robot.setAutoDelay(autoDelay);
 169   
             }
 170   
             else {
 171  0
                 autoDelay = robot.getAutoDelay();
 172   
             }
 173  96
             if (!verified) {
 174  96
                 verified = true;
 175  96
                 boolean skip = "false".
 176   
                     equals(System.getProperty("abbot.robot.verify"));
 177  96
                 if (!skip && !RobotVerifier.verify(robot)) {
 178   
                     // robot doesn't work (w32 service mode)
 179  0
                     serviceMode = true;
 180  0
                     System.err.println("Robot non-functional, "
 181   
                                        + "falling back to AWT mode");
 182  0
                     mode = "awt";
 183   
                 }
 184   
             }
 185   
         }
 186   
         catch(AWTException e) {
 187   
             // no robot available, send AWT events
 188  0
             System.err.println("Falling back to AWT mode: "
 189   
                                + e.getMessage());
 190  0
             mode = "awt";
 191   
         }
 192  96
         if (mode.equals("awt")) {
 193  0
             eventMode = EM_AWT;
 194   
         }
 195  96
         return robot;
 196   
     }
 197   
 
 198   
     /** Returns the current event-generation mode. */
 199  425
     public static int getEventMode() { return eventMode; }
 200  288
     public static String getEventModeDescription() {
 201  288
         String desc = eventMode == EM_ROBOT ? "robot" : "awt";
 202  288
         if (serviceMode)
 203  0
             desc += " (service)";
 204  288
         return desc;
 205   
     }
 206   
 
 207   
     /** Set the event-generation mode.
 208   
         @throws IllegalStateException if the requested mode is EM_ROBOT and
 209   
         java.awt.Robot is unavailable in the current environment.
 210   
      */
 211  27
     public static void setEventMode(int mode) {
 212  27
         if (eventMode != mode) {
 213  10
             if (mode == EM_ROBOT && (serviceMode || robot == null)) {
 214  0
                 String msg = Strings.get("tester.Robot.no_robot_mode");
 215  0
                 throw new IllegalStateException(msg);
 216   
             }
 217  10
             eventMode = mode;
 218   
         }
 219   
     }
 220   
 
 221  21
     public static int getEventPostDelay() { return eventPostDelay; }
 222  0
     public static void setEventPostDelay(int delay) {
 223  0
         eventPostDelay = Math.min(1000, Math.max(0, delay));
 224   
     }
 225   
 
 226   
     /** Allow this to be adjusted, mostly for testing. */
 227  724
     public static void setAutoDelay(int ms) {
 228  724
         ms = Math.min(60000, Math.max(0, ms));
 229  724
         if (eventMode == EM_ROBOT)
 230  684
             robot.setAutoDelay(ms);
 231  724
         autoDelay = ms;
 232  724
         Log.debug("Auto delay set to " + ms);
 233   
     }
 234   
 
 235   
     /** 
 236   
      * Move the mouse to the given location, in screen coordinates.  
 237   
      * NOTE: in robot mode, you may need to invoke this with a little jitter.
 238   
      * There are some conditions where a single mouse move will not
 239   
      * generate the necessary enter event on a component (typically a
 240   
      * dialog with an OK button) before a mousePress.  See also click().
 241   
      * NOTE: does 1.4+ need jitter?
 242   
      */
 243  2149
     private void mouseMove(int x, int y) {
 244  2149
         if (eventMode == EM_ROBOT) {
 245  2149
             Log.debug("ROBOT: Mouse move: (" + x + "," + y + ")");
 246  2149
             robot.mouseMove(x, y);
 247   
         }
 248   
         else {
 249   
             // Can't stuff an AWT event for an arbitrary location
 250   
         }
 251   
     }
 252   
 
 253   
     /** Send a button press event. */
 254  751
     public void mousePress(int buttons) {
 255  751
         if (eventMode == EM_ROBOT) {
 256  750
             Log.debug("ROBOT: Mouse press: " + AWT.getMouseModifiers(buttons));
 257   
             // OSX 1.4.1 accidentally swaps mb2 and mb3; fix it here
 258  750
             robot.mousePress(buttons);
 259   
         }
 260   
         else {
 261  1
             Component c = state.getMouseComponent();
 262  1
             if (c == null) {
 263  0
                 Log.warn("No current mouse component for button press", 4);
 264  0
                 return;
 265   
             }
 266  1
             Point where = state.getMouseLocation();
 267  1
             postMousePress(c, where.x, where.y, buttons);
 268   
         }
 269   
     }
 270   
 
 271   
     /** Send a button release event for button 1. */
 272  27
     public void mouseRelease() {
 273  27
         mouseRelease(MouseEvent.BUTTON1_MASK);
 274   
     }
 275   
 
 276   
     /** Send a button release event. */
 277  797
     public void mouseRelease(int buttons) {
 278  797
         if (eventMode == EM_ROBOT) {
 279  756
             Log.debug("ROBOT: Mouse release: " + AWT.getMouseModifiers(buttons));
 280  756
             robot.mouseRelease(buttons);
 281   
         }
 282   
         else {
 283  41
             Component source = state.isDragging()
 284   
                 ? state.getDragSource()
 285  0
                 : (lastMousePress != null 
 286   
                    ? lastMousePress.getComponent()
 287   
                    : state.getMouseComponent());
 288  41
             Point where = state.getMouseLocation();
 289  41
             if (source == null) {
 290  0
                 Log.warn("Mouse release outside of available frames");
 291  0
                 return;
 292   
             }
 293  41
             else if (where == null) {
 294  0
                 if (lastMousePress == null) {
 295  0
                     Log.warn("No record of most recent mouse press");
 296  0
                     return;
 297   
                 }
 298  0
                 where = lastMousePress.getPoint();
 299   
             }
 300  41
             postMouseRelease(source, where.x, where.y, buttons);
 301   
         }
 302   
     }
 303   
 
 304   
     /** Move keyboard focus to the given component.  Note that the component
 305   
         may not yet have focus when this method returns.
 306   
     */
 307  3
     public void focus(Component comp) {
 308  3
         focus(comp, false);
 309   
     }
 310   
 
 311   
     /** Use an explicit listener, since hasFocus is not always reliable. */
 312   
     private class FocusWatcher extends FocusAdapter {
 313   
         public volatile boolean focused = false;
 314  24
         public FocusWatcher(Component c) {
 315  24
             c.addFocusListener(this);
 316  24
             focused = AWT.getFocusOwner() == c;
 317   
         }
 318  24
         public void focusGained(FocusEvent f) {
 319  24
             focused = true;
 320   
         }
 321  0
         public void focusLost(FocusEvent f) {
 322  0
             focused = false;
 323   
         }
 324   
     }
 325   
 
 326   
     /** Move keyboard focus to the given component. */
 327  125
     public void focus(final Component comp, boolean wait) {
 328  125
         Component currentOwner = AWT.getFocusOwner();
 329  125
         if (currentOwner == comp) {
 330  101
             return;
 331   
         }
 332  24
         Log.debug("Focus change");
 333  24
         FocusWatcher fw = new FocusWatcher(comp);
 334   
         // for pointer focus
 335  24
         mouseMove(comp, comp.getWidth()/2, comp.getHeight()/2); 
 336  24
         waitForIdle();
 337   
         // Make sure the correct window is in front
 338  24
         Window w1 = currentOwner != null 
 339   
             ? AWT.getWindow(currentOwner) : null;
 340  24
         Window w2 = AWT.getWindow(comp);
 341  24
         if (w1 != w2) {
 342  8
             activate(w2);
 343  8
             waitForIdle();
 344   
         }
 345   
         // NOTE: while it would be nice to have a robot method instead of
 346   
         // requesting focus, clicking to change focus may have 
 347   
         // side effects 
 348  24
         invokeAndWait(comp, new Runnable() {
 349  24
             public void run() {
 350  24
                 comp.requestFocus();
 351   
             }
 352   
         });
 353  24
         try {
 354  24
             if (wait) {
 355  24
                 long start = System.currentTimeMillis();
 356  24
                 while (!fw.focused) {
 357  0
                     if (System.currentTimeMillis() - start > componentDelay) {
 358  0
                         String msg =
 359   
                             Strings.get("tester.Robot.focus_failed",
 360   
                                         new Object[] { toString(comp) });
 361  0
                         throw new ActionFailedException(msg);
 362   
                     }
 363  0
                     sleep();
 364   
                 }
 365   
             }
 366   
         }
 367   
         finally {
 368  24
             comp.removeFocusListener(fw);
 369   
         }
 370   
     }
 371   
 
 372   
     /** Usually only needed when dealing with Applets. */
 373  958
     protected EventQueue getEventQueue(Component c) {
 374  958
         return c != null ? tracker.getQueue(c) : toolkit.getSystemEventQueue();
 375   
     }
 376   
 
 377   
     /** Post a runnable on the given component's event queue.  Useful when
 378   
         driving multiple Applets, but is also useful to ensure an operation
 379   
         happens on the event dispatch thread.
 380   
     */
 381  265
     public void invokeLater(Component context, Runnable action) {
 382  265
         EventQueue queue = getEventQueue(context);
 383  265
         queue.postEvent(new InvocationEvent(toolkit, action));
 384   
     }
 385   
 
 386   
     /** Post a runnable on the given component's event queue and wait for it
 387   
         to finish.  */
 388  227
     public void invokeAndWait(Component c, Runnable action) {
 389  227
         invokeLater(c, action);
 390  227
         waitForIdle();
 391   
     }
 392   
 
 393   
     /** @deprecated Method renamed to {@link #invokeLater(Runnable)}
 394   
      * @param action
 395   
      */
 396  0
     public void invokeAction(Runnable action) {
 397  0
         invokeLater(action);
 398   
     }
 399   
 
 400   
     /** @deprecated Method renamed to {@link #invokeLater(Component, Runnable)}
 401   
      * @param c
 402   
      * @param action
 403   
      */
 404  0
     public void invokeAction(Component c, Runnable action) {
 405  0
         invokeLater(c, action);
 406   
     }
 407   
    
 408   
     /** Run the given action on the event dispatch thread.  This should be
 409   
      * used for any non-read-only methods invoked directly on a GUI
 410   
      * component. 
 411   
      * NOTE: if you want to use the results of the action, use invokeAndWait
 412   
      * instead. 
 413   
      */
 414  2
     public void invokeLater(Runnable action) {
 415  2
         invokeLater(null, action);
 416   
     }
 417   
 
 418   
     /** Run the given action on the event dispatch thread, but don't return
 419   
         until it's been run.
 420   
     */
 421  152
     public void invokeAndWait(Runnable action) {
 422  152
         invokeAndWait(null, action);
 423   
     }
 424   
 
 425  150
     public void keyPress(int keycode) {
 426  150
         keyPress(keycode, KeyEvent.CHAR_UNDEFINED);
 427   
     }
 428   
 
 429   
     /** Send a key press event. */
 430  910
     private void keyPress(int keycode, char keyChar) {
 431  910
         if (eventMode == EM_ROBOT) {
 432  754
             Log.debug("ROBOT: key press " + AWT.getKeyCode(keycode));
 433  754
             robot.keyPress(keycode);
 434   
         }
 435   
         else {
 436  156
             int mods = state.getModifiers();
 437  156
             if (AWT.isModifier(keycode))
 438  23
                 mods |= AWT.keyCodeToMask(keycode);
 439  156
             postKeyEvent(KeyEvent.KEY_PRESSED, mods,
 440   
                          keycode, KeyEvent.CHAR_UNDEFINED);
 441   
             // Auto-generate KEY_TYPED events, as best we can
 442  156
             int mask = state.getModifiers();
 443  156
             if (keyChar == KeyEvent.CHAR_UNDEFINED) {
 444  28
                 KeyStroke ks = KeyStroke.getKeyStroke(keycode, mask);
 445  28
                 keyChar = KeyStrokeMap.getChar(ks);
 446   
             }
 447  156
             if (keyChar != KeyEvent.CHAR_UNDEFINED) {
 448  132
                 postKeyEvent(KeyEvent.KEY_TYPED, mask,
 449   
                              KeyEvent.VK_UNDEFINED, keyChar);
 450   
             }
 451   
         }
 452   
     }
 453   
 
 454   
     /** Send a key release event. */
 455  910
     public void keyRelease(int keycode) {
 456  910
         if (eventMode == EM_ROBOT) {
 457  754
             Log.debug("ROBOT: key release " + AWT.getKeyCode(keycode));
 458  754
             robot.keyRelease(keycode);
 459  754
             if (Bugs.hasKeyInputDelay()) {
 460   
                 // OSX, empirical
 461  0
                 int KEY_INPUT_DELAY = 200;
 462  0
                 if (KEY_INPUT_DELAY > autoDelay) {
 463  0
                     delay(KEY_INPUT_DELAY - autoDelay);
 464   
                 }
 465   
             }
 466   
         }
 467   
         else {
 468  156
             int mods = state.getModifiers();
 469  156
             if (AWT.isModifier(keycode))
 470  23
                 mods &= ~AWT.keyCodeToMask(keycode);
 471  156
             postKeyEvent(KeyEvent.KEY_RELEASED, mods,
 472   
                          keycode, KeyEvent.CHAR_UNDEFINED);
 473   
         }
 474   
     }
 475   
 
 476  444
     private void postKeyEvent(int id, int modifiers, int keycode, char ch) {
 477  444
         Component c = findFocusOwner();
 478  444
         if (c != null) {
 479  444
             postEvent(c, new KeyEvent(c, id,
 480   
                                       System.currentTimeMillis(),
 481   
                                       modifiers, keycode, ch));
 482   
         }
 483   
         else {
 484  0
             Log.warn("No component has focus, key press discarded");
 485   
         }
 486   
     }
 487   
 
 488   
     /** Sleep for a little bit, measured in UI time. */
 489  9288
     public void sleep() {
 490  9288
         delay(SLEEP_INTERVAL);
 491   
     }
 492   
 
 493   
     /** Sleep the given duration of ms. */
 494  12154
     public void delay(int ms) {
 495  12154
         if (eventMode == EM_ROBOT) {
 496  11118
             while (ms > MAX_DELAY) {
 497  0
                 robot.delay(MAX_DELAY);
 498  0
                 ms -= MAX_DELAY;
 499   
             }
 500  11118
             robot.delay(ms);
 501   
         }
 502   
         else {
 503  1036
             try { Thread.sleep(ms); } catch(InterruptedException ie) { }
 504   
         }
 505   
     }
 506   
 
 507   
     private static final Runnable EMPTY_RUNNABLE =
 508  1737
         new Runnable() { public void run() { } };
 509   
 
 510   
     /** Check for a blocked event queue (symptomatic of an active w32 AWT
 511   
      * popup menu). 
 512   
      * @return whether the event queue is blocked.  
 513   
      */
 514  10
     protected boolean queueBlocked() {
 515  10
         return postInvocationEvent(toolkit.getSystemEventQueue(),
 516   
                                    toolkit, 200);
 517   
     }
 518   
 
 519   
     /** @return whether we timed out waiting for the invocation to run */
 520  1737
     protected boolean postInvocationEvent(EventQueue eq, Toolkit toolkit,
 521   
                                           long timeout) {
 522   
         class RobotIdleLock { }
 523  1737
         Object lock = new RobotIdleLock();
 524  1737
         synchronized(lock) {
 525  1737
             eq.postEvent(new InvocationEvent(toolkit, EMPTY_RUNNABLE,
 526   
                                              lock, true));
 527  1737
             long start = System.currentTimeMillis();
 528  1737
             try {
 529   
                 // NOTE: on fast linux systems when showing a dialog, if we
 530   
                 // don't provide a timeout, we're never notified, and the
 531   
                 // test will wait forever (up through 1.5.0_05).
 532  1737
                 lock.wait(timeout);
 533  1737
                 return (System.currentTimeMillis() - start) >= IDLE_TIMEOUT;
 534   
             }
 535   
             catch(InterruptedException e) {
 536  0
                 Log.warn("Invocation lock interrupted");
 537   
             }
 538   
         }
 539  0
         return false;
 540   
     }
 541   
 
 542  1724
     private void waitForIdle(EventQueue eq) {
 543  1724
         if (EventQueue.isDispatchThread())
 544  0
             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
 545   
 
 546   
         // NOTE: as of Java 1.3.1, robot.waitForIdle only waits for the
 547   
         // last event on the queue at the time of this invocation to be
 548   
         // processed.  We need better than that.  Make sure the given event
 549   
         // queue is empty when this method returns
 550   
 
 551   
         // We always post at least one idle event to allow any current event
 552   
         // dispatch processing to finish.
 553  1724
         long start = System.currentTimeMillis();
 554  1724
         int count = 0;
 555  1724
         do {
 556   
 
 557  1727
             if (postInvocationEvent(eq, toolkit, IDLE_TIMEOUT)) {
 558  1
                 Log.warn("Timed out waiting for posted invocation event: "
 559   
                          + IDLE_TIMEOUT + "ms (after " + count + " events)",
 560   
                          Log.FULL_STACK);
 561  1
                 break;
 562   
             }
 563  1726
             if (System.currentTimeMillis() - start > IDLE_TIMEOUT) {
 564  0
                 Log.warn("Timed out waiting for idle event queue after "
 565   
                          + count + " events");
 566  0
                 break;
 567   
             }
 568   
 
 569  1726
             ++count;
 570   
 
 571   
             // NOTE: this does not detect invocation events (i.e. what
 572   
             // gets posted with EventQueue.invokeLater), so if someone
 573   
             // is repeatedly posting one, we might get stuck.  Not too
 574   
             // worried, since if a Runnable keeps calling invokeLater
 575   
             // on itself, *nothing* else gets much chance to run, so it
 576   
             // seems to be a bad programming practice.
 577  1726
         } while (eq.peekEvent() != null);
 578   
     }
 579   
 
 580   
     /**
 581   
      * Wait for an idle AWT event queue.  Note that this is different from
 582   
      * the implementation of <code>java.awt.Robot.waitForIdle()</code>, which
 583   
      * may have events on the queue when it returns.  Do <b>NOT</b> use this
 584   
      * method if there are animations or other continual refreshes happening,
 585   
      * since in that case it may never return.<p>
 586   
      */
 587  1646
     public void waitForIdle() {
 588  1646
         if (eventPostDelay > autoDelay) {
 589  1646
             delay(eventPostDelay - autoDelay);
 590   
         }
 591  1646
         Collection queues = tracker.getEventQueues();
 592  1646
         if (queues.size() == 1) {
 593  1598
             waitForIdle(toolkit.getSystemEventQueue());
 594   
         }
 595   
         else {
 596   
             // FIXME this resurrects dead event queues
 597  48
             Iterator iter = queues.iterator();
 598  48
             while (iter.hasNext()) {
 599  126
                 EventQueue eq = (EventQueue)iter.next();
 600  126
                 waitForIdle(eq);
 601   
             }
 602   
         }
 603   
     }
 604   
 
 605   
     /** Sample the color at the given point on the screen. */
 606  6
     public Color sample(int x, int y) {
 607   
         // Service mode always returns black when sampled
 608  6
         if (robot != null && !serviceMode) 
 609  6
             return robot.getPixelColor(x, y);
 610  0
         String msg = Strings.get("tester.Robot.no_sample");
 611  0
         throw new UnsupportedOperationException(msg);
 612   
     }
 613   
 
 614   
     /** Sample the color at the given point on the component. */
 615  0
     public Color sample(Component c, int x, int y) {
 616  0
         Point p = AWT.getLocationOnScreen(c);
 617  0
         return sample(p.x + x, p.y + y);
 618   
     }
 619   
 
 620   
     /** Capture the contents of the given rectangle. */
 621   
     /* NOTE: Text components (and maybe others with a custom cursor) will
 622   
      * capture the cursor.  May want to move the cursor out of the component
 623   
      * bounds, although this might cause issues where the component is
 624   
      * responding visually to mouse movement. 
 625   
      * Is this an OSX bug?
 626   
      */
 627  3
     public BufferedImage capture(Rectangle bounds) {
 628  3
         return robot != null ? robot.createScreenCapture(bounds) : null;
 629   
     }
 630   
 
 631   
     /** Capture the contents of the given component, sans any border or
 632   
      * insets.  This should only be used on components that do not use a LAF
 633   
      * UI, or the results will not be consistent across platforms.
 634   
      */ 
 635  3
     public BufferedImage capture(Component comp) {
 636  3
         return capture(comp, true);
 637   
     }
 638   
 
 639   
     /** Capture the contents of the given component, optionally including the
 640   
      * border and/or insets.  This should only be used on components that do
 641   
      * not use a LAF UI, or the results will not be consistent across
 642   
      * platforms. 
 643   
      */ 
 644  3
     public BufferedImage capture(Component comp, boolean ignoreBorder) {
 645  3
         Rectangle bounds = new Rectangle(comp.getSize());
 646  3
         Point loc = AWT.getLocationOnScreen(comp);
 647  3
         bounds.x = loc.x;
 648  3
         bounds.y = loc.y;
 649  3
         Log.debug("Component bounds " + bounds);
 650   
         // Make sure we don't sample the decorating space
 651  3
         if (ignoreBorder && (comp instanceof Container)) {
 652  3
             Insets insets = ((Container)comp).getInsets();
 653  3
             bounds.x += insets.left;
 654  3
             bounds.y += insets.top;
 655  3
             bounds.width -= insets.left + insets.right;
 656  3
             bounds.height -= insets.top + insets.bottom;
 657  3
             Log.debug("Component insets " + insets);
 658  3
             Log.debug("Component bounds " + bounds);
 659   
         }
 660  3
         return capture(bounds);
 661   
     }
 662   
 
 663   
     // Bug workaround support
 664  275
     protected void jitter(Component comp, int x, int y) {
 665  275
         mouseMove(comp, (x > 0 ? x - 1 : x + 1), y);
 666   
     }
 667   
 
 668   
     // Bug workaround support
 669  0
     protected void jitter(int x, int y) {
 670  0
         mouseMove((x > 0 ? x - 1 : x + 1), y);
 671   
     }
 672   
 
 673   
     /** Move the pointer to the center of the given component. */
 674  44
     public void mouseMove(Component comp) {
 675  44
         mouseMove(comp, comp.getWidth() / 2, comp.getHeight() / 2);
 676   
     }
 677   
 
 678   
     /** Wait the given number of ms for the component to be showing and
 679   
         ready.  Returns false if the operation times out. */
 680  1474
     private boolean waitForComponent(Component c, long delay) {
 681  1474
         if (!isReadyForInput(c)) {
 682  134
             Log.debug("Waiting for component to show");
 683  134
             long start = System.currentTimeMillis();
 684  134
             while (!isReadyForInput(c)) {
 685  735
                 if (c instanceof JPopupMenu) {
 686   
                     // wiggle the mouse over the parent menu item to
 687   
                     // ensure the submenu shows
 688  275
                     Component invoker = ((JPopupMenu)c).getInvoker();
 689  275
                     if (invoker instanceof JMenu) {
 690  275
                         jitter(invoker, invoker.getWidth()/2, 
 691   
                                invoker.getHeight()/2);
 692   
                     }
 693   
                 }
 694  735
                 if (System.currentTimeMillis() - start > delay) {
 695  0
                     Log.warn("Component " + toString(c)
 696   
                              + " (" + Integer.toHexString(c.hashCode()) + ")"
 697   
                              + " not ready after " + delay + "ms: "
 698   
                              + "showing=" + c.isShowing()
 699   
                              + " win ready="
 700   
                              + tracker.isWindowReady(AWT.getWindow(c)));
 701  0
                     return false;
 702   
                 }
 703  735
                 sleep();
 704   
             }
 705   
         }
 706  1474
         return true;
 707   
     }
 708   
 
 709   
     /** If a component does not have mouse events enabled, use the
 710   
         first ancestor which does.
 711   
     */
 712  516
     private Component retargetMouseEvent(Component comp, int id, Point pt) {
 713  516
         Point where = pt;
 714  516
         while (!(comp instanceof Window)
 715   
                && !AWT.eventTypeEnabled(comp, id)) {
 716  45
             Log.debug("Retargeting event, " + toString(comp) 
 717   
                       + " not interested");
 718  45
             where = SwingUtilities.convertPoint(comp, where.x, where.y,
 719   
                                                 comp.getParent());
 720  45
             comp = comp.getParent();
 721   
         }
 722  516
         pt.setLocation(where);
 723  516
         return comp;
 724   
     }
 725   
 
 726   
     /** Move the pointer to the given coordinates relative to the given
 727   
      * component.
 728   
      */ 
 729  1425
     public void mouseMove(Component comp, int x, int y) {
 730  1425
         if (!waitForComponent(comp, componentDelay)) {
 731  0
             String msg = "Can't obtain position of component "
 732   
                 + toString(comp);
 733  0
             throw new ComponentNotShowingException(msg);
 734   
         }
 735  1425
         if (eventMode == EM_ROBOT) {
 736  1235
             try {
 737  1235
                 Point point = AWT.getLocationOnScreen(comp);
 738  1235
                 if (point != null) {
 739  1235
                     point.translate(x, y);
 740  1235
                     mouseMove(point.x, point.y);
 741   
                 }
 742   
             }
 743   
             catch(java.awt.IllegalComponentStateException e) {
 744   
             }
 745   
         }
 746   
         else {
 747  190
             Component eventSource = comp;
 748  190
             int id = MouseEvent.MOUSE_MOVED;
 749  190
             boolean outside = false;
 750   
 
 751   
             // When dragging, the event source is always the target of the
 752   
             // original mouse press.
 753  190
             if (state.isDragging()) {
 754  0
                 id = MouseEvent.MOUSE_DRAGGED;
 755  0
                 eventSource = state.getDragSource();
 756   
             }
 757   
             else {
 758  190
                 Point pt = new Point(x, y);
 759  190
                 eventSource = comp = retargetMouseEvent(comp, id, pt);
 760  190
                 x = pt.x; y = pt.y;
 761  190
                 outside = x < 0 || y < 0
 762   
                     || x >= comp.getWidth() || y >= comp.getHeight();
 763   
             }
 764   
 
 765  190
             Component current = state.getMouseComponent();
 766  190
             if (current != comp) {
 767  54
                 if (outside && current != null) {
 768  0
                     Point pt = 
 769   
                         SwingUtilities.convertPoint(comp, x, y, current);
 770  0
                     postMouseMotion(current, MouseEvent.MOUSE_EXITED, pt);
 771  0
                     return;
 772   
                 }
 773  54
                 postMouseMotion(comp, MouseEvent.MOUSE_ENTERED,
 774   
                                 new Point(x, y));
 775   
             }
 776  190
             Point pt = new Point(x, y);
 777  190
             if (id == MouseEvent.MOUSE_DRAGGED) {
 778   
                 // Drag coordinates are relative to drag source component
 779  0
                 pt = SwingUtilities.convertPoint(comp, pt, eventSource);
 780   
             }
 781  190
             postMouseMotion(eventSource, id, pt);
 782   
             // Add an exit event if warranted
 783  190
             if (outside) {
 784  0
                 postMouseMotion(comp, MouseEvent.MOUSE_EXITED,
 785   
                                 new Point(x, y));
 786   
             }
 787   
         }
 788   
     }
 789   
 
 790   
     /** Move the mouse appropriately to get from the source to the
 791   
         destination.  Enter/exit events will be generated where appropriate.
 792   
     */
 793  38
     public void dragOver(Component dst, int x, int y) {
 794  38
         mouseMove(dst, x-4, y);
 795  38
         mouseMove(dst, x, y);
 796   
     }
 797   
 
 798   
     /** Begin a drag operation using button 1.<p>
 799   
         This method is tuned for native drag/drop operations, so if you get
 800   
         odd behavior, you might try using a simple
 801   
         {@link #mousePress(Component,int,int)} instead. 
 802   
     */
 803  6
     public void drag(Component src, int sx, int sy) {
 804  6
         drag(src, sx, sy, InputEvent.BUTTON1_MASK);
 805   
     }
 806   
 
 807   
 
 808   
     /** Begin a drag operation using the given button mask.<p>
 809   
         This method is tuned for native drag/drop operations, so if you get
 810   
         odd behavior, you might try using a simple
 811   
         {@link #mousePress(Component,int,int,int)} instead. 
 812   
     */
 813   
     // TODO: maybe auto-switch to robot mode if available?
 814  35
     public void drag(Component src, int sx, int sy, int buttons) {
 815  35
         if (Bugs.dragDropRequiresNativeEvents()
 816   
             && eventMode != EM_ROBOT
 817   
             && !Boolean.getBoolean("abbot.ignore_drag_error")) {
 818  0
             String msg = Strings.get("abbot.Robot.no_drag_available");
 819  0
             if (serviceMode) {
 820   
                 // If we start a native drag in this mode, it'll pretty much
 821   
                 // lock up the system, apparently with the native AWT libs
 822   
                 // starting a thread invisible to the VM that chews up all CPU
 823   
                 // time. 
 824  0
                 throw new ActionFailedException(msg);
 825   
             }
 826  0
             Log.warn(msg);
 827   
         }
 828   
 
 829   
         // Some platforms require a pause between mouse down and mouse motion
 830  35
         int DRAG_DELAY = 
 831   
             Properties.getProperty("abbot.robot.drag_delay",
 832  35
                                    Platform.isX11() || Platform.isOSX()
 833   
                                    ? 100 : 0, 0,
 834   
                                    60000);
 835   
 
 836  35
         mousePress(src, sx, sy, buttons);
 837  35
         if (DRAG_DELAY > autoDelay)
 838  0
             delay(DRAG_DELAY);
 839  35
         if (Platform.isWindows() || Platform.isMacintosh()) {
 840  35
             int dx = sx + AWTConstants.DRAG_THRESHOLD < src.getWidth()
 841   
                 ? AWTConstants.DRAG_THRESHOLD : 0;
 842  35
             int dy = sy + AWTConstants.DRAG_THRESHOLD < src.getHeight()
 843   
                 ? AWTConstants.DRAG_THRESHOLD : 0;
 844  35
             if (dx == 0 && dy == 0)
 845  0
                 dx = AWTConstants.DRAG_THRESHOLD;
 846  35
             mouseMove(src, sx + dx/4, sy + dy/4);
 847  35
             mouseMove(src, sx + dx/2, sy + dy/2);
 848  35
             mouseMove(src, sx + dx, sy + dy);
 849  35
             mouseMove(src, sx + dx + 1, sy + dy);
 850   
         }
 851   
         else {
 852  0
             mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD/2, sy + AWTConstants.DRAG_THRESHOLD/2);
 853  0
             mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD, sy + AWTConstants.DRAG_THRESHOLD);
 854  0
             mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD/2, sy + AWTConstants.DRAG_THRESHOLD/2);
 855  0
             mouseMove(src, sx, sy);
 856   
         }
 857   
     }
 858   
 
 859   
     /** End a drag operation, releasing the mouse button over the given target
 860   
         location.<p>
 861   
         This method is tuned for native drag/drop operations, so if you get
 862   
         odd behavior, you might try using a simple
 863   
         {@link #mouseMove(Component,int,int)}, {@link #mouseRelease()}
 864   
         instead.  
 865   
     */
 866  35
     public void drop(Component target, int x, int y) {
 867   
         // Delay between final move and drop to ensure drop ends.
 868  35
         int DROP_DELAY = 
 869   
             Properties.getProperty("abbot.robot.drop_delay",
 870  35
                                    Platform.isWindows() ? 200 : 0, 0, 60000);
 871   
 
 872   
         // All motion events are relative to the drag source
 873  35
         long start = System.currentTimeMillis();
 874  35
         while (!state.isDragging()) {
 875  0
             if (System.currentTimeMillis() - start > eventPostDelay) {
 876  0
                 String msg = Strings.get("Robot.no_current_drag");
 877  0
                 throw new ActionFailedException(msg);
 878   
             }
 879  0
             sleep();
 880   
         }
 881  35
         dragOver(target, x, y);
 882  35
         if (DROP_DELAY > autoDelay)
 883  35
             delay(DROP_DELAY - autoDelay);
 884   
 
 885  35
         mouseRelease(state.getButtons());
 886   
     }
 887   
 
 888   
     /** Generate a mouse enter/exit/move/drag for the destination component.
 889   
      * NOTE: The VM automatically usually generates exit events; need a test
 890   
      * to define the behavior, though.
 891   
      */
 892  244
     private void postMouseMotion(Component dst, int id, Point to) {
 893   
         // The VM auto-generates exit events as needed (1.3, 1.4)
 894  244
         if (id != MouseEvent.MOUSE_DRAGGED)
 895  244
             dst = retargetMouseEvent(dst, id, to);
 896   
         // Avoid multiple moves to the same location
 897  244
         if (state.getMouseComponent() != dst
 898   
             || !to.equals(state.getMouseLocation())) {
 899  86
             postEvent(dst, new MouseEvent(dst, id, System.currentTimeMillis(),
 900   
                                           state.getModifiers(), to.x, to.y,
 901   
                                           state.getClickCount(), false));
 902   
         }
 903   
     }
 904   
 
 905   
     /** Type the given keycode with no modifiers. */
 906  8
     public void key(int keycode) {
 907  8
         key(keycode, 0);
 908   
     }
 909   
 
 910   
     /** Press or release the appropriate modifiers corresponding to the given
 911   
         mask.
 912   
     */
 913  2934
     public void setModifiers(int modifiers, boolean press) {
 914  2934
         boolean altGraph = (modifiers & InputEvent.ALT_GRAPH_MASK) != 0;
 915  2934
         boolean shift = (modifiers & InputEvent.SHIFT_MASK) != 0;
 916  2934
         boolean alt = (modifiers & InputEvent.ALT_MASK) != 0;
 917  2934
         boolean ctrl = (modifiers & InputEvent.CTRL_MASK) != 0;
 918  2934
         boolean meta = (modifiers & InputEvent.META_MASK) != 0;
 919  2934
         if (press) {
 920  0
             if (altGraph) keyPress(KeyEvent.VK_ALT_GRAPH);
 921  6
             if (alt) keyPress(KeyEvent.VK_ALT);
 922  111
             if (shift) keyPress(KeyEvent.VK_SHIFT);
 923  24
             if (ctrl) keyPress(KeyEvent.VK_CONTROL);
 924  0
             if (meta) keyPress(KeyEvent.VK_META);
 925   
         }
 926   
         else {
 927   
             // For consistency, release in the reverse order of press
 928  0
             if (meta) keyRelease(KeyEvent.VK_META);
 929  24
             if (ctrl) keyRelease(KeyEvent.VK_CONTROL);
 930  111
             if (shift) keyRelease(KeyEvent.VK_SHIFT);
 931  6
             if (alt) keyRelease(KeyEvent.VK_ALT);
 932  0
             if (altGraph) keyRelease(KeyEvent.VK_ALT_GRAPH);
 933   
         }
 934   
     }
 935   
 
 936   
     /** Type the given keycode with the given modifiers.  Modifiers is a mask
 937   
      * from the available InputEvent masks.
 938   
      */
 939  112
     public void key(int keycode, int modifiers) {
 940  112
         key(KeyEvent.CHAR_UNDEFINED, keycode, modifiers);
 941   
     }
 942   
 
 943  760
     private void key(char ch, int keycode, int modifiers) {
 944  760
         Log.debug("key keycode=" + AWT.getKeyCode(keycode)
 945   
                   + " mod=" + AWT.getKeyModifiers(modifiers));
 946  760
         boolean isModifier = true;
 947  760
         switch(keycode) {
 948  0
         case KeyEvent.VK_ALT_GRAPH:
 949  0
             modifiers |= InputEvent.ALT_GRAPH_MASK; break;
 950  0
         case KeyEvent.VK_ALT:
 951  0
             modifiers |= InputEvent.ALT_MASK; break;
 952  0
         case KeyEvent.VK_SHIFT:
 953  0
             modifiers |= InputEvent.SHIFT_MASK; break;
 954  0
         case KeyEvent.VK_CONTROL:
 955  0
             modifiers |= InputEvent.CTRL_MASK; break;
 956  0
         case KeyEvent.VK_META:
 957  0
             modifiers |= InputEvent.META_MASK;break;
 958  760
         default: isModifier = false; break;
 959   
         }
 960  760
         setModifiers(modifiers, true);
 961  760
         if (!isModifier) {
 962  760
             keyPress(keycode, ch);
 963  760
             keyRelease(keycode);
 964   
         }
 965  760
         setModifiers(modifiers, false);
 966  760
         if (Bugs.hasKeyStrokeGenerationBug())
 967  0
             delay(100);
 968   
     }
 969   
 
 970   
     /**
 971   
      * Type the given character.  Note that this sends the key to whatever
 972   
      * component currently has the focus.
 973   
      */
 974   
     // FIXME should this be renamed to "key"?
 975  656
     public void keyStroke(char ch) {
 976  656
         KeyStroke ks = KeyStrokeMap.getKeyStroke(ch);
 977  656
         if (ks == null) {
 978   
             // If no mapping is available, we omit press/release events and
 979   
             // only generate a KEY_TYPED event
 980  8
             Log.debug("No key mapping for '" + ch + "'");
 981  8
             Component focus = findFocusOwner();
 982  8
             if (focus == null) {
 983  0
                 Log.warn("No component has focus, keystroke discarded",
 984   
                          Log.FULL_STACK);
 985  0
                 return;
 986   
             }
 987  8
             KeyEvent ke = new KeyEvent(focus, KeyEvent.KEY_TYPED,
 988   
                                        System.currentTimeMillis(),
 989   
                                        0, KeyEvent.VK_UNDEFINED, ch);
 990   
             // Allow any pending robot events to complete; otherwise we
 991   
             // might stuff the typed event before previous robot-generated
 992   
             // events are posted.
 993  8
             if (eventMode == EM_ROBOT)
 994  4
                 waitForIdle();
 995  8
             postEvent(focus, ke);
 996   
         }
 997   
         else {
 998  648
             int keycode = ks.getKeyCode();
 999  648
             int mod = ks.getModifiers();
 1000  648
             Log.debug("Char '" + ch + "' generated by keycode=" 
 1001   
                       + keycode + " mod=" + mod);
 1002  648
             key(ch, keycode, mod);
 1003   
         }
 1004   
     }
 1005   
 
 1006   
     /** Type the given string. */
 1007  34
     public void keyString(String str) {
 1008  34
         char[] ch = str.toCharArray();
 1009  34
         for (int i=0;i < ch.length;i++) {
 1010  655
             keyStroke(ch[i]);
 1011   
         }
 1012   
     }
 1013   
 
 1014  2
     public void mousePress(Component comp) {
 1015  2
         mousePress(comp, InputEvent.BUTTON1_MASK);
 1016   
     }
 1017   
 
 1018  2
     public void mousePress(Component comp, int mask) {
 1019  2
         mousePress(comp, comp.getWidth() / 2, comp.getHeight() / 2, mask);
 1020   
     }
 1021   
 
 1022  19
     public void mousePress(Component comp, int x, int y) {
 1023  19
         mousePress(comp, x, y, InputEvent.BUTTON1_MASK);
 1024   
     }
 1025   
 
 1026   
     /** Mouse down in the given part of the component.  All other mousePress
 1027   
         methods must eventually invoke this one.
 1028   
     */
 1029  763
     public void mousePress(Component comp, int x, int y, int mask) {
 1030  763
         if (eventMode == EM_ROBOT && Bugs.hasRobotMotionBug()) {
 1031  0
             jitter(comp, x, y);
 1032   
         }
 1033  763
         mouseMove(comp, x, y);
 1034  763
         if (eventMode == EM_ROBOT)
 1035  723
             mousePress(mask);
 1036   
         else {
 1037  40
             postMousePress(comp, x, y, mask);
 1038   
         }
 1039   
     }
 1040   
 
 1041   
     /** Post a mouse press event to the AWT event queue for the given
 1042   
         component.
 1043   
     */
 1044  41
     private void postMousePress(Component comp, int x, int y, int mask) {
 1045  41
         long when = lastMousePress != null 
 1046   
             ? lastMousePress.getWhen() : 0;
 1047  41
         long now = System.currentTimeMillis();
 1048  41
         int count = 1;
 1049  41
         Point where = new Point(x, y);
 1050  41
         comp = retargetMouseEvent(comp, MouseEvent.MOUSE_PRESSED, where);
 1051  41
         if (countingClicks
 1052   
             && comp == lastMousePress.getComponent()) {
 1053  1
             long delta = now - when;
 1054  1
             if (delta < AWTConstants.MULTI_CLICK_INTERVAL) {
 1055  1
                 count = state.getClickCount() + 1;
 1056   
             }
 1057   
         }
 1058  41
         postEvent(comp, new MouseEvent(comp, MouseEvent.MOUSE_PRESSED, now,
 1059   
                                        state.getKeyModifiers() | mask,
 1060   
                                        where.x, where.y, count,
 1061   
                                        AWTConstants.POPUP_ON_PRESS
 1062   
                                        && (mask & AWTConstants.POPUP_MASK) != 0));
 1063   
     }
 1064   
 
 1065   
     /** Post a mouse release event to the AWT event queue for the given
 1066   
         component.
 1067   
     */
 1068  41
     private void postMouseRelease(Component c, int x, int y, int mask) {
 1069  41
         long now = System.currentTimeMillis();
 1070  41
         int count = state.getClickCount();
 1071  41
         Point where = new Point(x, y);
 1072  41
         c = retargetMouseEvent(c, MouseEvent.MOUSE_PRESSED, where);
 1073  41
         postEvent(c, new MouseEvent(c, MouseEvent.MOUSE_RELEASED, now,
 1074   
                                     state.getKeyModifiers() | mask,
 1075   
                                     where.x, where.y, count,
 1076   
                                     !AWTConstants.POPUP_ON_PRESS
 1077   
                                     && (mask & AWTConstants.POPUP_MASK) != 0));
 1078   
     }
 1079   
 
 1080   
     /** Click in the center of the given component. */
 1081  414
     final public void click(Component comp) {
 1082  414
         click(comp, comp.getWidth()/2, comp.getHeight()/2);
 1083   
     }
 1084   
 
 1085   
     /** Click in the center of the given component, specifying which button. */
 1086  4
     final public void click(Component comp, int mask) {
 1087  4
         click(comp, comp.getWidth()/2, comp.getHeight()/2, mask);
 1088   
     }
 1089   
 
 1090   
     /** Click in the component at the given location. */
 1091  446
     final public void click(Component comp, int x, int y) {
 1092  446
         click(comp, x, y, InputEvent.BUTTON1_MASK);
 1093   
     }
 1094   
 
 1095   
     /** Click in the component at the given location with the given button. */
 1096  467
     final public void click(Component comp, int x, int y, int mask) {
 1097  467
         click(comp, x, y, mask, 1);
 1098   
     }
 1099   
 
 1100   
     /** Click in the given part of the component.  All other click methods
 1101   
      * must eventually invoke this one.  This method sometimes needs to be
 1102   
      * redefined (i.e. JComponent to scroll before clicking).
 1103   
      */
 1104  707
     public void click(Component comp, int x, int y, int mask, int count) {
 1105  707
         Log.debug("Click at (" + x + "," + y + ") on " + toString(comp)
 1106  707
                   + (count > 1 ? (" count=" + count) : ""));
 1107  707
         int keyModifiers = mask & ~AWTConstants.BUTTON_MASK;
 1108  707
         mask &= AWTConstants.BUTTON_MASK;
 1109  707
         setModifiers(keyModifiers, true);
 1110   
         // Adjust the auto-delay to ensure we actually get a multiple click
 1111   
         // In general clicks have to be less than 200ms apart, although the
 1112   
         // actual setting is not readable by java that I'm aware of.
 1113  707
         int oldDelay = getAutoDelay();
 1114  707
         if (count > 1 && oldDelay * 2 > 200) {
 1115  0
             setAutoDelay(0);
 1116   
         }
 1117  707
         long last = System.currentTimeMillis();
 1118  707
         mousePress(comp, x, y, mask);
 1119  707
         while (count-- > 1) {
 1120  19
             mouseRelease(mask);
 1121  19
             long delta = System.currentTimeMillis() - last;
 1122  19
             if (delta > AWTConstants.MULTI_CLICK_INTERVAL)
 1123  0
                 Log.warn("Unexpected delay in multi-click: " + delta);
 1124  19
             last = System.currentTimeMillis();
 1125  19
             mousePress(mask);
 1126   
         }
 1127  707
         setAutoDelay(oldDelay);
 1128  707
         mouseRelease(mask);
 1129  707
         setModifiers(keyModifiers, false);
 1130   
     }
 1131   
 
 1132   
     /** @deprecated Renamed to {@link #selectAWTMenuItem(Frame,String)}. */
 1133  0
     public void selectAWTMenuItemByLabel(Frame frame, String path) {
 1134  0
         selectAWTMenuItem(frame, path);
 1135   
     }
 1136   
 
 1137   
     /** Select the given menu item from the given Frame.  The given String may
 1138   
         be either a label or path of labels, but must uniquely identify the
 1139   
         menu item.  For example, "Copy" would be valid if there is only one
 1140   
         instance of that menu label under the MenuBar, otherwise you would
 1141   
         need to specify "Edit|Copy" to ensure the proper selection. 
 1142   
         Note that this method doesn't require referencing the MenuComponent
 1143   
         directly as a parameter. 
 1144   
      */
 1145  4
     public void selectAWTMenuItem(Frame frame, String path) {
 1146  4
         MenuBar mb = frame.getMenuBar();
 1147  4
         if (mb == null) {
 1148  0
             String msg = Strings.get("tester.Robot.no_menu_bar",
 1149   
                                      new Object[] { toString(frame) });
 1150  0
             throw new ActionFailedException(msg);
 1151   
         }
 1152  4
         MenuItem[] items = AWT.findAWTMenuItems(frame, path);
 1153  4
         if (items.length == 0) {
 1154  0
             String msg = Strings.get("tester.Robot.no_menu_item",
 1155   
                                      new Object[] { path, toString(frame) });
 1156  0
             throw new ActionFailedException(msg);
 1157   
         }
 1158  4
         if (items.length > 1) {
 1159  0
             String msg = Strings.get("tester.Robot.multiple_menu_items");
 1160  0
             throw new ActionFailedException(msg);
 1161   
         }
 1162  4
         selectAWTMenuItem(items[0]);
 1163   
     }
 1164   
 
 1165   
     /** @deprecated Renamed to
 1166   
         {@link #selectAWTPopupMenuItem(Component,String)}.
 1167   
     */
 1168  0
     public void selectAWTPopupMenuItemByLabel(Component invoker, String path) {
 1169  0
         selectAWTPopupMenuItem(invoker, path);
 1170   
     }
 1171   
 
 1172   
     /** Select the given menu item from a PopupMenu on the given Component.
 1173   
         The given String may be either a label or path of labels, but must
 1174   
         uniquely identify the menu item.  For example, "Copy" would be valid
 1175   
         if there is only one instance of that menu label under the MenuBar,
 1176   
         otherwise you would need to specify "Edit|Copy" to ensure the proper
 1177   
         selection.  If there are more than one PopupMenu registerd on the
 1178   
         invoking component, you will need to prefix the PopupMenu name as
 1179   
         well, e.g. "popup0|Edit|Copy". */ 
 1180  4
     public void selectAWTPopupMenuItem(Component invoker, String path) {
 1181  4
         try {
 1182  4
             PopupMenu[] popups = AWT.getPopupMenus(invoker);
 1183  4
             if (popups.length == 0)
 1184  0
                 throw new ActionFailedException(Strings.get("tester.Robot.awt_popup_missing"));
 1185   
             
 1186  4
             MenuItem[] items = AWT.findAWTPopupMenuItems(invoker, path);
 1187  4
             if (items.length == 1) {
 1188  4
                 selectAWTPopupMenuItem(items[0]);
 1189  4
                 return;
 1190   
             }
 1191  0
             else if (items.length == 0) {
 1192  0
                 String msg = Strings.get("tester.Robot.no_popup_menu_item",
 1193   
                                          new Object[] { path,
 1194   
                                                         toString(invoker) });
 1195  0
                 throw new ActionFailedException(msg);
 1196   
             }
 1197  0
             String msg = Strings.get("tester.Robot.multiple_menu_items",
 1198   
                                      new Object[] { path });
 1199  0
             throw new ActionFailedException(msg);
 1200   
         }
 1201   
         finally {
 1202  4
             AWT.dismissAWTPopup();
 1203   
         }
 1204   
     }
 1205   
 
 1206  10
     protected void fireAccessibleAction(Component context,
 1207   
                                         final AccessibleAction action,
 1208   
                                         String name) {
 1209  10
         if (action != null && action.getAccessibleActionCount() > 0) {
 1210  10
             invokeLater(context, new Runnable() {
 1211  10
                 public void run() {
 1212  10
                     action.doAccessibleAction(0);
 1213   
                 }
 1214   
             });
 1215   
         }
 1216   
         else {
 1217  0
             String msg = Strings.get("tester.Robot.no_accessible_action",
 1218   
                                      new String[] { name });
 1219  0
             throw new ActionFailedException(msg);
 1220   
         }
 1221   
     }
 1222   
 
 1223  10
     private Component getContext(MenuComponent item) {
 1224  10
         while (!(item.getParent() instanceof Component)
 1225   
                && item.getParent() instanceof MenuComponent)
 1226  17
             item = (MenuComponent)item.getParent();
 1227  10
         return (Component)item.getParent();
 1228   
     }
 1229   
 
 1230   
     /** Select an AWT menu item.  */
 1231  4
     public void selectAWTMenuItem(MenuComponent item) {
 1232   
         // Can't do this through coordinates because MenuComponents don't
 1233   
         // store any of that information
 1234  4
         fireAccessibleAction(getContext(item), item.getAccessibleContext().
 1235   
                              getAccessibleAction(), toString(item));
 1236  4
         if (queueBlocked())
 1237  0
             key(KeyEvent.VK_ESCAPE);
 1238   
     }
 1239   
 
 1240   
     /** Select an AWT popup menu item. */
 1241  6
     public void selectAWTPopupMenuItem(MenuComponent item) {
 1242   
         // Can't do this through coordinates because MenuComponents don't
 1243   
         // store any of that information
 1244  6
         fireAccessibleAction(getContext(item), item.getAccessibleContext().
 1245   
                              getAccessibleAction(), toString(item));
 1246  6
         if (queueBlocked())
 1247  0
             key(KeyEvent.VK_ESCAPE);
 1248   
     }
 1249   
 
 1250   
     /** Is the given component ready for robot input? */
 1251  2357
     protected boolean isReadyForInput(Component c) {
 1252  2357
         if (eventMode == EM_AWT)
 1253  343
             return c.isShowing();
 1254  2014
         return c.isShowing()
 1255   
             && tracker.isWindowReady(AWT.getWindow(c));
 1256   
     }
 1257   
 
 1258  417
     private boolean isOnJMenuBar(Component item) {
 1259  417
         if (item instanceof javax.swing.JMenuBar)
 1260  64
             return true;
 1261  353
         Component parent = item instanceof JPopupMenu
 1262   
             ? ((JPopupMenu)item).getInvoker() : item.getParent();
 1263  353
         return parent != null && isOnJMenuBar(parent);
 1264   
     }
 1265   
 
 1266   
     /** Find and select the given menu item, by path. */
 1267  2
     public void selectMenuItem(Component sameWindow, String path) {
 1268  2
         try {
 1269  2
             Window window = AWT.getWindow(sameWindow);
 1270  2
             Matcher m = new JMenuItemMatcher(path);
 1271  2
             Component item = BasicFinder.getDefault().find(window, m);
 1272  2
             selectMenuItem(item);
 1273   
         }
 1274   
         catch(ComponentNotFoundException e) {
 1275  0
             throw new ComponentMissingException("Can't find menu item '"
 1276   
                                                 + path + "'");
 1277   
         }
 1278   
         catch(MultipleComponentsFoundException e) {
 1279  0
             throw new ActionFailedException(e.getMessage());
 1280   
         }
 1281   
     }
 1282   
 
 1283   
     /** Find and select the given menu item. */
 1284  81
     public void selectMenuItem(Component item) {
 1285  81
         Log.debug("Selecting menu item " + toString(item));
 1286  81
         Component parent = item.getParent();
 1287  81
         JPopupMenu parentPopup = null;
 1288  81
         if (parent instanceof JPopupMenu) {
 1289  62
             parentPopup = (JPopupMenu)parent;
 1290  62
             parent = ((JPopupMenu)parent).getInvoker();
 1291   
         }
 1292  81
         boolean inMenuBar = parent instanceof javax.swing.JMenuBar;
 1293  81
         boolean isMenu = item instanceof javax.swing.JMenu;
 1294   
 
 1295  81
         if (isOnJMenuBar(item) && useScreenMenuBar()) {
 1296   
             // Use accessibility action instead
 1297  0
             fireAccessibleAction(item, item.getAccessibleContext().
 1298   
                                  getAccessibleAction(), toString(item));
 1299  0
             return;
 1300   
         }
 1301   
 
 1302   
         // If our parent is a menu, activate it first, if it's not already.
 1303  81
         if (parent instanceof javax.swing.JMenuItem) {
 1304  51
             if (parentPopup == null || !parentPopup.isShowing()) {
 1305  49
                 Log.debug("Opening parent menu " + toString(parent));
 1306  49
                 selectMenuItem(parent);
 1307   
             }
 1308   
         }
 1309   
 
 1310   
         // Make sure the appropriate window is in front
 1311  81
         if (inMenuBar) {
 1312  19
             final Window win = AWT.getWindow(parent);
 1313  19
             if (win != null) {
 1314   
                 // Make sure the window is in front, or its menus may be
 1315   
                 // obscured by another window.
 1316  19
                 invokeAndWait(win, new Runnable() {
 1317  19
                     public void run() {
 1318  19
                         win.toFront();
 1319   
                     }
 1320   
                 });
 1321  19
                 mouseMove(win);
 1322   
             }
 1323   
         }
 1324   
 
 1325   
         // Activate the item
 1326  81
         if (isMenu && !inMenuBar) {
 1327   
             // Submenus only require a mouse-over to activate, but do
 1328   
             // a click to be certain
 1329  30
             if (subMenuDelay > autoDelay) {
 1330  0
                 delay(subMenuDelay - autoDelay);
 1331   
             }
 1332   
         }
 1333   
         // Top-level menus and menu items *must* be clicked on
 1334  81
         Log.debug("Activating menu item " + toString(item));
 1335  81
         click(item);
 1336  81
         waitForIdle();
 1337   
 
 1338   
         // If this item is a menu, make sure its popup is showing before we
 1339   
         // return 
 1340  81
         if (isMenu) {
 1341  49
             JPopupMenu popup = ((javax.swing.JMenu)item).getPopupMenu();
 1342  49
             if (!waitForComponent(popup, popupDelay)) {
 1343  0
                 String msg = "Clicking on '"
 1344   
                     + ((javax.swing.JMenu)item).getText() 
 1345   
                     + "' never produced a popup menu";
 1346  0
                 throw new ComponentMissingException(msg);
 1347   
             }
 1348   
             // for OSX 1.4.1; isShowing set before popup is available
 1349  49
             if (subMenuDelay > autoDelay) {
 1350  0
                 delay(subMenuDelay - autoDelay);
 1351   
             }
 1352   
         }
 1353   
     }
 1354   
 
 1355  11
     public void selectPopupMenuItem(Component invoker,
 1356   
                                     ComponentLocation loc,
 1357   
                                     String path) {
 1358  11
         Point where = loc.getPoint(invoker);
 1359   
 
 1360  11
         if (where.x == -1)
 1361  0
             where.x = invoker.getWidth()/2;
 1362  11
         if (where.y == -1)
 1363  0
             where.y = invoker.getHeight()/2;
 1364  11
         Component popup = showPopupMenu(invoker, where.x, where.y);
 1365  11
         try {
 1366  11
             Matcher m = new JMenuItemMatcher(path);
 1367   
             // Don't care about the hierarchy on this one, since there'll only
 1368   
             // ever be one popup active at a time.
 1369  11
             Component item = BasicFinder.getDefault().
 1370   
                 find((Container)popup, m);
 1371  9
             selectMenuItem(item);
 1372  9
             waitForIdle();
 1373   
         }
 1374   
         catch(ComponentNotFoundException e) {
 1375  2
             throw new ComponentMissingException("Can't find menu item '"
 1376   
                                                 + path + "'");
 1377   
         }
 1378   
         catch(MultipleComponentsFoundException e) {
 1379  0
             throw new ActionFailedException(e.getMessage());
 1380   
         }
 1381   
     }
 1382   
 
 1383   
     /** Attempt to display a popup menu at center of the component. */
 1384  3
     public Component showPopupMenu(Component invoker) {
 1385  3
         return showPopupMenu(invoker,
 1386   
                              invoker.getWidth()/2, invoker.getHeight()/2);
 1387   
     }
 1388   
 
 1389   
     /** Attempt to display a popup menu at the given coordinates. */
 1390  14
     public Component showPopupMenu(Component invoker, int x, int y) {
 1391  14
         String where = " at (" + x + "," + y + ")";
 1392  14
         Log.debug("Invoking popup " + where);
 1393  14
         click(invoker, x, y, AWTConstants.POPUP_MASK);
 1394   
 
 1395  14
         Component popup = AWT.findActivePopupMenu();
 1396  14
         if (popup == null) {
 1397  0
             String msg = "No popup responded to "
 1398   
                 + AWTConstants.POPUP_MODIFIER 
 1399   
                 + where + " on " 
 1400   
                 + toString(invoker);
 1401  0
             throw new ComponentMissingException(msg);
 1402   
         }
 1403  14
         int POPUP_DELAY = 10000;
 1404  14
         long start = System.currentTimeMillis();
 1405  14
         while (!isReadyForInput(SwingUtilities.getWindowAncestor(popup))
 1406   
                && System.currentTimeMillis() - start > POPUP_DELAY) {
 1407  0
             sleep();
 1408   
         }
 1409  14
         return popup;
 1410   
     }
 1411   
 
 1412   
     /** Activate the given window. */
 1413  11
     public void activate(final Window win) {
 1414   
         // ACTIVATE means window gets keyboard focus.
 1415  11
         invokeAndWait(win, new Runnable() {
 1416  11
             public void run() { win.toFront(); }
 1417   
         });
 1418   
         // For pointer-focus systems
 1419  11
         mouseMove(win);
 1420   
     }
 1421   
 
 1422  15
     protected Point getCloseLocation(Container c) {
 1423  15
         Dimension size = c.getSize();
 1424  15
         Insets insets = c.getInsets();
 1425  15
         if (Platform.isOSX()) {
 1426  0
             return new Point(insets.left + 15, insets.top / 2);
 1427   
         }
 1428   
         else {
 1429  15
             return new Point(size.width - insets.right - 10, insets.top / 2);
 1430   
         }
 1431   
     }
 1432   
 
 1433   
     /** Invoke the window close operation. */
 1434  13
     public void close(Window w) {
 1435  13
         if (w.isShowing()) {
 1436   
             // Move to a corner and "pretend" to use the window manager
 1437   
             // control 
 1438  13
             try {
 1439  13
                 Point p = getCloseLocation(w);
 1440  13
                 mouseMove(w, p.x, p.y);
 1441   
             }
 1442   
             catch(Exception e) {
 1443   
                 // ignore
 1444   
             }
 1445  13
             WindowEvent ev = new WindowEvent(w, WindowEvent.WINDOW_CLOSING);
 1446   
             // If the window contains an applet, send the event on the
 1447   
             // applet's queue instead to ensure a shutdown from the
 1448   
             // applet's context (assists AppletViewer cleanup).
 1449  13
             Component applet = AWT.findAppletDescendent(w);
 1450  13
             EventQueue eq = tracker.getQueue(applet != null ? applet : w);
 1451  13
             eq.postEvent(ev);
 1452   
         }
 1453   
     }
 1454   
 
 1455   
     /** Return where the mouse usually grabs to move a window.  Center of the
 1456   
      * top of the frame is usually a good choice.
 1457   
      */
 1458  10
     protected Point getMoveLocation(Container c) {
 1459  10
         Dimension size = c.getSize();
 1460  10
         Insets insets = c.getInsets();
 1461  10
         return new Point(size.width/2, insets.top/2);
 1462   
     }
 1463   
 
 1464   
     /** Move the given Frame/Dialog to the requested location. */
 1465  6
     public void move(Container comp, int newx, int newy) {
 1466  6
         Point loc = AWT.getLocationOnScreen(comp);
 1467  6
         moveBy(comp, newx - loc.x, newy - loc.y);
 1468   
     }
 1469   
 
 1470   
     /** Move the given Window by the given amount. */
 1471  7
     public void moveBy(final Container comp, final int dx, final int dy) {
 1472  7
         final Point loc = AWT.getLocationOnScreen(comp);
 1473  7
         boolean userMovable = userMovable(comp);
 1474  7
         if (userMovable) {
 1475  5
             Point p = getMoveLocation(comp);
 1476  5
             mouseMove(comp, p.x, p.y);
 1477  5
             mouseMove(comp, p.x + dx, p.y + dy);
 1478   
         }
 1479  7
         invokeAndWait(comp, new Runnable() {
 1480  7
             public void run() {
 1481  7
                 comp.setLocation(new Point(loc.x + dx, loc.y + dy));
 1482   
             }
 1483   
         });
 1484  7
         if (userMovable) {
 1485  5
             Point p = getMoveLocation(comp);
 1486  5
             mouseMove(comp, p.x, p.y);
 1487   
         }
 1488   
     }
 1489   
 
 1490   
     /** Return where the mouse usually grabs to resize a window.  The lower
 1491   
      * right corner of the window is usually a good choice.
 1492   
      */ 
 1493  4
     protected Point getResizeLocation(Container c) {
 1494  4
         Dimension size = c.getSize();
 1495  4
         Insets insets = c.getInsets();
 1496  4
         return new Point(size.width-insets.right/2,
 1497   
                          size.height-insets.bottom/2);
 1498   
     }
 1499   
 
 1500   
     /** Return whether it is possible for the user to move the given
 1501   
         component.
 1502   
     */
 1503  10
     protected boolean userMovable(Component comp) {
 1504  10
         return comp instanceof Dialog
 1505   
             || comp instanceof Frame
 1506   
             || canMoveWindows();
 1507   
     }
 1508   
 
 1509   
     /** Return whether it is possible for the user to resize the given
 1510   
         component.
 1511   
     */
 1512  10
     protected boolean userResizable(Component comp) {
 1513  10
         if (comp instanceof Dialog)
 1514  1
             return ((Dialog)comp).isResizable();
 1515  9
         if (comp instanceof Frame)
 1516  6
             return ((Frame)comp).isResizable();
 1517   
         // most X11 window managers allow arbitrary resizing
 1518  3
         return canResizeWindows();
 1519   
     }
 1520   
 
 1521   
     /** Resize the given Frame/Dialog to the given size.  */
 1522  5
     public void resize(Container comp, int width, int height) {
 1523  5
         Dimension size = comp.getSize();
 1524  5
         resizeBy(comp, width - size.width, height - size.height);
 1525   
     }
 1526   
 
 1527   
     /** Resize the given Frame/Dialog by the given amounts.  */
 1528  6
     public void resizeBy(final Container comp, final int dx, final int dy) {
 1529   
         // Fake the pointer motion like we're resizing
 1530  6
         boolean userResizable = userResizable(comp);
 1531  6
         if (userResizable) {
 1532  2
             Point p = getResizeLocation(comp);
 1533  2
             mouseMove(comp, p.x, p.y);
 1534  2
             mouseMove(comp, p.x + dx, p.y + dy);
 1535   
         }
 1536  6
         invokeAndWait(comp, new Runnable() {
 1537  6
             public void run() {
 1538  6
                 comp.setSize(comp.getWidth()+dx, comp.getHeight()+dy);
 1539   
             }
 1540   
         });
 1541  6
         if (userResizable) {
 1542  2
             Point p = getResizeLocation(comp);
 1543  2
             mouseMove(comp, p.x, p.y);
 1544   
         }
 1545   
     }
 1546   
 
 1547   
     /** Identify the coordinates of the iconify button where we can, returning
 1548   
      * (0, 0) if we can't.
 1549   
      */
 1550  14
     protected Point getIconifyLocation(Container c) {
 1551  14
         Dimension size = c.getSize();
 1552  14
         Insets insets = c.getInsets();
 1553   
         // We know the exact layout of the window manager frames for w32 and
 1554   
         // OSX.  Currently no way of detecting the WM under X11.  Maybe we
 1555   
         // could send a WM message (WM_ICONIFY)?
 1556  14
         Point loc = new Point();
 1557  14
         loc.y = insets.top / 2;
 1558  14
         if (Platform.isOSX()) {
 1559  0
             loc.x = 35;
 1560   
         }
 1561  14
         else if (Platform.isWindows()) {
 1562  14
             int offset = Platform.isWindowsXP() ? 64 : 45;
 1563  14
             loc.x = size.width - insets.right - offset;
 1564   
         }
 1565  14
         return loc;
 1566   
     }
 1567   
 
 1568   
     private static final int MAXIMIZE_BUTTON_OFFSET =
 1569  96
         Platform.isOSX() ? 25 
 1570  96
         : Platform.isWindows() ? -20 : 0;
 1571   
 
 1572   
     /** Identify the coordinates of the maximize button where possible,
 1573   
         returning null if not.
 1574   
     */
 1575  5
     protected Point getMaximizeLocation(Container c) {
 1576  5
         Point loc = getIconifyLocation(c);
 1577  5
         loc.x += MAXIMIZE_BUTTON_OFFSET;
 1578  5
         return loc;
 1579   
     }
 1580   
 
 1581   
     /** Iconify the given Frame.  Don't support iconification of Dialogs at
 1582   
      * this point (although maybe should).
 1583   
      */
 1584  2
     public void iconify(final Frame frame) {
 1585  2
         Point loc = getIconifyLocation(frame);
 1586  2
         if (loc != null) {
 1587  2
             mouseMove(frame, loc.x, loc.y);
 1588   
         }
 1589  2
         invokeLater(frame, new Runnable() {
 1590  2
             public void run() {
 1591  2
                 frame.setState(Frame.ICONIFIED);
 1592   
             }
 1593   
         });
 1594   
     }
 1595   
 
 1596  2
     public void deiconify(Frame frame) {
 1597  2
         normalize(frame);
 1598   
     }
 1599   
 
 1600  4
     public void normalize(final Frame frame) {
 1601  4
         invokeLater(frame, new Runnable() {
 1602  4
             public void run() {
 1603  4
                 frame.setState(Frame.NORMAL);
 1604  4
                 if (Bugs.hasFrameDeiconifyBug())
 1605  0
                     frame.setVisible(true);
 1606   
             }
 1607   
         });
 1608   
     }
 1609   
 
 1610   
     /** Make the window full size.  On 1.3.1, this is not reversible. */
 1611  2
     public void maximize(final Frame frame) {
 1612  2
         Point loc = getMaximizeLocation(frame);
 1613  2
         if (loc != null) {
 1614  2
             mouseMove(frame, loc.x, loc.y);
 1615   
         }
 1616  2
         invokeLater(frame, new Runnable() {
 1617  2
             public void run() {
 1618   
                 // If the maximize is unavailable, set to full screen size
 1619   
                 // instead. 
 1620  2
                 try {
 1621  2
                     final int MAXIMIZED_BOTH = 6;
 1622  2
                     Boolean b = (Boolean)
 1623   
                         Toolkit.class.getMethod("isFrameStateSupported",
 1624   
                                                 new Class[] { int.class }).
 1625   
                         invoke(toolkit, new Object[] {
 1626   
                             new Integer(MAXIMIZED_BOTH)
 1627   
                         });
 1628  2
                     if (b.booleanValue() && !serviceMode) {
 1629  2
                         Frame.class.getMethod("setExtendedState",
 1630   
                                               new Class[] { int.class, }).
 1631   
                             invoke(frame, new Object[] {
 1632   
                                 new Integer(MAXIMIZED_BOTH)
 1633   
                             });
 1634   
                     }
 1635   
                     else {
 1636  0
                         throw new RuntimeException("Platform won't maximize");
 1637   
                     }
 1638   
                 }
 1639   
                 catch(Exception e) {
 1640  0
                     Log.debug("Maximize not supported: " + e);
 1641  0
                     Rectangle rect =
 1642   
                         frame.getGraphicsConfiguration().getBounds();
 1643  0
                     frame.setLocation(rect.x, rect.y);
 1644  0
                     frame.setSize(rect.width, rect.height);
 1645   
                 }
 1646   
             }
 1647   
         });
 1648   
     }
 1649   
 
 1650   
     /** Send the given event as appropriate to the event-generation mode. */
 1651  0
     public void sendEvent(AWTEvent event) {
 1652   
         // Modifiers are ignored, assuming that an event will be
 1653   
         // sent that causes modifiers to be sent appropriately.  
 1654  0
         if (eventMode == EM_ROBOT) {
 1655  0
             int id = event.getID();
 1656  0
             Log.debug("Sending event id " + id);
 1657  0
             if (id >= MouseEvent.MOUSE_FIRST && id <= MouseEvent.MOUSE_LAST) {
 1658  0
                 MouseEvent me = (MouseEvent)event;
 1659  0
                 Component comp = me.getComponent();
 1660  0
                 if (id == MouseEvent.MOUSE_MOVED) {
 1661  0
                     mouseMove(comp, me.getX(), me.getY());
 1662   
                 }
 1663  0
                 else if (id == MouseEvent.MOUSE_DRAGGED) {
 1664  0
                     mouseMove(comp, me.getX(), me.getY());
 1665   
                 }
 1666  0
                 else if (id == MouseEvent.MOUSE_PRESSED) {
 1667  0
                     mouseMove(comp, me.getX(), me.getY());
 1668  0
                     mousePress(me.getModifiers() & AWTConstants.BUTTON_MASK);
 1669   
                 }
 1670  0
                 else if (id == MouseEvent.MOUSE_ENTERED) {
 1671  0
                     mouseMove(comp, me.getX(), me.getY());
 1672   
                 }
 1673  0
                 else if (id == MouseEvent.MOUSE_EXITED) {
 1674  0
                     mouseMove(comp, me.getX(), me.getY());
 1675   
                 }
 1676  0
                 else if (id == MouseEvent.MOUSE_RELEASED) {
 1677  0
                     mouseMove(comp, me.getX(), me.getY());
 1678  0
                     mouseRelease(me.getModifiers() & AWTConstants.BUTTON_MASK);
 1679   
                 }
 1680   
             }
 1681  0
             else if (id >= KeyEvent.KEY_FIRST && id <= KeyEvent.KEY_LAST) {
 1682  0
                 KeyEvent ke = (KeyEvent)event;
 1683  0
                 if (id == KeyEvent.KEY_PRESSED) {
 1684  0
                     keyPress(ke.getKeyCode());
 1685   
                 }
 1686  0
                 else if (id == KeyEvent.KEY_RELEASED) {
 1687  0
                     keyRelease(ke.getKeyCode());
 1688   
                 }
 1689   
             }
 1690   
             else {
 1691  0
                 Log.warn("Event not supported: " + event);
 1692   
             }
 1693   
         }
 1694   
         else {
 1695   
             // Post the event to the appropriate AWT event queue
 1696  0
             postEvent((Component)event.getSource(), event);
 1697   
         }
 1698   
     }
 1699   
 
 1700   
     /** Return the symbolic name of the given event's ID. */
 1701  3216
     public static String getEventID(AWTEvent event) {
 1702   
         // Optimize here to avoid field name lookup overhead
 1703  3216
         switch(event.getID()) {
 1704  196
         case MouseEvent.MOUSE_MOVED: return "MOUSE_MOVED";
 1705  60
         case MouseEvent.MOUSE_DRAGGED: return "MOUSE_DRAGGED";
 1706  157
         case MouseEvent.MOUSE_PRESSED: return "MOUSE_PRESSED";
 1707  117
         case MouseEvent.MOUSE_CLICKED: return "MOUSE_CLICKED";
 1708  152
         case MouseEvent.MOUSE_RELEASED: return "MOUSE_RELEASED";
 1709  74
         case MouseEvent.MOUSE_ENTERED: return "MOUSE_ENTERED";
 1710  77
         case MouseEvent.MOUSE_EXITED: return "MOUSE_EXITED";
 1711  229
         case KeyEvent.KEY_PRESSED: return "KEY_PRESSED";
 1712  141
         case KeyEvent.KEY_TYPED: return "KEY_TYPED";
 1713  221
         case KeyEvent.KEY_RELEASED: return "KEY_RELEASED";
 1714  30
         case WindowEvent.WINDOW_OPENED: return "WINDOW_OPENED";
 1715  1
         case WindowEvent.WINDOW_CLOSING: return "WINDOW_CLOSING";
 1716  12
         case WindowEvent.WINDOW_CLOSED: return "WINDOW_CLOSED";
 1717  0
         case WindowEvent.WINDOW_ICONIFIED: return "WINDOW_ICONIFIED";
 1718  0
         case WindowEvent.WINDOW_DEICONIFIED: return "WINDOW_DEICONIFIED";
 1719  16
         case WindowEvent.WINDOW_ACTIVATED: return "WINDOW_ACTIVATED";
 1720  16
         case WindowEvent.WINDOW_DEACTIVATED: return "WINDOW_DEACTIVATED";
 1721  585
         case ComponentEvent.COMPONENT_MOVED: return "COMPONENT_MOVED";
 1722  832
         case ComponentEvent.COMPONENT_RESIZED: return "COMPONENT_RESIZED";
 1723  70
         case ComponentEvent.COMPONENT_SHOWN: return "COMPONENT_SHOWN";
 1724  82
         case ComponentEvent.COMPONENT_HIDDEN: return "COMPONENT_HIDDEN";
 1725  50
         case FocusEvent.FOCUS_GAINED: return "FOCUS_GAINED";
 1726  53
         case FocusEvent.FOCUS_LOST: return "FOCUS_LOST";
 1727  0
         case HierarchyEvent.HIERARCHY_CHANGED: return "HIERARCHY_CHANGED";
 1728  0
         case HierarchyEvent.ANCESTOR_MOVED: return "ANCESTOR_MOVED";
 1729  0
         case HierarchyEvent.ANCESTOR_RESIZED: return "ANCESTOR_RESIZED";
 1730  0
         case PaintEvent.PAINT: return "PAINT";
 1731  0
         case PaintEvent.UPDATE: return "UPDATE";
 1732  7
         case ActionEvent.ACTION_PERFORMED: return "ACTION_PERFORMED";
 1733  0
         case InputMethodEvent.CARET_POSITION_CHANGED: return "CARET_POSITION_CHANGED";
 1734  0
         case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED: return "INPUT_METHOD_TEXT_CHANGED";
 1735  38
         default:
 1736  38
             return Reflector.getFieldName(event.getClass(), event.getID(), "");
 1737   
         }
 1738   
     }
 1739   
 
 1740  13652
     public static Class getCanonicalClass(Class refClass) {
 1741   
         // Don't use classnames from anonymous inner classes...
 1742   
         // Don't use classnames from platform LAF classes...
 1743  13652
         String className = refClass.getName();
 1744  13652
         while (className.indexOf("$") != -1
 1745   
                || className.startsWith("javax.swing.plaf")
 1746   
                || className.startsWith("com.apple.mrj")) {
 1747  3612
             refClass = refClass.getSuperclass();
 1748  3612
             className = refClass.getName();
 1749   
         }
 1750  13652
         return refClass;
 1751   
     }
 1752   
 
 1753   
     /** Provides a more concise representation of the component than the
 1754   
      * default Component.toString().
 1755   
      */
 1756  7442
     public static String toString(Component comp) {
 1757  7442
         if (comp == null)
 1758  1
             return "(null)";
 1759   
 
 1760  7441
         if (AWT.isTransientPopup(comp)) {
 1761  134
             boolean tooltip = AWT.isToolTip(comp);
 1762  134
             if (AWT.isHeavyweightPopup(comp)) {
 1763  126
                 return tooltip 
 1764   
                     ? Strings.get("component.heavyweight_tooltip")
 1765   
                     : Strings.get("component.heavyweight_popup");
 1766   
             }
 1767  8
             else if (AWT.isLightweightPopup(comp)) {
 1768  8
                 return tooltip 
 1769   
                     ? Strings.get("component.lightweight_tooltip")
 1770   
                     : Strings.get("component.lightweight_popup");
 1771   
             }
 1772   
         }
 1773  7307
         else if (AWT.isSharedInvisibleFrame(comp)) {
 1774  23
             return Strings.get("component.default_frame");
 1775   
         }
 1776  7284
         String name = ComponentReference.getDescriptiveName(comp);
 1777  7284
         String classDesc = descriptiveClassName(comp.getClass());
 1778  7284
         if (name == null) {
 1779  3424
             if (AWT.isContentPane(comp)) {
 1780  257
                 name = Strings.get("component.content_pane");
 1781   
             }
 1782  3167
             else if (AWT.isGlassPane(comp)) {
 1783  74
                 name = Strings.get("component.glass_pane");
 1784   
             }
 1785  3093
             else if (comp instanceof JLayeredPane) {
 1786  249
                 name = Strings.get("component.layered_pane");
 1787   
             }
 1788  2844
             else if (comp instanceof JRootPane) {
 1789  264
                 name = Strings.get("component.root_pane");
 1790   
             }
 1791   
             else {
 1792  2580
                 name = classDesc + " instance";
 1793   
             }
 1794   
         }
 1795   
         else {
 1796  3860
             name = "'" + name + "' (" + classDesc + ")";
 1797   
         }
 1798  7284
         return name;
 1799   
     }
 1800   
 
 1801   
     /** Provide a string representation of the given component (Component or
 1802   
      * MenuComponent.
 1803   
      */
 1804  3264
     public static String toString(Object obj) {
 1805  3264
         if (obj instanceof Component)
 1806  3247
             return toString((Component)obj);
 1807  17
         else if (obj instanceof MenuBar)
 1808  0
             return "MenuBar";
 1809  17
         else if (obj instanceof MenuItem)
 1810  17
             return ((MenuItem)obj).getLabel();
 1811  0
         return obj.toString();
 1812   
     }
 1813   
 
 1814  11541
     protected static String descriptiveClassName(Class cls) {
 1815  11541
         StringBuffer desc = new StringBuffer(simpleClassName(cls));
 1816  11541
         Class coreClass = getCanonicalClass(cls);
 1817  11541
         String coreClassName = coreClass.getName();
 1818  11541
         while (!coreClassName.startsWith("java.awt.")
 1819   
                && !coreClassName.startsWith("javax.swing.")
 1820   
                     && !coreClassName.startsWith("java.applet.")) {
 1821  172
             coreClass = coreClass.getSuperclass();
 1822  172
             coreClassName = coreClass.getName();
 1823   
         }
 1824  11541
         if (!coreClass.equals(cls)) {
 1825  2801
             desc.append("/");
 1826  2801
             desc.append(simpleClassName(coreClass));
 1827   
         }
 1828  11541
         return desc.toString();
 1829   
     }
 1830   
 
 1831   
     /** Provides the hierarchic path of the given component by component
 1832   
         class, e.g. "JFrame:JRootPane:JPanel:JButton".
 1833   
     */
 1834  4257
     public static String toHierarchyPath(Component c) {
 1835  4257
         StringBuffer buf = new StringBuffer();
 1836  4257
         Container parent = c.getParent();
 1837  4257
         if (parent != null) {
 1838  3006
             buf.append(toHierarchyPath(parent));
 1839  3006
             buf.append(":");
 1840   
         }
 1841  4257
         buf.append(descriptiveClassName(c.getClass()));
 1842  4257
         String name = ComponentReference.getDescriptiveName(c);
 1843  4257
         if (name != null) {
 1844  1251
             buf.append("(");
 1845  1251
             buf.append(name);
 1846  1251
             buf.append(")");
 1847   
         }
 1848  3006
         else if (parent != null
 1849   
                  && parent.getComponentCount() > 1
 1850   
                  && c instanceof JPanel) {
 1851  0
             buf.append("[");
 1852  0
             buf.append(String.valueOf(ComponentReference.getIndex(parent, c)));
 1853  0
             buf.append("]");
 1854   
         }
 1855  4257
         return buf.toString();
 1856   
     }
 1857   
 
 1858   
     /** Provide a more concise representation of the event than the default
 1859   
      * AWTEvent.toString().
 1860   
      */
 1861  3120
     public static String toString(AWTEvent event) {
 1862  3120
         String name = toString(event.getSource());
 1863  3120
         String desc = getEventID(event);
 1864  3120
         if (event.getID() == KeyEvent.KEY_PRESSED
 1865   
             || event.getID() == KeyEvent.KEY_RELEASED) {
 1866  355
             KeyEvent ke = (KeyEvent)event;
 1867  355
             desc += " (" + AWT.getKeyCode(ke.getKeyCode());
 1868  355
             if (ke.getModifiers() != 0) {
 1869  145
                 desc += "/" + AWT.getKeyModifiers(ke.getModifiers());
 1870   
             }
 1871  355
             desc += ")";
 1872   
         }
 1873  2765
         else if (event.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
 1874  0
             desc += " (" + ((InputMethodEvent)event).getCommittedCharacterCount() + ")";
 1875   
         }
 1876  2765
         else if (event.getID() == KeyEvent.KEY_TYPED) {
 1877  141
             char ch = ((KeyEvent)event).getKeyChar();
 1878  141
             int mods = ((KeyEvent)event).getModifiers();
 1879  141
             desc += " ('" + ch
 1880  141
                 + (mods != 0 ? "/" + AWT.getKeyModifiers(mods) : "")
 1881   
                 + "')";
 1882   
         }
 1883  2624
         else if (event.getID() >= MouseEvent.MOUSE_FIRST
 1884   
                  && event.getID() <= MouseEvent.MOUSE_LAST) {
 1885  832
             MouseEvent me = (MouseEvent)event;
 1886  832
             if (me.getModifiers() != 0) {
 1887  514
                 desc += " <" + AWT.getMouseModifiers(me.getModifiers());
 1888  514
                 if (me.getClickCount() > 1) {
 1889  28
                     desc += "," + me.getClickCount();
 1890   
                 }
 1891  514
                 desc += ">";
 1892   
             }
 1893  832
             desc += " (" + me.getX() + "," + me.getY() + ")";
 1894   
         }
 1895  1792
         else if (event.getID() == HierarchyEvent.HIERARCHY_CHANGED) {
 1896  0
             HierarchyEvent he = (HierarchyEvent)event;
 1897  0
             long flags = he.getChangeFlags();
 1898  0
             String type = "";
 1899  0
             String bar = "";
 1900  0
             if ((flags & HierarchyEvent.SHOWING_CHANGED) != 0) {
 1901  0
                 type += (he.getComponent().isShowing() ? "" : "!") 
 1902  0
                     + "SHOWING"; bar = "|";
 1903   
             }
 1904  0
             if ((flags & HierarchyEvent.PARENT_CHANGED) != 0) {
 1905  0
                 type += bar + "PARENT:" 
 1906  0
                     + toString(he.getComponent().getParent()); bar = "|";
 1907   
             }
 1908  0
             if ((flags & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
 1909  0
                 type += bar + "DISPLAYABILITY"; 
 1910   
             }
 1911  0
             desc += " (" + type + ")";
 1912   
         }
 1913  3120
         return desc + " on " + name;
 1914   
     }
 1915   
 
 1916   
     /** Return the numeric event ID corresponding to the given string. */
 1917  3
     public static int getEventID(Class cls, String id) {
 1918  3
         return Reflector.getFieldValue(cls, id);
 1919   
     }
 1920   
 
 1921   
     /** Strip the package from the class name. */
 1922  14852
     public static String simpleClassName(Class cls) {
 1923  14852
         String name = cls.getName();
 1924  14852
         int dot = name.lastIndexOf(".");
 1925  14852
         return name.substring(dot+1, name.length());
 1926   
     }
 1927   
 
 1928   
     private AWTEvent lastEventPosted = null;
 1929   
     private MouseEvent lastMousePress = null;
 1930   
     private boolean countingClicks = false;
 1931   
 
 1932   
     /** Post the given event to the corresponding event queue for the given
 1933   
         component. */
 1934  693
     protected void postEvent(Component comp, AWTEvent ev) { 
 1935  693
         if (Log.isClassDebugEnabled(Robot.class))
 1936  0
             Log.debug("POST: " + toString(ev));
 1937  693
         if (eventMode == EM_AWT
 1938   
             && AWT.isAWTPopupMenuBlocking()) {
 1939  0
             throw new Error("Event queue is blocked by an active AWT PopupMenu");
 1940   
         }
 1941   
         // Force an update of the input state, so that we're in synch
 1942   
         // internally.  Otherwise we might post more events before this
 1943   
         // one gets processed and end up using stale values for those events.
 1944  693
         state.update(ev);
 1945  693
         EventQueue q = getEventQueue(comp);
 1946  693
         q.postEvent(ev);
 1947  693
         delay(autoDelay);
 1948  693
         AWTEvent prev = lastEventPosted;
 1949  693
         lastEventPosted = ev;
 1950  693
         if (ev instanceof MouseEvent) {
 1951  209
             if (ev.getID() == MouseEvent.MOUSE_PRESSED) {
 1952  41
                 lastMousePress = (MouseEvent)ev;
 1953  41
                 countingClicks = true;
 1954   
             }
 1955  168
             else if (ev.getID() != MouseEvent.MOUSE_RELEASED
 1956   
                      && ev.getID() != MouseEvent.MOUSE_CLICKED) {
 1957  86
                 countingClicks = false;
 1958   
             }
 1959   
         }
 1960   
 
 1961   
         // Generate a click if there are no events between press/release
 1962   
         // Unfortunately, I can only guess how the VM generates them
 1963  693
         if (eventMode == EM_AWT
 1964   
             && ev.getID() == MouseEvent.MOUSE_RELEASED
 1965   
             && prev.getID() == MouseEvent.MOUSE_PRESSED) {
 1966  41
             MouseEvent me = (MouseEvent)ev;
 1967  41
             AWTEvent click = new MouseEvent(comp,
 1968   
                                             MouseEvent.MOUSE_CLICKED,
 1969   
                                             System.currentTimeMillis(),
 1970   
                                             me.getModifiers(),
 1971   
                                             me.getX(), me.getY(), 
 1972   
                                             me.getClickCount(),
 1973   
                                             false);
 1974  41
             postEvent(comp, click);
 1975   
         }
 1976   
     }
 1977   
 
 1978   
     /** Wait for the given Condition to return true.  The default timeout may
 1979   
      * be changed by setting abbot.robot.default_delay.
 1980   
      * @throws WaitTimedOutError if the default timeout (30s) is exceeded. 
 1981   
      */
 1982  0
     public void wait(Condition condition) {
 1983  0
         wait(condition, defaultDelay);
 1984   
     }
 1985   
 
 1986   
     /** Wait for the given Condition to return true, waiting for timeout ms.
 1987   
      * @throws WaitTimedOutError if the timeout is exceeded. 
 1988   
      */
 1989  39
     public void wait(Condition condition, long timeout) {
 1990  39
         wait(condition, timeout, SLEEP_INTERVAL);
 1991   
     }
 1992   
 
 1993   
     /** Wait for the given Condition to return true, waiting for timeout ms,
 1994   
      * polling at the given interval.
 1995   
      * @throws WaitTimedOutError if the timeout is exceeded. 
 1996   
      */
 1997  39
     public void wait(Condition condition, long timeout, int interval) {
 1998  39
         long start = System.currentTimeMillis();
 1999  39
         while (!condition.test()) {
 2000  0
             if (System.currentTimeMillis() - start > timeout) {
 2001  0
                 String msg = "Timed out waiting for " + condition;
 2002  0
                 throw new WaitTimedOutError(msg);
 2003   
             }
 2004  0
             delay(interval);
 2005   
         }
 2006   
     }
 2007   
 
 2008  489
     public void reset() {
 2009  489
         if (eventMode == EM_ROBOT) {
 2010  457
             Dimension d = toolkit.getScreenSize();
 2011  457
             mouseMove(d.width/2, d.height/2);
 2012  457
             mouseMove(d.width/2-1, d.height/2-1);
 2013   
         }
 2014   
         else {
 2015   
             // clear any held state
 2016  32
             state.clear();
 2017   
         }
 2018   
     }
 2019   
 
 2020   
     /** Return the Component which currently owns the focus. */
 2021  454
     public Component findFocusOwner() {
 2022  454
         return AWT.getFocusOwner();
 2023   
     }
 2024   
 
 2025   
     /** Returns whether it is possible to resize windows that are not an
 2026   
         instance of Frame or Dialog.  Most X11 window managers will allow
 2027   
         this, but stock Macintosh and Windows do not.
 2028   
     */
 2029  4
     public static boolean canResizeWindows() {
 2030  4
         return !Platform.isWindows() && !Platform.isMacintosh();
 2031   
     }
 2032   
 
 2033   
     /** Returns whether it is possible to move windows that are not an
 2034   
         instance of Frame or Dialog.  Most X11 window managers will allow
 2035   
         this, but stock Macintosh and Windows do not.
 2036   
     */
 2037  2
     public static boolean canMoveWindows() {
 2038  2
         return !Platform.isWindows() && !Platform.isMacintosh();
 2039   
     }
 2040   
 
 2041   
     /** Returns the appropriate auto delay for robot-generated events. 
 2042   
      * As platforms are tested at 0 delay, adjust this value.<p>
 2043   
      */
 2044  96
     public static int getPreferredRobotAutoDelay() {
 2045  96
         if (Platform.isWindows() || Platform.isOSX() || Platform.isX11())
 2046  96
             return 0;
 2047  0
         return 50;
 2048   
     }
 2049   
 }
 2050