1 /* 2 * Copyright (C) 2013 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 17 package com.android.inputmethod.latin; 18 19 import com.android.inputmethod.latin.StringUtils; 20 21 import java.util.Locale; 22 23 /** 24 * The status of the current recapitalize process. 25 */ 26 public class RecapitalizeStatus { 27 public static final int NOT_A_RECAPITALIZE_MODE = -1; 28 public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0; 29 public static final int CAPS_MODE_ALL_LOWER = 1; 30 public static final int CAPS_MODE_FIRST_WORD_UPPER = 2; 31 public static final int CAPS_MODE_ALL_UPPER = 3; 32 // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant. 33 public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER; 34 35 private static final int[] ROTATION_STYLE = { 36 CAPS_MODE_ORIGINAL_MIXED_CASE, 37 CAPS_MODE_ALL_LOWER, 38 CAPS_MODE_FIRST_WORD_UPPER, 39 CAPS_MODE_ALL_UPPER 40 }; 41 42 private static final int getStringMode(final String string, final String separators) { 43 if (StringUtils.isIdenticalAfterUpcase(string)) { 44 return CAPS_MODE_ALL_UPPER; 45 } else if (StringUtils.isIdenticalAfterDowncase(string)) { 46 return CAPS_MODE_ALL_LOWER; 47 } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) { 48 return CAPS_MODE_FIRST_WORD_UPPER; 49 } else { 50 return CAPS_MODE_ORIGINAL_MIXED_CASE; 51 } 52 } 53 54 /** 55 * We store the location of the cursor and the string that was there before the recapitalize 56 * action was done, and the location of the cursor and the string that was there after. 57 */ 58 private int mCursorStartBefore; 59 private String mStringBefore; 60 private int mCursorStartAfter; 61 private int mCursorEndAfter; 62 private int mRotationStyleCurrentIndex; 63 private boolean mSkipOriginalMixedCaseMode; 64 private Locale mLocale; 65 private String mSeparators; 66 private String mStringAfter; 67 private boolean mIsActive; 68 69 public RecapitalizeStatus() { 70 // By default, initialize with dummy values that won't match any real recapitalize. 71 initialize(-1, -1, "", Locale.getDefault(), ""); 72 deactivate(); 73 } 74 75 public void initialize(final int cursorStart, final int cursorEnd, final String string, 76 final Locale locale, final String separators) { 77 mCursorStartBefore = cursorStart; 78 mStringBefore = string; 79 mCursorStartAfter = cursorStart; 80 mCursorEndAfter = cursorEnd; 81 mStringAfter = string; 82 final int initialMode = getStringMode(mStringBefore, separators); 83 mLocale = locale; 84 mSeparators = separators; 85 if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) { 86 mRotationStyleCurrentIndex = 0; 87 mSkipOriginalMixedCaseMode = false; 88 } else { 89 // Find the current mode in the array. 90 int currentMode; 91 for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) { 92 if (ROTATION_STYLE[currentMode] == initialMode) { 93 break; 94 } 95 } 96 mRotationStyleCurrentIndex = currentMode; 97 mSkipOriginalMixedCaseMode = true; 98 } 99 mIsActive = true; 100 } 101 102 public void deactivate() { 103 mIsActive = false; 104 } 105 106 public boolean isActive() { 107 return mIsActive; 108 } 109 110 public boolean isSetAt(final int cursorStart, final int cursorEnd) { 111 return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter; 112 } 113 114 /** 115 * Rotate through the different possible capitalization modes. 116 */ 117 public void rotate() { 118 final String oldResult = mStringAfter; 119 int count = 0; // Protection against infinite loop. 120 do { 121 mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; 122 if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex] 123 && mSkipOriginalMixedCaseMode) { 124 mRotationStyleCurrentIndex = 125 (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; 126 } 127 ++count; 128 switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) { 129 case CAPS_MODE_ORIGINAL_MIXED_CASE: 130 mStringAfter = mStringBefore; 131 break; 132 case CAPS_MODE_ALL_LOWER: 133 mStringAfter = mStringBefore.toLowerCase(mLocale); 134 break; 135 case CAPS_MODE_FIRST_WORD_UPPER: 136 mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators, 137 mLocale); 138 break; 139 case CAPS_MODE_ALL_UPPER: 140 mStringAfter = mStringBefore.toUpperCase(mLocale); 141 break; 142 default: 143 mStringAfter = mStringBefore; 144 } 145 } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1); 146 mCursorEndAfter = mCursorStartAfter + mStringAfter.length(); 147 } 148 149 /** 150 * Remove leading/trailing whitespace from the considered string. 151 */ 152 public void trim() { 153 final int len = mStringBefore.length(); 154 int nonWhitespaceStart = 0; 155 for (; nonWhitespaceStart < len; 156 nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) { 157 final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart); 158 if (!Character.isWhitespace(codePoint)) break; 159 } 160 int nonWhitespaceEnd = len; 161 for (; nonWhitespaceEnd > 0; 162 nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) { 163 final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd); 164 if (!Character.isWhitespace(codePoint)) break; 165 } 166 if (0 != nonWhitespaceStart || len != nonWhitespaceEnd) { 167 mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd; 168 mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart; 169 mStringAfter = mStringBefore = 170 mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd); 171 } 172 } 173 174 public String getRecapitalizedString() { 175 return mStringAfter; 176 } 177 178 public int getNewCursorStart() { 179 return mCursorStartAfter; 180 } 181 182 public int getNewCursorEnd() { 183 return mCursorEndAfter; 184 } 185 186 public int getCurrentMode() { 187 return ROTATION_STYLE[mRotationStyleCurrentIndex]; 188 } 189 } 190