WordComposer.java revision 8aa9963a895f9dd5bb1bc92ab2e4f461e058f87a
1923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/*
2443c360d0afdbab091994244f045f4756feaf2b4Jean-Baptiste Queru * Copyright (C) 2008 The Android Open Source Project
30fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard *
48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License.
68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at
70fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard *
88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
90fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard *
10923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and
148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License.
15923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
16923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
17923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectpackage com.android.inputmethod.latin;
18923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
196b1f500da451de56932a8b2a99c63857994ece85Jean Chalardimport com.android.inputmethod.keyboard.Key;
203708787fe91227083d2a1874fa41493d3bc9fe10Tadashi G. Takaokaimport com.android.inputmethod.keyboard.Keyboard;
21887f11ee43ad621aa6ad93d535ab7f48dec73fc7Tadashi G. Takaoka
22c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalardimport java.util.Arrays;
23923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
24923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
25923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * A place to store the currently composing word with information such as adjacent key codes as well
26923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
27a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class WordComposer {
2896b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa    private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
298fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
30adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    public static final int CAPS_MODE_OFF = 0;
31adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    // 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
32adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    // aren't used anywhere in the code
33adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    public static final int CAPS_MODE_MANUAL_SHIFTED = 0x1;
34adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    public static final int CAPS_MODE_MANUAL_SHIFT_LOCKED = 0x3;
35adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
36adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
37adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard
3801ab7c8b59a7f12862fbd95fb252e56719f1757fsatok    private int[] mPrimaryKeyCodes;
3996b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa    private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
406b4ce58fc6216b9befd0567b56522ee32f2471a2Tadashi G. Takaoka    private final StringBuilder mTypedWord;
41bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    private String mAutoCorrection;
424b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard    private boolean mIsResumed;
43d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    private boolean mIsBatchMode;
444a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani
45be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard    // Cache these values for performance
464a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani    private int mCapsCount;
47e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard    private int mDigitsCount;
48adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    private int mCapitalizedMode;
49117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard    private int mTrailingSingleQuotesCount;
5001ab7c8b59a7f12862fbd95fb252e56719f1757fsatok    private int mCodePointSize;
51c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard
52923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
530b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     * Whether the user chose to capitalize the first char of the word.
54923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
550b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    private boolean mIsFirstCharCapitalized;
56923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
57979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public WordComposer() {
5896b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa        mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
5996b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa        mTypedWord = new StringBuilder(MAX_WORD_LENGTH);
60be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
61117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        mTrailingSingleQuotesCount = 0;
624b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard        mIsResumed = false;
63d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        mIsBatchMode = false;
6401ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        refreshSize();
65923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
66923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
67bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public WordComposer(final WordComposer source) {
6801ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length);
69be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord = new StringBuilder(source.mTypedWord);
7071538b08e4e08d556f700ad344562ca2c7b74d82Satoshi Kataoka        mInputPointers.copy(source.mInputPointers);
71ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mCapsCount = source.mCapsCount;
72e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard        mDigitsCount = source.mDigitsCount;
73ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
74adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard        mCapitalizedMode = source.mCapitalizedMode;
75117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
764b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard        mIsResumed = source.mIsResumed;
77d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        mIsBatchMode = source.mIsBatchMode;
7801ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        refreshSize();
79979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
80979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
81923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
82923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Clear out the keys registered so far.
83923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
84923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public void reset() {
85be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.setLength(0);
86be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
874a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani        mCapsCount = 0;
88e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard        mDigitsCount = 0;
89ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mIsFirstCharCapitalized = false;
90117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        mTrailingSingleQuotesCount = 0;
914b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard        mIsResumed = false;
92d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        mIsBatchMode = false;
9301ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        refreshSize();
9401ab7c8b59a7f12862fbd95fb252e56719f1757fsatok    }
9501ab7c8b59a7f12862fbd95fb252e56719f1757fsatok
96e55c23e4b0b8d9d66349a3b275d0fa1540d7450aKen Wakasa    private final void refreshSize() {
9701ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        mCodePointSize = mTypedWord.codePointCount(0, mTypedWord.length());
98923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
99923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
100923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
101923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Number of keystrokes in the composing word.
102923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @return the number of keystrokes
103923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
104ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka    public final int size() {
10501ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        return mCodePointSize;
106923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
107923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
108196d82cdd740580ed79d801483dbc282be85d076Jean Chalard    public final boolean isComposingWord() {
10901ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        return size() > 0;
110196d82cdd740580ed79d801483dbc282be85d076Jean Chalard    }
111196d82cdd740580ed79d801483dbc282be85d076Jean Chalard
1129611b281e18ac71d825ff5bc771a111423772cb3satok    // TODO: make sure that the index should not exceed MAX_WORD_LENGTH
11301ab7c8b59a7f12862fbd95fb252e56719f1757fsatok    public int getCodeAt(int index) {
11496b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa        if (index >= MAX_WORD_LENGTH) {
1159611b281e18ac71d825ff5bc771a111423772cb3satok            return -1;
1169611b281e18ac71d825ff5bc771a111423772cb3satok        }
11701ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        return mPrimaryKeyCodes[index];
118923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
119923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
12071538b08e4e08d556f700ad344562ca2c7b74d82Satoshi Kataoka    public InputPointers getInputPointers() {
12171538b08e4e08d556f700ad344562ca2c7b74d82Satoshi Kataoka        return mInputPointers;
1228fbd55229243cb66c03d5ea1f79dfb39f596590dsatok    }
1238fbd55229243cb66c03d5ea1f79dfb39f596590dsatok
124bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    private static boolean isFirstCharCapitalized(final int index, final int codePoint,
125bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final boolean previous) {
126ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        if (index == 0) return Character.isUpperCase(codePoint);
127436a645ea837d36f7e0f81948d343fa6e166f33aTadashi G. Takaoka        return previous && !Character.isUpperCase(codePoint);
128ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka    }
129ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka
130923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
131c61cd79229b1871d0f603a23389695d7f7751e66Tadashi G. Takaoka     * Add a new keystroke, with the pressed key's code point with the touch point coordinates.
132923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
133bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void add(final int primaryCode, final int keyX, final int keyY) {
13401ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        final int newIndex = size();
1359159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        mTypedWord.appendCodePoint(primaryCode);
13601ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        refreshSize();
13796b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa        if (newIndex < MAX_WORD_LENGTH) {
138240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka            mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
13967094f5bdece00994f70c6f1fa9a6ff7b8f3c3c1satok                    ? Character.toLowerCase(primaryCode) : primaryCode;
140eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            // In the batch input mode, the {@code mInputPointers} holds batch input points and
141eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            // shouldn't be overridden by the "typed key" coordinates
142eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            // (See {@link #setBatchInputWord}).
143eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            if (!mIsBatchMode) {
144eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                // TODO: Set correct pointer id and time
145eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                mInputPointers.addPointer(newIndex, keyX, keyY, 0, 0);
146eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            }
1478fbd55229243cb66c03d5ea1f79dfb39f596590dsatok        }
148ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        mIsFirstCharCapitalized = isFirstCharCapitalized(
149ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka                newIndex, primaryCode, mIsFirstCharCapitalized);
150ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        if (Character.isUpperCase(primaryCode)) mCapsCount++;
151e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard        if (Character.isDigit(primaryCode)) mDigitsCount++;
152240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka        if (Constants.CODE_SINGLE_QUOTE == primaryCode) {
153117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            ++mTrailingSingleQuotesCount;
154117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        } else {
155117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            mTrailingSingleQuotesCount = 0;
156117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        }
157be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
158923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
159923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
160bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void setBatchInputPointers(final InputPointers batchPointers) {
161eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        mInputPointers.set(batchPointers);
162d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        mIsBatchMode = true;
163d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
164d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
165bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void setBatchInputWord(final String word) {
166eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        reset();
167eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        mIsBatchMode = true;
168eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        final int length = word.length();
169eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
170eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            final int codePoint = Character.codePointAt(word, i);
171eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            // We don't want to override the batch input points that are held in mInputPointers
172eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            // (See {@link #add(int,int,int)}).
173ac78633be28e8990fc3b3a8de192c80966e746e3Tadashi G. Takaoka            add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
174eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        }
175eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang    }
176eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang
177923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
1786b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     * Internal method to retrieve reasonable proximity info for a character.
1796b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     */
180a492790982c6d7df62f66344db30b31995800e1bJean Chalard    private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
181d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka        final int x, y;
182d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka        final Key key;
183d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka        if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) {
184d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka            x = key.mX + key.mWidth / 2;
185d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka            y = key.mY + key.mHeight / 2;
186d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka        } else {
187d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka            x = Constants.NOT_A_COORDINATE;
188d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka            y = Constants.NOT_A_COORDINATE;
1896b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        }
190d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka        add(codePoint, x, y);
1916b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    }
1926b1f500da451de56932a8b2a99c63857994ece85Jean Chalard
1936b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    /**
1946b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     * Set the currently composing word to the one passed as an argument.
1956b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
1966b1f500da451de56932a8b2a99c63857994ece85Jean Chalard     */
197a492790982c6d7df62f66344db30b31995800e1bJean Chalard    public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
1986b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        reset();
1996b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        final int length = word.length();
2009159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
201d445b56ce14152b30143302899790af255691148Tadashi G. Takaoka            final int codePoint = Character.codePointAt(word, i);
202a492790982c6d7df62f66344db30b31995800e1bJean Chalard            addKeyInfo(codePoint, keyboard);
2036b1f500da451de56932a8b2a99c63857994ece85Jean Chalard        }
2044b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard        mIsResumed = true;
2056b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    }
2066b1f500da451de56932a8b2a99c63857994ece85Jean Chalard
2076b1f500da451de56932a8b2a99c63857994ece85Jean Chalard    /**
208923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Delete the last keystroke as a result of hitting backspace.
209923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
210923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public void deleteLast() {
21101ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        final int size = size();
212ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka        if (size > 0) {
2139159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            // Note: mTypedWord.length() and mCodes.length differ when there are surrogate pairs
2149159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            final int stringBuilderLength = mTypedWord.length();
2159159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            if (stringBuilderLength < size) {
2169159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                throw new RuntimeException(
2179159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                        "In WordComposer: mCodes and mTypedWords have non-matching lengths");
2189159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            }
2199159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            final int lastChar = mTypedWord.codePointBefore(stringBuilderLength);
2209159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            if (Character.isSupplementaryCodePoint(lastChar)) {
2219159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                mTypedWord.delete(stringBuilderLength - 2, stringBuilderLength);
2229159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            } else {
2239159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard                mTypedWord.deleteCharAt(stringBuilderLength - 1);
2249159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard            }
225ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka            if (Character.isUpperCase(lastChar)) mCapsCount--;
226e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard            if (Character.isDigit(lastChar)) mDigitsCount--;
22701ab7c8b59a7f12862fbd95fb252e56719f1757fsatok            refreshSize();
228d1a8e3088bb6267a31e3351d304796d1507e3af6Tadashi G. Takaoka        }
2299159b9953d857de83ae2f90a121fcd259f5ee01dJean Chalard        // We may have deleted the last one.
23001ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        if (0 == size()) {
231ea843f2a2404f4bc04fda494e475520162cfca27Tadashi G. Takaoka            mIsFirstCharCapitalized = false;
232117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        }
233117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        if (mTrailingSingleQuotesCount > 0) {
234117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            --mTrailingSingleQuotesCount;
235c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard        } else {
236825e2bbd910cce3055a4ca808d3744bc0b2ceddaJean Chalard            int i = mTypedWord.length();
237825e2bbd910cce3055a4ca808d3744bc0b2ceddaJean Chalard            while (i > 0) {
238825e2bbd910cce3055a4ca808d3744bc0b2ceddaJean Chalard                i = mTypedWord.offsetByCodePoints(i, -1);
239240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka                if (Constants.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
240117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard                ++mTrailingSingleQuotesCount;
241117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard            }
2428fbd55229243cb66c03d5ea1f79dfb39f596590dsatok        }
243be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
244923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
245923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
246923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
247923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Returns the word as it was typed, without any correction applied.
248117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     * @return the word that was typed so far. Never returns null.
249923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
2505c08151c227d98031abe27c3f0a8f43a7126ae9dJean Chalard    public String getTypedWord() {
251be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        return mTypedWord.toString();
252923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
253923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
254923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
255923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Whether or not the user typed a capital letter as the first letter in the word
256923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @return capitalization preference
257923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
2580b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    public boolean isFirstCharCapitalized() {
2590b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa        return mIsFirstCharCapitalized;
260923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
2610b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa
262117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard    public int trailingSingleQuotesCount() {
263117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard        return mTrailingSingleQuotesCount;
264c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard    }
265c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard
2660b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    /**
2670b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     * Whether or not all of the user typed chars are upper case
2680b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     * @return true if all user typed chars are upper case, false otherwise
2690b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa     */
2700b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    public boolean isAllUpperCase() {
271ad0642cf258ca9b123f74ca0ae8bf970792908f1Jean Chalard        if (size() <= 1) {
272ad0642cf258ca9b123f74ca0ae8bf970792908f1Jean Chalard            return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
273ad0642cf258ca9b123f74ca0ae8bf970792908f1Jean Chalard                    || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED;
274ad0642cf258ca9b123f74ca0ae8bf970792908f1Jean Chalard        } else {
275ad0642cf258ca9b123f74ca0ae8bf970792908f1Jean Chalard            return mCapsCount == size();
276ad0642cf258ca9b123f74ca0ae8bf970792908f1Jean Chalard        }
2771eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard    }
2781eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard
2791eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard    public boolean wasShiftedNoLock() {
2801eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        return mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED
2811eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard                || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFTED;
2820b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    }
2830b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa
284923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
2854a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani     * Returns true if more than one character is upper case, otherwise returns false.
2864a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani     */
2874a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani    public boolean isMostlyCaps() {
2884a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani        return mCapsCount > 1;
2894a7ff90d513f8b6cbf39688c08be0828a57e311bAmith Yamasani    }
2901c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani
2910fd625bcfdfac1c10e7bd7f9088bf425fec08989Jean Chalard    /**
292e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard     * Returns true if we have digits in the composing word.
293e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard     */
294e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard    public boolean hasDigits() {
295e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard        return mDigitsCount > 0;
296e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard    }
297e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard
298e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard    /**
299adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * Saves the caps mode at the start of composing.
300adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     *
301adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * WordComposer needs to know about this for several reasons. The first is, we need to know
302adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * after the fact what the reason was, to register the correct form into the user history
303adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * dictionary: if the word was automatically capitalized, we should insert it in all-lower
304adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * case but if it's a manual pressing of shift, then it should be inserted as is.
305adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * Also, batch input needs to know about the current caps mode to display correctly
306adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * capitalized suggestions.
307adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard     * @param mode the mode at the time of start
3081c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     */
309adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    public void setCapitalizedModeAtStartComposingTime(final int mode) {
310adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard        mCapitalizedMode = mode;
3111c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    }
3121c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani
3131c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    /**
3141c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     * Returns whether the word was automatically capitalized.
3151c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     * @return whether the word was automatically capitalized
3161c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani     */
317adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard    public boolean wasAutoCapitalized() {
318adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard        return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
319adbd9ae105e06287b59379d7f7127d95fd0663f4Jean Chalard                || mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED;
3201c551251106e506c70fad7ba0cb8b1e2a7dff3a9Amith Yamasani    }
321117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard
322117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    /**
323117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     * Sets the auto-correction for this word.
324117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     */
325bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void setAutoCorrection(final String correction) {
326be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = correction;
327117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    }
328117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard
329117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    /**
330f7d6517d6b1a1dd88e2142e1a15703bb839be01bJean Chalard     * @return the auto-correction for this word, or null if none.
331117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard     */
332bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public String getAutoCorrectionOrNull() {
333be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        return mAutoCorrection;
334117fc93f373cb86d4120c1261f9d0562c6529fecJean Chalard    }
335c73c26790fa9dcd836a918774d6efa39a05c0152Jean Chalard
3364b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard    /**
3374b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard     * @return whether we started composing this word by resuming suggestion on an existing string
3384b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard     */
3394b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard    public boolean isResumed() {
3404b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard        return mIsResumed;
3414b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard    }
3424b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard
343267563d1bb4d8091293fbd8774f0f95ef59f03c4Jean Chalard    // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
34466bb563535dbe3672f99f75bd71763a551444867Jean Chalard    public LastComposedWord commitWord(final int type, final String committedWord,
345bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final String separatorString, final String prevWord) {
3469271b770e81350e232c351f76f9f7a2ec23dff5fJean Chalard        // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
3479271b770e81350e232c351f76f9f7a2ec23dff5fJean Chalard        // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
3489271b770e81350e232c351f76f9f7a2ec23dff5fJean Chalard        // the last composed word to ensure this does not happen.
34901ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        final int[] primaryKeyCodes = mPrimaryKeyCodes;
35096b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa        mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
35101ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
352a05a0f20776b4c33f41f043f1bff245331937580Jean Chalard                mInputPointers, mTypedWord.toString(), committedWord, separatorString,
353b6b7f5e39e9ea1bf9a05203c536327a6be7e7214Jean Chalard                prevWord, mCapitalizedMode);
35471538b08e4e08d556f700ad344562ca2c7b74d82Satoshi Kataoka        mInputPointers.reset();
3559271b770e81350e232c351f76f9f7a2ec23dff5fJean Chalard        if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
3569271b770e81350e232c351f76f9f7a2ec23dff5fJean Chalard                && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
357449415c72f437f523a49a9ccfcde8a3c0f583a18Jean Chalard            lastComposedWord.deactivate();
358449415c72f437f523a49a9ccfcde8a3c0f583a18Jean Chalard        }
359e9808694fecbf7be776cd5cf8ec0333e158286b1Jean Chalard        mCapsCount = 0;
360e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard        mDigitsCount = 0;
361e9808694fecbf7be776cd5cf8ec0333e158286b1Jean Chalard        mIsBatchMode = false;
362be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.setLength(0);
3638914555776a4d3dfd6afc4926a69169ca1c82a0eJean Chalard        mCodePointSize = 0;
3642a37fb9d30848aee42757546e8478cb7a9e45bc6Jean Chalard        mTrailingSingleQuotesCount = 0;
365e9808694fecbf7be776cd5cf8ec0333e158286b1Jean Chalard        mIsFirstCharCapitalized = false;
3668914555776a4d3dfd6afc4926a69169ca1c82a0eJean Chalard        mCapitalizedMode = CAPS_MODE_OFF;
36701ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        refreshSize();
368be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mAutoCorrection = null;
3694b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard        mIsResumed = false;
3701f8fc62ccb5018716457dc309ab11ad3e1506ad1Jean Chalard        return lastComposedWord;
371c73c26790fa9dcd836a918774d6efa39a05c0152Jean Chalard    }
372c73c26790fa9dcd836a918774d6efa39a05c0152Jean Chalard
3732712f23acbb197af3b125da4cc47108e71b7446dJean Chalard    public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
37401ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
37571538b08e4e08d556f700ad344562ca2c7b74d82Satoshi Kataoka        mInputPointers.set(lastComposedWord.mInputPointers);
376be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.setLength(0);
377be79227dc99421ff7be62224c51c553b3fa73777Jean Chalard        mTypedWord.append(lastComposedWord.mTypedWord);
37801ab7c8b59a7f12862fbd95fb252e56719f1757fsatok        refreshSize();
379b6b7f5e39e9ea1bf9a05203c536327a6be7e7214Jean Chalard        mCapitalizedMode = lastComposedWord.mCapitalizedMode;
380cf9d92629cae88273805eaf7984fcfdd8afd11f5Jean Chalard        mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
3814b5b46bb66bf74ef5edd65c55e186b02f3c56e5dJean Chalard        mIsResumed = true;
3829e8761c4402ddc11c942ed2e583bd7d58f70c5eaJean Chalard    }
383d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
384d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    public boolean isBatchMode() {
385d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        return mIsBatchMode;
386d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
387923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
388