Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e5bd3b6 authored by Jean Chalard's avatar Jean Chalard Committed by Android Git Automerger
Browse files

am dea5e6a3: am 03395833: Merge "Have Latin IME re-capitalize a selected string"

* commit 'dea5e6a3':
  Have Latin IME re-capitalize a selected string
parents d69892fc dea5e6a3
Loading
Loading
Loading
Loading
+39 −1
Original line number Diff line number Diff line
@@ -161,6 +161,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
            mPositionalInfoForUserDictPendingAddition = null;
    private final WordComposer mWordComposer = new WordComposer();
    private final RichInputConnection mConnection = new RichInputConnection(this);
    private RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus(-1, -1, "",
            Locale.getDefault(), ""); // Dummy object that will match no real recapitalize

    // Keep track of the last selection range to decide if we need to show word alternatives
    private static final int NOT_A_CURSOR_POSITION = -1;
@@ -1387,8 +1389,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
            LatinImeLogger.logOnDelete(x, y);
            break;
        case Constants.CODE_SHIFT:
            // Note: calling back to the keyboard on Shift key is handled in onPressKey()
            // and onReleaseKey().
            handleRecapitalize();
            break;
        case Constants.CODE_SWITCH_ALPHA_SYMBOL:
            // Shift and symbol key is handled in onPressKey() and onReleaseKey().
            // Note: calling back to the keyboard on symbol key is handled in onPressKey()
            // and onReleaseKey().
            break;
        case Constants.CODE_SETTINGS:
            onSettingsKeyPressed();
@@ -1943,6 +1950,37 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
        }
    }

    private void handleRecapitalize() {
        if (mLastSelectionStart == mLastSelectionEnd) return; // No selection
        // If we have a recapitalize in progress, use it; otherwise, create a new one.
        if (null == mRecapitalizeStatus
                || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
            mRecapitalizeStatus =
                    new RecapitalizeStatus(mLastSelectionStart, mLastSelectionEnd,
                    mConnection.getSelectedText(0 /* flags, 0 for no styles */).toString(),
                    mSettings.getCurrentLocale(), mSettings.getWordSeparators());
            // We trim leading and trailing whitespace.
            mRecapitalizeStatus.trim();
            // Trimming the object may have changed the length of the string, and we need to
            // reposition the selection handles accordingly. As this result in an IPC call,
            // only do it if it's actually necessary, in other words if the recapitalize status
            // is not set at the same place as before.
            if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
                mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
                mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
                mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
            }
        }
        mRecapitalizeStatus.rotate();
        final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
        mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
        mConnection.deleteSurroundingText(numCharsDeleted, 0);
        mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
        mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
        mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
        mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
    }

    // Returns true if we did an autocorrection, false otherwise.
    private boolean handleSeparator(final int primaryCode, final int x, final int y,
            final int spaceState) {
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * 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.android.inputmethod.latin;

import com.android.inputmethod.latin.StringUtils;

import java.util.Locale;

/**
 * The status of the current recapitalize process.
 */
public class RecapitalizeStatus {
    public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0;
    public static final int CAPS_MODE_ALL_LOWER = 1;
    public static final int CAPS_MODE_FIRST_WORD_UPPER = 2;
    public static final int CAPS_MODE_ALL_UPPER = 3;
    // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant.
    public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER;

    private static final int[] ROTATION_STYLE = {
        CAPS_MODE_ORIGINAL_MIXED_CASE,
        CAPS_MODE_ALL_LOWER,
        CAPS_MODE_FIRST_WORD_UPPER,
        CAPS_MODE_ALL_UPPER
    };
    private static final int getStringMode(final String string, final String separators) {
        if (StringUtils.isIdenticalAfterUpcase(string)) {
            return CAPS_MODE_ALL_UPPER;
        } else if (StringUtils.isIdenticalAfterDowncase(string)) {
            return CAPS_MODE_ALL_LOWER;
        } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) {
            return CAPS_MODE_FIRST_WORD_UPPER;
        } else {
            return CAPS_MODE_ORIGINAL_MIXED_CASE;
        }
    }

    /**
     * We store the location of the cursor and the string that was there before the undoable
     * action was done, and the location of the cursor and the string that was there after.
     */
    private int mCursorStartBefore;
    private int mCursorEndBefore;
    private String mStringBefore;
    private int mCursorStartAfter;
    private int mCursorEndAfter;
    private int mRotationStyleCurrentIndex;
    private final boolean mSkipOriginalMixedCaseMode;
    private final Locale mLocale;
    private final String mSeparators;
    private String mStringAfter;

    public RecapitalizeStatus(final int cursorStart, final int cursorEnd, final String string,
            final Locale locale, final String separators) {
        mCursorStartBefore = cursorStart;
        mCursorEndBefore = cursorEnd;
        mStringBefore = string;
        mCursorStartAfter = cursorStart;
        mCursorEndAfter = cursorEnd;
        mStringAfter = string;
        final int initialMode = getStringMode(mStringBefore, separators);
        mLocale = locale;
        mSeparators = separators;
        if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) {
            mRotationStyleCurrentIndex = 0;
            mSkipOriginalMixedCaseMode = false;
        } else {
            // Find the current mode in the array.
            int currentMode;
            for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) {
                if (ROTATION_STYLE[currentMode] == initialMode) {
                    break;
                }
            }
            mRotationStyleCurrentIndex = currentMode;
            mSkipOriginalMixedCaseMode = true;
        }
    }

    public boolean isSetAt(final int cursorStart, final int cursorEnd) {
        return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter;
    }

    /**
     * Rotate through the different possible capitalization modes.
     */
    public void rotate() {
        final String oldResult = mStringAfter;
        int count = 0; // Protection against infinite loop.
        do {
            mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
            if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex]
                    && mSkipOriginalMixedCaseMode) {
                mRotationStyleCurrentIndex =
                        (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
            }
            ++count;
            switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) {
                case CAPS_MODE_ORIGINAL_MIXED_CASE:
                    mStringAfter = mStringBefore;
                    break;
                case CAPS_MODE_ALL_LOWER:
                    mStringAfter = mStringBefore.toLowerCase(mLocale);
                    break;
                case CAPS_MODE_FIRST_WORD_UPPER:
                    mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
                            mLocale);
                    break;
                case CAPS_MODE_ALL_UPPER:
                    mStringAfter = mStringBefore.toUpperCase(mLocale);
                    break;
                default:
                    mStringAfter = mStringBefore;
            }
        } while (mStringAfter.equals(oldResult) && count < 5);
        mCursorEndAfter = mCursorStartAfter + mStringAfter.length();
    }

    /**
     * Remove leading/trailing whitespace from the considered string.
     */
    public void trim() {
        final int len = mStringBefore.length();
        int nonWhitespaceStart = 0;
        for (; nonWhitespaceStart < len;
                nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) {
            final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart);
            if (!Character.isWhitespace(codePoint)) break;
        }
        int nonWhitespaceEnd = len;
        for (; nonWhitespaceEnd > 0;
                nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) {
            final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd);
            if (!Character.isWhitespace(codePoint)) break;
        }
        if (0 != nonWhitespaceStart || len != nonWhitespaceEnd) {
            mCursorEndBefore = mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
            mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
            mStringAfter = mStringBefore =
                    mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd);
        }
    }

    public String getRecapitalizedString() {
        return mStringAfter;
    }

    public int getNewCursorStart() {
        return mCursorStartAfter;
    }

    public int getNewCursorEnd() {
        return mCursorEndAfter;
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -183,6 +183,11 @@ public final class RichInputConnection {
        }
    }

    public CharSequence getSelectedText(final int flags) {
        if (null == mIC) return null;
        return mIC.getSelectedText(flags);
    }

    /**
     * Gets the caps modes we should be in after this specific string.
     *
+4 −0
Original line number Diff line number Diff line
@@ -138,6 +138,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
        return mSettingsValues.mWordSeparators;
    }

    public Locale getCurrentLocale() {
        return mCurrentLocale;
    }

    // Accessed from the settings interface, hence public
    public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
            final Resources res) {
+203 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * 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.android.inputmethod.latin;

import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;

import java.util.Locale;

@SmallTest
public class RecapitalizeStatusTests extends AndroidTestCase {
    public void testTrim() {
        RecapitalizeStatus status = new RecapitalizeStatus(30, 40, "abcdefghij",
                Locale.ENGLISH, " ");
        status.trim();
        assertEquals("abcdefghij", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(40, status.getNewCursorEnd());

        status = new RecapitalizeStatus(30, 44, "    abcdefghij",
                Locale.ENGLISH, " ");
        status.trim();
        assertEquals("abcdefghij", status.getRecapitalizedString());
        assertEquals(34, status.getNewCursorStart());
        assertEquals(44, status.getNewCursorEnd());

        status = new RecapitalizeStatus(30, 40, "abcdefgh  ",
                Locale.ENGLISH, " ");
        status.trim();
        assertEquals("abcdefgh", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(38, status.getNewCursorEnd());

        status = new RecapitalizeStatus(30, 45, "   abcdefghij  ",
                Locale.ENGLISH, " ");
        status.trim();
        assertEquals("abcdefghij", status.getRecapitalizedString());
        assertEquals(33, status.getNewCursorStart());
        assertEquals(43, status.getNewCursorEnd());
    }

    public void testRotate() {
        RecapitalizeStatus status = new RecapitalizeStatus(29, 40, "abcd efghij",
                Locale.ENGLISH, " ");
        status.rotate();
        assertEquals("Abcd Efghij", status.getRecapitalizedString());
        assertEquals(29, status.getNewCursorStart());
        assertEquals(40, status.getNewCursorEnd());
        status.rotate();
        assertEquals("ABCD EFGHIJ", status.getRecapitalizedString());
        status.rotate();
        assertEquals("abcd efghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("Abcd Efghij", status.getRecapitalizedString());

        status = new RecapitalizeStatus(29, 40, "Abcd Efghij",
                Locale.ENGLISH, " ");
        status.rotate();
        assertEquals("ABCD EFGHIJ", status.getRecapitalizedString());
        assertEquals(29, status.getNewCursorStart());
        assertEquals(40, status.getNewCursorEnd());
        status.rotate();
        assertEquals("abcd efghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("Abcd Efghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("ABCD EFGHIJ", status.getRecapitalizedString());

        status = new RecapitalizeStatus(29, 40, "ABCD EFGHIJ",
                Locale.ENGLISH, " ");
        status.rotate();
        assertEquals("abcd efghij", status.getRecapitalizedString());
        assertEquals(29, status.getNewCursorStart());
        assertEquals(40, status.getNewCursorEnd());
        status.rotate();
        assertEquals("Abcd Efghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("ABCD EFGHIJ", status.getRecapitalizedString());
        status.rotate();
        assertEquals("abcd efghij", status.getRecapitalizedString());

        status = new RecapitalizeStatus(29, 39, "AbCDefghij",
                Locale.ENGLISH, " ");
        status.rotate();
        assertEquals("abcdefghij", status.getRecapitalizedString());
        assertEquals(29, status.getNewCursorStart());
        assertEquals(39, status.getNewCursorEnd());
        status.rotate();
        assertEquals("Abcdefghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("ABCDEFGHIJ", status.getRecapitalizedString());
        status.rotate();
        assertEquals("AbCDefghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("abcdefghij", status.getRecapitalizedString());

        status = new RecapitalizeStatus(29, 40, "Abcd efghij",
                Locale.ENGLISH, " ");
        status.rotate();
        assertEquals("abcd efghij", status.getRecapitalizedString());
        assertEquals(29, status.getNewCursorStart());
        assertEquals(40, status.getNewCursorEnd());
        status.rotate();
        assertEquals("Abcd Efghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("ABCD EFGHIJ", status.getRecapitalizedString());
        status.rotate();
        assertEquals("Abcd efghij", status.getRecapitalizedString());
        status.rotate();
        assertEquals("abcd efghij", status.getRecapitalizedString());

        status = new RecapitalizeStatus(30, 34, "grüß", Locale.GERMAN, " ");
        status.rotate();
        assertEquals("Grüß", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(34, status.getNewCursorEnd());
        status.rotate();
        assertEquals("GRÜSS", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(35, status.getNewCursorEnd());
        status.rotate();
        assertEquals("grüß", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(34, status.getNewCursorEnd());
        status.rotate();
        assertEquals("Grüß", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(34, status.getNewCursorEnd());


        status = new RecapitalizeStatus(30, 33, "œuf", Locale.FRENCH, " ");
        status.rotate();
        assertEquals("Œuf", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());
        status.rotate();
        assertEquals("ŒUF", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());
        status.rotate();
        assertEquals("œuf", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());
        status.rotate();
        assertEquals("Œuf", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());

        status = new RecapitalizeStatus(30, 33, "œUf", Locale.FRENCH, " ");
        status.rotate();
        assertEquals("œuf", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());
        status.rotate();
        assertEquals("Œuf", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());
        status.rotate();
        assertEquals("ŒUF", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());
        status.rotate();
        assertEquals("œUf", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());
        status.rotate();
        assertEquals("œuf", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(33, status.getNewCursorEnd());

        status = new RecapitalizeStatus(30, 35, "école", Locale.FRENCH, " ");
        status.rotate();
        assertEquals("École", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(35, status.getNewCursorEnd());
        status.rotate();
        assertEquals("ÉCOLE", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(35, status.getNewCursorEnd());
        status.rotate();
        assertEquals("école", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(35, status.getNewCursorEnd());
        status.rotate();
        assertEquals("École", status.getRecapitalizedString());
        assertEquals(30, status.getNewCursorStart());
        assertEquals(35, status.getNewCursorEnd());
    }
}