// 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.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Locale; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.eventcore.IEventTarget; import de.ugoe.cs.autoquest.eventcore.gui.KeyInteraction; import de.ugoe.cs.autoquest.eventcore.gui.KeyPressed; import de.ugoe.cs.autoquest.eventcore.gui.KeyReleased; import de.ugoe.cs.autoquest.eventcore.gui.TextInput.TextEquality; import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea; import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField; import de.ugoe.cs.autoquest.keyboardmaps.KeyboardMap; import de.ugoe.cs.autoquest.keyboardmaps.KeyboardMapFactory; import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey; /** *

* The text input detector iterates a list of events and searches for subsequent key events. Those * are replaced by a single text input event representing the text entered through the key events. * The replacement is only done, if the key events have a text field or text area as target *

* * @version $Revision: $ $Date: 18.03.2012$ * @author 2012, last modified by $Author: patrick$ */ public class TextInputDetector { /** the keyboard map to use for character recognition */ private KeyboardMap keyboardMap = KeyboardMapFactory.createKeyboardMap(Locale.GERMAN); /** the keys pressed in parallel */ private List pressedKeys = new ArrayList(); /** the chosen text equality type */ private final TextEquality textEqualityType; /** *

* Constructor. Creates a new TextInputDectector that generates {@link TextInput} with * {@link TextEquality#LEXICAL} equality. *

* */ public TextInputDetector() { this(TextEquality.LEXICAL); } /** *

* Constructor. Creates a new TextInputDectector that generates {@link TextInput} with a given * {@link TextEquality} type. *

* * @param textEqualityType * equality type of the generated events */ public TextInputDetector(TextEquality textEqualityType) { this.textEqualityType = textEqualityType; } /** *

* in the provided list of events, this method detects any event sequences that consists of key * interactions and replaces them with a single text input interaction. This contains the * entered text as well as the replaced key interaction events *

* * @param sequence * the event sequence to search for text input events * * @return the resulting sequence, in which key interactions on text fields and areas are * reduced to text input interactions */ public List detectTextInputs(List sequence) { List resultingSequence = new LinkedList(); int textEntryStartIndex = -1; IEventTarget lastEventTarget = null; int index = 0; Event currentEvent = null; Event textInputEvent = null; while (index < sequence.size()) { currentEvent = sequence.get(index); textInputEvent = null; if (isKeyInteraction(currentEvent) && isDataInputEventTarget(currentEvent.getTarget())) { if (textEntryStartIndex < 0) { textEntryStartIndex = index; lastEventTarget = currentEvent.getTarget(); } else if (!lastEventTarget.equals(currentEvent.getTarget())) { textInputEvent = handleTextEntrySequence(sequence, textEntryStartIndex, index - 1, lastEventTarget); textEntryStartIndex = index; lastEventTarget = currentEvent.getTarget(); } currentEvent = null; } else { if (textEntryStartIndex >= 0) { textInputEvent = handleTextEntrySequence(sequence, textEntryStartIndex, index - 1, lastEventTarget); textEntryStartIndex = -1; lastEventTarget = null; } } if (textInputEvent != null) { resultingSequence.add(textInputEvent); } if (currentEvent != null) { resultingSequence.add(currentEvent); } index++; } if (textEntryStartIndex >= 0) { textInputEvent = handleTextEntrySequence(sequence, textEntryStartIndex, sequence.size() - 1, lastEventTarget); if (textInputEvent != null) { resultingSequence.add(textInputEvent); } } return resultingSequence; } /** *

* returns true if the provide event is a key interaction; false else *

* * @param event * the even to check * * @return as described */ private boolean isKeyInteraction(Event event) { return (event.getType() instanceof KeyInteraction); } /** *

* creates a single text input event as replacement for key interactions being part of the * subsequence of events denoted by the start and end index in the provide event sequence. If no * text was entered, because the subsequence only contained key released events, then no text * input event is generated (the method returns null). *

* * @param sequence * the event sequence of which the subsequence is analyzed * @param startIndex * the start index in the event sequence from which the analysis should start * (inclusive) * @param endIndex * the end index in the event sequence where the analysis should end (inclusive) * @param eventTarget * the event target to be used for the new event * * @return a text input event representing the text input resulting from the events of the * provided subsequence * * @throws IllegalArgumentException * if the denoted subsequence contains other events than key interactions */ private Event handleTextEntrySequence(List sequence, int startIndex, int endIndex, IEventTarget eventTarget) { List textInputEvents = new ArrayList(); String enteredText = determineEnteredText(sequence, startIndex, endIndex, textInputEvents); if ((enteredText != null) && (!"".equals(enteredText))) { TextInput textInput = new TextInput(enteredText, textInputEvents, textEqualityType); return new Event(textInput, eventTarget); } else { return null; } } /** *

* check if an event target is a data input field, i.e. a text field or a text area *

* * @param eventTarget * the event target to check * * @return true, if it is a text field or a text area ; false else */ private boolean isDataInputEventTarget(IEventTarget eventTarget) { return ((eventTarget instanceof ITextField) || (eventTarget instanceof ITextArea)); } /** *

* determines the text entered in the event subsequence denoted by the start and end index of * the provided event sequence. The method records any pressed and released key interaction as * well as combinations of pressed key interactions (such as shift + letter) and determines the * respective character. All identified characters are then combined to the entered text. The * method also identifies the usage of the back space. The enter key is ignored for text fields * in which pressing the enter key results in finishing the text entry. All analyzed key * interaction events are stored in the provided text input events result list. If the * subsequence only contains key released events, no text was entered and the returned text is * null. *

* * @param sequence * the event sequence of which the subsequence is analyzed * @param startIndex * the start index in the event sequence from which the analysis should start * (inclusive) * @param endIndex * the end index in the event sequence where the analysis should end (inclusive) * @param textInputEvents * a buffer to contain any key interaction event analyzed (in out) * * @return the text entered through the interaction events of the denoted subsequence * * @throws IllegalArgumentException * if the denoted sequence contains other events than key interactions */ private String determineEnteredText(List sequence, int startIndex, int endIndex, List textInputEvents) throws IllegalArgumentException { Event event; StringBuffer enteredText = new StringBuffer(); for (int i = startIndex; i <= endIndex; i++) { event = sequence.get(i); if (event.getType() instanceof KeyPressed || event.getType() instanceof KeyTyped) { VirtualKey key = ((KeyInteraction) event.getType()).getKey(); pressedKeys.add(key); if (key == VirtualKey.BACK_SPACE) { if (enteredText.length() > 0) { enteredText.deleteCharAt(enteredText.length() - 1); } } else if (key == VirtualKey.ENTER) { // text fields only contain one line of code. Therefore the return is ignored. if (!(event.getTarget() instanceof ITextField)) { enteredText.append(getCharacter(key, pressedKeys)); } } else { char theChar = getCharacter(key, pressedKeys); if (theChar != Character.UNASSIGNED) { enteredText.append(theChar); } } } else if (event.getType() instanceof KeyReleased || event.getType() instanceof KeyTyped) { pressedKeys.remove(((KeyInteraction) event.getType()).getKey()); } else { throw new IllegalArgumentException( "the subsequence denoted by the indexes contains other interactions than " + "just key strokes"); } textInputEvents.add(event); } if (enteredText.length() > 0) { return enteredText.toString(); } else { return null; } } /** *

* determines the character matching the pressed key depending on other keys pressed in parallel * such as the shift key. *

* * @param key * the key for which the character shall be determined * @param pressedKeys * the list of other keys pressed in parallel * * @return the character resulting from the combination of pressed keys */ private char getCharacter(VirtualKey key, List pressedKeys) { boolean numlock = false; boolean shift = false; boolean altgr = false; for (VirtualKey pressedKey : pressedKeys) { if (pressedKey.isShiftKey()) { shift = !shift; } else if (pressedKey == VirtualKey.ALT_GRAPH) { altgr = !altgr; } else if (pressedKey == VirtualKey.NUM_LOCK) { numlock = !numlock; } } return keyboardMap.getCharacterFor(key, numlock, shift, altgr, false); } }