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 }