// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.eventcore.gui; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.logging.Level; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey; import de.ugoe.cs.util.console.Console; /** *

* This class provides the functionality to sort and clean up all key interactions in a log. In * particular: *

    *
  1. In case a combination key (e.g., shift, alt, control) is held down, multiple * {@link KeyPressed} events are logged, even though only the first one is of importance. This class * removes all {@link KeyPressed} events for combination keys except the first.
  2. *
  3. In case a normal key is held down, multiple {@link KeyPressed} events are logged, but there * is only one {@link KeyReleased} event. This class adds a {@link KeyReleased} event for all such * {@link KeyPressed} events.
  4. *
  5. Due to message filtering of applications, it is possible that a {@link KeyReleased} event * without a preceding {@link KeyPressed} event is logged. This class either adds the missing * {@link KeyPressed} right in front of the {@link KeyReleased} or removes the {@link KeyReleased} * depending on the {@link #mode}. *
  6. As a result of steps 2-3, we have always a matching {@link KeyPressed}/{@link KeyReleased} * pairs for all normal keys. This class replaces these pairs with a {@link KeyTyped} event at the * position of the {@link KeyPressed} event.
  7. *
  8. Sometimes combination keys are not released in the same order they have been pressed. This * class ensures that the {@link KeyReleased} are in the opposite order of the {@link KeyPressed} * events for all combination keys.
  9. *
*

* * @version 1.0 * @author Steffen Herbold */ public class KeyInteractionCorrector { /** *

* Describes the clean-up mode. *

* * @version 1.0 * @author Steffen Herbold */ public static enum CleanupMode { /** *

* Single {@link KeyReleased} are removed from the sequence. *

*/ REMOVAL, /** *

* {@link KeyPressed} events are added to single {@link KeyReleased} events *

*/ ADDITION }; /** *

* *

*/ private final CleanupMode mode; /** *

* Constructor. Creates a new {@link KeyInteractionCorrector} with {@link #mode}= * {@link CleanupMode#ADDITION}. *

*/ public KeyInteractionCorrector() { this(CleanupMode.ADDITION); } /** *

* Constructor. Creates a new {@link KeyInteractionCorrector}. *

* * @param mode * {@link #mode} of the instance */ public KeyInteractionCorrector(CleanupMode mode) { this.mode = mode; } /** *

* Sorts and cleans up key interactions according to the class specification (@see * {@link KeyInteractionCorrector} class comment). *

*

* This method returns a sorted copy of a sequence, the sequence itself is not changed. *

* * @param sequence * sequence which is sorted * @return sorted copy of sequence */ public List sortKeyInteractions(final List sequence) { List sortedSequence = new LinkedList(sequence); handleIncompleteKeyPairs(sortedSequence); sortCombinationKeyPairs(sortedSequence); return sortedSequence; } /** *

* Performs tasks 1-4 defined in the class description. Operations are performed in-place on the * passed sequence. *

* * @param sequence * sequence which is sorted */ private void sortCombinationKeyPairs(List sequence) { LinkedList pressedCombinationKeys = new LinkedList(); for (int i = 0; i < sequence.size(); i++) { Event event = sequence.get(i); if (event.getType() instanceof KeyPressed) { final VirtualKey key = ((KeyPressed) event.getType()).getKey(); if (key.isCombinationKey()) { pressedCombinationKeys.add(key); } } if (event.getType() instanceof KeyReleased) { final VirtualKey key = ((KeyReleased) event.getType()).getKey(); if (key.isCombinationKey()) { /* * if( pressedCombinationKeys.isEmpty() ) { Console.traceln(Level.INFO, "" + i); * for( int j=i-30 ; j<=i ; j++ ) { Console.traceln(Level.INFO, * sequence.get(i).toString()); } } */ if (key.equals(pressedCombinationKeys.getLast())) { pressedCombinationKeys.removeLast(); } else { // look-ahead to find new position int offset; for (offset = 1; offset + i < sequence.size(); offset++) { Event lookaheadEvent = sequence.get(i + offset); if (lookaheadEvent.getType() instanceof KeyReleased) { if (((KeyReleased) lookaheadEvent.getType()).getKey() .equals(pressedCombinationKeys.getLast())) { break; } } } sequence.add(i + offset + 1, event); sequence.remove(i); i--; } } } } } /** *

* Performs task 5 defined in the class description. Operations are performed in-place on the * passed sequence. *

* * @param sequence * sequence which is sorted */ private void handleIncompleteKeyPairs(List sequence) { Set pressedKeys = new HashSet(); int firstPressedIndex = -1; Set pressedCombinationKeysSession = new HashSet(); for (int i = 0; i < sequence.size(); i++) { Event event = sequence.get(i); if (event.getType() instanceof KeyboardFocusChange) { if (firstPressedIndex != -1) { sequence.remove(i); sequence.add(firstPressedIndex, event); } } if (event.getType() instanceof KeyPressed) { if (pressedKeys.isEmpty()) { firstPressedIndex = i; } VirtualKey key = ((KeyPressed) event.getType()).getKey(); if (!key.isCombinationKey()) { ListIterator iter = sequence.listIterator(i); iter.next(); iter.set(new Event(new KeyTyped(key), event.getTarget())); } else { pressedCombinationKeysSession.add(key); if (pressedKeys.contains(key)) { sequence.remove(i); i--; } } pressedKeys.add(key); } if (event.getType() instanceof KeyReleased) { VirtualKey key = ((KeyReleased) event.getType()).getKey(); if (!key.isCombinationKey()) { if (pressedKeys.contains(key)) { sequence.remove(i); i--; } else { // found KeyReleased event without KeyPressed switch (mode) { case REMOVAL: sequence.remove(i); i--; break; case ADDITION: ListIterator iter = sequence.listIterator(i); iter.next(); iter.set(new Event(new KeyTyped(key), event.getTarget())); break; default: throw new AssertionError( "reached source code that should be unreachable"); } } } else { if (!pressedKeys.contains(key)) { if (pressedCombinationKeysSession.contains(key)) { Console.traceln(Level.SEVERE, "Found a " + key + " KeyReleased event without a KeyPressed event." + "The event will be dropped and the session is possibly faulty."); sequence.remove(i); i--; } else { Console .traceln(Level.SEVERE, "Found a " + key + " KeyReleased event without a KeyPressed event." + "Since no KeyPressed of key " + key + " has been part of the session " + "till now, we assume that the key has been pressed since the beginning " + "of the session and add a KeyPressed event for " + key + " to the start " + "of the session."); sequence.add(0, new Event(new KeyPressed(key), event.getTarget())); i++; } } } pressedKeys.remove(key); if (pressedKeys.isEmpty()) { firstPressedIndex = -1; } } } if (!pressedKeys.isEmpty()) { Console .traceln(Level.WARNING, "There was probably a failure during the handling of incomplete key event pairs."); } } }