0001: package abbot.tester;
0002:
0003: import java.awt.AWTEvent;
0004: import java.awt.AWTException;
0005: import java.awt.Button;
0006: import java.awt.Checkbox;
0007: import java.awt.Color;
0008: import java.awt.Component;
0009: import java.awt.Container;
0010: import java.awt.Dialog;
0011: import java.awt.Dimension;
0012: import java.awt.EventQueue;
0013: import java.awt.Frame;
0014: import java.awt.Insets;
0015: import java.awt.Label;
0016: import java.awt.MenuBar;
0017: import java.awt.MenuComponent;
0018: import java.awt.MenuItem;
0019: import java.awt.Point;
0020: import java.awt.PopupMenu;
0021: import java.awt.Rectangle;
0022: import java.awt.Toolkit;
0023: import java.awt.Window;
0024: import java.awt.event.ActionEvent;
0025: import java.awt.event.ComponentEvent;
0026: import java.awt.event.FocusAdapter;
0027: import java.awt.event.FocusEvent;
0028: import java.awt.event.HierarchyEvent;
0029: import java.awt.event.InputEvent;
0030: import java.awt.event.InputMethodEvent;
0031: import java.awt.event.InvocationEvent;
0032: import java.awt.event.KeyEvent;
0033: import java.awt.event.MouseEvent;
0034: import java.awt.event.PaintEvent;
0035: import java.awt.event.WindowEvent;
0036: import java.awt.image.BufferedImage;
0037: import java.util.Collection;
0038: import java.util.Iterator;
0039:
0040: import javax.accessibility.AccessibleAction;
0041: import javax.accessibility.AccessibleContext;
0042: import javax.accessibility.AccessibleIcon;
0043: import javax.swing.AbstractButton;
0044: import javax.swing.JComponent;
0045: import javax.swing.JInternalFrame;
0046: import javax.swing.JLabel;
0047: import javax.swing.JLayeredPane;
0048: import javax.swing.JMenu;
0049: import javax.swing.JPanel;
0050: import javax.swing.JPopupMenu;
0051: import javax.swing.JRootPane;
0052: import javax.swing.KeyStroke;
0053: import javax.swing.SwingUtilities;
0054: import javax.swing.border.Border;
0055: import javax.swing.border.CompoundBorder;
0056: import javax.swing.border.TitledBorder;
0057:
0058: import abbot.Log;
0059: import abbot.Platform;
0060: import abbot.WaitTimedOutError;
0061: import abbot.finder.BasicFinder;
0062: import abbot.finder.ComponentNotFoundException;
0063: import abbot.finder.Matcher;
0064: import abbot.finder.MultipleComponentsFoundException;
0065: import abbot.finder.matchers.JMenuItemMatcher;
0066: import abbot.finder.matchers.JMenuMatcher;
0067: import abbot.i18n.Strings;
0068: import abbot.util.AWT;
0069: import abbot.util.Bugs;
0070: import abbot.util.Condition;
0071: import abbot.util.Properties;
0072: import abbot.util.Reflector;
0073:
0074: /** Provide a higher level of abstraction for user input (A Better Robot).
0075: The Robot's operation may be affected by the following properties:<br>
0076: <pre><code>abbot.robot.auto_delay</code></pre><br>
0077: Set this to a value representing the millisecond count in between
0078: generated events. Usually just set to 100-200 if you want to slow down
0079: the playback to simulate actual user input. The default is zero delay.<br>
0080: <pre><code>abbot.robot.mode</code></pre><br>
0081: Set this to either "robot" or "awt" to designate the desired mode of event
0082: generation. "robot" uses java.awt.Robot to generate events, while "awt"
0083: stuffs events directly into the AWT event queue.<br>
0084: <pre><code>abbot.robot.event_post_delay</code></pre><br>
0085: This is the maximum number of ms it takes the system to post an AWT event
0086: in response to a Robot-generated event.
0087: <pre><code>abbot.robot.default_delay</code></pre><br>
0088: Base delay setting, acts as default value for the next two.
0089: <pre><code>abbot.robot.popup_delay</code></pre><br>
0090: Set this to the maximum time to wait for a menu to appear or be generated.
0091: <pre><code>abbot.robot.component_delay</code></pre><br>
0092: Set this to the maximum time to wait for a Component to become available.
0093: <p>
0094: The Robot class provides a generic solution for dealing with asynchronous
0095: updates to the UI with the {@link #wait(Condition,long, int)} method.
0096: This allows the testing thread to pause until a given component of data value
0097: in a component is ready.
0098: <p>
0099: NOTE: Only use event queue synchronization (e.g.
0100: {@link #invokeAndWait(Runnable)} or {@link #waitForIdle()} when a
0101: subsequent robot-level action is being applied to the results of a prior
0102: action (e.g. focus, deiconify, menu selection). Otherwise, don't
0103: introduce a mandatory delay (e.g. use {@link #invokeLater(Runnable)}).
0104: <p>
0105: NOTE: If a robot action isn't reproduced properly, you may need to
0106: introduce either additional events or extra delay. Adding enforced delay
0107: for a given platform is usually preferable to generating additional events,
0108: so always try that first, but be sure to restrict it to the platform in
0109: question.
0110: <p>
0111: NOTE: Robot actions should <b>never</b> be invoked on the event dispatch
0112: thread.
0113: */
0114:
0115: public class Robot implements AWTConstants {
0116: /** Use java.awt.Robot to generate events. */
0117: public static int EM_ROBOT = 0;
0118: /** Post events to the AWT event queue. */
0119: public static int EM_AWT = 1;
0120:
0121: private static final String LABELED_BY_PROPERTY = "labeledBy";
0122: private static final Toolkit toolkit = Toolkit.getDefaultToolkit();
0123: // Max robot delay, in ms
0124: private static final int MAX_DELAY = 60000;
0125: // TODO: verify this value for X11, etc.; ALT for w32, option for OSX
0126: public static final int MOUSELESS_MODIFIER_MASK = InputEvent.ALT_MASK;
0127: public static final String MOUSELESS_MODIFIER = AWT
0128: .getKeyModifiers(MOUSELESS_MODIFIER_MASK);
0129:
0130: /** OS X using screenMenuBar actually uses an AWT menu as the live
0131: component. The JXXX components exist, but are not effectively
0132: active.
0133: */
0134: protected static final boolean useScreenMenuBar() {
0135: // Ideally we'd install a menu and check where it ended up, since the
0136: // property is read once at startup and ignored thereafter.
0137: return Platform.isOSX()
0138: && (Boolean
0139: .getBoolean("com.apple.macos.useScreenMenuBar") || Boolean
0140: .getBoolean("apple.laf.useScreenMenuBar"));
0141: }
0142:
0143: /** Base delay setting. */
0144: public static int defaultDelay = Properties.getProperty(
0145: "abbot.robot.default_delay", 30000, 0, 60000);
0146:
0147: /** Delay before checking for idle. This allows the system a little time
0148: to put a native event onto the AWT event queue. */
0149: private static int eventPostDelay = Properties.getProperty(
0150: "abbot.robot.event_post_delay", 100, 0, 1000);
0151:
0152: protected static long IDLE_TIMEOUT = Integer.getInteger(
0153: "abbot.robot.idle_timeout", 10000).intValue();
0154:
0155: /** Delay before failing to find a popup menu that should appear. */
0156: protected static int popupDelay = Properties.getProperty(
0157: "abbot.robot.popup_delay", defaultDelay, 0, 60000);
0158:
0159: /** Delay before failing to find a component that should be visible. */
0160: public static int componentDelay = Properties.getProperty(
0161: "abbot.robot.component_delay", defaultDelay, 0, 60000);
0162:
0163: /** With decreased robot auto delay, OSX popup menus don't activate
0164: * properly. Indicate the minimum delay for proper operation (determined
0165: * experimentally).
0166: */
0167: private static final int subMenuDelay = Platform.isOSX() ? 100 : 0;
0168:
0169: /** How events are generated. */
0170: private static int eventMode = EM_ROBOT;
0171: private static boolean verified = false;
0172: private static boolean serviceMode = false;
0173:
0174: // FIXME add one per graphics device?
0175: /** The robot used to generate events. */
0176: private static java.awt.Robot robot;
0177: private static WindowTracker tracker;
0178: /** Current input state. This will either be that of the AWT event queue
0179: * or of the robot, depending on the dispatch mode.
0180: * Note that the robot state may be different from that seen by the AWT
0181: * event queue, since robot events may be as yet unprocessed.
0182: */
0183: private static InputState state;
0184:
0185: /** Suitable inter-event delay for most cases; tests have been run safely
0186: * at this value. Should definitely be less than the double-click
0187: * threshold.<p>
0188: */
0189: private static final int DEFAULT_DELAY = getPreferredRobotAutoDelay();
0190: private static final int SLEEP_INTERVAL = 10;
0191:
0192: private static int autoDelay = DEFAULT_DELAY;
0193:
0194: public static int getAutoDelay() {
0195: return autoDelay;
0196: }
0197:
0198: /** Returns a functioning instance of java.awt.Robot. If this method
0199: * returns null, it should be assumed that java.awt.Robot is unavailable
0200: * or non-functional on the current system.
0201: */
0202: public static java.awt.Robot getRobot() {
0203: initializeRobot();
0204: return serviceMode ? null : robot;
0205: }
0206:
0207: /** Return a singleton InputState object. */
0208: public static InputState getState() {
0209: initializeRobot();
0210: return state;
0211: }
0212:
0213: private static synchronized void initializeRobot() {
0214: if (state == null) {
0215: robot = createRobot();
0216: tracker = WindowTracker.getTracker();
0217: state = new InputState();
0218: }
0219: }
0220:
0221: private static java.awt.Robot createRobot() {
0222: java.awt.Robot robot = null;
0223: String mode = System.getProperty("abbot.robot.mode", "robot");
0224: autoDelay = Properties.getProperty("abbot.robot.auto_delay",
0225: autoDelay, -1, 60000);
0226: try {
0227: // Even if the robot doesn't work, we can still use it for some
0228: // things.
0229: robot = new java.awt.Robot();
0230: if (autoDelay != -1) {
0231: robot.setAutoDelay(autoDelay);
0232: } else {
0233: autoDelay = robot.getAutoDelay();
0234: }
0235: if (!verified) {
0236: verified = true;
0237: boolean skip = !"false".equals(System
0238: .getProperty("abbot.robot.verify"));
0239: if (!skip && !RobotVerifier.verify(robot)) {
0240: // robot doesn't work (w32 service mode)
0241: serviceMode = true;
0242: System.err.println("Robot non-functional, "
0243: + "falling back to AWT mode");
0244: mode = "awt";
0245: }
0246: }
0247: } catch (AWTException e) {
0248: // no robot available, send AWT events
0249: System.err.println("Falling back to AWT mode: "
0250: + e.getMessage());
0251: mode = "awt";
0252: }
0253: if (mode.equals("awt")) {
0254: eventMode = EM_AWT;
0255: }
0256: return robot;
0257: }
0258:
0259: /** Returns the current event-generation mode. */
0260: public static int getEventMode() {
0261: initializeRobot();
0262: return eventMode;
0263: }
0264:
0265: public static String getEventModeDescription() {
0266: initializeRobot();
0267: String desc = eventMode == EM_ROBOT ? "robot" : "awt";
0268: if (serviceMode)
0269: desc += " (service)";
0270: return desc;
0271: }
0272:
0273: /** Set the event-generation mode.
0274: @throws IllegalStateException if the requested mode is EM_ROBOT and
0275: java.awt.Robot is unavailable in the current environment.
0276: */
0277: public static void setEventMode(int mode) {
0278: initializeRobot();
0279: if (eventMode != mode) {
0280: if (mode == EM_ROBOT && (serviceMode || robot == null)) {
0281: String msg = Strings.get("tester.Robot.no_robot_mode");
0282: throw new IllegalStateException(msg);
0283: }
0284: eventMode = mode;
0285: }
0286: }
0287:
0288: public static int getEventPostDelay() {
0289: return eventPostDelay;
0290: }
0291:
0292: public static void setEventPostDelay(int delay) {
0293: eventPostDelay = Math.min(1000, Math.max(0, delay));
0294: }
0295:
0296: /** Allow this to be adjusted, mostly for testing. */
0297: public static void setAutoDelay(int ms) {
0298: initializeRobot();
0299: ms = Math.min(60000, Math.max(0, ms));
0300: if (eventMode == EM_ROBOT)
0301: robot.setAutoDelay(ms);
0302: autoDelay = ms;
0303: Log.debug("Auto delay set to " + ms);
0304: }
0305:
0306: /** Default constructor. */
0307: public Robot() {
0308: initializeRobot();
0309: }
0310:
0311: /**
0312: * Move the mouse to the given location, in screen coordinates.
0313: * NOTE: in robot mode, you may need to invoke this with a little jitter.
0314: * There are some conditions where a single mouse move will not
0315: * generate the necessary enter event on a component (typically a
0316: * dialog with an OK button) before a mousePress. See also click().
0317: * NOTE: does 1.4+ need jitter?
0318: */
0319: private void mouseMove(int x, int y) {
0320: if (eventMode == EM_ROBOT) {
0321: Log.debug("ROBOT: Mouse move: (" + x + "," + y + ")");
0322: robot.mouseMove(x, y);
0323: } else {
0324: // Can't stuff an AWT event for an arbitrary location
0325: }
0326: }
0327:
0328: /** Send a button press event. */
0329: public void mousePress(int buttons) {
0330: if (eventMode == EM_ROBOT) {
0331: Log.debug("ROBOT: Mouse press: "
0332: + AWT.getMouseModifiers(buttons));
0333: // OSX 1.4.1 accidentally swaps mb2 and mb3; fix it here
0334: robot.mousePress(buttons);
0335: } else {
0336: Component c = state.getMouseComponent();
0337: if (c == null) {
0338: Log.warn("No current mouse component for button press",
0339: 4);
0340: return;
0341: }
0342: Point where = state.getMouseLocation();
0343: postMousePress(c, where.x, where.y, buttons);
0344: }
0345: }
0346:
0347: /** Send a button release event for button 1. */
0348: public void mouseRelease() {
0349: mouseRelease(MouseEvent.BUTTON1_MASK);
0350: }
0351:
0352: /** Send a button release event. */
0353: public void mouseRelease(int buttons) {
0354: if (eventMode == EM_ROBOT) {
0355: Log.debug("ROBOT: Mouse release: "
0356: + AWT.getMouseModifiers(buttons));
0357: robot.mouseRelease(buttons);
0358: } else {
0359: Component source = state.isDragging() ? state
0360: .getDragSource()
0361: : (lastMousePress != null ? lastMousePress
0362: .getComponent() : state.getMouseComponent());
0363: Point where = state.getMouseLocation();
0364: if (source == null) {
0365: Log.warn("Mouse release outside of available frames");
0366: return;
0367: } else if (where == null) {
0368: if (lastMousePress == null) {
0369: Log.warn("No record of most recent mouse press");
0370: return;
0371: }
0372: where = lastMousePress.getPoint();
0373: }
0374: postMouseRelease(source, where.x, where.y, buttons);
0375: }
0376: }
0377:
0378: /** Move keyboard focus to the given component. Note that the component
0379: may not yet have focus when this method returns.
0380: */
0381: public void focus(Component comp) {
0382: focus(comp, false);
0383: }
0384:
0385: /** Use an explicit listener, since hasFocus is not always reliable. */
0386: private class FocusWatcher extends FocusAdapter {
0387: public volatile boolean focused = false;
0388:
0389: public FocusWatcher(Component c) {
0390: c.addFocusListener(this );
0391: focused = AWT.getFocusOwner() == c;
0392: }
0393:
0394: public void focusGained(FocusEvent f) {
0395: focused = true;
0396: }
0397:
0398: public void focusLost(FocusEvent f) {
0399: focused = false;
0400: }
0401: }
0402:
0403: /** Move keyboard focus to the given component. */
0404: public void focus(final Component comp, boolean wait) {
0405: Component currentOwner = AWT.getFocusOwner();
0406: if (currentOwner == comp) {
0407: return;
0408: }
0409: Log.debug("Focus change");
0410: FocusWatcher fw = new FocusWatcher(comp);
0411: // for pointer focus
0412: mouseMove(comp, comp.getWidth() / 2, comp.getHeight() / 2);
0413: waitForIdle();
0414: // Make sure the correct window is in front
0415: Window w1 = currentOwner != null ? AWT.getWindow(currentOwner)
0416: : null;
0417: Window w2 = AWT.getWindow(comp);
0418: if (w1 != w2) {
0419: activate(w2);
0420: waitForIdle();
0421: }
0422: // NOTE: while it would be nice to have a robot method instead of
0423: // requesting focus, clicking to change focus may have
0424: // side effects
0425: invokeAndWait(comp, new Runnable() {
0426: public void run() {
0427: comp.requestFocus();
0428: }
0429: });
0430: try {
0431: if (wait) {
0432: long start = System.currentTimeMillis();
0433: while (!fw.focused) {
0434: if (System.currentTimeMillis() - start > componentDelay) {
0435: String msg = Strings.get(
0436: "tester.Robot.focus_failed",
0437: new Object[] { toString(comp) });
0438: throw new ActionFailedException(msg);
0439: }
0440: sleep();
0441: }
0442: }
0443: } finally {
0444: comp.removeFocusListener(fw);
0445: }
0446: }
0447:
0448: /** Usually only needed when dealing with Applets. */
0449: protected EventQueue getEventQueue(Component c) {
0450: return c != null ? tracker.getQueue(c) : toolkit
0451: .getSystemEventQueue();
0452: }
0453:
0454: /** Post a runnable on the given component's event queue. Useful when
0455: driving multiple Applets, but is also useful to ensure an operation
0456: happens on the event dispatch thread.
0457: */
0458: public void invokeLater(Component context, Runnable action) {
0459: EventQueue queue = getEventQueue(context);
0460: queue.postEvent(new InvocationEvent(toolkit, action));
0461: }
0462:
0463: /** Post a runnable on the given component's event queue and wait for it
0464: to finish. */
0465: public void invokeAndWait(Component c, Runnable action) {
0466: invokeLater(c, action);
0467: waitForIdle();
0468: }
0469:
0470: /** @deprecated Method renamed to {@link #invokeLater(Runnable)}
0471: * @param action
0472: */
0473: public void invokeAction(Runnable action) {
0474: invokeLater(action);
0475: }
0476:
0477: /** @deprecated Method renamed to {@link #invokeLater(Component, Runnable)}
0478: * @param c
0479: * @param action
0480: */
0481: public void invokeAction(Component c, Runnable action) {
0482: invokeLater(c, action);
0483: }
0484:
0485: /** Run the given action on the event dispatch thread. This should be
0486: * used for any non-read-only methods invoked directly on a GUI
0487: * component.
0488: * NOTE: if you want to use the results of the action, use invokeAndWait
0489: * instead.
0490: */
0491: public void invokeLater(Runnable action) {
0492: invokeLater(null, action);
0493: }
0494:
0495: /** Run the given action on the event dispatch thread, but don't return
0496: until it's been run.
0497: */
0498: public void invokeAndWait(Runnable action) {
0499: invokeAndWait(null, action);
0500: }
0501:
0502: public void keyPress(int keycode) {
0503: keyPress(keycode, KeyEvent.CHAR_UNDEFINED);
0504: }
0505:
0506: /** Send a key press event. */
0507: private void keyPress(int keycode, char keyChar) {
0508: if (eventMode == EM_ROBOT) {
0509: Log.debug("ROBOT: key press " + AWT.getKeyCode(keycode));
0510: try {
0511: robot.keyPress(keycode);
0512: } catch (IllegalArgumentException e) {
0513: throw new IllegalArgumentException("invalid key code "
0514: + keycode + " (char " + keyChar + ")");
0515: }
0516: } else {
0517: int mods = state.getModifiers();
0518: if (AWT.isModifier(keycode))
0519: mods |= AWT.keyCodeToMask(keycode);
0520: postKeyEvent(KeyEvent.KEY_PRESSED, mods, keycode,
0521: KeyEvent.CHAR_UNDEFINED);
0522: // Auto-generate KEY_TYPED events, as best we can
0523: int mask = state.getModifiers();
0524: if (keyChar == KeyEvent.CHAR_UNDEFINED) {
0525: KeyStroke ks = KeyStroke.getKeyStroke(keycode, mask);
0526: keyChar = KeyStrokeMap.getChar(ks);
0527: }
0528: if (keyChar != KeyEvent.CHAR_UNDEFINED) {
0529: postKeyEvent(KeyEvent.KEY_TYPED, mask,
0530: KeyEvent.VK_UNDEFINED, keyChar);
0531: }
0532: }
0533: }
0534:
0535: /** Send a key release event. */
0536: public void keyRelease(int keycode) {
0537: if (eventMode == EM_ROBOT) {
0538: Log.debug("ROBOT: key release " + AWT.getKeyCode(keycode));
0539: robot.keyRelease(keycode);
0540: if (Bugs.hasKeyInputDelay()) {
0541: // OSX, empirical
0542: int KEY_INPUT_DELAY = 200;
0543: if (KEY_INPUT_DELAY > autoDelay) {
0544: delay(KEY_INPUT_DELAY - autoDelay);
0545: }
0546: }
0547: } else {
0548: int mods = state.getModifiers();
0549: if (AWT.isModifier(keycode))
0550: mods &= ~AWT.keyCodeToMask(keycode);
0551: postKeyEvent(KeyEvent.KEY_RELEASED, mods, keycode,
0552: KeyEvent.CHAR_UNDEFINED);
0553: }
0554: }
0555:
0556: private void postKeyEvent(int id, int modifiers, int keycode,
0557: char ch) {
0558: Component c = findFocusOwner();
0559: if (c != null) {
0560: postEvent(c, new KeyEvent(c, id,
0561: System.currentTimeMillis(), modifiers, keycode, ch));
0562: } else {
0563: Log.warn("No component has focus, key press discarded");
0564: }
0565: }
0566:
0567: /** Sleep for a little bit, measured in UI time. */
0568: public void sleep() {
0569: delay(SLEEP_INTERVAL);
0570: }
0571:
0572: /** Sleep the given duration of ms. */
0573: public void delay(int ms) {
0574: if (eventMode == EM_ROBOT) {
0575: while (ms > MAX_DELAY) {
0576: robot.delay(MAX_DELAY);
0577: ms -= MAX_DELAY;
0578: }
0579: robot.delay(ms);
0580: } else {
0581: try {
0582: Thread.sleep(ms);
0583: } catch (InterruptedException ie) {
0584: }
0585: }
0586: }
0587:
0588: private static final Runnable EMPTY_RUNNABLE = new Runnable() {
0589: public void run() {
0590: }
0591: };
0592:
0593: /** Check for a blocked event queue (symptomatic of an active w32 AWT
0594: * popup menu).
0595: * @return whether the event queue is blocked.
0596: */
0597: protected boolean queueBlocked() {
0598: return postInvocationEvent(toolkit.getSystemEventQueue(),
0599: toolkit, 200);
0600: }
0601:
0602: /** @return whether we timed out waiting for the invocation to run */
0603: protected boolean postInvocationEvent(EventQueue eq,
0604: Toolkit toolkit, long timeout) {
0605: class RobotIdleLock {
0606: }
0607: Object lock = new RobotIdleLock();
0608: synchronized (lock) {
0609: eq.postEvent(new InvocationEvent(toolkit, EMPTY_RUNNABLE,
0610: lock, true));
0611: long start = System.currentTimeMillis();
0612: try {
0613: // NOTE: on fast linux systems when showing a dialog, if we
0614: // don't provide a timeout, we're never notified, and the
0615: // test will wait forever (up through 1.5.0_05).
0616: lock.wait(timeout);
0617: return (System.currentTimeMillis() - start) >= IDLE_TIMEOUT;
0618: } catch (InterruptedException e) {
0619: Log.warn("Invocation lock interrupted");
0620: }
0621: }
0622: return false;
0623: }
0624:
0625: private void waitForIdle(EventQueue eq) {
0626: if (EventQueue.isDispatchThread())
0627: throw new IllegalThreadStateException(
0628: "Cannot call method from the event dispatcher thread");
0629:
0630: // NOTE: as of Java 1.3.1, robot.waitForIdle only waits for the
0631: // last event on the queue at the time of this invocation to be
0632: // processed. We need better than that. Make sure the given event
0633: // queue is empty when this method returns
0634:
0635: // We always post at least one idle event to allow any current event
0636: // dispatch processing to finish.
0637: long start = System.currentTimeMillis();
0638: int count = 0;
0639: do {
0640:
0641: if (postInvocationEvent(eq, toolkit, IDLE_TIMEOUT)) {
0642: Log.warn("Timed out waiting for idle"
0643: + " (posted invocation event): " + IDLE_TIMEOUT
0644: + "ms (after " + count + " events)"
0645: + " posted to " + eq, Log.FULL_STACK);
0646: break;
0647: }
0648: if (System.currentTimeMillis() - start > IDLE_TIMEOUT) {
0649: Log
0650: .warn("Timed out waiting for idle event queue after "
0651: + count + " events");
0652: break;
0653: }
0654: ++count;
0655: // Force a yield
0656: sleep();
0657:
0658: // NOTE: this does not detect invocation events (i.e. what
0659: // gets posted with EventQueue.invokeLater), so if someone
0660: // is repeatedly posting one, we might get stuck. Not too
0661: // worried, since if a Runnable keeps calling invokeLater
0662: // on itself, *nothing* else gets much chance to run, so it
0663: // seems to be a bad programming practice.
0664: } while (eq.peekEvent() != null);
0665: }
0666:
0667: /**
0668: * Wait for an idle AWT event queue. Note that this is different from
0669: * the implementation of <code>java.awt.Robot.waitForIdle()</code>, which
0670: * may have events on the queue when it returns. Do <b>NOT</b> use this
0671: * method if there are animations or other continual refreshes happening,
0672: * since in that case it may never return.<p>
0673: */
0674: public void waitForIdle() {
0675: if (eventPostDelay > autoDelay) {
0676: delay(eventPostDelay - autoDelay);
0677: }
0678: Collection queues = tracker.getEventQueues();
0679: if (queues.size() == 1) {
0680: waitForIdle(toolkit.getSystemEventQueue());
0681: } else {
0682: // FIXME this resurrects dead event queues
0683: Iterator iter = queues.iterator();
0684: while (iter.hasNext()) {
0685: EventQueue eq = (EventQueue) iter.next();
0686: waitForIdle(eq);
0687: }
0688: }
0689: }
0690:
0691: /** Sample the color at the given point on the screen. */
0692: public Color sample(int x, int y) {
0693: // Service mode always returns black when sampled
0694: if (robot != null && !serviceMode) {
0695: return robot.getPixelColor(x, y);
0696: }
0697: String msg = Strings.get("tester.Robot.no_sample");
0698: throw new UnsupportedOperationException(msg);
0699: }
0700:
0701: /** Sample the color at the given point on the component. */
0702: public Color sample(Component c, int x, int y) {
0703: return sample(c, new ComponentLocation(new Point(x, y)));
0704: }
0705:
0706: /** Sample the color at the given location on the component. */
0707: public Color sample(Component c, ComponentLocation loc) {
0708: Point p = loc.getPoint(c);
0709: Point where = AWT.getLocationOnScreen(c);
0710: where.translate(p.x, p.y);
0711: return sample(where.x, where.y);
0712: }
0713:
0714: /** Capture the contents of the given rectangle. */
0715: /* NOTE: Text components (and maybe others with a custom cursor) will
0716: * capture the cursor. May want to move the cursor out of the component
0717: * bounds, although this might cause issues where the component is
0718: * responding visually to mouse movement.
0719: * Is this an OSX bug?
0720: */
0721: public BufferedImage capture(final Rectangle bounds) {
0722: Log.debug("Screen capture " + bounds);
0723: BufferedImage image = null;
0724: if (robot != null) {
0725: image = robot.createScreenCapture(bounds);
0726: }
0727: return image;
0728: }
0729:
0730: /** Capture the contents of the given component, sans any border or
0731: * insets. This should only be used on components that do not use a LAF
0732: * UI, or the results will not be consistent across platforms.
0733: */
0734: public BufferedImage capture(Component comp) {
0735: return capture(comp, true);
0736: }
0737:
0738: /** Capture the contents of the given component, optionally including the
0739: * border and/or insets. This should only be used on components that do
0740: * not use a LAF UI, or the results will not be consistent across
0741: * platforms.
0742: */
0743: public BufferedImage capture(Component comp, boolean ignoreBorder) {
0744: Rectangle bounds = new Rectangle(comp.getSize());
0745: Point loc = AWT.getLocationOnScreen(comp);
0746: bounds.setLocation(loc.x, loc.y);
0747: Log.debug("Component bounds " + bounds);
0748: if (ignoreBorder) {
0749: Insets insets = ((Container) comp).getInsets();
0750: if (insets != null) {
0751: bounds.x += insets.left;
0752: bounds.y += insets.top;
0753: bounds.width -= insets.left + insets.right;
0754: bounds.height -= insets.top + insets.bottom;
0755: Log.debug("Component insets " + insets);
0756: }
0757: }
0758: return capture(bounds);
0759: }
0760:
0761: // Bug workaround support
0762: protected void jitter(Component comp, int x, int y) {
0763: Log.debug("jitter");
0764: mouseMove(comp, (x > 0 ? x - 1 : x + 1), y);
0765: }
0766:
0767: // Bug workaround support
0768: protected void jitter(int x, int y) {
0769: mouseMove((x > 0 ? x - 1 : x + 1), y);
0770: }
0771:
0772: /** Move the pointer to the center of the given component. */
0773: public void mouseMove(Component comp) {
0774: mouseMove(comp, comp.getWidth() / 2, comp.getHeight() / 2);
0775: }
0776:
0777: /** Wait the given number of ms for the component to be showing and
0778: ready. Returns false if the operation times out. */
0779: private boolean waitForComponent(Component c, long delay) {
0780: if (!isReadyForInput(c)) {
0781: Log.debug("Waiting for component to show: " + toString(c));
0782: long start = System.currentTimeMillis();
0783: while (!isReadyForInput(c)) {
0784: if (c instanceof JPopupMenu) {
0785: // wiggle the mouse over the parent menu item to
0786: // ensure the submenu shows
0787: Component invoker = ((JPopupMenu) c).getInvoker();
0788: if (invoker instanceof JMenu) {
0789: jitter(invoker, invoker.getWidth() / 2, invoker
0790: .getHeight() / 2);
0791: }
0792: }
0793: if (System.currentTimeMillis() - start > delay) {
0794: Log.warn("Component " + toString(c) + " ("
0795: + Integer.toHexString(c.hashCode()) + ")"
0796: + " not ready after " + delay + "ms: "
0797: + "showing=" + c.isShowing()
0798: + " win ready="
0799: + tracker.isWindowReady(AWT.getWindow(c)));
0800: return false;
0801: }
0802: sleep();
0803: }
0804: }
0805: return true;
0806: }
0807:
0808: /** Move the pointer to the given coordinates relative to the given
0809: * component.
0810: */
0811: public void mouseMove(Component comp, int x, int y) {
0812: if (!waitForComponent(comp, componentDelay)) {
0813: String msg = "Can't obtain position of component "
0814: + toString(comp);
0815: throw new ComponentNotShowingException(msg);
0816: }
0817: if (eventMode == EM_ROBOT) {
0818: try {
0819: Point point = AWT.getLocationOnScreen(comp);
0820: if (point != null) {
0821: point.translate(x, y);
0822: mouseMove(point.x, point.y);
0823: }
0824: } catch (java.awt.IllegalComponentStateException e) {
0825: }
0826: } else {
0827: Component eventSource = comp;
0828: int id = MouseEvent.MOUSE_MOVED;
0829: boolean outside = false;
0830:
0831: // When dragging, the event source is always the target of the
0832: // original mouse press.
0833: if (state.isDragging()) {
0834: id = MouseEvent.MOUSE_DRAGGED;
0835: eventSource = state.getDragSource();
0836: } else {
0837: Point pt = new Point(x, y);
0838: eventSource = comp = AWT.retargetMouseEvent(comp, id,
0839: pt);
0840: x = pt.x;
0841: y = pt.y;
0842: outside = x < 0 || y < 0 || x >= comp.getWidth()
0843: || y >= comp.getHeight();
0844: }
0845:
0846: Component current = state.getMouseComponent();
0847: if (current != comp) {
0848: if (outside && current != null) {
0849: Point pt = SwingUtilities.convertPoint(comp, x, y,
0850: current);
0851: postMouseMotion(current, MouseEvent.MOUSE_EXITED,
0852: pt);
0853: return;
0854: }
0855: postMouseMotion(comp, MouseEvent.MOUSE_ENTERED,
0856: new Point(x, y));
0857: }
0858: Point pt = new Point(x, y);
0859: if (id == MouseEvent.MOUSE_DRAGGED) {
0860: // Drag coordinates are relative to drag source component
0861: pt = SwingUtilities.convertPoint(comp, pt, eventSource);
0862: }
0863: postMouseMotion(eventSource, id, pt);
0864: // Add an exit event if warranted
0865: if (outside) {
0866: postMouseMotion(comp, MouseEvent.MOUSE_EXITED,
0867: new Point(x, y));
0868: }
0869: }
0870: }
0871:
0872: /** Move the mouse appropriately to get from the source to the
0873: destination. Enter/exit events will be generated where appropriate.
0874: */
0875: public void dragOver(Component dst, int x, int y) {
0876: mouseMove(dst, x - 4, y);
0877: mouseMove(dst, x, y);
0878: }
0879:
0880: /** Begin a drag operation using button 1.<p>
0881: This method is tuned for native drag/drop operations, so if you get
0882: odd behavior, you might try using a simple
0883: {@link #mousePress(Component,int,int)} instead.
0884: */
0885: public void drag(Component src, int sx, int sy) {
0886: drag(src, sx, sy, InputEvent.BUTTON1_MASK);
0887: }
0888:
0889: /** Begin a drag operation using the given button mask.<p>
0890: This method is tuned for native drag/drop operations, so if you get
0891: odd behavior, you might try using a simple
0892: {@link #mousePress(Component,int,int,int)} instead.
0893: */
0894: // TODO: maybe auto-switch to robot mode if available?
0895: public void drag(Component src, int sx, int sy, int buttons) {
0896: if (Bugs.dragDropRequiresNativeEvents()
0897: && eventMode != EM_ROBOT
0898: && !Boolean.getBoolean("abbot.ignore_drag_error")) {
0899: String msg = Strings.get("abbot.Robot.no_drag_available");
0900: if (serviceMode) {
0901: // If we start a native drag in this mode, it'll pretty much
0902: // lock up the system, apparently with the native AWT libs
0903: // starting a thread invisible to the VM that chews up all CPU
0904: // time.
0905: throw new ActionFailedException(msg);
0906: }
0907: Log.warn(msg);
0908: }
0909:
0910: Log.debug("drag");
0911: // Some platforms require a pause between mouse down and mouse motion
0912: int DRAG_DELAY = Properties.getProperty(
0913: "abbot.robot.drag_delay", Platform.isX11()
0914: || Platform.isOSX() ? 100 : 0, 0, 60000);
0915: mousePress(src, sx, sy, buttons);
0916: if (DRAG_DELAY > autoDelay) {
0917: delay(DRAG_DELAY);
0918: }
0919: if (Platform.isWindows() || Platform.isMacintosh()) {
0920: int dx = sx + AWTConstants.DRAG_THRESHOLD < src.getWidth() ? AWTConstants.DRAG_THRESHOLD
0921: : 0;
0922: int dy = sy + AWTConstants.DRAG_THRESHOLD < src.getHeight() ? AWTConstants.DRAG_THRESHOLD
0923: : 0;
0924: if (dx == 0 && dy == 0)
0925: dx = AWTConstants.DRAG_THRESHOLD;
0926: mouseMove(src, sx + dx / 4, sy + dy / 4);
0927: mouseMove(src, sx + dx / 2, sy + dy / 2);
0928: mouseMove(src, sx + dx, sy + dy);
0929: mouseMove(src, sx + dx + 1, sy + dy);
0930: } else {
0931: mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD / 2, sy
0932: + AWTConstants.DRAG_THRESHOLD / 2);
0933: mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD, sy
0934: + AWTConstants.DRAG_THRESHOLD);
0935: mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD / 2, sy
0936: + AWTConstants.DRAG_THRESHOLD / 2);
0937: mouseMove(src, sx, sy);
0938: }
0939: Log.debug("drag started");
0940: }
0941:
0942: /** End a drag operation, releasing the mouse button over the given target
0943: location.<p>
0944: This method is tuned for native drag/drop operations, so if you get
0945: odd behavior, you might try using a simple
0946: {@link #mouseMove(Component,int,int)}, {@link #mouseRelease()}
0947: instead.
0948: */
0949: public void drop(Component target, int x, int y) {
0950: Log.debug("drop");
0951: // Delay between final move and drop to ensure drop ends.
0952: int DROP_DELAY = Properties.getProperty(
0953: "abbot.robot.drop_delay", Platform.isWindows() ? 200
0954: : 0, 0, 60000);
0955:
0956: dragOver(target, x, y);
0957: long start = System.currentTimeMillis();
0958: while (!state.isDragging()) {
0959: if (System.currentTimeMillis() - start > eventPostDelay * 4) {
0960: String msg = Strings.get("Robot.no_current_drag");
0961: throw new ActionFailedException(msg);
0962: }
0963: sleep();
0964: }
0965: if (DROP_DELAY > autoDelay)
0966: delay(DROP_DELAY - autoDelay);
0967:
0968: mouseRelease(state.getButtons());
0969: Log.debug("dropped");
0970: }
0971:
0972: /** Generate a mouse enter/exit/move/drag for the destination component.
0973: * NOTE: The VM automatically usually generates exit events; need a test
0974: * to define the behavior, though.
0975: */
0976: private void postMouseMotion(Component dst, int id, Point to) {
0977: // The VM auto-generates exit events as needed (1.3, 1.4)
0978: if (id != MouseEvent.MOUSE_DRAGGED)
0979: dst = AWT.retargetMouseEvent(dst, id, to);
0980: // Avoid multiple moves to the same location
0981: if (state.getMouseComponent() != dst
0982: || !to.equals(state.getMouseLocation())) {
0983: postEvent(dst, new MouseEvent(dst, id, System
0984: .currentTimeMillis(), state.getModifiers(), to.x,
0985: to.y, state.getClickCount(), false));
0986: }
0987: }
0988:
0989: /** Type the given keycode with no modifiers. */
0990: public void key(int keycode) {
0991: key(keycode, 0);
0992: }
0993:
0994: /** Press or release the appropriate modifiers corresponding to the given
0995: mask.
0996: */
0997: public void setModifiers(int modifiers, boolean press) {
0998: boolean altGraph = (modifiers & InputEvent.ALT_GRAPH_MASK) != 0;
0999: boolean shift = (modifiers & InputEvent.SHIFT_MASK) != 0;
1000: boolean alt = (modifiers & InputEvent.ALT_MASK) != 0;
1001: boolean ctrl = (modifiers & InputEvent.CTRL_MASK) != 0;
1002: boolean meta = (modifiers & InputEvent.META_MASK) != 0;
1003: if (press) {
1004: if (altGraph)
1005: keyPress(KeyEvent.VK_ALT_GRAPH);
1006: if (alt)
1007: keyPress(KeyEvent.VK_ALT);
1008: if (shift)
1009: keyPress(KeyEvent.VK_SHIFT);
1010: if (ctrl)
1011: keyPress(KeyEvent.VK_CONTROL);
1012: if (meta)
1013: keyPress(KeyEvent.VK_META);
1014: } else {
1015: // For consistency, release in the reverse order of press
1016: if (meta)
1017: keyRelease(KeyEvent.VK_META);
1018: if (ctrl)
1019: keyRelease(KeyEvent.VK_CONTROL);
1020: if (shift)
1021: keyRelease(KeyEvent.VK_SHIFT);
1022: if (alt)
1023: keyRelease(KeyEvent.VK_ALT);
1024: if (altGraph)
1025: keyRelease(KeyEvent.VK_ALT_GRAPH);
1026: }
1027: }
1028:
1029: /** Type the given keycode with the given modifiers. Modifiers is a mask
1030: * from the available InputEvent masks.
1031: */
1032: public void key(int keycode, int modifiers) {
1033: key(KeyEvent.CHAR_UNDEFINED, keycode, modifiers);
1034: }
1035:
1036: private void key(char ch, int keycode, int modifiers) {
1037: Log.debug("key keycode=" + AWT.getKeyCode(keycode) + " mod="
1038: + AWT.getKeyModifiers(modifiers));
1039: boolean isModifier = true;
1040: switch (keycode) {
1041: case KeyEvent.VK_ALT_GRAPH:
1042: modifiers |= InputEvent.ALT_GRAPH_MASK;
1043: break;
1044: case KeyEvent.VK_ALT:
1045: modifiers |= InputEvent.ALT_MASK;
1046: break;
1047: case KeyEvent.VK_SHIFT:
1048: modifiers |= InputEvent.SHIFT_MASK;
1049: break;
1050: case KeyEvent.VK_CONTROL:
1051: modifiers |= InputEvent.CTRL_MASK;
1052: break;
1053: case KeyEvent.VK_META:
1054: modifiers |= InputEvent.META_MASK;
1055: break;
1056: default:
1057: isModifier = false;
1058: break;
1059: }
1060: setModifiers(modifiers, true);
1061: if (!isModifier) {
1062: keyPress(keycode, ch);
1063: keyRelease(keycode);
1064: }
1065: setModifiers(modifiers, false);
1066: if (Bugs.hasKeyStrokeGenerationBug())
1067: delay(100);
1068: }
1069:
1070: /**
1071: * Type the given character. Note that this sends the key to whatever
1072: * component currently has the focus.
1073: */
1074: // FIXME should this be renamed to "key"?
1075: public void keyStroke(char ch) {
1076: KeyStroke ks = KeyStrokeMap.getKeyStroke(ch);
1077: if (ks == null) {
1078: // If no mapping is available, we omit press/release events and
1079: // only generate a KEY_TYPED event
1080: Log.debug("No key mapping for '" + ch + "'");
1081: Component focus = findFocusOwner();
1082: if (focus == null) {
1083: Log.warn("No component has focus, keystroke discarded",
1084: Log.FULL_STACK);
1085: return;
1086: }
1087: KeyEvent ke = new KeyEvent(focus, KeyEvent.KEY_TYPED,
1088: System.currentTimeMillis(), 0,
1089: KeyEvent.VK_UNDEFINED, ch);
1090: // Allow any pending robot events to complete; otherwise we
1091: // might stuff the typed event before previous robot-generated
1092: // events are posted.
1093: if (eventMode == EM_ROBOT)
1094: waitForIdle();
1095: postEvent(focus, ke);
1096: } else {
1097: int keycode = ks.getKeyCode();
1098: int mod = ks.getModifiers();
1099: Log.debug("Char '" + ch + "' generated by keycode="
1100: + keycode + " mod=" + mod);
1101: key(ch, keycode, mod);
1102: }
1103: }
1104:
1105: /** Type the given string. */
1106: public void keyString(String str) {
1107: char[] ch = str.toCharArray();
1108: for (int i = 0; i < ch.length; i++) {
1109: keyStroke(ch[i]);
1110: }
1111: }
1112:
1113: public void mousePress(Component comp) {
1114: mousePress(comp, InputEvent.BUTTON1_MASK);
1115: }
1116:
1117: public void mousePress(Component comp, int mask) {
1118: mousePress(comp, comp.getWidth() / 2, comp.getHeight() / 2,
1119: mask);
1120: }
1121:
1122: public void mousePress(Component comp, int x, int y) {
1123: mousePress(comp, x, y, InputEvent.BUTTON1_MASK);
1124: }
1125:
1126: /** Mouse down in the given part of the component. All other mousePress
1127: methods must eventually invoke this one.
1128: */
1129: public void mousePress(Component comp, int x, int y, int mask) {
1130: if (eventMode == EM_ROBOT && Bugs.hasRobotMotionBug()) {
1131: jitter(comp, x, y);
1132: }
1133: mouseMove(comp, x, y);
1134: if (eventMode == EM_ROBOT)
1135: mousePress(mask);
1136: else {
1137: postMousePress(comp, x, y, mask);
1138: }
1139: }
1140:
1141: /** Post a mouse press event to the AWT event queue for the given
1142: component.
1143: */
1144: private void postMousePress(Component comp, int x, int y, int mask) {
1145: long when = lastMousePress != null ? lastMousePress.getWhen()
1146: : 0;
1147: long now = System.currentTimeMillis();
1148: int count = 1;
1149: Point where = new Point(x, y);
1150: comp = AWT.retargetMouseEvent(comp, MouseEvent.MOUSE_PRESSED,
1151: where);
1152: if (countingClicks && comp == lastMousePress.getComponent()) {
1153: long delta = now - when;
1154: if (delta < AWTConstants.MULTI_CLICK_INTERVAL) {
1155: count = state.getClickCount() + 1;
1156: }
1157: }
1158: postEvent(comp, new MouseEvent(comp, MouseEvent.MOUSE_PRESSED,
1159: now, state.getKeyModifiers() | mask, where.x, where.y,
1160: count, AWTConstants.POPUP_ON_PRESS
1161: && (mask & AWTConstants.POPUP_MASK) != 0));
1162: }
1163:
1164: /** Post a mouse release event to the AWT event queue for the given
1165: component.
1166: */
1167: private void postMouseRelease(Component c, int x, int y, int mask) {
1168: long now = System.currentTimeMillis();
1169: int count = state.getClickCount();
1170: Point where = new Point(x, y);
1171: c = AWT.retargetMouseEvent(c, MouseEvent.MOUSE_PRESSED, where);
1172: postEvent(c, new MouseEvent(c, MouseEvent.MOUSE_RELEASED, now,
1173: state.getKeyModifiers() | mask, where.x, where.y,
1174: count, !AWTConstants.POPUP_ON_PRESS
1175: && (mask & AWTConstants.POPUP_MASK) != 0));
1176: }
1177:
1178: /** Click in the center of the given component. */
1179: final public void click(Component comp) {
1180: click(comp, comp.getWidth() / 2, comp.getHeight() / 2);
1181: }
1182:
1183: /** Click in the center of the given component, specifying which button. */
1184: final public void click(Component comp, int mask) {
1185: click(comp, comp.getWidth() / 2, comp.getHeight() / 2, mask);
1186: }
1187:
1188: /** Click in the component at the given location. */
1189: final public void click(Component comp, int x, int y) {
1190: click(comp, x, y, InputEvent.BUTTON1_MASK);
1191: }
1192:
1193: /** Click in the component at the given location with the given button. */
1194: final public void click(Component comp, int x, int y, int mask) {
1195: click(comp, x, y, mask, 1);
1196: }
1197:
1198: /** Click in the given part of the component. All other click methods
1199: * must eventually invoke this one. This method sometimes needs to be
1200: * redefined (i.e. JComponent to scroll before clicking).
1201: */
1202: public void click(Component comp, int x, int y, int mask, int count) {
1203: Log.debug("Click at (" + x + "," + y + ") on " + toString(comp)
1204: + (count > 1 ? (" count=" + count) : ""));
1205: int keyModifiers = mask & ~AWTConstants.BUTTON_MASK;
1206: mask &= AWTConstants.BUTTON_MASK;
1207: setModifiers(keyModifiers, true);
1208: // Adjust the auto-delay to ensure we actually get a multiple click
1209: // In general clicks have to be less than 200ms apart, although the
1210: // actual setting is not readable by java that I'm aware of.
1211: int oldDelay = getAutoDelay();
1212: if (count > 1 && oldDelay * 2 > 200) {
1213: setAutoDelay(0);
1214: }
1215: long last = System.currentTimeMillis();
1216: mousePress(comp, x, y, mask);
1217: while (count-- > 1) {
1218: mouseRelease(mask);
1219: long delta = System.currentTimeMillis() - last;
1220: if (delta > AWTConstants.MULTI_CLICK_INTERVAL)
1221: Log.warn("Unexpected delay in multi-click: " + delta);
1222: last = System.currentTimeMillis();
1223: mousePress(mask);
1224: }
1225: setAutoDelay(oldDelay);
1226: mouseRelease(mask);
1227: setModifiers(keyModifiers, false);
1228: }
1229:
1230: /** @deprecated Renamed to {@link #selectAWTMenuItem(Frame,String)}. */
1231: public void selectAWTMenuItemByLabel(Frame frame, String path) {
1232: selectAWTMenuItem(frame, path);
1233: }
1234:
1235: /** Select the given menu item from the given Frame. The given String may
1236: be either a label or path of labels, but must uniquely identify the
1237: menu item. For example, "Copy" would be valid if there is only one
1238: instance of that menu label under the MenuBar, otherwise you would
1239: need to specify "Edit|Copy" to ensure the proper selection.
1240: Note that this method doesn't require referencing the MenuComponent
1241: directly as a parameter.
1242: */
1243: public void selectAWTMenuItem(Frame frame, String path) {
1244: MenuBar mb = frame.getMenuBar();
1245: if (mb == null) {
1246: String msg = Strings.get("tester.Robot.no_menu_bar",
1247: new Object[] { toString(frame) });
1248: throw new ActionFailedException(msg);
1249: }
1250: MenuItem[] items = AWT.findAWTMenuItems(frame, path);
1251: if (items.length == 0) {
1252: String msg = Strings.get("tester.Robot.no_menu_item",
1253: new Object[] { path, toString(frame) });
1254: throw new ActionFailedException(msg);
1255: }
1256: if (items.length > 1) {
1257: String msg = Strings
1258: .get("tester.Robot.multiple_menu_items");
1259: throw new ActionFailedException(msg);
1260: }
1261: selectAWTMenuItem(items[0]);
1262: }
1263:
1264: /** @deprecated Renamed to
1265: {@link #selectAWTPopupMenuItem(Component,String)}.
1266: */
1267: public void selectAWTPopupMenuItemByLabel(Component invoker,
1268: String path) {
1269: selectAWTPopupMenuItem(invoker, path);
1270: }
1271:
1272: /** Select the given menu item from a PopupMenu on the given Component.
1273: The given String may be either a label or path of labels, but must
1274: uniquely identify the menu item. For example, "Copy" would be valid
1275: if there is only one instance of that menu label under the MenuBar,
1276: otherwise you would need to specify "Edit|Copy" to ensure the proper
1277: selection. If there are more than one PopupMenu registerd on the
1278: invoking component, you will need to prefix the PopupMenu name as
1279: well, e.g. "popup0|Edit|Copy". */
1280: public void selectAWTPopupMenuItem(Component invoker, String path) {
1281: try {
1282: PopupMenu[] popups = AWT.getPopupMenus(invoker);
1283: if (popups.length == 0)
1284: throw new ActionFailedException(Strings
1285: .get("tester.Robot.awt_popup_missing"));
1286:
1287: MenuItem[] items = AWT.findAWTPopupMenuItems(invoker, path);
1288: if (items.length == 1) {
1289: selectAWTPopupMenuItem(items[0]);
1290: return;
1291: } else if (items.length == 0) {
1292: String msg = Strings.get(
1293: "tester.Robot.no_popup_menu_item",
1294: new Object[] { path, toString(invoker) });
1295: throw new ActionFailedException(msg);
1296: }
1297: String msg = Strings.get(
1298: "tester.Robot.multiple_menu_items",
1299: new Object[] { path });
1300: throw new ActionFailedException(msg);
1301: } finally {
1302: AWT.dismissAWTPopup();
1303: }
1304: }
1305:
1306: protected void fireAccessibleAction(Component context,
1307: final AccessibleAction action, String name) {
1308: if (action != null && action.getAccessibleActionCount() > 0) {
1309: invokeLater(context, new Runnable() {
1310: public void run() {
1311: action.doAccessibleAction(0);
1312: }
1313: });
1314: } else {
1315: String msg = Strings.get(
1316: "tester.Robot.no_accessible_action",
1317: new String[] { name });
1318: throw new ActionFailedException(msg);
1319: }
1320: }
1321:
1322: private Component getContext(MenuComponent item) {
1323: while (!(item.getParent() instanceof Component)
1324: && item.getParent() instanceof MenuComponent)
1325: item = (MenuComponent) item.getParent();
1326: return (Component) item.getParent();
1327: }
1328:
1329: /** Select an AWT menu item. */
1330: public void selectAWTMenuItem(MenuComponent item) {
1331: // Can't do this through coordinates because MenuComponents don't
1332: // store any of that information
1333: fireAccessibleAction(getContext(item), item
1334: .getAccessibleContext().getAccessibleAction(),
1335: toString(item));
1336: if (queueBlocked())
1337: key(KeyEvent.VK_ESCAPE);
1338: }
1339:
1340: /** Select an AWT popup menu item. */
1341: public void selectAWTPopupMenuItem(MenuComponent item) {
1342: // Can't do this through coordinates because MenuComponents don't
1343: // store any of that information
1344: fireAccessibleAction(getContext(item), item
1345: .getAccessibleContext().getAccessibleAction(),
1346: toString(item));
1347: if (queueBlocked())
1348: key(KeyEvent.VK_ESCAPE);
1349: }
1350:
1351: /** Is the given component ready for robot input? */
1352: protected boolean isReadyForInput(Component c) {
1353: if (eventMode == EM_AWT)
1354: return c.isShowing();
1355: Window w = AWT.getWindow(c);
1356: if (w == null) {
1357: throw new ActionFailedException("Component '" + toString(c)
1358: + "' has no Window ancestor");
1359: }
1360: return c.isShowing() && tracker.isWindowReady(w);
1361: }
1362:
1363: private boolean isOnJMenuBar(Component item) {
1364: if (item instanceof javax.swing.JMenuBar)
1365: return true;
1366: Component parent = item instanceof JPopupMenu ? ((JPopupMenu) item)
1367: .getInvoker()
1368: : item.getParent();
1369: return parent != null && isOnJMenuBar(parent);
1370: }
1371:
1372: /** Find and select the given menu item, by path. */
1373: public void selectMenuItem(Component sameWindow, String path) {
1374: try {
1375: Window window = AWT.getWindow(sameWindow);
1376:
1377: java.util.List selectionPath = JMenuItemMatcher
1378: .splitMenuPath(path);
1379:
1380: // For each path entry select in turn to make sure we trigger
1381: // any lazy loading
1382: //
1383:
1384: int i = selectionPath.size();
1385: Container context = window;
1386: for (int j = 0; j < i; j++) {
1387:
1388: // If we are at the last item then we are looking
1389: // for a menu item, otherwise we are looking for
1390: // a menu instead
1391: //
1392:
1393: String nextPath = (String) selectionPath.get(j);
1394: Matcher m = (j == i - 1) ? new JMenuItemMatcher(
1395: nextPath) : new JMenuMatcher(nextPath);
1396: // Don't care about the hierarchy on this one, since there'll only
1397: // ever be one popup active at a time.
1398: Component item = BasicFinder.getDefault().find(context,
1399: m);
1400: selectMenuItem(item);
1401: waitForIdle();
1402: context = (Container) item;
1403: }
1404: } catch (ComponentNotFoundException e) {
1405: throw new ComponentMissingException(
1406: "Can't find menu item '" + path + "'");
1407: } catch (MultipleComponentsFoundException e) {
1408: throw new ActionFailedException(e.getMessage());
1409: }
1410: }
1411:
1412: /** Find and select the given menu item. */
1413: public void selectMenuItem(Component item) {
1414: Log.debug("Selecting menu item " + toString(item));
1415: Component parent = item.getParent();
1416: JPopupMenu parentPopup = null;
1417: if (parent instanceof JPopupMenu) {
1418: parentPopup = (JPopupMenu) parent;
1419: parent = ((JPopupMenu) parent).getInvoker();
1420: }
1421: boolean inMenuBar = parent instanceof javax.swing.JMenuBar;
1422: boolean isMenu = item instanceof javax.swing.JMenu;
1423:
1424: if (isOnJMenuBar(item) && useScreenMenuBar()) {
1425: // Use accessibility action instead
1426: fireAccessibleAction(item, item.getAccessibleContext()
1427: .getAccessibleAction(), toString(item));
1428: return;
1429: }
1430:
1431: // If our parent is a menu, activate it first, if it's not already.
1432: if (parent instanceof javax.swing.JMenuItem) {
1433: if (parentPopup == null || !parentPopup.isShowing()) {
1434: Log.debug("Opening parent menu " + toString(parent));
1435: selectMenuItem(parent);
1436: }
1437: }
1438:
1439: // Make sure the appropriate window is in front
1440: if (inMenuBar) {
1441: final Window win = AWT.getWindow(parent);
1442: if (win != null) {
1443: // Make sure the window is in front, or its menus may be
1444: // obscured by another window.
1445: invokeAndWait(win, new Runnable() {
1446: public void run() {
1447: win.toFront();
1448: }
1449: });
1450: mouseMove(win);
1451: }
1452: }
1453:
1454: // Activate the item
1455: if (isMenu && !inMenuBar) {
1456: // Submenus only require a mouse-over to activate, but do
1457: // a click to be certain
1458: if (subMenuDelay > autoDelay) {
1459: delay(subMenuDelay - autoDelay);
1460: }
1461: }
1462: // Top-level menus and menu items *must* be clicked on
1463: Log.debug("Activating menu item " + toString(item));
1464: if (!item.isEnabled()) {
1465: throw new ActionFailedException("Menu item "
1466: + toString(item) + " is disabled");
1467: }
1468: click(item);
1469: waitForIdle();
1470:
1471: // If this item is a menu, make sure its popup is showing before we
1472: // return
1473: if (isMenu) {
1474: JPopupMenu popup = ((javax.swing.JMenu) item)
1475: .getPopupMenu();
1476: if (!waitForComponent(popup, popupDelay)) {
1477: String msg = "Clicking on '"
1478: + ((javax.swing.JMenu) item).getText()
1479: + "' never produced a popup menu";
1480: throw new ComponentMissingException(msg);
1481: }
1482: // for OSX 1.4.1; isShowing set before popup is available
1483: if (subMenuDelay > autoDelay) {
1484: delay(subMenuDelay - autoDelay);
1485: }
1486: }
1487: }
1488:
1489: public void selectPopupMenuItem(Component invoker,
1490: ComponentLocation loc, String path) {
1491: Point where = loc.getPoint(invoker);
1492:
1493: if (where.x == -1)
1494: where.x = invoker.getWidth() / 2;
1495: if (where.y == -1)
1496: where.y = invoker.getHeight() / 2;
1497: Component popup = showPopupMenu(invoker, where.x, where.y);
1498: try {
1499:
1500: java.util.List selectionPath = JMenuItemMatcher
1501: .splitMenuPath(path);
1502:
1503: // For each path entry select in turn to make sure we trigger
1504: // any lazy loading
1505: //
1506:
1507: Container context = (Container) popup;
1508: int i = selectionPath.size();
1509: for (int j = 0; j < i; j++) {
1510: Matcher m = new JMenuItemMatcher((String) selectionPath
1511: .get(j));
1512: // Don't care about the hierarchy on this one, since there'll only
1513: // ever be one popup active at a time.
1514: Component item = BasicFinder.getDefault().find(context,
1515: m);
1516: selectMenuItem(item);
1517: waitForIdle();
1518: context = (Container) item;
1519: }
1520:
1521: } catch (ComponentNotFoundException e) {
1522: throw new ComponentMissingException(
1523: "Can't find menu item '" + path + "'");
1524: } catch (MultipleComponentsFoundException e) {
1525: throw new ActionFailedException(e.getMessage());
1526: }
1527: }
1528:
1529: /** Attempt to display a popup menu at center of the component. */
1530: public Component showPopupMenu(Component invoker) {
1531: return showPopupMenu(invoker, invoker.getWidth() / 2, invoker
1532: .getHeight() / 2);
1533: }
1534:
1535: /** Attempt to display a popup menu at the given coordinates. */
1536: public Component showPopupMenu(Component invoker, int x, int y) {
1537: String where = " at (" + x + "," + y + ")";
1538: Log.debug("Invoking popup " + where);
1539: click(invoker, x, y, AWTConstants.POPUP_MASK);
1540:
1541: Component popup = AWT.findActivePopupMenu();
1542: if (popup == null) {
1543: String msg = "No popup responded to "
1544: + AWTConstants.POPUP_MODIFIER + where + " on "
1545: + toString(invoker);
1546: throw new ComponentMissingException(msg);
1547: }
1548: int POPUP_DELAY = 10000;
1549: long start = System.currentTimeMillis();
1550: while (!isReadyForInput(SwingUtilities.getWindowAncestor(popup))
1551: && System.currentTimeMillis() - start > POPUP_DELAY) {
1552: sleep();
1553: }
1554: return popup;
1555: }
1556:
1557: /** Activate the given window. */
1558: public void activate(final Window win) {
1559: // ACTIVATE means window gets keyboard focus.
1560: invokeAndWait(win, new Runnable() {
1561: // FIXME figure out why two are sometimes needed
1562: public void run() {
1563: win.toFront();
1564: win.toFront();
1565: }
1566: });
1567: // For pointer-focus systems
1568: mouseMove(win);
1569: }
1570:
1571: protected Point getCloseLocation(Container c) {
1572: Dimension size = c.getSize();
1573: Insets insets = c.getInsets();
1574: if (Platform.isOSX()) {
1575: return new Point(insets.left + 15, insets.top / 2);
1576: }
1577: return new Point(size.width - insets.right - 10, insets.top / 2);
1578: }
1579:
1580: /** Invoke the window close operation. */
1581: public void close(Window w) {
1582: if (w.isShowing()) {
1583: // Move to a corner and "pretend" to use the window manager
1584: // control
1585: try {
1586: Point p = getCloseLocation(w);
1587: mouseMove(w, p.x, p.y);
1588: } catch (Exception e) {
1589: // ignore
1590: }
1591: WindowEvent ev = new WindowEvent(w,
1592: WindowEvent.WINDOW_CLOSING);
1593: // If the window contains an applet, send the event on the
1594: // applet's queue instead to ensure a shutdown from the
1595: // applet's context (assists AppletViewer cleanup).
1596: Component applet = AWT.findAppletDescendent(w);
1597: EventQueue eq = tracker.getQueue(applet != null ? applet
1598: : w);
1599: eq.postEvent(ev);
1600: }
1601: }
1602:
1603: /** Return where the mouse usually grabs to move a window. Center of the
1604: * top of the frame is usually a good choice.
1605: */
1606: protected Point getMoveLocation(Container c) {
1607: Dimension size = c.getSize();
1608: Insets insets = c.getInsets();
1609: return new Point(size.width / 2, insets.top / 2);
1610: }
1611:
1612: /** Move the given Frame/Dialog to the requested location. */
1613: public void move(Container comp, int newx, int newy) {
1614: Point loc = AWT.getLocationOnScreen(comp);
1615: moveBy(comp, newx - loc.x, newy - loc.y);
1616: }
1617:
1618: /** Move the given Window by the given amount. */
1619: public void moveBy(final Container comp, final int dx, final int dy) {
1620: final Point loc = AWT.getLocationOnScreen(comp);
1621: boolean userMovable = userMovable(comp);
1622: if (userMovable) {
1623: Point p = getMoveLocation(comp);
1624: mouseMove(comp, p.x, p.y);
1625: mouseMove(comp, p.x + dx, p.y + dy);
1626: }
1627: invokeAndWait(comp, new Runnable() {
1628: public void run() {
1629: comp.setLocation(new Point(loc.x + dx, loc.y + dy));
1630: }
1631: });
1632: if (userMovable) {
1633: Point p = getMoveLocation(comp);
1634: mouseMove(comp, p.x, p.y);
1635: }
1636: }
1637:
1638: /** Return where the mouse usually grabs to resize a window. The lower
1639: * right corner of the window is usually a good choice.
1640: */
1641: protected Point getResizeLocation(Container c) {
1642: Dimension size = c.getSize();
1643: Insets insets = c.getInsets();
1644: return new Point(size.width - insets.right / 2, size.height
1645: - insets.bottom / 2);
1646: }
1647:
1648: /** Return whether it is possible for the user to move the given
1649: component.
1650: */
1651: protected boolean userMovable(Component comp) {
1652: return comp instanceof Dialog || comp instanceof Frame
1653: || canMoveWindows();
1654: }
1655:
1656: /** Return whether it is possible for the user to resize the given
1657: component.
1658: */
1659: protected boolean userResizable(Component comp) {
1660: if (comp instanceof Dialog)
1661: return ((Dialog) comp).isResizable();
1662: if (comp instanceof Frame)
1663: return ((Frame) comp).isResizable();
1664: // most X11 window managers allow arbitrary resizing
1665: return canResizeWindows();
1666: }
1667:
1668: /** Resize the given Frame/Dialog to the given size. */
1669: public void resize(Container comp, int width, int height) {
1670: Dimension size = comp.getSize();
1671: resizeBy(comp, width - size.width, height - size.height);
1672: }
1673:
1674: /** Resize the given Frame/Dialog by the given amounts. */
1675: public void resizeBy(final Container comp, final int dx,
1676: final int dy) {
1677: // Fake the pointer motion like we're resizing
1678: boolean userResizable = userResizable(comp);
1679: if (userResizable) {
1680: Point p = getResizeLocation(comp);
1681: mouseMove(comp, p.x, p.y);
1682: mouseMove(comp, p.x + dx, p.y + dy);
1683: }
1684: invokeAndWait(comp, new Runnable() {
1685: public void run() {
1686: comp.setSize(comp.getWidth() + dx, comp.getHeight()
1687: + dy);
1688: }
1689: });
1690: if (userResizable) {
1691: Point p = getResizeLocation(comp);
1692: mouseMove(comp, p.x, p.y);
1693: }
1694: }
1695:
1696: /** Identify the coordinates of the iconify button where we can, returning
1697: * (0, 0) if we can't.
1698: */
1699: protected Point getIconifyLocation(Container c) {
1700: Dimension size = c.getSize();
1701: Insets insets = c.getInsets();
1702: // We know the exact layout of the window manager frames for w32 and
1703: // OSX. Currently no way of detecting the WM under X11. Maybe we
1704: // could send a WM message (WM_ICONIFY)?
1705: Point loc = new Point();
1706: loc.y = insets.top / 2;
1707: if (Platform.isOSX()) {
1708: loc.x = 35;
1709: } else if (Platform.isWindows()) {
1710: int offset = Platform.isWindowsXP() ? 64 : 45;
1711: loc.x = size.width - insets.right - offset;
1712: }
1713: return loc;
1714: }
1715:
1716: private static final int MAXIMIZE_BUTTON_OFFSET = Platform.isOSX() ? 25
1717: : Platform.isWindows() ? -20 : 0;
1718:
1719: /** Identify the coordinates of the maximize button where possible,
1720: returning null if not.
1721: */
1722: protected Point getMaximizeLocation(Container c) {
1723: Point loc = getIconifyLocation(c);
1724: loc.x += MAXIMIZE_BUTTON_OFFSET;
1725: return loc;
1726: }
1727:
1728: /** Iconify the given Frame. Don't support iconification of Dialogs at
1729: * this point (although maybe should).
1730: */
1731: public void iconify(final Frame frame) {
1732: Point loc = getIconifyLocation(frame);
1733: if (loc != null) {
1734: mouseMove(frame, loc.x, loc.y);
1735: }
1736: invokeLater(frame, new Runnable() {
1737: public void run() {
1738: frame.setState(Frame.ICONIFIED);
1739: }
1740: });
1741: }
1742:
1743: public void deiconify(Frame frame) {
1744: normalize(frame);
1745: }
1746:
1747: public void normalize(final Frame frame) {
1748: invokeLater(frame, new Runnable() {
1749: public void run() {
1750: frame.setState(Frame.NORMAL);
1751: if (Bugs.hasFrameDeiconifyBug())
1752: frame.setVisible(true);
1753: }
1754: });
1755: }
1756:
1757: /** Make the window full size. On 1.3.1, this is not reversible. */
1758: public void maximize(final Frame frame) {
1759: Point loc = getMaximizeLocation(frame);
1760: if (loc != null) {
1761: mouseMove(frame, loc.x, loc.y);
1762: }
1763: invokeLater(frame, new Runnable() {
1764: public void run() {
1765: // If the maximize is unavailable, set to full screen size
1766: // instead.
1767: try {
1768: final int MAXIMIZED_BOTH = 6;
1769: Boolean b = (Boolean) Toolkit.class.getMethod(
1770: "isFrameStateSupported",
1771: new Class[] { int.class })
1772: .invoke(
1773: toolkit,
1774: new Object[] { new Integer(
1775: MAXIMIZED_BOTH) });
1776: if (b.booleanValue() && !serviceMode) {
1777: Frame.class.getMethod("setExtendedState",
1778: new Class[] { int.class, }).invoke(
1779: frame,
1780: new Object[] { new Integer(
1781: MAXIMIZED_BOTH) });
1782: } else {
1783: throw new ActionFailedException(
1784: "Platform won't maximize");
1785: }
1786: } catch (Exception e) {
1787: Log.debug("Maximize not supported: " + e);
1788: Rectangle rect = frame.getGraphicsConfiguration()
1789: .getBounds();
1790: frame.setLocation(rect.x, rect.y);
1791: frame.setSize(rect.width, rect.height);
1792: }
1793: }
1794: });
1795: }
1796:
1797: /** Send the given event as appropriate to the event-generation mode. */
1798: public void sendEvent(AWTEvent event) {
1799: // Modifiers are ignored, assuming that an event will be
1800: // sent that causes modifiers to be sent appropriately.
1801: if (eventMode == EM_ROBOT) {
1802: int id = event.getID();
1803: Log.debug("Sending event id " + id);
1804: if (id >= MouseEvent.MOUSE_FIRST
1805: && id <= MouseEvent.MOUSE_LAST) {
1806: MouseEvent me = (MouseEvent) event;
1807: Component comp = me.getComponent();
1808: if (id == MouseEvent.MOUSE_MOVED) {
1809: mouseMove(comp, me.getX(), me.getY());
1810: } else if (id == MouseEvent.MOUSE_DRAGGED) {
1811: mouseMove(comp, me.getX(), me.getY());
1812: } else if (id == MouseEvent.MOUSE_PRESSED) {
1813: mouseMove(comp, me.getX(), me.getY());
1814: mousePress(me.getModifiers()
1815: & AWTConstants.BUTTON_MASK);
1816: } else if (id == MouseEvent.MOUSE_ENTERED) {
1817: mouseMove(comp, me.getX(), me.getY());
1818: } else if (id == MouseEvent.MOUSE_EXITED) {
1819: mouseMove(comp, me.getX(), me.getY());
1820: } else if (id == MouseEvent.MOUSE_RELEASED) {
1821: mouseMove(comp, me.getX(), me.getY());
1822: mouseRelease(me.getModifiers()
1823: & AWTConstants.BUTTON_MASK);
1824: }
1825: } else if (id >= KeyEvent.KEY_FIRST
1826: && id <= KeyEvent.KEY_LAST) {
1827: KeyEvent ke = (KeyEvent) event;
1828: if (id == KeyEvent.KEY_PRESSED) {
1829: keyPress(ke.getKeyCode());
1830: } else if (id == KeyEvent.KEY_RELEASED) {
1831: keyRelease(ke.getKeyCode());
1832: }
1833: } else {
1834: Log.warn("Event not supported: " + event);
1835: }
1836: } else {
1837: // Post the event to the appropriate AWT event queue
1838: postEvent((Component) event.getSource(), event);
1839: }
1840: }
1841:
1842: /** Return the symbolic name of the given event's ID. */
1843: public static String getEventID(AWTEvent event) {
1844: // Optimize here to avoid field name lookup overhead
1845: switch (event.getID()) {
1846: case MouseEvent.MOUSE_MOVED:
1847: return "MOUSE_MOVED";
1848: case MouseEvent.MOUSE_DRAGGED:
1849: return "MOUSE_DRAGGED";
1850: case MouseEvent.MOUSE_PRESSED:
1851: return "MOUSE_PRESSED";
1852: case MouseEvent.MOUSE_CLICKED:
1853: return "MOUSE_CLICKED";
1854: case MouseEvent.MOUSE_RELEASED:
1855: return "MOUSE_RELEASED";
1856: case MouseEvent.MOUSE_ENTERED:
1857: return "MOUSE_ENTERED";
1858: case MouseEvent.MOUSE_EXITED:
1859: return "MOUSE_EXITED";
1860: case KeyEvent.KEY_PRESSED:
1861: return "KEY_PRESSED";
1862: case KeyEvent.KEY_TYPED:
1863: return "KEY_TYPED";
1864: case KeyEvent.KEY_RELEASED:
1865: return "KEY_RELEASED";
1866: case WindowEvent.WINDOW_OPENED:
1867: return "WINDOW_OPENED";
1868: case WindowEvent.WINDOW_CLOSING:
1869: return "WINDOW_CLOSING";
1870: case WindowEvent.WINDOW_CLOSED:
1871: return "WINDOW_CLOSED";
1872: case WindowEvent.WINDOW_ICONIFIED:
1873: return "WINDOW_ICONIFIED";
1874: case WindowEvent.WINDOW_DEICONIFIED:
1875: return "WINDOW_DEICONIFIED";
1876: case WindowEvent.WINDOW_ACTIVATED:
1877: return "WINDOW_ACTIVATED";
1878: case WindowEvent.WINDOW_DEACTIVATED:
1879: return "WINDOW_DEACTIVATED";
1880: case ComponentEvent.COMPONENT_MOVED:
1881: return "COMPONENT_MOVED";
1882: case ComponentEvent.COMPONENT_RESIZED:
1883: return "COMPONENT_RESIZED";
1884: case ComponentEvent.COMPONENT_SHOWN:
1885: return "COMPONENT_SHOWN";
1886: case ComponentEvent.COMPONENT_HIDDEN:
1887: return "COMPONENT_HIDDEN";
1888: case FocusEvent.FOCUS_GAINED:
1889: return "FOCUS_GAINED";
1890: case FocusEvent.FOCUS_LOST:
1891: return "FOCUS_LOST";
1892: case HierarchyEvent.HIERARCHY_CHANGED:
1893: return "HIERARCHY_CHANGED";
1894: case HierarchyEvent.ANCESTOR_MOVED:
1895: return "ANCESTOR_MOVED";
1896: case HierarchyEvent.ANCESTOR_RESIZED:
1897: return "ANCESTOR_RESIZED";
1898: case PaintEvent.PAINT:
1899: return "PAINT";
1900: case PaintEvent.UPDATE:
1901: return "UPDATE";
1902: case ActionEvent.ACTION_PERFORMED:
1903: return "ACTION_PERFORMED";
1904: case InputMethodEvent.CARET_POSITION_CHANGED:
1905: return "CARET_POSITION_CHANGED";
1906: case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED:
1907: return "INPUT_METHOD_TEXT_CHANGED";
1908: default:
1909: return Reflector.getFieldName(event.getClass(), event
1910: .getID(), "");
1911: }
1912: }
1913:
1914: public static Class getCanonicalClass(Class refClass) {
1915: // Don't use classnames from anonymous inner classes...
1916: // Don't use classnames from platform LAF classes...
1917: String className = refClass.getName();
1918: while (className.indexOf("$") != -1
1919: || className.startsWith("javax.swing.plaf")
1920: || className.startsWith("com.apple.mrj")) {
1921: refClass = refClass.getSuperclass();
1922: className = refClass.getName();
1923: }
1924: return refClass;
1925: }
1926:
1927: /** Provides a more concise representation of the component than the
1928: * default Component.toString().
1929: */
1930: public static String toString(Component comp) {
1931: if (comp == null)
1932: return "(null)";
1933:
1934: if (AWT.isTransientPopup(comp)) {
1935: boolean tooltip = AWT.isToolTip(comp);
1936: if (AWT.isHeavyweightPopup(comp)) {
1937: return tooltip ? Strings
1938: .get("component.heavyweight_tooltip") : Strings
1939: .get("component.heavyweight_popup");
1940: } else if (AWT.isLightweightPopup(comp)) {
1941: return tooltip ? Strings
1942: .get("component.lightweight_tooltip") : Strings
1943: .get("component.lightweight_popup");
1944: }
1945: } else if (AWT.isSharedInvisibleFrame(comp)) {
1946: return Strings.get("component.default_frame");
1947: }
1948: String name = getDescriptiveName(comp);
1949: String classDesc = descriptiveClassName(comp.getClass());
1950: if (name == null) {
1951: if (AWT.isContentPane(comp)) {
1952: name = Strings.get("component.content_pane");
1953: } else if (AWT.isGlassPane(comp)) {
1954: name = Strings.get("component.glass_pane");
1955: } else if (comp instanceof JLayeredPane) {
1956: name = Strings.get("component.layered_pane");
1957: } else if (comp instanceof JRootPane) {
1958: name = Strings.get("component.root_pane");
1959: } else {
1960: name = classDesc + " instance";
1961: }
1962: } else {
1963: name = "'" + name + "' (" + classDesc + ")";
1964: }
1965: return name;
1966: }
1967:
1968: /** Provide a string representation of the given component (Component or
1969: * MenuComponent.
1970: */
1971: public static String toString(Object obj) {
1972: if (obj instanceof Component)
1973: return toString((Component) obj);
1974: else if (obj instanceof MenuBar)
1975: return "MenuBar";
1976: else if (obj instanceof MenuItem)
1977: return ((MenuItem) obj).getLabel();
1978: return obj.toString();
1979: }
1980:
1981: protected static String descriptiveClassName(Class cls) {
1982: StringBuffer desc = new StringBuffer(simpleClassName(cls));
1983: Class coreClass = getCanonicalClass(cls);
1984: String coreClassName = coreClass.getName();
1985: while (!coreClassName.startsWith("java.awt.")
1986: && !coreClassName.startsWith("javax.swing.")
1987: && !coreClassName.startsWith("java.applet.")) {
1988: coreClass = coreClass.getSuperclass();
1989: coreClassName = coreClass.getName();
1990: }
1991: if (!coreClass.equals(cls)) {
1992: desc.append("/");
1993: desc.append(simpleClassName(coreClass));
1994: }
1995: return desc.toString();
1996: }
1997:
1998: /** Provides the hierarchic path of the given component by component
1999: class, e.g. "JFrame:JRootPane:JPanel:JButton".
2000: */
2001: public static String toHierarchyPath(Component c) {
2002: StringBuffer buf = new StringBuffer();
2003: Container parent = c.getParent();
2004: if (parent != null) {
2005: buf.append(toHierarchyPath(parent));
2006: buf.append(":");
2007: }
2008: buf.append(descriptiveClassName(c.getClass()));
2009: String name = getDescriptiveName(c);
2010: if (name != null) {
2011: buf.append("(");
2012: buf.append(name);
2013: buf.append(")");
2014: } else if (parent != null && parent.getComponentCount() > 1
2015: && c instanceof JPanel) {
2016: buf.append("[");
2017: buf.append(String.valueOf(getIndex(parent, c)));
2018: buf.append("]");
2019: }
2020: return buf.toString();
2021: }
2022:
2023: /** Provide a more concise representation of the event than the default
2024: * AWTEvent.toString().
2025: */
2026: public static String toString(AWTEvent event) {
2027: String name = toString(event.getSource());
2028: String desc = getEventID(event);
2029: if (event.getID() == KeyEvent.KEY_PRESSED
2030: || event.getID() == KeyEvent.KEY_RELEASED) {
2031: KeyEvent ke = (KeyEvent) event;
2032: desc += " (" + AWT.getKeyCode(ke.getKeyCode());
2033: if (ke.getModifiers() != 0) {
2034: desc += "/" + AWT.getKeyModifiers(ke.getModifiers());
2035: }
2036: desc += ")";
2037: } else if (event.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
2038: desc += " ("
2039: + ((InputMethodEvent) event)
2040: .getCommittedCharacterCount() + ")";
2041: } else if (event.getID() == KeyEvent.KEY_TYPED) {
2042: char ch = ((KeyEvent) event).getKeyChar();
2043: int mods = ((KeyEvent) event).getModifiers();
2044: desc += " ('"
2045: + ch
2046: + (mods != 0 ? "/" + AWT.getKeyModifiers(mods) : "")
2047: + "')";
2048: } else if (event.getID() >= MouseEvent.MOUSE_FIRST
2049: && event.getID() <= MouseEvent.MOUSE_LAST) {
2050: MouseEvent me = (MouseEvent) event;
2051: if (me.getModifiers() != 0) {
2052: desc += " <" + AWT.getMouseModifiers(me.getModifiers());
2053: if (me.getClickCount() > 1) {
2054: desc += "," + me.getClickCount();
2055: }
2056: desc += ">";
2057: }
2058: desc += " (" + me.getX() + "," + me.getY() + ")";
2059: } else if (event.getID() == HierarchyEvent.HIERARCHY_CHANGED) {
2060: HierarchyEvent he = (HierarchyEvent) event;
2061: long flags = he.getChangeFlags();
2062: String type = "";
2063: String bar = "";
2064: if ((flags & HierarchyEvent.SHOWING_CHANGED) != 0) {
2065: type += (he.getComponent().isShowing() ? "" : "!")
2066: + "SHOWING";
2067: bar = "|";
2068: }
2069: if ((flags & HierarchyEvent.PARENT_CHANGED) != 0) {
2070: type += bar + "PARENT:"
2071: + toString(he.getComponent().getParent());
2072: bar = "|";
2073: }
2074: if ((flags & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
2075: type += bar + "DISPLAYABILITY";
2076: }
2077: desc += " (" + type + ")";
2078: }
2079: return desc + " on " + name;
2080: }
2081:
2082: /** Return the numeric event ID corresponding to the given string. */
2083: public static int getEventID(Class cls, String id) {
2084: return Reflector.getFieldValue(cls, id);
2085: }
2086:
2087: /** Strip the package from the class name. */
2088: public static String simpleClassName(Class cls) {
2089: String name = cls.getName();
2090: int dot = name.lastIndexOf(".");
2091: return name.substring(dot + 1, name.length());
2092: }
2093:
2094: private AWTEvent lastEventPosted = null;
2095: private MouseEvent lastMousePress = null;
2096: private boolean countingClicks = false;
2097:
2098: /** Post the given event to the corresponding event queue for the given
2099: component. */
2100: protected void postEvent(Component comp, AWTEvent ev) {
2101: if (Log.isClassDebugEnabled(Robot.class))
2102: Log.debug("POST: " + toString(ev));
2103: if (eventMode == EM_AWT && AWT.isAWTPopupMenuBlocking()) {
2104: throw new Error(
2105: "Event queue is blocked by an active AWT PopupMenu");
2106: }
2107: // Force an update of the input state, so that we're in synch
2108: // internally. Otherwise we might post more events before this
2109: // one gets processed and end up using stale values for those events.
2110: state.update(ev);
2111: EventQueue q = getEventQueue(comp);
2112: q.postEvent(ev);
2113: delay(autoDelay);
2114: AWTEvent prev = lastEventPosted;
2115: lastEventPosted = ev;
2116: if (ev instanceof MouseEvent) {
2117: if (ev.getID() == MouseEvent.MOUSE_PRESSED) {
2118: lastMousePress = (MouseEvent) ev;
2119: countingClicks = true;
2120: } else if (ev.getID() != MouseEvent.MOUSE_RELEASED
2121: && ev.getID() != MouseEvent.MOUSE_CLICKED) {
2122: countingClicks = false;
2123: }
2124: }
2125:
2126: // Generate a click if there are no events between press/release
2127: // Unfortunately, I can only guess how the VM generates them
2128: if (eventMode == EM_AWT
2129: && ev.getID() == MouseEvent.MOUSE_RELEASED
2130: && prev.getID() == MouseEvent.MOUSE_PRESSED) {
2131: MouseEvent me = (MouseEvent) ev;
2132: AWTEvent click = new MouseEvent(comp,
2133: MouseEvent.MOUSE_CLICKED, System
2134: .currentTimeMillis(), me.getModifiers(), me
2135: .getX(), me.getY(), me.getClickCount(),
2136: false);
2137: postEvent(comp, click);
2138: }
2139: }
2140:
2141: /** Wait for the given Condition to return true. The default timeout may
2142: * be changed by setting abbot.robot.default_delay.
2143: * @throws WaitTimedOutError if the default timeout (30s) is exceeded.
2144: * @see Robot#wait(Condition, long, int) Robot.wait For a description of the use of this function to support lazy loading in class files
2145: */
2146: public void wait(Condition condition) {
2147: wait(condition, defaultDelay);
2148: }
2149:
2150: /** Wait for the given Condition to return true, waiting for timeout ms.
2151: * @throws WaitTimedOutError if the timeout is exceeded.
2152: * @see Robot#wait(Condition, long, int) Robot.wait For a description of the use of this function to support lazy loading in class files
2153: */
2154: public void wait(Condition condition, long timeout) {
2155: wait(condition, timeout, SLEEP_INTERVAL);
2156: }
2157:
2158: /**
2159: * Wait for the given Condition to return true, waiting for timeout ms,
2160: * polling at the given interval. This method can be used generically to
2161: * support components that are lazily loaded in the context of writing
2162: * testers or other testing code.
2163: * <p>
2164: * Take for example the simple case of trying to select an item in a list.
2165: * If the model for this list is populate asynchronously then you may need
2166: * to poll the list for a given amount of time until the item appears. This
2167: * is very common in an application that contains many long running tasks.
2168: * <p>
2169: * A general solution to the problem can be seen in this pseudo code:
2170: * <pre>
2171: * wait(new Condition()
2172: * {
2173: * public boolean test()
2174: * {
2175: * return whether the ui element is avaliable
2176: * }
2177: * });
2178: * performAction(ui element);
2179: * </pre>
2180: * <p>
2181: * If you are writing a tester method then it is a good idea to use a standard
2182: * timeout such as {@link Robot#componentDelay} to ensure they are consistently
2183: * handled accross different testers.
2184: *
2185: * @throws WaitTimedOutError if the timeout is exceeded.
2186: */
2187: public void wait(Condition condition, long timeout, int interval) {
2188: long start = System.currentTimeMillis();
2189: while (!condition.test()) {
2190: if (System.currentTimeMillis() - start > timeout) {
2191: String msg = "Timed out waiting for " + condition;
2192: throw new WaitTimedOutError(msg);
2193: }
2194: delay(interval);
2195: }
2196: }
2197:
2198: public void reset() {
2199: if (eventMode == EM_ROBOT) {
2200: Dimension d = toolkit.getScreenSize();
2201: mouseMove(d.width / 2, d.height / 2);
2202: mouseMove(d.width / 2 - 1, d.height / 2 - 1);
2203: } else {
2204: // clear any held state
2205: state.clear();
2206: }
2207: }
2208:
2209: /** Return the Component which currently owns the focus. */
2210: public Component findFocusOwner() {
2211: return AWT.getFocusOwner();
2212: }
2213:
2214: /** Return a descriptive name for the given component for use in UI
2215: * text (may be localized if appropriate and need not be re-usable
2216: * across locales.
2217: */
2218: public static String getDescriptiveName(Component c) {
2219: if (AWT.isSharedInvisibleFrame(c))
2220: return Strings.get("component.default_frame");
2221:
2222: String name = getName(c);
2223: if (name == null) {
2224: if ((name = getTitle(c)) == null) {
2225: if ((name = getText(c)) == null) {
2226: if ((name = getLabel(c)) == null) {
2227: if ((name = getIconName(c)) == null) {
2228: }
2229: }
2230: }
2231: }
2232: }
2233: return name;
2234: }
2235:
2236: public static String getName(Component c) {
2237: String name = AWT.hasDefaultName(c) ? null : c.getName();
2238: // Accessibility behaves like what we used to do with getTag.
2239: // Not too helpful for our purposes, especially when the
2240: // data on which the name is based might be dynamic.
2241: /*
2242: if (name == null) {
2243: AccessibleContext context = c.getAccessibleContext();
2244: if (context != null)
2245: name = context.getAccessibleName();
2246: }
2247: */
2248: return name;
2249: }
2250:
2251: /** Returns the index of the given component within the given container. */
2252: public static int getIndex(Container parent, Component comp) {
2253: if (comp instanceof Window) {
2254: Window[] owned = ((Window) parent).getOwnedWindows();
2255: for (int i = 0; i < owned.length; i++) {
2256: if (owned[i] == comp) {
2257: return i;
2258: }
2259: }
2260: } else {
2261: Component[] children = parent.getComponents();
2262: for (int i = 0; i < children.length; ++i) {
2263: if (children[i] == comp) {
2264: return i;
2265: }
2266: }
2267: }
2268: return -1;
2269: }
2270:
2271: public static String getText(Component c) {
2272: if (c instanceof AbstractButton) {
2273: return ComponentTester.stripHTML(((AbstractButton) c)
2274: .getText());
2275: } else if (c instanceof JLabel) {
2276: return ComponentTester.stripHTML(((JLabel) c).getText());
2277: } else if (c instanceof Label) {
2278: return ((Label) c).getText();
2279: }
2280: return null;
2281: }
2282:
2283: public static String getLabel(Component c) {
2284: String label = null;
2285: if (c instanceof JComponent) {
2286: Object obj = ((JComponent) c)
2287: .getClientProperty(LABELED_BY_PROPERTY);
2288: // While the default is a JLabel, users may use something else as
2289: // the property, so be careful.
2290: if (obj != null) {
2291: if (obj instanceof JLabel) {
2292: label = ((JLabel) obj).getText();
2293: } else if (obj instanceof String) {
2294: label = (String) obj;
2295: }
2296: }
2297: } else if (c instanceof Button) {
2298: label = ((Button) c).getLabel();
2299: } else if (c instanceof Checkbox) {
2300: label = ((Checkbox) c).getLabel();
2301: }
2302: return ComponentTester.stripHTML(label);
2303: }
2304:
2305: public static String getIconName(Component c) {
2306: String icon = null;
2307: AccessibleContext context = c.getAccessibleContext();
2308: if (context != null) {
2309: AccessibleIcon[] icons = context.getAccessibleIcon();
2310: if (icons != null && icons.length > 0) {
2311: icon = icons[0].getAccessibleIconDescription();
2312: if (icon != null) {
2313: icon = icon.substring(icon.lastIndexOf("/") + 1);
2314: icon = icon.substring(icon.lastIndexOf("\\") + 1);
2315: }
2316: }
2317: }
2318: return icon;
2319: }
2320:
2321: public static String getBorderTitle(Component c) {
2322: String title = null;
2323: if (c instanceof JComponent) {
2324: title = getBorderTitle(((JComponent) c).getBorder());
2325: }
2326: return title;
2327: }
2328:
2329: /** See javax.swing.JComponent.getBorderTitle. */
2330: private static String getBorderTitle(Border b) {
2331: String title = null;
2332: if (b instanceof TitledBorder)
2333: title = ((TitledBorder) b).getTitle();
2334: else if (b instanceof CompoundBorder) {
2335: title = getBorderTitle(((CompoundBorder) b)
2336: .getInsideBorder());
2337: if (title == null) {
2338: title = getBorderTitle(((CompoundBorder) b)
2339: .getOutsideBorder());
2340: }
2341: }
2342: return title;
2343: }
2344:
2345: public static String getTitle(Component c) {
2346: if (c instanceof Dialog)
2347: return ((Dialog) c).getTitle();
2348: else if (c instanceof Frame)
2349: return ((Frame) c).getTitle();
2350: else if (c instanceof JInternalFrame)
2351: return ((JInternalFrame) c).getTitle();
2352: return null;
2353: }
2354:
2355: /** Returns whether it is possible to resize windows that are not an
2356: instance of Frame or Dialog. Most X11 window managers will allow
2357: this, but stock Macintosh and Windows do not.
2358: */
2359: public static boolean canResizeWindows() {
2360: return !Platform.isWindows() && !Platform.isMacintosh();
2361: }
2362:
2363: /** Returns whether it is possible to move windows that are not an
2364: instance of Frame or Dialog. Most X11 window managers will allow
2365: this, but stock Macintosh and Windows do not.
2366: */
2367: public static boolean canMoveWindows() {
2368: return !Platform.isWindows() && !Platform.isMacintosh();
2369: }
2370:
2371: /** Returns the appropriate auto delay for robot-generated events.
2372: */
2373: public static int getPreferredRobotAutoDelay() {
2374: // better safe than sorry, and slower and accurate than
2375: // fast and inaccurate.
2376: /*
2377: if (Platform.isWindows() || Platform.isOSX() || Platform.isX11())
2378: return 0;
2379: */
2380: // >= 40 causes problems registering a double-click when clicks
2381: // are requested separately (due to auto-jitter and other delay
2382: // being added on individual clicks).
2383: return 30;
2384: }
2385: }
|