Loading core/java/android/widget/TextView.java +15 −150 Original line number Diff line number Diff line Loading @@ -9541,10 +9541,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener { private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE; private static final float AVERAGE_HIGHLIGHTS_PER_SUGGESTION = 1.4f; private WordIterator mSuggestionWordIterator; private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan [(int) (AVERAGE_HIGHLIGHTS_PER_SUGGESTION * MAX_NUMBER_SUGGESTIONS)]; private SuggestionInfo[] mSuggestionInfos; private int mNumberOfSuggestions; private boolean mCursorWasVisibleBeforeSuggestions; Loading @@ -9571,10 +9567,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public SuggestionsPopupWindow() { for (int i = 0; i < mHighlightSpans.length; i++) { mHighlightSpans[i] = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); } mCursorWasVisibleBeforeSuggestions = mCursorVisible; } Loading Loading @@ -9608,6 +9600,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents int suggestionIndex; // the index of the suggestion inside suggestionSpan SpannableStringBuilder text = new SpannableStringBuilder(); TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); void removeMisspelledFlag() { int suggestionSpanFlags = suggestionSpan.getFlags(); Loading Loading @@ -9820,152 +9814,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; } private long[] getWordLimits(CharSequence text) { // TODO locale for mSuggestionWordIterator if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator(); mSuggestionWordIterator.setCharSequence(text); // First pass will simply count the number of words to be able to create an array // Not too expensive since previous break positions are cached by the BreakIterator int nbWords = 0; int position = mSuggestionWordIterator.following(0); while (position != BreakIterator.DONE) { nbWords++; position = mSuggestionWordIterator.following(position); } int index = 0; long[] result = new long[nbWords]; position = mSuggestionWordIterator.following(0); while (position != BreakIterator.DONE) { int wordStart = mSuggestionWordIterator.getBeginning(position); result[index++] = packRangeInLong(wordStart, position); position = mSuggestionWordIterator.following(position); } return result; } private TextAppearanceSpan highlightSpan(int index) { final int length = mHighlightSpans.length; if (index < length) { return mHighlightSpans[index]; } // Assumes indexes are requested in sequence: simply append one more item TextAppearanceSpan[] newArray = new TextAppearanceSpan[length + 1]; System.arraycopy(mHighlightSpans, 0, newArray, 0, length); TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); newArray[length] = highlightSpan; mHighlightSpans = newArray; return highlightSpan; } private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart, int unionEnd) { private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart, int unionEnd) { final int spanStart = suggestionInfo.spanStart; final int spanEnd = suggestionInfo.spanEnd; // Remove all text formating by converting to Strings final String text = suggestionInfo.text.toString(); final String sourceText = mText.subSequence(spanStart, spanEnd).toString(); long[] sourceWordLimits = getWordLimits(sourceText); long[] wordLimits = getWordLimits(text); SpannableStringBuilder ssb = new SpannableStringBuilder(); // span [spanStart, spanEnd] is included in union [spanUnionStart, int spanUnionEnd] // The final result is made of 3 parts: the text before, between and after the span // This is the text before, provided for context ssb.append(mText.subSequence(unionStart, spanStart).toString()); // shift is used to offset spans positions wrt span's beginning final int shift = spanStart - unionStart; suggestionInfo.suggestionStart = shift; suggestionInfo.suggestionEnd = shift + text.length(); // This is the actual suggestion text, which will be highlighted by the following code ssb.append(text); String[] words = new String[wordLimits.length]; for (int i = 0; i < wordLimits.length; i++) { int wordStart = extractRangeStartFromLong(wordLimits[i]); int wordEnd = extractRangeEndFromLong(wordLimits[i]); words[i] = text.substring(wordStart, wordEnd); } // Highlighted word algorithm is based on word matching between source and text // Matching words are found from left to right. TODO: change for RTL languages // Characters between matching words are highlighted int previousCommonWordIndex = -1; int nbHighlightSpans = 0; for (int i = 0; i < sourceWordLimits.length; i++) { int wordStart = extractRangeStartFromLong(sourceWordLimits[i]); int wordEnd = extractRangeEndFromLong(sourceWordLimits[i]); String sourceWord = sourceText.substring(wordStart, wordEnd); for (int j = previousCommonWordIndex + 1; j < words.length; j++) { if (sourceWord.equals(words[j])) { if (j != previousCommonWordIndex + 1) { int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 : extractRangeEndFromLong(wordLimits[previousCommonWordIndex]); int lastDifferentPosition = extractRangeStartFromLong(wordLimits[j]); ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + firstDifferentPosition, shift + lastDifferentPosition, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { // Compare characters between words int previousSourceWordEnd = i == 0 ? 0 : extractRangeEndFromLong(sourceWordLimits[i - 1]); int sourceWordStart = extractRangeStartFromLong(sourceWordLimits[i]); String sourceSpaces = sourceText.substring(previousSourceWordEnd, sourceWordStart); int previousWordEnd = j == 0 ? 0 : extractRangeEndFromLong(wordLimits[j - 1]); int currentWordStart = extractRangeStartFromLong(wordLimits[j]); String textSpaces = text.substring(previousWordEnd, currentWordStart); if (!sourceSpaces.equals(textSpaces)) { ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + previousWordEnd, shift + currentWordStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } previousCommonWordIndex = j; break; } } } // Finally, compare ends of Strings if (previousCommonWordIndex < words.length - 1) { int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 : extractRangeEndFromLong(wordLimits[previousCommonWordIndex]); int lastDifferentPosition = text.length(); ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + firstDifferentPosition, shift + lastDifferentPosition, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { int lastSourceWordEnd = sourceWordLimits.length == 0 ? 0 : extractRangeEndFromLong(sourceWordLimits[sourceWordLimits.length - 1]); String sourceSpaces = sourceText.substring(lastSourceWordEnd, sourceText.length()); // Adjust the start/end of the suggestion span suggestionInfo.suggestionStart = spanStart - unionStart; suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart + suggestionInfo.text.length(); int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 : extractRangeEndFromLong(wordLimits[previousCommonWordIndex]); String textSpaces = text.substring(lastCommonTextWordEnd, text.length()); if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) { ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + lastCommonTextWordEnd, shift + text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } suggestionInfo.text.clearSpans(); suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Final part, text after the current suggestion range. ssb.append(mText.subSequence(spanEnd, unionEnd).toString()); // Add the text before and after the span. suggestionInfo.text.insert(0, mText.subSequence(unionStart, spanStart).toString()); suggestionInfo.text.append(mText.subSequence(spanEnd, unionEnd).toString()); } @Override Loading Loading
core/java/android/widget/TextView.java +15 −150 Original line number Diff line number Diff line Loading @@ -9541,10 +9541,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener { private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE; private static final float AVERAGE_HIGHLIGHTS_PER_SUGGESTION = 1.4f; private WordIterator mSuggestionWordIterator; private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan [(int) (AVERAGE_HIGHLIGHTS_PER_SUGGESTION * MAX_NUMBER_SUGGESTIONS)]; private SuggestionInfo[] mSuggestionInfos; private int mNumberOfSuggestions; private boolean mCursorWasVisibleBeforeSuggestions; Loading @@ -9571,10 +9567,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public SuggestionsPopupWindow() { for (int i = 0; i < mHighlightSpans.length; i++) { mHighlightSpans[i] = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); } mCursorWasVisibleBeforeSuggestions = mCursorVisible; } Loading Loading @@ -9608,6 +9600,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents int suggestionIndex; // the index of the suggestion inside suggestionSpan SpannableStringBuilder text = new SpannableStringBuilder(); TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); void removeMisspelledFlag() { int suggestionSpanFlags = suggestionSpan.getFlags(); Loading Loading @@ -9820,152 +9814,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; } private long[] getWordLimits(CharSequence text) { // TODO locale for mSuggestionWordIterator if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator(); mSuggestionWordIterator.setCharSequence(text); // First pass will simply count the number of words to be able to create an array // Not too expensive since previous break positions are cached by the BreakIterator int nbWords = 0; int position = mSuggestionWordIterator.following(0); while (position != BreakIterator.DONE) { nbWords++; position = mSuggestionWordIterator.following(position); } int index = 0; long[] result = new long[nbWords]; position = mSuggestionWordIterator.following(0); while (position != BreakIterator.DONE) { int wordStart = mSuggestionWordIterator.getBeginning(position); result[index++] = packRangeInLong(wordStart, position); position = mSuggestionWordIterator.following(position); } return result; } private TextAppearanceSpan highlightSpan(int index) { final int length = mHighlightSpans.length; if (index < length) { return mHighlightSpans[index]; } // Assumes indexes are requested in sequence: simply append one more item TextAppearanceSpan[] newArray = new TextAppearanceSpan[length + 1]; System.arraycopy(mHighlightSpans, 0, newArray, 0, length); TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); newArray[length] = highlightSpan; mHighlightSpans = newArray; return highlightSpan; } private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart, int unionEnd) { private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart, int unionEnd) { final int spanStart = suggestionInfo.spanStart; final int spanEnd = suggestionInfo.spanEnd; // Remove all text formating by converting to Strings final String text = suggestionInfo.text.toString(); final String sourceText = mText.subSequence(spanStart, spanEnd).toString(); long[] sourceWordLimits = getWordLimits(sourceText); long[] wordLimits = getWordLimits(text); SpannableStringBuilder ssb = new SpannableStringBuilder(); // span [spanStart, spanEnd] is included in union [spanUnionStart, int spanUnionEnd] // The final result is made of 3 parts: the text before, between and after the span // This is the text before, provided for context ssb.append(mText.subSequence(unionStart, spanStart).toString()); // shift is used to offset spans positions wrt span's beginning final int shift = spanStart - unionStart; suggestionInfo.suggestionStart = shift; suggestionInfo.suggestionEnd = shift + text.length(); // This is the actual suggestion text, which will be highlighted by the following code ssb.append(text); String[] words = new String[wordLimits.length]; for (int i = 0; i < wordLimits.length; i++) { int wordStart = extractRangeStartFromLong(wordLimits[i]); int wordEnd = extractRangeEndFromLong(wordLimits[i]); words[i] = text.substring(wordStart, wordEnd); } // Highlighted word algorithm is based on word matching between source and text // Matching words are found from left to right. TODO: change for RTL languages // Characters between matching words are highlighted int previousCommonWordIndex = -1; int nbHighlightSpans = 0; for (int i = 0; i < sourceWordLimits.length; i++) { int wordStart = extractRangeStartFromLong(sourceWordLimits[i]); int wordEnd = extractRangeEndFromLong(sourceWordLimits[i]); String sourceWord = sourceText.substring(wordStart, wordEnd); for (int j = previousCommonWordIndex + 1; j < words.length; j++) { if (sourceWord.equals(words[j])) { if (j != previousCommonWordIndex + 1) { int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 : extractRangeEndFromLong(wordLimits[previousCommonWordIndex]); int lastDifferentPosition = extractRangeStartFromLong(wordLimits[j]); ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + firstDifferentPosition, shift + lastDifferentPosition, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { // Compare characters between words int previousSourceWordEnd = i == 0 ? 0 : extractRangeEndFromLong(sourceWordLimits[i - 1]); int sourceWordStart = extractRangeStartFromLong(sourceWordLimits[i]); String sourceSpaces = sourceText.substring(previousSourceWordEnd, sourceWordStart); int previousWordEnd = j == 0 ? 0 : extractRangeEndFromLong(wordLimits[j - 1]); int currentWordStart = extractRangeStartFromLong(wordLimits[j]); String textSpaces = text.substring(previousWordEnd, currentWordStart); if (!sourceSpaces.equals(textSpaces)) { ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + previousWordEnd, shift + currentWordStart, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } previousCommonWordIndex = j; break; } } } // Finally, compare ends of Strings if (previousCommonWordIndex < words.length - 1) { int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 : extractRangeEndFromLong(wordLimits[previousCommonWordIndex]); int lastDifferentPosition = text.length(); ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + firstDifferentPosition, shift + lastDifferentPosition, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { int lastSourceWordEnd = sourceWordLimits.length == 0 ? 0 : extractRangeEndFromLong(sourceWordLimits[sourceWordLimits.length - 1]); String sourceSpaces = sourceText.substring(lastSourceWordEnd, sourceText.length()); // Adjust the start/end of the suggestion span suggestionInfo.suggestionStart = spanStart - unionStart; suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart + suggestionInfo.text.length(); int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 : extractRangeEndFromLong(wordLimits[previousCommonWordIndex]); String textSpaces = text.substring(lastCommonTextWordEnd, text.length()); if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) { ssb.setSpan(highlightSpan(nbHighlightSpans++), shift + lastCommonTextWordEnd, shift + text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } suggestionInfo.text.clearSpans(); suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Final part, text after the current suggestion range. ssb.append(mText.subSequence(spanEnd, unionEnd).toString()); // Add the text before and after the span. suggestionInfo.text.insert(0, mText.subSequence(unionStart, spanStart).toString()); suggestionInfo.text.append(mText.subSequence(spanEnd, unionEnd).toString()); } @Override Loading