source: trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateJacaretoReplay.java @ 1809

Last change on this file since 1809 was 1809, checked in by dmay, 10 years ago

more click fixes

  • Property svn:mime-type set to text/plain
File size: 23.4 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.plugin.jfc.commands;
16
17import java.io.BufferedWriter;
18import java.io.File;
19import java.io.FileOutputStream;
20import java.io.IOException;
21import java.io.OutputStreamWriter;
22import java.util.ArrayList;
23import java.util.Calendar;
24import java.util.Collection;
25import java.util.HashMap;
26import java.util.Iterator;
27import java.util.List;
28import java.util.UUID;
29import java.util.logging.Level;
30
31import de.ugoe.cs.autoquest.CommandHelpers;
32import de.ugoe.cs.autoquest.SequenceInstanceOf;
33import de.ugoe.cs.util.console.Command;
34import de.ugoe.cs.autoquest.eventcore.Event;
35import de.ugoe.cs.autoquest.eventcore.IEventTarget;
36import de.ugoe.cs.autoquest.eventcore.gui.*;
37import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
38import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
39import de.ugoe.cs.util.console.Console;
40import de.ugoe.cs.util.console.GlobalDataContainer;
41
42// helper class for the tree like structure part within a Jacareto file
43class StructureNode {
44    public static int nextRef = 0;
45
46    public String content;
47    public ArrayList<StructureNode> children;
48
49    public StructureNode(String type) {
50        setContent(type);
51        children = new ArrayList<StructureNode>();
52    }
53
54    public StructureNode() {
55        content = "<Recordable ref=\"" + (nextRef++) + "\" />";
56        children = new ArrayList<StructureNode>();
57    }
58
59    public void setContent(String type) {
60        content = "<StructureElement class=\"jacareto.struct." + type + "\">";
61    }
62
63    public StructureNode add(String type) {
64        StructureNode node = new StructureNode(type);
65        children.add(node);
66        return node;
67    }
68
69    public void addRecordable() {
70        children.add(new StructureNode());
71    }
72
73    @Override
74    public String toString() {
75        String separator = System.getProperty("line.separator");
76        String result = content + separator;
77
78        for (StructureNode child : children) {
79            result += child.toString();
80        }
81
82        if (content.endsWith("/>")) {
83            return result;
84        }
85        return result + "</StructureElement>" + separator;
86    }
87}
88
89/**
90 * <p>
91 * Command to create a Jacareto xml replay file from stored sessions.
92 * </p>
93 *
94 * @author Daniel May
95 * @version 1.0
96 */
97public class CMDgenerateJacaretoReplay implements Command {
98    private static final int EVENT_DURATION = 150;
99    private static final int DOUBLE_CLICK_DURATION = 50;
100    private static final int STARTUP_DELAY = 10000;
101
102    private JFCGUIElement currentFocus;
103    private StructureNode structure;
104
105    private StructureNode lastKeySequenceEvent;
106    private StructureNode lastKeyTypedEvent;
107    private int currentKeyModifiers;
108
109    private HashMap<VirtualKey, Integer> modifiers;
110
111    private StructureNode lastMouseClickEvent;
112    private StructureNode lastFocusChangeEvent;
113    private StructureNode lastItemActionEvent;
114    private IEventTarget lastMouseDownTarget;
115
116    /*
117     * (non-Javadoc)
118     *
119     * @see de.ugoe.cs.util.console.Command#help()
120     */
121    @Override
122    public String help() {
123        return "generateJacaretoReplay <filename> <sequences> <class> <basepath> <classpathext> {<initclass>}";
124    }
125
126    /*
127     * (non-Javadoc)
128     *
129     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
130     */
131    @SuppressWarnings("unchecked")
132    @Override
133    public void run(List<Object> parameters) {
134        String filename;
135        String sequencesName;
136        String classpath;
137        String basepath;
138        String classpathext;
139        String initclass = "";
140        try {
141            filename = (String) parameters.get(0);
142            sequencesName = (String) parameters.get(1);
143            classpath = (String) parameters.get(2);
144            basepath = (String) parameters.get(3);
145            classpathext = (String) parameters.get(4);
146        }
147        catch (Exception e) {
148            throw new IllegalArgumentException();
149        }
150
151        if (parameters.size() > 5) {
152            initclass = (String) parameters.get(5);
153        }
154
155        Collection<List<Event>> sequences = null;
156        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName);
157        if (dataObject == null) {
158            CommandHelpers.objectNotFoundMessage(sequencesName);
159            return;
160        }
161        if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
162            CommandHelpers.objectNotType(sequencesName, "Collection<List<Event<?>>>");
163            return;
164        }
165
166        sequences = (Collection<List<Event>>) dataObject;
167
168        // map which maps VirtualKeys back to awt key modifier codes
169        modifiers = new HashMap<>();
170        modifiers.put(VirtualKey.SHIFT, 1);
171        modifiers.put(VirtualKey.CONTROL, 2);
172        modifiers.put(VirtualKey.ALT, 8);
173        modifiers.put(VirtualKey.ALT_GRAPH, 32);
174        currentKeyModifiers = 0;
175
176        StructureNode.nextRef = 0;
177
178        writeJacaretoXML(sequences, filename, classpath, initclass, basepath, classpathext);
179    }
180
181    private void writeLine(BufferedWriter writer, String line) throws IOException {
182        writer.write(line);
183        writer.newLine();
184    }
185
186    private void writeJacaretoHead(BufferedWriter writer,
187                                   String classname,
188                                   String initclass,
189                                   String basepath,
190                                   String classpathext) throws IOException
191    {
192        Calendar now = Calendar.getInstance();
193
194        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
195        writeLine(writer, "<JacaretoStructure>");
196        writeLine(writer, "<Record>");
197
198        //@formatter:off
199        writeLine(writer, "<Calendar "
200            + "procTime=\"0\" "
201            + "duration=\"0\" "
202            + "year=\"" + now.get(Calendar.YEAR) + "\" "
203            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
204            + "date=\"" + now.get(Calendar.DAY_OF_MONTH) + "\" "
205            + "hour=\"" + now.get(Calendar.HOUR_OF_DAY) + "\" "
206            + "min=\"" + now.get(Calendar.MINUTE) + "\" "
207            + "sec=\"" + now.get(Calendar.SECOND) + "\" "
208            + "uuid=\"" + UUID.randomUUID() + "\" />"
209        );
210        writeLine(writer,
211                  "<SystemInfo procTime=\"0\" duration=\"0\" screenWidth=\"2646\" screenHeight=\"1024\" javaVersion=\"1.7.0_65\" lookAndFeel=\"javax.swing.plaf.metal.MetalLookAndFeel\" uuid=\"720f430f-52cf-4d8b-9fbe-58434f766efe\" />");
212        writeLine(writer,
213                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
214        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
215        writeLine(writer, "<ApplicationStarter "
216            + "procTime=\"" + STARTUP_DELAY + "\" "
217            + "duration=\"" + STARTUP_DELAY + "\" "
218            + "name=\"Autoquest Replay\" "
219            + "class=\"" + classname + "\" "
220            + "initclass=\"" + initclass + "\" "
221            + "basepath=\"" + basepath + "\" "
222            + "classpathext=\"" + classpathext + "\" "
223            + "detectDuration=\"false\" "
224            + "captureparams=\"\" "
225            + "replayparams=\"\" "
226            + "uuid=\"" + UUID.randomUUID() + "\" />"
227        );
228        //@formatter:on
229    }
230
231    private void writeJacaretoEvents(BufferedWriter writer, Collection<List<Event>> sequences)
232        throws IOException
233    {
234        structure = new StructureNode("RootElement");
235        // reference the elements that we included in the header
236        structure.addRecordable(); // Calendar
237        structure.addRecordable(); // SystemInfo
238        structure.addRecordable(); // KeyboardState
239        structure.addRecordable(); // ComponentMode
240        structure.addRecordable(); // ApplicationStarter
241
242        for (List<Event> sequence : sequences) {
243            for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
244                Event event = eventIter.next();
245
246                if (event.getType() instanceof MouseButtonDown) {
247                    commitFocusEvent();
248                    lastKeySequenceEvent = null;
249
250                    lastMouseClickEvent = new StructureNode("MouseClick");
251                    lastMouseDownTarget = event.getTarget();
252                    writeMouseClickEvent(writer, event, EVENT_DURATION, 501);
253                }
254                else if (event.getType() instanceof MouseButtonUp) {
255                    lastKeySequenceEvent = null;
256
257                    writeMouseClickEvent(writer, event, EVENT_DURATION, 502);
258                }
259                else if (event.getType() instanceof MouseDoubleClick) {
260                    StructureNode multiClick = structure.add("MultipleMouseClick");
261
262                    // first click
263                    lastMouseClickEvent = multiClick.add("MouseClick");
264                    writeMouseClickEvent(writer, event, DOUBLE_CLICK_DURATION, 501);
265                    writeMouseClickEvent(writer, event, DOUBLE_CLICK_DURATION, 502);
266                    writeMouseClickEvent(writer, event, DOUBLE_CLICK_DURATION, 500);
267                    // second click
268                    lastMouseClickEvent = multiClick.add("MouseClick");
269                    writeMouseClickEvent(writer, event, DOUBLE_CLICK_DURATION, 501);
270                    writeMouseClickEvent(writer, event, DOUBLE_CLICK_DURATION, 502);
271                    writeMouseClickEvent(writer, event, DOUBLE_CLICK_DURATION, 500);
272
273                    lastMouseClickEvent = null;
274                }
275                else if (event.getType() instanceof MouseClick) {
276                    lastKeySequenceEvent = null;
277
278                    if (lastMouseClickEvent != null) {
279                        if (lastMouseDownTarget == event.getTarget()) {
280                            // this is the standard case:
281                            // mouseDown, mouseUp and mouseClick sequence
282                            // was triggered on this target
283
284                            writeMouseClickEvent(writer, event, EVENT_DURATION, 500);
285                            writeItemActionEvent(writer, event);
286
287                            if (lastFocusChangeEvent == null) {
288                                // write structure sequentially
289                                structure.children.add(lastMouseClickEvent);
290                                structure.children.add(lastItemActionEvent);
291                            }
292                            else {
293                                // with nested structure
294                                structure.children.add(lastItemActionEvent);
295                                lastItemActionEvent.children.add(0, lastFocusChangeEvent);
296                                lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
297
298                                lastFocusChangeEvent = null;
299                                lastMouseClickEvent = null;
300                            }
301                        }
302                        else {
303                            // target of mouseDown and mouseClick are different
304                            // -> this is, for example, a click on a menu item
305                            // within a condensed sequence
306                            commitFocusEvent();
307
308                            // finish the last click on the old target
309                            writeMouseClickEvent(writer, event, EVENT_DURATION, 500);
310                            structure.children.add(lastMouseClickEvent);
311
312                            // and generate a new one
313                            generateFullClick(writer, event);
314                        }
315                    }
316                    else {
317                        // a target was clicked repeatedly:
318                        // the condensed sequence contains no mouseDowns or
319                        // mouseUps anymore
320                        // -> just generate another full click
321                        generateFullClick(writer, event);
322                    }
323                }
324                else if (event.getType() instanceof KeyboardFocusChange) {
325                    lastKeySequenceEvent = null;
326
327                    writeFocusChangeEvent(writer, event);
328                }
329                else if (event.getType() instanceof KeyPressed) {
330                    commitFocusEvent();
331
332                    if (lastKeySequenceEvent == null) {
333                        lastKeySequenceEvent = structure.add("KeySequence");
334                    }
335                    lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
336                    writeKeyEvent(writer, event, 401);
337                }
338                else if (event.getType() instanceof KeyReleased) {
339                    commitFocusEvent();
340
341                    writeKeyEvent(writer, event, 402);
342                }
343            }
344        }
345    }
346
347    private void commitFocusEvent() {
348        if (lastFocusChangeEvent != null) {
349            structure.children.add(lastFocusChangeEvent);
350            lastFocusChangeEvent = null;
351        }
352    }
353
354    private void generateFullClick(BufferedWriter writer, Event event) throws IOException {
355        lastMouseClickEvent = new StructureNode("MouseClick");
356        lastMouseDownTarget = event.getTarget();
357
358        writeMouseClickEvent(writer, event, EVENT_DURATION, 501);
359        writeMouseClickEvent(writer, event, EVENT_DURATION, 502);
360        writeMouseClickEvent(writer, event, EVENT_DURATION, 500);
361        writeItemActionEvent(writer, event);
362
363        structure.children.add(lastMouseClickEvent);
364        structure.children.add(lastItemActionEvent);
365
366        lastMouseDownTarget = null;
367
368    }
369
370    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
371        writeLine(writer, "</Record>");
372
373        // write the recording's structure
374        writeLine(writer, "<Structure>");
375        writer.write(structure.toString());
376        // close root element
377        writeLine(writer, "</Structure>");
378    }
379
380    private void writeJacaretoXML(Collection<List<Event>> sequences,
381                                  String filename,
382                                  String classpath,
383                                  String initclass,
384                                  String basepath,
385                                  String classpathext)
386    {
387        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
388
389        try {
390            writeJacaretoHead(writer, classpath, initclass, basepath, classpathext);
391            writeJacaretoEvents(writer, sequences);
392            writeJacaretoTail(writer);
393            writeLine(writer, "</JacaretoStructure>");
394
395            writer.flush();
396            writer.close();
397        }
398        catch (IOException e) {
399            Console.printerrln("Unable to write Jacareto replay file " + filename);
400        }
401    }
402
403    /**
404     * <p>
405     * Helper function that opens the replay file for writing.
406     * </p>
407     *
408     * @param filename
409     *            name and path of the replay file
410     * @param encoding
411     *            file encoding, empty string for platform default
412     * @return {@link OutputStreamWriter} that writes to the replay file
413     */
414    private OutputStreamWriter openReplayFile(String filename) {
415        File file = new File(filename);
416        boolean fileCreated;
417        try {
418            fileCreated = file.createNewFile();
419            if (!fileCreated) {
420                Console.traceln(Level.INFO, "Created logfile " + filename);
421            }
422            else {
423                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
424            }
425        }
426        catch (IOException e) {
427            Console.printerrln("Unable to create file " + filename);
428            Console.logException(e);
429        }
430        OutputStreamWriter writer = null;
431        try {
432            writer = new OutputStreamWriter(new FileOutputStream(file));
433        }
434        catch (IOException e) {
435            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
436            Console.logException(e);
437        }
438        return writer;
439    }
440
441    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
442        JFCGUIElement target = (JFCGUIElement) event.getTarget();
443        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
444
445        //@formatter:off
446        writeLine(writer,
447            "<ItemEvent "
448            + "procTime=\"0\" "
449            + "duration=\"0\" "
450            + "source=\"" + target.getJacaretoHierarchy() + "\" "
451            + "class=\"" + target.getSpecification().getType() + "\" "
452            + "uuid=\"" + UUID.randomUUID() + "\" "
453            + "ID=\"701\" "
454            + "item=\"\" "
455            + "stateChange=\"1\" />"
456        );
457        writeLine(writer,
458            "<ActionEvent "
459            + "procTime=\"0\" "
460            + "duration=\"0\" "
461            + "source=\"" + target.getJacaretoHierarchy() + "\" "
462            + "class=\"" + target.getSpecification().getType() + "\" "
463            + "uuid=\"" + UUID.randomUUID() + "\" "
464            + "ID=\"1001\" "
465            + "command=" + target.getName() + " "
466            + "modifiers=\"" + getButtonModifier(info) + "\" />"
467        );
468        //@formatter:on
469        lastItemActionEvent = new StructureNode("ItemStateChange");
470        lastItemActionEvent.addRecordable();
471        lastItemActionEvent.addRecordable();
472    }
473
474    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
475        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
476        JFCGUIElement target = (JFCGUIElement) event.getTarget();
477
478        if (currentFocus != null) {
479            lastFocusChangeEvent = new StructureNode("FocusChange");
480
481            // focus lost on old target
482            writeFocusEvent(writer, info, currentFocus, 1005);
483            // focus gained on new target
484            writeFocusEvent(writer, info, target, 1004);
485        }
486        else {
487            // TODO: it seems like Jacareto wants a window activation before
488            // the first focus event but that is not the case in autoquest,
489            // skip for now
490        }
491
492        currentFocus = target;
493    }
494
495    private void writeFocusEvent(BufferedWriter writer,
496                                 KeyboardFocusChange info,
497                                 JFCGUIElement target,
498                                 int jacId) throws IOException
499    {
500        //@formatter:off
501        writeLine(writer,
502            "<FocusEvent "
503            + "procTime=\"0\" "
504            + "duration=\"0\" "
505            + "source=\"" + target.getJacaretoHierarchy() + "\" "
506            + "class=\"" + target.getSpecification().getType() + "\" "
507            + "uuid=\"" + UUID.randomUUID() + "\" "
508            + "ID=\"" + jacId + "\" "
509            + "component=\"null\" "
510            + "root=\"" + target.getJacaretoRoot() + "\" "
511            + "xPos=\"0\" "
512            + "yPos=\"0\" "
513            + "width=\"0\" "
514            + "height=\"0\" "
515            + "isTemporary=\"false\" />"
516        );
517        //@formatter:on
518        lastFocusChangeEvent.addRecordable();
519    }
520
521    private void writeMouseClickEvent(BufferedWriter writer, Event event, int duration, int jacId)
522        throws IOException
523    {
524        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
525        JFCGUIElement target = (JFCGUIElement) event.getTarget();
526        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
527
528        // TODO: change procTime and duration to adequate values
529        //@formatter:off
530        writeLine(writer,
531            "<MouseEvent "
532            + "procTime=\"0\" "
533            + "duration=\"" + duration + "\" "
534            + "source=\"" + target.getJacaretoHierarchy() + "\" "
535            + "class=\"" + target.getSpecification().getType() + "\" "
536            + "uuid=\"" + UUID.randomUUID() + "\" "
537            + "ID=\"" + jacId + "\" "
538            + "component=\"null\" "
539            + "root=\"" + target.getJacaretoRoot() + "\" "
540            + "xPos=\"0\" "
541            + "yPos=\"0\" "
542            + "width=\"0\" "
543            + "height=\"0\" "
544            + "when=\"" + event.getTimestamp() + "\" "
545            + "isConsumed=\"false\">"
546        );
547        writeLine(writer,
548            "<MouseInfo "
549            + "xPosition=\"" + info.getX() + "\" "
550            + "yPosition=\"" + info.getY() + "\" "
551            + "rootX=\"0\" "
552            + "rootY=\"0\" "
553            + "clickCount=\"" + clickCount + "\" "
554            + "modifiers=\"" + getButtonModifier(info) + "\" "
555            + "isPopupTrigger=\"false\" />"
556        );
557        writeLine(writer, "</MouseEvent>");
558        //@formatter:on
559
560        lastMouseClickEvent.addRecordable();
561    }
562
563    private int getButtonModifier(MouseButtonInteraction info) {
564        switch (info.getButton())
565        {
566            case LEFT:
567                return 16;
568            case MIDDLE:
569                return 8;
570            case RIGHT:
571                return 4;
572            default:
573                // TODO: handle unknown mouse button
574                return -1;
575        }
576    }
577
578    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
579        KeyInteraction info = (KeyInteraction) event.getType();
580        JFCGUIElement target = (JFCGUIElement) event.getTarget();
581        int keyCode = info.getKey().getVirtualKeyCode();
582
583        applyKeyModifier(info.getKey(), jacId == 401);
584
585        //@formatter:off
586        writeLine(writer,
587            "<KeyEvent "
588            + "procTime=\"0\" "
589            + "duration=\"" + EVENT_DURATION + "\" "
590            + "source=\"" + target.getJacaretoHierarchy() + "\" "
591            + "class=\"" + target.getSpecification().getType() + "\" "
592            + "uuid=\"" + UUID.randomUUID() + "\" "
593            + "ID=\"" + jacId + "\" "
594            + "component=\"null\" "
595            + "root=\"" + target.getJacaretoRoot() + "\" "
596            + "xPos=\"0\" "
597            + "yPos=\"0\" "
598            + "width=\"0\" "
599            + "height=\"0\" "
600            + "when=\"" + event.getTimestamp() + "\" "
601            + "isConsumed=\"false\">"
602        );
603        writeLine(writer,
604            "<KeyInfo "
605            + "keyCode=\"" + keyCode + "\" "
606            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
607            + "modifiers=\"" + currentKeyModifiers + "\" />"
608        );
609       
610        writeLine(writer, "</KeyEvent>");
611       
612        lastKeyTypedEvent.addRecordable();
613    }
614   
615    private String getKeyChar (int keyCode) {
616        if (keyCode >= 32 && keyCode < 127) {
617            return String.valueOf((char)keyCode);
618        }
619        return "_NO_LEGAL_XML_CHAR";
620    }
621   
622    private void applyKeyModifier (VirtualKey key, boolean set) {
623        Integer modifier = modifiers.get(key);
624        if (modifier != null) {
625            if (set) {
626                currentKeyModifiers |= modifier;
627            }
628            else {
629                currentKeyModifiers &= ~modifier;
630            }
631        }
632    }
633}
Note: See TracBrowser for help on using the repository browser.