diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/suggestions')
4 files changed, 693 insertions, 593 deletions
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index 09f81d4c7..acd47450b 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -24,14 +24,13 @@ import android.graphics.drawable.Drawable; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.TypefaceUtils; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.Utils; +import com.android.inputmethod.latin.utils.TypefaceUtils; public final class MoreSuggestions extends Keyboard { public static final int SUGGESTION_CODE_BASE = 1024; @@ -61,7 +60,7 @@ public final class MoreSuggestions extends Keyboard { super(); } - public int layout(final SuggestedWords suggestedWords, final int fromPos, + public int layout(final SuggestedWords suggestedWords, final int fromIndex, final int maxWidth, final int minWidth, final int maxRow, final Paint paint, final Resources res) { clearKeys(); @@ -70,53 +69,54 @@ public final class MoreSuggestions extends Keyboard { final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding); int row = 0; - int pos = fromPos, rowStartPos = fromPos; + int index = fromIndex; + int rowStartIndex = fromIndex; final int size = Math.min(suggestedWords.size(), SuggestionStripView.MAX_SUGGESTIONS); - while (pos < size) { - final String word = suggestedWords.getWord(pos); + while (index < size) { + final String word = suggestedWords.getWord(index); // TODO: Should take care of text x-scaling. - mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding); - final int numColumn = pos - rowStartPos + 1; + mWidths[index] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding); + final int numColumn = index - rowStartIndex + 1; final int columnWidth = (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn; if (numColumn > MAX_COLUMNS_IN_ROW - || !fitInWidth(rowStartPos, pos + 1, columnWidth)) { + || !fitInWidth(rowStartIndex, index + 1, columnWidth)) { if ((row + 1) >= maxRow) { break; } - mNumColumnsInRow[row] = pos - rowStartPos; - rowStartPos = pos; + mNumColumnsInRow[row] = index - rowStartIndex; + rowStartIndex = index; row++; } - mColumnOrders[pos] = pos - rowStartPos; - mRowNumbers[pos] = row; - pos++; + mColumnOrders[index] = index - rowStartIndex; + mRowNumbers[index] = row; + index++; } - mNumColumnsInRow[row] = pos - rowStartPos; + mNumColumnsInRow[row] = index - rowStartIndex; mNumRows = row + 1; mBaseWidth = mOccupiedWidth = Math.max( - minWidth, calcurateMaxRowWidth(fromPos, pos)); + minWidth, calcurateMaxRowWidth(fromIndex, index)); mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; - return pos - fromPos; + return index - fromIndex; } - private boolean fitInWidth(final int startPos, final int endPos, final int width) { - for (int pos = startPos; pos < endPos; pos++) { - if (mWidths[pos] > width) + private boolean fitInWidth(final int startIndex, final int endIndex, final int width) { + for (int index = startIndex; index < endIndex; index++) { + if (mWidths[index] > width) return false; } return true; } - private int calcurateMaxRowWidth(final int startPos, final int endPos) { + private int calcurateMaxRowWidth(final int startIndex, final int endIndex) { int maxRowWidth = 0; - int pos = startPos; + int index = startIndex; for (int row = 0; row < mNumRows; row++) { final int numColumnInRow = mNumColumnsInRow[row]; int maxKeyWidth = 0; - while (pos < endPos && mRowNumbers[pos] == row) { - maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]); - pos++; + while (index < endIndex && mRowNumbers[index] == row) { + maxKeyWidth = Math.max(maxKeyWidth, mWidths[index]); + index++; } maxRowWidth = Math.max(maxRowWidth, maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1)); @@ -130,40 +130,40 @@ public final class MoreSuggestions extends Keyboard { { 2, 0, 1}, }; - public int getNumColumnInRow(final int pos) { - return mNumColumnsInRow[mRowNumbers[pos]]; + public int getNumColumnInRow(final int index) { + return mNumColumnsInRow[mRowNumbers[index]]; } - public int getColumnNumber(final int pos) { - final int columnOrder = mColumnOrders[pos]; - final int numColumn = getNumColumnInRow(pos); + public int getColumnNumber(final int index) { + final int columnOrder = mColumnOrders[index]; + final int numColumn = getNumColumnInRow(index); return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; } - public int getX(final int pos) { - final int columnNumber = getColumnNumber(pos); - return columnNumber * (getWidth(pos) + mDividerWidth); + public int getX(final int index) { + final int columnNumber = getColumnNumber(index); + return columnNumber * (getWidth(index) + mDividerWidth); } - public int getY(final int pos) { - final int row = mRowNumbers[pos]; + public int getY(final int index) { + final int row = mRowNumbers[index]; return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; } - public int getWidth(final int pos) { - final int numColumnInRow = getNumColumnInRow(pos); + public int getWidth(final int index) { + final int numColumnInRow = getNumColumnInRow(index); return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; } - public void markAsEdgeKey(final Key key, final int pos) { - final int row = mRowNumbers[pos]; + public void markAsEdgeKey(final Key key, final int index) { + final int row = mRowNumbers[index]; if (row == 0) key.markAsBottomEdge(this); if (row == mNumRows - 1) key.markAsTopEdge(this); final int numColumnInRow = mNumColumnsInRow[row]; - final int column = getColumnNumber(pos); + final int column = getColumnNumber(index); if (column == 0) key.markAsLeftEdge(this); if (column == numColumnInRow - 1) @@ -174,15 +174,15 @@ public final class MoreSuggestions extends Keyboard { public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> { private final MoreSuggestionsView mPaneView; private SuggestedWords mSuggestedWords; - private int mFromPos; - private int mToPos; + private int mFromIndex; + private int mToIndex; public Builder(final Context context, final MoreSuggestionsView paneView) { super(context, new MoreSuggestionsParam()); mPaneView = paneView; } - public Builder layout(final SuggestedWords suggestedWords, final int fromPos, + public Builder layout(final SuggestedWords suggestedWords, final int fromIndex, final int maxWidth, final int minWidth, final int maxRow, final Keyboard parentKeyboard) { final int xmlId = R.xml.kbd_suggestions_pane_template; @@ -190,10 +190,10 @@ public final class MoreSuggestions extends Keyboard { mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight); - final int count = mParams.layout(suggestedWords, fromPos, maxWidth, minWidth, maxRow, + final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow, mPaneView.newLabelPaint(null /* key */), mResources); - mFromPos = fromPos; - mToPos = fromPos + count; + mFromIndex = fromIndex; + mToIndex = fromIndex + count; mSuggestedWords = suggestedWords; return this; } @@ -201,20 +201,21 @@ public final class MoreSuggestions extends Keyboard { @Override public MoreSuggestions build() { final MoreSuggestionsParam params = mParams; - for (int pos = mFromPos; pos < mToPos; pos++) { - final int x = params.getX(pos); - final int y = params.getY(pos); - final int width = params.getWidth(pos); - final String word = mSuggestedWords.getWord(pos); - final String info = Utils.getDebugInfo(mSuggestedWords, pos); - final int index = pos + SUGGESTION_CODE_BASE; + for (int index = mFromIndex; index < mToIndex; index++) { + final int x = params.getX(index); + final int y = params.getY(index); + final int width = params.getWidth(index); + final String word = mSuggestedWords.getWord(index); + final String info = mSuggestedWords.getDebugString(index); + final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE; final Key key = new Key( - params, word, info, KeyboardIconsSet.ICON_UNDEFINED, index, null, x, y, - width, params.mDefaultRowHeight, 0); - params.markAsEdgeKey(key, pos); + params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions, + null /* outputText */, x, y, width, params.mDefaultRowHeight, + 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL); + params.markAsEdgeKey(key, index); params.onAddKey(key); - final int columnNumber = params.getColumnNumber(pos); - final int numColumnInRow = params.getNumColumnInRow(pos); + final int columnNumber = params.getColumnNumber(index); + final int numColumnInRow = params.getNumColumnInRow(index); if (columnNumber < numColumnInRow - 1) { final Divider divider = new Divider(params, params.mDivider, x + width, y, params.mDividerWidth, params.mDefaultRowHeight); diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java index d585b5c7f..0ebe37782 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -34,7 +34,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView { private static final String TAG = MoreSuggestionsView.class.getSimpleName(); public MoreSuggestionsView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.moreSuggestionsViewStyle); + this(context, attrs, R.attr.moreKeysKeyboardViewStyle); } public MoreSuggestionsView(final Context context, final AttributeSet attrs, diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java new file mode 100644 index 000000000..8d2689a7d --- /dev/null +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.suggestions; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.CharacterStyle; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.utils.AutoCorrectionUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; +import com.android.inputmethod.latin.utils.ViewLayoutUtils; + +import java.util.ArrayList; + +final class SuggestionStripLayoutHelper { + private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; + private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; + private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; + private static final int PUNCTUATIONS_IN_STRIP = 5; + private static final float MIN_TEXT_XSCALE = 0.70f; + + public final int mPadding; + public final int mDividerWidth; + public final int mSuggestionsStripHeight; + public final int mSuggestionsCountInStrip; + public final int mMoreSuggestionsRowHeight; + private int mMaxMoreSuggestionsRow; + public final float mMinMoreSuggestionsWidth; + public final int mMoreSuggestionsBottomGap; + public boolean mMoreSuggestionsAvailable; + + // The index of these {@link ArrayList} is the position in the suggestion strip. The indices + // increase towards the right for LTR scripts and the left for RTL scripts, starting with 0. + // The position of the most important suggestion is in {@link #mCenterPositionInStrip} + private final ArrayList<TextView> mWordViews; + private final ArrayList<View> mDividerViews; + private final ArrayList<TextView> mDebugInfoViews; + + private final int mColorValidTypedWord; + private final int mColorTypedWord; + private final int mColorAutoCorrect; + private final int mColorSuggested; + private final float mAlphaObsoleted; + private final float mCenterSuggestionWeight; + private final int mCenterPositionInStrip; + private final int mTypedWordPositionWhenAutocorrect; + private final Drawable mMoreSuggestionsHint; + private static final String MORE_SUGGESTIONS_HINT = "\u2026"; + private static final String LEFTWARDS_ARROW = "\u2190"; + + private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); + private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); + + private final int mSuggestionStripOption; + // These constants are the flag values of + // {@link R.styleable#SuggestionStripView_suggestionStripOption} attribute. + private static final int AUTO_CORRECT_BOLD = 0x01; + private static final int AUTO_CORRECT_UNDERLINE = 0x02; + private static final int VALID_TYPED_WORD_BOLD = 0x04; + + private final TextView mWordToSaveView; + private final TextView mLeftwardsArrowView; + private final TextView mHintToSaveView; + + public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs, + final int defStyle, final ArrayList<TextView> wordViews, + final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) { + mWordViews = wordViews; + mDividerViews = dividerViews; + mDebugInfoViews = debugInfoViews; + + final TextView wordView = wordViews.get(0); + final View dividerView = dividerViews.get(0); + mPadding = wordView.getCompoundPaddingLeft() + wordView.getCompoundPaddingRight(); + dividerView.measure( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mDividerWidth = dividerView.getMeasuredWidth(); + + final Resources res = wordView.getResources(); + mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView); + mSuggestionStripOption = a.getInt( + R.styleable.SuggestionStripView_suggestionStripOption, 0); + mAlphaObsoleted = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_alphaObsoleted, 1.0f); + mColorValidTypedWord = a.getColor(R.styleable.SuggestionStripView_colorValidTypedWord, 0); + mColorTypedWord = a.getColor(R.styleable.SuggestionStripView_colorTypedWord, 0); + mColorAutoCorrect = a.getColor(R.styleable.SuggestionStripView_colorAutoCorrect, 0); + mColorSuggested = a.getColor(R.styleable.SuggestionStripView_colorSuggested, 0); + mSuggestionsCountInStrip = a.getInt( + R.styleable.SuggestionStripView_suggestionsCountInStrip, + DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); + mCenterSuggestionWeight = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_centerSuggestionPercentile, + DEFAULT_CENTER_SUGGESTION_PERCENTILE); + mMaxMoreSuggestionsRow = a.getInt( + R.styleable.SuggestionStripView_maxMoreSuggestionsRow, + DEFAULT_MAX_MORE_SUGGESTIONS_ROW); + mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f); + a.recycle(); + + mMoreSuggestionsHint = getMoreSuggestionsHint(res, + res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect); + mCenterPositionInStrip = mSuggestionsCountInStrip / 2; + // Assuming there are at least three suggestions. Also, note that the suggestions are + // laid out according to script direction, so this is left of the center for LTR scripts + // and right of the center for RTL scripts. + mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1; + mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( + R.dimen.more_suggestions_bottom_gap); + mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height); + + final LayoutInflater inflater = LayoutInflater.from(context); + mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); + mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); + mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); + } + + public int getMaxMoreSuggestionsRow() { + return mMaxMoreSuggestionsRow; + } + + private int getMoreSuggestionsHeight() { + return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap; + } + + public int setMoreSuggestionsHeight(final int remainingHeight) { + final int currentHeight = getMoreSuggestionsHeight(); + if (currentHeight <= remainingHeight) { + return currentHeight; + } + + mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap) + / mMoreSuggestionsRowHeight; + final int newHeight = getMoreSuggestionsHeight(); + return newHeight; + } + + private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize, + final int color) { + final Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setTextAlign(Align.CENTER); + paint.setTextSize(textSize); + paint.setColor(color); + final Rect bounds = new Rect(); + paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); + final int width = Math.round(bounds.width() + 0.5f); + final int height = Math.round(bounds.height() + 0.5f); + final Bitmap buffer = Bitmap.createBitmap(width, (height * 3 / 2), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(buffer); + canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); + return new BitmapDrawable(res, buffer); + } + + private CharSequence getStyledSuggestedWord(final SuggestedWords suggestedWords, + final int indexInSuggestedWords) { + if (indexInSuggestedWords >= suggestedWords.size()) { + return null; + } + final String word = suggestedWords.getWord(indexInSuggestedWords); + final boolean isAutoCorrect = indexInSuggestedWords == 1 + && suggestedWords.willAutoCorrect(); + final boolean isTypedWordValid = indexInSuggestedWords == 0 + && suggestedWords.mTypedWordValid; + if (!isAutoCorrect && !isTypedWordValid) { + return word; + } + + final int len = word.length(); + final Spannable spannedWord = new SpannableString(word); + final int option = mSuggestionStripOption; + if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0) + || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) { + spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) { + spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + return spannedWord; + } + + private int getPositionInSuggestionStrip(final int indexInSuggestedWords, + final SuggestedWords suggestedWords) { + final int indexToDisplayMostImportantSuggestion; + final int indexToDisplaySecondMostImportantSuggestion; + if (suggestedWords.willAutoCorrect()) { + indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION; + indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD; + } else { + indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD; + indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION; + } + if (indexInSuggestedWords == indexToDisplayMostImportantSuggestion) { + return mCenterPositionInStrip; + } + if (indexInSuggestedWords == indexToDisplaySecondMostImportantSuggestion) { + return mTypedWordPositionWhenAutocorrect; + } + // If neither of those, the order in the suggestion strip is the same as in SuggestedWords. + return indexInSuggestedWords; + } + + private int getSuggestionTextColor(final int indexInSuggestedWords, + final SuggestedWords suggestedWords) { + final int positionInStrip = + getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords); + // TODO: Need to revisit this logic with bigram suggestions + final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD); + + final int color; + if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) { + color = mColorAutoCorrect; + } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) { + color = mColorValidTypedWord; + } else if (isSuggested) { + color = mColorSuggested; + } else { + color = mColorTypedWord; + } + if (LatinImeLogger.sDBG && suggestedWords.size() > 1) { + // If we auto-correct, then the autocorrection is in slot 0 and the typed word + // is in slot 1. + if (positionInStrip == mCenterPositionInStrip + && AutoCorrectionUtils.shouldBlockAutoCorrectionBySafetyNet( + suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION), + suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD))) { + return 0xFFFF0000; + } + } + + if (suggestedWords.mIsObsoleteSuggestions && isSuggested) { + return applyAlpha(color, mAlphaObsoleted); + } + return color; + } + + private static int applyAlpha(final int color, final float alpha) { + final int newAlpha = (int)(Color.alpha(color) * alpha); + return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + private static void addDivider(final ViewGroup stripView, final View dividerView) { + stripView.addView(dividerView); + final LinearLayout.LayoutParams params = + (LinearLayout.LayoutParams)dividerView.getLayoutParams(); + params.gravity = Gravity.CENTER; + } + + public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView, + final ViewGroup placerView) { + if (suggestedWords.mIsPunctuationSuggestions) { + layoutPunctuationSuggestions(suggestedWords, stripView); + return; + } + + final int countInStrip = mSuggestionsCountInStrip; + setupWordViewsTextAndColor(suggestedWords, countInStrip); + final TextView centerWordView = mWordViews.get(mCenterPositionInStrip); + final int stripWidth = placerView.getWidth(); + final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth); + if (getTextScaleX(centerWordView.getText(), centerWidth, centerWordView.getPaint()) + < MIN_TEXT_XSCALE) { + // Layout only the most relevant suggested word at the center of the suggestion strip + // by consolidating all slots in the strip. + mMoreSuggestionsAvailable = (suggestedWords.size() > 1); + layoutWord(mCenterPositionInStrip, stripWidth); + stripView.addView(centerWordView); + setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT); + if (SuggestionStripView.DBG) { + layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth); + } + return; + } + + mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip); + int x = 0; + for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) { + if (positionInStrip != 0) { + final View divider = mDividerViews.get(positionInStrip); + // Add divider if this isn't the left most suggestion in suggestions strip. + addDivider(stripView, divider); + x += divider.getMeasuredWidth(); + } + + final int width = getSuggestionWidth(positionInStrip, stripWidth); + final TextView wordView = layoutWord(positionInStrip, width); + stripView.addView(wordView); + setLayoutWeight(wordView, getSuggestionWeight(positionInStrip), + ViewGroup.LayoutParams.MATCH_PARENT); + x += wordView.getMeasuredWidth(); + + if (SuggestionStripView.DBG) { + layoutDebugInfo(positionInStrip, placerView, x); + } + } + } + + /** + * Format appropriately the suggested word in {@link #mWordViews} specified by + * <code>positionInStrip</code>. When the suggested word doesn't exist, the corresponding + * {@link TextView} will be disabled and never respond to user interaction. The suggested word + * may be shrunk or ellipsized to fit in the specified width. + * + * The <code>positionInStrip</code> argument is the index in the suggestion strip. The indices + * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0. + * The position of the most important suggestion is in {@link #mCenterPositionInStrip}. This + * usually doesn't match the index in <code>suggedtedWords</code> -- see + * {@link #getPositionInSuggestionStrip(int,SuggestedWords)}. + * + * @param positionInStrip the position in the suggestion strip. + * @param width the maximum width for layout in pixels. + * @return the {@link TextView} containing the suggested word appropriately formatted. + */ + private TextView layoutWord(final int positionInStrip, final int width) { + final TextView wordView = mWordViews.get(positionInStrip); + final CharSequence word = wordView.getText(); + if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) { + // TODO: This "more suggestions hint" should have a nicely designed icon. + wordView.setCompoundDrawablesWithIntrinsicBounds( + null, null, null, mMoreSuggestionsHint); + // HACK: Align with other TextViews that have no compound drawables. + wordView.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); + } else { + wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } + + // Disable this suggestion if the suggestion is null or empty. + wordView.setEnabled(!TextUtils.isEmpty(word)); + final CharSequence text = getEllipsizedText(word, width, wordView.getPaint()); + final float scaleX = wordView.getTextScaleX(); + wordView.setText(text); // TextView.setText() resets text scale x to 1.0. + wordView.setTextScaleX(scaleX); + return wordView; + } + + private void layoutDebugInfo(final int positionInStrip, final ViewGroup placerView, + final int x) { + final TextView debugInfoView = mDebugInfoViews.get(positionInStrip); + final CharSequence debugInfo = debugInfoView.getText(); + if (debugInfo == null) { + return; + } + placerView.addView(debugInfoView); + debugInfoView.measure( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final int infoWidth = debugInfoView.getMeasuredWidth(); + final int y = debugInfoView.getMeasuredHeight(); + ViewLayoutUtils.placeViewAt( + debugInfoView, x - infoWidth, y, infoWidth, debugInfoView.getMeasuredHeight()); + } + + private int getSuggestionWidth(final int positionInStrip, final int maxWidth) { + final int paddings = mPadding * mSuggestionsCountInStrip; + final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); + final int availableWidth = maxWidth - paddings - dividers; + return (int)(availableWidth * getSuggestionWeight(positionInStrip)); + } + + private float getSuggestionWeight(final int positionInStrip) { + if (positionInStrip == mCenterPositionInStrip) { + return mCenterSuggestionWeight; + } + // TODO: Revisit this for cases of 5 or more suggestions + return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1); + } + + private void setupWordViewsTextAndColor(final SuggestedWords suggestedWords, + final int countInStrip) { + // Clear all suggestions first + for (int positionInStrip = 0; positionInStrip < countInStrip; ++positionInStrip) { + mWordViews.get(positionInStrip).setText(null); + // Make this inactive for touches in {@link #layoutWord(int,int)}. + if (SuggestionStripView.DBG) { + mDebugInfoViews.get(positionInStrip).setText(null); + } + } + final int count = Math.min(suggestedWords.size(), countInStrip); + for (int indexInSuggestedWords = 0; indexInSuggestedWords < count; + indexInSuggestedWords++) { + final int positionInStrip = + getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords); + final TextView wordView = mWordViews.get(positionInStrip); + // {@link TextView#getTag()} is used to get the index in suggestedWords at + // {@link SuggestionStripView#onClick(View)}. + wordView.setTag(indexInSuggestedWords); + wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords)); + wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords)); + if (SuggestionStripView.DBG) { + mDebugInfoViews.get(positionInStrip).setText( + suggestedWords.getDebugString(indexInSuggestedWords)); + } + } + } + + private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords, + final ViewGroup stripView) { + final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP); + for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) { + if (positionInStrip != 0) { + // Add divider if this isn't the left most suggestion in suggestions strip. + addDivider(stripView, mDividerViews.get(positionInStrip)); + } + + final TextView wordView = mWordViews.get(positionInStrip); + wordView.setEnabled(true); + wordView.setTextColor(mColorAutoCorrect); + // {@link TextView#getTag()} is used to get the index in suggestedWords at + // {@link SuggestionStripView#onClick(View)}. + wordView.setTag(positionInStrip); + wordView.setText(suggestedWords.getWord(positionInStrip)); + wordView.setTextScaleX(1.0f); + wordView.setCompoundDrawables(null, null, null, null); + stripView.addView(wordView); + setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight); + } + mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip); + } + + public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView, + final int stripWidth, final CharSequence hintText, final OnClickListener listener) { + final int width = stripWidth - mDividerWidth - mPadding * 2; + + final TextView wordView = mWordToSaveView; + wordView.setTextColor(mColorTypedWord); + final int wordWidth = (int)(width * mCenterSuggestionWeight); + final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint()); + final float wordScaleX = wordView.getTextScaleX(); + // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word + // will be extracted at {@link #getAddToDictionaryWord()}. + wordView.setTag(word); + wordView.setText(text); + wordView.setTextScaleX(wordScaleX); + stripView.addView(wordView); + setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); + + stripView.addView(mDividerViews.get(0)); + + final TextView leftArrowView = mLeftwardsArrowView; + leftArrowView.setTextColor(mColorAutoCorrect); + leftArrowView.setText(LEFTWARDS_ARROW); + stripView.addView(leftArrowView); + + final TextView hintView = mHintToSaveView; + hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + hintView.setTextColor(mColorAutoCorrect); + final int hintWidth = width - wordWidth - leftArrowView.getWidth(); + final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); + hintView.setText(hintText); + hintView.setTextScaleX(hintScaleX); + stripView.addView(hintView); + setLayoutWeight( + hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); + + wordView.setOnClickListener(listener); + leftArrowView.setOnClickListener(listener); + hintView.setOnClickListener(listener); + } + + public String getAddToDictionaryWord() { + // String tag is set at + // {@link #layoutAddToDictionaryHint(String,ViewGroup,int,CharSequence,OnClickListener}. + return (String)mWordToSaveView.getTag(); + } + + public boolean isAddToDictionaryShowing(final View v) { + return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView; + } + + private static void setLayoutWeight(final View v, final float weight, final int height) { + final ViewGroup.LayoutParams lp = v.getLayoutParams(); + if (lp instanceof LinearLayout.LayoutParams) { + final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; + llp.weight = weight; + llp.width = 0; + llp.height = height; + } + } + + private static float getTextScaleX(final CharSequence text, final int maxWidth, + final TextPaint paint) { + paint.setTextScaleX(1.0f); + final int width = getTextWidth(text, paint); + if (width <= maxWidth) { + return 1.0f; + } + return maxWidth / (float)width; + } + + private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, + final TextPaint paint) { + if (text == null) { + return null; + } + final float scaleX = getTextScaleX(text, maxWidth, paint); + if (scaleX >= MIN_TEXT_XSCALE) { + paint.setTextScaleX(scaleX); + return text; + } + + // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To + // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). + final CharSequence ellipsized = TextUtils.ellipsize( + text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); + paint.setTextScaleX(MIN_TEXT_XSCALE); + return ellipsized; + } + + private static int getTextWidth(final CharSequence text, final TextPaint paint) { + if (TextUtils.isEmpty(text)) { + return 0; + } + final Typeface savedTypeface = paint.getTypeface(); + paint.setTypeface(getTextTypeface(text)); + final int len = text.length(); + final float[] widths = new float[len]; + final int count = paint.getTextWidths(text, 0, len, widths); + int width = 0; + for (int i = 0; i < count; i++) { + width += Math.round(widths[i] + 0.5f); + } + paint.setTypeface(savedTypeface); + return width; + } + + private static Typeface getTextTypeface(final CharSequence text) { + if (!(text instanceof SpannableString)) { + return Typeface.DEFAULT; + } + + final SpannableString ss = (SpannableString)text; + final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class); + if (styles.length == 0) { + return Typeface.DEFAULT; + } + + if (styles[0].getStyle() == Typeface.BOLD) { + return Typeface.DEFAULT_BOLD; + } + // TODO: BOLD_ITALIC, ITALIC case? + return Typeface.DEFAULT; + } +} diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index ad350a02f..75f17c559 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -18,34 +18,14 @@ package com.android.inputmethod.latin.suggestions; import android.content.Context; import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.text.style.StyleSpan; -import android.text.style.UnderlineSpan; import android.util.AttributeSet; import android.view.GestureDetector; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; -import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -53,18 +33,15 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.ViewLayoutUtils; -import com.android.inputmethod.latin.AutoCorrection; -import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener; +import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; @@ -88,477 +65,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final MoreSuggestionsView mMoreSuggestionsView; private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - private final ArrayList<TextView> mWords = CollectionUtils.newArrayList(); - private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList(); - private final ArrayList<View> mDividers = CollectionUtils.newArrayList(); + private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList(); + private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList(); + private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList(); Listener mListener; private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - private final SuggestionStripViewParams mParams; - private static final float MIN_TEXT_XSCALE = 0.70f; - - private static final class SuggestionStripViewParams { - private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; - private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; - private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; - private static final int PUNCTUATIONS_IN_STRIP = 5; - - public final int mPadding; - public final int mDividerWidth; - public final int mSuggestionsStripHeight; - public final int mSuggestionsCountInStrip; - public final int mMoreSuggestionsRowHeight; - private int mMaxMoreSuggestionsRow; - public final float mMinMoreSuggestionsWidth; - public final int mMoreSuggestionsBottomGap; - - private final ArrayList<TextView> mWords; - private final ArrayList<View> mDividers; - private final ArrayList<TextView> mInfos; - - private final int mColorValidTypedWord; - private final int mColorTypedWord; - private final int mColorAutoCorrect; - private final int mColorSuggested; - private final float mAlphaObsoleted; - private final float mCenterSuggestionWeight; - private final int mCenterSuggestionIndex; - private final Drawable mMoreSuggestionsHint; - private static final String MORE_SUGGESTIONS_HINT = "\u2026"; - private static final String LEFTWARDS_ARROW = "\u2190"; - - private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); - private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - private static final int AUTO_CORRECT_BOLD = 0x01; - private static final int AUTO_CORRECT_UNDERLINE = 0x02; - private static final int VALID_TYPED_WORD_BOLD = 0x04; - - private final int mSuggestionStripOption; - - private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList(); - - public boolean mMoreSuggestionsAvailable; - - private final TextView mWordToSaveView; - private final TextView mLeftwardsArrowView; - private final TextView mHintToSaveView; - - public SuggestionStripViewParams(final Context context, final AttributeSet attrs, - final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers, - final ArrayList<TextView> infos) { - mWords = words; - mDividers = dividers; - mInfos = infos; - - final TextView word = words.get(0); - final View divider = dividers.get(0); - mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight(); - divider.measure( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - mDividerWidth = divider.getMeasuredWidth(); - - final Resources res = word.getResources(); - mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle); - mSuggestionStripOption = a.getInt( - R.styleable.SuggestionStripView_suggestionStripOption, 0); - final float alphaValidTypedWord = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f); - final float alphaTypedWord = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaTypedWord, 1.0f); - final float alphaAutoCorrect = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f); - final float alphaSuggested = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaSuggested, 1.0f); - mAlphaObsoleted = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaSuggested, 1.0f); - mColorValidTypedWord = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord); - mColorTypedWord = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord); - mColorAutoCorrect = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect); - mColorSuggested = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested); - mSuggestionsCountInStrip = a.getInt( - R.styleable.SuggestionStripView_suggestionsCountInStrip, - DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); - mCenterSuggestionWeight = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_centerSuggestionPercentile, - DEFAULT_CENTER_SUGGESTION_PERCENTILE); - mMaxMoreSuggestionsRow = a.getInt( - R.styleable.SuggestionStripView_maxMoreSuggestionsRow, - DEFAULT_MAX_MORE_SUGGESTIONS_ROW); - mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f); - a.recycle(); - - mMoreSuggestionsHint = getMoreSuggestionsHint(res, - res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect); - mCenterSuggestionIndex = mSuggestionsCountInStrip / 2; - mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( - R.dimen.more_suggestions_bottom_gap); - mMoreSuggestionsRowHeight = res.getDimensionPixelSize( - R.dimen.more_suggestions_row_height); - - final LayoutInflater inflater = LayoutInflater.from(context); - mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); - mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); - mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); - } - - public int getMaxMoreSuggestionsRow() { - return mMaxMoreSuggestionsRow; - } - - private int getMoreSuggestionsHeight() { - return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap; - } - - public int setMoreSuggestionsHeight(final int remainingHeight) { - final int currentHeight = getMoreSuggestionsHeight(); - if (currentHeight <= remainingHeight) { - return currentHeight; - } - - mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap) - / mMoreSuggestionsRowHeight; - final int newHeight = getMoreSuggestionsHeight(); - return newHeight; - } - - private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize, - final int color) { - final Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - paint.setTextSize(textSize); - paint.setColor(color); - final Rect bounds = new Rect(); - paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); - final int width = Math.round(bounds.width() + 0.5f); - final int height = Math.round(bounds.height() + 0.5f); - final Bitmap buffer = Bitmap.createBitmap( - width, (height * 3 / 2), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); - return new BitmapDrawable(res, buffer); - } - - private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords, - final int pos) { - final String word = suggestedWords.getWord(pos); - final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect(); - final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid; - if (!isAutoCorrect && !isTypedWordValid) - return word; - - final int len = word.length(); - final Spannable spannedWord = new SpannableString(word); - final int option = mSuggestionStripOption; - if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0) - || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) { - spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) { - spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - return spannedWord; - } - - private int getWordPosition(final int index, final SuggestedWords suggestedWords) { - // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more - // suggestions. - final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0; - if (index == mCenterSuggestionIndex) { - return centerPos; - } else if (index == centerPos) { - return mCenterSuggestionIndex; - } else { - return index; - } - } - - private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords, - final int pos) { - // TODO: Need to revisit this logic with bigram suggestions - final boolean isSuggested = (pos != 0); - - final int color; - if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) { - color = mColorAutoCorrect; - } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) { - color = mColorValidTypedWord; - } else if (isSuggested) { - color = mColorSuggested; - } else { - color = mColorTypedWord; - } - if (LatinImeLogger.sDBG && suggestedWords.size() > 1) { - // If we auto-correct, then the autocorrection is in slot 0 and the typed word - // is in slot 1. - if (index == mCenterSuggestionIndex - && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet( - suggestedWords.getWord(1), suggestedWords.getWord(0))) { - return 0xFFFF0000; - } - } - - if (suggestedWords.mIsObsoleteSuggestions && isSuggested) { - return applyAlpha(color, mAlphaObsoleted); - } else { - return color; - } - } - - private static int applyAlpha(final int color, final float alpha) { - final int newAlpha = (int)(Color.alpha(color) * alpha); - return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); - } - - private static void addDivider(final ViewGroup stripView, final View divider) { - stripView.addView(divider); - final LinearLayout.LayoutParams params = - (LinearLayout.LayoutParams)divider.getLayoutParams(); - params.gravity = Gravity.CENTER; - } - - public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView, - final ViewGroup placer, final int stripWidth) { - if (suggestedWords.mIsPunctuationSuggestions) { - layoutPunctuationSuggestions(suggestedWords, stripView); - return; - } - - final int countInStrip = mSuggestionsCountInStrip; - setupTexts(suggestedWords, countInStrip); - mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip); - int x = 0; - for (int index = 0; index < countInStrip; index++) { - final int pos = getWordPosition(index, suggestedWords); - - if (index != 0) { - final View divider = mDividers.get(pos); - // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, divider); - x += divider.getMeasuredWidth(); - } - - final CharSequence styled = mTexts.get(pos); - final TextView word = mWords.get(pos); - if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) { - // TODO: This "more suggestions hint" should have nicely designed icon. - word.setCompoundDrawablesWithIntrinsicBounds( - null, null, null, mMoreSuggestionsHint); - // HACK: To align with other TextView that has no compound drawables. - word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); - } else { - word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - } - - // Disable this suggestion if the suggestion is null or empty. - word.setEnabled(!TextUtils.isEmpty(styled)); - word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos)); - final int width = getSuggestionWidth(index, stripWidth); - final CharSequence text = getEllipsizedText(styled, width, word.getPaint()); - final float scaleX = word.getTextScaleX(); - word.setText(text); // TextView.setText() resets text scale x to 1.0. - word.setTextScaleX(scaleX); - stripView.addView(word); - setLayoutWeight( - word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT); - x += word.getMeasuredWidth(); - - if (DBG && pos < suggestedWords.size()) { - final String debugInfo = Utils.getDebugInfo(suggestedWords, pos); - if (debugInfo != null) { - final TextView info = mInfos.get(pos); - info.setText(debugInfo); - placer.addView(info); - info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - final int infoWidth = info.getMeasuredWidth(); - final int y = info.getMeasuredHeight(); - ViewLayoutUtils.placeViewAt( - info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); - } - } - } - } - - private int getSuggestionWidth(final int index, final int maxWidth) { - final int paddings = mPadding * mSuggestionsCountInStrip; - final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); - final int availableWidth = maxWidth - paddings - dividers; - return (int)(availableWidth * getSuggestionWeight(index)); - } - - private float getSuggestionWeight(final int index) { - if (index == mCenterSuggestionIndex) { - return mCenterSuggestionWeight; - } else { - // TODO: Revisit this for cases of 5 or more suggestions - return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1); - } - } - - private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) { - mTexts.clear(); - final int count = Math.min(suggestedWords.size(), countInStrip); - for (int pos = 0; pos < count; pos++) { - final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos); - mTexts.add(styled); - } - for (int pos = count; pos < countInStrip; pos++) { - // Make this inactive for touches in layout(). - mTexts.add(null); - } - } - - private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords, - final ViewGroup stripView) { - final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP); - for (int index = 0; index < countInStrip; index++) { - if (index != 0) { - // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, mDividers.get(index)); - } - - final TextView word = mWords.get(index); - word.setEnabled(true); - word.setTextColor(mColorAutoCorrect); - final String text = suggestedWords.getWord(index); - word.setText(text); - word.setTextScaleX(1.0f); - word.setCompoundDrawables(null, null, null, null); - stripView.addView(word); - setLayoutWeight(word, 1.0f, mSuggestionsStripHeight); - } - mMoreSuggestionsAvailable = false; - } - - public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView, - final int stripWidth, final CharSequence hintText, final OnClickListener listener) { - final int width = stripWidth - mDividerWidth - mPadding * 2; - - final TextView wordView = mWordToSaveView; - wordView.setTextColor(mColorTypedWord); - final int wordWidth = (int)(width * mCenterSuggestionWeight); - final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint()); - final float wordScaleX = wordView.getTextScaleX(); - wordView.setTag(word); - wordView.setText(text); - wordView.setTextScaleX(wordScaleX); - stripView.addView(wordView); - setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - - stripView.addView(mDividers.get(0)); - - final TextView leftArrowView = mLeftwardsArrowView; - leftArrowView.setTextColor(mColorAutoCorrect); - leftArrowView.setText(LEFTWARDS_ARROW); - stripView.addView(leftArrowView); - - final TextView hintView = mHintToSaveView; - hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); - hintView.setTextColor(mColorAutoCorrect); - final int hintWidth = width - wordWidth - leftArrowView.getWidth(); - final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); - hintView.setText(hintText); - hintView.setTextScaleX(hintScaleX); - stripView.addView(hintView); - setLayoutWeight( - hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - - wordView.setOnClickListener(listener); - leftArrowView.setOnClickListener(listener); - hintView.setOnClickListener(listener); - } - - public CharSequence getAddToDictionaryWord() { - return (CharSequence)mWordToSaveView.getTag(); - } - - public boolean isAddToDictionaryShowing(final View v) { - return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView; - } - - private static void setLayoutWeight(final View v, final float weight, final int height) { - final ViewGroup.LayoutParams lp = v.getLayoutParams(); - if (lp instanceof LinearLayout.LayoutParams) { - final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; - llp.weight = weight; - llp.width = 0; - llp.height = height; - } - } - - private static float getTextScaleX(final CharSequence text, final int maxWidth, - final TextPaint paint) { - paint.setTextScaleX(1.0f); - final int width = getTextWidth(text, paint); - if (width <= maxWidth) { - return 1.0f; - } - return maxWidth / (float)width; - } - - private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, - final TextPaint paint) { - if (text == null) return null; - paint.setTextScaleX(1.0f); - final int width = getTextWidth(text, paint); - if (width <= maxWidth) { - return text; - } - final float scaleX = maxWidth / (float)width; - if (scaleX >= MIN_TEXT_XSCALE) { - paint.setTextScaleX(scaleX); - return text; - } - - // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To - // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). - final CharSequence ellipsized = TextUtils.ellipsize( - text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); - paint.setTextScaleX(MIN_TEXT_XSCALE); - return ellipsized; - } - - private static int getTextWidth(final CharSequence text, final TextPaint paint) { - if (TextUtils.isEmpty(text)) return 0; - final Typeface savedTypeface = paint.getTypeface(); - paint.setTypeface(getTextTypeface(text)); - final int len = text.length(); - final float[] widths = new float[len]; - final int count = paint.getTextWidths(text, 0, len, widths); - int width = 0; - for (int i = 0; i < count; i++) { - width += Math.round(widths[i] + 0.5f); - } - paint.setTypeface(savedTypeface); - return width; - } - - private static Typeface getTextTypeface(final CharSequence text) { - if (!(text instanceof SpannableString)) - return Typeface.DEFAULT; - - final SpannableString ss = (SpannableString)text; - final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class); - if (styles.length == 0) - return Typeface.DEFAULT; - - switch (styles[0].getStyle()) { - case Typeface.BOLD: return Typeface.DEFAULT_BOLD; - // TODO: BOLD_ITALIC, ITALIC case? - default: return Typeface.DEFAULT; - } - } - } + private final SuggestionStripLayoutHelper mLayoutHelper; /** * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user. @@ -579,19 +93,17 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) { final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null); - word.setTag(pos); word.setOnClickListener(this); word.setOnLongClickListener(this); - mWords.add(word); + mWordViews.add(word); final View divider = inflater.inflate(R.layout.suggestion_divider, null); - divider.setTag(pos); divider.setOnClickListener(this); - mDividers.add(divider); - mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); + mDividerViews.add(divider); + mDebugInfoViews.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); } - mParams = new SuggestionStripViewParams( - context, attrs, defStyle, mWords, mDividers, mInfos); + mLayoutHelper = new SuggestionStripLayoutHelper( + context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews); mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer @@ -617,24 +129,25 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick public void setSuggestions(final SuggestedWords suggestedWords) { clear(); mSuggestedWords = suggestedWords; - mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth()); + mLayoutHelper.layout(mSuggestedWords, mSuggestionsStrip, this); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords); } } public int setMoreSuggestionsHeight(final int remainingHeight) { - return mParams.setMoreSuggestionsHeight(remainingHeight); + return mLayoutHelper.setMoreSuggestionsHeight(remainingHeight); } public boolean isShowingAddToDictionaryHint() { return mSuggestionsStrip.getChildCount() > 0 - && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); + && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); } public void showAddToDictionaryHint(final String word, final CharSequence hintText) { clear(); - mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this); + mLayoutHelper.layoutAddToDictionaryHint( + word, mSuggestionsStrip, getWidth(), hintText, this); } public boolean dismissAddToDictionaryHint() { @@ -649,27 +162,27 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick mSuggestionsStrip.removeAllViews(); removeAllViews(); addView(mSuggestionsStrip); - dismissMoreSuggestions(); + mMoreSuggestionsView.dismissMoreKeysPanel(); } private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() { @Override public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) { mListener.pickSuggestionManually(index, wordInfo); - dismissMoreSuggestions(); + mMoreSuggestionsView.dismissMoreKeysPanel(); } @Override public void onCancelInput() { - dismissMoreSuggestions(); + mMoreSuggestionsView.dismissMoreKeysPanel(); } }; private final MoreKeysPanel.Controller mMoreSuggestionsController = new MoreKeysPanel.Controller() { @Override - public boolean onDismissMoreKeysPanel() { - return mMainKeyboardView.onDismissMoreKeysPanel(); + public void onDismissMoreKeysPanel(final MoreKeysPanel panel) { + mMainKeyboardView.onDismissMoreKeysPanel(panel); } @Override @@ -678,18 +191,15 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } @Override - public void onCancelMoreKeysPanel() { - dismissMoreSuggestions(); + public void onCancelMoreKeysPanel(final MoreKeysPanel panel) { + mMoreSuggestionsView.dismissMoreKeysPanel(); } }; - boolean dismissMoreSuggestions() { - return mMoreSuggestionsView.dismissMoreKeysPanel(); - } - @Override public boolean onLongClick(final View view) { - KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE); + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( + Constants.NOT_A_CODE, this); return showMoreSuggestions(); } @@ -698,30 +208,30 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (parentKeyboard == null) { return false; } - final SuggestionStripViewParams params = mParams; - if (!params.mMoreSuggestionsAvailable) { + final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper; + if (!layoutHelper.mMoreSuggestionsAvailable) { return false; } final int stripWidth = getWidth(); final View container = mMoreSuggestionsContainer; final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight(); final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth, - (int)(maxWidth * params.mMinMoreSuggestionsWidth), - params.getMaxMoreSuggestionsRow(), parentKeyboard); + builder.layout(mSuggestedWords, layoutHelper.mSuggestionsCountInStrip, maxWidth, + (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth), + layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard); mMoreSuggestionsView.setKeyboard(builder.build()); container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; final int pointX = stripWidth / 2; - final int pointY = -params.mMoreSuggestionsBottomGap; + final int pointY = -layoutHelper.mMoreSuggestionsBottomGap; moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY, mMoreSuggestionsListener); mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; mOriginX = mLastX; mOriginY = mLastY; - for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { - mWords.get(i).setPressed(false); + for (int i = 0; i < layoutHelper.mSuggestionsCountInStrip; i++) { + mWordViews.get(i).setPressed(false); } return true; } @@ -760,15 +270,10 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return super.dispatchTouchEvent(me); } - final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; final int action = me.getAction(); - final long eventTime = me.getEventTime(); final int index = me.getActionIndex(); - final int id = me.getPointerId(index); final int x = (int)me.getX(index); final int y = (int)me.getY(index); - final int translatedX = moreKeysPanel.translateX(x); - final int translatedY = moreKeysPanel.translateY(y); if (mMoreSuggestionsMode == MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING) { if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance @@ -785,24 +290,30 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } // MORE_SUGGESTIONS_IN_SLIDING_MODE - mMoreSuggestionsView.processMotionEvent(action, translatedX, translatedY, id, eventTime); + me.setLocation(mMoreSuggestionsView.translateX(x), mMoreSuggestionsView.translateY(y)); + mMoreSuggestionsView.onTouchEvent(me); return true; } @Override public void onClick(final View view) { - if (mParams.isAddToDictionaryShowing(view)) { - mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString()); + if (mLayoutHelper.isAddToDictionaryShowing(view)) { + mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord()); clear(); return; } final Object tag = view.getTag(); - if (!(tag instanceof Integer)) + // Integer tag is set at + // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and + // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup} + if (!(tag instanceof Integer)) { return; + } final int index = (Integer) tag; - if (index >= mSuggestedWords.size()) + if (index >= mSuggestedWords.size()) { return; + } final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index); mListener.pickSuggestionManually(index, wordInfo); @@ -811,6 +322,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - dismissMoreSuggestions(); + mMoreSuggestionsView.dismissMoreKeysPanel(); } } |