Newer
Older
* Copyright (C) 2011 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.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.UnderlineSpan;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
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;
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.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.AutoCorrection;
import com.android.inputmethod.latin.CollectionUtils;
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.define.ProductionFlag;
import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
public final class SuggestionStripView extends RelativeLayout implements OnClickListener,
public void addWordToUserDictionary(String word);
public void pickSuggestionManually(int index, SuggestedWordInfo word);
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
public static final int MAX_SUGGESTIONS = 18;
static final boolean DBG = LatinImeLogger.sDBG;
private final ViewGroup mSuggestionsStrip;
MainKeyboardView mMainKeyboardView;
private final View mMoreSuggestionsContainer;
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();
Listener mListener;
private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
Amith Yamasani
committed
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;
Tadashi G. Takaoka
committed
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;
Tadashi G. Takaoka
committed
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);
Tadashi G. Takaoka
committed
mMaxMoreSuggestionsRow = a.getInt(
R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
Tadashi G. Takaoka
committed
DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
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 indexInSuggestedWords) {
final String word = suggestedWords.getWord(indexInSuggestedWords);
final boolean isAutoCorrect = indexInSuggestedWords == 1
&& suggestedWords.willAutoCorrect();
final boolean isTypedWordValid = indexInSuggestedWords == 0
&& suggestedWords.mTypedWordValid;
if (!isAutoCorrect && !isTypedWordValid)
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);
private int getIndexInSuggestedWords(final int indexInStrip,
final SuggestedWords suggestedWords) {
// TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
// suggestions.
final int mostImportantIndexInSuggestedWords = suggestedWords.willAutoCorrect() ? 1 : 0;
if (indexInStrip == mCenterSuggestionIndex) {
return mostImportantIndexInSuggestedWords;
} else if (indexInStrip == mostImportantIndexInSuggestedWords) {
return mCenterSuggestionIndex;
return indexInStrip;
private int getSuggestionTextColor(final int indexInStrip,
final SuggestedWords suggestedWords) {
final int indexInSuggestedWords = getIndexInSuggestedWords(
indexInStrip, suggestedWords);
// TODO: Need to revisit this logic with bigram suggestions
final boolean isSuggested = (indexInSuggestedWords != 0);
if (indexInStrip == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
} else if (indexInStrip == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
color = mColorValidTypedWord;
} else if (isSuggested) {
color = mColorSuggested;
} else {
color = mColorTypedWord;
}
// If we auto-correct, then the autocorrection is in slot 0 and the typed word
// is in slot 1.
if (indexInStrip == mCenterSuggestionIndex
&& AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
suggestedWords.getWord(1), suggestedWords.getWord(0))) {
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);
final int countInStrip = mSuggestionsCountInStrip;
setupTexts(suggestedWords, countInStrip);
mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) {
if (indexInStrip != 0) {
final View divider = mDividers.get(indexInStrip);
// Add divider if this isn't the left most suggestion in suggestions strip.
x += divider.getMeasuredWidth();
final int width = getSuggestionWidth(indexInStrip, stripWidth);
final TextView word = layoutWord(suggestedWords, indexInStrip, width);
setLayoutWeight(word, getSuggestionWeight(indexInStrip),
ViewGroup.LayoutParams.MATCH_PARENT);
x += word.getMeasuredWidth();
if (DBG) {
layoutDebugInfo(suggestedWords, indexInStrip, placer, x);
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/**
* Format appropriately the suggested word indirectly specified by
* <code>indexInStrip</code> as text in a corresponding {@link TextView}. 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>indexInStrip</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 index of the most important suggestion is in {@link #mCenterSuggestionIndex}. This
* usually doesn't match the index in <code>suggedtedWords</code> -- see
* {@link #getIndexInSuggestedWords(int,SuggestedWords)}.
*
* @param suggestedWords the list of suggestions.
* @param indexInStrip the 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 SuggestedWords suggestedWords, final int indexInStrip,
final int width) {
final int indexInSuggestedWords = getIndexInSuggestedWords(
indexInStrip, suggestedWords);
final CharSequence styled = mTexts.get(indexInSuggestedWords);
final TextView word = mWords.get(indexInSuggestedWords);
if (indexInStrip == mCenterSuggestionIndex && mMoreSuggestionsAvailable) {
// TODO: This "more suggestions hint" should have a nicely designed icon.
word.setCompoundDrawablesWithIntrinsicBounds(
null, null, null, mMoreSuggestionsHint);
// HACK: Align with other TextViews that have 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(indexInStrip, suggestedWords));
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);
return word;
}
private void layoutDebugInfo(final SuggestedWords suggestedWords, final int indexInStrip,
final ViewGroup placer, final int x) {
final int indexInSuggestedWords = getIndexInSuggestedWords(
indexInStrip, suggestedWords);
if (indexInSuggestedWords >= suggestedWords.size()) {
return;
}
final String debugInfo = Utils.getDebugInfo(suggestedWords, indexInSuggestedWords);
if (debugInfo == null) {
return;
}
final TextView info = mInfos.get(indexInSuggestedWords);
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 indexInStrip, final int maxWidth) {
final int paddings = mPadding * mSuggestionsCountInStrip;
final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
final int availableWidth = maxWidth - paddings - dividers;
return (int)(availableWidth * getSuggestionWeight(indexInStrip));
private float getSuggestionWeight(final int indexInStrip) {
if (indexInStrip == 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) {
final int count = Math.min(suggestedWords.size(), countInStrip);
for (int pos = 0; pos < count; pos++) {
final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos);
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 indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) {
if (indexInStrip != 0) {
// Add divider if this isn't the left most suggestion in suggestions strip.
addDivider(stripView, mDividers.get(indexInStrip));
final TextView word = mWords.get(indexInStrip);
word.setTextColor(mColorAutoCorrect);
final String text = suggestedWords.getWord(indexInStrip);
word.setText(text);
word.setTextScaleX(1.0f);
word.setCompoundDrawables(null, null, null, null);
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);
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;
}
}
* Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user.
* @param context
* @param attrs
*/
public SuggestionStripView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.suggestionStripViewStyle);
public SuggestionStripView(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
final LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.suggestions_strip, this);
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.setOnClickListener(this);
word.setOnLongClickListener(this);
final View divider = inflater.inflate(R.layout.suggestion_divider, null);
divider.setOnClickListener(this);
mDividers.add(divider);
mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
mParams = new SuggestionStripViewParams(
context, attrs, defStyle, mWords, mDividers, mInfos);
mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
.findViewById(R.id.more_suggestions_view);
mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView);
final Resources res = context.getResources();
mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
R.dimen.more_suggestions_modal_tolerance);
mMoreSuggestionsSlidingDetector = new GestureDetector(
context, mMoreSuggestionsSlidingListener);
* A connection back to the input method.
* @param listener
*/
public void setListener(final Listener listener, final View inputView) {
mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
public void setSuggestions(final SuggestedWords suggestedWords) {
mSuggestedWords = suggestedWords;
mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
public int setMoreSuggestionsHeight(final int remainingHeight) {
return mParams.setMoreSuggestionsHeight(remainingHeight);
}
Amith Yamasani
committed
public boolean isShowingAddToDictionaryHint() {
return mSuggestionsStrip.getChildCount() > 0
&& mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
Amith Yamasani
committed
}
public void showAddToDictionaryHint(final String word, final CharSequence hintText) {
mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this);
}
public boolean dismissAddToDictionaryHint() {
if (isShowingAddToDictionaryHint()) {
clear();
return true;
}
return false;
public void clear() {
mSuggestionsStrip.removeAllViews();
removeAllViews();
addView(mSuggestionsStrip);
dismissMoreSuggestions();
private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() {
public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) {
mListener.pickSuggestionManually(index, wordInfo);
dismissMoreSuggestions();
}
@Override
public void onCancelInput() {
dismissMoreSuggestions();
}
};
private final MoreKeysPanel.Controller mMoreSuggestionsController =
new MoreKeysPanel.Controller() {
@Override
public boolean onDismissMoreKeysPanel() {
return mMainKeyboardView.onDismissMoreKeysPanel();
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
mMainKeyboardView.onShowMoreKeysPanel(panel);
@Override
public void onCancelMoreKeysPanel() {
dismissMoreSuggestions();
}
boolean dismissMoreSuggestions() {
return mMoreSuggestionsView.dismissMoreKeysPanel();
public boolean onLongClick(final View view) {
AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
Constants.NOT_A_CODE, this);
return showMoreSuggestions();
}
boolean showMoreSuggestions() {
final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard();
if (parentKeyboard == null) {
return false;
final SuggestionStripViewParams params = mParams;
if (!params.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);
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;
moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
mOriginX = mLastX;
mOriginY = mLastY;
for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
mWords.get(i).setPressed(false);
}
return true;
// Working variables for onLongClick and dispatchTouchEvent.
private int mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
private static final int MORE_SUGGESTIONS_IN_MODAL_MODE = 0;
private static final int MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING = 1;
private static final int MORE_SUGGESTIONS_IN_SLIDING_MODE = 2;
private int mLastX;
private int mLastY;
private int mOriginX;
private int mOriginY;
private final int mMoreSuggestionsModalTolerance;
private final GestureDetector mMoreSuggestionsSlidingDetector;
private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener =
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent down, MotionEvent me, float deltaX, float deltaY) {
final float dy = me.getY() - down.getY();
if (deltaY > 0 && dy < 0) {
return showMoreSuggestions();
}
return false;
}
};
public boolean dispatchTouchEvent(final MotionEvent me) {
if (!mMoreSuggestionsView.isShowingInParent()) {
mLastX = (int)me.getX();
mLastY = (int)me.getY();
if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) {
return true;
}
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) {
Tadashi G. Takaoka
committed
if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance
|| mOriginY - y >= mMoreSuggestionsModalTolerance) {
// Decided to be in the sliding input mode only when the touch point has been moved
// upward.
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE;
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
mMoreSuggestionsView.adjustVerticalCorrectionForModalMode();
// MORE_SUGGESTIONS_IN_SLIDING_MODE
mMoreSuggestionsView.processMotionEvent(action, translatedX, translatedY, id, eventTime);
return true;
}
@Override
public void onClick(final View view) {
if (mParams.isAddToDictionaryShowing(view)) {
mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
clear();
return;
}
final Object tag = view.getTag();
if (!(tag instanceof Integer))
return;
final int index = (Integer) tag;
if (index >= mSuggestedWords.size())
final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
mListener.pickSuggestionManually(index, wordInfo);

Amith Yamasani
committed
@Override
protected void onDetachedFromWindow() {

Amith Yamasani
committed
super.onDetachedFromWindow();
dismissMoreSuggestions();

Amith Yamasani
committed
}