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

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

Work of the last week: Completely redid most of the menu handling, since there were too many cornercases that did not work. Turns out, that in these cases, wrong indices were generated. Fixed.

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