aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
authorJean Chalard <jchalard@google.com>2012-12-03 22:00:35 +0900
committerJean Chalard <jchalard@google.com>2013-04-15 20:23:01 +0900
commit2995abe7aadd483aa57a9b088740d46ac07bbe46 (patch)
treefcf2a3303cb3fc67b621adac0cbf235a26b405ab /java/src
parent059e084e983ce4a1440dc065f5167d278d8939e7 (diff)
downloadlatinime-2995abe7aadd483aa57a9b088740d46ac07bbe46.tar.gz
latinime-2995abe7aadd483aa57a9b088740d46ac07bbe46.tar.xz
latinime-2995abe7aadd483aa57a9b088740d46ac07bbe46.zip
Have Latin IME re-capitalize a selected string
Upon pressing Shift, if there is currently a selected string, have Latin IME change its capitalization. This does not yet have the keyboard mode follow the mode - the change is complicated enough as is. Bug: 7657025 Change-Id: I54fe8485f44e04efd72c71ac9feee5ce21ba06f2
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java40
-rw-r--r--java/src/com/android/inputmethod/latin/RecapitalizeStatus.java169
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java5
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java4
4 files changed, 217 insertions, 1 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 47d51c586..eaa095256 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -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();
@@ -1928,6 +1935,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) {
diff --git a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
new file mode 100644
index 000000000..9edd3a160
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
@@ -0,0 +1,169 @@
+/*
+ * 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;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index b74ea593d..e17846618 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -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.
*
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 318d2b23f..72e08700a 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -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) {