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--java/src/com/android/inputmethod/latin/CandidateView.java658
1 files changed, 542 insertions, 116 deletions
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index c52f6b2c4..565b01d5a 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -16,17 +16,17 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
-import android.os.Handler;
+import android.graphics.drawable.Drawable;
import android.os.Message;
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;
@@ -40,54 +40,95 @@ import android.view.View;
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.List;
public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener {
+ public interface Listener {
+ public boolean addWordToDictionary(String word);
+ public void pickSuggestionManually(int index, CharSequence word);
+ }
+
private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
- private static final int MAX_SUGGESTIONS = 16;
+ // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
+ private static final int MAX_SUGGESTIONS = 18;
+ private static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
+ private static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
private static final boolean DBG = LatinImeLogger.sDBG;
- private final ArrayList<View> mWords = new ArrayList<View>();
- private final boolean mConfigCandidateHighlightFontColorEnabled;
+ private final ViewGroup mCandidatesStrip;
+ private final int mCandidateCountInStrip;
+ private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3;
+ private final ViewGroup mCandidatesPaneControl;
+ private final TextView mExpandCandidatesPane;
+ private final TextView 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 mCandidateStripHeight;
private final CharacterStyle mInvertedForegroundColorSpan;
private final CharacterStyle mInvertedBackgroundColorSpan;
- private final int mColorNormal;
- private final int mColorRecommended;
- private final int mColorOther;
+ 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 final TextView mPreviewText;
- private LatinIME mService;
+ private final View mTouchToSave;
+ private final TextView mWordToSave;
+
+ private Listener mListener;
private SuggestedWords mSuggestions = SuggestedWords.EMPTY;
private boolean mShowingAutoCorrectionInverted;
private boolean mShowingAddToDictionary;
- private final UiHandler mHandler = new UiHandler();
+ private final CandidateViewLayoutParams mParams;
+ private static final int PUNCTUATIONS_IN_STRIP = 6;
+ private static final float MIN_TEXT_XSCALE = 0.75f;
+
+ private final UiHandler mHandler = new UiHandler(this);
- private class UiHandler extends Handler {
+ private static class UiHandler extends StaticInnerHandlerWrapper<CandidateView> {
private static final int MSG_HIDE_PREVIEW = 0;
private static final int MSG_UPDATE_SUGGESTION = 1;
private static final long DELAY_HIDE_PREVIEW = 1000;
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:
- hidePreview();
+ candidateView.hidePreview();
break;
case MSG_UPDATE_SUGGESTION:
- updateSuggestions();
+ candidateView.updateSuggestions();
break;
}
}
@@ -117,59 +158,233 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
}
+ private static class CandidateViewLayoutParams {
+ public final TextPaint mPaint;
+ public final int mPadding;
+ public final int mDividerWidth;
+ public final int mDividerHeight;
+ public final int mControlWidth;
+ private final int mAutoCorrectHighlight;
+
+ public final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+
+ public int mCountInStrip;
+ // True if the mCountInStrip suggestions can fit in suggestion strip in equally divided
+ // width without squeezing the text.
+ public boolean mCanUseFixedWidthColumns;
+ public int mMaxWidth;
+ public int mAvailableWidthForWords;
+ public int mConstantWidthForPaddings;
+ public int mVariableWidthForWords;
+ public float mScaleX;
+
+ public CandidateViewLayoutParams(Resources res, TextView word, View divider, View control,
+ int autoCorrectHighlight) {
+ mPaint = new TextPaint();
+ final float textSize = res.getDimension(R.dimen.candidate_text_size);
+ mPaint.setTextSize(textSize);
+ mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
+ divider.measure(WRAP_CONTENT, MATCH_PARENT);
+ mDividerWidth = divider.getMeasuredWidth();
+ mDividerHeight = divider.getMeasuredHeight();
+ mControlWidth = control.getMeasuredWidth();
+ mAutoCorrectHighlight = autoCorrectHighlight;
+ }
+
+ public void layoutStrip(SuggestedWords suggestions, int maxWidth, int maxCount) {
+ final int size = suggestions.size();
+ setupTexts(suggestions, size, mAutoCorrectHighlight);
+ mCountInStrip = Math.min(maxCount, size);
+ mScaleX = 1.0f;
+
+ do {
+ mMaxWidth = maxWidth;
+ if (size > mCountInStrip) {
+ mMaxWidth -= mControlWidth;
+ }
+
+ tryLayout();
+
+ if (mCanUseFixedWidthColumns) {
+ return;
+ }
+ if (mVariableWidthForWords <= mAvailableWidthForWords) {
+ return;
+ }
+
+ final float scaleX = mAvailableWidthForWords / (float)mVariableWidthForWords;
+ if (scaleX >= MIN_TEXT_XSCALE) {
+ mScaleX = scaleX;
+ return;
+ }
+
+ mCountInStrip--;
+ } while (mCountInStrip > 1);
+ }
+
+ public void tryLayout() {
+ final int maxCount = mCountInStrip;
+ final int dividers = mDividerWidth * (maxCount - 1);
+ mConstantWidthForPaddings = dividers + mPadding * maxCount;
+ mAvailableWidthForWords = mMaxWidth - mConstantWidthForPaddings;
+
+ mPaint.setTextScaleX(mScaleX);
+ final int maxFixedWidthForWord = (mMaxWidth - dividers) / maxCount - mPadding;
+ mCanUseFixedWidthColumns = true;
+ mVariableWidthForWords = 0;
+ for (int i = 0; i < maxCount; i++) {
+ final int width = getTextWidth(mTexts.get(i), mPaint);
+ if (width > maxFixedWidthForWord)
+ mCanUseFixedWidthColumns = false;
+ mVariableWidthForWords += width;
+ }
+ }
+
+ private void setupTexts(SuggestedWords suggestions, int count, int autoCorrectHighlight) {
+ mTexts.clear();
+ for (int i = 0; i < count; i++) {
+ final CharSequence suggestion = suggestions.getWord(i);
+ if (suggestion == null) continue;
+
+ 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 CharSequence styled = getStyledCandidateWord(suggestion, isAutoCorrect,
+ autoCorrectHighlight);
+ mTexts.add(styled);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "count=%d width=%d avail=%d fixcol=%s scaleX=%4.2f const=%d var=%d",
+ mCountInStrip, mMaxWidth, mAvailableWidthForWords, mCanUseFixedWidthColumns,
+ mScaleX, mConstantWidthForPaddings, mVariableWidthForWords);
+ }
+ }
+
/**
* 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.candidayeViewStyle: defStyle="
+ + defStyle);
+ }
+ setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable(
+ context, attrs, defStyle, R.style.CandidateViewStyle));
+
+ 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);
+ mCandidateCountInStrip = a.getInt(
+ R.styleable.CandidateView_candidateCountInStrip, DEFAULT_CANDIDATE_COUNT_IN_STRIP);
+ a.recycle();
Resources res = context.getResources();
- mPreviewPopup = new PopupWindow(context);
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);
- mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
- mConfigCandidateHighlightFontColorEnabled =
- res.getBoolean(R.bool.config_candidate_highlight_font_color_enabled);
- mColorNormal = res.getColor(R.color.candidate_normal);
- mColorRecommended = res.getColor(R.color.candidate_recommended);
- mColorOther = res.getColor(R.color.candidate_other);
- mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorNormal ^ 0x00ffffff);
- mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorNormal);
+ mCandidatesStrip = (ViewGroup)findViewById(R.id.candidates_strip);
+ mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height);
for (int i = 0; i < MAX_SUGGESTIONS; i++) {
- View v = inflater.inflate(R.layout.candidate, null);
- TextView tv = (TextView)v.findViewById(R.id.candidate_word);
- tv.setTag(i);
- tv.setOnClickListener(this);
+ final TextView word = (TextView)inflater.inflate(R.layout.candidate_word, null);
+ word.setTag(i);
+ word.setOnClickListener(this);
if (i == 0)
- tv.setOnLongClickListener(this);
- ImageView divider = (ImageView)v.findViewById(R.id.candidate_divider);
- // Do not display divider of first candidate.
- divider.setVisibility(i == 0 ? GONE : VISIBLE);
- mWords.add(v);
+ word.setOnLongClickListener(this);
+ mWords.add(word);
+ mInfos.add((TextView)inflater.inflate(R.layout.candidate_info, null));
+ mDividers.add(inflater.inflate(R.layout.candidate_divider, null));
}
- scrollTo(0, getScrollY());
+ mTouchToSave = findViewById(R.id.touch_to_save);
+ mWordToSave = (TextView)findViewById(R.id.word_to_save);
+ mWordToSave.setOnClickListener(this);
+
+ mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
+ mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
+
+ final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
+ attrs, R.styleable.KeyboardView, R.attr.keyboardViewStyle, R.style.KeyboardView);
+ final Drawable expandBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyBackground);
+ final Drawable closeBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyBackground);
+ final int keyTextColor = keyboardViewAttr.getColor(
+ R.styleable.KeyboardView_keyTextColor, 0xFF000000);
+ keyboardViewAttr.recycle();
+
+ mCandidatesPaneControl = (ViewGroup)findViewById(R.id.candidates_pane_control);
+ mExpandCandidatesPane = (TextView)findViewById(R.id.expand_candidates_pane);
+ mExpandCandidatesPane.setBackgroundDrawable(expandBackground);
+ mExpandCandidatesPane.setTextColor(keyTextColor);
+ mExpandCandidatesPane.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ expandCandidatesPane();
+ }
+ });
+ mCloseCandidatesPane = (TextView)findViewById(R.id.close_candidates_pane);
+ mCloseCandidatesPane.setBackgroundDrawable(closeBackground);
+ mCloseCandidatesPane.setTextColor(keyTextColor);
+ mCloseCandidatesPane.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ closeCandidatesPane();
+ }
+ });
+ mCandidatesPaneControl.measure(WRAP_CONTENT, WRAP_CONTENT);
+
+ mParams = new CandidateViewLayoutParams(res,
+ mWords.get(0), mDividers.get(0), mCandidatesPaneControl, mAutoCorrectHighlight);
}
/**
- * 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);
}
public void setSuggestions(SuggestedWords suggestions) {
if (suggestions == null)
return;
mSuggestions = suggestions;
+ mExpandCandidatesPane.setEnabled(false);
if (mShowingAutoCorrectionInverted) {
mHandler.postUpdateSuggestions();
} else {
@@ -177,80 +392,281 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
}
+ private static CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect,
+ int autoCorrectHighlight) {
+ if (!isAutoCorrect)
+ return word;
+ final Spannable spannedWord = new SpannableString(word);
+ if ((autoCorrectHighlight & AUTO_CORRECT_BOLD) != 0)
+ spannedWord.setSpan(BOLD_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ if ((autoCorrectHighlight & 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;
+ }
+ 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;
+ }
+ }
+
private void updateSuggestions() {
final SuggestedWords suggestions = mSuggestions;
+ final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList;
+ final int paneWidth = getWidth();
+ final CandidateViewLayoutParams params = mParams;
+
clear();
- final int count = suggestions.size();
+ closeCandidatesPane();
+ if (suggestions.size() == 0)
+ return;
+
+ params.layoutStrip(suggestions, paneWidth, suggestions.isPunctuationSuggestions()
+ ? PUNCTUATIONS_IN_STRIP : mCandidateCountInStrip);
+
+ final int count = Math.min(mWords.size(), suggestions.size());
+ if (count <= params.mCountInStrip && !DBG) {
+ mCandidatesPaneControl.setVisibility(GONE);
+ } else {
+ mCandidatesPaneControl.setVisibility(VISIBLE);
+ mExpandCandidatesPane.setVisibility(VISIBLE);
+ mExpandCandidatesPane.setEnabled(true);
+ }
+
+ final int countInStrip = params.mCountInStrip;
+ View centeringFrom = null, lastView = null;
+ int x = 0, y = 0, infoX = 0;
for (int i = 0; i < count; i++) {
- CharSequence word = suggestions.getWord(i);
- if (word == null) continue;
- final int wordLength = word.length();
- final List<SuggestedWordInfo> suggestedWordInfoList =
- suggestions.mSuggestedWordInfoList;
-
- final View v = mWords.get(i);
- final TextView tv = (TextView)v.findViewById(R.id.candidate_word);
- final TextView dv = (TextView)v.findViewById(R.id.candidate_debug_info);
- tv.setTextColor(mColorNormal);
- // TODO: Needs safety net?
- if (suggestions.mHasMinimalSuggestion
- && ((i == 1 && !suggestions.mTypedWordValid)
- || (i == 0 && suggestions.mTypedWordValid))) {
- final CharacterStyle style;
- if (mConfigCandidateHighlightFontColorEnabled) {
- style = BOLD_SPAN;
- tv.setTextColor(mColorRecommended);
+ final int pos;
+ if (i <= 1) {
+ final boolean willAutoCorrect = !suggestions.mTypedWordValid
+ && suggestions.mHasMinimalSuggestion;
+ pos = willAutoCorrect ? 1 - i : i;
+ } else {
+ pos = i;
+ }
+ final CharSequence suggestion = suggestions.getWord(pos);
+ if (suggestion == null) continue;
+
+ final SuggestedWordInfo suggestionInfo = (suggestedWordInfoList != null)
+ ? suggestedWordInfoList.get(pos) : null;
+ final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
+ && ((pos == 1 && !suggestions.mTypedWordValid)
+ || (pos == 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 = (pos != 0);
+ final boolean isPunctuationSuggestions = (suggestion.length() == 1 && count > 1);
+
+ final TextView word = mWords.get(pos);
+ final TextPaint paint = word.getPaint();
+ // 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));
+ final CharSequence styled = params.mTexts.get(pos);
+
+ final TextView info;
+ if (DBG && suggestionInfo != null
+ && !TextUtils.isEmpty(suggestionInfo.getDebugString())) {
+ info = mInfos.get(i);
+ info.setText(suggestionInfo.getDebugString());
+ } else {
+ info = null;
+ }
+
+ final CharSequence text;
+ final float scaleX;
+ if (i < countInStrip) {
+ if (i == 0 && params.mCountInStrip == 1) {
+ text = getEllipsizedText(styled, params.mMaxWidth, paint);
+ scaleX = paint.getTextScaleX();
} else {
- style = UNDERLINE_SPAN;
+ text = styled;
+ scaleX = params.mScaleX;
}
- final Spannable spannedWord = new SpannableString(word);
- spannedWord.setSpan(style, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word = spannedWord;
- } 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.
- if (mConfigCandidateHighlightFontColorEnabled)
- tv.setTextColor(mColorOther);
- }
- tv.setText(word);
- tv.setClickable(true);
-
- if (suggestedWordInfoList != null && suggestedWordInfoList.get(i) != null) {
- final SuggestedWordInfo info = suggestedWordInfoList.get(i);
- if (info.isPreviousSuggestedWord()) {
- int color = tv.getCurrentTextColor();
- tv.setTextColor(Color.argb((int)(Color.alpha(color) * 0.5f), Color.red(color),
- Color.green(color), Color.blue(color)));
+ word.setText(text);
+ word.setTextScaleX(scaleX);
+ if (i != 0) {
+ // Add divider if this isn't the left most suggestion in candidate strip.
+ mCandidatesStrip.addView(mDividers.get(i));
}
- final String debugString = info.getDebugString();
- if (DBG) {
- if (TextUtils.isEmpty(debugString)) {
- dv.setVisibility(GONE);
- } else {
- dv.setText(debugString);
- dv.setVisibility(VISIBLE);
- }
+ mCandidatesStrip.addView(word);
+ if (params.mCanUseFixedWidthColumns) {
+ setLayoutWeight(word, 1.0f, mCandidateStripHeight);
} else {
- dv.setVisibility(GONE);
+ final int width = getTextWidth(text, paint) + params.mPadding;
+ setLayoutWeight(word, width, mCandidateStripHeight);
+ }
+ if (info != null) {
+ mCandidatesPane.addView(info);
+ info.measure(WRAP_CONTENT, WRAP_CONTENT);
+ final int width = info.getMeasuredWidth();
+ y = info.getMeasuredHeight();
+ FrameLayoutCompatUtils.placeViewAt(info, infoX, 0, width, y);
+ infoX += width * 2;
}
} else {
- dv.setVisibility(GONE);
+ paint.setTextScaleX(1.0f);
+ final int textWidth = getTextWidth(styled, paint);
+ int available = paneWidth - x - params.mPadding;
+ if (textWidth >= available) {
+ // Needs new row, centering previous row.
+ centeringCandidates(centeringFrom, lastView, x, paneWidth);
+ x = 0;
+ y += mCandidateStripHeight;
+ }
+ if (x != 0) {
+ // Add divider if this isn't the left most suggestion in current row.
+ final View divider = mDividers.get(i);
+ mCandidatesPane.addView(divider);
+ FrameLayoutCompatUtils.placeViewAt(
+ divider, x, y + (mCandidateStripHeight - params.mDividerHeight) / 2,
+ params.mDividerWidth, params.mDividerHeight);
+ x += params.mDividerWidth;
+ }
+ available = paneWidth - x - params.mPadding;
+ text = getEllipsizedText(styled, available, paint);
+ scaleX = paint.getTextScaleX();
+ word.setText(text);
+ word.setTextScaleX(scaleX);
+ mCandidatesPane.addView(word);
+ lastView = word;
+ if (x == 0) centeringFrom = word;
+ word.measure(WRAP_CONTENT,
+ MeasureSpec.makeMeasureSpec(mCandidateStripHeight, MeasureSpec.EXACTLY));
+ final int width = word.getMeasuredWidth();
+ final int height = word.getMeasuredHeight();
+ FrameLayoutCompatUtils.placeViewAt(
+ word, x, y + (mCandidateStripHeight - height) / 2, width, height);
+ x += width;
+ if (info != null) {
+ mCandidatesPane.addView(info);
+ lastView = info;
+ info.measure(WRAP_CONTENT, WRAP_CONTENT);
+ final int infoWidth = info.getMeasuredWidth();
+ FrameLayoutCompatUtils.placeViewAt(
+ info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
+ }
}
- addView(v);
}
+ if (x != 0) {
+ // Centering last candidates row.
+ centeringCandidates(centeringFrom, lastView, x, paneWidth);
+ }
+ }
- scrollTo(0, getScrollY());
- requestLayout();
+ 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 void centeringCandidates(View from, View to, int width, int paneWidth) {
+ final ViewGroup pane = mCandidatesPane;
+ final int fromIndex = pane.indexOfChild(from);
+ final int toIndex = pane.indexOfChild(to);
+ final int offset = (paneWidth - width) / 2;
+ for (int index = fromIndex; index <= toIndex; index++) {
+ offsetMargin(pane.getChildAt(index), offset, 0);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
+ TextPaint paint) {
+ paint.setTextScaleX(1.0f);
+ final int width = getTextWidth(text, paint);
+ final float scaleX = Math.min(maxWidth / (float)width, 1.0f);
+ 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 ellipsezed 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;
+ }
+ }
+
+ private void expandCandidatesPane() {
+ mExpandCandidatesPane.setVisibility(GONE);
+ mCloseCandidatesPane.setVisibility(VISIBLE);
+ mCandidatesPaneContainer.setMinimumHeight(mKeyboardView.getMeasuredHeight());
+ mCandidatesPaneContainer.setVisibility(VISIBLE);
+ mKeyboardView.setVisibility(GONE);
+ }
+
+ private void closeCandidatesPane() {
+ mExpandCandidatesPane.setVisibility(VISIBLE);
+ mCloseCandidatesPane.setVisibility(GONE);
+ mCandidatesPaneContainer.setVisibility(GONE);
+ mKeyboardView.setVisibility(VISIBLE);
}
public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) {
- // Displaying auto corrected word as inverted is enabled only when highlighting candidate
- // with color is disabled.
- if (mConfigCandidateHighlightFontColorEnabled)
+ if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0)
return;
- final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word);
+ final TextView tv = mWords.get(1);
final Spannable word = new SpannableString(autoCorrectedWord);
final int wordLength = word.length();
word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
@@ -261,23 +677,16 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
mShowingAutoCorrectionInverted = true;
}
- public boolean isConfigCandidateHighlightFontColorEnabled() {
- return mConfigCandidateHighlightFontColorEnabled;
- }
-
public boolean isShowingAddToDictionaryHint() {
return mShowingAddToDictionary;
}
public void showAddToDictionaryHint(CharSequence word) {
- SuggestedWords.Builder builder = new SuggestedWords.Builder()
- .addWord(word)
- .addWord(getContext().getText(R.string.hint_add_to_dictionary));
- setSuggestions(builder.build());
+ mWordToSave.setText(word);
mShowingAddToDictionary = true;
- // Disable R.string.hint_add_to_dictionary button
- TextView tv = (TextView)getChildAt(1).findViewById(R.id.candidate_word);
- tv.setClickable(false);
+ mCandidatesStrip.setVisibility(GONE);
+ mCandidatesPaneControl.setVisibility(GONE);
+ mTouchToSave.setVisibility(VISIBLE);
}
public boolean dismissAddToDictionaryHint() {
@@ -293,7 +702,11 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
public void clear() {
mShowingAddToDictionary = false;
mShowingAutoCorrectionInverted = false;
- removeAllViews();
+ mTouchToSave.setVisibility(GONE);
+ mCandidatesStrip.setVisibility(VISIBLE);
+ mCandidatesStrip.removeAllViews();
+ mCandidatesPane.removeAllViews();
+ closeCandidatesPane();
}
private void hidePreview() {
@@ -305,11 +718,11 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
return;
final TextView previewText = mPreviewText;
- previewText.setTextColor(mColorNormal);
+ previewText.setTextColor(mColorTypedWord);
previewText.setText(word);
previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- View v = getChildAt(index);
+ View v = mWords.get(index);
final int[] offsetInWindow = new int[2];
v.getLocationInWindow(offsetInWindow);
final int posX = offsetInWindow[0];
@@ -325,16 +738,20 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
private void addToDictionary(CharSequence word) {
- if (mService.addWordToDictionary(word.toString())) {
+ if (mListener.addWordToDictionary(word.toString())) {
showPreview(0, getContext().getString(R.string.added_word, word));
}
}
@Override
public boolean onLongClick(View view) {
- final int index = (Integer) view.getTag();
+ 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;
@@ -344,15 +761,24 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
@Override
public void onClick(View view) {
- final int index = (Integer) view.getTag();
+ if (view == mWordToSave) {
+ addToDictionary(((TextView)view).getText());
+ 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);
- if (mShowingAddToDictionary && index == 0) {
- addToDictionary(word);
- } else {
- mService.pickSuggestionManually(index, word);
- }
+ 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