WordComposer.java revision c83359f9746ca6f0269a1a7017b585c1a5cab9b8
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.KeyDetector;
21
22import java.util.ArrayList;
23import java.util.Arrays;
24
25/**
26 * A place to store the currently composing word with information such as adjacent key codes as well
27 */
28public class WordComposer {
29
30    public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
31    public static final int NOT_A_COORDINATE = -1;
32
33    /**
34     * The list of unicode values for each keystroke (including surrounding keys)
35     */
36    private ArrayList<int[]> mCodes;
37
38    private int[] mXCoordinates;
39    private int[] mYCoordinates;
40
41    private StringBuilder mTypedWord;
42
43    private int mCapsCount;
44
45    private boolean mAutoCapitalized;
46    // Cache this value for performance
47    private boolean mIsLastCharASingleQuote;
48
49    /**
50     * Whether the user chose to capitalize the first char of the word.
51     */
52    private boolean mIsFirstCharCapitalized;
53
54    public WordComposer() {
55        final int N = BinaryDictionary.MAX_WORD_LENGTH;
56        mCodes = new ArrayList<int[]>(N);
57        mTypedWord = new StringBuilder(N);
58        mXCoordinates = new int[N];
59        mYCoordinates = new int[N];
60        mIsLastCharASingleQuote = false;
61    }
62
63    public WordComposer(WordComposer source) {
64        init(source);
65    }
66
67    public void init(WordComposer source) {
68        mCodes = new ArrayList<int[]>(source.mCodes);
69        mTypedWord = new StringBuilder(source.mTypedWord);
70        mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
71        mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
72        mCapsCount = source.mCapsCount;
73        mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
74        mAutoCapitalized = source.mAutoCapitalized;
75        mIsLastCharASingleQuote = source.mIsLastCharASingleQuote;
76    }
77
78    /**
79     * Clear out the keys registered so far.
80     */
81    public void reset() {
82        mCodes.clear();
83        mTypedWord.setLength(0);
84        mCapsCount = 0;
85        mIsFirstCharCapitalized = false;
86        mIsLastCharASingleQuote = false;
87    }
88
89    /**
90     * Number of keystrokes in the composing word.
91     * @return the number of keystrokes
92     */
93    public final int size() {
94        return mTypedWord.length();
95    }
96
97    /**
98     * Returns the codes at a particular position in the word.
99     * @param index the position in the word
100     * @return the unicode for the pressed and surrounding keys
101     */
102    public int[] getCodesAt(int index) {
103        return mCodes.get(index);
104    }
105
106    public int[] getXCoordinates() {
107        return mXCoordinates;
108    }
109
110    public int[] getYCoordinates() {
111        return mYCoordinates;
112    }
113
114    private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) {
115        if (index == 0) return Character.isUpperCase(codePoint);
116        return previous && !Character.isUpperCase(codePoint);
117    }
118
119    /**
120     * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
121     * the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
122     * @param codes the array of unicode values
123     */
124    public void add(int primaryCode, int[] codes, int x, int y) {
125        final int newIndex = size();
126        mTypedWord.append((char) primaryCode);
127        correctPrimaryJuxtapos(primaryCode, codes);
128        mCodes.add(codes);
129        if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
130            mXCoordinates[newIndex] = x;
131            mYCoordinates[newIndex] = y;
132        }
133        mIsFirstCharCapitalized = isFirstCharCapitalized(
134                newIndex, primaryCode, mIsFirstCharCapitalized);
135        if (Character.isUpperCase(primaryCode)) mCapsCount++;
136        mIsLastCharASingleQuote = Keyboard.CODE_SINGLE_QUOTE == primaryCode;
137    }
138
139    /**
140     * Swaps the first and second values in the codes array if the primary code is not the first
141     * value in the array but the second. This happens when the preferred key is not the key that
142     * the user released the finger on.
143     * @param primaryCode the preferred character
144     * @param codes array of codes based on distance from touch point
145     */
146    private static void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
147        if (codes.length < 2) return;
148        if (codes[0] > 0 && codes[1] > 0 && codes[0] != primaryCode && codes[1] == primaryCode) {
149            codes[1] = codes[0];
150            codes[0] = primaryCode;
151        }
152    }
153
154    /**
155     * Delete the last keystroke as a result of hitting backspace.
156     */
157    public void deleteLast() {
158        final int size = size();
159        if (size > 0) {
160            final int lastPos = size - 1;
161            char lastChar = mTypedWord.charAt(lastPos);
162            mCodes.remove(lastPos);
163            mTypedWord.deleteCharAt(lastPos);
164            if (Character.isUpperCase(lastChar)) mCapsCount--;
165        }
166        if (size() == 0) {
167            mIsFirstCharCapitalized = false;
168            mIsLastCharASingleQuote = false;
169        } else {
170            mIsLastCharASingleQuote =
171                    Keyboard.CODE_SINGLE_QUOTE == mTypedWord.codePointAt(mTypedWord.length() - 1);
172        }
173    }
174
175    /**
176     * Returns the word as it was typed, without any correction applied.
177     * @return the word that was typed so far
178     */
179    public String getTypedWord() {
180        if (size() == 0) {
181            return null;
182        }
183        return mTypedWord.toString();
184    }
185
186    /**
187     * Whether or not the user typed a capital letter as the first letter in the word
188     * @return capitalization preference
189     */
190    public boolean isFirstCharCapitalized() {
191        return mIsFirstCharCapitalized;
192    }
193
194    public boolean isLastCharASingleQuote() {
195        return mIsLastCharASingleQuote;
196    }
197
198    /**
199     * Whether or not all of the user typed chars are upper case
200     * @return true if all user typed chars are upper case, false otherwise
201     */
202    public boolean isAllUpperCase() {
203        return (mCapsCount > 0) && (mCapsCount == size());
204    }
205
206    /**
207     * Returns true if more than one character is upper case, otherwise returns false.
208     */
209    public boolean isMostlyCaps() {
210        return mCapsCount > 1;
211    }
212
213    /**
214     * Saves the reason why the word is capitalized - whether it was automatic or
215     * due to the user hitting shift in the middle of a sentence.
216     * @param auto whether it was an automatic capitalization due to start of sentence
217     */
218    public void setAutoCapitalized(boolean auto) {
219        mAutoCapitalized = auto;
220    }
221
222    /**
223     * Returns whether the word was automatically capitalized.
224     * @return whether the word was automatically capitalized
225     */
226    public boolean isAutoCapitalized() {
227        return mAutoCapitalized;
228    }
229}
230