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