001    /*
002     * Created on Sep 29, 2006
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2006-2010 the original author or authors.
014     */
015    package org.fest.swing.core;
016    
017    import static java.awt.event.InputEvent.BUTTON1_MASK;
018    import static java.awt.event.InputEvent.BUTTON2_MASK;
019    import static java.awt.event.InputEvent.BUTTON3_MASK;
020    import static java.awt.event.KeyEvent.CHAR_UNDEFINED;
021    import static java.awt.event.KeyEvent.KEY_TYPED;
022    import static java.awt.event.KeyEvent.VK_UNDEFINED;
023    import static java.awt.event.WindowEvent.WINDOW_CLOSING;
024    import static java.lang.System.currentTimeMillis;
025    import static javax.swing.SwingUtilities.getWindowAncestor;
026    import static javax.swing.SwingUtilities.isEventDispatchThread;
027    import static org.fest.swing.awt.AWT.centerOf;
028    import static org.fest.swing.awt.AWT.visibleCenterOf;
029    import static org.fest.swing.core.ActivateWindowTask.activateWindow;
030    import static org.fest.swing.core.ComponentIsFocusableQuery.isFocusable;
031    import static org.fest.swing.core.ComponentRequestFocusTask.giveFocusTo;
032    import static org.fest.swing.core.FocusOwnerFinder.focusOwner;
033    import static org.fest.swing.core.FocusOwnerFinder.inEdtFocusOwner;
034    import static org.fest.swing.core.InputModifiers.unify;
035    import static org.fest.swing.core.MouseButton.LEFT_BUTTON;
036    import static org.fest.swing.core.MouseButton.RIGHT_BUTTON;
037    import static org.fest.swing.core.Scrolling.scrollToVisible;
038    import static org.fest.swing.core.WindowAncestorFinder.windowAncestorOf;
039    import static org.fest.swing.edt.GuiActionRunner.execute;
040    import static org.fest.swing.exception.ActionFailedException.actionFailure;
041    import static org.fest.swing.format.Formatting.format;
042    import static org.fest.swing.format.Formatting.inEdtFormat;
043    import static org.fest.swing.hierarchy.NewHierarchy.ignoreExistingComponents;
044    import static org.fest.swing.keystroke.KeyStrokeMap.keyStrokeFor;
045    import static org.fest.swing.query.ComponentShowingQuery.isShowing;
046    import static org.fest.swing.timing.Pause.pause;
047    import static org.fest.swing.util.Modifiers.keysFor;
048    import static org.fest.swing.util.Modifiers.updateModifierWithKeyCode;
049    import static org.fest.swing.util.TimeoutWatch.startWatchWithTimeoutOf;
050    import static org.fest.util.Strings.concat;
051    import static org.fest.util.Strings.isEmpty;
052    
053    import java.applet.Applet;
054    import java.awt.Component;
055    import java.awt.Container;
056    import java.awt.Dimension;
057    import java.awt.EventQueue;
058    import java.awt.Point;
059    import java.awt.Toolkit;
060    import java.awt.Window;
061    import java.awt.event.InvocationEvent;
062    import java.awt.event.KeyEvent;
063    import java.awt.event.WindowEvent;
064    import java.util.ArrayList;
065    import java.util.Collection;
066    import java.util.List;
067    
068    import javax.swing.JComponent;
069    import javax.swing.JMenu;
070    import javax.swing.JPopupMenu;
071    import javax.swing.KeyStroke;
072    
073    import net.jcip.annotations.GuardedBy;
074    
075    import org.fest.swing.annotation.RunsInCurrentThread;
076    import org.fest.swing.annotation.RunsInEDT;
077    import org.fest.swing.edt.GuiQuery;
078    import org.fest.swing.edt.GuiTask;
079    import org.fest.swing.exception.ActionFailedException;
080    import org.fest.swing.exception.ComponentLookupException;
081    import org.fest.swing.exception.WaitTimedOutError;
082    import org.fest.swing.hierarchy.ComponentHierarchy;
083    import org.fest.swing.hierarchy.ExistingHierarchy;
084    import org.fest.swing.input.InputState;
085    import org.fest.swing.lock.ScreenLock;
086    import org.fest.swing.monitor.WindowMonitor;
087    import org.fest.swing.util.Pair;
088    import org.fest.swing.util.TimeoutWatch;
089    import org.fest.util.VisibleForTesting;
090    
091    /**
092     * Understands simulation of user events on a GUI <code>{@link Component}</code>.
093     *
094     * @author Alex Ruiz
095     * @author Yvonne Wang
096     */
097    public class BasicRobot implements Robot {
098    
099      private static final int POPUP_DELAY = 10000;
100      private static final int POPUP_TIMEOUT = 5000;
101      private static final int WINDOW_DELAY = 20000;
102    
103      private static final ComponentMatcher POPUP_MATCHER = new TypeMatcher(JPopupMenu.class, true);
104    
105      @GuardedBy("this") private volatile boolean active;
106    
107      private static final Runnable EMPTY_RUNNABLE = new Runnable() {
108        public void run() {}
109      };
110    
111      private static final int BUTTON_MASK = BUTTON1_MASK | BUTTON2_MASK | BUTTON3_MASK;
112    
113      private static Toolkit toolkit = Toolkit.getDefaultToolkit();
114      private static WindowMonitor windowMonitor = WindowMonitor.instance();
115      private static InputState inputState = new InputState(toolkit);
116    
117      /** Provides access to all the components in the hierarchy. */
118      private final ComponentHierarchy hierarchy;
119    
120      private final Object screenLockOwner;
121    
122      /** Looks up <code>{@link java.awt.Component}</code>s. */
123      private final ComponentFinder finder;
124    
125      private final Settings settings;
126    
127      private final AWTEventPoster eventPoster;
128      private final InputEventGenerator eventGenerator;
129      private final UnexpectedJOptionPaneFinder unexpectedJOptionPaneFinder;
130    
131      /**
132       * Creates a new <code>{@link Robot}</code> with a new AWT hierarchy. The created <code>Robot</code> will not be able
133       * to access any components that were created before it.
134       * @return the created <code>Robot</code>.
135       */
136      public static Robot robotWithNewAwtHierarchy() {
137        Object screenLockOwner = acquireScreenLock();
138        return new BasicRobot(screenLockOwner, ignoreExistingComponents());
139      }
140    
141      /**
142       * Creates a new <code>{@link Robot}</code> that has access to all the GUI components in the AWT hierarchy.
143       * @return the created <code>Robot</code>.
144       */
145      public static Robot robotWithCurrentAwtHierarchy() {
146        Object screenLockOwner = acquireScreenLock();
147        return new BasicRobot(screenLockOwner, new ExistingHierarchy());
148      }
149    
150      private static Object acquireScreenLock() {
151        Object screenLockOwner = new Object();
152        ScreenLock.instance().acquire(screenLockOwner);
153        return screenLockOwner;
154      }
155    
156      @VisibleForTesting
157      BasicRobot(Object screenLockOwner, ComponentHierarchy hierarchy) {
158        this.screenLockOwner = screenLockOwner;
159        this.hierarchy = hierarchy;
160        settings = new Settings();
161        eventGenerator = new RobotEventGenerator(settings);
162        eventPoster = new AWTEventPoster(toolkit, inputState, windowMonitor, settings);
163        finder = new BasicComponentFinder(this.hierarchy, settings);
164        unexpectedJOptionPaneFinder = new UnexpectedJOptionPaneFinder(finder);
165        active = true;
166      }
167    
168      /** {@inheritDoc} */
169      public ComponentPrinter printer() {
170        return finder().printer();
171      }
172    
173      /** {@inheritDoc} */
174      public ComponentFinder finder() {
175        return finder;
176      }
177    
178      /** {@inheritDoc} */
179      @RunsInEDT
180      public void showWindow(Window w) {
181        showWindow(w, null, true);
182      }
183    
184      /** {@inheritDoc} */
185      @RunsInEDT
186      public void showWindow(Window w, Dimension size) {
187        showWindow(w, size, true);
188      }
189    
190      /** {@inheritDoc} */
191      @RunsInEDT
192      public void showWindow(final Window w, final Dimension size, final boolean pack) {
193        EventQueue.invokeLater(new Runnable() {
194          public void run() {
195            if (pack) packAndEnsureSafePosition(w);
196            if (size != null) w.setSize(size);
197            w.setVisible(true);
198          }
199        });
200        waitForWindow(w);
201      }
202    
203      @RunsInCurrentThread
204      private void packAndEnsureSafePosition(Window w) {
205        w.pack();
206        w.setLocation(100, 100);
207      }
208    
209      @RunsInEDT
210      private void waitForWindow(Window w) {
211        long start = currentTimeMillis();
212        while (!windowMonitor.isWindowReady(w) || !isShowing(w)) {
213          long elapsed = currentTimeMillis() - start;
214          if (elapsed > WINDOW_DELAY)
215            throw new WaitTimedOutError(concat("Timed out waiting for Window to open (", String.valueOf(elapsed), "ms)"));
216          pause();
217        }
218      }
219    
220      /** {@inheritDoc} */
221      @RunsInEDT
222      public void close(Window w) {
223        WindowEvent event = new WindowEvent(w, WINDOW_CLOSING);
224        // If the window contains an applet, send the event on the applet's queue instead to ensure a shutdown from the
225        // applet's context (assists AppletViewer cleanup).
226        Component applet = findAppletDescendent(w);
227        EventQueue eventQueue = windowMonitor.eventQueueFor(applet != null ? applet : w);
228        eventQueue.postEvent(event);
229        waitForIdle();
230      }
231    
232      /**
233       * Returns the <code>{@link Applet}</code> descendant of the given <code>{@link Container}</code>, if any.
234       * @param c the given <code>Container</code>.
235       * @return the <code>Applet</code> descendant of the given <code>Container</code>, or <code>null</code> if none
236       * is found.
237       */
238      @RunsInEDT
239      private Applet findAppletDescendent(Container c) {
240        List<Component> found = new ArrayList<Component>(finder.findAll(c, new TypeMatcher(Applet.class)));
241        if (found.size() == 1) return (Applet)found.get(0);
242        return null;
243      }
244    
245      /** {@inheritDoc} */
246      @RunsInEDT
247      public void focusAndWaitForFocusGain(Component c) {
248        focus(c, true);
249      }
250    
251      /** {@inheritDoc} */
252      @RunsInEDT
253      public void focus(Component c) {
254        focus(c, false);
255      }
256    
257      @RunsInEDT
258      private void focus(Component target, boolean wait) {
259        Component currentOwner = inEdtFocusOwner();
260        if (currentOwner == target) return;
261        FocusMonitor focusMonitor = FocusMonitor.attachTo(target);
262        // for pointer focus
263        moveMouse(target);
264        // Make sure the correct window is in front
265        activateWindowOfFocusTarget(target, currentOwner);
266        giveFocusTo(target);
267        try {
268          if (wait) {
269            TimeoutWatch watch = startWatchWithTimeoutOf(settings().timeoutToBeVisible());
270            while (!focusMonitor.hasFocus()) {
271              if (watch.isTimeOut()) throw actionFailure(concat("Focus change to ", format(target), " failed"));
272              pause();
273            }
274          }
275        } finally {
276          target.removeFocusListener(focusMonitor);
277        }
278      }
279    
280      @RunsInEDT
281      private void activateWindowOfFocusTarget(Component target, Component currentOwner) {
282        Pair<Window, Window> windowAncestors = windowAncestorsOf(currentOwner, target);
283        Window currentOwnerAncestor = windowAncestors.i;
284        Window targetAncestor = windowAncestors.ii;
285        if (currentOwnerAncestor == targetAncestor) return;
286        activate(targetAncestor);
287        waitForIdle();
288      }
289    
290      @RunsInEDT
291      private static Pair<Window, Window> windowAncestorsOf(final Component one, final Component two) {
292        return execute(new GuiQuery<Pair<Window, Window>>() {
293          protected Pair<Window, Window> executeInEDT() throws Throwable {
294            return new Pair<Window, Window>(windowAncestor(one), windowAncestor(two));
295          }
296    
297          private Window windowAncestor(Component c) {
298            return (c != null) ? windowAncestorOf(c) : null;
299          }
300        });
301      }
302    
303      /**
304       * Activates the given <code>{@link Window}</code>. "Activate" means that the given window gets the keyboard focus.
305       * @param w the window to activate.
306       */
307      @RunsInEDT
308      private void activate(Window w) {
309        activateWindow(w);
310        moveMouse(w); // For pointer-focus systems
311      }
312    
313      /** {@inheritDoc} */
314      @RunsInEDT
315      public synchronized void cleanUp() {
316        cleanUp(true);
317      }
318    
319      /** {@inheritDoc} */
320      @RunsInEDT
321      public synchronized void cleanUpWithoutDisposingWindows() {
322        cleanUp(false);
323      }
324    
325      @RunsInEDT
326      private void cleanUp(boolean disposeWindows) {
327        try {
328          if (disposeWindows) disposeWindows(hierarchy);
329          releaseMouseButtons();
330        } finally {
331          active = false;
332          releaseScreenLock();
333        }
334      }
335    
336      private void releaseScreenLock() {
337        ScreenLock screenLock = ScreenLock.instance();
338        if (screenLock.acquiredBy(screenLockOwner)) screenLock.release(screenLockOwner);
339      }
340    
341      @RunsInEDT
342      private static void disposeWindows(final ComponentHierarchy hierarchy) {
343        execute(new GuiTask() {
344          protected void executeInEDT() {
345            for (Container c : hierarchy.roots()) if (c instanceof Window) dispose(hierarchy, (Window)c);
346          }
347        });
348      }
349    
350      @RunsInCurrentThread
351      private static void dispose(final ComponentHierarchy hierarchy, Window w) {
352        hierarchy.dispose(w);
353        w.setVisible(false);
354        w.dispose();
355      }
356    
357      /** {@inheritDoc} */
358      @RunsInEDT
359      public void click(Component c) {
360        click(c, LEFT_BUTTON);
361      }
362    
363      /** {@inheritDoc} */
364      @RunsInEDT
365      public void rightClick(Component c) {
366        click(c, RIGHT_BUTTON);
367      }
368    
369      /** {@inheritDoc} */
370      @RunsInEDT
371      public void click(Component c, MouseButton button) {
372        click(c, button, 1);
373      }
374    
375      /** {@inheritDoc} */
376      @RunsInEDT
377      public void doubleClick(Component c) {
378        click(c, LEFT_BUTTON, 2);
379      }
380    
381      /** {@inheritDoc} */
382      @RunsInEDT
383      public void click(Component c, MouseButton button, int times) {
384        Point where = visibleCenterOf(c);
385        if (c instanceof JComponent)
386          where = scrollIfNecessary((JComponent) c, where);
387        click(c, where, button, times);
388      }
389    
390      private Point scrollIfNecessary(JComponent c, Point p) {
391        scrollToVisible(this, c);
392        return visibleCenterOf(c);
393      }
394    
395      /** {@inheritDoc} */
396      @RunsInEDT
397      public void click(Component c, Point where) {
398        click(c, where, LEFT_BUTTON, 1);
399      }
400    
401      /** {@inheritDoc} */
402      @RunsInEDT
403      public void click(Point where, MouseButton button, int times) {
404        click(null, where, button, times);
405      }
406    
407      /** {@inheritDoc} */
408      @RunsInEDT
409      public void click(Component c, Point where, MouseButton button, int times) {
410        int mask = button.mask;
411        int modifierMask = mask & ~BUTTON_MASK;
412        mask &= BUTTON_MASK;
413        pressModifiers(modifierMask);
414        // From Abbot: Adjust the auto-delay to ensure we actually get a multiple click
415        // In general clicks have to be less than 200ms apart, although the actual setting is not readable by Java.
416        int delayBetweenEvents = settings.delayBetweenEvents();
417        if (shouldSetDelayBetweenEventsToZeroWhenClicking(times)) settings.delayBetweenEvents(0);
418        eventGenerator.pressMouse(c, where, mask);
419        for (int i = times; i > 1; i--) {
420          eventGenerator.releaseMouse(mask);
421          eventGenerator.pressMouse(c, where, mask);
422        }
423        settings.delayBetweenEvents(delayBetweenEvents);
424        eventGenerator.releaseMouse(mask);
425        releaseModifiers(modifierMask);
426        waitForIdle();
427      }
428    
429      private boolean shouldSetDelayBetweenEventsToZeroWhenClicking(int times) {
430        return times > 1 /*FEST-137: && settings.delayBetweenEvents() * 2 > 200*/;
431      }
432    
433      /** {@inheritDoc} */
434      public void pressModifiers(int modifierMask) {
435        for (int modifierKey : keysFor(modifierMask))
436          pressKey(modifierKey);
437      }
438    
439      /** {@inheritDoc} */
440      public void releaseModifiers(int modifierMask) {
441        // For consistency, release in the reverse order of press.
442        int[] modifierKeys = keysFor(modifierMask);
443        for (int i = modifierKeys.length - 1; i >= 0; i--)
444          releaseKey(modifierKeys[i]);
445      }
446    
447      /** {@inheritDoc} */
448      @RunsInEDT
449      public void moveMouse(Component c) {
450        moveMouse(c, visibleCenterOf(c));
451      }
452    
453      /** {@inheritDoc} */
454      @RunsInEDT
455      public void moveMouse(Component c, Point p) {
456        moveMouse(c, p.x, p.y);
457      }
458    
459      /** {@inheritDoc} */
460      @RunsInEDT
461      public void moveMouse(Component c, int x, int y) {
462        if (!waitForComponentToBeReady(c, settings.timeoutToBeVisible()))
463          throw actionFailure(concat("Could not obtain position of component ", format(c)));
464        eventGenerator.moveMouse(c, x, y);
465        waitForIdle();
466      }
467    
468      /** {@inheritDoc} */
469      public void moveMouse(Point p) {
470        moveMouse(p.x, p.y);
471      }
472    
473      /** {@inheritDoc} */
474      public void moveMouse(int x, int y) {
475        eventGenerator.moveMouse(x, y);
476      }
477    
478      /** {@inheritDoc} */
479      public void pressMouse(MouseButton button) {
480        eventGenerator.pressMouse(button.mask);
481      }
482    
483      /** {@inheritDoc} */
484      public void pressMouse(Component c, Point where) {
485        pressMouse(c, where, LEFT_BUTTON);
486      }
487    
488      /** {@inheritDoc} */
489      public void pressMouse(Component c, Point where, MouseButton button) {
490        jitter(c, where);
491        moveMouse(c, where.x, where.y);
492        eventGenerator.pressMouse(c, where, button.mask);
493      }
494    
495      /** {@inheritDoc} */
496      public void pressMouse(Point where, MouseButton button) {
497        eventGenerator.pressMouse(where, button.mask);
498      }
499    
500      /** {@inheritDoc} */
501      @RunsInEDT
502      public void releaseMouse(MouseButton button) {
503        mouseRelease(button.mask);
504      }
505    
506      /** {@inheritDoc} */
507      @RunsInEDT
508      public void releaseMouseButtons() {
509        int buttons = inputState.buttons();
510        if (buttons == 0) return;
511        mouseRelease(buttons);
512      }
513    
514      /** {@inheritDoc} */
515      public void rotateMouseWheel(Component c, int amount) {
516        moveMouse(c);
517        rotateMouseWheel(amount);
518      }
519    
520      /** {@inheritDoc} */
521      public void rotateMouseWheel(int amount) {
522        eventGenerator.rotateMouseWheel(amount);
523        waitForIdle();
524      }
525    
526      /** {@inheritDoc} */
527      @RunsInEDT
528      public void jitter(Component c) {
529        jitter(c, visibleCenterOf(c));
530      }
531    
532      /** {@inheritDoc} */
533      @RunsInEDT
534      public void jitter(Component c, Point where) {
535        int x = where.x;
536        int y = where.y;
537        moveMouse(c, (x > 0 ? x - 1 : x + 1), y);
538      }
539    
540      // Wait the given number of milliseconds for the component to be showing and ready.
541      @RunsInEDT
542      private boolean waitForComponentToBeReady(Component c, long timeout) {
543        if (isReadyForInput(c)) return true;
544        TimeoutWatch watch = startWatchWithTimeoutOf(timeout);
545        while (!isReadyForInput(c)) {
546          if (c instanceof JPopupMenu) {
547            // wiggle the mouse over the parent menu item to ensure the sub-menu shows
548            Pair<Component, Point> invokerAndCenterOfInvoker = invokerAndCenterOfInvoker((JPopupMenu)c);
549            Component invoker = invokerAndCenterOfInvoker.i;
550            if (invoker instanceof JMenu) jitter(invoker, invokerAndCenterOfInvoker.ii);
551          }
552          if (watch.isTimeOut()) return false;
553          pause();
554        }
555        return true;
556      }
557    
558      @RunsInEDT
559      private static Pair<Component, Point> invokerAndCenterOfInvoker(final JPopupMenu popupMenu) {
560        return execute(new GuiQuery<Pair<Component, Point>>() {
561          protected Pair<Component, Point> executeInEDT() {
562            Component invoker = popupMenu.getInvoker();
563            return new Pair<Component, Point>(invoker, centerOf(invoker));
564          }
565        });
566      }
567    
568      /** {@inheritDoc} */
569      @RunsInEDT
570      public void enterText(String text) {
571        if (isEmpty(text)) return;
572        for (char character : text.toCharArray()) type(character);
573      }
574    
575      /** {@inheritDoc} */
576      @RunsInEDT
577      public void type(char character) {
578        KeyStroke keyStroke = keyStrokeFor(character);
579        if (keyStroke == null) {
580          Component focus = focusOwner();
581          if (focus == null) return;
582          KeyEvent keyEvent = keyEventFor(focus, character);
583          // Allow any pending robot events to complete; otherwise we might stuff the typed event before previous
584          // robot-generated events are posted.
585          waitForIdle();
586          eventPoster.postEvent(focus, keyEvent);
587          return;
588        }
589        keyPressAndRelease(keyStroke.getKeyCode(), keyStroke.getModifiers());
590      }
591    
592      private KeyEvent keyEventFor(Component c, char character) {
593        return new KeyEvent(c, KEY_TYPED, System.currentTimeMillis(), 0, VK_UNDEFINED, character);
594      }
595    
596      /** {@inheritDoc} */
597      @RunsInEDT
598      public void pressAndReleaseKey(int keyCode, int... modifiers) {
599        keyPressAndRelease(keyCode, unify(modifiers));
600        waitForIdle();
601      }
602    
603      /** {@inheritDoc} */
604      @RunsInEDT
605      public void pressAndReleaseKeys(int... keyCodes) {
606        for (int keyCode : keyCodes) {
607          keyPressAndRelease(keyCode, 0);
608          waitForIdle();
609          pause(50); // it seems that even when waiting for idle the events are not completely propagated
610        }
611      }
612    
613      @RunsInEDT
614      private void keyPressAndRelease(int keyCode, int modifiers) {
615        int updatedModifiers = updateModifierWithKeyCode(keyCode, modifiers);
616        pressModifiers(updatedModifiers);
617        if (updatedModifiers == modifiers) {
618          doPressKey(keyCode);
619          eventGenerator.releaseKey(keyCode);
620        }
621        releaseModifiers(updatedModifiers);
622      }
623    
624      /** {@inheritDoc} */
625      @RunsInEDT
626      public void pressKey(int keyCode) {
627        doPressKey(keyCode);
628        waitForIdle();
629      }
630    
631      @RunsInEDT
632      private void doPressKey(int keyCode) {
633        eventGenerator.pressKey(keyCode, CHAR_UNDEFINED);
634      }
635    
636      /** {@inheritDoc} */
637      @RunsInEDT
638      public void releaseKey(int keyCode) {
639        eventGenerator.releaseKey(keyCode);
640        waitForIdle();
641      }
642    
643      @RunsInEDT
644      private void mouseRelease(int buttons) {
645        eventGenerator.releaseMouse(buttons);
646      }
647    
648      /** {@inheritDoc} */
649      @RunsInEDT
650      public void waitForIdle() {
651        waitIfNecessary();
652        Collection<EventQueue> queues = windowMonitor.allEventQueues();
653        if (queues.size() == 1) {
654          waitForIdle(toolkit.getSystemEventQueue());
655          return;
656        }
657        // FIXME this resurrects dead event queues
658        for (EventQueue queue : queues) waitForIdle(queue);
659      }
660    
661      private void waitIfNecessary() {
662        int delayBetweenEvents = settings.delayBetweenEvents();
663        int eventPostingDelay  = settings.eventPostingDelay();
664        if (eventPostingDelay > delayBetweenEvents) pause(eventPostingDelay - delayBetweenEvents);
665      }
666    
667      private void waitForIdle(EventQueue eventQueue) {
668        if (EventQueue.isDispatchThread())
669          throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
670        // Abbot: as of Java 1.3.1, robot.waitForIdle only waits for the last event on the queue at the time of this
671        // invocation to be processed. We need better than that. Make sure the given event queue is empty when this method
672        // returns.
673        // We always post at least one idle event to allow any current event dispatch processing to finish.
674        long start = currentTimeMillis();
675        int count = 0;
676        do {
677          // Timed out waiting for idle
678          int idleTimeout = settings.idleTimeout();
679          if (postInvocationEvent(eventQueue, idleTimeout)) break;
680          // Timed out waiting for idle event queue
681          if (currentTimeMillis() - start > idleTimeout) break;
682          ++count;
683          // Force a yield
684          pause();
685          // Abbot: this does not detect invocation events (i.e. what gets posted with EventQueue.invokeLater), so if
686          // someone is repeatedly posting one, we might get stuck. Not too worried, since if a Runnable keeps calling
687          // invokeLater on itself, *nothing* else gets much chance to run, so it seems to be a bad programming practice.
688        } while (eventQueue.peekEvent() != null);
689      }
690    
691      // Indicates whether we timed out waiting for the invocation to run
692      @RunsInEDT
693      private boolean postInvocationEvent(EventQueue eventQueue, long timeout) {
694        Object lock = new RobotIdleLock();
695        synchronized (lock) {
696          eventQueue.postEvent(new InvocationEvent(toolkit, EMPTY_RUNNABLE, lock, true));
697          long start = currentTimeMillis();
698          try {
699            // NOTE: on fast linux systems when showing a dialog, if we don't provide a timeout, we're never notified, and
700            // the test will wait forever (up through 1.5.0_05).
701            lock.wait(timeout);
702            return (currentTimeMillis() - start) >= settings.idleTimeout();
703          } catch (InterruptedException e) {}
704          return false;
705        }
706      }
707    
708      private static class RobotIdleLock {
709        RobotIdleLock() {}
710      }
711    
712      /** {@inheritDoc} */
713      public boolean isDragging() {
714        return inputState.dragInProgress();
715      }
716    
717      /** {@inheritDoc} */
718      @RunsInEDT
719      public JPopupMenu showPopupMenu(Component invoker) {
720        return showPopupMenu(invoker, visibleCenterOf(invoker));
721      }
722    
723      /** {@inheritDoc} */
724      @RunsInEDT
725      public JPopupMenu showPopupMenu(Component invoker, Point location) {
726        if (isFocusable(invoker)) focusAndWaitForFocusGain(invoker);
727        click(invoker, location, RIGHT_BUTTON, 1);
728        JPopupMenu popup = findActivePopupMenu();
729        if (popup == null)
730          throw new ComponentLookupException(concat("Unable to show popup at ", location, " on ", inEdtFormat(invoker)));
731        long start = currentTimeMillis();
732        while (!isWindowAncestorReadyForInput(popup) && currentTimeMillis() - start > POPUP_DELAY)
733          pause();
734        return popup;
735      }
736    
737      @RunsInEDT
738      private boolean isWindowAncestorReadyForInput(final JPopupMenu popup) {
739        return execute(new GuiQuery<Boolean>() {
740          protected Boolean executeInEDT() {
741            return isReadyForInput(getWindowAncestor(popup));
742          }
743        });
744      }
745    
746      /**
747       * Indicates whether the given <code>{@link Component}</code> is ready for input.
748       * <p>
749       * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
750       * responsible for calling this method from the EDT.
751       * </p>
752       * @param c the given <code>Component</code>.
753       * @return <code>true</code> if the given <code>Component</code> is ready for input, <code>false</code> otherwise.
754       * @throws ActionFailedException if the given <code>Component</code> does not have a <code>Window</code> ancestor.
755       */
756      @RunsInCurrentThread
757      public boolean isReadyForInput(Component c) {
758        Window w = windowAncestorOf(c);
759        if (w == null) throw actionFailure(concat("Component ", format(c), " does not have a Window ancestor"));
760        return c.isShowing() && windowMonitor.isWindowReady(w);
761      }
762    
763      /** {@inheritDoc} */
764      @RunsInEDT
765      public JPopupMenu findActivePopupMenu() {
766        JPopupMenu popup = activePopupMenu();
767        if (popup != null || isEventDispatchThread()) return popup;
768        TimeoutWatch watch = startWatchWithTimeoutOf(POPUP_TIMEOUT);
769        while ((popup = activePopupMenu()) == null) {
770          if (watch.isTimeOut()) break;
771          pause(100);
772        }
773        return popup;
774      }
775    
776      @RunsInEDT
777      private JPopupMenu activePopupMenu() {
778        List<Component> found = new ArrayList<Component>(finder().findAll(POPUP_MATCHER));
779        if (found.size() == 1) return (JPopupMenu)found.get(0);
780        return null;
781      }
782    
783      /** {@inheritDoc} */
784      @RunsInEDT
785      public void requireNoJOptionPaneIsShowing() {
786        unexpectedJOptionPaneFinder.requireNoJOptionPaneIsShowing();
787      }
788    
789      /** {@inheritDoc} */
790      public Settings settings() {
791        return settings;
792      }
793    
794      /** {@inheritDoc} */
795      public ComponentHierarchy hierarchy() {
796        return hierarchy;
797      }
798    
799      /** {@inheritDoc} */
800      public synchronized boolean isActive() { return active; }
801    
802      @VisibleForTesting
803      final Object screenLockOwner() { return screenLockOwner; }
804    }