WordComposer.java revision 3651220327c051d8017045aa5e8919461507b3f8
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import com.android.inputmethod.keyboard.Keyboard; 20import com.android.inputmethod.keyboard.Key; 21import com.android.inputmethod.keyboard.KeyDetector; 22import com.android.inputmethod.keyboard.LatinKeyboard; 23 24import java.util.ArrayList; 25import java.util.Arrays; 26 27/** 28 * A place to store the currently composing word with information such as adjacent key codes as well 29 */ 30public class WordComposer { 31 32 public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE; 33 public static final int NOT_A_COORDINATE = -1; 34 35 /** 36 * The list of unicode values for each keystroke (including surrounding keys) 37 */ 38 private ArrayList<int[]> mCodes; 39 40 private int[] mXCoordinates; 41 private int[] mYCoordinates; 42 43 private StringBuilder mTypedWord; 44 45 private int mCapsCount; 46 47 private boolean mAutoCapitalized; 48 // Cache this value for performance 49 private int mTrailingSingleQuotesCount; 50 51 /** 52 * Whether the user chose to capitalize the first char of the word. 53 */ 54 private boolean mIsFirstCharCapitalized; 55 56 public WordComposer() { 57 final int N = BinaryDictionary.MAX_WORD_LENGTH; 58 mCodes = new ArrayList<int[]>(N); 59 mTypedWord = new StringBuilder(N); 60 mXCoordinates = new int[N]; 61 mYCoordinates = new int[N]; 62 mTrailingSingleQuotesCount = 0; 63 } 64 65 public WordComposer(WordComposer source) { 66 init(source); 67 } 68 69 public void init(WordComposer source) { 70 mCodes = new ArrayList<int[]>(source.mCodes); 71 mTypedWord = new StringBuilder(source.mTypedWord); 72 mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length); 73 mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length); 74 mCapsCount = source.mCapsCount; 75 mIsFirstCharCapitalized = source.mIsFirstCharCapitalized; 76 mAutoCapitalized = source.mAutoCapitalized; 77 mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount; 78 } 79 80 /** 81 * Clear out the keys registered so far. 82 */ 83 public void reset() { 84 mCodes.clear(); 85 mTypedWord.setLength(0); 86 mCapsCount = 0; 87 mIsFirstCharCapitalized = false; 88 mTrailingSingleQuotesCount = 0; 89 } 90 91 /** 92 * Number of keystrokes in the composing word. 93 * @return the number of keystrokes 94 */ 95 public final int size() { 96 return mTypedWord.length(); 97 } 98 99 /** 100 * Returns the codes at a particular position in the word. 101 * @param index the position in the word 102 * @return the unicode for the pressed and surrounding keys 103 */ 104 public int[] getCodesAt(int index) { 105 return mCodes.get(index); 106 } 107 108 public int[] getXCoordinates() { 109 return mXCoordinates; 110 } 111 112 public int[] getYCoordinates() { 113 return mYCoordinates; 114 } 115 116 private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) { 117 if (index == 0) return Character.isUpperCase(codePoint); 118 return previous && !Character.isUpperCase(codePoint); 119 } 120 121 /** 122 * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of 123 * the array containing unicode for adjacent keys, sorted by reducing probability/proximity. 124 * @param codes the array of unicode values 125 */ 126 public void add(int primaryCode, int[] codes, int x, int y) { 127 final int newIndex = size(); 128 mTypedWord.append((char) primaryCode); 129 correctPrimaryJuxtapos(primaryCode, codes); 130 mCodes.add(codes); 131 if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) { 132 mXCoordinates[newIndex] = x; 133 mYCoordinates[newIndex] = y; 134 } 135 mIsFirstCharCapitalized = isFirstCharCapitalized( 136 newIndex, primaryCode, mIsFirstCharCapitalized); 137 if (Character.isUpperCase(primaryCode)) mCapsCount++; 138 if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) { 139 ++mTrailingSingleQuotesCount; 140 } else { 141 mTrailingSingleQuotesCount = 0; 142 } 143 } 144 145 /** 146 * Internal method to retrieve reasonable proximity info for a character. 147 */ 148 private void addKeyInfo(final int codePoint, final LatinKeyboard keyboard, 149 final KeyDetector keyDetector) { 150 for (final Key key : keyboard.mKeys) { 151 if (key.mCode == codePoint) { 152 final int x = key.mX + key.mWidth / 2; 153 final int y = key.mY + key.mHeight / 2; 154 final int[] codes = keyDetector.newCodeArray(); 155 keyDetector.getKeyAndNearbyCodes(x, y, codes); 156 add(codePoint, codes, x, y); 157 return; 158 } 159 } 160 add(codePoint, new int[] { codePoint }, 161 WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); 162 } 163 164 /** 165 * Set the currently composing word to the one passed as an argument. 166 * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity. 167 */ 168 public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard, 169 final KeyDetector keyDetector) { 170 reset(); 171 final int length = word.length(); 172 for (int i = 0; i < length; ++i) { 173 int codePoint = word.charAt(i); 174 addKeyInfo(codePoint, keyboard, keyDetector); 175 } 176 } 177 178 /** 179 * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard. 180 */ 181 public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard) { 182 final KeyDetector keyDetector = new KeyDetector(0); 183 keyDetector.setKeyboard(keyboard, 0, 0); 184 keyDetector.setProximityCorrectionEnabled(true); 185 keyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth); 186 setComposingWord(word, keyboard, keyDetector); 187 } 188 189 /** 190 * Swaps the first and second values in the codes array if the primary code is not the first 191 * value in the array but the second. This happens when the preferred key is not the key that 192 * the user released the finger on. 193 * @param primaryCode the preferred character 194 * @param codes array of codes based on distance from touch point 195 */ 196 private static void correctPrimaryJuxtapos(int primaryCode, int[] codes) { 197 if (codes.length < 2) return; 198 if (codes[0] > 0 && codes[1] > 0 && codes[0] != primaryCode && codes[1] == primaryCode) { 199 codes[1] = codes[0]; 200 codes[0] = primaryCode; 201 } 202 } 203 204 /** 205 * Delete the last keystroke as a result of hitting backspace. 206 */ 207 public void deleteLast() { 208 final int size = size(); 209 if (size > 0) { 210 final int lastPos = size - 1; 211 char lastChar = mTypedWord.charAt(lastPos); 212 mCodes.remove(lastPos); 213 mTypedWord.deleteCharAt(lastPos); 214 if (Character.isUpperCase(lastChar)) mCapsCount--; 215 } 216 if (size() == 0) { 217 mIsFirstCharCapitalized = false; 218 } 219 if (mTrailingSingleQuotesCount > 0) { 220 --mTrailingSingleQuotesCount; 221 } else { 222 for (int i = mTypedWord.length() - 1; i >= 0; --i) { 223 if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break; 224 ++mTrailingSingleQuotesCount; 225 } 226 } 227 } 228 229 /** 230 * Returns the word as it was typed, without any correction applied. 231 * @return the word that was typed so far 232 */ 233 public String getTypedWord() { 234 return mTypedWord.toString(); 235 } 236 237 /** 238 * Whether or not the user typed a capital letter as the first letter in the word 239 * @return capitalization preference 240 */ 241 public boolean isFirstCharCapitalized() { 242 return mIsFirstCharCapitalized; 243 } 244 245 public int trailingSingleQuotesCount() { 246 return mTrailingSingleQuotesCount; 247 } 248 249 /** 250 * Whether or not all of the user typed chars are upper case 251 * @return true if all user typed chars are upper case, false otherwise 252 */ 253 public boolean isAllUpperCase() { 254 return (mCapsCount > 0) && (mCapsCount == size()); 255 } 256 257 /** 258 * Returns true if more than one character is upper case, otherwise returns false. 259 */ 260 public boolean isMostlyCaps() { 261 return mCapsCount > 1; 262 } 263 264 /** 265 * Saves the reason why the word is capitalized - whether it was automatic or 266 * due to the user hitting shift in the middle of a sentence. 267 * @param auto whether it was an automatic capitalization due to start of sentence 268 */ 269 public void setAutoCapitalized(boolean auto) { 270 mAutoCapitalized = auto; 271 } 272 273 /** 274 * Returns whether the word was automatically capitalized. 275 * @return whether the word was automatically capitalized 276 */ 277 public boolean isAutoCapitalized() { 278 return mAutoCapitalized; 279 } 280} 281