Package org.fest.swing.core

Source Code of org.fest.swing.core.BasicRobot

/*
* 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-2010 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.Strings.concat;
import static org.fest.util.Strings.isEmpty;

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.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;

import net.jcip.annotations.GuardedBy;

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.util.VisibleForTesting;

/**
* Understands simulation of user events on a GUI <code>{@link Component}</code>.
*
* @author Alex Ruiz
* @author Yvonne Wang
*/
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() {
    public void run() {}
  };

  private static final int BUTTON_MASK = BUTTON1_MASK | BUTTON2_MASK | BUTTON3_MASK;

  private static Toolkit toolkit = Toolkit.getDefaultToolkit();
  private static WindowMonitor windowMonitor = WindowMonitor.instance();
  private static InputState inputState = new InputState(toolkit);

  /** Provides access to all the components in the hierarchy. */
  private final ComponentHierarchy hierarchy;

  private final Object screenLockOwner;

  /** Looks up <code>{@link java.awt.Component}</code>s. */
  private final ComponentFinder finder;

  private final Settings settings;

  private final AWTEventPoster eventPoster;
  private final InputEventGenerator eventGenerator;
  private final UnexpectedJOptionPaneFinder unexpectedJOptionPaneFinder;

  /**
   * Creates a new <code>{@link Robot}</code> with a new AWT hierarchy. The created <code>Robot</code> will not be able
   * to access any components that were created before it.
   * @return the created <code>Robot</code>.
   */
  public static Robot robotWithNewAwtHierarchy() {
    Object screenLockOwner = acquireScreenLock();
    return new BasicRobot(screenLockOwner, ignoreExistingComponents());
  }

  /**
   * Creates a new <code>{@link Robot}</code> that has access to all the GUI components in the AWT hierarchy.
   * @return the created <code>Robot</code>.
   */
  public static Robot robotWithCurrentAwtHierarchy() {
    Object screenLockOwner = acquireScreenLock();
    return new BasicRobot(screenLockOwner, new ExistingHierarchy());
  }

  private static Object acquireScreenLock() {
    Object screenLockOwner = new Object();
    ScreenLock.instance().acquire(screenLockOwner);
    return screenLockOwner;
  }

  @VisibleForTesting
  BasicRobot(Object screenLockOwner, 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(this.hierarchy, settings);
    unexpectedJOptionPaneFinder = new UnexpectedJOptionPaneFinder(finder);
    active = true;
  }

  /** {@inheritDoc} */
  public ComponentPrinter printer() {
    return finder().printer();
  }

  /** {@inheritDoc} */
  public ComponentFinder finder() {
    return finder;
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void showWindow(Window w) {
    showWindow(w, null, true);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void showWindow(Window w, Dimension size) {
    showWindow(w, size, true);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void showWindow(final Window w, final Dimension size, final boolean pack) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        if (pack) packAndEnsureSafePosition(w);
        if (size != null) w.setSize(size);
        w.setVisible(true);
      }
    });
    waitForWindow(w);
  }

  @RunsInCurrentThread
  private void packAndEnsureSafePosition(Window w) {
    w.pack();
    w.setLocation(100, 100);
  }

  @RunsInEDT
  private void waitForWindow(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
  public void close(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);
    eventQueue.postEvent(event);
    waitForIdle();
  }

  /**
   * Returns the <code>{@link Applet}</code> descendant of the given <code>{@link Container}</code>, if any.
   * @param c the given <code>Container</code>.
   * @return the <code>Applet</code> descendant of the given <code>Container</code>, or <code>null</code> if none
   * is found.
   */
  @RunsInEDT
  private Applet findAppletDescendent(Container c) {
    List<Component> found = new ArrayList<Component>(finder.findAll(c, new TypeMatcher(Applet.class)));
    if (found.size() == 1) return (Applet)found.get(0);
    return null;
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void focusAndWaitForFocusGain(Component c) {
    focus(c, true);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void focus(Component c) {
    focus(c, false);
  }

  @RunsInEDT
  private void focus(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(Component target, Component currentOwner) {
    Pair<Window, Window> windowAncestors = windowAncestorsOf(currentOwner, target);
    Window currentOwnerAncestor = windowAncestors.i;
    Window targetAncestor = windowAncestors.ii;
    if (currentOwnerAncestor == targetAncestor) return;
    activate(targetAncestor);
    waitForIdle();
  }

  @RunsInEDT
  private static Pair<Window, Window> windowAncestorsOf(final Component one, final Component two) {
    return execute(new GuiQuery<Pair<Window, Window>>() {
      protected Pair<Window, Window> executeInEDT() throws Throwable {
        return new Pair<Window, Window>(windowAncestor(one), windowAncestor(two));
      }

      private Window windowAncestor(Component c) {
        return (c != null) ? windowAncestorOf(c) : null;
      }
    });
  }

  /**
   * Activates the given <code>{@link Window}</code>. "Activate" means that the given window gets the keyboard focus.
   * @param w the window to activate.
   */
  @RunsInEDT
  private void activate(Window w) {
    activateWindow(w);
    moveMouse(w); // For pointer-focus systems
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public synchronized void cleanUp() {
    cleanUp(true);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  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 ComponentHierarchy hierarchy) {
    execute(new GuiTask() {
      protected void executeInEDT() {
        for (Container c : hierarchy.roots()) if (c instanceof Window) dispose(hierarchy, (Window)c);
      }
    });
  }

  @RunsInCurrentThread
  private static void dispose(final ComponentHierarchy hierarchy, Window w) {
    hierarchy.dispose(w);
    w.setVisible(false);
    w.dispose();
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void click(Component c) {
    click(c, LEFT_BUTTON);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void rightClick(Component c) {
    click(c, RIGHT_BUTTON);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void click(Component c, MouseButton button) {
    click(c, button, 1);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void doubleClick(Component c) {
    click(c, LEFT_BUTTON, 2);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void click(Component c, MouseButton button, int times) {
    Point where = visibleCenterOf(c);
    if (c instanceof JComponent)
      where = scrollIfNecessary((JComponent) c, where);
    click(c, where, button, times);
  }

  private Point scrollIfNecessary(JComponent c, Point p) {
    scrollToVisible(this, c);
    return visibleCenterOf(c);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void click(Component c, Point where) {
    click(c, where, LEFT_BUTTON, 1);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void click(Point where, MouseButton button, int times) {
    click(null, where, button, times);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void click(Component c, Point where, 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);
    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} */
  public void pressModifiers(int modifierMask) {
    for (int modifierKey : keysFor(modifierMask))
      pressKey(modifierKey);
  }

  /** {@inheritDoc} */
  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
  public void moveMouse(Component c) {
    moveMouse(c, visibleCenterOf(c));
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void moveMouse(Component c, Point p) {
    moveMouse(c, p.x, p.y);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void moveMouse(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} */
  public void moveMouse(Point p) {
    moveMouse(p.x, p.y);
  }

  /** {@inheritDoc} */
  public void moveMouse(int x, int y) {
    eventGenerator.moveMouse(x, y);
  }

  /** {@inheritDoc} */
  public void pressMouse(MouseButton button) {
    eventGenerator.pressMouse(button.mask);
  }

  /** {@inheritDoc} */
  public void pressMouse(Component c, Point where) {
    pressMouse(c, where, LEFT_BUTTON);
  }

  /** {@inheritDoc} */
  public void pressMouse(Component c, Point where, MouseButton button) {
    jitter(c, where);
    moveMouse(c, where.x, where.y);
    eventGenerator.pressMouse(c, where, button.mask);
  }

  /** {@inheritDoc} */
  public void pressMouse(Point where, MouseButton button) {
    eventGenerator.pressMouse(where, button.mask);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void releaseMouse(MouseButton button) {
    mouseRelease(button.mask);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void releaseMouseButtons() {
    int buttons = inputState.buttons();
    if (buttons == 0) return;
    mouseRelease(buttons);
  }

  /** {@inheritDoc} */
  public void rotateMouseWheel(Component c, int amount) {
    moveMouse(c);
    rotateMouseWheel(amount);
  }

  /** {@inheritDoc} */
  public void rotateMouseWheel(int amount) {
    eventGenerator.rotateMouseWheel(amount);
    waitForIdle();
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void jitter(Component c) {
    jitter(c, visibleCenterOf(c));
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void jitter(Component c, 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(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.i;
        if (invoker instanceof JMenu) jitter(invoker, invokerAndCenterOfInvoker.ii);
      }
      if (watch.isTimeOut()) return false;
      pause();
    }
    return true;
  }

  @RunsInEDT
  private static Pair<Component, Point> invokerAndCenterOfInvoker(final JPopupMenu popupMenu) {
    return execute(new GuiQuery<Pair<Component, Point>>() {
      protected Pair<Component, Point> executeInEDT() {
        Component invoker = popupMenu.getInvoker();
        return new Pair<Component, Point>(invoker, centerOf(invoker));
      }
    });
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void enterText(String text) {
    if (isEmpty(text)) return;
    for (char character : text.toCharArray()) type(character);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  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, System.currentTimeMillis(), 0, VK_UNDEFINED, character);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void pressAndReleaseKey(int keyCode, int... modifiers) {
    keyPressAndRelease(keyCode, unify(modifiers));
    waitForIdle();
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void pressAndReleaseKeys(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
  public void pressKey(int keyCode) {
    doPressKey(keyCode);
    waitForIdle();
  }

  @RunsInEDT
  private void doPressKey(int keyCode) {
    eventGenerator.pressKey(keyCode, CHAR_UNDEFINED);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void releaseKey(int keyCode) {
    eventGenerator.releaseKey(keyCode);
    waitForIdle();
  }

  @RunsInEDT
  private void mouseRelease(int buttons) {
    eventGenerator.releaseMouse(buttons);
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void waitForIdle() {
    waitIfNecessary();
    Collection<EventQueue> queues = windowMonitor.allEventQueues();
    if (queues.size() == 1) {
      waitForIdle(toolkit.getSystemEventQueue());
      return;
    }
    // FIXME this resurrects dead event queues
    for (EventQueue queue : queues) waitForIdle(queue);
  }

  private void waitIfNecessary() {
    int delayBetweenEvents = settings.delayBetweenEvents();
    int eventPostingDelay  = settings.eventPostingDelay();
    if (eventPostingDelay > delayBetweenEvents) pause(eventPostingDelay - delayBetweenEvents);
  }

  private void waitForIdle(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(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} */
  public boolean isDragging() {
    return inputState.dragInProgress();
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public JPopupMenu showPopupMenu(Component invoker) {
    return showPopupMenu(invoker, visibleCenterOf(invoker));
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public JPopupMenu showPopupMenu(Component invoker, 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) {
    return execute(new GuiQuery<Boolean>() {
      protected Boolean executeInEDT() {
        return isReadyForInput(getWindowAncestor(popup));
      }
    });
  }

  /**
   * Indicates whether the given <code>{@link Component}</code> is ready for input.
   * <p>
   * <b>Note:</b> This method is <b>not</b> executed in the event dispatch thread (EDT.) Clients are responsible for
   * invoking this method in the EDT.
   * </p>
   * @param c the given <code>Component</code>.
   * @return <code>true</code> if the given <code>Component</code> is ready for input, <code>false</code> otherwise.
   * @throws ActionFailedException if the given <code>Component</code> does not have a <code>Window</code> ancestor.
   */
  @RunsInCurrentThread
  public boolean isReadyForInput(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
  public 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 JPopupMenu activePopupMenu() {
    List<Component> found = new ArrayList<Component>(finder().findAll(POPUP_MATCHER));
    if (found.size() == 1) return (JPopupMenu)found.get(0);
    return null;
  }

  /** {@inheritDoc} */
  @RunsInEDT
  public void requireNoJOptionPaneIsShowing() {
    unexpectedJOptionPaneFinder.requireNoJOptionPaneIsShowing();
  }

  /** {@inheritDoc} */
  public Settings settings() {
    return settings;
  }

  /** {@inheritDoc} */
  public ComponentHierarchy hierarchy() {
    return hierarchy;
  }

  /** {@inheritDoc} */
  public synchronized boolean isActive() { return active; }

  @VisibleForTesting
  final Object screenLockOwner() { return screenLockOwner; }
}
TOP

Related Classes of org.fest.swing.core.BasicRobot

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.