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.utils; 18 19 import com.android.inputmethod.latin.common.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 int[] sortedSeparators) { 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, sortedSeparators)) { 48 return CAPS_MODE_FIRST_WORD_UPPER; 49 } else { 50 return CAPS_MODE_ORIGINAL_MIXED_CASE; 51 } 52 } 53 54 public static String modeToString(final int recapitalizeMode) { 55 switch (recapitalizeMode) { 56 case NOT_A_RECAPITALIZE_MODE: return "undefined"; 57 case CAPS_MODE_ORIGINAL_MIXED_CASE: return "mixedCase"; 58 case CAPS_MODE_ALL_LOWER: return "allLower"; 59 case CAPS_MODE_FIRST_WORD_UPPER: return "firstWordUpper"; 60 case CAPS_MODE_ALL_UPPER: return "allUpper"; 61 default: return "unknown<" + recapitalizeMode + ">"; 62 } 63 } 64 65 /** 66 * We store the location of the cursor and the string that was there before the recapitalize 67 * action was done, and the location of the cursor and the string that was there after. 68 */ 69 private int mCursorStartBefore; 70 private String mStringBefore; 71 private int mCursorStartAfter; 72 private int mCursorEndAfter; 73 private int mRotationStyleCurrentIndex; 74 private boolean mSkipOriginalMixedCaseMode; 75 private Locale mLocale; 76 private int[] mSortedSeparators; 77 private String mStringAfter; 78 private boolean mIsStarted; 79 private boolean mIsEnabled = true; 80 81 private static final int[] EMPTY_STORTED_SEPARATORS = {}; 82 83 public RecapitalizeStatus() { 84 // By default, initialize with dummy values that won't match any real recapitalize. 85 start(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS); 86 stop(); 87 } 88 89 public void start(final int cursorStart, final int cursorEnd, final String string, 90 final Locale locale, final int[] sortedSeparators) { 91 if (!mIsEnabled) { 92 return; 93 } 94 mCursorStartBefore = cursorStart; 95 mStringBefore = string; 96 mCursorStartAfter = cursorStart; 97 mCursorEndAfter = cursorEnd; 98 mStringAfter = string; 99 final int initialMode = getStringMode(mStringBefore, sortedSeparators); 100 mLocale = locale; 101 mSortedSeparators = sortedSeparators; 102 if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) { 103 mRotationStyleCurrentIndex = 0; 104 mSkipOriginalMixedCaseMode = false; 105 } else { 106 // Find the current mode in the array. 107 int currentMode; 108 for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) { 109 if (ROTATION_STYLE[currentMode] == initialMode) { 110 break; 111 } 112 } 113 mRotationStyleCurrentIndex = currentMode; 114 mSkipOriginalMixedCaseMode = true; 115 } 116 mIsStarted = true; 117 } 118 119 public void stop() { 120 mIsStarted = false; 121 } 122 123 public boolean isStarted() { 124 return mIsStarted; 125 } 126 127 public void enable() { 128 mIsEnabled = true; 129 } 130 131 public void disable() { 132 mIsEnabled = false; 133 } 134 135 public boolean mIsEnabled() { 136 return mIsEnabled; 137 } 138 139 public boolean isSetAt(final int cursorStart, final int cursorEnd) { 140 return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter; 141 } 142 143 /** 144 * Rotate through the different possible capitalization modes. 145 */ 146 public void rotate() { 147 final String oldResult = mStringAfter; 148 int count = 0; // Protection against infinite loop. 149 do { 150 mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; 151 if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex] 152 && mSkipOriginalMixedCaseMode) { 153 mRotationStyleCurrentIndex = 154 (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; 155 } 156 ++count; 157 switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) { 158 case CAPS_MODE_ORIGINAL_MIXED_CASE: 159 mStringAfter = mStringBefore; 160 break; 161 case CAPS_MODE_ALL_LOWER: 162 mStringAfter = mStringBefore.toLowerCase(mLocale); 163 break; 164 case CAPS_MODE_FIRST_WORD_UPPER: 165 mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSortedSeparators, 166 mLocale); 167 break; 168 case CAPS_MODE_ALL_UPPER: 169 mStringAfter = mStringBefore.toUpperCase(mLocale); 170 break; 171 default: 172 mStringAfter = mStringBefore; 173 } 174 } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1); 175 mCursorEndAfter = mCursorStartAfter + mStringAfter.length(); 176 } 177 178 /** 179 * Remove leading/trailing whitespace from the considered string. 180 */ 181 public void trim() { 182 final int len = mStringBefore.length(); 183 int nonWhitespaceStart = 0; 184 for (; nonWhitespaceStart < len; 185 nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) { 186 final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart); 187 if (!Character.isWhitespace(codePoint)) break; 188 } 189 int nonWhitespaceEnd = len; 190 for (; nonWhitespaceEnd > 0; 191 nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) { 192 final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd); 193 if (!Character.isWhitespace(codePoint)) break; 194 } 195 // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only 196 // whitespace, so we leave it as is. 197 if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd) 198 && nonWhitespaceStart < nonWhitespaceEnd) { 199 mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd; 200 mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart; 201 mStringAfter = mStringBefore = 202 mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd); 203 } 204 } 205 206 public String getRecapitalizedString() { 207 return mStringAfter; 208 } 209 210 public int getNewCursorStart() { 211 return mCursorStartAfter; 212 } 213 214 public int getNewCursorEnd() { 215 return mCursorEndAfter; 216 } 217 218 public int getCurrentMode() { 219 return ROTATION_STYLE[mRotationStyleCurrentIndex]; 220 } 221 } 222