1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
|
/*
* Copyright (c) 2013 Menny Even-Danan
*
* 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 com.anysoftkeyboard;
import com.anysoftkeyboard.dictionaries.content.AndroidUserDictionary;
import com.anysoftkeyboard.keyboards.views.AnyKeyboardBaseView;
import com.anysoftkeyboard.utils.Log;
import com.menny.android.anysoftkeyboard.FeaturesSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
*/
public class WordComposer {
private static final String CHEWBACCAONTHEDRUMS = "chewbacca";
private static final String TAG = "ASK _WC";
/**
* The list of unicode values for each keystroke (including surrounding keys)
*/
private final ArrayList<int[]> mCodes = new ArrayList<>(AndroidUserDictionary.MAX_WORD_LENGTH);
/**
* This holds arrays for reuse. Will not exceed AndroidUserDictionary.MAX_WORD_LENGTH
*/
private final List<int[]> mArraysToReuse = new ArrayList<>(AndroidUserDictionary.MAX_WORD_LENGTH);
/**
* The word chosen from the candidate list, until it is committed.
*/
private CharSequence mPreferredWord;
private final StringBuilder mTypedWord = new StringBuilder(AndroidUserDictionary.MAX_WORD_LENGTH);
private int mCursorPosition;
private int mGlobalCursorPosition;
private int mCapsCount;
private boolean mAutoCapitalized;
/**
* Whether the user chose to capitalize the first char of the word.
*/
private boolean mIsFirstCharCapitalized;
public WordComposer() {
}
/*
WordComposer(WordComposer copy) {
mCodes = new ArrayList<int[]>(copy.mCodes);
mPreferredWord = copy.mPreferredWord;
mTypedWord = new StringBuilder(copy.mTypedWord);
mCapsCount = copy.mCapsCount;
mAutoCapitalized = copy.mAutoCapitalized;
mIsFirstCharCapitalized = copy.mIsFirstCharCapitalized;
}
*/
/**
* Clear out the keys registered so far.
*/
public void reset() {
//moving arrays back to re-use list
for (int[] array : mCodes) {
mArraysToReuse.add(array);
}
mCodes.clear();
mIsFirstCharCapitalized = false;
mPreferredWord = null;
mTypedWord.setLength(0);
mCapsCount = 0;
mCursorPosition = 0;
mGlobalCursorPosition = 0;
}
/**
* Number of keystrokes in the composing word.
*
* @return the number of keystrokes
*/
public int length() {
return mTypedWord.length();
}
/**
* Cursor position
*/
public int cursorPosition() {
return mCursorPosition;
}
public int globalCursorPosition() {
return mGlobalCursorPosition;
}
public void setGlobalCursorPosition(int position) {
mGlobalCursorPosition = position;
}
public boolean setCursorPostion(int position/*, int candidatesStartPosition*/) {
if (position < 0 || position > length())//note: the cursor can be AFTER the word, so it can be equal to size()
{
Log.w(TAG, "New cursor position is invalid! It is outside the word (size " + length() + ", new position " + position + ". Disregarding!!!!");
return false;
}
final boolean changed = mCursorPosition != position;
mCursorPosition = position;
return changed;
//mCandidatesStartPosition = candidatesStartPosition;
}
/*
public boolean hasUserMovedCursor(int cursorPosition)
{
if (AnyApplication.DEBUG)
{
Log.d(TAG, "Current cursor position inside word is "+mCursorPosition+", and word starts at "+mCandidatesStartPosition+". Input's cursor is at "+cursorPosition);
}
return (cursorPosition != (mCursorPosition + mCandidatesStartPosition));
}
public boolean hasUserMovedCursorInsideOfWord(int cursorPosition)
{
if (AnyApplication.DEBUG)
{
Log.d(TAG, "Current word length is "+mTypedWord.length()+", and word starts at "+mCandidatesStartPosition+". Input's cursor is at "+cursorPosition);
}
return (cursorPosition >= mCandidatesStartPosition && cursorPosition <= (mCandidatesStartPosition+mTypedWord.length()));
}
*/
/**
* Returns the codes at a particular position in the word.
*
* @param index the position in the word
* @return the unicode for the pressed and surrounding keys
*/
public int[] getCodesAt(int index) {
return mCodes.get(index);
}
/**
* Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
* the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
*
* @param codes the array of unicode values
*/
public boolean add(int primaryCode, int[] codes) {
mTypedWord.insert(mCursorPosition, (char) primaryCode);
/*if (codes != null)
{
for(int i=0; i<codes.length; i++)
{
if (codes[i] > 32) codes[i] = Character.toLowerCase(codes[i]);
}
}*/
correctPrimaryJuxtapos(primaryCode, codes);
//this will return a copy of the codes array, stored in an array with sufficent storage
int[] reusableArray = getReusableArray(codes);
mCodes.add(mCursorPosition, reusableArray);
mCursorPosition++;
if (Character.isUpperCase((char) primaryCode)) mCapsCount++;
if (mTypedWord.length() == CHEWBACCAONTHEDRUMS.length()) {
if (mTypedWord.toString().equalsIgnoreCase(CHEWBACCAONTHEDRUMS)) {
return true;
}
}
return false;
}
private int[] getReusableArray(int[] codes) {
while (mArraysToReuse.size() > 0) {
int[] possibleArray = mArraysToReuse.remove(0);
//is it usable in this situation?
if (possibleArray.length >= codes.length) {
System.arraycopy(codes, 0, possibleArray, 0, codes.length);
if (possibleArray.length > codes.length)
Arrays.fill(possibleArray, codes.length, possibleArray.length, AnyKeyboardBaseView.NOT_A_KEY);
Log.d(TAG, "Found an array to reuse with length " + possibleArray.length);
return possibleArray;
}
}
//if I got here, it means that the reusableArray does not contain a long enough array
Log.d(TAG, "Creating a new array with length " + codes.length);
int[] newArray = new int[codes.length];
mArraysToReuse.add(newArray);
return getReusableArray(codes);
}
/**
* Swaps the first and second values in the codes array if the primary code is not the first
* value in the array but the second. This happens when the preferred key is not the key that
* the user released the finger on.
*
* @param primaryCode the preferred character
* @param nearByKeyCodes array of codes based on distance from touch point
*/
private static void correctPrimaryJuxtapos(int primaryCode, int[] nearByKeyCodes) {
/*if (codes.length < 2) return;
if (codes[0] > 0 && codes[1] > 0 && codes[0] != primaryCode && codes[1] == primaryCode) {
codes[1] = codes[0];
codes[0] = primaryCode;
}*/
if (nearByKeyCodes != null && nearByKeyCodes.length > 1 && primaryCode != nearByKeyCodes[0] && primaryCode != Character.toLowerCase((char) nearByKeyCodes[0])) {
int swappedItem = nearByKeyCodes[0];
nearByKeyCodes[0] = primaryCode;
boolean found = false;
for (int i = 1; i < nearByKeyCodes.length; i++) {
if (nearByKeyCodes[i] == primaryCode) {
nearByKeyCodes[i] = swappedItem;
found = true;
break;
}
}
if (!found) //reverting
nearByKeyCodes[0] = swappedItem;
}
}
/**
* Delete the last keystroke as a result of hitting backspace.
*/
public void deleteLast() {
if (mCursorPosition > 0) {
//removing from the codes list, and taking it back to the reusable list
mArraysToReuse.add(mCodes.remove(mCursorPosition - 1));
//final int lastPos = mTypedWord.length() - 1;
char last = mTypedWord.charAt(mCursorPosition - 1);
mTypedWord.deleteCharAt(mCursorPosition - 1);
mCursorPosition--;
if (Character.isUpperCase(last)) mCapsCount--;
}
}
/**
* Returns the word as it was typed, without any correction applied.
*
* @return the word that was typed so far
*/
public CharSequence getTypedWord() {
int wordSize = mCodes.size();
if (wordSize == 0) {
return "";
}
return mTypedWord;
}
public void setFirstCharCapitalized(boolean capitalized) {
mIsFirstCharCapitalized = capitalized;
}
/**
* Whether or not the user typed a capital letter as the first letter in the word
*
* @return capitalization preference
*/
public boolean isFirstCharCapitalized() {
return mIsFirstCharCapitalized;
}
/**
* Whether or not all of the user typed chars are upper case
*
* @return true if all user typed chars are upper case, false otherwise
*/
public boolean isAllUpperCase() {
return (mCapsCount > 0) && (mCapsCount == length());
}
/**
* Stores the user's selected word, before it is actually committed to the text field.
*
* @param preferred
*/
public void setPreferredWord(CharSequence preferred) {
mPreferredWord = preferred;
}
public CharSequence getPreferredWord() {
return mPreferredWord;
}
/**
* Returns true if more than one character is upper case, otherwise returns false.
*/
public boolean isMostlyCaps() {
return mCapsCount > 1;
}
/**
* Saves the reason why the word is capitalized - whether it was automatic or
* due to the user hitting shift in the middle of a sentence.
*
* @param auto whether it was an automatic capitalization due to start of sentence
*/
public void setAutoCapitalized(boolean auto) {
mAutoCapitalized = auto;
}
/**
* Returns whether the word was automatically capitalized.
*
* @return whether the word was automatically capitalized
*/
public boolean isAutoCapitalized() {
return mAutoCapitalized;
}
public void logCodes() {
if (!FeaturesSet.DEBUG_LOG) return;
Log.d(TAG, "Word: " + mTypedWord + ", prefered word:" + mPreferredWord);
int i = 0;
for (int[] codes : mCodes) {
String codesString = "Codes #" + i + ": ";
for (int c : codes) {
codesString += "" + c + ",";
}
Log.d(TAG, codesString);
}
}
}
|