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

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