|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| Robot.java | 65.1% | 76.1% | 92.4% | 74.4% |
|
||||||||||||||
| 1 |
package abbot.tester;
|
|
| 2 |
|
|
| 3 |
import java.applet.Applet;
|
|
| 4 |
import java.awt.*;
|
|
| 5 |
import java.awt.event.*;
|
|
| 6 |
import java.awt.geom.Rectangle2D;
|
|
| 7 |
import java.awt.image.BufferedImage;
|
|
| 8 |
import java.lang.reflect.InvocationTargetException;
|
|
| 9 |
import java.util.Collection;
|
|
| 10 |
import java.util.Iterator;
|
|
| 11 |
|
|
| 12 |
import javax.accessibility.AccessibleAction;
|
|
| 13 |
import javax.swing.*;
|
|
| 14 |
|
|
| 15 |
import abbot.*;
|
|
| 16 |
import abbot.finder.*;
|
|
| 17 |
import abbot.finder.ComponentFinder;
|
|
| 18 |
import abbot.finder.matchers.*;
|
|
| 19 |
import abbot.i18n.Strings;
|
|
| 20 |
import abbot.script.*;
|
|
| 21 |
import abbot.util.*;
|
|
| 22 |
|
|
| 23 |
/** Provide a higher level of abstraction for user input (A Better Robot).
|
|
| 24 |
The Robot's operation may be affected by the following properties:<br>
|
|
| 25 |
<pre><code>abbot.robot.auto_delay</code></pre><br>
|
|
| 26 |
Set this to a value representing the millisecond count in between
|
|
| 27 |
generated events. Usually just set to 100-200 if you want to slow down
|
|
| 28 |
the playback to simulate actual user input. The default is zero delay.<br>
|
|
| 29 |
<pre><code>abbot.robot.mode</code></pre><br>
|
|
| 30 |
Set this to either "robot" or "awt" to designate the desired mode of event
|
|
| 31 |
generation. "robot" uses java.awt.Robot to generate events, while "awt"
|
|
| 32 |
stuffs events directly into the AWT event queue.<br>
|
|
| 33 |
<pre><code>abbot.robot.event_post_delay</code></pre><br>
|
|
| 34 |
This is the maximum number of ms it takes the system to post an AWT event
|
|
| 35 |
in response to a Robot-generated event.
|
|
| 36 |
<pre><code>abbot.robot.default_delay</code></pre><br>
|
|
| 37 |
Base delay setting, acts as default value for the next two.
|
|
| 38 |
<pre><code>abbot.robot.popup_delay</code></pre><br>
|
|
| 39 |
Set this to the maximum time to wait for a menu to appear or be generated.
|
|
| 40 |
<pre><code>abbot.robot.component_delay</code></pre><br>
|
|
| 41 |
Set this to the maximum time to wait for a Component to become available.
|
|
| 42 |
<p>
|
|
| 43 |
NOTE: Only use event queue synchronization (e.g.
|
|
| 44 |
{@link #invokeAndWait(Runnable)} or {@link #waitForIdle()} when a
|
|
| 45 |
subsequent robot-level action is being applied to the results of a prior
|
|
| 46 |
action (e.g. focus, deiconify, menu selection). Otherwise, don't
|
|
| 47 |
introduce a mandatory delay (e.g. use {@link #invokeLater(Runnable)}).
|
|
| 48 |
<p>
|
|
| 49 |
NOTE: If a robot action isn't reproduced properly, you may need to
|
|
| 50 |
introduce either additional events or extra delay. Adding enforced delay
|
|
| 51 |
for a given platform is usually preferable to generating additional events,
|
|
| 52 |
so always try that first, but be sure to restrict it to the platform in
|
|
| 53 |
question.
|
|
| 54 |
<p>
|
|
| 55 |
NOTE: Robot actions should <b>never</b> be invoked on the event dispatch
|
|
| 56 |
thread.
|
|
| 57 |
*/
|
|
| 58 |
|
|
| 59 |
public class Robot implements AWTConstants { |
|
| 60 |
/** Use java.awt.Robot to generate events. */
|
|
| 61 |
public static int EM_ROBOT = 0; |
|
| 62 |
/** Post events to the AWT event queue. */
|
|
| 63 |
public static int EM_AWT = 1; |
|
| 64 |
|
|
| 65 |
private static final Toolkit toolkit = Toolkit.getDefaultToolkit(); |
|
| 66 |
// Max robot delay, in ms
|
|
| 67 |
private static final int MAX_DELAY = 60000; |
|
| 68 |
// TODO: verify this value for X11, etc.; ALT for w32, option for OSX
|
|
| 69 |
public static final int MOUSELESS_MODIFIER_MASK = InputEvent.ALT_MASK; |
|
| 70 |
public static final String MOUSELESS_MODIFIER = |
|
| 71 |
AWT.getKeyModifiers(MOUSELESS_MODIFIER_MASK); |
|
| 72 |
|
|
| 73 |
/** OS X using screenMenuBar actually uses an AWT menu as the live
|
|
| 74 |
component. The JXXX components exist, but are not effectively
|
|
| 75 |
active.
|
|
| 76 |
*/
|
|
| 77 | 64 |
protected static final boolean useScreenMenuBar() { |
| 78 |
// Ideally we'd install a menu and check where it ended up, since the
|
|
| 79 |
// property is read once at startup and ignored thereafter.
|
|
| 80 | 64 |
return Platform.isOSX()
|
| 81 |
&& (Boolean.getBoolean("com.apple.macos.useScreenMenuBar")
|
|
| 82 |
|| Boolean.getBoolean("apple.laf.useScreenMenuBar"));
|
|
| 83 |
} |
|
| 84 |
|
|
| 85 |
/** Base delay setting. */
|
|
| 86 |
public static int defaultDelay = |
|
| 87 |
Properties.getProperty("abbot.robot.default_delay", 30000, 0, 60000);
|
|
| 88 |
|
|
| 89 |
/** Delay before checking for idle. This allows the system a little time
|
|
| 90 |
to put a native event onto the AWT event queue. */
|
|
| 91 |
private static int eventPostDelay = |
|
| 92 |
Properties.getProperty("abbot.robot.event_post_delay", 100, 0, 1000);
|
|
| 93 |
|
|
| 94 |
protected static long IDLE_TIMEOUT = |
|
| 95 |
Integer.getInteger("abbot.robot.idle_timeout", 10000).intValue();
|
|
| 96 |
|
|
| 97 |
/** Delay before failing to find a popup menu that should appear. */
|
|
| 98 |
protected static int popupDelay = |
|
| 99 |
Properties.getProperty("abbot.robot.popup_delay",
|
|
| 100 |
defaultDelay, 0, 60000); |
|
| 101 |
|
|
| 102 |
/** Delay before failing to find a component that should be visible. */
|
|
| 103 |
public static int componentDelay = |
|
| 104 |
Properties.getProperty("abbot.robot.component_delay",
|
|
| 105 |
defaultDelay, 0, 60000); |
|
| 106 |
|
|
| 107 |
/** With decreased robot auto delay, OSX popup menus don't activate
|
|
| 108 |
* properly. Indicate the minimum delay for proper operation (determined
|
|
| 109 |
* experimentally).
|
|
| 110 |
*/
|
|
| 111 | 96 |
private static final int subMenuDelay = Platform.isOSX() ? 100 : 0; |
| 112 |
|
|
| 113 |
/** How events are generated. */
|
|
| 114 |
private static int eventMode = EM_ROBOT; |
|
| 115 |
private static boolean verified = false; |
|
| 116 |
private static boolean serviceMode = false; |
|
| 117 |
|
|
| 118 |
// FIXME add one per graphics device?
|
|
| 119 |
/** The robot used to generate events. */
|
|
| 120 |
private static java.awt.Robot robot; |
|
| 121 |
private static WindowTracker tracker; |
|
| 122 |
/** Current input state. This will either be that of the AWT event queue
|
|
| 123 |
* or of the robot, depending on the dispatch mode.
|
|
| 124 |
* Note that the robot state may be different from that seen by the AWT
|
|
| 125 |
* event queue, since robot events may be as yet unprocessed.
|
|
| 126 |
*/
|
|
| 127 |
private static InputState state; |
|
| 128 |
|
|
| 129 |
|
|
| 130 |
/** Suitable inter-event delay for most cases; tests have been run safely
|
|
| 131 |
* at this value. Should definitely be less than the double-click
|
|
| 132 |
* threshold.<p>
|
|
| 133 |
*/
|
|
| 134 |
private static final int DEFAULT_DELAY = getPreferredRobotAutoDelay(); |
|
| 135 |
private static final int SLEEP_INTERVAL = 10; |
|
| 136 |
|
|
| 137 |
private static int autoDelay = DEFAULT_DELAY; |
|
| 138 | 724 |
public static int getAutoDelay() { return autoDelay; } |
| 139 |
/** Returns a functioning instance of java.awt.Robot. If this method
|
|
| 140 |
* returns null, it should be assumed that java.awt.Robot is unavailable
|
|
| 141 |
* or non-functional on the current system.
|
|
| 142 |
*/
|
|
| 143 | 519 |
public static java.awt.Robot getRobot() { |
| 144 | 519 |
return serviceMode ? null : robot; |
| 145 |
} |
|
| 146 |
|
|
| 147 |
/** Return a singleton InputState object. */
|
|
| 148 | 501 |
public static InputState getState() { |
| 149 | 501 |
return state;
|
| 150 |
} |
|
| 151 |
|
|
| 152 |
static {
|
|
| 153 | 96 |
robot = createRobot(); |
| 154 | 96 |
tracker = WindowTracker.getTracker(); |
| 155 | 96 |
state = new InputState();
|
| 156 |
} |
|
| 157 |
|
|
| 158 | 96 |
private static java.awt.Robot createRobot() { |
| 159 | 96 |
java.awt.Robot robot = null;
|
| 160 | 96 |
String mode = System.getProperty("abbot.robot.mode", "robot"); |
| 161 | 96 |
autoDelay = Properties.getProperty("abbot.robot.auto_delay",
|
| 162 |
autoDelay, -1, 60000); |
|
| 163 | 96 |
try {
|
| 164 |
// Even if the robot doesn't work, we can still use it for some
|
|
| 165 |
// things.
|
|
| 166 | 96 |
robot = new java.awt.Robot();
|
| 167 | 96 |
if (autoDelay != -1) {
|
| 168 | 96 |
robot.setAutoDelay(autoDelay); |
| 169 |
} |
|
| 170 |
else {
|
|
| 171 | 0 |
autoDelay = robot.getAutoDelay(); |
| 172 |
} |
|
| 173 | 96 |
if (!verified) {
|
| 174 | 96 |
verified = true;
|
| 175 | 96 |
boolean skip = "false". |
| 176 |
equals(System.getProperty("abbot.robot.verify"));
|
|
| 177 | 96 |
if (!skip && !RobotVerifier.verify(robot)) {
|
| 178 |
// robot doesn't work (w32 service mode)
|
|
| 179 | 0 |
serviceMode = true;
|
| 180 | 0 |
System.err.println("Robot non-functional, "
|
| 181 |
+ "falling back to AWT mode");
|
|
| 182 | 0 |
mode = "awt";
|
| 183 |
} |
|
| 184 |
} |
|
| 185 |
} |
|
| 186 |
catch(AWTException e) {
|
|
| 187 |
// no robot available, send AWT events
|
|
| 188 | 0 |
System.err.println("Falling back to AWT mode: "
|
| 189 |
+ e.getMessage()); |
|
| 190 | 0 |
mode = "awt";
|
| 191 |
} |
|
| 192 | 96 |
if (mode.equals("awt")) { |
| 193 | 0 |
eventMode = EM_AWT; |
| 194 |
} |
|
| 195 | 96 |
return robot;
|
| 196 |
} |
|
| 197 |
|
|
| 198 |
/** Returns the current event-generation mode. */
|
|
| 199 | 425 |
public static int getEventMode() { return eventMode; } |
| 200 | 288 |
public static String getEventModeDescription() { |
| 201 | 288 |
String desc = eventMode == EM_ROBOT ? "robot" : "awt"; |
| 202 | 288 |
if (serviceMode)
|
| 203 | 0 |
desc += " (service)";
|
| 204 | 288 |
return desc;
|
| 205 |
} |
|
| 206 |
|
|
| 207 |
/** Set the event-generation mode.
|
|
| 208 |
@throws IllegalStateException if the requested mode is EM_ROBOT and
|
|
| 209 |
java.awt.Robot is unavailable in the current environment.
|
|
| 210 |
*/
|
|
| 211 | 27 |
public static void setEventMode(int mode) { |
| 212 | 27 |
if (eventMode != mode) {
|
| 213 | 10 |
if (mode == EM_ROBOT && (serviceMode || robot == null)) { |
| 214 | 0 |
String msg = Strings.get("tester.Robot.no_robot_mode");
|
| 215 | 0 |
throw new IllegalStateException(msg); |
| 216 |
} |
|
| 217 | 10 |
eventMode = mode; |
| 218 |
} |
|
| 219 |
} |
|
| 220 |
|
|
| 221 | 21 |
public static int getEventPostDelay() { return eventPostDelay; } |
| 222 | 0 |
public static void setEventPostDelay(int delay) { |
| 223 | 0 |
eventPostDelay = Math.min(1000, Math.max(0, delay)); |
| 224 |
} |
|
| 225 |
|
|
| 226 |
/** Allow this to be adjusted, mostly for testing. */
|
|
| 227 | 724 |
public static void setAutoDelay(int ms) { |
| 228 | 724 |
ms = Math.min(60000, Math.max(0, ms)); |
| 229 | 724 |
if (eventMode == EM_ROBOT)
|
| 230 | 684 |
robot.setAutoDelay(ms); |
| 231 | 724 |
autoDelay = ms; |
| 232 | 724 |
Log.debug("Auto delay set to " + ms);
|
| 233 |
} |
|
| 234 |
|
|
| 235 |
/**
|
|
| 236 |
* Move the mouse to the given location, in screen coordinates.
|
|
| 237 |
* NOTE: in robot mode, you may need to invoke this with a little jitter.
|
|
| 238 |
* There are some conditions where a single mouse move will not
|
|
| 239 |
* generate the necessary enter event on a component (typically a
|
|
| 240 |
* dialog with an OK button) before a mousePress. See also click().
|
|
| 241 |
* NOTE: does 1.4+ need jitter?
|
|
| 242 |
*/
|
|
| 243 | 2149 |
private void mouseMove(int x, int y) { |
| 244 | 2149 |
if (eventMode == EM_ROBOT) {
|
| 245 | 2149 |
Log.debug("ROBOT: Mouse move: (" + x + "," + y + ")"); |
| 246 | 2149 |
robot.mouseMove(x, y); |
| 247 |
} |
|
| 248 |
else {
|
|
| 249 |
// Can't stuff an AWT event for an arbitrary location
|
|
| 250 |
} |
|
| 251 |
} |
|
| 252 |
|
|
| 253 |
/** Send a button press event. */
|
|
| 254 | 751 |
public void mousePress(int buttons) { |
| 255 | 751 |
if (eventMode == EM_ROBOT) {
|
| 256 | 750 |
Log.debug("ROBOT: Mouse press: " + AWT.getMouseModifiers(buttons));
|
| 257 |
// OSX 1.4.1 accidentally swaps mb2 and mb3; fix it here
|
|
| 258 | 750 |
robot.mousePress(buttons); |
| 259 |
} |
|
| 260 |
else {
|
|
| 261 | 1 |
Component c = state.getMouseComponent(); |
| 262 | 1 |
if (c == null) { |
| 263 | 0 |
Log.warn("No current mouse component for button press", 4);
|
| 264 | 0 |
return;
|
| 265 |
} |
|
| 266 | 1 |
Point where = state.getMouseLocation(); |
| 267 | 1 |
postMousePress(c, where.x, where.y, buttons); |
| 268 |
} |
|
| 269 |
} |
|
| 270 |
|
|
| 271 |
/** Send a button release event for button 1. */
|
|
| 272 | 27 |
public void mouseRelease() { |
| 273 | 27 |
mouseRelease(MouseEvent.BUTTON1_MASK); |
| 274 |
} |
|
| 275 |
|
|
| 276 |
/** Send a button release event. */
|
|
| 277 | 797 |
public void mouseRelease(int buttons) { |
| 278 | 797 |
if (eventMode == EM_ROBOT) {
|
| 279 | 756 |
Log.debug("ROBOT: Mouse release: " + AWT.getMouseModifiers(buttons));
|
| 280 | 756 |
robot.mouseRelease(buttons); |
| 281 |
} |
|
| 282 |
else {
|
|
| 283 | 41 |
Component source = state.isDragging() |
| 284 |
? state.getDragSource() |
|
| 285 | 0 |
: (lastMousePress != null
|
| 286 |
? lastMousePress.getComponent() |
|
| 287 |
: state.getMouseComponent()); |
|
| 288 | 41 |
Point where = state.getMouseLocation(); |
| 289 | 41 |
if (source == null) { |
| 290 | 0 |
Log.warn("Mouse release outside of available frames");
|
| 291 | 0 |
return;
|
| 292 |
} |
|
| 293 | 41 |
else if (where == null) { |
| 294 | 0 |
if (lastMousePress == null) { |
| 295 | 0 |
Log.warn("No record of most recent mouse press");
|
| 296 | 0 |
return;
|
| 297 |
} |
|
| 298 | 0 |
where = lastMousePress.getPoint(); |
| 299 |
} |
|
| 300 | 41 |
postMouseRelease(source, where.x, where.y, buttons); |
| 301 |
} |
|
| 302 |
} |
|
| 303 |
|
|
| 304 |
/** Move keyboard focus to the given component. Note that the component
|
|
| 305 |
may not yet have focus when this method returns.
|
|
| 306 |
*/
|
|
| 307 | 3 |
public void focus(Component comp) { |
| 308 | 3 |
focus(comp, false);
|
| 309 |
} |
|
| 310 |
|
|
| 311 |
/** Use an explicit listener, since hasFocus is not always reliable. */
|
|
| 312 |
private class FocusWatcher extends FocusAdapter { |
|
| 313 |
public volatile boolean focused = false; |
|
| 314 | 24 |
public FocusWatcher(Component c) {
|
| 315 | 24 |
c.addFocusListener(this);
|
| 316 | 24 |
focused = AWT.getFocusOwner() == c; |
| 317 |
} |
|
| 318 | 24 |
public void focusGained(FocusEvent f) { |
| 319 | 24 |
focused = true;
|
| 320 |
} |
|
| 321 | 0 |
public void focusLost(FocusEvent f) { |
| 322 | 0 |
focused = false;
|
| 323 |
} |
|
| 324 |
} |
|
| 325 |
|
|
| 326 |
/** Move keyboard focus to the given component. */
|
|
| 327 | 125 |
public void focus(final Component comp, boolean wait) { |
| 328 | 125 |
Component currentOwner = AWT.getFocusOwner(); |
| 329 | 125 |
if (currentOwner == comp) {
|
| 330 | 101 |
return;
|
| 331 |
} |
|
| 332 | 24 |
Log.debug("Focus change");
|
| 333 | 24 |
FocusWatcher fw = new FocusWatcher(comp);
|
| 334 |
// for pointer focus
|
|
| 335 | 24 |
mouseMove(comp, comp.getWidth()/2, comp.getHeight()/2); |
| 336 | 24 |
waitForIdle(); |
| 337 |
// Make sure the correct window is in front
|
|
| 338 | 24 |
Window w1 = currentOwner != null
|
| 339 |
? AWT.getWindow(currentOwner) : null;
|
|
| 340 | 24 |
Window w2 = AWT.getWindow(comp); |
| 341 | 24 |
if (w1 != w2) {
|
| 342 | 8 |
activate(w2); |
| 343 | 8 |
waitForIdle(); |
| 344 |
} |
|
| 345 |
// NOTE: while it would be nice to have a robot method instead of
|
|
| 346 |
// requesting focus, clicking to change focus may have
|
|
| 347 |
// side effects
|
|
| 348 | 24 |
invokeAndWait(comp, new Runnable() {
|
| 349 | 24 |
public void run() { |
| 350 | 24 |
comp.requestFocus(); |
| 351 |
} |
|
| 352 |
}); |
|
| 353 | 24 |
try {
|
| 354 | 24 |
if (wait) {
|
| 355 | 24 |
long start = System.currentTimeMillis();
|
| 356 | 24 |
while (!fw.focused) {
|
| 357 | 0 |
if (System.currentTimeMillis() - start > componentDelay) {
|
| 358 | 0 |
String msg = |
| 359 |
Strings.get("tester.Robot.focus_failed",
|
|
| 360 |
new Object[] { toString(comp) });
|
|
| 361 | 0 |
throw new ActionFailedException(msg); |
| 362 |
} |
|
| 363 | 0 |
sleep(); |
| 364 |
} |
|
| 365 |
} |
|
| 366 |
} |
|
| 367 |
finally {
|
|
| 368 | 24 |
comp.removeFocusListener(fw); |
| 369 |
} |
|
| 370 |
} |
|
| 371 |
|
|
| 372 |
/** Usually only needed when dealing with Applets. */
|
|
| 373 | 958 |
protected EventQueue getEventQueue(Component c) {
|
| 374 | 958 |
return c != null ? tracker.getQueue(c) : toolkit.getSystemEventQueue(); |
| 375 |
} |
|
| 376 |
|
|
| 377 |
/** Post a runnable on the given component's event queue. Useful when
|
|
| 378 |
driving multiple Applets, but is also useful to ensure an operation
|
|
| 379 |
happens on the event dispatch thread.
|
|
| 380 |
*/
|
|
| 381 | 265 |
public void invokeLater(Component context, Runnable action) { |
| 382 | 265 |
EventQueue queue = getEventQueue(context); |
| 383 | 265 |
queue.postEvent(new InvocationEvent(toolkit, action));
|
| 384 |
} |
|
| 385 |
|
|
| 386 |
/** Post a runnable on the given component's event queue and wait for it
|
|
| 387 |
to finish. */
|
|
| 388 | 227 |
public void invokeAndWait(Component c, Runnable action) { |
| 389 | 227 |
invokeLater(c, action); |
| 390 | 227 |
waitForIdle(); |
| 391 |
} |
|
| 392 |
|
|
| 393 |
/** @deprecated Method renamed to {@link #invokeLater(Runnable)}
|
|
| 394 |
* @param action
|
|
| 395 |
*/
|
|
| 396 | 0 |
public void invokeAction(Runnable action) { |
| 397 | 0 |
invokeLater(action); |
| 398 |
} |
|
| 399 |
|
|
| 400 |
/** @deprecated Method renamed to {@link #invokeLater(Component, Runnable)}
|
|
| 401 |
* @param c
|
|
| 402 |
* @param action
|
|
| 403 |
*/
|
|
| 404 | 0 |
public void invokeAction(Component c, Runnable action) { |
| 405 | 0 |
invokeLater(c, action); |
| 406 |
} |
|
| 407 |
|
|
| 408 |
/** Run the given action on the event dispatch thread. This should be
|
|
| 409 |
* used for any non-read-only methods invoked directly on a GUI
|
|
| 410 |
* component.
|
|
| 411 |
* NOTE: if you want to use the results of the action, use invokeAndWait
|
|
| 412 |
* instead.
|
|
| 413 |
*/
|
|
| 414 | 2 |
public void invokeLater(Runnable action) { |
| 415 | 2 |
invokeLater(null, action);
|
| 416 |
} |
|
| 417 |
|
|
| 418 |
/** Run the given action on the event dispatch thread, but don't return
|
|
| 419 |
until it's been run.
|
|
| 420 |
*/
|
|
| 421 | 152 |
public void invokeAndWait(Runnable action) { |
| 422 | 152 |
invokeAndWait(null, action);
|
| 423 |
} |
|
| 424 |
|
|
| 425 | 150 |
public void keyPress(int keycode) { |
| 426 | 150 |
keyPress(keycode, KeyEvent.CHAR_UNDEFINED); |
| 427 |
} |
|
| 428 |
|
|
| 429 |
/** Send a key press event. */
|
|
| 430 | 910 |
private void keyPress(int keycode, char keyChar) { |
| 431 | 910 |
if (eventMode == EM_ROBOT) {
|
| 432 | 754 |
Log.debug("ROBOT: key press " + AWT.getKeyCode(keycode));
|
| 433 | 754 |
robot.keyPress(keycode); |
| 434 |
} |
|
| 435 |
else {
|
|
| 436 | 156 |
int mods = state.getModifiers();
|
| 437 | 156 |
if (AWT.isModifier(keycode))
|
| 438 | 23 |
mods |= AWT.keyCodeToMask(keycode); |
| 439 | 156 |
postKeyEvent(KeyEvent.KEY_PRESSED, mods, |
| 440 |
keycode, KeyEvent.CHAR_UNDEFINED); |
|
| 441 |
// Auto-generate KEY_TYPED events, as best we can
|
|
| 442 | 156 |
int mask = state.getModifiers();
|
| 443 | 156 |
if (keyChar == KeyEvent.CHAR_UNDEFINED) {
|
| 444 | 28 |
KeyStroke ks = KeyStroke.getKeyStroke(keycode, mask); |
| 445 | 28 |
keyChar = KeyStrokeMap.getChar(ks); |
| 446 |
} |
|
| 447 | 156 |
if (keyChar != KeyEvent.CHAR_UNDEFINED) {
|
| 448 | 132 |
postKeyEvent(KeyEvent.KEY_TYPED, mask, |
| 449 |
KeyEvent.VK_UNDEFINED, keyChar); |
|
| 450 |
} |
|
| 451 |
} |
|
| 452 |
} |
|
| 453 |
|
|
| 454 |
/** Send a key release event. */
|
|
| 455 | 910 |
public void keyRelease(int keycode) { |
| 456 | 910 |
if (eventMode == EM_ROBOT) {
|
| 457 | 754 |
Log.debug("ROBOT: key release " + AWT.getKeyCode(keycode));
|
| 458 | 754 |
robot.keyRelease(keycode); |
| 459 | 754 |
if (Bugs.hasKeyInputDelay()) {
|
| 460 |
// OSX, empirical
|
|
| 461 | 0 |
int KEY_INPUT_DELAY = 200;
|
| 462 | 0 |
if (KEY_INPUT_DELAY > autoDelay) {
|
| 463 | 0 |
delay(KEY_INPUT_DELAY - autoDelay); |
| 464 |
} |
|
| 465 |
} |
|
| 466 |
} |
|
| 467 |
else {
|
|
| 468 | 156 |
int mods = state.getModifiers();
|
| 469 | 156 |
if (AWT.isModifier(keycode))
|
| 470 | 23 |
mods &= ~AWT.keyCodeToMask(keycode); |
| 471 | 156 |
postKeyEvent(KeyEvent.KEY_RELEASED, mods, |
| 472 |
keycode, KeyEvent.CHAR_UNDEFINED); |
|
| 473 |
} |
|
| 474 |
} |
|
| 475 |
|
|
| 476 | 444 |
private void postKeyEvent(int id, int modifiers, int keycode, char ch) { |
| 477 | 444 |
Component c = findFocusOwner(); |
| 478 | 444 |
if (c != null) { |
| 479 | 444 |
postEvent(c, new KeyEvent(c, id,
|
| 480 |
System.currentTimeMillis(), |
|
| 481 |
modifiers, keycode, ch)); |
|
| 482 |
} |
|
| 483 |
else {
|
|
| 484 | 0 |
Log.warn("No component has focus, key press discarded");
|
| 485 |
} |
|
| 486 |
} |
|
| 487 |
|
|
| 488 |
/** Sleep for a little bit, measured in UI time. */
|
|
| 489 | 9288 |
public void sleep() { |
| 490 | 9288 |
delay(SLEEP_INTERVAL); |
| 491 |
} |
|
| 492 |
|
|
| 493 |
/** Sleep the given duration of ms. */
|
|
| 494 | 12154 |
public void delay(int ms) { |
| 495 | 12154 |
if (eventMode == EM_ROBOT) {
|
| 496 | 11118 |
while (ms > MAX_DELAY) {
|
| 497 | 0 |
robot.delay(MAX_DELAY); |
| 498 | 0 |
ms -= MAX_DELAY; |
| 499 |
} |
|
| 500 | 11118 |
robot.delay(ms); |
| 501 |
} |
|
| 502 |
else {
|
|
| 503 | 1036 |
try { Thread.sleep(ms); } catch(InterruptedException ie) { } |
| 504 |
} |
|
| 505 |
} |
|
| 506 |
|
|
| 507 |
private static final Runnable EMPTY_RUNNABLE = |
|
| 508 | 1737 |
new Runnable() { public void run() { } }; |
| 509 |
|
|
| 510 |
/** Check for a blocked event queue (symptomatic of an active w32 AWT
|
|
| 511 |
* popup menu).
|
|
| 512 |
* @return whether the event queue is blocked.
|
|
| 513 |
*/
|
|
| 514 | 10 |
protected boolean queueBlocked() { |
| 515 | 10 |
return postInvocationEvent(toolkit.getSystemEventQueue(),
|
| 516 |
toolkit, 200); |
|
| 517 |
} |
|
| 518 |
|
|
| 519 |
/** @return whether we timed out waiting for the invocation to run */
|
|
| 520 | 1737 |
protected boolean postInvocationEvent(EventQueue eq, Toolkit toolkit, |
| 521 |
long timeout) {
|
|
| 522 |
class RobotIdleLock { }
|
|
| 523 | 1737 |
Object lock = new RobotIdleLock();
|
| 524 | 1737 |
synchronized(lock) {
|
| 525 | 1737 |
eq.postEvent(new InvocationEvent(toolkit, EMPTY_RUNNABLE,
|
| 526 |
lock, true));
|
|
| 527 | 1737 |
long start = System.currentTimeMillis();
|
| 528 | 1737 |
try {
|
| 529 |
// NOTE: on fast linux systems when showing a dialog, if we
|
|
| 530 |
// don't provide a timeout, we're never notified, and the
|
|
| 531 |
// test will wait forever (up through 1.5.0_05).
|
|
| 532 | 1737 |
lock.wait(timeout); |
| 533 | 1737 |
return (System.currentTimeMillis() - start) >= IDLE_TIMEOUT;
|
| 534 |
} |
|
| 535 |
catch(InterruptedException e) {
|
|
| 536 | 0 |
Log.warn("Invocation lock interrupted");
|
| 537 |
} |
|
| 538 |
} |
|
| 539 | 0 |
return false; |
| 540 |
} |
|
| 541 |
|
|
| 542 | 1724 |
private void waitForIdle(EventQueue eq) { |
| 543 | 1724 |
if (EventQueue.isDispatchThread())
|
| 544 | 0 |
throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread"); |
| 545 |
|
|
| 546 |
// NOTE: as of Java 1.3.1, robot.waitForIdle only waits for the
|
|
| 547 |
// last event on the queue at the time of this invocation to be
|
|
| 548 |
// processed. We need better than that. Make sure the given event
|
|
| 549 |
// queue is empty when this method returns
|
|
| 550 |
|
|
| 551 |
// We always post at least one idle event to allow any current event
|
|
| 552 |
// dispatch processing to finish.
|
|
| 553 | 1724 |
long start = System.currentTimeMillis();
|
| 554 | 1724 |
int count = 0;
|
| 555 | 1724 |
do {
|
| 556 |
|
|
| 557 | 1727 |
if (postInvocationEvent(eq, toolkit, IDLE_TIMEOUT)) {
|
| 558 | 1 |
Log.warn("Timed out waiting for posted invocation event: "
|
| 559 |
+ IDLE_TIMEOUT + "ms (after " + count + " events)", |
|
| 560 |
Log.FULL_STACK); |
|
| 561 | 1 |
break;
|
| 562 |
} |
|
| 563 | 1726 |
if (System.currentTimeMillis() - start > IDLE_TIMEOUT) {
|
| 564 | 0 |
Log.warn("Timed out waiting for idle event queue after "
|
| 565 |
+ count + " events");
|
|
| 566 | 0 |
break;
|
| 567 |
} |
|
| 568 |
|
|
| 569 | 1726 |
++count; |
| 570 |
|
|
| 571 |
// NOTE: this does not detect invocation events (i.e. what
|
|
| 572 |
// gets posted with EventQueue.invokeLater), so if someone
|
|
| 573 |
// is repeatedly posting one, we might get stuck. Not too
|
|
| 574 |
// worried, since if a Runnable keeps calling invokeLater
|
|
| 575 |
// on itself, *nothing* else gets much chance to run, so it
|
|
| 576 |
// seems to be a bad programming practice.
|
|
| 577 | 1726 |
} while (eq.peekEvent() != null); |
| 578 |
} |
|
| 579 |
|
|
| 580 |
/**
|
|
| 581 |
* Wait for an idle AWT event queue. Note that this is different from
|
|
| 582 |
* the implementation of <code>java.awt.Robot.waitForIdle()</code>, which
|
|
| 583 |
* may have events on the queue when it returns. Do <b>NOT</b> use this
|
|
| 584 |
* method if there are animations or other continual refreshes happening,
|
|
| 585 |
* since in that case it may never return.<p>
|
|
| 586 |
*/
|
|
| 587 | 1646 |
public void waitForIdle() { |
| 588 | 1646 |
if (eventPostDelay > autoDelay) {
|
| 589 | 1646 |
delay(eventPostDelay - autoDelay); |
| 590 |
} |
|
| 591 | 1646 |
Collection queues = tracker.getEventQueues(); |
| 592 | 1646 |
if (queues.size() == 1) {
|
| 593 | 1598 |
waitForIdle(toolkit.getSystemEventQueue()); |
| 594 |
} |
|
| 595 |
else {
|
|
| 596 |
// FIXME this resurrects dead event queues
|
|
| 597 | 48 |
Iterator iter = queues.iterator(); |
| 598 | 48 |
while (iter.hasNext()) {
|
| 599 | 126 |
EventQueue eq = (EventQueue)iter.next(); |
| 600 | 126 |
waitForIdle(eq); |
| 601 |
} |
|
| 602 |
} |
|
| 603 |
} |
|
| 604 |
|
|
| 605 |
/** Sample the color at the given point on the screen. */
|
|
| 606 | 6 |
public Color sample(int x, int y) { |
| 607 |
// Service mode always returns black when sampled
|
|
| 608 | 6 |
if (robot != null && !serviceMode) |
| 609 | 6 |
return robot.getPixelColor(x, y);
|
| 610 | 0 |
String msg = Strings.get("tester.Robot.no_sample");
|
| 611 | 0 |
throw new UnsupportedOperationException(msg); |
| 612 |
} |
|
| 613 |
|
|
| 614 |
/** Sample the color at the given point on the component. */
|
|
| 615 | 0 |
public Color sample(Component c, int x, int y) { |
| 616 | 0 |
Point p = AWT.getLocationOnScreen(c); |
| 617 | 0 |
return sample(p.x + x, p.y + y);
|
| 618 |
} |
|
| 619 |
|
|
| 620 |
/** Capture the contents of the given rectangle. */
|
|
| 621 |
/* NOTE: Text components (and maybe others with a custom cursor) will
|
|
| 622 |
* capture the cursor. May want to move the cursor out of the component
|
|
| 623 |
* bounds, although this might cause issues where the component is
|
|
| 624 |
* responding visually to mouse movement.
|
|
| 625 |
* Is this an OSX bug?
|
|
| 626 |
*/
|
|
| 627 | 3 |
public BufferedImage capture(Rectangle bounds) {
|
| 628 | 3 |
return robot != null ? robot.createScreenCapture(bounds) : null; |
| 629 |
} |
|
| 630 |
|
|
| 631 |
/** Capture the contents of the given component, sans any border or
|
|
| 632 |
* insets. This should only be used on components that do not use a LAF
|
|
| 633 |
* UI, or the results will not be consistent across platforms.
|
|
| 634 |
*/
|
|
| 635 | 3 |
public BufferedImage capture(Component comp) {
|
| 636 | 3 |
return capture(comp, true); |
| 637 |
} |
|
| 638 |
|
|
| 639 |
/** Capture the contents of the given component, optionally including the
|
|
| 640 |
* border and/or insets. This should only be used on components that do
|
|
| 641 |
* not use a LAF UI, or the results will not be consistent across
|
|
| 642 |
* platforms.
|
|
| 643 |
*/
|
|
| 644 | 3 |
public BufferedImage capture(Component comp, boolean ignoreBorder) { |
| 645 | 3 |
Rectangle bounds = new Rectangle(comp.getSize());
|
| 646 | 3 |
Point loc = AWT.getLocationOnScreen(comp); |
| 647 | 3 |
bounds.x = loc.x; |
| 648 | 3 |
bounds.y = loc.y; |
| 649 | 3 |
Log.debug("Component bounds " + bounds);
|
| 650 |
// Make sure we don't sample the decorating space
|
|
| 651 | 3 |
if (ignoreBorder && (comp instanceof Container)) { |
| 652 | 3 |
Insets insets = ((Container)comp).getInsets(); |
| 653 | 3 |
bounds.x += insets.left; |
| 654 | 3 |
bounds.y += insets.top; |
| 655 | 3 |
bounds.width -= insets.left + insets.right; |
| 656 | 3 |
bounds.height -= insets.top + insets.bottom; |
| 657 | 3 |
Log.debug("Component insets " + insets);
|
| 658 | 3 |
Log.debug("Component bounds " + bounds);
|
| 659 |
} |
|
| 660 | 3 |
return capture(bounds);
|
| 661 |
} |
|
| 662 |
|
|
| 663 |
// Bug workaround support
|
|
| 664 | 275 |
protected void jitter(Component comp, int x, int y) { |
| 665 | 275 |
mouseMove(comp, (x > 0 ? x - 1 : x + 1), y); |
| 666 |
} |
|
| 667 |
|
|
| 668 |
// Bug workaround support
|
|
| 669 | 0 |
protected void jitter(int x, int y) { |
| 670 | 0 |
mouseMove((x > 0 ? x - 1 : x + 1), y); |
| 671 |
} |
|
| 672 |
|
|
| 673 |
/** Move the pointer to the center of the given component. */
|
|
| 674 | 44 |
public void mouseMove(Component comp) { |
| 675 | 44 |
mouseMove(comp, comp.getWidth() / 2, comp.getHeight() / 2); |
| 676 |
} |
|
| 677 |
|
|
| 678 |
/** Wait the given number of ms for the component to be showing and
|
|
| 679 |
ready. Returns false if the operation times out. */
|
|
| 680 | 1474 |
private boolean waitForComponent(Component c, long delay) { |
| 681 | 1474 |
if (!isReadyForInput(c)) {
|
| 682 | 134 |
Log.debug("Waiting for component to show");
|
| 683 | 134 |
long start = System.currentTimeMillis();
|
| 684 | 134 |
while (!isReadyForInput(c)) {
|
| 685 | 735 |
if (c instanceof JPopupMenu) { |
| 686 |
// wiggle the mouse over the parent menu item to
|
|
| 687 |
// ensure the submenu shows
|
|
| 688 | 275 |
Component invoker = ((JPopupMenu)c).getInvoker(); |
| 689 | 275 |
if (invoker instanceof JMenu) { |
| 690 | 275 |
jitter(invoker, invoker.getWidth()/2, |
| 691 |
invoker.getHeight()/2); |
|
| 692 |
} |
|
| 693 |
} |
|
| 694 | 735 |
if (System.currentTimeMillis() - start > delay) {
|
| 695 | 0 |
Log.warn("Component " + toString(c)
|
| 696 |
+ " (" + Integer.toHexString(c.hashCode()) + ")" |
|
| 697 |
+ " not ready after " + delay + "ms: " |
|
| 698 |
+ "showing=" + c.isShowing()
|
|
| 699 |
+ " win ready="
|
|
| 700 |
+ tracker.isWindowReady(AWT.getWindow(c))); |
|
| 701 | 0 |
return false; |
| 702 |
} |
|
| 703 | 735 |
sleep(); |
| 704 |
} |
|
| 705 |
} |
|
| 706 | 1474 |
return true; |
| 707 |
} |
|
| 708 |
|
|
| 709 |
/** If a component does not have mouse events enabled, use the
|
|
| 710 |
first ancestor which does.
|
|
| 711 |
*/
|
|
| 712 | 516 |
private Component retargetMouseEvent(Component comp, int id, Point pt) { |
| 713 | 516 |
Point where = pt; |
| 714 | 516 |
while (!(comp instanceof Window) |
| 715 |
&& !AWT.eventTypeEnabled(comp, id)) {
|
|
| 716 | 45 |
Log.debug("Retargeting event, " + toString(comp)
|
| 717 |
+ " not interested");
|
|
| 718 | 45 |
where = SwingUtilities.convertPoint(comp, where.x, where.y, |
| 719 |
comp.getParent()); |
|
| 720 | 45 |
comp = comp.getParent(); |
| 721 |
} |
|
| 722 | 516 |
pt.setLocation(where); |
| 723 | 516 |
return comp;
|
| 724 |
} |
|
| 725 |
|
|
| 726 |
/** Move the pointer to the given coordinates relative to the given
|
|
| 727 |
* component.
|
|
| 728 |
*/
|
|
| 729 | 1425 |
public void mouseMove(Component comp, int x, int y) { |
| 730 | 1425 |
if (!waitForComponent(comp, componentDelay)) {
|
| 731 | 0 |
String msg = "Can't obtain position of component "
|
| 732 |
+ toString(comp); |
|
| 733 | 0 |
throw new ComponentNotShowingException(msg); |
| 734 |
} |
|
| 735 | 1425 |
if (eventMode == EM_ROBOT) {
|
| 736 | 1235 |
try {
|
| 737 | 1235 |
Point point = AWT.getLocationOnScreen(comp); |
| 738 | 1235 |
if (point != null) { |
| 739 | 1235 |
point.translate(x, y); |
| 740 | 1235 |
mouseMove(point.x, point.y); |
| 741 |
} |
|
| 742 |
} |
|
| 743 |
catch(java.awt.IllegalComponentStateException e) {
|
|
| 744 |
} |
|
| 745 |
} |
|
| 746 |
else {
|
|
| 747 | 190 |
Component eventSource = comp; |
| 748 | 190 |
int id = MouseEvent.MOUSE_MOVED;
|
| 749 | 190 |
boolean outside = false; |
| 750 |
|
|
| 751 |
// When dragging, the event source is always the target of the
|
|
| 752 |
// original mouse press.
|
|
| 753 | 190 |
if (state.isDragging()) {
|
| 754 | 0 |
id = MouseEvent.MOUSE_DRAGGED; |
| 755 | 0 |
eventSource = state.getDragSource(); |
| 756 |
} |
|
| 757 |
else {
|
|
| 758 | 190 |
Point pt = new Point(x, y);
|
| 759 | 190 |
eventSource = comp = retargetMouseEvent(comp, id, pt); |
| 760 | 190 |
x = pt.x; y = pt.y; |
| 761 | 190 |
outside = x < 0 || y < 0 |
| 762 |
|| x >= comp.getWidth() || y >= comp.getHeight(); |
|
| 763 |
} |
|
| 764 |
|
|
| 765 | 190 |
Component current = state.getMouseComponent(); |
| 766 | 190 |
if (current != comp) {
|
| 767 | 54 |
if (outside && current != null) { |
| 768 | 0 |
Point pt = |
| 769 |
SwingUtilities.convertPoint(comp, x, y, current); |
|
| 770 | 0 |
postMouseMotion(current, MouseEvent.MOUSE_EXITED, pt); |
| 771 | 0 |
return;
|
| 772 |
} |
|
| 773 | 54 |
postMouseMotion(comp, MouseEvent.MOUSE_ENTERED, |
| 774 |
new Point(x, y));
|
|
| 775 |
} |
|
| 776 | 190 |
Point pt = new Point(x, y);
|
| 777 | 190 |
if (id == MouseEvent.MOUSE_DRAGGED) {
|
| 778 |
// Drag coordinates are relative to drag source component
|
|
| 779 | 0 |
pt = SwingUtilities.convertPoint(comp, pt, eventSource); |
| 780 |
} |
|
| 781 | 190 |
postMouseMotion(eventSource, id, pt); |
| 782 |
// Add an exit event if warranted
|
|
| 783 | 190 |
if (outside) {
|
| 784 | 0 |
postMouseMotion(comp, MouseEvent.MOUSE_EXITED, |
| 785 |
new Point(x, y));
|
|
| 786 |
} |
|
| 787 |
} |
|
| 788 |
} |
|
| 789 |
|
|
| 790 |
/** Move the mouse appropriately to get from the source to the
|
|
| 791 |
destination. Enter/exit events will be generated where appropriate.
|
|
| 792 |
*/
|
|
| 793 | 38 |
public void dragOver(Component dst, int x, int y) { |
| 794 | 38 |
mouseMove(dst, x-4, y); |
| 795 | 38 |
mouseMove(dst, x, y); |
| 796 |
} |
|
| 797 |
|
|
| 798 |
/** Begin a drag operation using button 1.<p>
|
|
| 799 |
This method is tuned for native drag/drop operations, so if you get
|
|
| 800 |
odd behavior, you might try using a simple
|
|
| 801 |
{@link #mousePress(Component,int,int)} instead.
|
|
| 802 |
*/
|
|
| 803 | 6 |
public void drag(Component src, int sx, int sy) { |
| 804 | 6 |
drag(src, sx, sy, InputEvent.BUTTON1_MASK); |
| 805 |
} |
|
| 806 |
|
|
| 807 |
|
|
| 808 |
/** Begin a drag operation using the given button mask.<p>
|
|
| 809 |
This method is tuned for native drag/drop operations, so if you get
|
|
| 810 |
odd behavior, you might try using a simple
|
|
| 811 |
{@link #mousePress(Component,int,int,int)} instead.
|
|
| 812 |
*/
|
|
| 813 |
// TODO: maybe auto-switch to robot mode if available?
|
|
| 814 | 35 |
public void drag(Component src, int sx, int sy, int buttons) { |
| 815 | 35 |
if (Bugs.dragDropRequiresNativeEvents()
|
| 816 |
&& eventMode != EM_ROBOT |
|
| 817 |
&& !Boolean.getBoolean("abbot.ignore_drag_error")) {
|
|
| 818 | 0 |
String msg = Strings.get("abbot.Robot.no_drag_available");
|
| 819 | 0 |
if (serviceMode) {
|
| 820 |
// If we start a native drag in this mode, it'll pretty much
|
|
| 821 |
// lock up the system, apparently with the native AWT libs
|
|
| 822 |
// starting a thread invisible to the VM that chews up all CPU
|
|
| 823 |
// time.
|
|
| 824 | 0 |
throw new ActionFailedException(msg); |
| 825 |
} |
|
| 826 | 0 |
Log.warn(msg); |
| 827 |
} |
|
| 828 |
|
|
| 829 |
// Some platforms require a pause between mouse down and mouse motion
|
|
| 830 | 35 |
int DRAG_DELAY =
|
| 831 |
Properties.getProperty("abbot.robot.drag_delay",
|
|
| 832 | 35 |
Platform.isX11() || Platform.isOSX() |
| 833 |
? 100 : 0, 0, |
|
| 834 |
60000); |
|
| 835 |
|
|
| 836 | 35 |
mousePress(src, sx, sy, buttons); |
| 837 | 35 |
if (DRAG_DELAY > autoDelay)
|
| 838 | 0 |
delay(DRAG_DELAY); |
| 839 | 35 |
if (Platform.isWindows() || Platform.isMacintosh()) {
|
| 840 | 35 |
int dx = sx + AWTConstants.DRAG_THRESHOLD < src.getWidth()
|
| 841 |
? AWTConstants.DRAG_THRESHOLD : 0; |
|
| 842 | 35 |
int dy = sy + AWTConstants.DRAG_THRESHOLD < src.getHeight()
|
| 843 |
? AWTConstants.DRAG_THRESHOLD : 0; |
|
| 844 | 35 |
if (dx == 0 && dy == 0)
|
| 845 | 0 |
dx = AWTConstants.DRAG_THRESHOLD; |
| 846 | 35 |
mouseMove(src, sx + dx/4, sy + dy/4); |
| 847 | 35 |
mouseMove(src, sx + dx/2, sy + dy/2); |
| 848 | 35 |
mouseMove(src, sx + dx, sy + dy); |
| 849 | 35 |
mouseMove(src, sx + dx + 1, sy + dy); |
| 850 |
} |
|
| 851 |
else {
|
|
| 852 | 0 |
mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD/2, sy + AWTConstants.DRAG_THRESHOLD/2); |
| 853 | 0 |
mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD, sy + AWTConstants.DRAG_THRESHOLD); |
| 854 | 0 |
mouseMove(src, sx + AWTConstants.DRAG_THRESHOLD/2, sy + AWTConstants.DRAG_THRESHOLD/2); |
| 855 | 0 |
mouseMove(src, sx, sy); |
| 856 |
} |
|
| 857 |
} |
|
| 858 |
|
|
| 859 |
/** End a drag operation, releasing the mouse button over the given target
|
|
| 860 |
location.<p>
|
|
| 861 |
This method is tuned for native drag/drop operations, so if you get
|
|
| 862 |
odd behavior, you might try using a simple
|
|
| 863 |
{@link #mouseMove(Component,int,int)}, {@link #mouseRelease()}
|
|
| 864 |
instead.
|
|
| 865 |
*/
|
|
| 866 | 35 |
public void drop(Component target, int x, int y) { |
| 867 |
// Delay between final move and drop to ensure drop ends.
|
|
| 868 | 35 |
int DROP_DELAY =
|
| 869 |
Properties.getProperty("abbot.robot.drop_delay",
|
|
| 870 | 35 |
Platform.isWindows() ? 200 : 0, 0, 60000); |
| 871 |
|
|
| 872 |
// All motion events are relative to the drag source
|
|
| 873 | 35 |
long start = System.currentTimeMillis();
|
| 874 | 35 |
while (!state.isDragging()) {
|
| 875 | 0 |
if (System.currentTimeMillis() - start > eventPostDelay) {
|
| 876 | 0 |
String msg = Strings.get("Robot.no_current_drag");
|
| 877 | 0 |
throw new ActionFailedException(msg); |
| 878 |
} |
|
| 879 | 0 |
sleep(); |
| 880 |
} |
|
| 881 | 35 |
dragOver(target, x, y); |
| 882 | 35 |
if (DROP_DELAY > autoDelay)
|
| 883 | 35 |
delay(DROP_DELAY - autoDelay); |
| 884 |
|
|
| 885 | 35 |
mouseRelease(state.getButtons()); |
| 886 |
} |
|
| 887 |
|
|
| 888 |
/** Generate a mouse enter/exit/move/drag for the destination component.
|
|
| 889 |
* NOTE: The VM automatically usually generates exit events; need a test
|
|
| 890 |
* to define the behavior, though.
|
|
| 891 |
*/
|
|
| 892 | 244 |
private void postMouseMotion(Component dst, int id, Point to) { |
| 893 |
// The VM auto-generates exit events as needed (1.3, 1.4)
|
|
| 894 | 244 |
if (id != MouseEvent.MOUSE_DRAGGED)
|
| 895 | 244 |
dst = retargetMouseEvent(dst, id, to); |
| 896 |
// Avoid multiple moves to the same location
|
|
| 897 | 244 |
if (state.getMouseComponent() != dst
|
| 898 |
|| !to.equals(state.getMouseLocation())) {
|
|
| 899 | 86 |
postEvent(dst, new MouseEvent(dst, id, System.currentTimeMillis(),
|
| 900 |
state.getModifiers(), to.x, to.y, |
|
| 901 |
state.getClickCount(), false));
|
|
| 902 |
} |
|
| 903 |
} |
|
| 904 |
|
|
| 905 |
/** Type the given keycode with no modifiers. */
|
|
| 906 | 8 |
public void key(int keycode) { |
| 907 | 8 |
key(keycode, 0); |
| 908 |
} |
|
| 909 |
|
|
| 910 |
/** Press or release the appropriate modifiers corresponding to the given
|
|
| 911 |
mask.
|
|
| 912 |
*/
|
|
| 913 | 2934 |
public void setModifiers(int modifiers, boolean press) { |
| 914 | 2934 |
boolean altGraph = (modifiers & InputEvent.ALT_GRAPH_MASK) != 0;
|
| 915 | 2934 |
boolean shift = (modifiers & InputEvent.SHIFT_MASK) != 0;
|
| 916 | 2934 |
boolean alt = (modifiers & InputEvent.ALT_MASK) != 0;
|
| 917 | 2934 |
boolean ctrl = (modifiers & InputEvent.CTRL_MASK) != 0;
|
| 918 | 2934 |
boolean meta = (modifiers & InputEvent.META_MASK) != 0;
|
| 919 | 2934 |
if (press) {
|
| 920 | 0 |
if (altGraph) keyPress(KeyEvent.VK_ALT_GRAPH);
|
| 921 | 6 |
if (alt) keyPress(KeyEvent.VK_ALT);
|
| 922 | 111 |
if (shift) keyPress(KeyEvent.VK_SHIFT);
|
| 923 | 24 |
if (ctrl) keyPress(KeyEvent.VK_CONTROL);
|
| 924 | 0 |
if (meta) keyPress(KeyEvent.VK_META);
|
| 925 |
} |
|
| 926 |
else {
|
|
| 927 |
// For consistency, release in the reverse order of press
|
|
| 928 | 0 |
if (meta) keyRelease(KeyEvent.VK_META);
|
| 929 | 24 |
if (ctrl) keyRelease(KeyEvent.VK_CONTROL);
|
| 930 | 111 |
if (shift) keyRelease(KeyEvent.VK_SHIFT);
|
| 931 | 6 |
if (alt) keyRelease(KeyEvent.VK_ALT);
|
| 932 | 0 |
if (altGraph) keyRelease(KeyEvent.VK_ALT_GRAPH);
|
| 933 |
} |
|
| 934 |
} |
|
| 935 |
|
|
| 936 |
/** Type the given keycode with the given modifiers. Modifiers is a mask
|
|
| 937 |
* from the available InputEvent masks.
|
|
| 938 |
*/
|
|
| 939 | 112 |
public void key(int keycode, int modifiers) { |
| 940 | 112 |
key(KeyEvent.CHAR_UNDEFINED, keycode, modifiers); |
| 941 |
} |
|
| 942 |
|
|
| 943 | 760 |
private void key(char ch, int keycode, int modifiers) { |
| 944 | 760 |
Log.debug("key keycode=" + AWT.getKeyCode(keycode)
|
| 945 |
+ " mod=" + AWT.getKeyModifiers(modifiers));
|
|
| 946 | 760 |
boolean isModifier = true; |
| 947 | 760 |
switch(keycode) {
|
| 948 | 0 |
case KeyEvent.VK_ALT_GRAPH:
|
| 949 | 0 |
modifiers |= InputEvent.ALT_GRAPH_MASK; break;
|
| 950 | 0 |
case KeyEvent.VK_ALT:
|
| 951 | 0 |
modifiers |= InputEvent.ALT_MASK; break;
|
| 952 | 0 |
case KeyEvent.VK_SHIFT:
|
| 953 | 0 |
modifiers |= InputEvent.SHIFT_MASK; break;
|
| 954 | 0 |
case KeyEvent.VK_CONTROL:
|
| 955 | 0 |
modifiers |= InputEvent.CTRL_MASK; break;
|
| 956 | 0 |
case KeyEvent.VK_META:
|
| 957 | 0 |
modifiers |= InputEvent.META_MASK;break;
|
| 958 | 760 |
default: isModifier = false; break; |
| 959 |
} |
|
| 960 | 760 |
setModifiers(modifiers, true);
|
| 961 | 760 |
if (!isModifier) {
|
| 962 | 760 |
keyPress(keycode, ch); |
| 963 | 760 |
keyRelease(keycode); |
| 964 |
} |
|
| 965 | 760 |
setModifiers(modifiers, false);
|
| 966 | 760 |
if (Bugs.hasKeyStrokeGenerationBug())
|
| 967 | 0 |
delay(100); |
| 968 |
} |
|
| 969 |
|
|
| 970 |
/**
|
|
| 971 |
* Type the given character. Note that this sends the key to whatever
|
|
| 972 |
* component currently has the focus.
|
|
| 973 |
*/
|
|
| 974 |
// FIXME should this be renamed to "key"?
|
|
| 975 | 656 |
public void keyStroke(char ch) { |
| 976 | 656 |
KeyStroke ks = KeyStrokeMap.getKeyStroke(ch); |
| 977 | 656 |
if (ks == null) { |
| 978 |
// If no mapping is available, we omit press/release events and
|
|
| 979 |
// only generate a KEY_TYPED event
|
|
| 980 | 8 |
Log.debug("No key mapping for '" + ch + "'"); |
| 981 | 8 |
Component focus = findFocusOwner(); |
| 982 | 8 |
if (focus == null) { |
| 983 | 0 |
Log.warn("No component has focus, keystroke discarded",
|
| 984 |
Log.FULL_STACK); |
|
| 985 | 0 |
return;
|
| 986 |
} |
|
| 987 | 8 |
KeyEvent ke = new KeyEvent(focus, KeyEvent.KEY_TYPED,
|
| 988 |
System.currentTimeMillis(), |
|
| 989 |
0, KeyEvent.VK_UNDEFINED, ch); |
|
| 990 |
// Allow any pending robot events to complete; otherwise we
|
|
| 991 |
// might stuff the typed event before previous robot-generated
|
|
| 992 |
// events are posted.
|
|
| 993 | 8 |
if (eventMode == EM_ROBOT)
|
| 994 | 4 |
waitForIdle(); |
| 995 | 8 |
postEvent(focus, ke); |
| 996 |
} |
|
| 997 |
else {
|
|
| 998 | 648 |
int keycode = ks.getKeyCode();
|
| 999 | 648 |
int mod = ks.getModifiers();
|
| 1000 | 648 |
Log.debug("Char '" + ch + "' generated by keycode=" |
| 1001 |
+ keycode + " mod=" + mod);
|
|
| 1002 | 648 |
key(ch, keycode, mod); |
| 1003 |
} |
|
| 1004 |
} |
|
| 1005 |
|
|
| 1006 |
/** Type the given string. */
|
|
| 1007 | 34 |
public void keyString(String str) { |
| 1008 | 34 |
char[] ch = str.toCharArray();
|
| 1009 | 34 |
for (int i=0;i < ch.length;i++) { |
| 1010 | 655 |
keyStroke(ch[i]); |
| 1011 |
} |
|
| 1012 |
} |
|
| 1013 |
|
|
| 1014 | 2 |
public void mousePress(Component comp) { |
| 1015 | 2 |
mousePress(comp, InputEvent.BUTTON1_MASK); |
| 1016 |
} |
|
| 1017 |
|
|
| 1018 | 2 |
public void mousePress(Component comp, int mask) { |
| 1019 | 2 |
mousePress(comp, comp.getWidth() / 2, comp.getHeight() / 2, mask); |
| 1020 |
} |
|
| 1021 |
|
|
| 1022 | 19 |
public void mousePress(Component comp, int x, int y) { |
| 1023 | 19 |
mousePress(comp, x, y, InputEvent.BUTTON1_MASK); |
| 1024 |
} |
|
| 1025 |
|
|
| 1026 |
/** Mouse down in the given part of the component. All other mousePress
|
|
| 1027 |
methods must eventually invoke this one.
|
|
| 1028 |
*/
|
|
| 1029 | 763 |
public void mousePress(Component comp, int x, int y, int mask) { |
| 1030 | 763 |
if (eventMode == EM_ROBOT && Bugs.hasRobotMotionBug()) {
|
| 1031 | 0 |
jitter(comp, x, y); |
| 1032 |
} |
|
| 1033 | 763 |
mouseMove(comp, x, y); |
| 1034 | 763 |
if (eventMode == EM_ROBOT)
|
| 1035 | 723 |
mousePress(mask); |
| 1036 |
else {
|
|
| 1037 | 40 |
postMousePress(comp, x, y, mask); |
| 1038 |
} |
|
| 1039 |
} |
|
| 1040 |
|
|
| 1041 |
/** Post a mouse press event to the AWT event queue for the given
|
|
| 1042 |
component.
|
|
| 1043 |
*/
|
|
| 1044 | 41 |
private void postMousePress(Component comp, int x, int y, int mask) { |
| 1045 | 41 |
long when = lastMousePress != null |
| 1046 |
? lastMousePress.getWhen() : 0; |
|
| 1047 | 41 |
long now = System.currentTimeMillis();
|
| 1048 | 41 |
int count = 1;
|
| 1049 | 41 |
Point where = new Point(x, y);
|
| 1050 | 41 |
comp = retargetMouseEvent(comp, MouseEvent.MOUSE_PRESSED, where); |
| 1051 | 41 |
if (countingClicks
|
| 1052 |
&& comp == lastMousePress.getComponent()) {
|
|
| 1053 | 1 |
long delta = now - when;
|
| 1054 | 1 |
if (delta < AWTConstants.MULTI_CLICK_INTERVAL) {
|
| 1055 | 1 |
count = state.getClickCount() + 1; |
| 1056 |
} |
|
| 1057 |
} |
|
| 1058 | 41 |
postEvent(comp, new MouseEvent(comp, MouseEvent.MOUSE_PRESSED, now,
|
| 1059 |
state.getKeyModifiers() | mask, |
|
| 1060 |
where.x, where.y, count, |
|
| 1061 |
AWTConstants.POPUP_ON_PRESS |
|
| 1062 |
&& (mask & AWTConstants.POPUP_MASK) != 0)); |
|
| 1063 |
} |
|
| 1064 |
|
|
| 1065 |
/** Post a mouse release event to the AWT event queue for the given
|
|
| 1066 |
component.
|
|
| 1067 |
*/
|
|
| 1068 | 41 |
private void postMouseRelease(Component c, int x, int y, int mask) { |
| 1069 | 41 |
long now = System.currentTimeMillis();
|
| 1070 | 41 |
int count = state.getClickCount();
|
| 1071 | 41 |
Point where = new Point(x, y);
|
| 1072 | 41 |
c = retargetMouseEvent(c, MouseEvent.MOUSE_PRESSED, where); |
| 1073 | 41 |
postEvent(c, new MouseEvent(c, MouseEvent.MOUSE_RELEASED, now,
|
| 1074 |
state.getKeyModifiers() | mask, |
|
| 1075 |
where.x, where.y, count, |
|
| 1076 |
!AWTConstants.POPUP_ON_PRESS |
|
| 1077 |
&& (mask & AWTConstants.POPUP_MASK) != 0)); |
|
| 1078 |
} |
|
| 1079 |
|
|
| 1080 |
/** Click in the center of the given component. */
|
|
| 1081 | 414 |
final public void click(Component comp) { |
| 1082 | 414 |
click(comp, comp.getWidth()/2, comp.getHeight()/2); |
| 1083 |
} |
|
| 1084 |
|
|
| 1085 |
/** Click in the center of the given component, specifying which button. */
|
|
| 1086 | 4 |
final public void click(Component comp, int mask) { |
| 1087 | 4 |
click(comp, comp.getWidth()/2, comp.getHeight()/2, mask); |
| 1088 |
} |
|
| 1089 |
|
|
| 1090 |
/** Click in the component at the given location. */
|
|
| 1091 | 446 |
final public void click(Component comp, int x, int y) { |
| 1092 | 446 |
click(comp, x, y, InputEvent.BUTTON1_MASK); |
| 1093 |
} |
|
| 1094 |
|
|
| 1095 |
/** Click in the component at the given location with the given button. */
|
|
| 1096 | 467 |
final public void click(Component comp, int x, int y, int mask) { |
| 1097 | 467 |
click(comp, x, y, mask, 1); |
| 1098 |
} |
|
| 1099 |
|
|
| 1100 |
/** Click in the given part of the component. All other click methods
|
|
| 1101 |
* must eventually invoke this one. This method sometimes needs to be
|
|
| 1102 |
* redefined (i.e. JComponent to scroll before clicking).
|
|
| 1103 |
*/
|
|
| 1104 | 707 |
public void click(Component comp, int x, int y, int mask, int count) { |
| 1105 | 707 |
Log.debug("Click at (" + x + "," + y + ") on " + toString(comp) |
| 1106 | 707 |
+ (count > 1 ? (" count=" + count) : "")); |
| 1107 | 707 |
int keyModifiers = mask & ~AWTConstants.BUTTON_MASK;
|
| 1108 | 707 |
mask &= AWTConstants.BUTTON_MASK; |
| 1109 | 707 |
setModifiers(keyModifiers, true);
|
| 1110 |
// Adjust the auto-delay to ensure we actually get a multiple click
|
|
| 1111 |
// In general clicks have to be less than 200ms apart, although the
|
|
| 1112 |
// actual setting is not readable by java that I'm aware of.
|
|
| 1113 | 707 |
int oldDelay = getAutoDelay();
|
| 1114 | 707 |
if (count > 1 && oldDelay * 2 > 200) {
|
| 1115 | 0 |
setAutoDelay(0); |
| 1116 |
} |
|
| 1117 | 707 |
long last = System.currentTimeMillis();
|
| 1118 | 707 |
mousePress(comp, x, y, mask); |
| 1119 | 707 |
while (count-- > 1) {
|
| 1120 | 19 |
mouseRelease(mask); |
| 1121 | 19 |
long delta = System.currentTimeMillis() - last;
|
| 1122 | 19 |
if (delta > AWTConstants.MULTI_CLICK_INTERVAL)
|
| 1123 | 0 |
Log.warn("Unexpected delay in multi-click: " + delta);
|
| 1124 | 19 |
last = System.currentTimeMillis(); |
| 1125 | 19 |
mousePress(mask); |
| 1126 |
} |
|
| 1127 | 707 |
setAutoDelay(oldDelay); |
| 1128 | 707 |
mouseRelease(mask); |
| 1129 | 707 |
setModifiers(keyModifiers, false);
|
| 1130 |
} |
|
| 1131 |
|
|
| 1132 |
/** @deprecated Renamed to {@link #selectAWTMenuItem(Frame,String)}. */
|
|
| 1133 | 0 |
public void selectAWTMenuItemByLabel(Frame frame, String path) { |
| 1134 | 0 |
selectAWTMenuItem(frame, path); |
| 1135 |
} |
|
| 1136 |
|
|
| 1137 |
/** Select the given menu item from the given Frame. The given String may
|
|
| 1138 |
be either a label or path of labels, but must uniquely identify the
|
|
| 1139 |
menu item. For example, "Copy" would be valid if there is only one
|
|
| 1140 |
instance of that menu label under the MenuBar, otherwise you would
|
|
| 1141 |
need to specify "Edit|Copy" to ensure the proper selection.
|
|
| 1142 |
Note that this method doesn't require referencing the MenuComponent
|
|
| 1143 |
directly as a parameter.
|
|
| 1144 |
*/
|
|
| 1145 | 4 |
public void selectAWTMenuItem(Frame frame, String path) { |
| 1146 | 4 |
MenuBar mb = frame.getMenuBar(); |
| 1147 | 4 |
if (mb == null) { |
| 1148 | 0 |
String msg = Strings.get("tester.Robot.no_menu_bar",
|
| 1149 |
new Object[] { toString(frame) });
|
|
| 1150 | 0 |
throw new ActionFailedException(msg); |
| 1151 |
} |
|
| 1152 | 4 |
MenuItem[] items = AWT.findAWTMenuItems(frame, path); |
| 1153 | 4 |
if (items.length == 0) {
|
| 1154 | 0 |
String msg = Strings.get("tester.Robot.no_menu_item",
|
| 1155 |
new Object[] { path, toString(frame) });
|
|
| 1156 | 0 |
throw new ActionFailedException(msg); |
| 1157 |
} |
|
| 1158 | 4 |
if (items.length > 1) {
|
| 1159 | 0 |
String msg = Strings.get("tester.Robot.multiple_menu_items");
|
| 1160 | 0 |
throw new ActionFailedException(msg); |
| 1161 |
} |
|
| 1162 | 4 |
selectAWTMenuItem(items[0]); |
| 1163 |
} |
|
| 1164 |
|
|
| 1165 |
/** @deprecated Renamed to
|
|
| 1166 |
{@link #selectAWTPopupMenuItem(Component,String)}.
|
|
| 1167 |
*/
|
|
| 1168 | 0 |
public void selectAWTPopupMenuItemByLabel(Component invoker, String path) { |
| 1169 | 0 |
selectAWTPopupMenuItem(invoker, path); |
| 1170 |
} |
|
| 1171 |
|
|
| 1172 |
/** Select the given menu item from a PopupMenu on the given Component.
|
|
| 1173 |
The given String may be either a label or path of labels, but must
|
|
| 1174 |
uniquely identify the menu item. For example, "Copy" would be valid
|
|
| 1175 |
if there is only one instance of that menu label under the MenuBar,
|
|
| 1176 |
otherwise you would need to specify "Edit|Copy" to ensure the proper
|
|
| 1177 |
selection. If there are more than one PopupMenu registerd on the
|
|
| 1178 |
invoking component, you will need to prefix the PopupMenu name as
|
|
| 1179 |
well, e.g. "popup0|Edit|Copy". */
|
|
| 1180 | 4 |
public void selectAWTPopupMenuItem(Component invoker, String path) { |
| 1181 | 4 |
try {
|
| 1182 | 4 |
PopupMenu[] popups = AWT.getPopupMenus(invoker); |
| 1183 | 4 |
if (popups.length == 0)
|
| 1184 | 0 |
throw new ActionFailedException(Strings.get("tester.Robot.awt_popup_missing")); |
| 1185 |
|
|
| 1186 | 4 |
MenuItem[] items = AWT.findAWTPopupMenuItems(invoker, path); |
| 1187 | 4 |
if (items.length == 1) {
|
| 1188 | 4 |
selectAWTPopupMenuItem(items[0]); |
| 1189 | 4 |
return;
|
| 1190 |
} |
|
| 1191 | 0 |
else if (items.length == 0) { |
| 1192 | 0 |
String msg = Strings.get("tester.Robot.no_popup_menu_item",
|
| 1193 |
new Object[] { path,
|
|
| 1194 |
toString(invoker) }); |
|
| 1195 | 0 |
throw new ActionFailedException(msg); |
| 1196 |
} |
|
| 1197 | 0 |
String msg = Strings.get("tester.Robot.multiple_menu_items",
|
| 1198 |
new Object[] { path });
|
|
| 1199 | 0 |
throw new ActionFailedException(msg); |
| 1200 |
} |
|
| 1201 |
finally {
|
|
| 1202 | 4 |
AWT.dismissAWTPopup(); |
| 1203 |
} |
|
| 1204 |
} |
|
| 1205 |
|
|
| 1206 | 10 |
protected void fireAccessibleAction(Component context, |
| 1207 |
final AccessibleAction action, |
|
| 1208 |
String name) {
|
|
| 1209 | 10 |
if (action != null && action.getAccessibleActionCount() > 0) { |
| 1210 | 10 |
invokeLater(context, new Runnable() {
|
| 1211 | 10 |
public void run() { |
| 1212 | 10 |
action.doAccessibleAction(0); |
| 1213 |
} |
|
| 1214 |
}); |
|
| 1215 |
} |
|
| 1216 |
else {
|
|
| 1217 | 0 |
String msg = Strings.get("tester.Robot.no_accessible_action",
|
| 1218 |
new String[] { name });
|
|
| 1219 | 0 |
throw new ActionFailedException(msg); |
| 1220 |
} |
|
| 1221 |
} |
|
| 1222 |
|
|
| 1223 | 10 |
private Component getContext(MenuComponent item) {
|
| 1224 | 10 |
while (!(item.getParent() instanceof Component) |
| 1225 |
&& item.getParent() instanceof MenuComponent)
|
|
| 1226 | 17 |
item = (MenuComponent)item.getParent(); |
| 1227 | 10 |
return (Component)item.getParent();
|
| 1228 |
} |
|
| 1229 |
|
|
| 1230 |
/** Select an AWT menu item. */
|
|
| 1231 | 4 |
public void selectAWTMenuItem(MenuComponent item) { |
| 1232 |
// Can't do this through coordinates because MenuComponents don't
|
|
| 1233 |
// store any of that information
|
|
| 1234 | 4 |
fireAccessibleAction(getContext(item), item.getAccessibleContext(). |
| 1235 |
getAccessibleAction(), toString(item)); |
|
| 1236 | 4 |
if (queueBlocked())
|
| 1237 | 0 |
key(KeyEvent.VK_ESCAPE); |
| 1238 |
} |
|
| 1239 |
|
|
| 1240 |
/** Select an AWT popup menu item. */
|
|
| 1241 | 6 |
public void selectAWTPopupMenuItem(MenuComponent item) { |
| 1242 |
// Can't do this through coordinates because MenuComponents don't
|
|
| 1243 |
// store any of that information
|
|
| 1244 | 6 |
fireAccessibleAction(getContext(item), item.getAccessibleContext(). |
| 1245 |
getAccessibleAction(), toString(item)); |
|
| 1246 | 6 |
if (queueBlocked())
|
| 1247 | 0 |
key(KeyEvent.VK_ESCAPE); |
| 1248 |
} |
|
| 1249 |
|
|
| 1250 |
/** Is the given component ready for robot input? */
|
|
| 1251 | 2357 |
protected boolean isReadyForInput(Component c) { |
| 1252 | 2357 |
if (eventMode == EM_AWT)
|
| 1253 | 343 |
return c.isShowing();
|
| 1254 | 2014 |
return c.isShowing()
|
| 1255 |
&& tracker.isWindowReady(AWT.getWindow(c)); |
|
| 1256 |
} |
|
| 1257 |
|
|
| 1258 | 417 |
private boolean isOnJMenuBar(Component item) { |
| 1259 | 417 |
if (item instanceof javax.swing.JMenuBar) |
| 1260 | 64 |
return true; |
| 1261 | 353 |
Component parent = item instanceof JPopupMenu
|
| 1262 |
? ((JPopupMenu)item).getInvoker() : item.getParent(); |
|
| 1263 | 353 |
return parent != null && isOnJMenuBar(parent); |
| 1264 |
} |
|
| 1265 |
|
|
| 1266 |
/** Find and select the given menu item, by path. */
|
|
| 1267 | 2 |
public void selectMenuItem(Component sameWindow, String path) { |
| 1268 | 2 |
try {
|
| 1269 | 2 |
Window window = AWT.getWindow(sameWindow); |
| 1270 | 2 |
Matcher m = new JMenuItemMatcher(path);
|
| 1271 | 2 |
Component item = BasicFinder.getDefault().find(window, m); |
| 1272 | 2 |
selectMenuItem(item); |
| 1273 |
} |
|
| 1274 |
catch(ComponentNotFoundException e) {
|
|
| 1275 | 0 |
throw new ComponentMissingException("Can't find menu item '" |
| 1276 |
+ path + "'");
|
|
| 1277 |
} |
|
| 1278 |
catch(MultipleComponentsFoundException e) {
|
|
| 1279 | 0 |
throw new ActionFailedException(e.getMessage()); |
| 1280 |
} |
|
| 1281 |
} |
|
| 1282 |
|
|
| 1283 |
/** Find and select the given menu item. */
|
|
| 1284 | 81 |
public void selectMenuItem(Component item) { |
| 1285 | 81 |
Log.debug("Selecting menu item " + toString(item));
|
| 1286 | 81 |
Component parent = item.getParent(); |
| 1287 | 81 |
JPopupMenu parentPopup = null;
|
| 1288 | 81 |
if (parent instanceof JPopupMenu) { |
| 1289 | 62 |
parentPopup = (JPopupMenu)parent; |
| 1290 | 62 |
parent = ((JPopupMenu)parent).getInvoker(); |
| 1291 |
} |
|
| 1292 | 81 |
boolean inMenuBar = parent instanceof javax.swing.JMenuBar; |
| 1293 | 81 |
boolean isMenu = item instanceof javax.swing.JMenu; |
| 1294 |
|
|
| 1295 | 81 |
if (isOnJMenuBar(item) && useScreenMenuBar()) {
|
| 1296 |
// Use accessibility action instead
|
|
| 1297 | 0 |
fireAccessibleAction(item, item.getAccessibleContext(). |
| 1298 |
getAccessibleAction(), toString(item)); |
|
| 1299 | 0 |
return;
|
| 1300 |
} |
|
| 1301 |
|
|
| 1302 |
// If our parent is a menu, activate it first, if it's not already.
|
|
| 1303 | 81 |
if (parent instanceof javax.swing.JMenuItem) { |
| 1304 | 51 |
if (parentPopup == null || !parentPopup.isShowing()) { |
| 1305 | 49 |
Log.debug("Opening parent menu " + toString(parent));
|
| 1306 | 49 |
selectMenuItem(parent); |
| 1307 |
} |
|
| 1308 |
} |
|
| 1309 |
|
|
| 1310 |
// Make sure the appropriate window is in front
|
|
| 1311 | 81 |
if (inMenuBar) {
|
| 1312 | 19 |
final Window win = AWT.getWindow(parent); |
| 1313 | 19 |
if (win != null) { |
| 1314 |
// Make sure the window is in front, or its menus may be
|
|
| 1315 |
// obscured by another window.
|
|
| 1316 | 19 |
invokeAndWait(win, new Runnable() {
|
| 1317 | 19 |
public void run() { |
| 1318 | 19 |
win.toFront(); |
| 1319 |
} |
|
| 1320 |
}); |
|
| 1321 | 19 |
mouseMove(win); |
| 1322 |
} |
|
| 1323 |
} |
|
| 1324 |
|
|
| 1325 |
// Activate the item
|
|
| 1326 | 81 |
if (isMenu && !inMenuBar) {
|
| 1327 |
// Submenus only require a mouse-over to activate, but do
|
|
| 1328 |
// a click to be certain
|
|
| 1329 | 30 |
if (subMenuDelay > autoDelay) {
|
| 1330 | 0 |
delay(subMenuDelay - autoDelay); |
| 1331 |
} |
|
| 1332 |
} |
|
| 1333 |
// Top-level menus and menu items *must* be clicked on
|
|
| 1334 | 81 |
Log.debug("Activating menu item " + toString(item));
|
| 1335 | 81 |
click(item); |
| 1336 | 81 |
waitForIdle(); |
| 1337 |
|
|
| 1338 |
// If this item is a menu, make sure its popup is showing before we
|
|
| 1339 |
// return
|
|
| 1340 | 81 |
if (isMenu) {
|
| 1341 | 49 |
JPopupMenu popup = ((javax.swing.JMenu)item).getPopupMenu(); |
| 1342 | 49 |
if (!waitForComponent(popup, popupDelay)) {
|
| 1343 | 0 |
String msg = "Clicking on '"
|
| 1344 |
+ ((javax.swing.JMenu)item).getText() |
|
| 1345 |
+ "' never produced a popup menu";
|
|
| 1346 | 0 |
throw new ComponentMissingException(msg); |
| 1347 |
} |
|
| 1348 |
// for OSX 1.4.1; isShowing set before popup is available
|
|
| 1349 | 49 |
if (subMenuDelay > autoDelay) {
|
| 1350 | 0 |
delay(subMenuDelay - autoDelay); |
| 1351 |
} |
|
| 1352 |
} |
|
| 1353 |
} |
|
| 1354 |
|
|
| 1355 | 11 |
public void selectPopupMenuItem(Component invoker, |
| 1356 |
ComponentLocation loc, |
|
| 1357 |
String path) {
|
|
| 1358 | 11 |
Point where = loc.getPoint(invoker); |
| 1359 |
|
|
| 1360 | 11 |
if (where.x == -1)
|
| 1361 | 0 |
where.x = invoker.getWidth()/2; |
| 1362 | 11 |
if (where.y == -1)
|
| 1363 | 0 |
where.y = invoker.getHeight()/2; |
| 1364 | 11 |
Component popup = showPopupMenu(invoker, where.x, where.y); |
| 1365 | 11 |
try {
|
| 1366 | 11 |
Matcher m = new JMenuItemMatcher(path);
|
| 1367 |
// Don't care about the hierarchy on this one, since there'll only
|
|
| 1368 |
// ever be one popup active at a time.
|
|
| 1369 | 11 |
Component item = BasicFinder.getDefault(). |
| 1370 |
find((Container)popup, m); |
|
| 1371 | 9 |
selectMenuItem(item); |
| 1372 | 9 |
waitForIdle(); |
| 1373 |
} |
|
| 1374 |
catch(ComponentNotFoundException e) {
|
|
| 1375 | 2 |
throw new ComponentMissingException("Can't find menu item '" |
| 1376 |
+ path + "'");
|
|
| 1377 |
} |
|
| 1378 |
catch(MultipleComponentsFoundException e) {
|
|
| 1379 | 0 |
throw new ActionFailedException(e.getMessage()); |
| 1380 |
} |
|
| 1381 |
} |
|
| 1382 |
|
|
| 1383 |
/** Attempt to display a popup menu at center of the component. */
|
|
| 1384 | 3 |
public Component showPopupMenu(Component invoker) {
|
| 1385 | 3 |
return showPopupMenu(invoker,
|
| 1386 |
invoker.getWidth()/2, invoker.getHeight()/2); |
|
| 1387 |
} |
|
| 1388 |
|
|
| 1389 |
/** Attempt to display a popup menu at the given coordinates. */
|
|
| 1390 | 14 |
public Component showPopupMenu(Component invoker, int x, int y) { |
| 1391 | 14 |
String where = " at (" + x + "," + y + ")"; |
| 1392 | 14 |
Log.debug("Invoking popup " + where);
|
| 1393 | 14 |
click(invoker, x, y, AWTConstants.POPUP_MASK); |
| 1394 |
|
|
| 1395 | 14 |
Component popup = AWT.findActivePopupMenu(); |
| 1396 | 14 |
if (popup == null) { |
| 1397 | 0 |
String msg = "No popup responded to "
|
| 1398 |
+ AWTConstants.POPUP_MODIFIER |
|
| 1399 |
+ where + " on "
|
|
| 1400 |
+ toString(invoker); |
|
| 1401 | 0 |
throw new ComponentMissingException(msg); |
| 1402 |
} |
|
| 1403 | 14 |
int POPUP_DELAY = 10000;
|
| 1404 | 14 |
long start = System.currentTimeMillis();
|
| 1405 | 14 |
while (!isReadyForInput(SwingUtilities.getWindowAncestor(popup))
|
| 1406 |
&& System.currentTimeMillis() - start > POPUP_DELAY) {
|
|
| 1407 | 0 |
sleep(); |
| 1408 |
} |
|
| 1409 | 14 |
return popup;
|
| 1410 |
} |
|
| 1411 |
|
|
| 1412 |
/** Activate the given window. */
|
|
| 1413 | 11 |
public void activate(final Window win) { |
| 1414 |
// ACTIVATE means window gets keyboard focus.
|
|
| 1415 | 11 |
invokeAndWait(win, new Runnable() {
|
| 1416 | 11 |
public void run() { win.toFront(); } |
| 1417 |
}); |
|
| 1418 |
// For pointer-focus systems
|
|
| 1419 | 11 |
mouseMove(win); |
| 1420 |
} |
|
| 1421 |
|
|
| 1422 | 15 |
protected Point getCloseLocation(Container c) {
|
| 1423 | 15 |
Dimension size = c.getSize(); |
| 1424 | 15 |
Insets insets = c.getInsets(); |
| 1425 | 15 |
if (Platform.isOSX()) {
|
| 1426 | 0 |
return new Point(insets.left + 15, insets.top / 2); |
| 1427 |
} |
|
| 1428 |
else {
|
|
| 1429 | 15 |
return new Point(size.width - insets.right - 10, insets.top / 2); |
| 1430 |
} |
|
| 1431 |
} |
|
| 1432 |
|
|
| 1433 |
/** Invoke the window close operation. */
|
|
| 1434 | 13 |
public void close(Window w) { |
| 1435 | 13 |
if (w.isShowing()) {
|
| 1436 |
// Move to a corner and "pretend" to use the window manager
|
|
| 1437 |
// control
|
|
| 1438 | 13 |
try {
|
| 1439 | 13 |
Point p = getCloseLocation(w); |
| 1440 | 13 |
mouseMove(w, p.x, p.y); |
| 1441 |
} |
|
| 1442 |
catch(Exception e) {
|
|
| 1443 |
// ignore
|
|
| 1444 |
} |
|
| 1445 | 13 |
WindowEvent ev = new WindowEvent(w, WindowEvent.WINDOW_CLOSING);
|
| 1446 |
// If the window contains an applet, send the event on the
|
|
| 1447 |
// applet's queue instead to ensure a shutdown from the
|
|
| 1448 |
// applet's context (assists AppletViewer cleanup).
|
|
| 1449 | 13 |
Component applet = AWT.findAppletDescendent(w); |
| 1450 | 13 |
EventQueue eq = tracker.getQueue(applet != null ? applet : w);
|
| 1451 | 13 |
eq.postEvent(ev); |
| 1452 |
} |
|
| 1453 |
} |
|
| 1454 |
|
|
| 1455 |
/** Return where the mouse usually grabs to move a window. Center of the
|
|
| 1456 |
* top of the frame is usually a good choice.
|
|
| 1457 |
*/
|
|
| 1458 | 10 |
protected Point getMoveLocation(Container c) {
|
| 1459 | 10 |
Dimension size = c.getSize(); |
| 1460 | 10 |
Insets insets = c.getInsets(); |
| 1461 | 10 |
return new Point(size.width/2, insets.top/2); |
| 1462 |
} |
|
| 1463 |
|
|
| 1464 |
/** Move the given Frame/Dialog to the requested location. */
|
|
| 1465 | 6 |
public void move(Container comp, int newx, int newy) { |
| 1466 | 6 |
Point loc = AWT.getLocationOnScreen(comp); |
| 1467 | 6 |
moveBy(comp, newx - loc.x, newy - loc.y); |
| 1468 |
} |
|
| 1469 |
|
|
| 1470 |
/** Move the given Window by the given amount. */
|
|
| 1471 | 7 |
public void moveBy(final Container comp, final int dx, final int dy) { |
| 1472 | 7 |
final Point loc = AWT.getLocationOnScreen(comp); |
| 1473 | 7 |
boolean userMovable = userMovable(comp);
|
| 1474 | 7 |
if (userMovable) {
|
| 1475 | 5 |
Point p = getMoveLocation(comp); |
| 1476 | 5 |
mouseMove(comp, p.x, p.y); |
| 1477 | 5 |
mouseMove(comp, p.x + dx, p.y + dy); |
| 1478 |
} |
|
| 1479 | 7 |
invokeAndWait(comp, new Runnable() {
|
| 1480 | 7 |
public void run() { |
| 1481 | 7 |
comp.setLocation(new Point(loc.x + dx, loc.y + dy));
|
| 1482 |
} |
|
| 1483 |
}); |
|
| 1484 | 7 |
if (userMovable) {
|
| 1485 | 5 |
Point p = getMoveLocation(comp); |
| 1486 | 5 |
mouseMove(comp, p.x, p.y); |
| 1487 |
} |
|
| 1488 |
} |
|
| 1489 |
|
|
| 1490 |
/** Return where the mouse usually grabs to resize a window. The lower
|
|
| 1491 |
* right corner of the window is usually a good choice.
|
|
| 1492 |
*/
|
|
| 1493 | 4 |
protected Point getResizeLocation(Container c) {
|
| 1494 | 4 |
Dimension size = c.getSize(); |
| 1495 | 4 |
Insets insets = c.getInsets(); |
| 1496 | 4 |
return new Point(size.width-insets.right/2, |
| 1497 |
size.height-insets.bottom/2); |
|
| 1498 |
} |
|
| 1499 |
|
|
| 1500 |
/** Return whether it is possible for the user to move the given
|
|
| 1501 |
component.
|
|
| 1502 |
*/
|
|
| 1503 | 10 |
protected boolean userMovable(Component comp) { |
| 1504 | 10 |
return comp instanceof Dialog |
| 1505 |
|| comp instanceof Frame
|
|
| 1506 |
|| canMoveWindows(); |
|
| 1507 |
} |
|
| 1508 |
|
|
| 1509 |
/** Return whether it is possible for the user to resize the given
|
|
| 1510 |
component.
|
|
| 1511 |
*/
|
|
| 1512 | 10 |
protected boolean userResizable(Component comp) { |
| 1513 | 10 |
if (comp instanceof Dialog) |
| 1514 | 1 |
return ((Dialog)comp).isResizable();
|
| 1515 | 9 |
if (comp instanceof Frame) |
| 1516 | 6 |
return ((Frame)comp).isResizable();
|
| 1517 |
// most X11 window managers allow arbitrary resizing
|
|
| 1518 | 3 |
return canResizeWindows();
|
| 1519 |
} |
|
| 1520 |
|
|
| 1521 |
/** Resize the given Frame/Dialog to the given size. */
|
|
| 1522 | 5 |
public void resize(Container comp, int width, int height) { |
| 1523 | 5 |
Dimension size = comp.getSize(); |
| 1524 | 5 |
resizeBy(comp, width - size.width, height - size.height); |
| 1525 |
} |
|
| 1526 |
|
|
| 1527 |
/** Resize the given Frame/Dialog by the given amounts. */
|
|
| 1528 | 6 |
public void resizeBy(final Container comp, final int dx, final int dy) { |
| 1529 |
// Fake the pointer motion like we're resizing
|
|
| 1530 | 6 |
boolean userResizable = userResizable(comp);
|
| 1531 | 6 |
if (userResizable) {
|
| 1532 | 2 |
Point p = getResizeLocation(comp); |
| 1533 | 2 |
mouseMove(comp, p.x, p.y); |
| 1534 | 2 |
mouseMove(comp, p.x + dx, p.y + dy); |
| 1535 |
} |
|
| 1536 | 6 |
invokeAndWait(comp, new Runnable() {
|
| 1537 | 6 |
public void run() { |
| 1538 | 6 |
comp.setSize(comp.getWidth()+dx, comp.getHeight()+dy); |
| 1539 |
} |
|
| 1540 |
}); |
|
| 1541 | 6 |
if (userResizable) {
|
| 1542 | 2 |
Point p = getResizeLocation(comp); |
| 1543 | 2 |
mouseMove(comp, p.x, p.y); |
| 1544 |
} |
|
| 1545 |
} |
|
| 1546 |
|
|
| 1547 |
/** Identify the coordinates of the iconify button where we can, returning
|
|
| 1548 |
* (0, 0) if we can't.
|
|
| 1549 |
*/
|
|
| 1550 | 14 |
protected Point getIconifyLocation(Container c) {
|
| 1551 | 14 |
Dimension size = c.getSize(); |
| 1552 | 14 |
Insets insets = c.getInsets(); |
| 1553 |
// We know the exact layout of the window manager frames for w32 and
|
|
| 1554 |
// OSX. Currently no way of detecting the WM under X11. Maybe we
|
|
| 1555 |
// could send a WM message (WM_ICONIFY)?
|
|
| 1556 | 14 |
Point loc = new Point();
|
| 1557 | 14 |
loc.y = insets.top / 2; |
| 1558 | 14 |
if (Platform.isOSX()) {
|
| 1559 | 0 |
loc.x = 35; |
| 1560 |
} |
|
| 1561 | 14 |
else if (Platform.isWindows()) { |
| 1562 | 14 |
int offset = Platform.isWindowsXP() ? 64 : 45;
|
| 1563 | 14 |
loc.x = size.width - insets.right - offset; |
| 1564 |
} |
|
| 1565 | 14 |
return loc;
|
| 1566 |
} |
|
| 1567 |
|
|
| 1568 |
private static final int MAXIMIZE_BUTTON_OFFSET = |
|
| 1569 | 96 |
Platform.isOSX() ? 25 |
| 1570 | 96 |
: Platform.isWindows() ? -20 : 0; |
| 1571 |
|
|
| 1572 |
/** Identify the coordinates of the maximize button where possible,
|
|
| 1573 |
returning null if not.
|
|
| 1574 |
*/
|
|
| 1575 | 5 |
protected Point getMaximizeLocation(Container c) {
|
| 1576 | 5 |
Point loc = getIconifyLocation(c); |
| 1577 | 5 |
loc.x += MAXIMIZE_BUTTON_OFFSET; |
| 1578 | 5 |
return loc;
|
| 1579 |
} |
|
| 1580 |
|
|
| 1581 |
/** Iconify the given Frame. Don't support iconification of Dialogs at
|
|
| 1582 |
* this point (although maybe should).
|
|
| 1583 |
*/
|
|
| 1584 | 2 |
public void iconify(final Frame frame) { |
| 1585 | 2 |
Point loc = getIconifyLocation(frame); |
| 1586 | 2 |
if (loc != null) { |
| 1587 | 2 |
mouseMove(frame, loc.x, loc.y); |
| 1588 |
} |
|
| 1589 | 2 |
invokeLater(frame, new Runnable() {
|
| 1590 | 2 |
public void run() { |
| 1591 | 2 |
frame.setState(Frame.ICONIFIED); |
| 1592 |
} |
|
| 1593 |
}); |
|
| 1594 |
} |
|
| 1595 |
|
|
| 1596 | 2 |
public void deiconify(Frame frame) { |
| 1597 | 2 |
normalize(frame); |
| 1598 |
} |
|
| 1599 |
|
|
| 1600 | 4 |
public void normalize(final Frame frame) { |
| 1601 | 4 |
invokeLater(frame, new Runnable() {
|
| 1602 | 4 |
public void run() { |
| 1603 | 4 |
frame.setState(Frame.NORMAL); |
| 1604 | 4 |
if (Bugs.hasFrameDeiconifyBug())
|
| 1605 | 0 |
frame.setVisible(true);
|
| 1606 |
} |
|
| 1607 |
}); |
|
| 1608 |
} |
|
| 1609 |
|
|
| 1610 |
/** Make the window full size. On 1.3.1, this is not reversible. */
|
|
| 1611 | 2 |
public void maximize(final Frame frame) { |
| 1612 | 2 |
Point loc = getMaximizeLocation(frame); |
| 1613 | 2 |
if (loc != null) { |
| 1614 | 2 |
mouseMove(frame, loc.x, loc.y); |
| 1615 |
} |
|
| 1616 | 2 |
invokeLater(frame, new Runnable() {
|
| 1617 | 2 |
public void run() { |
| 1618 |
// If the maximize is unavailable, set to full screen size
|
|
| 1619 |
// instead.
|
|
| 1620 | 2 |
try {
|
| 1621 | 2 |
final int MAXIMIZED_BOTH = 6;
|
| 1622 | 2 |
Boolean b = (Boolean) |
| 1623 |
Toolkit.class.getMethod("isFrameStateSupported", |
|
| 1624 |
new Class[] { int.class }). |
|
| 1625 |
invoke(toolkit, new Object[] {
|
|
| 1626 |
new Integer(MAXIMIZED_BOTH)
|
|
| 1627 |
}); |
|
| 1628 | 2 |
if (b.booleanValue() && !serviceMode) {
|
| 1629 | 2 |
Frame.class.getMethod("setExtendedState", |
| 1630 |
new Class[] { int.class, }). |
|
| 1631 |
invoke(frame, new Object[] {
|
|
| 1632 |
new Integer(MAXIMIZED_BOTH)
|
|
| 1633 |
}); |
|
| 1634 |
} |
|
| 1635 |
else {
|
|
| 1636 | 0 |
throw new RuntimeException("Platform won't maximize"); |
| 1637 |
} |
|
| 1638 |
} |
|
| 1639 |
catch(Exception e) {
|
|
| 1640 | 0 |
Log.debug("Maximize not supported: " + e);
|
| 1641 | 0 |
Rectangle rect = |
| 1642 |
frame.getGraphicsConfiguration().getBounds(); |
|
| 1643 | 0 |
frame.setLocation(rect.x, rect.y); |
| 1644 | 0 |
frame.setSize(rect.width, rect.height); |
| 1645 |
} |
|
| 1646 |
} |
|
| 1647 |
}); |
|
| 1648 |
} |
|
| 1649 |
|
|
| 1650 |
/** Send the given event as appropriate to the event-generation mode. */
|
|
| 1651 | 0 |
public void sendEvent(AWTEvent event) { |
| 1652 |
// Modifiers are ignored, assuming that an event will be
|
|
| 1653 |
// sent that causes modifiers to be sent appropriately.
|
|
| 1654 | 0 |
if (eventMode == EM_ROBOT) {
|
| 1655 | 0 |
int id = event.getID();
|
| 1656 | 0 |
Log.debug("Sending event id " + id);
|
| 1657 | 0 |
if (id >= MouseEvent.MOUSE_FIRST && id <= MouseEvent.MOUSE_LAST) {
|
| 1658 | 0 |
MouseEvent me = (MouseEvent)event; |
| 1659 | 0 |
Component comp = me.getComponent(); |
| 1660 | 0 |
if (id == MouseEvent.MOUSE_MOVED) {
|
| 1661 | 0 |
mouseMove(comp, me.getX(), me.getY()); |
| 1662 |
} |
|
| 1663 | 0 |
else if (id == MouseEvent.MOUSE_DRAGGED) { |
| 1664 | 0 |
mouseMove(comp, me.getX(), me.getY()); |
| 1665 |
} |
|
| 1666 | 0 |
else if (id == MouseEvent.MOUSE_PRESSED) { |
| 1667 | 0 |
mouseMove(comp, me.getX(), me.getY()); |
| 1668 | 0 |
mousePress(me.getModifiers() & AWTConstants.BUTTON_MASK); |
| 1669 |
} |
|
| 1670 | 0 |
else if (id == MouseEvent.MOUSE_ENTERED) { |
| 1671 | 0 |
mouseMove(comp, me.getX(), me.getY()); |
| 1672 |
} |
|
| 1673 | 0 |
else if (id == MouseEvent.MOUSE_EXITED) { |
| 1674 | 0 |
mouseMove(comp, me.getX(), me.getY()); |
| 1675 |
} |
|
| 1676 | 0 |
else if (id == MouseEvent.MOUSE_RELEASED) { |
| 1677 | 0 |
mouseMove(comp, me.getX(), me.getY()); |
| 1678 | 0 |
mouseRelease(me.getModifiers() & AWTConstants.BUTTON_MASK); |
| 1679 |
} |
|
| 1680 |
} |
|
| 1681 | 0 |
else if (id >= KeyEvent.KEY_FIRST && id <= KeyEvent.KEY_LAST) { |
| 1682 | 0 |
KeyEvent ke = (KeyEvent)event; |
| 1683 | 0 |
if (id == KeyEvent.KEY_PRESSED) {
|
| 1684 | 0 |
keyPress(ke.getKeyCode()); |
| 1685 |
} |
|
| 1686 | 0 |
else if (id == KeyEvent.KEY_RELEASED) { |
| 1687 | 0 |
keyRelease(ke.getKeyCode()); |
| 1688 |
} |
|
| 1689 |
} |
|
| 1690 |
else {
|
|
| 1691 | 0 |
Log.warn("Event not supported: " + event);
|
| 1692 |
} |
|
| 1693 |
} |
|
| 1694 |
else {
|
|
| 1695 |
// Post the event to the appropriate AWT event queue
|
|
| 1696 | 0 |
postEvent((Component)event.getSource(), event); |
| 1697 |
} |
|
| 1698 |
} |
|
| 1699 |
|
|
| 1700 |
/** Return the symbolic name of the given event's ID. */
|
|
| 1701 | 3216 |
public static String getEventID(AWTEvent event) { |
| 1702 |
// Optimize here to avoid field name lookup overhead
|
|
| 1703 | 3216 |
switch(event.getID()) {
|
| 1704 | 196 |
case MouseEvent.MOUSE_MOVED: return "MOUSE_MOVED"; |
| 1705 | 60 |
case MouseEvent.MOUSE_DRAGGED: return "MOUSE_DRAGGED"; |
| 1706 | 157 |
case MouseEvent.MOUSE_PRESSED: return "MOUSE_PRESSED"; |
| 1707 | 117 |
case MouseEvent.MOUSE_CLICKED: return "MOUSE_CLICKED"; |
| 1708 | 152 |
case MouseEvent.MOUSE_RELEASED: return "MOUSE_RELEASED"; |
| 1709 | 74 |
case MouseEvent.MOUSE_ENTERED: return "MOUSE_ENTERED"; |
| 1710 | 77 |
case MouseEvent.MOUSE_EXITED: return "MOUSE_EXITED"; |
| 1711 | 229 |
case KeyEvent.KEY_PRESSED: return "KEY_PRESSED"; |
| 1712 | 141 |
case KeyEvent.KEY_TYPED: return "KEY_TYPED"; |
| 1713 | 221 |
case KeyEvent.KEY_RELEASED: return "KEY_RELEASED"; |
| 1714 | 30 |
case WindowEvent.WINDOW_OPENED: return "WINDOW_OPENED"; |
| 1715 | 1 |
case WindowEvent.WINDOW_CLOSING: return "WINDOW_CLOSING"; |
| 1716 | 12 |
case WindowEvent.WINDOW_CLOSED: return "WINDOW_CLOSED"; |
| 1717 | 0 |
case WindowEvent.WINDOW_ICONIFIED: return "WINDOW_ICONIFIED"; |
| 1718 | 0 |
case WindowEvent.WINDOW_DEICONIFIED: return "WINDOW_DEICONIFIED"; |
| 1719 | 16 |
case WindowEvent.WINDOW_ACTIVATED: return "WINDOW_ACTIVATED"; |
| 1720 | 16 |
case WindowEvent.WINDOW_DEACTIVATED: return "WINDOW_DEACTIVATED"; |
| 1721 | 585 |
case ComponentEvent.COMPONENT_MOVED: return "COMPONENT_MOVED"; |
| 1722 | 832 |
case ComponentEvent.COMPONENT_RESIZED: return "COMPONENT_RESIZED"; |
| 1723 | 70 |
case ComponentEvent.COMPONENT_SHOWN: return "COMPONENT_SHOWN"; |
| 1724 | 82 |
case ComponentEvent.COMPONENT_HIDDEN: return "COMPONENT_HIDDEN"; |
| 1725 | 50 |
case FocusEvent.FOCUS_GAINED: return "FOCUS_GAINED"; |
| 1726 | 53 |
case FocusEvent.FOCUS_LOST: return "FOCUS_LOST"; |
| 1727 | 0 |
case HierarchyEvent.HIERARCHY_CHANGED: return "HIERARCHY_CHANGED"; |
| 1728 | 0 |
case HierarchyEvent.ANCESTOR_MOVED: return "ANCESTOR_MOVED"; |
| 1729 | 0 |
case HierarchyEvent.ANCESTOR_RESIZED: return "ANCESTOR_RESIZED"; |
| 1730 | 0 |
case PaintEvent.PAINT: return "PAINT"; |
| 1731 | 0 |
case PaintEvent.UPDATE: return "UPDATE"; |
| 1732 | 7 |
case ActionEvent.ACTION_PERFORMED: return "ACTION_PERFORMED"; |
| 1733 | 0 |
case InputMethodEvent.CARET_POSITION_CHANGED: return "CARET_POSITION_CHANGED"; |
| 1734 | 0 |
case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED: return "INPUT_METHOD_TEXT_CHANGED"; |
| 1735 | 38 |
default:
|
| 1736 | 38 |
return Reflector.getFieldName(event.getClass(), event.getID(), ""); |
| 1737 |
} |
|
| 1738 |
} |
|
| 1739 |
|
|
| 1740 | 13652 |
public static Class getCanonicalClass(Class refClass) { |
| 1741 |
// Don't use classnames from anonymous inner classes...
|
|
| 1742 |
// Don't use classnames from platform LAF classes...
|
|
| 1743 | 13652 |
String className = refClass.getName(); |
| 1744 | 13652 |
while (className.indexOf("$") != -1 |
| 1745 |
|| className.startsWith("javax.swing.plaf")
|
|
| 1746 |
|| className.startsWith("com.apple.mrj")) {
|
|
| 1747 | 3612 |
refClass = refClass.getSuperclass(); |
| 1748 | 3612 |
className = refClass.getName(); |
| 1749 |
} |
|
| 1750 | 13652 |
return refClass;
|
| 1751 |
} |
|
| 1752 |
|
|
| 1753 |
/** Provides a more concise representation of the component than the
|
|
| 1754 |
* default Component.toString().
|
|
| 1755 |
*/
|
|
| 1756 | 7442 |
public static String toString(Component comp) { |
| 1757 | 7442 |
if (comp == null) |
| 1758 | 1 |
return "(null)"; |
| 1759 |
|
|
| 1760 | 7441 |
if (AWT.isTransientPopup(comp)) {
|
| 1761 | 134 |
boolean tooltip = AWT.isToolTip(comp);
|
| 1762 | 134 |
if (AWT.isHeavyweightPopup(comp)) {
|
| 1763 | 126 |
return tooltip
|
| 1764 |
? Strings.get("component.heavyweight_tooltip")
|
|
| 1765 |
: Strings.get("component.heavyweight_popup");
|
|
| 1766 |
} |
|
| 1767 | 8 |
else if (AWT.isLightweightPopup(comp)) { |
| 1768 | 8 |
return tooltip
|
| 1769 |
? Strings.get("component.lightweight_tooltip")
|
|
| 1770 |
: Strings.get("component.lightweight_popup");
|
|
| 1771 |
} |
|
| 1772 |
} |
|
| 1773 | 7307 |
else if (AWT.isSharedInvisibleFrame(comp)) { |
| 1774 | 23 |
return Strings.get("component.default_frame"); |
| 1775 |
} |
|
| 1776 | 7284 |
String name = ComponentReference.getDescriptiveName(comp); |
| 1777 | 7284 |
String classDesc = descriptiveClassName(comp.getClass()); |
| 1778 | 7284 |
if (name == null) { |
| 1779 | 3424 |
if (AWT.isContentPane(comp)) {
|
| 1780 | 257 |
name = Strings.get("component.content_pane");
|
| 1781 |
} |
|
| 1782 | 3167 |
else if (AWT.isGlassPane(comp)) { |
| 1783 | 74 |
name = Strings.get("component.glass_pane");
|
| 1784 |
} |
|
| 1785 | 3093 |
else if (comp instanceof JLayeredPane) { |
| 1786 | 249 |
name = Strings.get("component.layered_pane");
|
| 1787 |
} |
|
| 1788 | 2844 |
else if (comp instanceof JRootPane) { |
| 1789 | 264 |
name = Strings.get("component.root_pane");
|
| 1790 |
} |
|
| 1791 |
else {
|
|
| 1792 | 2580 |
name = classDesc + " instance";
|
| 1793 |
} |
|
| 1794 |
} |
|
| 1795 |
else {
|
|
| 1796 | 3860 |
name = "'" + name + "' (" + classDesc + ")"; |
| 1797 |
} |
|
| 1798 | 7284 |
return name;
|
| 1799 |
} |
|
| 1800 |
|
|
| 1801 |
/** Provide a string representation of the given component (Component or
|
|
| 1802 |
* MenuComponent.
|
|
| 1803 |
*/
|
|
| 1804 | 3264 |
public static String toString(Object obj) { |
| 1805 | 3264 |
if (obj instanceof Component) |
| 1806 | 3247 |
return toString((Component)obj);
|
| 1807 | 17 |
else if (obj instanceof MenuBar) |
| 1808 | 0 |
return "MenuBar"; |
| 1809 | 17 |
else if (obj instanceof MenuItem) |
| 1810 | 17 |
return ((MenuItem)obj).getLabel();
|
| 1811 | 0 |
return obj.toString();
|
| 1812 |
} |
|
| 1813 |
|
|
| 1814 | 11541 |
protected static String descriptiveClassName(Class cls) { |
| 1815 | 11541 |
StringBuffer desc = new StringBuffer(simpleClassName(cls));
|
| 1816 | 11541 |
Class coreClass = getCanonicalClass(cls); |
| 1817 | 11541 |
String coreClassName = coreClass.getName(); |
| 1818 | 11541 |
while (!coreClassName.startsWith("java.awt.") |
| 1819 |
&& !coreClassName.startsWith("javax.swing.")
|
|
| 1820 |
&& !coreClassName.startsWith("java.applet.")) {
|
|
| 1821 | 172 |
coreClass = coreClass.getSuperclass(); |
| 1822 | 172 |
coreClassName = coreClass.getName(); |
| 1823 |
} |
|
| 1824 | 11541 |
if (!coreClass.equals(cls)) {
|
| 1825 | 2801 |
desc.append("/");
|
| 1826 | 2801 |
desc.append(simpleClassName(coreClass)); |
| 1827 |
} |
|
| 1828 | 11541 |
return desc.toString();
|
| 1829 |
} |
|
| 1830 |
|
|
| 1831 |
/** Provides the hierarchic path of the given component by component
|
|
| 1832 |
class, e.g. "JFrame:JRootPane:JPanel:JButton".
|
|
| 1833 |
*/
|
|
| 1834 | 4257 |
public static String toHierarchyPath(Component c) { |
| 1835 | 4257 |
StringBuffer buf = new StringBuffer();
|
| 1836 | 4257 |
Container parent = c.getParent(); |
| 1837 | 4257 |
if (parent != null) { |
| 1838 | 3006 |
buf.append(toHierarchyPath(parent)); |
| 1839 | 3006 |
buf.append(":");
|
| 1840 |
} |
|
| 1841 | 4257 |
buf.append(descriptiveClassName(c.getClass())); |
| 1842 | 4257 |
String name = ComponentReference.getDescriptiveName(c); |
| 1843 | 4257 |
if (name != null) { |
| 1844 | 1251 |
buf.append("(");
|
| 1845 | 1251 |
buf.append(name); |
| 1846 | 1251 |
buf.append(")");
|
| 1847 |
} |
|
| 1848 | 3006 |
else if (parent != null |
| 1849 |
&& parent.getComponentCount() > 1 |
|
| 1850 |
&& c instanceof JPanel) {
|
|
| 1851 | 0 |
buf.append("[");
|
| 1852 | 0 |
buf.append(String.valueOf(ComponentReference.getIndex(parent, c))); |
| 1853 | 0 |
buf.append("]");
|
| 1854 |
} |
|
| 1855 | 4257 |
return buf.toString();
|
| 1856 |
} |
|
| 1857 |
|
|
| 1858 |
/** Provide a more concise representation of the event than the default
|
|
| 1859 |
* AWTEvent.toString().
|
|
| 1860 |
*/
|
|
| 1861 | 3120 |
public static String toString(AWTEvent event) { |
| 1862 | 3120 |
String name = toString(event.getSource()); |
| 1863 | 3120 |
String desc = getEventID(event); |
| 1864 | 3120 |
if (event.getID() == KeyEvent.KEY_PRESSED
|
| 1865 |
|| event.getID() == KeyEvent.KEY_RELEASED) {
|
|
| 1866 | 355 |
KeyEvent ke = (KeyEvent)event; |
| 1867 | 355 |
desc += " (" + AWT.getKeyCode(ke.getKeyCode());
|
| 1868 | 355 |
if (ke.getModifiers() != 0) {
|
| 1869 | 145 |
desc += "/" + AWT.getKeyModifiers(ke.getModifiers());
|
| 1870 |
} |
|
| 1871 | 355 |
desc += ")";
|
| 1872 |
} |
|
| 1873 | 2765 |
else if (event.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) { |
| 1874 | 0 |
desc += " (" + ((InputMethodEvent)event).getCommittedCharacterCount() + ")"; |
| 1875 |
} |
|
| 1876 | 2765 |
else if (event.getID() == KeyEvent.KEY_TYPED) { |
| 1877 | 141 |
char ch = ((KeyEvent)event).getKeyChar();
|
| 1878 | 141 |
int mods = ((KeyEvent)event).getModifiers();
|
| 1879 | 141 |
desc += " ('" + ch
|
| 1880 | 141 |
+ (mods != 0 ? "/" + AWT.getKeyModifiers(mods) : "") |
| 1881 |
+ "')";
|
|
| 1882 |
} |
|
| 1883 | 2624 |
else if (event.getID() >= MouseEvent.MOUSE_FIRST |
| 1884 |
&& event.getID() <= MouseEvent.MOUSE_LAST) {
|
|
| 1885 | 832 |
MouseEvent me = (MouseEvent)event; |
| 1886 | 832 |
if (me.getModifiers() != 0) {
|
| 1887 | 514 |
desc += " <" + AWT.getMouseModifiers(me.getModifiers());
|
| 1888 | 514 |
if (me.getClickCount() > 1) {
|
| 1889 | 28 |
desc += "," + me.getClickCount();
|
| 1890 |
} |
|
| 1891 | 514 |
desc += ">";
|
| 1892 |
} |
|
| 1893 | 832 |
desc += " (" + me.getX() + "," + me.getY() + ")"; |
| 1894 |
} |
|
| 1895 | 1792 |
else if (event.getID() == HierarchyEvent.HIERARCHY_CHANGED) { |
| 1896 | 0 |
HierarchyEvent he = (HierarchyEvent)event; |
| 1897 | 0 |
long flags = he.getChangeFlags();
|
| 1898 | 0 |
String type = "";
|
| 1899 | 0 |
String bar = "";
|
| 1900 | 0 |
if ((flags & HierarchyEvent.SHOWING_CHANGED) != 0) {
|
| 1901 | 0 |
type += (he.getComponent().isShowing() ? "" : "!") |
| 1902 | 0 |
+ "SHOWING"; bar = "|"; |
| 1903 |
} |
|
| 1904 | 0 |
if ((flags & HierarchyEvent.PARENT_CHANGED) != 0) {
|
| 1905 | 0 |
type += bar + "PARENT:"
|
| 1906 | 0 |
+ toString(he.getComponent().getParent()); bar = "|";
|
| 1907 |
} |
|
| 1908 | 0 |
if ((flags & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
|
| 1909 | 0 |
type += bar + "DISPLAYABILITY";
|
| 1910 |
} |
|
| 1911 | 0 |
desc += " (" + type + ")"; |
| 1912 |
} |
|
| 1913 | 3120 |
return desc + " on " + name; |
| 1914 |
} |
|
| 1915 |
|
|
| 1916 |
/** Return the numeric event ID corresponding to the given string. */
|
|
| 1917 | 3 |
public static int getEventID(Class cls, String id) { |
| 1918 | 3 |
return Reflector.getFieldValue(cls, id);
|
| 1919 |
} |
|
| 1920 |
|
|
| 1921 |
/** Strip the package from the class name. */
|
|
| 1922 | 14852 |
public static String simpleClassName(Class cls) { |
| 1923 | 14852 |
String name = cls.getName(); |
| 1924 | 14852 |
int dot = name.lastIndexOf("."); |
| 1925 | 14852 |
return name.substring(dot+1, name.length());
|
| 1926 |
} |
|
| 1927 |
|
|
| 1928 |
private AWTEvent lastEventPosted = null; |
|
| 1929 |
private MouseEvent lastMousePress = null; |
|
| 1930 |
private boolean countingClicks = false; |
|
| 1931 |
|
|
| 1932 |
/** Post the given event to the corresponding event queue for the given
|
|
| 1933 |
component. */
|
|
| 1934 | 693 |
protected void postEvent(Component comp, AWTEvent ev) { |
| 1935 | 693 |
if (Log.isClassDebugEnabled(Robot.class)) |
| 1936 | 0 |
Log.debug("POST: " + toString(ev));
|
| 1937 | 693 |
if (eventMode == EM_AWT
|
| 1938 |
&& AWT.isAWTPopupMenuBlocking()) {
|
|
| 1939 | 0 |
throw new Error("Event queue is blocked by an active AWT PopupMenu"); |
| 1940 |
} |
|
| 1941 |
// Force an update of the input state, so that we're in synch
|
|
| 1942 |
// internally. Otherwise we might post more events before this
|
|
| 1943 |
// one gets processed and end up using stale values for those events.
|
|
| 1944 | 693 |
state.update(ev); |
| 1945 | 693 |
EventQueue q = getEventQueue(comp); |
| 1946 | 693 |
q.postEvent(ev); |
| 1947 | 693 |
delay(autoDelay); |
| 1948 | 693 |
AWTEvent prev = lastEventPosted; |
| 1949 | 693 |
lastEventPosted = ev; |
| 1950 | 693 |
if (ev instanceof MouseEvent) { |
| 1951 | 209 |
if (ev.getID() == MouseEvent.MOUSE_PRESSED) {
|
| 1952 | 41 |
lastMousePress = (MouseEvent)ev; |
| 1953 | 41 |
countingClicks = true;
|
| 1954 |
} |
|
| 1955 | 168 |
else if (ev.getID() != MouseEvent.MOUSE_RELEASED |
| 1956 |
&& ev.getID() != MouseEvent.MOUSE_CLICKED) {
|
|
| 1957 | 86 |
countingClicks = false;
|
| 1958 |
} |
|
| 1959 |
} |
|
| 1960 |
|
|
| 1961 |
// Generate a click if there are no events between press/release
|
|
| 1962 |
// Unfortunately, I can only guess how the VM generates them
|
|
| 1963 | 693 |
if (eventMode == EM_AWT
|
| 1964 |
&& ev.getID() == MouseEvent.MOUSE_RELEASED |
|
| 1965 |
&& prev.getID() == MouseEvent.MOUSE_PRESSED) {
|
|
| 1966 | 41 |
MouseEvent me = (MouseEvent)ev; |
| 1967 | 41 |
AWTEvent click = new MouseEvent(comp,
|
| 1968 |
MouseEvent.MOUSE_CLICKED, |
|
| 1969 |
System.currentTimeMillis(), |
|
| 1970 |
me.getModifiers(), |
|
| 1971 |
me.getX(), me.getY(), |
|
| 1972 |
me.getClickCount(), |
|
| 1973 |
false);
|
|
| 1974 | 41 |
postEvent(comp, click); |
| 1975 |
} |
|
| 1976 |
} |
|
| 1977 |
|
|
| 1978 |
/** Wait for the given Condition to return true. The default timeout may
|
|
| 1979 |
* be changed by setting abbot.robot.default_delay.
|
|
| 1980 |
* @throws WaitTimedOutError if the default timeout (30s) is exceeded.
|
|
| 1981 |
*/
|
|
| 1982 | 0 |
public void wait(Condition condition) { |
| 1983 | 0 |
wait(condition, defaultDelay); |
| 1984 |
} |
|
| 1985 |
|
|
| 1986 |
/** Wait for the given Condition to return true, waiting for timeout ms.
|
|
| 1987 |
* @throws WaitTimedOutError if the timeout is exceeded.
|
|
| 1988 |
*/
|
|
| 1989 | 39 |
public void wait(Condition condition, long timeout) { |
| 1990 | 39 |
wait(condition, timeout, SLEEP_INTERVAL); |
| 1991 |
} |
|
| 1992 |
|
|
| 1993 |
/** Wait for the given Condition to return true, waiting for timeout ms,
|
|
| 1994 |
* polling at the given interval.
|
|
| 1995 |
* @throws WaitTimedOutError if the timeout is exceeded.
|
|
| 1996 |
*/
|
|
| 1997 | 39 |
public void wait(Condition condition, long timeout, int interval) { |
| 1998 | 39 |
long start = System.currentTimeMillis();
|
| 1999 | 39 |
while (!condition.test()) {
|
| 2000 | 0 |
if (System.currentTimeMillis() - start > timeout) {
|
| 2001 | 0 |
String msg = "Timed out waiting for " + condition;
|
| 2002 | 0 |
throw new WaitTimedOutError(msg); |
| 2003 |
} |
|
| 2004 | 0 |
delay(interval); |
| 2005 |
} |
|
| 2006 |
} |
|
| 2007 |
|
|
| 2008 | 489 |
public void reset() { |
| 2009 | 489 |
if (eventMode == EM_ROBOT) {
|
| 2010 | 457 |
Dimension d = toolkit.getScreenSize(); |
| 2011 | 457 |
mouseMove(d.width/2, d.height/2); |
| 2012 | 457 |
mouseMove(d.width/2-1, d.height/2-1); |
| 2013 |
} |
|
| 2014 |
else {
|
|
| 2015 |
// clear any held state
|
|
| 2016 | 32 |
state.clear(); |
| 2017 |
} |
|
| 2018 |
} |
|
| 2019 |
|
|
| 2020 |
/** Return the Component which currently owns the focus. */
|
|
| 2021 | 454 |
public Component findFocusOwner() {
|
| 2022 | 454 |
return AWT.getFocusOwner();
|
| 2023 |
} |
|
| 2024 |
|
|
| 2025 |
/** Returns whether it is possible to resize windows that are not an
|
|
| 2026 |
instance of Frame or Dialog. Most X11 window managers will allow
|
|
| 2027 |
this, but stock Macintosh and Windows do not.
|
|
| 2028 |
*/
|
|
| 2029 | 4 |
public static boolean canResizeWindows() { |
| 2030 | 4 |
return !Platform.isWindows() && !Platform.isMacintosh();
|
| 2031 |
} |
|
| 2032 |
|
|
| 2033 |
/** Returns whether it is possible to move windows that are not an
|
|
| 2034 |
instance of Frame or Dialog. Most X11 window managers will allow
|
|
| 2035 |
this, but stock Macintosh and Windows do not.
|
|
| 2036 |
*/
|
|
| 2037 | 2 |
public static boolean canMoveWindows() { |
| 2038 | 2 |
return !Platform.isWindows() && !Platform.isMacintosh();
|
| 2039 |
} |
|
| 2040 |
|
|
| 2041 |
/** Returns the appropriate auto delay for robot-generated events.
|
|
| 2042 |
* As platforms are tested at 0 delay, adjust this value.<p>
|
|
| 2043 |
*/
|
|
| 2044 | 96 |
public static int getPreferredRobotAutoDelay() { |
| 2045 | 96 |
if (Platform.isWindows() || Platform.isOSX() || Platform.isX11())
|
| 2046 | 96 |
return 0;
|
| 2047 | 0 |
return 50;
|
| 2048 |
} |
|
| 2049 |
} |
|
| 2050 |
|
|
||||||||||