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

Last change on this file since 765 was 765, checked in by sherbold, 12 years ago
  • complete rewrite of keyboard interaction sorting. As a result, the command cleanupKeyInteractions is deprecated.
File size: 11.5 KB
Line 
1package de.ugoe.cs.quest.eventcore.gui;
2
3import java.util.ArrayList;
4import java.util.LinkedList;
5import java.util.List;
6import java.util.Locale;
7
8import de.ugoe.cs.quest.eventcore.Event;
9import de.ugoe.cs.quest.eventcore.IEventTarget;
10import de.ugoe.cs.quest.eventcore.gui.KeyInteraction;
11import de.ugoe.cs.quest.eventcore.gui.KeyPressed;
12import de.ugoe.cs.quest.eventcore.gui.KeyReleased;
13import de.ugoe.cs.quest.eventcore.gui.TextInput.TextEquality;
14import de.ugoe.cs.quest.eventcore.guimodel.ITextArea;
15import de.ugoe.cs.quest.eventcore.guimodel.ITextField;
16import de.ugoe.cs.tasktree.keyboardmaps.KeyboardMap;
17import de.ugoe.cs.tasktree.keyboardmaps.KeyboardMapFactory;
18import de.ugoe.cs.tasktree.keyboardmaps.VirtualKey;
19
20/**
21 * <p>
22 * The text input detector iterates a list of events and searches for subsequent key events.
23 * Those are replaced by a single text input event representing the text entered through the key
24 * events. The replacement is only done, if the key events have a text field or text area as target
25 * </p>
26 *
27 * @version $Revision: $ $Date: 18.03.2012$
28 * @author 2012, last modified by $Author: patrick$
29 */
30public class TextInputDetector {
31   
32    /** the keyboard map to use for character recognition*/
33    private KeyboardMap keyboardMap = KeyboardMapFactory.createKeyboardMap(Locale.GERMAN);
34   
35    /** the keys pressed in parallel */
36    List<VirtualKey> pressedKeys = new ArrayList<VirtualKey>();
37   
38    private final TextEquality textEqualityType;
39   
40    public TextInputDetector() {
41        this(TextEquality.LEXICAL);
42    }
43   
44    public TextInputDetector(TextEquality textEqualityType) {
45        this.textEqualityType = textEqualityType;
46    }
47
48    /**
49     * <p>
50     * in the provided list of events, this method detects any event sequences that consists of
51     * key interactions and replaces them with a single text input interaction. This contains
52     * the entered text as well as the replaced key interaction events
53     * </p>
54     *
55     * @param sequence the event sequence to search for text input events
56     *
57     * @return the resulting sequence, in which key interactions on text fields and areas are
58     *         reduced to text input interactions
59     */
60    public List<Event> detectTextInputs(List<Event> sequence) {
61        List<Event> resultingSequence = new LinkedList<Event>();
62       
63        int textEntryStartIndex = -1;
64        IEventTarget lastEventTarget = null;
65
66        int index = 0;
67        Event currentEvent = null;
68        Event textInputEvent = null;
69        while (index < sequence.size()) {
70            currentEvent = sequence.get(index);
71            textInputEvent = null;
72           
73            if (isKeyInteraction(currentEvent) && isDataInputEventTarget(currentEvent.getTarget()))
74            {
75                if (textEntryStartIndex < 0) {
76                    textEntryStartIndex = index;
77                    lastEventTarget = currentEvent.getTarget();
78                }
79                else if (!lastEventTarget.equals(currentEvent.getTarget())) {
80                    textInputEvent = handleTextEntrySequence
81                        (sequence, textEntryStartIndex, index - 1, lastEventTarget);
82                   
83                    textEntryStartIndex = index;
84                    lastEventTarget = currentEvent.getTarget();
85                }
86                currentEvent = null;
87            }
88            else {
89                if (textEntryStartIndex >= 0) {
90                    textInputEvent = handleTextEntrySequence
91                        (sequence, textEntryStartIndex, index - 1, lastEventTarget);
92                   
93                    textEntryStartIndex = -1;
94                    lastEventTarget = null;
95                }
96               
97            }
98
99            if (textInputEvent != null) {
100                resultingSequence.add(textInputEvent);
101            }
102           
103            if (currentEvent != null) {
104                resultingSequence.add(currentEvent);
105            }
106           
107            index++;
108        }
109
110        if (textEntryStartIndex >= 0) {
111            textInputEvent = handleTextEntrySequence
112                (sequence, textEntryStartIndex, sequence.size() - 1, lastEventTarget);
113           
114            if (textInputEvent != null) {
115                resultingSequence.add(textInputEvent);
116            }
117        }
118
119        return resultingSequence;
120    }
121
122    /**
123     * <p>
124     * returns true if the provide event is a key interaction; false else
125     * </p>
126     *
127     * @param event the even to check
128     *
129     * @return as described
130     */
131    private boolean isKeyInteraction(Event event) {
132        return (event.getType() instanceof KeyInteraction);
133    }
134
135    /**
136     * <p>
137     * creates a single text input event as replacement for key interactions being part of the
138     * subsequence of events denoted by the start and end index in the provide event sequence. If
139     * no text was entered, because the subsequence only contained key released events, then no
140     * text input event is generated (the method returns null).
141     * </p>
142     *
143     * @param sequence
144     *            the event sequence of which the subsequence is analyzed
145     * @param startIndex
146     *            the start index in the event sequence from which the analysis should start
147     *            (inclusive)
148     * @param endIndex
149     *            the end index in the event sequence where the analysis should end (inclusive)
150     * @param eventTarget
151     *            the event target to be used for the new event
152     *
153     * @return a text input event representing the text input resulting from the events of
154     *         the provided subsequence
155     *
156     * @throws IllegalArgumentException
157     *             if the denoted subsequence contains other events than key interactions
158     */
159    private Event handleTextEntrySequence(List<Event>  sequence,
160                                          int          startIndex,
161                                          int          endIndex,
162                                          IEventTarget eventTarget)
163    {
164        List<Event> textInputEvents = new ArrayList<Event>();
165
166        String enteredText = determineEnteredText(sequence, startIndex, endIndex, textInputEvents);
167       
168        if ((enteredText != null) && (!"".equals(enteredText))) {
169            TextInput textInput = new TextInput(enteredText, textInputEvents, textEqualityType);
170            return new Event(textInput, eventTarget);
171        }
172        else {
173            return null;
174        }
175    }
176
177    /**
178     * <p>
179     * check if an event target is a data input field, i.e. a text field or a text area
180     * </p>
181     *
182     * @param eventTarget the event target to check
183     *
184     * @return true, if it is a text field or a text area ; false else
185     */
186    private boolean isDataInputEventTarget(IEventTarget eventTarget) {
187        return ((eventTarget instanceof ITextField) || (eventTarget instanceof ITextArea));
188    }
189
190    /**
191     * <p>
192     * determines the text entered in the event subsequence denoted by the start and end index of
193     * the provided event sequence. The method records any pressed and released key interaction as
194     * well as combinations of pressed key interactions (such as shift + letter) and determines the
195     * respective character. All identified characters are then combined to the entered text. The
196     * method also identifies the usage of the back space. The enter key is ignored for text fields
197     * in which pressing the enter key results in finishing the text entry. All analyzed key
198     * interaction events are stored in the provided text input events result list. If the
199     * subsequence only contains key released events, no text was entered and the returned text is
200     * null.
201     * </p>
202     *
203     * @param sequence
204     *            the event sequence of which the subsequence is analyzed
205     * @param startIndex
206     *            the start index in the event sequence from which the analysis should start
207     *            (inclusive)
208     * @param endIndex
209     *            the end index in the event sequence where the analysis should end (inclusive)
210     * @param textInputEvents
211     *            a buffer to contain any key interaction event analyzed (in out)
212     *
213     * @return the text entered through the interaction events of the denoted subsequence
214     *
215     * @throws IllegalArgumentException
216     *             if the denoted sequence contains other events than key interactions
217     */
218    private String determineEnteredText(List<Event> sequence,
219                                        int         startIndex,
220                                        int         endIndex,
221                                        List<Event> textInputEvents)
222        throws IllegalArgumentException
223    {
224        Event event;
225        StringBuffer enteredText = new StringBuffer();
226       
227        for (int i = startIndex; i <= endIndex; i++) {
228            event = sequence.get(i);
229           
230            if (event.getType() instanceof KeyPressed || event.getType() instanceof KeyTyped) {
231                VirtualKey key = ((KeyInteraction) event.getType()).getKey();
232
233                pressedKeys.add(key);
234
235                if (key == VirtualKey.BACK_SPACE) {
236                    if (enteredText.length() > 0) {
237                        enteredText.deleteCharAt(enteredText.length() - 1);
238                    }
239                }
240                else if (key == VirtualKey.ENTER) {
241                    // text fields only contain one line of code. Therefore the return is ignored.
242                    if (!(event.getTarget() instanceof ITextField)) {
243                        enteredText.append(getCharacter(key, pressedKeys));
244                    }
245                }
246                else {
247                    char theChar = getCharacter(key, pressedKeys);
248                    if (theChar != Character.UNASSIGNED) {
249                        enteredText.append(theChar);
250                    }
251                }
252            }
253            else if (event.getType() instanceof KeyReleased || event.getType() instanceof KeyTyped) {
254                pressedKeys.remove(((KeyInteraction) event.getType()).getKey());
255            }
256            else {
257                throw new IllegalArgumentException
258                    ("the subsequence denoted by the indexes contains other interactions than " +
259                     "just key strokes");
260            }
261           
262            textInputEvents.add(event);
263        }
264       
265        if (enteredText.length() > 0) {
266            return enteredText.toString();
267        }
268        else {
269            return null;
270        }
271    }
272
273    /**
274     * <p>
275     * determines the character matching the pressed key depending on other keys pressed in parallel
276     * such as the shift key.
277     * </p>
278     *
279     * @param key         the key for which the character shall be determined
280     * @param pressedKeys the list of other keys pressed in parallel
281     *
282     * @return the character resulting from the combination of pressed keys
283     */
284    private char getCharacter(VirtualKey key, List<VirtualKey> pressedKeys) {
285        boolean numlock = false;
286        boolean shift = false;
287        boolean altgr = false;
288
289        for (VirtualKey pressedKey : pressedKeys) {
290            if (pressedKey.isShiftKey()) {
291                shift = !shift;
292            }
293            else if (pressedKey == VirtualKey.ALT_GRAPH) {
294                altgr = !altgr;
295            }
296            else if (pressedKey == VirtualKey.NUM_LOCK) {
297                numlock = !numlock;
298            }
299        }
300
301        return keyboardMap.getCharacterFor(key, numlock, shift, altgr, false);
302    }
303
304}
Note: See TracBrowser for help on using the repository browser.