aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/CandidateView.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/CandidateView.java')
-rw-r--r--[-rwxr-xr-x]java/src/com/android/inputmethod/latin/CandidateView.java806
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();
}
}