source: trunk/autoquest-core-events/src/main/java/de/ugoe/cs/autoquest/eventcore/gui/KeyInteractionCorrector.java @ 927

Last change on this file since 927 was 927, checked in by sherbold, 12 years ago
  • added copyright under the Apache License, Version 2.0
  • Property svn:mime-type set to text/plain
File size: 11.1 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.eventcore.gui;
16
17import java.util.HashSet;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.ListIterator;
21import java.util.Set;
22import java.util.logging.Level;
23
24import de.ugoe.cs.autoquest.eventcore.Event;
25import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
26import de.ugoe.cs.util.console.Console;
27
28/**
29 * <p>
30 * This class provides the functionality to sort and clean up all key interactions in a log. In
31 * particular:
32 * <ol>
33 * <li>In case a combination key (e.g., shift, alt, control) is held down, multiple
34 * {@link KeyPressed} events are logged, even though only the first one is of importance. This class
35 * removes all {@link KeyPressed} events for combination keys except the first.</li>
36 * <li>In case a normal key is held down, multiple {@link KeyPressed} events are logged, but there
37 * is only one {@link KeyReleased} event. This class adds a {@link KeyReleased} event for all such
38 * {@link KeyPressed} events.</li>
39 * <li>Due to message filtering of applications, it is possible that a {@link KeyReleased} event
40 * without a preceding {@link KeyPressed} event is logged. This class either adds the missing
41 * {@link KeyPressed} right in front of the {@link KeyReleased} or removes the {@link KeyReleased}
42 * depending on the {@link #mode}.
43 * <li>As a result of steps 2-3, we have always a matching {@link KeyPressed}/{@link KeyReleased}
44 * pairs for all normal keys. This class replaces these pairs with a {@link KeyTyped} event at the
45 * position of the {@link KeyPressed} event.</li>
46 * <li>Sometimes combination keys are not released in the same order they have been pressed. This
47 * class ensures that the {@link KeyReleased} are in the opposite order of the {@link KeyPressed}
48 * events for all combination keys.</li>
49 * </ol>
50 * </p>
51 *
52 * @version 1.0
53 * @author Steffen Herbold
54 */
55public class KeyInteractionCorrector {
56
57    /**
58     * <p>
59     * Describes the clean-up mode.
60     * </p>
61     *
62     * @version 1.0
63     * @author Steffen Herbold
64     */
65    public static enum CleanupMode {
66        /**
67         * <p>
68         * Single {@link KeyReleased} are removed from the sequence.
69         * </p>
70         */
71        REMOVAL,
72
73        /**
74         * <p>
75         * {@link KeyPressed} events are added to single {@link KeyReleased} events
76         * </p>
77         */
78        ADDITION
79    };
80
81    /**
82     * <p>
83     *
84     * </p>
85     */
86    private final CleanupMode mode;
87
88    /**
89     * <p>
90     * Constructor. Creates a new {@link KeyInteractionCorrector} with {@link #mode}=
91     * {@link CleanupMode#ADDITION}.
92     * </p>
93     */
94    public KeyInteractionCorrector() {
95        this(CleanupMode.ADDITION);
96    }
97
98    /**
99     * <p>
100     * Constructor. Creates a new {@link KeyInteractionCorrector}.
101     * </p>
102     *
103     * @param mode
104     *            {@link #mode} of the instance
105     */
106    public KeyInteractionCorrector(CleanupMode mode) {
107        this.mode = mode;
108    }
109
110    /**
111     * <p>
112     * Sorts and cleans up key interactions according to the class specification (@see
113     * {@link KeyInteractionCorrector} class comment).
114     * </p>
115     * <p>
116     * This method returns a sorted copy of a sequence, the sequence itself is not changed.
117     * </p>
118     *
119     * @param sequence
120     *            sequence which is sorted
121     * @return sorted copy of sequence
122     */
123    public List<Event> sortKeyInteractions(final List<Event> sequence) {
124        List<Event> sortedSequence = new LinkedList<Event>(sequence);
125
126        handleIncompleteKeyPairs(sortedSequence);
127        sortCombinationKeyPairs(sortedSequence);
128
129        return sortedSequence;
130    }
131
132    /**
133     * <p>
134     * Performs tasks 1-4 defined in the class description. Operations are performed in-place on the
135     * passed sequence.
136     * </p>
137     *
138     * @param sequence
139     *            sequence which is sorted
140     */
141    private void sortCombinationKeyPairs(List<Event> sequence) {
142        LinkedList<VirtualKey> pressedCombinationKeys = new LinkedList<VirtualKey>();
143
144        for (int i = 0; i < sequence.size(); i++) {
145            Event event = sequence.get(i);
146            if (event.getType() instanceof KeyPressed) {
147                final VirtualKey key = ((KeyPressed) event.getType()).getKey();
148                if (key.isCombinationKey()) {
149                    pressedCombinationKeys.add(key);
150                }
151            }
152            if (event.getType() instanceof KeyReleased) {
153                final VirtualKey key = ((KeyReleased) event.getType()).getKey();
154                if (key.isCombinationKey()) {
155                    /*
156                     * if( pressedCombinationKeys.isEmpty() ) { Console.traceln(Level.INFO, "" + i);
157                     * for( int j=i-30 ; j<=i ; j++ ) { Console.traceln(Level.INFO,
158                     * sequence.get(i).toString()); } }
159                     */
160                    if (key.equals(pressedCombinationKeys.getLast())) {
161                        pressedCombinationKeys.removeLast();
162                    }
163                    else {
164                        // look-ahead to find new position
165                        int offset;
166                        for (offset = 1; offset + i < sequence.size(); offset++) {
167                            Event lookaheadEvent = sequence.get(i + offset);
168                            if (lookaheadEvent.getType() instanceof KeyReleased) {
169                                if (((KeyReleased) lookaheadEvent.getType()).getKey()
170                                    .equals(pressedCombinationKeys.getLast()))
171                                {
172                                    break;
173                                }
174                            }
175                        }
176                        sequence.add(i + offset + 1, event);
177                        sequence.remove(i);
178                        i--;
179                    }
180                }
181            }
182        }
183    }
184
185    /**
186     * <p>
187     * Performs task 5 defined in the class description. Operations are performed in-place on the
188     * passed sequence.
189     * </p>
190     *
191     * @param sequence
192     *            sequence which is sorted
193     */
194    private void handleIncompleteKeyPairs(List<Event> sequence) {
195        Set<VirtualKey> pressedKeys = new HashSet<VirtualKey>();
196        int firstPressedIndex = -1;
197
198        Set<VirtualKey> pressedCombinationKeysSession = new HashSet<VirtualKey>();
199
200        for (int i = 0; i < sequence.size(); i++) {
201            Event event = sequence.get(i);
202            if (event.getType() instanceof KeyboardFocusChange) {
203                if (firstPressedIndex != -1) {
204                    sequence.remove(i);
205                    sequence.add(firstPressedIndex, event);
206                }
207            }
208
209            if (event.getType() instanceof KeyPressed) {
210                if (pressedKeys.isEmpty()) {
211                    firstPressedIndex = i;
212                }
213                VirtualKey key = ((KeyPressed) event.getType()).getKey();
214                if (!key.isCombinationKey()) {
215                    ListIterator<Event> iter = sequence.listIterator(i);
216                    iter.next();
217                    iter.set(new Event(new KeyTyped(key), event.getTarget()));
218                }
219                else {
220                    pressedCombinationKeysSession.add(key);
221                    if (pressedKeys.contains(key)) {
222                        sequence.remove(i);
223                        i--;
224                    }
225                }
226                pressedKeys.add(key);
227            }
228            if (event.getType() instanceof KeyReleased) {
229                VirtualKey key = ((KeyReleased) event.getType()).getKey();
230                if (!key.isCombinationKey()) {
231                    if (pressedKeys.contains(key)) {
232                        sequence.remove(i);
233                        i--;
234                    }
235                    else {
236                        // found KeyReleased event without KeyPressed
237                        switch (mode)
238                        {
239                            case REMOVAL:
240                                sequence.remove(i);
241                                i--;
242                                break;
243                            case ADDITION:
244                                ListIterator<Event> iter = sequence.listIterator(i);
245                                iter.next();
246                                iter.set(new Event(new KeyTyped(key), event.getTarget()));
247                                break;
248                            default:
249                                throw new AssertionError(
250                                                         "reached source code that should be unreachable");
251                        }
252                    }
253                }
254                else {
255                    if (!pressedKeys.contains(key)) {
256                        if (pressedCombinationKeysSession.contains(key)) {
257                            Console.traceln(Level.SEVERE, "Found a " + key +
258                                " KeyReleased event without a KeyPressed event." +
259                                "The event will be dropped and the session is possibly faulty.");
260                            sequence.remove(i);
261                            i--;
262                        }
263                        else {
264                            Console
265                                .traceln(Level.SEVERE,
266                                         "Found a " +
267                                             key +
268                                             " KeyReleased event without a KeyPressed event." +
269                                             "Since no KeyPressed of key " +
270                                             key +
271                                             " has been part of the session " +
272                                             "till now, we assume that the key has been pressed since the beginning " +
273                                             "of the session and add a KeyPressed event for " +
274                                             key + " to the start " + "of the session.");
275                            sequence.add(0, new Event(new KeyPressed(key), event.getTarget()));
276                            i++;
277                        }
278                    }
279                }
280                pressedKeys.remove(key);
281                if (pressedKeys.isEmpty()) {
282                    firstPressedIndex = -1;
283                }
284            }
285        }
286        if (!pressedKeys.isEmpty()) {
287            Console
288                .traceln(Level.WARNING,
289                         "There was probably a failure during the handling of incomplete key event pairs.");
290        }
291    }
292}
Note: See TracBrowser for help on using the repository browser.