12995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard/*
22995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * Copyright (C) 2013 The Android Open Source Project
32995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard *
42995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * Licensed under the Apache License, Version 2.0 (the "License"); you may not
52995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * use this file except in compliance with the License. You may obtain a copy of
62995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * the License at
72995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard *
82995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * http://www.apache.org/licenses/LICENSE-2.0
92995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard *
102995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * Unless required by applicable law or agreed to in writing, software
112995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
122995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
132995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * License for the specific language governing permissions and limitations under
142995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * the License.
152995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard */
162995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
17e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasapackage com.android.inputmethod.latin.utils;
182995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
192995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalardimport java.util.Locale;
202995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
212995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard/**
222995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard * The status of the current recapitalize process.
232995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard */
242995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalardpublic class RecapitalizeStatus {
258094bf45d73a5f7257076afc97d91d6708ee03c4Jean Chalard    public static final int NOT_A_RECAPITALIZE_MODE = -1;
262995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0;
272995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public static final int CAPS_MODE_ALL_LOWER = 1;
282995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public static final int CAPS_MODE_FIRST_WORD_UPPER = 2;
292995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public static final int CAPS_MODE_ALL_UPPER = 3;
302995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant.
312995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER;
322995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
332995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private static final int[] ROTATION_STYLE = {
342995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        CAPS_MODE_ORIGINAL_MIXED_CASE,
352995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        CAPS_MODE_ALL_LOWER,
362995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        CAPS_MODE_FIRST_WORD_UPPER,
372995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        CAPS_MODE_ALL_UPPER
382995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    };
39b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard
402995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private static final int getStringMode(final String string, final String separators) {
412995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        if (StringUtils.isIdenticalAfterUpcase(string)) {
422995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            return CAPS_MODE_ALL_UPPER;
432995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        } else if (StringUtils.isIdenticalAfterDowncase(string)) {
442995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            return CAPS_MODE_ALL_LOWER;
452995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) {
462995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            return CAPS_MODE_FIRST_WORD_UPPER;
472995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        } else {
482995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            return CAPS_MODE_ORIGINAL_MIXED_CASE;
492995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        }
502995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
512995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
522995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    /**
53b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard     * We store the location of the cursor and the string that was there before the recapitalize
542995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard     * action was done, and the location of the cursor and the string that was there after.
552995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard     */
562995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private int mCursorStartBefore;
572995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private String mStringBefore;
582995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private int mCursorStartAfter;
592995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private int mCursorEndAfter;
602995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private int mRotationStyleCurrentIndex;
61b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    private boolean mSkipOriginalMixedCaseMode;
62b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    private Locale mLocale;
63b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    private String mSeparators;
642995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    private String mStringAfter;
65b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    private boolean mIsActive;
66b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard
67b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    public RecapitalizeStatus() {
68b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard        // By default, initialize with dummy values that won't match any real recapitalize.
69b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard        initialize(-1, -1, "", Locale.getDefault(), "");
70b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard        deactivate();
71b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    }
722995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
73b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    public void initialize(final int cursorStart, final int cursorEnd, final String string,
742995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            final Locale locale, final String separators) {
752995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mCursorStartBefore = cursorStart;
762995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mStringBefore = string;
772995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mCursorStartAfter = cursorStart;
782995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mCursorEndAfter = cursorEnd;
792995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mStringAfter = string;
802995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        final int initialMode = getStringMode(mStringBefore, separators);
812995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mLocale = locale;
822995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mSeparators = separators;
832995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) {
842995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            mRotationStyleCurrentIndex = 0;
852995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            mSkipOriginalMixedCaseMode = false;
862995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        } else {
872995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            // Find the current mode in the array.
882995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            int currentMode;
892995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) {
902995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                if (ROTATION_STYLE[currentMode] == initialMode) {
912995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                    break;
922995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                }
932995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            }
942995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            mRotationStyleCurrentIndex = currentMode;
952995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            mSkipOriginalMixedCaseMode = true;
962995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        }
97b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard        mIsActive = true;
98b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    }
99b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard
100b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    public void deactivate() {
101b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard        mIsActive = false;
102b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    }
103b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard
104b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard    public boolean isActive() {
105b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard        return mIsActive;
1062995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
1072995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
1082995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public boolean isSetAt(final int cursorStart, final int cursorEnd) {
1092995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter;
1102995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
1112995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
1122995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    /**
1132995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard     * Rotate through the different possible capitalization modes.
1142995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard     */
1152995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public void rotate() {
1162995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        final String oldResult = mStringAfter;
1172995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        int count = 0; // Protection against infinite loop.
1182995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        do {
1192995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
1202995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex]
1212995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                    && mSkipOriginalMixedCaseMode) {
1222995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                mRotationStyleCurrentIndex =
1232995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                        (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
1242995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            }
1252995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            ++count;
1262995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) {
127b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard            case CAPS_MODE_ORIGINAL_MIXED_CASE:
128b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                mStringAfter = mStringBefore;
129b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                break;
130b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard            case CAPS_MODE_ALL_LOWER:
131b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                mStringAfter = mStringBefore.toLowerCase(mLocale);
132b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                break;
133b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard            case CAPS_MODE_FIRST_WORD_UPPER:
134b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
135b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                        mLocale);
136b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                break;
137b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard            case CAPS_MODE_ALL_UPPER:
138b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                mStringAfter = mStringBefore.toUpperCase(mLocale);
139b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                break;
140b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard            default:
141b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard                mStringAfter = mStringBefore;
1422995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            }
143b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard        } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1);
1442995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        mCursorEndAfter = mCursorStartAfter + mStringAfter.length();
1452995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
1462995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
1472995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    /**
1482995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard     * Remove leading/trailing whitespace from the considered string.
1492995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard     */
1502995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public void trim() {
1512995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        final int len = mStringBefore.length();
1522995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        int nonWhitespaceStart = 0;
1532995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        for (; nonWhitespaceStart < len;
1542995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) {
1552995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart);
1562995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            if (!Character.isWhitespace(codePoint)) break;
1572995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        }
1582995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        int nonWhitespaceEnd = len;
1592995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        for (; nonWhitespaceEnd > 0;
1602995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) {
1612995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd);
1622995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            if (!Character.isWhitespace(codePoint)) break;
1632995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        }
1642a81943d162383cc6e9d9429bf3f7949c994a8baJean Chalard        // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only
1652a81943d162383cc6e9d9429bf3f7949c994a8baJean Chalard        // whitespace, so we leave it as is.
1662a81943d162383cc6e9d9429bf3f7949c994a8baJean Chalard        if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd)
1672a81943d162383cc6e9d9429bf3f7949c994a8baJean Chalard                && nonWhitespaceStart < nonWhitespaceEnd) {
168b794e904a3586ac5f2d31fb24d5a1a8f9aa964b8Jean Chalard            mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
1692995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
1702995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard            mStringAfter = mStringBefore =
1712995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard                    mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd);
1722995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        }
1732995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
1742995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
1752995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public String getRecapitalizedString() {
1762995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        return mStringAfter;
1772995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
1782995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
1792995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public int getNewCursorStart() {
1802995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        return mCursorStartAfter;
1812995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
1822995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard
1832995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    public int getNewCursorEnd() {
1842995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard        return mCursorEndAfter;
1852995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard    }
1868094bf45d73a5f7257076afc97d91d6708ee03c4Jean Chalard
1878094bf45d73a5f7257076afc97d91d6708ee03c4Jean Chalard    public int getCurrentMode() {
1888094bf45d73a5f7257076afc97d91d6708ee03c4Jean Chalard        return ROTATION_STYLE[mRotationStyleCurrentIndex];
1898094bf45d73a5f7257076afc97d91d6708ee03c4Jean Chalard    }
1902995abe7aadd483aa57a9b088740d46ac07bbe46Jean Chalard}
191