aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java')
-rw-r--r--java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java650
1 files changed, 650 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
new file mode 100644
index 000000000..6e95de414
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -0,0 +1,650 @@
+/*
+ * 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 org.kelar.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.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.kelar.inputmethod.accessibility.AccessibilityUtils;
+import org.kelar.inputmethod.annotations.UsedForTesting;
+import org.kelar.inputmethod.latin.PunctuationSuggestions;
+import org.kelar.inputmethod.latin.R;
+import org.kelar.inputmethod.latin.SuggestedWords;
+import org.kelar.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import org.kelar.inputmethod.latin.settings.Settings;
+import org.kelar.inputmethod.latin.settings.SettingsValues;
+import org.kelar.inputmethod.latin.utils.ResourceUtils;
+import org.kelar.inputmethod.latin.utils.ViewLayoutUtils;
+
+import java.util.ArrayList;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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;
+ private final int mSuggestionsCountInStrip;
+ public final int mMoreSuggestionsRowHeight;
+ private int mMaxMoreSuggestionsRow;
+ public final float mMinMoreSuggestionsWidth;
+ public final int mMoreSuggestionsBottomGap;
+ private 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 CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+ private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
+
+ private final int mSuggestionStripOptions;
+ // These constants are the flag values of
+ // {@link R.styleable#SuggestionStripView_suggestionStripOptions} 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;
+
+ 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.config_suggestions_strip_height);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView);
+ mSuggestionStripOptions = a.getInt(
+ R.styleable.SuggestionStripView_suggestionStripOptions, 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.config_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.config_more_suggestions_bottom_gap);
+ mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
+ R.dimen.config_more_suggestions_row_height);
+ }
+
+ public int getMaxMoreSuggestionsRow() {
+ return mMaxMoreSuggestionsRow;
+ }
+
+ private int getMoreSuggestionsHeight() {
+ return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
+ }
+
+ public void setMoreSuggestionsHeight(final int remainingHeight) {
+ final int currentHeight = getMoreSuggestionsHeight();
+ if (currentHeight <= remainingHeight) {
+ return;
+ }
+
+ mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
+ / mMoreSuggestionsRowHeight;
+ }
+
+ 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);
+ BitmapDrawable bitmapDrawable = new BitmapDrawable(res, buffer);
+ bitmapDrawable.setTargetDensity(canvas);
+ return bitmapDrawable;
+ }
+
+ private CharSequence getStyledSuggestedWord(final SuggestedWords suggestedWords,
+ final int indexInSuggestedWords) {
+ if (indexInSuggestedWords >= suggestedWords.size()) {
+ return null;
+ }
+ final String word = suggestedWords.getLabel(indexInSuggestedWords);
+ // TODO: don't use the index to decide whether this is the auto-correction/typed word, as
+ // this is brittle
+ final boolean isAutoCorrection = suggestedWords.mWillAutoCorrect
+ && indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ final boolean isTypedWordValid = suggestedWords.mTypedWordValid
+ && indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD;
+ if (!isAutoCorrection && !isTypedWordValid) {
+ return word;
+ }
+
+ final Spannable spannedWord = new SpannableString(word);
+ final int options = mSuggestionStripOptions;
+ if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0)
+ || (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) {
+ addStyleSpan(spannedWord, BOLD_SPAN);
+ }
+ if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) {
+ addStyleSpan(spannedWord, UNDERLINE_SPAN);
+ }
+ return spannedWord;
+ }
+
+ /**
+ * Convert an index of {@link SuggestedWords} to position in the suggestion strip.
+ * @param indexInSuggestedWords the index of {@link SuggestedWords}.
+ * @param suggestedWords the suggested words list
+ * @return Non-negative integer of the position in the suggestion strip.
+ * Negative integer if the word of the index shouldn't be shown on the suggestion strip.
+ */
+ private int getPositionInSuggestionStrip(final int indexInSuggestedWords,
+ final SuggestedWords suggestedWords) {
+ final SettingsValues settingsValues = Settings.getInstance().getCurrent();
+ final boolean shouldOmitTypedWord = shouldOmitTypedWord(suggestedWords.mInputStyle,
+ settingsValues.mGestureFloatingPreviewTextEnabled,
+ settingsValues.mShouldShowLxxSuggestionUi);
+ return getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords.mWillAutoCorrect,
+ settingsValues.mShouldShowLxxSuggestionUi && shouldOmitTypedWord,
+ mCenterPositionInStrip, mTypedWordPositionWhenAutocorrect);
+ }
+
+ @UsedForTesting
+ static boolean shouldOmitTypedWord(final int inputStyle,
+ final boolean gestureFloatingPreviewTextEnabled,
+ final boolean shouldShowUiToAcceptTypedWord) {
+ final boolean omitTypedWord = (inputStyle == SuggestedWords.INPUT_STYLE_TYPING)
+ || (inputStyle == SuggestedWords.INPUT_STYLE_TAIL_BATCH)
+ || (inputStyle == SuggestedWords.INPUT_STYLE_UPDATE_BATCH
+ && gestureFloatingPreviewTextEnabled);
+ return shouldShowUiToAcceptTypedWord && omitTypedWord;
+ }
+
+ @UsedForTesting
+ static int getPositionInSuggestionStrip(final int indexInSuggestedWords,
+ final boolean willAutoCorrect, final boolean omitTypedWord,
+ final int centerPositionInStrip, final int typedWordPositionWhenAutoCorrect) {
+ if (omitTypedWord) {
+ if (indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD) {
+ // Ignore.
+ return -1;
+ }
+ if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION) {
+ // Center in the suggestion strip.
+ return centerPositionInStrip;
+ }
+ // If neither of those, the order in the suggestion strip is left of the center first
+ // then right of the center, to both edges of the suggestion strip.
+ // For example, center-1, center+1, center-2, center+2, and so on.
+ final int n = indexInSuggestedWords;
+ final int offsetFromCenter = (n % 2) == 0 ? -(n / 2) : (n / 2);
+ final int positionInSuggestionStrip = centerPositionInStrip + offsetFromCenter;
+ return positionInSuggestionStrip;
+ }
+ final int indexToDisplayMostImportantSuggestion;
+ final int indexToDisplaySecondMostImportantSuggestion;
+ if (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) {
+ // Center in the suggestion strip.
+ return centerPositionInStrip;
+ }
+ if (indexInSuggestedWords == indexToDisplaySecondMostImportantSuggestion) {
+ // Center-1.
+ return typedWordPositionWhenAutoCorrect;
+ }
+ // If neither of those, the order in the suggestion strip is right of the center first
+ // then left of the center, to both edges of the suggestion strip.
+ // For example, Center+1, center-2, center+2, center-3, and so on.
+ final int n = indexInSuggestedWords + 1;
+ final int offsetFromCenter = (n % 2) == 0 ? -(n / 2) : (n / 2);
+ final int positionInSuggestionStrip = centerPositionInStrip + offsetFromCenter;
+ return positionInSuggestionStrip;
+ }
+
+ private int getSuggestionTextColor(final SuggestedWords suggestedWords,
+ final int indexInSuggestedWords) {
+ // Use identity for strings, not #equals : it's the typed word if it's the same object
+ final boolean isTypedWord = suggestedWords.getInfo(indexInSuggestedWords).isKindOf(
+ SuggestedWordInfo.KIND_TYPED);
+
+ final int color;
+ if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION
+ && suggestedWords.mWillAutoCorrect) {
+ color = mColorAutoCorrect;
+ } else if (isTypedWord && suggestedWords.mTypedWordValid) {
+ color = mColorValidTypedWord;
+ } else if (isTypedWord) {
+ color = mColorTypedWord;
+ } else {
+ color = mColorSuggested;
+ }
+ if (suggestedWords.mIsObsoleteSuggestions && !isTypedWord) {
+ 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;
+ }
+
+ /**
+ * Layout suggestions to the suggestions strip. And returns the start index of more
+ * suggestions.
+ *
+ * @param suggestedWords suggestions to be shown in the suggestions strip.
+ * @param stripView the suggestions strip view.
+ * @param placerView the view where the debug info will be placed.
+ * @return the start index of more suggestions.
+ */
+ public int layoutAndReturnStartIndexOfMoreSuggestions(
+ final Context context,
+ final SuggestedWords suggestedWords,
+ final ViewGroup stripView,
+ final ViewGroup placerView) {
+ if (suggestedWords.isPunctuationSuggestions()) {
+ return layoutPunctuationsAndReturnStartIndexOfMoreSuggestions(
+ (PunctuationSuggestions)suggestedWords, stripView);
+ }
+
+ final int wordCountToShow = suggestedWords.getWordCountToShow(
+ Settings.getInstance().getCurrent().mShouldShowLxxSuggestionUi);
+ final int startIndexOfMoreSuggestions = setupWordViewsAndReturnStartIndexOfMoreSuggestions(
+ suggestedWords, mSuggestionsCountInStrip);
+ final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
+ final int stripWidth = stripView.getWidth();
+ final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
+ if (wordCountToShow == 1 || 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.
+ final int countInStrip = 1;
+ mMoreSuggestionsAvailable = (wordCountToShow > countInStrip);
+ layoutWord(context, mCenterPositionInStrip, stripWidth - mPadding);
+ stripView.addView(centerWordView);
+ setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
+ if (SuggestionStripView.DBG) {
+ layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth);
+ }
+ final Integer lastIndex = (Integer)centerWordView.getTag();
+ return (lastIndex == null ? 0 : lastIndex) + 1;
+ }
+
+ final int countInStrip = mSuggestionsCountInStrip;
+ mMoreSuggestionsAvailable = (wordCountToShow > countInStrip);
+ @SuppressWarnings("unused")
+ 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(context, positionInStrip, width);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ x += wordView.getMeasuredWidth();
+
+ if (SuggestionStripView.DBG) {
+ layoutDebugInfo(positionInStrip, placerView, x);
+ }
+ }
+ return startIndexOfMoreSuggestions;
+ }
+
+ /**
+ * 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 Context context, 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);
+ }
+ // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack.
+ // Use a simple {@link String} to avoid the issue.
+ wordView.setContentDescription(
+ TextUtils.isEmpty(word)
+ ? context.getResources().getString(R.string.spoken_empty_suggestion)
+ : word.toString());
+ final CharSequence text = getEllipsizedTextWithSettingScaleX(
+ word, width, wordView.getPaint());
+ final float scaleX = wordView.getTextScaleX();
+ wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
+ wordView.setTextScaleX(scaleX);
+ // A <code>wordView</code> should be disabled when <code>word</code> is empty in order to
+ // make it unclickable.
+ // With accessibility touch exploration on, <code>wordView</code> should be enabled even
+ // when it is empty to avoid announcing as "disabled".
+ wordView.setEnabled(!TextUtils.isEmpty(word)
+ || AccessibilityUtils.getInstance().isTouchExplorationEnabled());
+ 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 int setupWordViewsAndReturnStartIndexOfMoreSuggestions(
+ final SuggestedWords suggestedWords, final int maxSuggestionInStrip) {
+ // Clear all suggestions first
+ for (int positionInStrip = 0; positionInStrip < maxSuggestionInStrip; ++positionInStrip) {
+ final TextView wordView = mWordViews.get(positionInStrip);
+ wordView.setText(null);
+ wordView.setTag(null);
+ // Make this inactive for touches in {@link #layoutWord(int,int)}.
+ if (SuggestionStripView.DBG) {
+ mDebugInfoViews.get(positionInStrip).setText(null);
+ }
+ }
+ int count = 0;
+ int indexInSuggestedWords;
+ for (indexInSuggestedWords = 0; indexInSuggestedWords < suggestedWords.size()
+ && count < maxSuggestionInStrip; indexInSuggestedWords++) {
+ final int positionInStrip =
+ getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords);
+ if (positionInStrip < 0) {
+ continue;
+ }
+ 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(suggestedWords, indexInSuggestedWords));
+ if (SuggestionStripView.DBG) {
+ mDebugInfoViews.get(positionInStrip).setText(
+ suggestedWords.getDebugString(indexInSuggestedWords));
+ }
+ count++;
+ }
+ return indexInSuggestedWords;
+ }
+
+ private int layoutPunctuationsAndReturnStartIndexOfMoreSuggestions(
+ final PunctuationSuggestions punctuationSuggestions, final ViewGroup stripView) {
+ final int countInStrip = Math.min(punctuationSuggestions.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);
+ final String punctuation = punctuationSuggestions.getLabel(positionInStrip);
+ // {@link TextView#getTag()} is used to get the index in suggestedWords at
+ // {@link SuggestionStripView#onClick(View)}.
+ wordView.setTag(positionInStrip);
+ wordView.setText(punctuation);
+ wordView.setContentDescription(punctuation);
+ wordView.setTextScaleX(1.0f);
+ wordView.setCompoundDrawables(null, null, null, null);
+ wordView.setTextColor(mColorAutoCorrect);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
+ }
+ mMoreSuggestionsAvailable = (punctuationSuggestions.size() > countInStrip);
+ return countInStrip;
+ }
+
+ public void layoutImportantNotice(final View importantNoticeStrip,
+ final String importantNoticeTitle) {
+ final TextView titleView = (TextView)importantNoticeStrip.findViewById(
+ R.id.important_notice_title);
+ final int width = titleView.getWidth() - titleView.getPaddingLeft()
+ - titleView.getPaddingRight();
+ titleView.setTextColor(mColorAutoCorrect);
+ titleView.setText(importantNoticeTitle); // TextView.setText() resets text scale x to 1.0.
+ final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint());
+ titleView.setTextScaleX(titleScaleX);
+ }
+
+ 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(@Nullable final CharSequence text, final int maxWidth,
+ final TextPaint paint) {
+ paint.setTextScaleX(1.0f);
+ final int width = getTextWidth(text, paint);
+ if (width <= maxWidth || maxWidth <= 0) {
+ return 1.0f;
+ }
+ return maxWidth / (float) width;
+ }
+
+ @Nullable
+ private static CharSequence getEllipsizedTextWithSettingScaleX(
+ @Nullable final CharSequence text, final int maxWidth, @Nonnull 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;
+ }
+
+ // <code>text</code> must be ellipsized with minimum text scale x.
+ paint.setTextScaleX(MIN_TEXT_XSCALE);
+ final boolean hasBoldStyle = hasStyleSpan(text, BOLD_SPAN);
+ final boolean hasUnderlineStyle = hasStyleSpan(text, UNDERLINE_SPAN);
+ // TextUtils.ellipsize erases any span object existed after ellipsized point.
+ // We have to restore these spans afterward.
+ final CharSequence ellipsizedText = TextUtils.ellipsize(
+ text, paint, maxWidth, TextUtils.TruncateAt.MIDDLE);
+ if (!hasBoldStyle && !hasUnderlineStyle) {
+ return ellipsizedText;
+ }
+ final Spannable spannableText = (ellipsizedText instanceof Spannable)
+ ? (Spannable)ellipsizedText : new SpannableString(ellipsizedText);
+ if (hasBoldStyle) {
+ addStyleSpan(spannableText, BOLD_SPAN);
+ }
+ if (hasUnderlineStyle) {
+ addStyleSpan(spannableText, UNDERLINE_SPAN);
+ }
+ return spannableText;
+ }
+
+ private static boolean hasStyleSpan(@Nullable final CharSequence text,
+ final CharacterStyle style) {
+ if (text instanceof Spanned) {
+ return ((Spanned)text).getSpanStart(style) >= 0;
+ }
+ return false;
+ }
+
+ private static void addStyleSpan(@Nonnull final Spannable text, final CharacterStyle style) {
+ text.removeSpan(style);
+ text.setSpan(style, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+
+ private static int getTextWidth(@Nullable final CharSequence text, final TextPaint paint) {
+ if (TextUtils.isEmpty(text)) {
+ return 0;
+ }
+ final int length = text.length();
+ final float[] widths = new float[length];
+ final int count;
+ final Typeface savedTypeface = paint.getTypeface();
+ try {
+ paint.setTypeface(getTextTypeface(text));
+ count = paint.getTextWidths(text, 0, length, widths);
+ } finally {
+ paint.setTypeface(savedTypeface);
+ }
+ int width = 0;
+ for (int i = 0; i < count; i++) {
+ width += Math.round(widths[i] + 0.5f);
+ }
+ return width;
+ }
+
+ private static Typeface getTextTypeface(@Nullable final CharSequence text) {
+ return hasStyleSpan(text, BOLD_SPAN) ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT;
+ }
+}