content: /*
   * Created on Sep 29, 2006
   * 
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
   * the License. You may obtain a copy of the License at
   * 
   * http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
   * specific language governing permissions and limitations under the License.
   * 
   * Copyright @2006-2013 the original author or authors.
   */
  package org.fest.swing.core;
  
  import static java.awt.event.InputEvent.BUTTON1_MASK;
  import static java.awt.event.InputEvent.BUTTON2_MASK;
  import static java.awt.event.InputEvent.BUTTON3_MASK;
  import static java.awt.event.KeyEvent.CHAR_UNDEFINED;
  import static java.awt.event.KeyEvent.KEY_TYPED;
  import static java.awt.event.KeyEvent.VK_UNDEFINED;
  import static java.awt.event.WindowEvent.WINDOW_CLOSING;
  import static java.lang.System.currentTimeMillis;
  import static javax.swing.SwingUtilities.getWindowAncestor;
  import static javax.swing.SwingUtilities.isEventDispatchThread;
  import static org.fest.swing.awt.AWT.centerOf;
  import static org.fest.swing.awt.AWT.visibleCenterOf;
  import static org.fest.swing.core.ActivateWindowTask.activateWindow;
  import static org.fest.swing.core.ComponentIsFocusableQuery.isFocusable;
  import static org.fest.swing.core.ComponentRequestFocusTask.giveFocusTo;
  import static org.fest.swing.core.FocusOwnerFinder.focusOwner;
  import static org.fest.swing.core.FocusOwnerFinder.inEdtFocusOwner;
  import static org.fest.swing.core.InputModifiers.unify;
  import static org.fest.swing.core.MouseButton.LEFT_BUTTON;
  import static org.fest.swing.core.MouseButton.RIGHT_BUTTON;
  import static org.fest.swing.core.Scrolling.scrollToVisible;
  import static org.fest.swing.core.WindowAncestorFinder.windowAncestorOf;
  import static org.fest.swing.edt.GuiActionRunner.execute;
  import static org.fest.swing.exception.ActionFailedException.actionFailure;
  import static org.fest.swing.format.Formatting.format;
  import static org.fest.swing.format.Formatting.inEdtFormat;
  import static org.fest.swing.hierarchy.NewHierarchy.ignoreExistingComponents;
  import static org.fest.swing.keystroke.KeyStrokeMap.keyStrokeFor;
  import static org.fest.swing.query.ComponentShowingQuery.isShowing;
  import static org.fest.swing.timing.Pause.pause;
  import static org.fest.swing.util.Modifiers.keysFor;
  import static org.fest.swing.util.Modifiers.updateModifierWithKeyCode;
  import static org.fest.swing.util.TimeoutWatch.startWatchWithTimeoutOf;
  import static org.fest.util.Lists.newArrayList;
  import static org.fest.util.Preconditions.checkNotNull;
  import static org.fest.util.Strings.concat;
  
  import java.applet.Applet;
  import java.awt.Component;
  import java.awt.Container;
  import java.awt.Dimension;
  import java.awt.EventQueue;
  import java.awt.Point;
  import java.awt.Toolkit;
  import java.awt.Window;
  import java.awt.event.InvocationEvent;
  import java.awt.event.KeyEvent;
  import java.awt.event.WindowEvent;
  import java.util.Collection;
  import java.util.List;
  
  import javax.annotation.Nonnull;
  import javax.annotation.Nullable;
  import javax.annotation.concurrent.GuardedBy;
  import javax.swing.JComponent;
  import javax.swing.JMenu;
  import javax.swing.JPopupMenu;
  import javax.swing.KeyStroke;
  
  import org.fest.swing.annotation.RunsInCurrentThread;
  import org.fest.swing.annotation.RunsInEDT;
  import org.fest.swing.edt.GuiQuery;
  import org.fest.swing.edt.GuiTask;
  import org.fest.swing.exception.ActionFailedException;
  import org.fest.swing.exception.ComponentLookupException;
  import org.fest.swing.exception.WaitTimedOutError;
  import org.fest.swing.hierarchy.ComponentHierarchy;
  import org.fest.swing.hierarchy.ExistingHierarchy;
  import org.fest.swing.input.InputState;
  import org.fest.swing.lock.ScreenLock;
  import org.fest.swing.monitor.WindowMonitor;
  import org.fest.swing.util.Pair;
  import org.fest.swing.util.TimeoutWatch;
  import org.fest.swing.util.ToolkitProvider;
  import org.fest.util.VisibleForTesting;
  
  /**
   * Default implementation of {@link Robot}.
   * 
   * @author Alex Ruiz
   * @author Yvonne Wang
   * 
   * @see Robot
   */
  public class BasicRobot implements Robot {
    private static final int POPUP_DELAY = 10000;
    private static final int POPUP_TIMEOUT = 5000;
    private static final int WINDOW_DELAY = 20000;
  
    private static final ComponentMatcher POPUP_MATCHER = new TypeMatcher(JPopupMenu.class, true);
  
    @GuardedBy("this")
    private volatile boolean active;
  
    private static final Runnable EMPTY_RUNNABLE = new Runnable() {
      @Override
      public void run() {}
    };
  
    private static final int BUTTON_MASK = BUTTON1_MASK | BUTTON2_MASK | BUTTON3_MASK;
  
    private static Toolkit toolkit = ToolkitProvider.instance().defaultToolkit();
    private static WindowMonitor windowMonitor = WindowMonitor.instance();
    private static InputState inputState = new InputState(toolkit);
  
    private final ComponentHierarchy hierarchy;
    private final Object screenLockOwner;
    private final ComponentFinder finder;
    private final Settings settings;
    private final AWTEventPoster eventPoster;
    private final InputEventGenerator eventGenerator;
    private final UnexpectedJOptionPaneFinder unexpectedJOptionPaneFinder;
  
    /**
     * Creates a new {@link Robot} with a new AWT hierarchy. The created {@code Robot} will not be able to access any
     * AWT and Swing {@code Component}s that were created before it.
     * 
     * @return the created {@code Robot}.
     */
    public static @Nonnull Robot robotWithNewAwtHierarchy() {
      Object screenLockOwner = acquireScreenLock();
      return new BasicRobot(screenLockOwner, ignoreExistingComponents());
    }
  
    public static @Nonnull Robot robotWithNewAwtHierarchyWithoutScreenLock() {
      return new BasicRobot(null, ignoreExistingComponents());
    }
  
    /**
     * Creates a new {@link Robot} that has access to all the AWT and Swing {@code Component}s in the AWT hierarchy.
     * 
     * @return the created {@code Robot}.
     */
    public static @Nonnull Robot robotWithCurrentAwtHierarchy() {
      Object screenLockOwner = acquireScreenLock();
      return new BasicRobot(screenLockOwner, new ExistingHierarchy());
    }
  
    // TODO document
    public static @Nonnull Robot robotWithCurrentAwtHierarchyWithoutScreenLock() {
      return new BasicRobot(null, new ExistingHierarchy());
    }
  
    private static @Nonnull Object acquireScreenLock() {
      Object screenLockOwner = new Object();
      ScreenLock.instance().acquire(screenLockOwner);
      return screenLockOwner;
    }
  
    @VisibleForTesting
    BasicRobot(@Nullable Object screenLockOwner, @Nonnull ComponentHierarchy hierarchy) {
      this.screenLockOwner = screenLockOwner;
      this.hierarchy = hierarchy;
      settings = new Settings();
      eventGenerator = new RobotEventGenerator(settings);
      eventPoster = new AWTEventPoster(toolkit, inputState, windowMonitor, settings);
      finder = new BasicComponentFinder(hierarchy, settings);
      unexpectedJOptionPaneFinder = new UnexpectedJOptionPaneFinder(finder);
      active = true;
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull ComponentPrinter printer() {
      return finder().printer();
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull ComponentFinder finder() {
      return finder;
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void showWindow(@Nonnull Window w) {
      showWindow(w, null, true);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void showWindow(@Nonnull Window w, @Nonnull Dimension size) {
      showWindow(w, size, true);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void showWindow(@Nonnull final Window w, @Nullable final Dimension size, final boolean pack) {
      EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
          if (pack) {
            packAndEnsureSafePosition(w);
          }
          if (size != null) {
            w.setSize(size);
          }
          w.setVisible(true);
        }
      });
      waitForWindow(w);
    }
  
    @RunsInCurrentThread
    private void packAndEnsureSafePosition(@Nonnull Window w) {
      w.pack();
      w.setLocation(100, 100);
    }
  
    @RunsInEDT
    private void waitForWindow(@Nonnull Window w) {
      long start = currentTimeMillis();
      while (!windowMonitor.isWindowReady(w) || !isShowing(w)) {
        long elapsed = currentTimeMillis() - start;
        if (elapsed > WINDOW_DELAY) {
          throw new WaitTimedOutError(concat("Timed out waiting for Window to open (", String.valueOf(elapsed), "ms)"));
        }
        pause();
      }
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void close(@Nonnull Window w) {
      WindowEvent event = new WindowEvent(w, WINDOW_CLOSING);
      // If the window contains an applet, send the event on the applet's queue instead to ensure a shutdown from the
      // applet's context (assists AppletViewer cleanup).
      Component applet = findAppletDescendent(w);
      EventQueue eventQueue = windowMonitor.eventQueueFor(applet != null ? applet : w);
      checkNotNull(eventQueue).postEvent(event);
      waitForIdle();
    }
  
    /**
     * Returns the {@code Applet} descendant of the given AWT {@code Container}, if any.
     * 
     * @param c the given {@code Container}.
     * @return the {@code Applet} descendant of the given AWT {@code Container}, or {@code null} if none is found.
     */
    @RunsInEDT
    private @Nullable Applet findAppletDescendent(@Nonnull Container c) {
      List<Component> found = newArrayList(finder.findAll(c, new TypeMatcher(Applet.class)));
      if (found.size() == 1) {
        return (Applet) found.get(0);
      }
      return null;
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void focusAndWaitForFocusGain(@Nonnull Component c) {
      focus(c, true);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void focus(@Nonnull Component c) {
      focus(c, false);
    }
  
    @RunsInEDT
    private void focus(@Nonnull Component target, boolean wait) {
      Component currentOwner = inEdtFocusOwner();
      if (currentOwner == target) {
        return;
      }
      FocusMonitor focusMonitor = FocusMonitor.attachTo(target);
      // for pointer focus
      moveMouse(target);
      // Make sure the correct window is in front
      activateWindowOfFocusTarget(target, currentOwner);
      giveFocusTo(target);
      try {
        if (wait) {
          TimeoutWatch watch = startWatchWithTimeoutOf(settings().timeoutToBeVisible());
          while (!focusMonitor.hasFocus()) {
            if (watch.isTimeOut()) {
              throw actionFailure(concat("Focus change to ", format(target), " failed"));
            }
            pause();
          }
        }
      } finally {
        target.removeFocusListener(focusMonitor);
      }
    }
  
    @RunsInEDT
    private void activateWindowOfFocusTarget(@Nullable Component target, @Nullable Component currentOwner) {
      Pair<Window, Window> windowAncestors = windowAncestorsOf(currentOwner, target);
      Window currentOwnerAncestor = windowAncestors.first;
      Window targetAncestor = windowAncestors.second;
      if (currentOwnerAncestor == targetAncestor) {
        return;
      }
      activate(checkNotNull(targetAncestor));
      waitForIdle();
    }
  
    @RunsInEDT
    private static Pair<Window, Window> windowAncestorsOf(final @Nullable Component one, final @Nullable Component two) {
      return execute(new GuiQuery<Pair<Window, Window>>() {
        @Override
        protected Pair<Window, Window> executeInEDT() throws Throwable {
          return Pair.of(windowAncestor(one), windowAncestor(two));
        }
  
        private @Nullable Window windowAncestor(Component c) {
          return (c != null) ? windowAncestorOf(c) : null;
        }
      });
    }
  
    /**
     * Activates the given AWT {@code Window}. "Activate" means that the given window gets the keyboard focus.
     * 
     * @param w the window to activate.
     */
    @RunsInEDT
    private void activate(@Nonnull Window w) {
      activateWindow(w);
      moveMouse(w); // For pointer-focus systems
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public synchronized void cleanUp() {
      cleanUp(true);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public synchronized void cleanUpWithoutDisposingWindows() {
      cleanUp(false);
    }
  
    @RunsInEDT
    private void cleanUp(boolean disposeWindows) {
      try {
        if (disposeWindows) {
          disposeWindows(hierarchy);
        }
        releaseMouseButtons();
      } finally {
        active = false;
        releaseScreenLock();
      }
    }
  
    private void releaseScreenLock() {
      ScreenLock screenLock = ScreenLock.instance();
      if (screenLock.acquiredBy(screenLockOwner)) {
        screenLock.release(screenLockOwner);
      }
    }
  
    @RunsInEDT
    private static void disposeWindows(final @Nonnull ComponentHierarchy hierarchy) {
      execute(new GuiTask() {
        @Override
        protected void executeInEDT() {
          for (Container c : hierarchy.roots()) {
            if (c instanceof Window) {
              dispose(hierarchy, (Window) c);
            }
          }
        }
      });
    }
  
    @RunsInCurrentThread
    private static void dispose(final @Nonnull ComponentHierarchy hierarchy, @Nonnull Window w) {
      hierarchy.dispose(w);
      w.setVisible(false);
      w.dispose();
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void click(@Nonnull Component c) {
      click(c, LEFT_BUTTON);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void rightClick(@Nonnull Component c) {
      click(c, RIGHT_BUTTON);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void click(@Nonnull Component c, @Nonnull MouseButton button) {
      click(c, button, 1);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void doubleClick(@Nonnull Component c) {
      click(c, LEFT_BUTTON, 2);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void click(@Nonnull Component c, @Nonnull MouseButton button, int times) {
      Point where = visibleCenterOf(c);
      if (c instanceof JComponent) {
        where = scrollIfNecessary((JComponent) c);
      }
      click(c, where, button, times);
    }
  
    private @Nonnull Point scrollIfNecessary(@Nonnull JComponent c) {
      scrollToVisible(this, c);
      return visibleCenterOf(c);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void click(@Nonnull Component c, @Nonnull Point where) {
      click(c, where, LEFT_BUTTON, 1);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void click(@Nonnull Point where, @Nonnull MouseButton button, int times) {
      doClick(null, where, button, times);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void click(@Nonnull Component c, @Nonnull Point where, @Nonnull MouseButton button, int times) {
      doClick(c, where, button, times);
    }
  
    private void doClick(@Nullable Component c, @Nonnull Point where, @Nonnull MouseButton button, int times) {
      int mask = button.mask;
      int modifierMask = mask & ~BUTTON_MASK;
      mask &= BUTTON_MASK;
      pressModifiers(modifierMask);
      // From Abbot: Adjust the auto-delay to ensure we actually get a multiple click
      // In general clicks have to be less than 200ms apart, although the actual setting is not readable by Java.
      int delayBetweenEvents = settings.delayBetweenEvents();
      if (shouldSetDelayBetweenEventsToZeroWhenClicking(times)) {
        settings.delayBetweenEvents(0);
      }
      if (c == null) {
        eventGenerator.pressMouse(where, mask);
        for (int i = times; i > 1; i--) {
          eventGenerator.releaseMouse(mask);
          eventGenerator.pressMouse(where, mask);
        }
      } else {
        eventGenerator.pressMouse(c, where, mask);
        for (int i = times; i > 1; i--) {
          eventGenerator.releaseMouse(mask);
          eventGenerator.pressMouse(c, where, mask);
        }
      }
      settings.delayBetweenEvents(delayBetweenEvents);
      eventGenerator.releaseMouse(mask);
      releaseModifiers(modifierMask);
      waitForIdle();
    }
  
    private boolean shouldSetDelayBetweenEventsToZeroWhenClicking(int times) {
      return times > 1 /* FEST-137: && settings.delayBetweenEvents() * 2 > 200 */;
    }
  
    /** {@inheritDoc} */
    @Override
    public void pressModifiers(int modifierMask) {
      for (int modifierKey : keysFor(modifierMask)) {
        pressKey(modifierKey);
      }
    }
  
    /** {@inheritDoc} */
    @Override
    public void releaseModifiers(int modifierMask) {
      // For consistency, release in the reverse order of press.
      int[] modifierKeys = keysFor(modifierMask);
      for (int i = modifierKeys.length - 1; i >= 0; i--) {
        releaseKey(modifierKeys[i]);
      }
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void moveMouse(@Nonnull Component c) {
      moveMouse(c, visibleCenterOf(c));
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void moveMouse(@Nonnull Component c, @Nonnull Point p) {
      moveMouse(c, p.x, p.y);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void moveMouse(@Nonnull Component c, int x, int y) {
      if (!waitForComponentToBeReady(c, settings.timeoutToBeVisible())) {
        throw actionFailure(concat("Could not obtain position of component ", format(c)));
      }
      eventGenerator.moveMouse(c, x, y);
      waitForIdle();
    }
  
    /** {@inheritDoc} */
    @Override
    public void moveMouse(@Nonnull Point p) {
      moveMouse(p.x, p.y);
    }
  
    /** {@inheritDoc} */
    @Override
    public void moveMouse(int x, int y) {
      eventGenerator.moveMouse(x, y);
    }
  
    /** {@inheritDoc} */
    @Override
    public void pressMouse(@Nonnull MouseButton button) {
      eventGenerator.pressMouse(button.mask);
    }
  
    /** {@inheritDoc} */
    @Override
    public void pressMouse(@Nonnull Component c, @Nonnull Point where) {
      pressMouse(c, where, LEFT_BUTTON);
    }
  
    /** {@inheritDoc} */
    @Override
    public void pressMouse(@Nonnull Component c, @Nonnull Point where, @Nonnull MouseButton button) {
      jitter(c, where);
      moveMouse(c, where.x, where.y);
      eventGenerator.pressMouse(c, where, button.mask);
    }
  
    /** {@inheritDoc} */
    @Override
    public void pressMouse(@Nonnull Point where, @Nonnull MouseButton button) {
      eventGenerator.pressMouse(where, button.mask);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void releaseMouse(@Nonnull MouseButton button) {
      mouseRelease(button.mask);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void releaseMouseButtons() {
      int buttons = inputState.buttons();
      if (buttons == 0) {
        return;
      }
      mouseRelease(buttons);
    }
  
    /** {@inheritDoc} */
    @Override
    public void rotateMouseWheel(@Nonnull Component c, int amount) {
      moveMouse(c);
      rotateMouseWheel(amount);
    }
  
    /** {@inheritDoc} */
    @Override
    public void rotateMouseWheel(int amount) {
      eventGenerator.rotateMouseWheel(amount);
      waitForIdle();
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void jitter(@Nonnull Component c) {
      jitter(c, visibleCenterOf(c));
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void jitter(@Nonnull Component c, @Nonnull Point where) {
      int x = where.x;
      int y = where.y;
      moveMouse(c, (x > 0 ? x - 1 : x + 1), y);
    }
  
    // Wait the given number of milliseconds for the component to be showing and ready.
    @RunsInEDT
    private boolean waitForComponentToBeReady(@Nonnull Component c, long timeout) {
      if (isReadyForInput(c)) {
        return true;
      }
      TimeoutWatch watch = startWatchWithTimeoutOf(timeout);
      while (!isReadyForInput(c)) {
        if (c instanceof JPopupMenu) {
          // wiggle the mouse over the parent menu item to ensure the sub-menu shows
          Pair<Component, Point> invokerAndCenterOfInvoker = invokerAndCenterOfInvoker((JPopupMenu) c);
          Component invoker = invokerAndCenterOfInvoker.first;
          if (invoker instanceof JMenu) {
            jitter(invoker, invokerAndCenterOfInvoker.second);
          }
        }
        if (watch.isTimeOut()) {
          return false;
        }
        pause();
      }
      return true;
    }
  
    @RunsInEDT
    private static @Nonnull Pair<Component, Point> invokerAndCenterOfInvoker(final @Nonnull JPopupMenu popupMenu) {
      Pair<Component, Point> result = execute(new GuiQuery<Pair<Component, Point>>() {
        @Override
        protected Pair<Component, Point> executeInEDT() {
          Component invoker = checkNotNull(popupMenu.getInvoker());
          return Pair.of(invoker, centerOf(invoker));
        }
      });
      return checkNotNull(result);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void enterText(@Nonnull String text) {
      checkNotNull(text);
      if (text.isEmpty()) {
        return;
      }
      for (char character : text.toCharArray()) {
        type(character);
      }
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void type(char character) {
      KeyStroke keyStroke = keyStrokeFor(character);
      if (keyStroke == null) {
        Component focus = focusOwner();
        if (focus == null) {
          return;
        }
        KeyEvent keyEvent = keyEventFor(focus, character);
        // Allow any pending robot events to complete; otherwise we might stuff the typed event before previous
        // robot-generated events are posted.
        waitForIdle();
        eventPoster.postEvent(focus, keyEvent);
        return;
      }
      keyPressAndRelease(keyStroke.getKeyCode(), keyStroke.getModifiers());
    }
  
    private KeyEvent keyEventFor(Component c, char character) {
      return new KeyEvent(c, KEY_TYPED, currentTimeMillis(), 0, VK_UNDEFINED, character);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void pressAndReleaseKey(int keyCode, @Nonnull int... modifiers) {
      keyPressAndRelease(keyCode, unify(modifiers));
      waitForIdle();
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void pressAndReleaseKeys(@Nonnull int... keyCodes) {
      for (int keyCode : keyCodes) {
        keyPressAndRelease(keyCode, 0);
        waitForIdle();
        pause(50); // it seems that even when waiting for idle the events are not completely propagated
      }
    }
  
    @RunsInEDT
    private void keyPressAndRelease(int keyCode, int modifiers) {
      int updatedModifiers = updateModifierWithKeyCode(keyCode, modifiers);
      pressModifiers(updatedModifiers);
      if (updatedModifiers == modifiers) {
        doPressKey(keyCode);
        eventGenerator.releaseKey(keyCode);
      }
      releaseModifiers(updatedModifiers);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void pressKey(int keyCode) {
      doPressKey(keyCode);
      waitForIdle();
    }
  
    @RunsInEDT
    private void doPressKey(int keyCode) {
      eventGenerator.pressKey(keyCode, CHAR_UNDEFINED);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void releaseKey(int keyCode) {
      eventGenerator.releaseKey(keyCode);
      waitForIdle();
    }
  
    @RunsInEDT
    private void mouseRelease(int buttons) {
      eventGenerator.releaseMouse(buttons);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void waitForIdle() {
      waitIfNecessary();
      Collection<EventQueue> queues = windowMonitor.allEventQueues();
      if (queues.size() == 1) {
        waitForIdle(checkNotNull(toolkit.getSystemEventQueue()));
        return;
      }
      // FIXME this resurrects dead event queues
      for (EventQueue queue : queues) {
        waitForIdle(checkNotNull(queue));
      }
    }
  
    private void waitIfNecessary() {
      int delayBetweenEvents = settings.delayBetweenEvents();
      int eventPostingDelay = settings.eventPostingDelay();
      if (eventPostingDelay > delayBetweenEvents) {
        pause(eventPostingDelay - delayBetweenEvents);
      }
    }
  
    private void waitForIdle(@Nonnull EventQueue eventQueue) {
      if (EventQueue.isDispatchThread()) {
        throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
      }
      // Abbot: as of Java 1.3.1, robot.waitForIdle only waits for the last event on the queue at the time of this
      // invocation to be processed. We need better than that. Make sure the given event queue is empty when this method
      // returns.
      // We always post at least one idle event to allow any current event dispatch processing to finish.
      long start = currentTimeMillis();
      int count = 0;
      do {
        // Timed out waiting for idle
        int idleTimeout = settings.idleTimeout();
        if (postInvocationEvent(eventQueue, idleTimeout)) {
          break;
        }
        // Timed out waiting for idle event queue
        if (currentTimeMillis() - start > idleTimeout) {
          break;
        }
        ++count;
        // Force a yield
        pause();
        // Abbot: this does not detect invocation events (i.e. what gets posted with EventQueue.invokeLater), so if
        // someone is repeatedly posting one, we might get stuck. Not too worried, since if a Runnable keeps calling
        // invokeLater on itself, *nothing* else gets much chance to run, so it seems to be a bad programming practice.
      } while (eventQueue.peekEvent() != null);
    }
  
    // Indicates whether we timed out waiting for the invocation to run
    @RunsInEDT
    private boolean postInvocationEvent(@Nonnull EventQueue eventQueue, long timeout) {
      Object lock = new RobotIdleLock();
      synchronized (lock) {
        eventQueue.postEvent(new InvocationEvent(toolkit, EMPTY_RUNNABLE, lock, true));
        long start = currentTimeMillis();
        try {
          // NOTE: on fast linux systems when showing a dialog, if we don't provide a timeout, we're never notified, and
          // the test will wait forever (up through 1.5.0_05).
          lock.wait(timeout);
          return (currentTimeMillis() - start) >= settings.idleTimeout();
        } catch (InterruptedException e) {
        }
        return false;
      }
    }
  
    private static class RobotIdleLock {
      RobotIdleLock() {
      }
    }
  
    /** {@inheritDoc} */
    @Override
    public boolean isDragging() {
      return inputState.dragInProgress();
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull JPopupMenu showPopupMenu(@Nonnull Component invoker) {
      return showPopupMenu(invoker, visibleCenterOf(invoker));
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull JPopupMenu showPopupMenu(@Nonnull Component invoker, @Nonnull Point location) {
      if (isFocusable(invoker)) {
        focusAndWaitForFocusGain(invoker);
      }
      click(invoker, location, RIGHT_BUTTON, 1);
      JPopupMenu popup = findActivePopupMenu();
      if (popup == null) {
        throw new ComponentLookupException(concat("Unable to show popup at ", location, " on ", inEdtFormat(invoker)));
      }
      long start = currentTimeMillis();
      while (!isWindowAncestorReadyForInput(popup) && currentTimeMillis() - start > POPUP_DELAY) {
        pause();
      }
      return popup;
    }
  
    @RunsInEDT
    private boolean isWindowAncestorReadyForInput(final JPopupMenu popup) {
      Boolean result = execute(new GuiQuery<Boolean>() {
        @Override
        protected Boolean executeInEDT() {
          Window ancestor = checkNotNull(getWindowAncestor(popup));
          return isReadyForInput(ancestor);
        }
      });
      return checkNotNull(result);
    }
  
    /**
     * <p>
     * Indicates whether the given AWT or Swing {@code Component} is ready for input.
     * </p>
     * 
     * <p>
     * <b>Note:</b> This method is accessed in the current executing thread. Such thread may or may not be the event
     * dispatch thread (EDT.) Client code must call this method from the EDT.
     * </p>
     * 
     * @param c the given {@code Component}.
     * @return {@code true} if the given {@code Component} is ready for input, {@code false} otherwise.
     * @throws ActionFailedException if the given {@code Component} does not have a {@code Window} ancestor.
     */
    @Override
    @RunsInCurrentThread
    public boolean isReadyForInput(@Nonnull Component c) {
      Window w = windowAncestorOf(c);
      if (w == null) {
        throw actionFailure(concat("Component ", format(c), " does not have a Window ancestor"));
      }
      return c.isShowing() && windowMonitor.isWindowReady(w);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nullable JPopupMenu findActivePopupMenu() {
      JPopupMenu popup = activePopupMenu();
      if (popup != null || isEventDispatchThread()) {
        return popup;
      }
      TimeoutWatch watch = startWatchWithTimeoutOf(POPUP_TIMEOUT);
      while ((popup = activePopupMenu()) == null) {
        if (watch.isTimeOut()) {
          break;
        }
        pause(100);
      }
      return popup;
    }
  
    @RunsInEDT
    private @Nullable JPopupMenu activePopupMenu() {
      List<Component> found = newArrayList(finder().findAll(POPUP_MATCHER));
      if (found.size() == 1) {
        return (JPopupMenu) found.get(0);
      }
      return null;
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public void requireNoJOptionPaneIsShowing() {
      unexpectedJOptionPaneFinder.requireNoJOptionPaneIsShowing();
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull Settings settings() {
      return settings;
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull ComponentHierarchy hierarchy() {
      return hierarchy;
    }
  
    /** {@inheritDoc} */
    @Override
    public synchronized boolean isActive() {
      return active;
    }
  
    @VisibleForTesting
    final @Nullable Object screenLockOwner() {
      return screenLockOwner;
    }
  }
  