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