From 913e2aeef26f172d500a4ebfc644b5f47778841a Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Thu, 1 Sep 2011 14:54:28 +0900 Subject: Rename CandidateView to SuggestionsView Change-Id: I6480cdf025f065130e1969899b259a243e7a11be --- .../deprecated/recorrection/Recorrection.java | 26 +- .../android/inputmethod/latin/AutoCorrection.java | 10 +- .../android/inputmethod/latin/CandidateView.java | 819 --------------------- .../com/android/inputmethod/latin/LatinIME.java | 107 +-- .../android/inputmethod/latin/MoreSuggestions.java | 10 +- .../android/inputmethod/latin/SuggestionsView.java | 819 +++++++++++++++++++++ java/src/com/android/inputmethod/latin/Utils.java | 26 +- 7 files changed, 908 insertions(+), 909 deletions(-) delete mode 100644 java/src/com/android/inputmethod/latin/CandidateView.java create mode 100644 java/src/com/android/inputmethod/latin/SuggestionsView.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java index bf2512d7b..75efa52f2 100644 --- a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java +++ b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java @@ -16,28 +16,28 @@ package com.android.inputmethod.deprecated.recorrection; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.text.TextUtils; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; + import com.android.inputmethod.compat.InputConnectionCompatUtils; import com.android.inputmethod.compat.SuggestionSpanUtils; import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.AutoCorrection; -import com.android.inputmethod.latin.CandidateView; import com.android.inputmethod.latin.EditingUtils; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.Settings; import com.android.inputmethod.latin.Suggest; import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.SuggestionsView; import com.android.inputmethod.latin.TextEntryState; import com.android.inputmethod.latin.WordComposer; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.text.TextUtils; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; - import java.util.ArrayList; /** @@ -107,7 +107,7 @@ public class Recorrection implements SharedPreferences.OnSharedPreferenceChangeL } public void updateRecorrectionSelection(KeyboardSwitcher keyboardSwitcher, - CandidateView candidateView, int candidatesStart, int candidatesEnd, + SuggestionsView suggestionsView, int candidatesStart, int candidatesEnd, int newSelStart, int newSelEnd, int oldSelStart, int lastSelectionStart, int lastSelectionEnd, boolean hasUncommittedTypedChars) { if (SuggestionSpanUtils.SUGGESTION_SPAN_IS_SUPPORTED || !mRecorrectionEnabled) return; @@ -127,7 +127,7 @@ public class Recorrection implements SharedPreferences.OnSharedPreferenceChangeL // If showing the "touch again to save" hint, do not replace it. Else, // show the bigrams if we are at the end of the text, punctuation // otherwise. - if (candidateView != null && !candidateView.isShowingAddToDictionaryHint()) { + if (suggestionsView != null && !suggestionsView.isShowingAddToDictionaryHint()) { InputConnection ic = mService.getCurrentInputConnection(); if (null == ic || !TextUtils.isEmpty(ic.getTextAfterCursor(1, 0))) { if (!mService.isShowingPunctuationList()) { @@ -219,13 +219,13 @@ public class Recorrection implements SharedPreferences.OnSharedPreferenceChangeL } public void fetchAndDisplayRecorrectionSuggestions(VoiceProxy voiceProxy, - CandidateView candidateView, Suggest suggest, KeyboardSwitcher keyboardSwitcher, + SuggestionsView suggestionsView, Suggest suggest, KeyboardSwitcher keyboardSwitcher, WordComposer word, boolean hasUncommittedTypedChars, int lastSelectionStart, int lastSelectionEnd, String wordSeparators) { if (!InputConnectionCompatUtils.RECORRECTION_SUPPORTED) return; if (SuggestionSpanUtils.SUGGESTION_SPAN_IS_SUPPORTED || !mRecorrectionEnabled) return; voiceProxy.setShowingVoiceSuggestions(false); - if (candidateView != null && candidateView.isShowingAddToDictionaryHint()) { + if (suggestionsView != null && suggestionsView.isShowingAddToDictionaryHint()) { return; } InputConnection ic = mService.getCurrentInputConnection(); @@ -260,7 +260,7 @@ public class Recorrection implements SharedPreferences.OnSharedPreferenceChangeL if (SuggestionSpanUtils.SUGGESTION_SPAN_IS_SUPPORTED) return; if (force || TextEntryState.isRecorrecting()) { TextEntryState.onAbortRecorrection(); - mService.setCandidatesViewShown(mService.isCandidateStripVisible()); + mService.setCandidatesViewShown(mService.isSuggestionsStripVisible()); mService.getCurrentInputConnection().finishComposingText(); mService.clearSuggestions(); } diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index d3119792c..e6ae2c5c7 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -119,15 +119,15 @@ public class AutoCorrection { if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL || correctionMode == Suggest.CORRECTION_FULL_BIGRAM) && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) { - final CharSequence autoCorrectionCandidate = suggestions.get(0); - final int autoCorrectionCandidateScore = sortedScores[0]; + final CharSequence autoCorrectionSuggestion = suggestions.get(0); + final int autoCorrectionSuggestionScore = sortedScores[0]; // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. mNormalizedScore = Utils.calcNormalizedScore( - typedWord,autoCorrectionCandidate, autoCorrectionCandidateScore); + typedWord,autoCorrectionSuggestion, autoCorrectionSuggestionScore); if (DBG) { - Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionCandidate + "," - + autoCorrectionCandidateScore + ", " + mNormalizedScore + Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionSuggestion + "," + + autoCorrectionSuggestionScore + ", " + mNormalizedScore + "(" + autoCorrectionThreshold + ")"); } if (mNormalizedScore >= autoCorrectionThreshold) { diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java deleted file mode 100644 index fe48f0bc1..000000000 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Copyright (C) 2010 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; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Message; -import android.os.SystemClock; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.BackgroundColorSpan; -import android.text.style.CharacterStyle; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; -import android.text.style.UnderlineSpan; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -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.PopupWindow; -import android.widget.TextView; - -import com.android.inputmethod.compat.FrameLayoutCompatUtils; -import com.android.inputmethod.compat.LinearLayoutCompatUtils; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -import java.util.ArrayList; -import java.util.List; - -public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener { - public interface Listener { - public boolean addWordToDictionary(String word); - public void pickSuggestionManually(int index, CharSequence word); - } - - // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. - public static final int MAX_SUGGESTIONS = 18; - - private static final boolean DBG = LatinImeLogger.sDBG; - - private final ViewGroup mCandidatesPlacer; - private final ViewGroup mCandidatesStrip; - private View mKeyboardView; - - private final View mMoreSuggestionsContainer; - private final MoreSuggestionsView mMoreSuggestionsView; - private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - private final PopupWindow mMoreSuggestionsWindow; - - private final ArrayList mWords = new ArrayList(); - private final ArrayList mInfos = new ArrayList(); - private final ArrayList mDividers = new ArrayList(); - - private final PopupWindow mPreviewPopup; - private final TextView mPreviewText; - - private Listener mListener; - private SuggestedWords mSuggestions = SuggestedWords.EMPTY; - private boolean mShowingAutoCorrectionInverted; - - private final SuggestionsStripParams mStripParams; - private static final float MIN_TEXT_XSCALE = 0.70f; - - private final UiHandler mHandler = new UiHandler(this); - - private static class UiHandler extends StaticInnerHandlerWrapper { - private static final int MSG_HIDE_PREVIEW = 0; - private static final int MSG_UPDATE_SUGGESTION = 1; - - private static final long DELAY_HIDE_PREVIEW = 1300; - private static final long DELAY_UPDATE_SUGGESTION = 300; - - public UiHandler(CandidateView outerInstance) { - super(outerInstance); - } - - @Override - public void dispatchMessage(Message msg) { - final CandidateView candidateView = getOuterInstance(); - switch (msg.what) { - case MSG_HIDE_PREVIEW: - candidateView.hidePreview(); - break; - case MSG_UPDATE_SUGGESTION: - candidateView.updateSuggestions(); - break; - } - } - - public void postHidePreview() { - cancelHidePreview(); - sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW); - } - - public void cancelHidePreview() { - removeMessages(MSG_HIDE_PREVIEW); - } - - public void postUpdateSuggestions() { - cancelUpdateSuggestions(); - sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION), - DELAY_UPDATE_SUGGESTION); - } - - public void cancelUpdateSuggestions() { - removeMessages(MSG_UPDATE_SUGGESTION); - } - - public void cancelAllMessages() { - cancelHidePreview(); - cancelUpdateSuggestions(); - } - } - - private static class CandidateViewParams { - public final int mPadding; - public final int mDividerWidth; - public final int mCandidateStripHeight; - - protected final List mWords; - protected final List mDividers; - protected final List mInfos; - - protected CandidateViewParams(List words, List dividers, - List 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(); - mCandidateStripHeight = res.getDimensionPixelSize(R.dimen.candidate_strip_height); - } - } - - private static class SuggestionsStripParams extends CandidateViewParams { - private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3; - private static final int DEFAULT_CENTER_CANDIDATE_PERCENTILE = 40; - private static final int PUNCTUATIONS_IN_STRIP = 6; - - private final int mColorTypedWord; - private final int mColorAutoCorrect; - private final int mColorSuggestedCandidate; - public final int mCandidateCountInStrip; - private final float mCenterCandidateWeight; - private final int mCenterCandidateIndex; - private final Drawable mMoreCandidateHint; - - private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); - private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - private final CharacterStyle mInvertedForegroundColorSpan; - private final CharacterStyle mInvertedBackgroundColorSpan; - private static final int AUTO_CORRECT_BOLD = 0x01; - private static final int AUTO_CORRECT_UNDERLINE = 0x02; - private static final int AUTO_CORRECT_INVERT = 0x04; - private static final int VALID_TYPED_WORD_BOLD = 0x08; - - private final int mSuggestionStripOption; - - private final ArrayList mTexts = new ArrayList(); - - public boolean mMoreSuggestionsAvailable; - - public final TextView mWordToSaveView; - private final TextView mHintToSaveView; - private final CharSequence mHintToSaveText; - - public SuggestionsStripParams(Context context, AttributeSet attrs, int defStyle, - List words, List dividers, List infos) { - super(words, dividers, infos); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle); - mSuggestionStripOption = a.getInt(R.styleable.CandidateView_suggestionStripOption, 0); - mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0); - mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0); - mColorSuggestedCandidate = a.getColor(R.styleable.CandidateView_colorSuggested, 0); - mCandidateCountInStrip = a.getInt( - R.styleable.CandidateView_candidateCountInStrip, - DEFAULT_CANDIDATE_COUNT_IN_STRIP); - mCenterCandidateWeight = a.getInt( - R.styleable.CandidateView_centerCandidatePercentile, - DEFAULT_CENTER_CANDIDATE_PERCENTILE) / 100.0f; - a.recycle(); - - mCenterCandidateIndex = mCandidateCountInStrip / 2; - final Resources res = context.getResources(); - mMoreCandidateHint = res.getDrawable(R.drawable.more_suggestions_hint); - - mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff); - mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord); - - final LayoutInflater inflater = LayoutInflater.from(context); - mWordToSaveView = (TextView)inflater.inflate(R.layout.candidate_word, null); - mHintToSaveView = (TextView)inflater.inflate(R.layout.candidate_word, null); - mHintToSaveText = context.getText(R.string.hint_add_to_dictionary); - } - - private CharSequence getStyledCandidateWord(SuggestedWords suggestions, int pos) { - final CharSequence word = suggestions.getWord(pos); - final boolean isAutoCorrect = pos == 1 && willAutoCorrect(suggestions); - final boolean isTypedWordValid = pos == 0 && suggestions.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 static boolean willAutoCorrect(SuggestedWords suggestions) { - return !suggestions.mTypedWordValid && suggestions.mHasMinimalSuggestion; - } - - private int getWordPosition(int index, SuggestedWords suggestions) { - // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more - // suggestions. - final int centerPos = willAutoCorrect(suggestions) ? 1 : 0; - if (index == mCenterCandidateIndex) { - return centerPos; - } else if (index == centerPos) { - return mCenterCandidateIndex; - } else { - return index; - } - } - - private int getCandidateTextColor(int index, SuggestedWords suggestions, int pos) { - // TODO: Need to revisit this logic with bigram suggestions - final boolean isSuggestedCandidate = (pos != 0); - - final int color; - if (index == mCenterCandidateIndex && willAutoCorrect(suggestions)) { - color = mColorAutoCorrect; - } else if (isSuggestedCandidate) { - color = mColorSuggestedCandidate; - } else { - color = mColorTypedWord; - } - - final SuggestedWordInfo info = (pos < suggestions.size()) - ? suggestions.getInfo(pos) : null; - if (info != null && info.isObsoleteSuggestedWord()) { - return applyAlpha(color, 0.5f); - } 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)); - } - - public CharSequence getInvertedText(CharSequence text) { - if ((mSuggestionStripOption & AUTO_CORRECT_INVERT) == 0) - return null; - final int len = text.length(); - final Spannable word = new SpannableString(text); - word.setSpan(mInvertedBackgroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - word.setSpan(mInvertedForegroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - return word; - } - - public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer, - int stripWidth) { - if (suggestions.isPunctuationSuggestions()) { - layoutPunctuationSuggestions(suggestions, stripView); - return; - } - - final int countInStrip = mCandidateCountInStrip; - setupTexts(suggestions, countInStrip); - mMoreSuggestionsAvailable = (suggestions.size() > countInStrip); - int x = 0; - for (int index = 0; index < countInStrip; index++) { - final int pos = getWordPosition(index, suggestions); - - if (index != 0) { - final View divider = mDividers.get(pos); - // Add divider if this isn't the left most suggestion in candidate strip. - stripView.addView(divider); - x += divider.getMeasuredWidth(); - } - - final CharSequence styled = mTexts.get(pos); - final TextView word = mWords.get(pos); - if (index == mCenterCandidateIndex && mMoreSuggestionsAvailable) { - // TODO: This "more suggestions hint" should have nicely designed icon. - word.setCompoundDrawablesWithIntrinsicBounds( - null, null, null, mMoreCandidateHint); - // HACK: To align with other TextView that has no compound drawables. - word.setCompoundDrawablePadding(-mMoreCandidateHint.getIntrinsicHeight()); - } else { - word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - } - - // Disable this candidate if the suggestion is null or empty. - word.setEnabled(!TextUtils.isEmpty(styled)); - word.setTextColor(getCandidateTextColor(index, suggestions, pos)); - final int width = getCandidateWidth(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, getCandidateWeight(index), ViewGroup.LayoutParams.MATCH_PARENT); - x += word.getMeasuredWidth(); - - if (DBG) { - final CharSequence debugInfo = getDebugInfo(suggestions, 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(); - FrameLayoutCompatUtils.placeViewAt( - info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); - } - } - } - } - - private int getCandidateWidth(int index, int maxWidth) { - final int paddings = mPadding * mCandidateCountInStrip; - final int dividers = mDividerWidth * (mCandidateCountInStrip - 1); - final int availableWidth = maxWidth - paddings - dividers; - return (int)(availableWidth * getCandidateWeight(index)); - } - - private float getCandidateWeight(int index) { - if (index == mCenterCandidateIndex) { - return mCenterCandidateWeight; - } else { - // TODO: Revisit this for cases of 5 or more suggestions - return (1.0f - mCenterCandidateWeight) / (mCandidateCountInStrip - 1); - } - } - - private void setupTexts(SuggestedWords suggestions, int countInStrip) { - mTexts.clear(); - final int count = Math.min(suggestions.size(), countInStrip); - for (int pos = 0; pos < count; pos++) { - final CharSequence styled = getStyledCandidateWord(suggestions, pos); - mTexts.add(styled); - } - for (int pos = count; pos < countInStrip; pos++) { - // Make this inactive for touches in layout(). - mTexts.add(null); - } - } - - private void layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) { - final int countInStrip = Math.min(suggestions.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 candidate strip. - stripView.addView(mDividers.get(index)); - } - - final TextView word = mWords.get(index); - word.setEnabled(true); - word.setTextColor(mColorTypedWord); - final CharSequence text = suggestions.getWord(index); - word.setText(text); - word.setTextScaleX(1.0f); - word.setCompoundDrawables(null, null, null, null); - stripView.addView(word); - setLayoutWeight(word, 1.0f, mCandidateStripHeight); - } - mMoreSuggestionsAvailable = false; - } - - public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView, - int stripWidth) { - final int width = stripWidth - mDividerWidth - mPadding * 2; - - final TextView wordView = mWordToSaveView; - wordView.setTextColor(mColorTypedWord); - final int wordWidth = (int)(width * mCenterCandidateWeight); - 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, mCenterCandidateWeight, ViewGroup.LayoutParams.MATCH_PARENT); - - stripView.addView(mDividers.get(0)); - - final TextView hintView = mHintToSaveView; - hintView.setTextColor(mColorAutoCorrect); - final int hintWidth = width - wordWidth; - final float hintScaleX = getTextScaleX(mHintToSaveText, hintWidth, hintView.getPaint()); - hintView.setText(mHintToSaveText); - hintView.setTextScaleX(hintScaleX); - stripView.addView(hintView); - setLayoutWeight( - hintView, 1.0f - mCenterCandidateWeight, ViewGroup.LayoutParams.MATCH_PARENT); - } - } - - /** - * Construct a CandidateView for showing suggested words for completion. - * @param context - * @param attrs - */ - public CandidateView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.candidateViewStyle); - } - - public CandidateView(Context context, AttributeSet attrs, int defStyle) { - // Note: Up to version 10 (Gingerbread) of the API, LinearLayout doesn't have 3-argument - // constructor. - // TODO: Call 3-argument constructor, super(context, attrs, defStyle), when we abandon - // backward compatibility with the version 10 or earlier of the API. - super(context, attrs); - if (defStyle != R.attr.candidateViewStyle) { - throw new IllegalArgumentException( - "can't accept defStyle other than R.attr.candidateViewStyle: defStyle=" - + defStyle); - } - setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable( - context, attrs, defStyle, R.style.CandidateViewStyle)); - - final LayoutInflater inflater = LayoutInflater.from(context); - inflater.inflate(R.layout.candidates_strip, this); - - mPreviewPopup = new PopupWindow(context); - mPreviewText = (TextView) inflater.inflate(R.layout.candidate_preview, null); - mPreviewPopup.setWindowLayoutMode( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - mPreviewPopup.setContentView(mPreviewText); - mPreviewPopup.setBackgroundDrawable(null); - - mCandidatesPlacer = (ViewGroup)findViewById(R.id.candidates_placer); - mCandidatesStrip = (ViewGroup)findViewById(R.id.candidates_strip); - for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) { - final TextView word = (TextView)inflater.inflate(R.layout.candidate_word, null); - word.setTag(pos); - word.setOnClickListener(this); - word.setOnLongClickListener(this); - mWords.add(word); - final View divider = inflater.inflate(R.layout.candidate_divider, null); - divider.setTag(pos); - divider.setOnClickListener(this); - mDividers.add(divider); - mInfos.add((TextView)inflater.inflate(R.layout.candidate_info, null)); - } - - mStripParams = new SuggestionsStripParams(context, attrs, defStyle, mWords, mDividers, - mInfos); - mStripParams.mWordToSaveView.setOnClickListener(this); - - mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); - mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer - .findViewById(R.id.more_suggestions_view); - mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView); - mMoreSuggestionsWindow = new PopupWindow(context); - mMoreSuggestionsWindow.setWindowLayoutMode( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - mMoreSuggestionsWindow.setBackgroundDrawable(null); - } - - /** - * A connection back to the input method. - * @param listener - */ - public void setListener(Listener listener, View inputView) { - mListener = listener; - mKeyboardView = inputView.findViewById(R.id.keyboard_view); - } - - public void setSuggestions(SuggestedWords suggestions) { - if (suggestions == null) - return; - mSuggestions = suggestions; - if (mShowingAutoCorrectionInverted) { - mHandler.postUpdateSuggestions(); - } else { - updateSuggestions(); - } - } - - private void updateSuggestions() { - clear(); - if (mSuggestions.size() == 0) - return; - - mStripParams.layout(mSuggestions, mCandidatesStrip, mCandidatesPlacer, getWidth()); - } - - private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) { - if (DBG && pos < suggestions.size()) { - final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); - if (wordInfo != null) { - final CharSequence debugInfo = wordInfo.getDebugString(); - if (!TextUtils.isEmpty(debugInfo)) { - return debugInfo; - } - } - } - return null; - } - - private static void setLayoutWeight(View v, float weight, 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(CharSequence text, int maxWidth, 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(CharSequence text, int maxWidth, - 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(CharSequence text, 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(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; - } - } - - public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) { - final CharSequence inverted = mStripParams.getInvertedText(autoCorrectedWord); - if (inverted == null) - return; - final TextView tv = mWords.get(1); - tv.setText(inverted); - mShowingAutoCorrectionInverted = true; - } - - public boolean isShowingAddToDictionaryHint() { - return mCandidatesStrip.getChildCount() > 0 - && mCandidatesStrip.getChildAt(0) == mStripParams.mWordToSaveView; - } - - public void showAddToDictionaryHint(CharSequence word) { - clear(); - mStripParams.layoutAddToDictionaryHint(word, mCandidatesStrip, getWidth()); - } - - public boolean dismissAddToDictionaryHint() { - if (isShowingAddToDictionaryHint()) { - clear(); - return true; - } - return false; - } - - public SuggestedWords getSuggestions() { - return mSuggestions; - } - - public void clear() { - mShowingAutoCorrectionInverted = false; - mCandidatesPlacer.removeAllViews(); - mCandidatesPlacer.addView(mCandidatesStrip); - mCandidatesStrip.removeAllViews(); - mMoreSuggestionsWindow.dismiss(); - } - - private void hidePreview() { - mPreviewPopup.dismiss(); - } - - private void showPreview(View view, CharSequence word) { - if (TextUtils.isEmpty(word)) - return; - - final TextView previewText = mPreviewText; - previewText.setTextColor(mStripParams.mColorTypedWord); - previewText.setText(word); - previewText.measure( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - final int[] offsetInWindow = new int[2]; - view.getLocationInWindow(offsetInWindow); - final int posX = offsetInWindow[0]; - final int posY = offsetInWindow[1] - previewText.getMeasuredHeight(); - final PopupWindow previewPopup = mPreviewPopup; - if (previewPopup.isShowing()) { - previewPopup.update(posX, posY, previewPopup.getWidth(), previewPopup.getHeight()); - } else { - previewPopup.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY); - } - previewText.setVisibility(VISIBLE); - mHandler.postHidePreview(); - } - - private void addToDictionary(CharSequence word) { - if (mListener.addWordToDictionary(word.toString())) { - final CharSequence message = getContext().getString(R.string.added_word, word); - showPreview(mStripParams.mWordToSaveView, message); - } - } - - private final KeyboardActionListener mMoreSuggestionsListener = - new KeyboardActionListener.Adapter() { - @Override - public boolean onCustomRequest(int requestCode) { - final int index = requestCode; - final CharSequence word = mSuggestions.getWord(index); - mListener.pickSuggestionManually(index, word); - mMoreSuggestionsView.dismissMoreKeysPanel(); - return true; - } - - @Override - public void onCancelInput() { - mMoreSuggestionsView.dismissMoreKeysPanel(); - } - }; - - private final MoreKeysPanel.Controller mMoreSuggestionsController = - new MoreKeysPanel.Controller() { - @Override - public boolean dismissMoreKeysPanel() { - if (mMoreSuggestionsWindow.isShowing()) { - mMoreSuggestionsWindow.dismiss(); - return true; - } - return false; - } - }; - - @Override - public boolean onLongClick(View view) { - final SuggestionsStripParams params = mStripParams; - if (params.mMoreSuggestionsAvailable) { - final int stripWidth = getWidth(); - final View container = mMoreSuggestionsContainer; - final int maxWidth = stripWidth - container.getPaddingLeft() - - container.getPaddingRight(); - final DisplayMetrics dm = getContext().getResources().getDisplayMetrics(); - // TODO: Revise how we determine the height - final int maxHeight = dm.heightPixels - mKeyboardView.getHeight() - getHeight() * 3; - final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestions, params.mCandidateCountInStrip, maxWidth, maxHeight); - 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 = 0; - moreKeysPanel.showMoreKeysPanel( - this, mMoreSuggestionsController, pointX, pointY, - mMoreSuggestionsWindow, mMoreSuggestionsListener); - // TODO: Should figure out how to select the pointer tracker correctly. - final PointerTracker tracker = PointerTracker.getPointerTracker(0, moreKeysPanel); - final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); - final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); - tracker.onShowMoreKeysPanel( - translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel); - view.setPressed(false); - // TODO: Should gray out the keyboard here as well? - return true; - } - return false; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent me) { - if (!mMoreSuggestionsWindow.isShowing()) { - return super.dispatchTouchEvent(me); - } - final int action = me.getAction(); - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final PointerTracker tracker = PointerTracker.getPointerTracker(id, mMoreSuggestionsView); - final int x = mMoreSuggestionsView.translateX((int)me.getX(index)); - final int y = mMoreSuggestionsView.translateY((int)me.getY(index)); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - tracker.onDownEvent(x, y, eventTime, mMoreSuggestionsView); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - tracker.onUpEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_MOVE: - tracker.onMoveEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_CANCEL: - tracker.onCancelEvent(x, y, eventTime); - break; - } - return true; - } - - @Override - public void onClick(View view) { - if (view == mStripParams.mWordToSaveView) { - addToDictionary((CharSequence)view.getTag()); - clear(); - return; - } - - final Object tag = view.getTag(); - if (!(tag instanceof Integer)) - return; - final int index = (Integer) tag; - if (index >= mSuggestions.size()) - return; - - final CharSequence word = mSuggestions.getWord(index); - mListener.pickSuggestionManually(index, word); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mHandler.cancelAllMessages(); - hidePreview(); - } -} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index da6c22107..85363fd36 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -77,7 +77,7 @@ import java.util.Locale; * Input method implementation for Qwerty'ish keyboard. */ public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener, - CandidateView.Listener { + SuggestionsView.Listener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean PERF_DEBUG = false; private static final boolean TRACE = false; @@ -157,9 +157,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private Settings.Values mSettingsValues; - private View mCandidateViewContainer; - private int mCandidateStripHeight; - private CandidateView mCandidateView; + private View mSuggestionsContainer; + private int mSuggestionsStripHeight; + private SuggestionsView mSuggestionsView; private Suggest mSuggest; private CompletionInfo[] mApplicationSpecifiedCompletions; @@ -254,7 +254,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar break; case MSG_UPDATE_OLD_SUGGESTIONS: latinIme.mRecorrection.fetchAndDisplayRecorrectionSuggestions( - latinIme.mVoiceProxy, latinIme.mCandidateView, + latinIme.mVoiceProxy, latinIme.mSuggestionsView, latinIme.mSuggest, latinIme.mKeyboardSwitcher, latinIme.mWordComposer, latinIme.mHasUncommittedTypedChars, latinIme.mLastSelectionStart, latinIme.mLastSelectionEnd, latinIme.mSettingsValues.mWordSeparators); @@ -607,11 +607,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void setInputView(View view) { super.setInputView(view); - mCandidateViewContainer = view.findViewById(R.id.candidates_container); - mCandidateView = (CandidateView) view.findViewById(R.id.candidates); - if (mCandidateView != null) - mCandidateView.setListener(this, view); - mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height); + mSuggestionsContainer = view.findViewById(R.id.suggestions_container); + mSuggestionsView = (SuggestionsView) view.findViewById(R.id.suggestions_view); + if (mSuggestionsView != null) + mSuggestionsView.setListener(this, view); + mSuggestionsStripHeight = (int)mResources.getDimension(R.dimen.suggestions_strip_height); } @Override @@ -678,9 +678,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar switcher.loadKeyboard(attribute, mSettingsValues); } - if (mCandidateView != null) - mCandidateView.clear(); - setSuggestionStripShownInternal(isCandidateStripVisible(), /* needsInputViewShown */ false); + if (mSuggestionsView != null) + mSuggestionsView.clear(); + setSuggestionStripShownInternal( + isSuggestionsStripVisible(), /* needsInputViewShown */ false); // Delay updating suggestions because keyboard input view may not be shown at this point. mHandler.postUpdateSuggestions(); @@ -708,7 +709,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { mIsSettingsSuggestionStripOn = true; - // Make sure that passwords are not displayed in candidate view + // Make sure that passwords are not displayed in {@link SuggestionsView}. if (InputTypeCompatUtils.isPasswordInputType(inputType) || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) { mIsSettingsSuggestionStripOn = false; @@ -819,7 +820,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar && (selectionChanged || candidatesCleared)) { if (candidatesCleared) { // If the composing span has been cleared, save the typed word in the history for - // recorrection before we reset the candidate strip. Then, we'll be able to show + // recorrection before we reset the suggestions strip. Then, we'll be able to show // suggestions for recorrection right away. mRecorrection.saveRecorrectionSuggestion(mWordComposer, mComposingStringBuilder); } @@ -853,7 +854,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mLastSelectionEnd = newSelEnd; mRecorrection.updateRecorrectionSelection(mKeyboardSwitcher, - mCandidateView, candidatesStart, candidatesEnd, newSelStart, + mSuggestionsView, candidatesStart, candidatesEnd, newSelStart, newSelEnd, oldSelStart, mLastSelectionStart, mLastSelectionEnd, mHasUncommittedTypedChars); } @@ -866,10 +867,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar /** * This is called when the user has clicked on the extracted text view, * when running in fullscreen mode. The default implementation hides - * the candidates view when this happens, but only if the extracted text + * the suggestions view when this happens, but only if the extracted text * editor has a vertical scroll bar because its text doesn't fit. * Here we override the behavior due to the possibility that a re-correction could - * cause the candidate strip to disappear and re-appear. + * cause the suggestions strip to disappear and re-appear. */ @Override public void onExtractedTextClicked() { @@ -881,11 +882,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar /** * This is called when the user has performed a cursor movement in the * extracted text view, when it is running in fullscreen mode. The default - * implementation hides the candidates view when a vertical movement + * implementation hides the suggestions view when a vertical movement * happens, but only if the extracted text editor has a vertical scroll bar * because its text doesn't fit. * Here we override the behavior due to the possibility that a re-correction could - * cause the candidate strip to disappear and re-appear. + * cause the suggestions strip to disappear and re-appear. */ @Override public void onExtractedCursorMovement(int dx, int dy) { @@ -938,20 +939,20 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { - // TODO: Modify this if we support candidates with hard keyboard - if (onEvaluateInputViewShown() && mCandidateViewContainer != null) { - final boolean shouldShowCandidates = shown + // TODO: Modify this if we support suggestions with hard keyboard + if (onEvaluateInputViewShown() && mSuggestionsContainer != null) { + final boolean shouldShowSuggestions = shown && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true); if (isFullscreenMode()) { // No need to have extra space to show the key preview. - mCandidateViewContainer.setMinimumHeight(0); - mCandidateViewContainer.setVisibility( - shouldShowCandidates ? View.VISIBLE : View.GONE); + mSuggestionsContainer.setMinimumHeight(0); + mSuggestionsContainer.setVisibility( + shouldShowSuggestions ? View.VISIBLE : View.GONE); } else { // We must control the visibility of the suggestion strip in order to avoid clipped // key previews, even when we don't show the suggestion strip. - mCandidateViewContainer.setVisibility( - shouldShowCandidates ? View.VISIBLE : View.INVISIBLE); + mSuggestionsContainer.setVisibility( + shouldShowSuggestions ? View.VISIBLE : View.INVISIBLE); } } } @@ -964,14 +965,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public void onComputeInsets(InputMethodService.Insets outInsets) { super.onComputeInsets(outInsets); final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); - if (inputView == null || mCandidateViewContainer == null) + if (inputView == null || mSuggestionsContainer == null) return; - final int containerHeight = mCandidateViewContainer.getHeight(); + final int containerHeight = mSuggestionsContainer.getHeight(); int touchY = containerHeight; // Need to set touchable region only if input view is being shown if (mKeyboardSwitcher.isInputViewShown()) { - if (mCandidateViewContainer.getVisibility() == View.VISIBLE) { - touchY -= mCandidateStripHeight; + if (mSuggestionsContainer.getVisibility() == View.VISIBLE) { + touchY -= mSuggestionsStripHeight; } final int touchWidth = inputView.getWidth(); final int touchHeight = inputView.getHeight() + containerHeight @@ -1321,7 +1322,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { ic.deleteSurroundingText(mEnteredText.length(), 0); } else if (deleteChar) { - if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { + if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) { // Go back to the suggestion mode if the user canceled the // "Touch again to save". // NOTE: In gerenal, we don't revert the word when backspacing @@ -1440,7 +1441,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mVoiceProxy.handleSeparator(); // Should dismiss the "Touch again to save" message when handling separator - if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { + if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) { mHandler.cancelUpdateBigramPredictions(); mHandler.postUpdateSuggestions(); } @@ -1491,8 +1492,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) { InputConnectionCompatUtils.commitCorrection( ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); - if (mCandidateView != null) - mCandidateView.onAutoCorrectionInverted(mBestWord); + if (mSuggestionsView != null) + mSuggestionsView.onAutoCorrectionInverted(mBestWord); } } if (Keyboard.CODE_SPACE == primaryCode) { @@ -1527,7 +1528,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } public boolean isShowingPunctuationList() { - return mSettingsValues.mSuggestPuncList == mCandidateView.getSuggestions(); + return mSettingsValues.mSuggestPuncList == mSuggestionsView.getSuggestions(); } public boolean isShowingSuggestionsStrip() { @@ -1536,10 +1537,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar && mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT); } - public boolean isCandidateStripVisible() { - if (mCandidateView == null) + public boolean isSuggestionsStripVisible() { + if (mSuggestionsView == null) return false; - if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting()) + if (mSuggestionsView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting()) return true; if (!isShowingSuggestionsStrip()) return false; @@ -1561,7 +1562,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } setInputView(v); } - setSuggestionStripShown(isCandidateStripVisible()); + setSuggestionStripShown(isSuggestionsStripVisible()); updateInputViewShown(); mHandler.postUpdateSuggestions(); } @@ -1571,8 +1572,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } public void setSuggestions(SuggestedWords words) { - if (mCandidateView != null) { - mCandidateView.setSuggestions(words); + if (mSuggestionsView != null) { + mSuggestionsView.setSuggestions(words); mKeyboardSwitcher.onAutoCorrectionStateChanged( words.hasWordAboveAutoCorrectionScoreThreshold()); } @@ -1625,11 +1626,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // length == 1). if (typedWord != null) { if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid - || mCandidateView.isShowingAddToDictionaryHint()) { + || mSuggestionsView.isShowingAddToDictionaryHint()) { builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion( autoCorrectionAvailable); } else { - final SuggestedWords previousSuggestions = mCandidateView.getSuggestions(); + final SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions(); if (previousSuggestions == mSettingsValues.mSuggestPuncList) return; builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); @@ -1651,11 +1652,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } else { mBestWord = null; } - setSuggestionStripShown(isCandidateStripVisible()); + setSuggestionStripShown(isSuggestionsStripVisible()); } private boolean pickDefaultSuggestion(int separatorCode) { - // Complete any pending candidate query first + // Complete any pending suggestions query first if (mHandler.hasPendingUpdateSuggestions()) { mHandler.cancelUpdateSuggestions(); updateSuggestions(); @@ -1674,7 +1675,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void pickSuggestionManually(int index, CharSequence suggestion) { - SuggestedWords suggestions = mCandidateView.getSuggestions(); + SuggestedWords suggestions = mSuggestionsView.getSuggestions(); mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, mSettingsValues.mWordSeparators); @@ -1690,8 +1691,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar ic.commitCompletion(completionInfo); } mCommittedLength = suggestion.length(); - if (mCandidateView != null) { - mCandidateView.clear(); + if (mSuggestionsView != null) { + mSuggestionsView.clear(); } mKeyboardSwitcher.updateShiftState(); if (ic != null) { @@ -1786,7 +1787,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } if (showingAddToDictionaryHint) { if (mIsUserDictionaryAvaliable) { - mCandidateView.showAddToDictionaryHint(suggestion); + mSuggestionsView.showAddToDictionaryHint(suggestion); } else { mHandler.postUpdateSuggestions(); } @@ -1806,7 +1807,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar final InputConnection ic = getCurrentInputConnection(); if (ic != null) { mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators); - SuggestedWords suggestedWords = mCandidateView.getSuggestions(); + SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( this, bestWord, suggestedWords), 1); } @@ -1842,7 +1843,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public void setPunctuationSuggestions() { setSuggestions(mSettingsValues.mSuggestPuncList); - setSuggestionStripShown(isCandidateStripVisible()); + setSuggestionStripShown(isSuggestionsStripVisible()); } private void addToUserUnigramAndBigramDictionaries(CharSequence suggestion, diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/MoreSuggestions.java index 0446fb2a8..a9e75b7b3 100644 --- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/MoreSuggestions.java @@ -43,10 +43,10 @@ public class MoreSuggestions extends Keyboard { private int mToPos; public static class MoreSuggestionsParam extends KeyboardParams { - private final int[] mWidths = new int[CandidateView.MAX_SUGGESTIONS]; - private final int[] mRowNumbers = new int[CandidateView.MAX_SUGGESTIONS]; - private final int[] mColumnOrders = new int[CandidateView.MAX_SUGGESTIONS]; - private final int[] mNumColumnsInRow = new int[CandidateView.MAX_SUGGESTIONS]; + private final int[] mWidths = new int[SuggestionsView.MAX_SUGGESTIONS]; + private final int[] mRowNumbers = new int[SuggestionsView.MAX_SUGGESTIONS]; + private final int[] mColumnOrders = new int[SuggestionsView.MAX_SUGGESTIONS]; + private final int[] mNumColumnsInRow = new int[SuggestionsView.MAX_SUGGESTIONS]; private static final int MAX_COLUMNS_IN_ROW = 3; private int mNumRows; @@ -60,7 +60,7 @@ public class MoreSuggestions extends Keyboard { int row = 0; int pos = fromPos, rowStartPos = fromPos; - final int size = Math.min(suggestions.size(), CandidateView.MAX_SUGGESTIONS); + final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS); while (pos < size) { final CharSequence word = suggestions.getWord(pos); // TODO: Should take care of text x-scaling. diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/SuggestionsView.java new file mode 100644 index 000000000..617d7f1f2 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/SuggestionsView.java @@ -0,0 +1,819 @@ +/* + * 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; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Message; +import android.os.SystemClock; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +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.PopupWindow; +import android.widget.TextView; + +import com.android.inputmethod.compat.FrameLayoutCompatUtils; +import com.android.inputmethod.compat.LinearLayoutCompatUtils; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.MoreKeysPanel; +import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + +import java.util.ArrayList; +import java.util.List; + +public class SuggestionsView extends LinearLayout implements OnClickListener, OnLongClickListener { + public interface Listener { + public boolean addWordToDictionary(String word); + public void pickSuggestionManually(int index, CharSequence word); + } + + // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. + public static final int MAX_SUGGESTIONS = 18; + + private static final boolean DBG = LatinImeLogger.sDBG; + + private final ViewGroup mSuggestionsPlacer; + private final ViewGroup mSuggestionsStrip; + private View mKeyboardView; + + private final View mMoreSuggestionsContainer; + private final MoreSuggestionsView mMoreSuggestionsView; + private final MoreSuggestions.Builder mMoreSuggestionsBuilder; + private final PopupWindow mMoreSuggestionsWindow; + + private final ArrayList mWords = new ArrayList(); + private final ArrayList mInfos = new ArrayList(); + private final ArrayList mDividers = new ArrayList(); + + private final PopupWindow mPreviewPopup; + private final TextView mPreviewText; + + private Listener mListener; + private SuggestedWords mSuggestions = SuggestedWords.EMPTY; + private boolean mShowingAutoCorrectionInverted; + + private final SuggestionsStripParams mStripParams; + private static final float MIN_TEXT_XSCALE = 0.70f; + + private final UiHandler mHandler = new UiHandler(this); + + private static class UiHandler extends StaticInnerHandlerWrapper { + private static final int MSG_HIDE_PREVIEW = 0; + private static final int MSG_UPDATE_SUGGESTION = 1; + + private static final long DELAY_HIDE_PREVIEW = 1300; + private static final long DELAY_UPDATE_SUGGESTION = 300; + + public UiHandler(SuggestionsView outerInstance) { + super(outerInstance); + } + + @Override + public void dispatchMessage(Message msg) { + final SuggestionsView suggestionsView = getOuterInstance(); + switch (msg.what) { + case MSG_HIDE_PREVIEW: + suggestionsView.hidePreview(); + break; + case MSG_UPDATE_SUGGESTION: + suggestionsView.updateSuggestions(); + break; + } + } + + public void postHidePreview() { + cancelHidePreview(); + sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW); + } + + public void cancelHidePreview() { + removeMessages(MSG_HIDE_PREVIEW); + } + + public void postUpdateSuggestions() { + cancelUpdateSuggestions(); + sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION), + DELAY_UPDATE_SUGGESTION); + } + + public void cancelUpdateSuggestions() { + removeMessages(MSG_UPDATE_SUGGESTION); + } + + public void cancelAllMessages() { + cancelHidePreview(); + cancelUpdateSuggestions(); + } + } + + private static class SuggestionsViewParams { + public final int mPadding; + public final int mDividerWidth; + public final int mSuggestionsStripHeight; + + protected final List mWords; + protected final List mDividers; + protected final List mInfos; + + protected SuggestionsViewParams(List words, List dividers, + List 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); + } + } + + private static class SuggestionsStripParams extends SuggestionsViewParams { + private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; + private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40; + private static final int PUNCTUATIONS_IN_STRIP = 6; + + private final int mColorTypedWord; + private final int mColorAutoCorrect; + private final int mColorSuggested; + public final int mSuggestionsCountInStrip; + private final float mCenterSuggestionWeight; + private final int mCenterSuggestionIndex; + private final Drawable mMoreSuggestionsHint; + + private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); + private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); + private final CharacterStyle mInvertedForegroundColorSpan; + private final CharacterStyle mInvertedBackgroundColorSpan; + private static final int AUTO_CORRECT_BOLD = 0x01; + private static final int AUTO_CORRECT_UNDERLINE = 0x02; + private static final int AUTO_CORRECT_INVERT = 0x04; + private static final int VALID_TYPED_WORD_BOLD = 0x08; + + private final int mSuggestionStripOption; + + private final ArrayList mTexts = new ArrayList(); + + public boolean mMoreSuggestionsAvailable; + + public final TextView mWordToSaveView; + private final TextView mHintToSaveView; + private final CharSequence mHintToSaveText; + + public SuggestionsStripParams(Context context, AttributeSet attrs, int defStyle, + List words, List dividers, List infos) { + super(words, dividers, infos); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle); + mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0); + mColorTypedWord = a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0); + mColorAutoCorrect = a.getColor(R.styleable.SuggestionsView_colorAutoCorrect, 0); + mColorSuggested = a.getColor(R.styleable.SuggestionsView_colorSuggested, 0); + mSuggestionsCountInStrip = a.getInt( + R.styleable.SuggestionsView_suggestionsCountInStrip, + DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); + mCenterSuggestionWeight = a.getInt( + R.styleable.SuggestionsView_centerSuggestionPercentile, + DEFAULT_CENTER_SUGGESTION_PERCENTILE) / 100.0f; + a.recycle(); + + mCenterSuggestionIndex = mSuggestionsCountInStrip / 2; + final Resources res = context.getResources(); + mMoreSuggestionsHint = res.getDrawable(R.drawable.more_suggestions_hint); + + mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff); + mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord); + + final LayoutInflater inflater = LayoutInflater.from(context); + mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); + mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); + mHintToSaveText = context.getText(R.string.hint_add_to_dictionary); + } + + private CharSequence getStyledSuggestionWord(SuggestedWords suggestions, int pos) { + final CharSequence word = suggestions.getWord(pos); + final boolean isAutoCorrect = pos == 1 && willAutoCorrect(suggestions); + final boolean isTypedWordValid = pos == 0 && suggestions.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 static boolean willAutoCorrect(SuggestedWords suggestions) { + return !suggestions.mTypedWordValid && suggestions.mHasMinimalSuggestion; + } + + private int getWordPosition(int index, SuggestedWords suggestions) { + // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more + // suggestions. + final int centerPos = willAutoCorrect(suggestions) ? 1 : 0; + if (index == mCenterSuggestionIndex) { + return centerPos; + } else if (index == centerPos) { + return mCenterSuggestionIndex; + } else { + return index; + } + } + + private int getSuggestionTextColor(int index, SuggestedWords suggestions, int pos) { + // TODO: Need to revisit this logic with bigram suggestions + final boolean isSuggested = (pos != 0); + + final int color; + if (index == mCenterSuggestionIndex && willAutoCorrect(suggestions)) { + color = mColorAutoCorrect; + } else if (isSuggested) { + color = mColorSuggested; + } else { + color = mColorTypedWord; + } + + final SuggestedWordInfo info = (pos < suggestions.size()) + ? suggestions.getInfo(pos) : null; + if (info != null && info.isObsoleteSuggestedWord()) { + return applyAlpha(color, 0.5f); + } 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)); + } + + public CharSequence getInvertedText(CharSequence text) { + if ((mSuggestionStripOption & AUTO_CORRECT_INVERT) == 0) + return null; + final int len = text.length(); + final Spannable word = new SpannableString(text); + word.setSpan(mInvertedBackgroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + word.setSpan(mInvertedForegroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + return word; + } + + public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer, + int stripWidth) { + if (suggestions.isPunctuationSuggestions()) { + layoutPunctuationSuggestions(suggestions, stripView); + return; + } + + final int countInStrip = mSuggestionsCountInStrip; + setupTexts(suggestions, countInStrip); + mMoreSuggestionsAvailable = (suggestions.size() > countInStrip); + int x = 0; + for (int index = 0; index < countInStrip; index++) { + final int pos = getWordPosition(index, suggestions); + + if (index != 0) { + final View divider = mDividers.get(pos); + // Add divider if this isn't the left most suggestion in suggestions strip. + stripView.addView(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, suggestions, 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) { + final CharSequence debugInfo = getDebugInfo(suggestions, 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(); + FrameLayoutCompatUtils.placeViewAt( + info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); + } + } + } + } + + private int getSuggestionWidth(int index, 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(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(SuggestedWords suggestions, int countInStrip) { + mTexts.clear(); + final int count = Math.min(suggestions.size(), countInStrip); + for (int pos = 0; pos < count; pos++) { + final CharSequence styled = getStyledSuggestionWord(suggestions, pos); + mTexts.add(styled); + } + for (int pos = count; pos < countInStrip; pos++) { + // Make this inactive for touches in layout(). + mTexts.add(null); + } + } + + private void layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) { + final int countInStrip = Math.min(suggestions.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. + stripView.addView(mDividers.get(index)); + } + + final TextView word = mWords.get(index); + word.setEnabled(true); + word.setTextColor(mColorTypedWord); + final CharSequence text = suggestions.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(CharSequence word, ViewGroup stripView, + int stripWidth) { + 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 hintView = mHintToSaveView; + hintView.setTextColor(mColorAutoCorrect); + final int hintWidth = width - wordWidth; + final float hintScaleX = getTextScaleX(mHintToSaveText, hintWidth, hintView.getPaint()); + hintView.setText(mHintToSaveText); + hintView.setTextScaleX(hintScaleX); + stripView.addView(hintView); + setLayoutWeight( + hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); + } + } + + /** + * Construct a {@link SuggestionsView} for showing suggested words for completion. + * @param context + * @param attrs + */ + public SuggestionsView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.suggestionsViewStyle); + } + + public SuggestionsView(Context context, AttributeSet attrs, int defStyle) { + // Note: Up to version 10 (Gingerbread) of the API, LinearLayout doesn't have 3-argument + // constructor. + // TODO: Call 3-argument constructor, super(context, attrs, defStyle), when we abandon + // backward compatibility with the version 10 or earlier of the API. + super(context, attrs); + if (defStyle != R.attr.suggestionsViewStyle) { + throw new IllegalArgumentException( + "can't accept defStyle other than R.attr.suggestionsViewStyle: defStyle=" + + defStyle); + } + setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable( + context, attrs, defStyle, R.style.SuggestionsViewStyle)); + + final LayoutInflater inflater = LayoutInflater.from(context); + inflater.inflate(R.layout.suggestions_strip, this); + + mPreviewPopup = new PopupWindow(context); + mPreviewText = (TextView) inflater.inflate(R.layout.suggestion_preview, null); + mPreviewPopup.setWindowLayoutMode( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mPreviewPopup.setContentView(mPreviewText); + mPreviewPopup.setBackgroundDrawable(null); + + mSuggestionsPlacer = (ViewGroup)findViewById(R.id.suggestions_placer); + 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); + 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)); + } + + mStripParams = new SuggestionsStripParams(context, attrs, defStyle, mWords, mDividers, + mInfos); + mStripParams.mWordToSaveView.setOnClickListener(this); + + mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); + mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer + .findViewById(R.id.more_suggestions_view); + mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView); + mMoreSuggestionsWindow = new PopupWindow(context); + mMoreSuggestionsWindow.setWindowLayoutMode( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mMoreSuggestionsWindow.setBackgroundDrawable(null); + } + + /** + * A connection back to the input method. + * @param listener + */ + public void setListener(Listener listener, View inputView) { + mListener = listener; + mKeyboardView = inputView.findViewById(R.id.keyboard_view); + } + + public void setSuggestions(SuggestedWords suggestions) { + if (suggestions == null) + return; + mSuggestions = suggestions; + if (mShowingAutoCorrectionInverted) { + mHandler.postUpdateSuggestions(); + } else { + updateSuggestions(); + } + } + + private void updateSuggestions() { + clear(); + if (mSuggestions.size() == 0) + return; + + mStripParams.layout(mSuggestions, mSuggestionsStrip, mSuggestionsPlacer, getWidth()); + } + + private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) { + if (DBG && pos < suggestions.size()) { + final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); + if (wordInfo != null) { + final CharSequence debugInfo = wordInfo.getDebugString(); + if (!TextUtils.isEmpty(debugInfo)) { + return debugInfo; + } + } + } + return null; + } + + private static void setLayoutWeight(View v, float weight, 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(CharSequence text, int maxWidth, 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(CharSequence text, int maxWidth, + 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(CharSequence text, 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(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; + } + } + + public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) { + final CharSequence inverted = mStripParams.getInvertedText(autoCorrectedWord); + if (inverted == null) + return; + final TextView tv = mWords.get(1); + tv.setText(inverted); + mShowingAutoCorrectionInverted = true; + } + + public boolean isShowingAddToDictionaryHint() { + return mSuggestionsStrip.getChildCount() > 0 + && mSuggestionsStrip.getChildAt(0) == mStripParams.mWordToSaveView; + } + + public void showAddToDictionaryHint(CharSequence word) { + clear(); + mStripParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth()); + } + + public boolean dismissAddToDictionaryHint() { + if (isShowingAddToDictionaryHint()) { + clear(); + return true; + } + return false; + } + + public SuggestedWords getSuggestions() { + return mSuggestions; + } + + public void clear() { + mShowingAutoCorrectionInverted = false; + mSuggestionsPlacer.removeAllViews(); + mSuggestionsPlacer.addView(mSuggestionsStrip); + mSuggestionsStrip.removeAllViews(); + mMoreSuggestionsWindow.dismiss(); + } + + private void hidePreview() { + mPreviewPopup.dismiss(); + } + + private void showPreview(View view, CharSequence word) { + if (TextUtils.isEmpty(word)) + return; + + final TextView previewText = mPreviewText; + previewText.setTextColor(mStripParams.mColorTypedWord); + previewText.setText(word); + previewText.measure( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final int[] offsetInWindow = new int[2]; + view.getLocationInWindow(offsetInWindow); + final int posX = offsetInWindow[0]; + final int posY = offsetInWindow[1] - previewText.getMeasuredHeight(); + final PopupWindow previewPopup = mPreviewPopup; + if (previewPopup.isShowing()) { + previewPopup.update(posX, posY, previewPopup.getWidth(), previewPopup.getHeight()); + } else { + previewPopup.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY); + } + previewText.setVisibility(VISIBLE); + mHandler.postHidePreview(); + } + + private void addToDictionary(CharSequence word) { + if (mListener.addWordToDictionary(word.toString())) { + final CharSequence message = getContext().getString(R.string.added_word, word); + showPreview(mStripParams.mWordToSaveView, message); + } + } + + private final KeyboardActionListener mMoreSuggestionsListener = + new KeyboardActionListener.Adapter() { + @Override + public boolean onCustomRequest(int requestCode) { + final int index = requestCode; + final CharSequence word = mSuggestions.getWord(index); + mListener.pickSuggestionManually(index, word); + mMoreSuggestionsView.dismissMoreKeysPanel(); + return true; + } + + @Override + public void onCancelInput() { + mMoreSuggestionsView.dismissMoreKeysPanel(); + } + }; + + private final MoreKeysPanel.Controller mMoreSuggestionsController = + new MoreKeysPanel.Controller() { + @Override + public boolean dismissMoreKeysPanel() { + if (mMoreSuggestionsWindow.isShowing()) { + mMoreSuggestionsWindow.dismiss(); + return true; + } + return false; + } + }; + + @Override + public boolean onLongClick(View view) { + final SuggestionsStripParams params = mStripParams; + if (params.mMoreSuggestionsAvailable) { + final int stripWidth = getWidth(); + final View container = mMoreSuggestionsContainer; + final int maxWidth = stripWidth - container.getPaddingLeft() + - container.getPaddingRight(); + final DisplayMetrics dm = getContext().getResources().getDisplayMetrics(); + // TODO: Revise how we determine the height + final int maxHeight = dm.heightPixels - mKeyboardView.getHeight() - getHeight() * 3; + final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; + builder.layout(mSuggestions, params.mSuggestionsCountInStrip, maxWidth, maxHeight); + 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 = 0; + moreKeysPanel.showMoreKeysPanel( + this, mMoreSuggestionsController, pointX, pointY, + mMoreSuggestionsWindow, mMoreSuggestionsListener); + // TODO: Should figure out how to select the pointer tracker correctly. + final PointerTracker tracker = PointerTracker.getPointerTracker(0, moreKeysPanel); + final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); + final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); + tracker.onShowMoreKeysPanel( + translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel); + view.setPressed(false); + // TODO: Should gray out the keyboard here as well? + return true; + } + return false; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent me) { + if (!mMoreSuggestionsWindow.isShowing()) { + return super.dispatchTouchEvent(me); + } + final int action = me.getAction(); + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final PointerTracker tracker = PointerTracker.getPointerTracker(id, mMoreSuggestionsView); + final int x = mMoreSuggestionsView.translateX((int)me.getX(index)); + final int y = mMoreSuggestionsView.translateY((int)me.getY(index)); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + tracker.onDownEvent(x, y, eventTime, mMoreSuggestionsView); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + tracker.onUpEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_MOVE: + tracker.onMoveEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_CANCEL: + tracker.onCancelEvent(x, y, eventTime); + break; + } + return true; + } + + @Override + public void onClick(View view) { + if (view == mStripParams.mWordToSaveView) { + addToDictionary((CharSequence)view.getTag()); + clear(); + return; + } + + final Object tag = view.getTag(); + if (!(tag instanceof Integer)) + return; + final int index = (Integer) tag; + if (index >= mSuggestions.size()) + return; + + final CharSequence word = mSuggestions.getWord(index); + mListener.pickSuggestionManually(index, word); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mHandler.cancelAllMessages(); + hidePreview(); + } +} diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 60a4cfb38..6263ebefa 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -16,15 +16,7 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; -import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; -import com.android.inputmethod.compat.InputTypeCompatUtils; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; - import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.os.AsyncTask; @@ -37,6 +29,13 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.inputmethod.EditorInfo; +import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; +import com.android.inputmethod.compat.InputTypeCompatUtils; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; + import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; @@ -47,7 +46,6 @@ import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -175,21 +173,21 @@ public class Utils { // If user selected aggressive auto correction mode, there is no need to use the safety // net. if (suggest.isAggressiveAutoCorrectionMode()) return false; - CharSequence typedWord = suggestions.getWord(0); + final CharSequence typedWord = suggestions.getWord(0); // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH, // we should not use net because relatively edit distance can be big. if (typedWord.length() < MINIMUM_SAFETY_NET_CHAR_LENGTH) return false; - CharSequence candidateWord = suggestions.getWord(1); + final CharSequence suggestionWord = suggestions.getWord(1); final int typedWordLength = typedWord.length(); final int maxEditDistanceOfNativeDictionary = typedWordLength < 5 ? 2 : typedWordLength / 2; - final int distance = Utils.editDistance(typedWord, candidateWord); + final int distance = Utils.editDistance(typedWord, suggestionWord); if (DBG) { Log.d(TAG, "Autocorrected edit distance = " + distance + ", " + maxEditDistanceOfNativeDictionary); } if (distance > maxEditDistanceOfNativeDictionary) { if (DBG) { - Log.d(TAG, "Safety net: before = " + typedWord + ", after = " + candidateWord); + Log.d(TAG, "Safety net: before = " + typedWord + ", after = " + suggestionWord); Log.w(TAG, "(Error) The edit distance of this correction exceeds limit. " + "Turning off auto-correction."); } @@ -717,7 +715,7 @@ public class Utils { // Don't cache suggestions.size(), since we may be removing items while (i < suggestions.size()) { final CharSequence cur = suggestions.get(i); - // Compare each candidate with each previous candidate + // Compare each suggestion with each previous suggestion for (int j = 0; j < i; j++) { CharSequence previous = suggestions.get(j); if (TextUtils.equals(cur, previous)) { -- cgit v1.2.3-83-g751a