source: trunk/quest-core-events/src/main/java/de/ugoe/cs/quest/eventcore/gui/KeyInteractionSorter.java @ 780

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