diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/CandidateView.java')
-rw-r--r--[-rwxr-xr-x] | java/src/com/android/inputmethod/latin/CandidateView.java | 806 |
1 files changed, 437 insertions, 369 deletions
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index 68f288925..09fd3b473 100755..100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -1,12 +1,12 @@ /* - * Copyright (C) 2008 The Android Open Source Project - * + * 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 @@ -18,75 +18,128 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; +import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.Typeface; -import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +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.view.GestureDetector; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup.LayoutParams; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +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.latin.SuggestedWords.SuggestedWordInfo; + import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -public class CandidateView extends View { - - private static final int OUT_OF_BOUNDS_WORD_INDEX = -1; - private static final int OUT_OF_BOUNDS_X_COORD = -1; +public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener { - private LatinIME mService; - private final ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); - private boolean mShowingCompletions; - private CharSequence mSelectedString; - private int mSelectedIndex; - private int mTouchX = OUT_OF_BOUNDS_X_COORD; - private final Drawable mSelectionHighlight; - private boolean mTypedWordValid; - - private boolean mHaveMinimalSuggestion; - - private Rect mBgPadding; + public interface Listener { + public boolean addWordToDictionary(String word); + public void pickSuggestionManually(int index, CharSequence word); + } - private final TextView mPreviewText; + private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); + private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); + // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. + private static final int MAX_SUGGESTIONS = 18; + private static final int UNSPECIFIED_MEASURESPEC = MeasureSpec.makeMeasureSpec( + 0, MeasureSpec.UNSPECIFIED); + + private static final boolean DBG = LatinImeLogger.sDBG; + + private static final int NUM_CANDIDATES_IN_STRIP = 3; + private final ImageView mExpandCandidatesPane; + private final ImageView mCloseCandidatesPane; + private ViewGroup mCandidatesPane; + private ViewGroup mCandidatesPaneContainer; + private View mKeyboardView; + private final ArrayList<TextView> mWords = new ArrayList<TextView>(); + private final ArrayList<TextView> mInfos = new ArrayList<TextView>(); + private final ArrayList<View> mDividers = new ArrayList<View>(); + private final int mCandidatePadding; + private final int mCandidateStripHeight; + private final CharacterStyle mInvertedForegroundColorSpan; + private final CharacterStyle mInvertedBackgroundColorSpan; + private final int mAutoCorrectHighlight; + 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 final int mColorTypedWord; + private final int mColorAutoCorrect; + private final int mColorSuggestedCandidate; private final PopupWindow mPreviewPopup; - private int mCurrentWordIndex; - private Drawable mDivider; - - private static final int MAX_SUGGESTIONS = 32; - private static final int SCROLL_PIXELS = 20; - - private final int[] mWordWidth = new int[MAX_SUGGESTIONS]; - private final int[] mWordX = new int[MAX_SUGGESTIONS]; - private int mPopupPreviewX; - private int mPopupPreviewY; - - private static final int X_GAP = 10; - - private final int mColorNormal; - private final int mColorRecommended; - private final int mColorOther; - private final Paint mPaint; - private final int mDescent; - private boolean mScrolled; + private final TextView mPreviewText; + + private Listener mListener; + private SuggestedWords mSuggestions = SuggestedWords.EMPTY; + private boolean mShowingAutoCorrectionInverted; private boolean mShowingAddToDictionary; - private CharSequence mAddToDictionaryHint; - private int mTargetScrollX; + private final UiHandler mHandler = new UiHandler(); - private final int mMinTouchableWidth; + private class UiHandler extends Handler { + private static final int MSG_HIDE_PREVIEW = 0; + private static final int MSG_UPDATE_SUGGESTION = 1; - private int mTotalWidth; - - private final GestureDetector mGestureDetector; + private static final long DELAY_HIDE_PREVIEW = 1000; + private static final long DELAY_UPDATE_SUGGESTION = 300; + + @Override + public void dispatchMessage(Message msg) { + switch (msg.what) { + case MSG_HIDE_PREVIEW: + hidePreview(); + break; + case MSG_UPDATE_SUGGESTION: + 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(); + } + } /** * Construct a CandidateView for showing suggested words for completion. @@ -94,245 +147,298 @@ public class CandidateView extends View { * @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); - mSelectionHighlight = context.getResources().getDrawable( - R.drawable.list_selector_background_pressed); + if (defStyle != R.attr.candidateViewStyle) { + throw new IllegalArgumentException( + "can't accept defStyle other than R.attr.candidayeViewStyle: defStyle=" + + defStyle); + } + setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable( + context, attrs, defStyle, R.style.CandidateViewStyle)); - LayoutInflater inflate = - (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); Resources res = context.getResources(); + LayoutInflater inflater = LayoutInflater.from(context); + inflater.inflate(R.layout.candidates_strip, this); + mPreviewPopup = new PopupWindow(context); - mPreviewText = (TextView) inflate.inflate(R.layout.candidate_preview, null); - mPreviewPopup.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + 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); - mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation); - mColorNormal = res.getColor(R.color.candidate_normal); - mColorRecommended = res.getColor(R.color.candidate_recommended); - mColorOther = res.getColor(R.color.candidate_other); - mDivider = res.getDrawable(R.drawable.keyboard_suggest_strip_divider); - mAddToDictionaryHint = res.getString(R.string.hint_add_to_dictionary); - - mPaint = new Paint(); - mPaint.setColor(mColorNormal); - mPaint.setAntiAlias(true); - mPaint.setTextSize(mPreviewText.getTextSize()); - mPaint.setStrokeWidth(0); - mPaint.setTextAlign(Align.CENTER); - mDescent = (int) mPaint.descent(); - mMinTouchableWidth = (int)res.getDimension(R.dimen.candidate_min_touchable_width); - - mGestureDetector = new GestureDetector( - new CandidateStripGestureListener(mMinTouchableWidth)); - setWillNotDraw(false); - setHorizontalScrollBarEnabled(false); - setVerticalScrollBarEnabled(false); - scrollTo(0, getScrollY()); - } - - private class CandidateStripGestureListener extends GestureDetector.SimpleOnGestureListener { - private final int mTouchSlopSquare; - public CandidateStripGestureListener(int touchSlop) { - // Slightly reluctant to scroll to be able to easily choose the suggestion - mTouchSlopSquare = touchSlop * touchSlop; - } - - @Override - public void onLongPress(MotionEvent me) { - if (mSuggestions.size() > 0) { - if (me.getX() + getScrollX() < mWordWidth[0] && getScrollX() < 10) { - longPressFirstWord(); - } + mCandidatePadding = res.getDimensionPixelOffset(R.dimen.candidate_padding); + mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height); + for (int i = 0; i < MAX_SUGGESTIONS; i++) { + final TextView word, info; + switch (i) { + case 0: + word = (TextView)findViewById(R.id.word_left); + word.setPadding(mCandidatePadding, 0, 0, 0); + info = (TextView)findViewById(R.id.info_left); + break; + case 1: + word = (TextView)findViewById(R.id.word_center); + info = (TextView)findViewById(R.id.info_center); + break; + case 2: + word = (TextView)findViewById(R.id.word_right); + info = (TextView)findViewById(R.id.info_right); + break; + default: + word = (TextView)inflater.inflate(R.layout.candidate_word, null); + info = (TextView)inflater.inflate(R.layout.candidate_info, null); + break; } - } - - @Override - public boolean onDown(MotionEvent e) { - mScrolled = false; - return false; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - if (!mScrolled) { - // This is applied only when we recognize that scrolling is starting. - final int deltaX = (int) (e2.getX() - e1.getX()); - final int deltaY = (int) (e2.getY() - e1.getY()); - final int distance = (deltaX * deltaX) + (deltaY * deltaY); - if (distance < mTouchSlopSquare) { - return true; - } - mScrolled = true; + word.setTag(i); + word.setOnClickListener(this); + if (i == 0) + word.setOnLongClickListener(this); + mWords.add(word); + mInfos.add(info); + if (i > 0) { + final View divider = inflater.inflate(R.layout.candidate_divider, null); + divider.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC); + mDividers.add(divider); } + } - final int width = getWidth(); - mScrolled = true; - int scrollX = getScrollX(); - scrollX += (int) distanceX; - if (scrollX < 0) { - scrollX = 0; + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle); + mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 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); + mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff); + mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord); + + mExpandCandidatesPane = (ImageView)findViewById(R.id.expand_candidates_pane); + mExpandCandidatesPane.setImageDrawable( + a.getDrawable(R.styleable.CandidateView_iconExpandPane)); + mExpandCandidatesPane.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + expandCandidatesPane(); } - if (distanceX > 0 && scrollX + width > mTotalWidth) { - scrollX -= (int) distanceX; + }); + mCloseCandidatesPane = (ImageView)findViewById(R.id.close_candidates_pane); + mCloseCandidatesPane.setImageDrawable( + a.getDrawable(R.styleable.CandidateView_iconClosePane)); + mCloseCandidatesPane.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + closeCandidatesPane(); } - mTargetScrollX = scrollX; - scrollTo(scrollX, getScrollY()); - hidePreview(); - invalidate(); - return true; - } + }); + + a.recycle(); } /** - * A connection back to the service to communicate with the text field + * A connection back to the input method. * @param listener */ - public void setService(LatinIME listener) { - mService = listener; + public void setListener(Listener listener, View inputView) { + mListener = listener; + mKeyboardView = inputView.findViewById(R.id.keyboard_view); + mCandidatesPane = FrameLayoutCompatUtils.getPlacer( + (ViewGroup)inputView.findViewById(R.id.candidates_pane)); + mCandidatesPane.setOnClickListener(this); + mCandidatesPaneContainer = (ViewGroup)inputView.findViewById( + R.id.candidates_pane_container); } - - @Override - public int computeHorizontalScrollRange() { - return mTotalWidth; + + public void setSuggestions(SuggestedWords suggestions) { + if (suggestions == null) + return; + mSuggestions = suggestions; + if (mShowingAutoCorrectionInverted) { + mHandler.postUpdateSuggestions(); + } else { + updateSuggestions(); + } } - /** - * If the canvas is null, then only touch calculations are performed to pick the target - * candidate. - */ - @Override - protected void onDraw(Canvas canvas) { - if (canvas != null) { - super.onDraw(canvas); + private CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) { + if (!isAutoCorrect) + return word; + final Spannable spannedWord = new SpannableString(word); + if ((mAutoCorrectHighlight & AUTO_CORRECT_BOLD) != 0) + spannedWord.setSpan(BOLD_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + if ((mAutoCorrectHighlight & AUTO_CORRECT_UNDERLINE) != 0) + spannedWord.setSpan(UNDERLINE_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + return spannedWord; + } + + private int getCandidateTextColor(boolean isAutoCorrect, boolean isSuggestedCandidate, + SuggestedWordInfo info) { + final int color; + if (isAutoCorrect) { + color = mColorAutoCorrect; + } else if (isSuggestedCandidate) { + color = mColorSuggestedCandidate; + } else { + color = mColorTypedWord; } - mTotalWidth = 0; - - final int height = getHeight(); - if (mBgPadding == null) { - mBgPadding = new Rect(0, 0, 0, 0); - if (getBackground() != null) { - getBackground().getPadding(mBgPadding); - } - mDivider.setBounds(0, 0, mDivider.getIntrinsicWidth(), - mDivider.getIntrinsicHeight()); + if (info != null && info.isPreviousSuggestedWord()) { + final int newAlpha = (int)(Color.alpha(color) * 0.5f); + return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); + } else { + return color; } + } - final int count = mSuggestions.size(); - final Rect bgPadding = mBgPadding; - final Paint paint = mPaint; - final int touchX = mTouchX; - final int scrollX = getScrollX(); - final boolean scrolled = mScrolled; - final boolean typedWordValid = mTypedWordValid; - final int y = (int) (height + mPaint.getTextSize() - mDescent) / 2; - - boolean existsAutoCompletion = false; + private void updateSuggestions() { + final SuggestedWords suggestions = mSuggestions; + final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList; + clear(); + final int paneWidth = getWidth(); + final int dividerWidth = mDividers.get(0).getMeasuredWidth(); + final int dividerHeight = mDividers.get(0).getMeasuredHeight(); int x = 0; + int y = 0; + int fromIndex = NUM_CANDIDATES_IN_STRIP; + final int count = Math.min(mWords.size(), suggestions.size()); + closeCandidatesPane(); + mExpandCandidatesPane.setEnabled(count >= NUM_CANDIDATES_IN_STRIP); for (int i = 0; i < count; i++) { - CharSequence suggestion = mSuggestions.get(i); + final CharSequence suggestion = suggestions.getWord(i); if (suggestion == null) continue; - final int wordLength = suggestion.length(); - - paint.setColor(mColorNormal); - if (mHaveMinimalSuggestion - && ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) { - paint.setTypeface(Typeface.DEFAULT_BOLD); - paint.setColor(mColorRecommended); - existsAutoCompletion = true; - } else if (i != 0 || (wordLength == 1 && count > 1)) { - // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1 and - // there are multiple suggestions, such as the default punctuation list. - paint.setColor(mColorOther); - } - int wordWidth; - if ((wordWidth = mWordWidth[i]) == 0) { - float textWidth = paint.measureText(suggestion, 0, wordLength); - wordWidth = Math.max(mMinTouchableWidth, (int) textWidth + X_GAP * 2); - mWordWidth[i] = wordWidth; - } - mWordX[i] = x; - - if (touchX != OUT_OF_BOUNDS_X_COORD && !scrolled - && touchX + scrollX >= x && touchX + scrollX < x + wordWidth) { - if (canvas != null && !mShowingAddToDictionary) { - canvas.translate(x, 0); - mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); - mSelectionHighlight.draw(canvas); - canvas.translate(-x, 0); - } - mSelectedString = suggestion; - mSelectedIndex = i; + final SuggestedWordInfo suggestionInfo = (suggestedWordInfoList != null) + ? suggestedWordInfoList.get(i) : null; + final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion + && ((i == 1 && !suggestions.mTypedWordValid) + || (i == 0 && suggestions.mTypedWordValid)); + // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1 + // and there are multiple suggestions, such as the default punctuation list. + // TODO: Need to revisit this logic with bigram suggestions + final boolean isSuggestedCandidate = (i != 0); + final boolean isPunctuationSuggestions = (suggestion.length() == 1 && count > 1); + + final TextView word = mWords.get(i); + // TODO: Reorder candidates in strip as appropriate. The center candidate should hold + // the word when space is typed (valid typed word or auto corrected word). + word.setTextColor(getCandidateTextColor(isAutoCorrect, + isSuggestedCandidate || isPunctuationSuggestions, suggestionInfo)); + word.setText(getStyledCandidateWord(suggestion, isAutoCorrect)); + // TODO: call TextView.setTextScaleX() to fit the candidate in single line. + word.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC); + final int width = word.getMeasuredWidth(); + final int height = word.getMeasuredHeight(); + + final TextView info; + if (DBG && suggestionInfo != null + && !TextUtils.isEmpty(suggestionInfo.getDebugString())) { + info = mInfos.get(i); + info.setText(suggestionInfo.getDebugString()); + info.setVisibility(View.VISIBLE); + info.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC); + } else { + info = null; } - if (canvas != null) { - canvas.drawText(suggestion, 0, wordLength, x + wordWidth / 2, y, paint); - paint.setColor(mColorOther); - canvas.translate(x + wordWidth, 0); - // Draw a divider unless it's after the hint - if (!(mShowingAddToDictionary && i == 1)) { - mDivider.draw(canvas); + if (i < NUM_CANDIDATES_IN_STRIP) { + if (info != null) { + final int infoWidth = info.getMeasuredWidth(); + FrameLayoutCompatUtils.placeViewAt( + info, x + width - infoWidth, y, infoWidth, info.getMeasuredHeight()); } - canvas.translate(-x - wordWidth, 0); + } else { + // TODO: Handle overflow case. + if (dividerWidth + x + width >= paneWidth) { + centeringCandidates(fromIndex, i - 1, x, paneWidth); + x = 0; + y += mCandidateStripHeight; + fromIndex = i; + } + if (x != 0) { + final View divider = mDividers.get(i - NUM_CANDIDATES_IN_STRIP); + mCandidatesPane.addView(divider); + FrameLayoutCompatUtils.placeViewAt( + divider, x, y + (mCandidateStripHeight - dividerHeight) / 2, + dividerWidth, dividerHeight); + x += dividerWidth; + } + mCandidatesPane.addView(word); + FrameLayoutCompatUtils.placeViewAt( + word, x, y + (mCandidateStripHeight - height) / 2, width, height); + if (info != null) { + mCandidatesPane.addView(info); + final int infoWidth = info.getMeasuredWidth(); + FrameLayoutCompatUtils.placeViewAt( + info, x + width - infoWidth, y, infoWidth, info.getMeasuredHeight()); + } + x += width; } - paint.setTypeface(Typeface.DEFAULT); - x += wordWidth; } - mService.onAutoCompletionStateChanged(existsAutoCompletion); - mTotalWidth = x; - if (mTargetScrollX != scrollX) { - scrollToTarget(); + if (x != 0) { + // Centering last candidates row. + centeringCandidates(fromIndex, count - 1, x, paneWidth); } } - - private void scrollToTarget() { - int scrollX = getScrollX(); - if (mTargetScrollX > scrollX) { - scrollX += SCROLL_PIXELS; - if (scrollX >= mTargetScrollX) { - scrollX = mTargetScrollX; - scrollTo(scrollX, getScrollY()); - requestLayout(); - } else { - scrollTo(scrollX, getScrollY()); - } + + private void centeringCandidates(int from, int to, int width, int paneWidth) { + final ViewGroup pane = mCandidatesPane; + final int fromIndex = pane.indexOfChild(mWords.get(from)); + final int toIndex; + if (mInfos.get(to).getParent() != null) { + toIndex = pane.indexOfChild(mInfos.get(to)); } else { - scrollX -= SCROLL_PIXELS; - if (scrollX <= mTargetScrollX) { - scrollX = mTargetScrollX; - scrollTo(scrollX, getScrollY()); - requestLayout(); - } else { - scrollTo(scrollX, getScrollY()); - } + toIndex = pane.indexOfChild(mWords.get(to)); + } + final int offset = (paneWidth - width) / 2; + for (int index = fromIndex; index <= toIndex; index++) { + offsetMargin(pane.getChildAt(index), offset, 0); } - invalidate(); } - - public void setSuggestions(List<CharSequence> suggestions, boolean completions, - boolean typedWordValid, boolean haveMinimalSuggestion) { - clear(); - if (suggestions != null) { - int insertCount = Math.min(suggestions.size(), MAX_SUGGESTIONS); - for (CharSequence suggestion : suggestions) { - mSuggestions.add(suggestion); - if (--insertCount == 0) - break; - } + + private static void offsetMargin(View v, int dx, int dy) { + if (v == null) + return; + final ViewGroup.LayoutParams lp = v.getLayoutParams(); + if (lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp; + mlp.setMargins(mlp.leftMargin + dx, mlp.topMargin + dy, 0, 0); } - mShowingCompletions = completions; - mTypedWordValid = typedWordValid; - scrollTo(0, getScrollY()); - mTargetScrollX = 0; - mHaveMinimalSuggestion = haveMinimalSuggestion; - // Compute the total width - onDraw(null); - invalidate(); - requestLayout(); + } + + private void expandCandidatesPane() { + mExpandCandidatesPane.setVisibility(View.GONE); + mCloseCandidatesPane.setVisibility(View.VISIBLE); + mCandidatesPaneContainer.setMinimumHeight(mKeyboardView.getMeasuredHeight()); + mCandidatesPaneContainer.setVisibility(View.VISIBLE); + mKeyboardView.setVisibility(View.GONE); + } + + private void closeCandidatesPane() { + mExpandCandidatesPane.setVisibility(View.VISIBLE); + mCloseCandidatesPane.setVisibility(View.GONE); + mCandidatesPaneContainer.setVisibility(View.GONE); + mKeyboardView.setVisibility(View.VISIBLE); + } + + public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) { + if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0) + return; + final TextView tv = mWords.get(1); + final Spannable word = new SpannableString(autoCorrectedWord); + final int wordLength = word.length(); + word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + tv.setText(word); + mShowingAutoCorrectionInverted = true; } public boolean isShowingAddToDictionaryHint() { @@ -340,11 +446,14 @@ public class CandidateView extends View { } public void showAddToDictionaryHint(CharSequence word) { - ArrayList<CharSequence> suggestions = new ArrayList<CharSequence>(); - suggestions.add(word); - suggestions.add(mAddToDictionaryHint); - setSuggestions(suggestions, false, false, false); + SuggestedWords.Builder builder = new SuggestedWords.Builder() + .addWord(word) + .addWord(getContext().getText(R.string.hint_add_to_dictionary)); + setSuggestions(builder.build()); mShowingAddToDictionary = true; + // Disable R.string.hint_add_to_dictionary button + TextView tv = mWords.get(1); + tv.setClickable(false); } public boolean dismissAddToDictionaryHint() { @@ -353,135 +462,94 @@ public class CandidateView extends View { return true; } - /* package */ List<CharSequence> getSuggestions() { + public SuggestedWords getSuggestions() { return mSuggestions; } public void clear() { - // Don't call mSuggestions.clear() because it's being used for logging - // in LatinIME.pickSuggestionManually(). - mSuggestions.clear(); - mTouchX = OUT_OF_BOUNDS_X_COORD; - mSelectedString = null; - mSelectedIndex = -1; mShowingAddToDictionary = false; - invalidate(); - Arrays.fill(mWordWidth, 0); - Arrays.fill(mWordX, 0); - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - - if (mGestureDetector.onTouchEvent(me)) { - return true; - } - - int action = me.getAction(); - int x = (int) me.getX(); - int y = (int) me.getY(); - mTouchX = x; - - switch (action) { - case MotionEvent.ACTION_DOWN: - invalidate(); - break; - case MotionEvent.ACTION_MOVE: - if (y <= 0) { - // Fling up!? - if (mSelectedString != null) { - // If there are completions from the application, we don't change the state to - // STATE_PICKED_SUGGESTION - if (!mShowingCompletions) { - // This "acceptedSuggestion" will not be counted as a word because - // it will be counted in pickSuggestion instead. - TextEntryState.acceptedSuggestion(mSuggestions.get(0), - mSelectedString); - } - mService.pickSuggestionManually(mSelectedIndex, mSelectedString); - mSelectedString = null; - mSelectedIndex = -1; - } - } - break; - case MotionEvent.ACTION_UP: - if (!mScrolled) { - if (mSelectedString != null) { - if (mShowingAddToDictionary) { - longPressFirstWord(); - clear(); - } else { - if (!mShowingCompletions) { - TextEntryState.acceptedSuggestion(mSuggestions.get(0), - mSelectedString); - } - mService.pickSuggestionManually(mSelectedIndex, mSelectedString); - } - } - } - mSelectedString = null; - mSelectedIndex = -1; - requestLayout(); - hidePreview(); - invalidate(); - break; + mShowingAutoCorrectionInverted = false; + for (int i = 0; i < NUM_CANDIDATES_IN_STRIP; i++) { + mWords.get(i).setText(null); + mInfos.get(i).setVisibility(View.GONE); } - return true; + mCandidatesPane.removeAllViews(); } private void hidePreview() { - mTouchX = OUT_OF_BOUNDS_X_COORD; - mCurrentWordIndex = OUT_OF_BOUNDS_WORD_INDEX; mPreviewPopup.dismiss(); } - - private void showPreview(int wordIndex, String altText) { - int oldWordIndex = mCurrentWordIndex; - mCurrentWordIndex = wordIndex; - // If index changed or changing text - if (oldWordIndex != mCurrentWordIndex || altText != null) { - if (wordIndex == OUT_OF_BOUNDS_WORD_INDEX) { - hidePreview(); - } else { - CharSequence word = altText != null? altText : mSuggestions.get(wordIndex); - mPreviewText.setText(word); - mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - int wordWidth = (int) (mPaint.measureText(word, 0, word.length()) + X_GAP * 2); - final int popupWidth = wordWidth - + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight(); - final int popupHeight = mPreviewText.getMeasuredHeight(); - //mPreviewText.setVisibility(INVISIBLE); - mPopupPreviewX = mWordX[wordIndex] - mPreviewText.getPaddingLeft() - getScrollX() - + (mWordWidth[wordIndex] - wordWidth) / 2; - mPopupPreviewY = - popupHeight; - int [] offsetInWindow = new int[2]; - getLocationInWindow(offsetInWindow); - if (mPreviewPopup.isShowing()) { - mPreviewPopup.update(mPopupPreviewX, mPopupPreviewY + offsetInWindow[1], - popupWidth, popupHeight); - } else { - mPreviewPopup.setWidth(popupWidth); - mPreviewPopup.setHeight(popupHeight); - mPreviewPopup.showAtLocation(this, Gravity.NO_GRAVITY, mPopupPreviewX, - mPopupPreviewY + offsetInWindow[1]); - } - mPreviewText.setVisibility(VISIBLE); - } + + private void showPreview(int index, CharSequence word) { + if (TextUtils.isEmpty(word)) + return; + + final TextView previewText = mPreviewText; + previewText.setTextColor(mColorTypedWord); + previewText.setText(word); + previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + View v = mWords.get(index); + final int[] offsetInWindow = new int[2]; + v.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 longPressFirstWord() { - CharSequence word = mSuggestions.get(0); - if (word.length() < 2) return; - if (mService.addWordToDictionary(word.toString())) { - showPreview(0, getContext().getResources().getString(R.string.added_word, word)); + private void addToDictionary(CharSequence word) { + if (mListener.addWordToDictionary(word.toString())) { + showPreview(0, getContext().getString(R.string.added_word, word)); } } - + + @Override + public boolean onLongClick(View view) { + final Object tag = view.getTag(); + if (!(tag instanceof Integer)) + return true; + final int index = (Integer) tag; + if (index >= mSuggestions.size()) + return true; + + final CharSequence word = mSuggestions.getWord(index); + if (word.length() < 2) + return false; + addToDictionary(word); + return true; + } + + @Override + public void onClick(View view) { + 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); + if (mShowingAddToDictionary && index == 0) { + addToDictionary(word); + } else { + mListener.pickSuggestionManually(index, word); + } + // Because some punctuation letters are not treated as word separator depending on locale, + // {@link #setSuggestions} might not be called and candidates pane left opened. + closeCandidatesPane(); + } + @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); + mHandler.cancelAllMessages(); hidePreview(); } } |