1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.netbeans.jemmy.drivers.input;
24
25import java.awt.event.InputEvent;
26import java.awt.event.KeyEvent;
27import java.lang.reflect.InvocationTargetException;
28
29import org.netbeans.jemmy.ClassReference;
30import org.netbeans.jemmy.JemmyException;
31import org.netbeans.jemmy.JemmyProperties;
32import org.netbeans.jemmy.QueueTool;
33import org.netbeans.jemmy.TestOut;
34import org.netbeans.jemmy.Timeout;
35import org.netbeans.jemmy.drivers.LightSupportiveDriver;
36
37/**
38 * Superclass for all drivers using robot.
39 *
40 * @author Alexandre Iline(alexandre.iline@oracle.com)
41 */
42public class RobotDriver extends LightSupportiveDriver {
43
44    private boolean haveOldPos;
45    private boolean smooth = false;
46    private double oldX;
47    private double oldY;
48    private static final double CONSTANT1 = 0.75;
49    private static final double CONSTANT2 = 12.0;
50    /**
51     * A reference to the robot instance.
52     */
53    protected ClassReference robotReference = null;
54
55    /**
56     * A QueueTool instance.
57     */
58    protected QueueTool qtool;
59
60    protected Timeout autoDelay;
61
62    /**
63     * Constructs a RobotDriver object.
64     *
65     * @param autoDelay Time for {@code Robot.setAutoDelay(long)} method.
66     * @param supported an array of supported class names
67     */
68    public RobotDriver(Timeout autoDelay, String[] supported) {
69        super(supported);
70        qtool = new QueueTool();
71        qtool.setOutput(TestOut.getNullOutput());
72        this.autoDelay = autoDelay;
73    }
74
75    public RobotDriver(Timeout autoDelay, String[] supported, boolean smooth) {
76        this(autoDelay, supported);
77        this.smooth = smooth;
78    }
79
80    /**
81     * Constructs a RobotDriver object.
82     *
83     * @param autoDelay Time for {@code Robot.setAutoDelay(long)} method.
84     */
85    public RobotDriver(Timeout autoDelay) {
86        this(autoDelay, new String[]{"org.netbeans.jemmy.operators.ComponentOperator"});
87    }
88
89    public RobotDriver(Timeout autoDelay, boolean smooth) {
90        this(autoDelay);
91        this.smooth = smooth;
92    }
93
94    public void pressMouse(int mouseButton, int modifiers) {
95        pressModifiers(modifiers);
96        makeAnOperation("mousePress",
97                new Object[]{mouseButton},
98                new Class<?>[]{Integer.TYPE});
99    }
100
101    public void releaseMouse(int mouseButton, int modifiers) {
102        makeAnOperation("mouseRelease",
103                new Object[]{mouseButton},
104                new Class<?>[]{Integer.TYPE});
105        releaseModifiers(modifiers);
106    }
107
108    public void moveMouse(int x, int y) {
109        if (!smooth) {
110            makeAnOperation("mouseMove",
111                    new Object[]{x, y},
112                    new Class<?>[]{Integer.TYPE, Integer.TYPE});
113        } else {
114            double targetX = x;
115            double targetY = y;
116            if (haveOldPos) {
117                double currX = oldX;
118                double currY = oldY;
119                double vx = 0.0;
120                double vy = 0.0;
121                while (Math.round(currX) != Math.round(targetX)
122                        || Math.round(currY) != Math.round(targetY)) {
123                    vx = vx * CONSTANT1 + (targetX - currX) / CONSTANT2 * (1.0 - CONSTANT1);
124                    vy = vy * CONSTANT1 + (targetY - currY) / CONSTANT2 * (1.0 - CONSTANT1);
125                    currX += vx;
126                    currY += vy;
127                    makeAnOperation("mouseMove", new Object[]{
128                                    (int) Math.round(currX),
129                                    (int) Math.round(currY)},
130                            new Class<?>[]{Integer.TYPE, Integer.TYPE});
131                }
132            } else {
133                makeAnOperation("mouseMove", new Object[]{
134                                (int) Math.round(targetX),
135                                (int) Math.round(targetY)},
136                        new Class<?>[]{Integer.TYPE, Integer.TYPE});
137            }
138            haveOldPos = true;
139            oldX = targetX;
140            oldY = targetY;
141        }
142    }
143
144    public void clickMouse(int x, int y, int clickCount, int mouseButton,
145            int modifiers, Timeout mouseClick) {
146        pressModifiers(modifiers);
147        moveMouse(x, y);
148        makeAnOperation("mousePress", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
149        for (int i = 1; i < clickCount; i++) {
150            makeAnOperation("mouseRelease", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
151            makeAnOperation("mousePress", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
152        }
153        mouseClick.sleep();
154        makeAnOperation("mouseRelease", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
155        releaseModifiers(modifiers);
156    }
157
158    public void dragMouse(int x, int y, int mouseButton, int modifiers) {
159        moveMouse(x, y);
160    }
161
162    public void dragNDrop(int start_x, int start_y, int end_x, int end_y,
163            int mouseButton, int modifiers, Timeout before, Timeout after) {
164        moveMouse(start_x, start_y);
165        pressMouse(mouseButton, modifiers);
166        before.sleep();
167        moveMouse(end_x, end_y);
168        after.sleep();
169        releaseMouse(mouseButton, modifiers);
170    }
171
172    /**
173     * Presses a key.
174     *
175     * @param keyCode Key code ({@code KeyEventVK_*} field.
176     * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
177     */
178    public void pressKey(int keyCode, int modifiers) {
179        pressModifiers(modifiers);
180        makeAnOperation("keyPress",
181                new Object[]{keyCode},
182                new Class<?>[]{Integer.TYPE});
183    }
184
185    /**
186     * Releases a key.
187     *
188     * @param keyCode Key code ({@code KeyEventVK_*} field.
189     * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
190     */
191    public void releaseKey(int keyCode, int modifiers) {
192        releaseModifiers(modifiers);
193        makeAnOperation("keyRelease",
194                new Object[]{keyCode},
195                new Class<?>[]{Integer.TYPE});
196    }
197
198    /**
199     * Performs a single operation.
200     *
201     * @param method a name of {@code java.awt.Robot} method.
202     * @param params method parameters
203     * @param paramClasses method parameters classes
204     */
205    protected void makeAnOperation(final String method, final Object[] params, final Class<?>[] paramClasses) {
206        if (robotReference == null) {
207            initRobot();
208        }
209        try {
210            robotReference.invokeMethod(method, params, paramClasses);
211            synchronizeRobot();
212        } catch (InvocationTargetException
213                | IllegalStateException
214                | NoSuchMethodException
215                | IllegalAccessException e) {
216            throw (new JemmyException("Exception during java.awt.Robot accessing", e));
217        }
218    }
219
220    /**
221     * Calls {@code java.awt.Robot.waitForIdle()} method.
222     */
223    protected void synchronizeRobot() {
224        if (!QueueTool.isDispatchThread()) {
225            if ((JemmyProperties.getCurrentDispatchingModel() & JemmyProperties.QUEUE_MODEL_MASK) != 0) {
226                if (robotReference == null) {
227                    initRobot();
228                }
229                try {
230                    robotReference.invokeMethod("waitForIdle", null, null);
231                } catch (Exception e) {
232                    e.printStackTrace();
233                }
234            }
235        }
236    }
237
238    /**
239     * Presses modifiers keys by robot.
240     *
241     * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
242     */
243    protected void pressModifiers(int modifiers) {
244        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
245            pressKey(KeyEvent.VK_SHIFT, modifiers & ~InputEvent.SHIFT_MASK);
246        } else if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
247            pressKey(KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
248        } else if ((modifiers & InputEvent.ALT_MASK) != 0) {
249            pressKey(KeyEvent.VK_ALT, modifiers & ~InputEvent.ALT_MASK);
250        } else if ((modifiers & InputEvent.META_MASK) != 0) {
251            pressKey(KeyEvent.VK_META, modifiers & ~InputEvent.META_MASK);
252        } else if ((modifiers & InputEvent.CTRL_MASK) != 0) {
253            pressKey(KeyEvent.VK_CONTROL, modifiers & ~InputEvent.CTRL_MASK);
254        }
255    }
256
257    /*
258    protected void pressModifiers(ComponentOperator oper, int modifiers) {
259        if       ((modifiers & InputEvent.SHIFT_MASK) != 0) {
260            pressKey(oper, KeyEvent.VK_SHIFT,     modifiers & ~InputEvent.SHIFT_MASK);
261        } else if((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
262            pressKey(oper, KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
263        } else if((modifiers & InputEvent.ALT_MASK) != 0) {
264            pressKey(oper, KeyEvent.VK_ALT,       modifiers & ~InputEvent.ALT_MASK);
265        } else if((modifiers & InputEvent.META_MASK) != 0) {
266            pressKey(oper, KeyEvent.VK_META,      modifiers & ~InputEvent.META_MASK);
267        } else if((modifiers & InputEvent.CTRL_MASK) != 0) {
268            pressKey(oper, KeyEvent.VK_CONTROL,   modifiers & ~InputEvent.CTRL_MASK);
269        }
270    }
271     */
272    /**
273     * Releases modifiers keys by robot.
274     *
275     * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
276     */
277    protected void releaseModifiers(int modifiers) {
278        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
279            releaseKey(KeyEvent.VK_SHIFT, modifiers & ~InputEvent.SHIFT_MASK);
280        } else if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
281            releaseKey(KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
282        } else if ((modifiers & InputEvent.ALT_MASK) != 0) {
283            releaseKey(KeyEvent.VK_ALT, modifiers & ~InputEvent.ALT_MASK);
284        } else if ((modifiers & InputEvent.META_MASK) != 0) {
285            releaseKey(KeyEvent.VK_META, modifiers & ~InputEvent.META_MASK);
286        } else if ((modifiers & InputEvent.CTRL_MASK) != 0) {
287            releaseKey(KeyEvent.VK_CONTROL, modifiers & ~InputEvent.CTRL_MASK);
288        }
289    }
290
291    /*
292    protected void releaseModifiers(ComponentOperator oper, int modifiers) {
293        if       ((modifiers & InputEvent.SHIFT_MASK) != 0) {
294            releaseKey(oper, KeyEvent.VK_SHIFT,     modifiers & ~InputEvent.SHIFT_MASK);
295        } else if((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
296            releaseKey(oper, KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
297        } else if((modifiers & InputEvent.ALT_MASK) != 0) {
298            releaseKey(oper, KeyEvent.VK_ALT,       modifiers & ~InputEvent.ALT_MASK);
299        } else if((modifiers & InputEvent.META_MASK) != 0) {
300            releaseKey(oper, KeyEvent.VK_META,      modifiers & ~InputEvent.META_MASK);
301        } else if((modifiers & InputEvent.CTRL_MASK) != 0) {
302            releaseKey(oper, KeyEvent.VK_CONTROL,   modifiers & ~InputEvent.CTRL_MASK);
303        }
304    }
305     */
306    private void initRobot() {
307        // need to init Robot in dispatch thread because it hangs on Linux
308        // (see http://www.netbeans.org/issues/show_bug.cgi?id=37476)
309        if (QueueTool.isDispatchThread()) {
310            doInitRobot();
311        } else {
312            qtool.invokeAndWait(new Runnable() {
313                @Override
314                public void run() {
315                    doInitRobot();
316                }
317            });
318        }
319    }
320
321    private void doInitRobot() {
322        try {
323            ClassReference robotClassReverence = new ClassReference("java.awt.Robot");
324            robotReference = new ClassReference(robotClassReverence.newInstance(null, null));
325            robotReference.invokeMethod("setAutoDelay",
326                    new Object[]{(int) ((autoDelay != null)
327                            ? autoDelay.getValue()
328                            : 0)},
329                    new Class<?>[]{Integer.TYPE});
330        } catch (InvocationTargetException
331                | IllegalStateException
332                | NoSuchMethodException
333                | IllegalAccessException
334                | ClassNotFoundException
335                | InstantiationException e) {
336            throw (new JemmyException("Exception during java.awt.Robot accessing", e));
337        }
338    }
339
340}
341