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 
getStringMode(final String string, final int[] sortedSeparators)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 
RecapitalizeStatus()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 
start(final int cursorStart, final int cursorEnd, final String string, final Locale locale, final int[] sortedSeparators)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 
stop()106     public void stop() {
107         mIsStarted = false;
108     }
109 
isStarted()110     public boolean isStarted() {
111         return mIsStarted;
112     }
113 
enable()114     public void enable() {
115         mIsEnabled = true;
116     }
117 
disable()118     public void disable() {
119         mIsEnabled = false;
120     }
121 
mIsEnabled()122     public boolean mIsEnabled() {
123         return mIsEnabled;
124     }
125 
isSetAt(final int cursorStart, final int cursorEnd)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      */
rotate()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      */
trim()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 
getRecapitalizedString()193     public String getRecapitalizedString() {
194         return mStringAfter;
195     }
196 
getNewCursorStart()197     public int getNewCursorStart() {
198         return mCursorStartAfter;
199     }
200 
getNewCursorEnd()201     public int getNewCursorEnd() {
202         return mCursorEndAfter;
203     }
204 
getCurrentMode()205     public int getCurrentMode() {
206         return ROTATION_STYLE[mRotationStyleCurrentIndex];
207     }
208 }
209