From 8c3d5b6961a9b9d40c4bf21ad495f852971c24f4 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 16 Dec 2011 11:26:14 +0900 Subject: Add com.android.inputmethod.latin.suggestions package Change-Id: If4b823768ab9c073625da3034ecc8091be1df230 --- .../com/android/inputmethod/latin/LatinIME.java | 1 + .../android/inputmethod/latin/MoreSuggestions.java | 227 ------ .../inputmethod/latin/MoreSuggestionsView.java | 237 ------ .../android/inputmethod/latin/SuggestionsView.java | 846 -------------------- .../latin/suggestions/MoreSuggestions.java | 230 ++++++ .../latin/suggestions/MoreSuggestionsView.java | 238 ++++++ .../latin/suggestions/SuggestionsView.java | 851 +++++++++++++++++++++ 7 files changed, 1320 insertions(+), 1310 deletions(-) delete mode 100644 java/src/com/android/inputmethod/latin/MoreSuggestions.java delete mode 100644 java/src/com/android/inputmethod/latin/MoreSuggestionsView.java delete mode 100644 java/src/com/android/inputmethod/latin/SuggestionsView.java create mode 100644 java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java create mode 100644 java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java create mode 100644 java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 20c87ad13..a220bf42c 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -67,6 +67,7 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.LatinKeyboard; import com.android.inputmethod.keyboard.LatinKeyboardView; +import com.android.inputmethod.latin.suggestions.SuggestionsView; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/MoreSuggestions.java deleted file mode 100644 index 86072b64b..000000000 --- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.content.res.Resources; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.internal.KeyboardBuilder; -import com.android.inputmethod.keyboard.internal.KeyboardParams; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -public class MoreSuggestions extends Keyboard { - private static final boolean DBG = LatinImeLogger.sDBG; - - public static final int SUGGESTION_CODE_BASE = 1024; - - private MoreSuggestions(Builder.MoreSuggestionsParam params) { - super(params); - } - - public static class Builder extends KeyboardBuilder { - private final MoreSuggestionsView mPaneView; - private SuggestedWords mSuggestions; - private int mFromPos; - private int mToPos; - - public static class MoreSuggestionsParam extends KeyboardParams { - private final int[] mWidths = new int[SuggestionsView.MAX_SUGGESTIONS]; - private final int[] mRowNumbers = new int[SuggestionsView.MAX_SUGGESTIONS]; - private final int[] mColumnOrders = new int[SuggestionsView.MAX_SUGGESTIONS]; - private final int[] mNumColumnsInRow = new int[SuggestionsView.MAX_SUGGESTIONS]; - private static final int MAX_COLUMNS_IN_ROW = 3; - private int mNumRows; - public Drawable mDivider; - public int mDividerWidth; - - public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth, - int maxRow, KeyboardView view) { - clearKeys(); - final Paint paint = new Paint(); - paint.setAntiAlias(true); - final Resources res = view.getContext().getResources(); - mDivider = res.getDrawable(R.drawable.more_suggestions_divider); - // TODO: Drawable itself should has an alpha value. - mDivider.setAlpha(128); - mDividerWidth = mDivider.getIntrinsicWidth(); - final int padding = (int) res.getDimension( - R.dimen.more_suggestions_key_horizontal_padding); - - int row = 0; - int pos = fromPos, rowStartPos = fromPos; - final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS); - while (pos < size) { - final CharSequence word = suggestions.getWord(pos); - // TODO: Should take care of text x-scaling. - mWidths[pos] = (int)view.getDefaultLabelWidth(word, paint) + padding; - final int numColumn = pos - rowStartPos + 1; - final int columnWidth = - (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn; - if (numColumn > MAX_COLUMNS_IN_ROW - || !fitInWidth(rowStartPos, pos + 1, columnWidth)) { - if ((row + 1) >= maxRow) { - break; - } - mNumColumnsInRow[row] = pos - rowStartPos; - rowStartPos = pos; - row++; - } - mColumnOrders[pos] = pos - rowStartPos; - mRowNumbers[pos] = row; - pos++; - } - mNumColumnsInRow[row] = pos - rowStartPos; - mNumRows = row + 1; - mBaseWidth = mOccupiedWidth = Math.max( - minWidth, calcurateMaxRowWidth(fromPos, pos)); - mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; - return pos - fromPos; - } - - private boolean fitInWidth(int startPos, int endPos, int width) { - for (int pos = startPos; pos < endPos; pos++) { - if (mWidths[pos] > width) - return false; - } - return true; - } - - private int calcurateMaxRowWidth(int startPos, int endPos) { - int maxRowWidth = 0; - int pos = startPos; - for (int row = 0; row < mNumRows; row++) { - final int numColumnInRow = mNumColumnsInRow[row]; - int maxKeyWidth = 0; - while (pos < endPos && mRowNumbers[pos] == row) { - maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]); - pos++; - } - maxRowWidth = Math.max(maxRowWidth, - maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1)); - } - return maxRowWidth; - } - - private static final int[][] COLUMN_ORDER_TO_NUMBER = { - { 0, }, - { 1, 0, }, - { 2, 0, 1}, - }; - - public int getNumColumnInRow(int pos) { - return mNumColumnsInRow[mRowNumbers[pos]]; - } - - public int getColumnNumber(int pos) { - final int columnOrder = mColumnOrders[pos]; - final int numColumn = getNumColumnInRow(pos); - return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; - } - - public int getX(int pos) { - final int columnNumber = getColumnNumber(pos); - return columnNumber * (getWidth(pos) + mDividerWidth); - } - - public int getY(int pos) { - final int row = mRowNumbers[pos]; - return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; - } - - public int getWidth(int pos) { - final int numColumnInRow = getNumColumnInRow(pos); - return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; - } - - public void markAsEdgeKey(Key key, int pos) { - final int row = mRowNumbers[pos]; - if (row == 0) - key.markAsBottomEdge(this); - if (row == mNumRows - 1) - key.markAsTopEdge(this); - - final int numColumnInRow = mNumColumnsInRow[row]; - final int column = getColumnNumber(pos); - if (column == 0) - key.markAsLeftEdge(this); - if (column == numColumnInRow - 1) - key.markAsRightEdge(this); - } - } - - public Builder(MoreSuggestionsView paneView) { - super(paneView.getContext(), new MoreSuggestionsParam()); - mPaneView = paneView; - } - - public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth, - int minWidth, int maxRow) { - final Keyboard keyboard = KeyboardSwitcher.getInstance().getLatinKeyboard(); - final int xmlId = R.xml.kbd_suggestions_pane_template; - load(keyboard.mId.cloneWithNewXml(xmlId)); - mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2; - - final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow, - mPaneView); - mFromPos = fromPos; - mToPos = fromPos + count; - mSuggestions = suggestions; - return this; - } - - private static String getDebugInfo(SuggestedWords suggestions, int pos) { - if (!DBG) return null; - final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); - if (wordInfo == null) return null; - final String info = wordInfo.getDebugString(); - if (TextUtils.isEmpty(info)) return null; - return info; - } - - @Override - public MoreSuggestions build() { - final MoreSuggestionsParam params = mParams; - for (int pos = mFromPos; pos < mToPos; pos++) { - final int x = params.getX(pos); - final int y = params.getY(pos); - final int width = params.getWidth(pos); - final String word = mSuggestions.getWord(pos).toString(); - final String info = getDebugInfo(mSuggestions, pos); - final int index = pos + SUGGESTION_CODE_BASE; - final Key key = new Key( - params, word, info, null, index, null, x, y, width, - params.mDefaultRowHeight); - params.markAsEdgeKey(key, pos); - params.onAddKey(key); - final int columnNumber = params.getColumnNumber(pos); - final int numColumnInRow = params.getNumColumnInRow(pos); - if (columnNumber < numColumnInRow - 1) { - final Key.Spacer spacer = new Key.Spacer(params, params.mDivider, x + width, y, - params.mDividerWidth, params.mDefaultRowHeight); - params.onAddKey(spacer); - } - } - return new MoreSuggestions(params); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java deleted file mode 100644 index c61dd6313..000000000 --- a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import android.content.Context; -import android.content.res.Resources; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.widget.PopupWindow; - -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.MoreKeysDetector; -import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; -import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler; -import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; - -/** - * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting - * key presses and touch movements. - */ -public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { - private final int[] mCoordinates = new int[2]; - - private final KeyDetector mModalPanelKeyDetector; - private final KeyDetector mSlidingPanelKeyDetector; - - private Controller mController; - private KeyboardActionListener mListener; - private int mOriginX; - private int mOriginY; - - private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter(); - - private final KeyboardActionListener mSuggestionsPaneListener = - new KeyboardActionListener.Adapter() { - @Override - public void onPress(int primaryCode, boolean withSliding) { - mListener.onPress(primaryCode, withSliding); - } - - @Override - public void onRelease(int primaryCode, boolean withSliding) { - mListener.onRelease(primaryCode, withSliding); - } - - @Override - public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { - final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE; - if (index >= 0 && index < SuggestionsView.MAX_SUGGESTIONS) { - mListener.onCustomRequest(index); - } - } - - @Override - public void onCancelInput() { - mListener.onCancelInput(); - } - }; - - public MoreSuggestionsView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.moreSuggestionsViewStyle); - } - - public MoreSuggestionsView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - final Resources res = context.getResources(); - mModalPanelKeyDetector = new KeyDetector(/* keyHysteresisDistance */ 0); - mSlidingPanelKeyDetector = new MoreKeysDetector( - res.getDimension(R.dimen.more_suggestions_slide_allowance)); - setKeyPreviewPopupEnabled(false, 0); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final Keyboard keyboard = getKeyboard(); - if (keyboard != null) { - final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); - final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); - setMeasuredDimension(width, height); - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - @Override - public void setKeyboard(Keyboard keyboard) { - super.setKeyboard(keyboard); - mModalPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop()); - mSlidingPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), - -getPaddingTop() + mVerticalCorrection); - } - - @Override - public KeyDetector getKeyDetector() { - return mSlidingPanelKeyDetector; - } - - @Override - public KeyboardActionListener getKeyboardActionListener() { - return mSuggestionsPaneListener; - } - - @Override - public DrawingProxy getDrawingProxy() { - return this; - } - - @Override - public TimerProxy getTimerProxy() { - return EMPTY_TIMER_PROXY; - } - - @Override - public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { - // Suggestions pane needs no pop-up key preview displayed, so we pass always false with a - // delay of 0. The delay does not matter actually since the popup is not shown anyway. - super.setKeyPreviewPopupEnabled(false, 0); - } - - @Override - public void setShifted(boolean shifted) { - // Nothing to do with. - } - - @Override - public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, - PopupWindow window, KeyboardActionListener listener) { - mController = controller; - mListener = listener; - final View container = (View)getParent(); - final MoreSuggestions pane = (MoreSuggestions)getKeyboard(); - - parentView.getLocationInWindow(mCoordinates); - final int paneLeft = pointX - (pane.mOccupiedWidth / 2) + parentView.getPaddingLeft(); - final int x = wrapUp(Math.max(0, Math.min(paneLeft, - parentView.getWidth() - pane.mOccupiedWidth)) - - container.getPaddingLeft() + mCoordinates[0], - container.getMeasuredWidth(), 0, parentView.getWidth()); - final int y = pointY - - (container.getMeasuredHeight() - container.getPaddingBottom()) - + parentView.getPaddingTop() + mCoordinates[1]; - - window.setContentView(container); - window.setWidth(container.getMeasuredWidth()); - window.setHeight(container.getMeasuredHeight()); - window.showAtLocation(parentView, Gravity.NO_GRAVITY, x, y); - - mOriginX = x + container.getPaddingLeft() - mCoordinates[0]; - mOriginY = y + container.getPaddingTop() - mCoordinates[1]; - } - - private static int wrapUp(int x, int width, int left, int right) { - if (x < left) - return left; - if (x + width > right) - return right - width; - return x; - } - - private boolean mIsDismissing; - - @Override - public boolean dismissMoreKeysPanel() { - if (mIsDismissing) return false; - mIsDismissing = true; - final boolean dismissed = mController.dismissMoreKeysPanel(); - mIsDismissing = false; - return dismissed; - } - - @Override - public int translateX(int x) { - return x - mOriginX; - } - - @Override - public int translateY(int y) { - return y - mOriginY; - } - - private final KeyEventHandler mModalPanelKeyEventHandler = new KeyEventHandler() { - @Override - public KeyDetector getKeyDetector() { - return mModalPanelKeyDetector; - } - - @Override - public KeyboardActionListener getKeyboardActionListener() { - return mSuggestionsPaneListener; - } - - @Override - public DrawingProxy getDrawingProxy() { - return MoreSuggestionsView.this; - } - - @Override - public TimerProxy getTimerProxy() { - return EMPTY_TIMER_PROXY; - } - }; - - @Override - public boolean onTouchEvent(MotionEvent me) { - final int action = me.getAction(); - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - tracker.processMotionEvent(action, x, y, eventTime, mModalPanelKeyEventHandler); - return true; - } -} diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/SuggestionsView.java deleted file mode 100644 index ec0e0aece..000000000 --- a/java/src/com/android/inputmethod/latin/SuggestionsView.java +++ /dev/null @@ -1,846 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -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.CharacterStyle; -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.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.PopupWindow; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.android.inputmethod.compat.FrameLayoutCompatUtils; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -import java.util.ArrayList; -import java.util.List; - -public class SuggestionsView extends RelativeLayout implements OnClickListener, - OnLongClickListener { - public interface Listener { - public boolean addWordToDictionary(String word); - public void pickSuggestionManually(int index, CharSequence word); - } - - // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. - public static final int MAX_SUGGESTIONS = 18; - - private static final boolean DBG = LatinImeLogger.sDBG; - - private final ViewGroup mSuggestionsStrip; - private KeyboardView mKeyboardView; - - private final View mMoreSuggestionsContainer; - private final MoreSuggestionsView mMoreSuggestionsView; - private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - private final PopupWindow mMoreSuggestionsWindow; - - private final ArrayList mWords = new ArrayList(); - private final ArrayList mInfos = new ArrayList(); - private final ArrayList mDividers = new ArrayList(); - - private final PopupWindow mPreviewPopup; - private final TextView mPreviewText; - - private Listener mListener; - private SuggestedWords mSuggestions = SuggestedWords.EMPTY; - - private final SuggestionsViewParams mParams; - private static final float MIN_TEXT_XSCALE = 0.70f; - - private final UiHandler mHandler = new UiHandler(this); - - private static class UiHandler extends StaticInnerHandlerWrapper { - private static final int MSG_HIDE_PREVIEW = 0; - - public UiHandler(SuggestionsView outerInstance) { - super(outerInstance); - } - - @Override - public void dispatchMessage(Message msg) { - final SuggestionsView suggestionsView = getOuterInstance(); - switch (msg.what) { - case MSG_HIDE_PREVIEW: - suggestionsView.hidePreview(); - break; - } - } - - public void cancelHidePreview() { - removeMessages(MSG_HIDE_PREVIEW); - } - - public void cancelAllMessages() { - cancelHidePreview(); - } - } - - private static class SuggestionsViewParams { - private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; - private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40; - private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; - private static final int PUNCTUATIONS_IN_STRIP = 5; - - public final int mPadding; - public final int mDividerWidth; - public final int mSuggestionsStripHeight; - public final int mSuggestionsCountInStrip; - public final int mMaxMoreSuggestionsRow; - public final float mMinMoreSuggestionsWidth; - public final int mMoreSuggestionsBottomGap; - - private final List mWords; - private final List mDividers; - private final List mInfos; - - private final int mColorValidTypedWord; - private final int mColorTypedWord; - private final int mColorAutoCorrect; - private final int mColorSuggested; - private final float mAlphaObsoleted; - private final float mCenterSuggestionWeight; - private final int mCenterSuggestionIndex; - private final Drawable mMoreSuggestionsHint; - private static final String MORE_SUGGESTIONS_HINT = "\u2026"; - - private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); - private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - private static final int AUTO_CORRECT_BOLD = 0x01; - private static final int AUTO_CORRECT_UNDERLINE = 0x02; - private static final int VALID_TYPED_WORD_BOLD = 0x04; - - private final int mSuggestionStripOption; - - private final ArrayList mTexts = new ArrayList(); - - public boolean mMoreSuggestionsAvailable; - - public final TextView mWordToSaveView; - private final TextView mHintToSaveView; - - public SuggestionsViewParams(Context context, AttributeSet attrs, int defStyle, - List words, List dividers, List infos) { - mWords = words; - mDividers = dividers; - mInfos = infos; - - final TextView word = words.get(0); - final View divider = dividers.get(0); - mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight(); - divider.measure( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - mDividerWidth = divider.getMeasuredWidth(); - - final Resources res = word.getResources(); - mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height); - - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle); - mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0); - final float alphaValidTypedWord = getPercent(a, - R.styleable.SuggestionsView_alphaValidTypedWord, 100); - final float alphaTypedWord = getPercent(a, - R.styleable.SuggestionsView_alphaTypedWord, 100); - final float alphaAutoCorrect = getPercent(a, - R.styleable.SuggestionsView_alphaAutoCorrect, 100); - final float alphaSuggested = getPercent(a, - R.styleable.SuggestionsView_alphaSuggested, 100); - mAlphaObsoleted = getPercent(a, R.styleable.SuggestionsView_alphaSuggested, 100); - mColorValidTypedWord = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorValidTypedWord, 0), - alphaValidTypedWord); - mColorTypedWord = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0), alphaTypedWord); - mColorAutoCorrect = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorAutoCorrect, 0), alphaAutoCorrect); - mColorSuggested = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorSuggested, 0), alphaSuggested); - mSuggestionsCountInStrip = a.getInt( - R.styleable.SuggestionsView_suggestionsCountInStrip, - DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); - mCenterSuggestionWeight = getPercent(a, - R.styleable.SuggestionsView_centerSuggestionPercentile, - DEFAULT_CENTER_SUGGESTION_PERCENTILE); - mMaxMoreSuggestionsRow = a.getInt( - R.styleable.SuggestionsView_maxMoreSuggestionsRow, - DEFAULT_MAX_MORE_SUGGESTIONS_ROW); - mMinMoreSuggestionsWidth = getRatio(a, - R.styleable.SuggestionsView_minMoreSuggestionsWidth); - a.recycle(); - - mMoreSuggestionsHint = getMoreSuggestionsHint(res, - res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect); - mCenterSuggestionIndex = mSuggestionsCountInStrip / 2; - mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( - R.dimen.more_suggestions_bottom_gap); - - final LayoutInflater inflater = LayoutInflater.from(context); - mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); - mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); - } - - private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) { - final Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - paint.setTextSize(textSize); - paint.setColor(color); - final Rect bounds = new Rect(); - paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); - final int width = Math.round(bounds.width() + 0.5f); - final int height = Math.round(bounds.height() + 0.5f); - final Bitmap buffer = Bitmap.createBitmap( - width, (height * 3 / 2), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); - return new BitmapDrawable(res, buffer); - } - - // Read integer value in TypedArray as percent. - private static float getPercent(TypedArray a, int index, int defValue) { - return a.getInt(index, defValue) / 100.0f; - } - - // Read fraction value in TypedArray as float. - private static float getRatio(TypedArray a, int index) { - return a.getFraction(index, 1000, 1000, 1) / 1000.0f; - } - - private CharSequence getStyledSuggestionWord(SuggestedWords suggestions, int pos) { - final CharSequence word = suggestions.getWord(pos); - final boolean isAutoCorrect = pos == 1 && Utils.willAutoCorrect(suggestions); - final boolean isTypedWordValid = pos == 0 && suggestions.mTypedWordValid; - if (!isAutoCorrect && !isTypedWordValid) - return word; - - final int len = word.length(); - final Spannable spannedWord = new SpannableString(word); - final int option = mSuggestionStripOption; - if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0) - || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) { - spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) { - spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - return spannedWord; - } - - private int getWordPosition(int index, SuggestedWords suggestions) { - // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more - // suggestions. - final int centerPos = Utils.willAutoCorrect(suggestions) ? 1 : 0; - if (index == mCenterSuggestionIndex) { - return centerPos; - } else if (index == centerPos) { - return mCenterSuggestionIndex; - } else { - return index; - } - } - - private int getSuggestionTextColor(int index, SuggestedWords suggestions, int pos) { - // TODO: Need to revisit this logic with bigram suggestions - final boolean isSuggested = (pos != 0); - - final int color; - if (index == mCenterSuggestionIndex && Utils.willAutoCorrect(suggestions)) { - color = mColorAutoCorrect; - } else if (index == mCenterSuggestionIndex && suggestions.mTypedWordValid) { - color = mColorValidTypedWord; - } else if (isSuggested) { - color = mColorSuggested; - } else { - color = mColorTypedWord; - } - if (LatinImeLogger.sDBG) { - if (index == mCenterSuggestionIndex && suggestions.mHasAutoCorrectionCandidate - && suggestions.shouldBlockAutoCorrection()) { - return 0xFFFF0000; - } - } - - final SuggestedWordInfo info = (pos < suggestions.size()) - ? suggestions.getInfo(pos) : null; - if (info != null && info.isObsoleteSuggestedWord()) { - return applyAlpha(color, mAlphaObsoleted); - } else { - return color; - } - } - - private static int applyAlpha(final int color, final float alpha) { - final int newAlpha = (int)(Color.alpha(color) * alpha); - return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); - } - - private static void addDivider(final ViewGroup stripView, final View divider) { - stripView.addView(divider); - final LinearLayout.LayoutParams params = - (LinearLayout.LayoutParams)divider.getLayoutParams(); - params.gravity = Gravity.CENTER; - } - - public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer, - int stripWidth) { - if (suggestions.isPunctuationSuggestions()) { - layoutPunctuationSuggestions(suggestions, stripView); - return; - } - - final int countInStrip = mSuggestionsCountInStrip; - setupTexts(suggestions, countInStrip); - mMoreSuggestionsAvailable = (suggestions.size() > countInStrip); - int x = 0; - for (int index = 0; index < countInStrip; index++) { - final int pos = getWordPosition(index, suggestions); - - if (index != 0) { - final View divider = mDividers.get(pos); - // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, divider); - x += divider.getMeasuredWidth(); - } - - final CharSequence styled = mTexts.get(pos); - final TextView word = mWords.get(pos); - if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) { - // TODO: This "more suggestions hint" should have nicely designed icon. - word.setCompoundDrawablesWithIntrinsicBounds( - null, null, null, mMoreSuggestionsHint); - // HACK: To align with other TextView that has no compound drawables. - word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); - } else { - word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - } - - // Disable this suggestion if the suggestion is null or empty. - word.setEnabled(!TextUtils.isEmpty(styled)); - word.setTextColor(getSuggestionTextColor(index, suggestions, pos)); - final int width = getSuggestionWidth(index, stripWidth); - final CharSequence text = getEllipsizedText(styled, width, word.getPaint()); - final float scaleX = word.getTextScaleX(); - word.setText(text); // TextView.setText() resets text scale x to 1.0. - word.setTextScaleX(scaleX); - stripView.addView(word); - setLayoutWeight( - word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT); - x += word.getMeasuredWidth(); - - if (DBG) { - final CharSequence debugInfo = getDebugInfo(suggestions, pos); - if (debugInfo != null) { - final TextView info = mInfos.get(pos); - info.setText(debugInfo); - placer.addView(info); - info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - final int infoWidth = info.getMeasuredWidth(); - final int y = info.getMeasuredHeight(); - FrameLayoutCompatUtils.placeViewAt( - info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); - } - } - } - } - - private int getSuggestionWidth(int index, int maxWidth) { - final int paddings = mPadding * mSuggestionsCountInStrip; - final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); - final int availableWidth = maxWidth - paddings - dividers; - return (int)(availableWidth * getSuggestionWeight(index)); - } - - private float getSuggestionWeight(int index) { - if (index == mCenterSuggestionIndex) { - return mCenterSuggestionWeight; - } else { - // TODO: Revisit this for cases of 5 or more suggestions - return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1); - } - } - - private void setupTexts(SuggestedWords suggestions, int countInStrip) { - mTexts.clear(); - final int count = Math.min(suggestions.size(), countInStrip); - for (int pos = 0; pos < count; pos++) { - final CharSequence styled = getStyledSuggestionWord(suggestions, pos); - mTexts.add(styled); - } - for (int pos = count; pos < countInStrip; pos++) { - // Make this inactive for touches in layout(). - mTexts.add(null); - } - } - - private void layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) { - final int countInStrip = Math.min(suggestions.size(), PUNCTUATIONS_IN_STRIP); - for (int index = 0; index < countInStrip; index++) { - if (index != 0) { - // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, mDividers.get(index)); - } - - final TextView word = mWords.get(index); - word.setEnabled(true); - word.setTextColor(mColorAutoCorrect); - final CharSequence text = suggestions.getWord(index); - word.setText(text); - word.setTextScaleX(1.0f); - word.setCompoundDrawables(null, null, null, null); - stripView.addView(word); - setLayoutWeight(word, 1.0f, mSuggestionsStripHeight); - } - mMoreSuggestionsAvailable = false; - } - - public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView, - int stripWidth, CharSequence hintText) { - final int width = stripWidth - mDividerWidth - mPadding * 2; - - final TextView wordView = mWordToSaveView; - wordView.setTextColor(mColorTypedWord); - final int wordWidth = (int)(width * mCenterSuggestionWeight); - final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint()); - final float wordScaleX = wordView.getTextScaleX(); - wordView.setTag(word); - wordView.setText(text); - wordView.setTextScaleX(wordScaleX); - stripView.addView(wordView); - setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - - stripView.addView(mDividers.get(0)); - - final TextView hintView = mHintToSaveView; - hintView.setTextColor(mColorAutoCorrect); - final int hintWidth = width - wordWidth; - final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); - hintView.setText(hintText); - hintView.setTextScaleX(hintScaleX); - stripView.addView(hintView); - setLayoutWeight( - hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - } - - private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) { - if (DBG && pos < suggestions.size()) { - final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); - if (wordInfo != null) { - final CharSequence debugInfo = wordInfo.getDebugString(); - if (!TextUtils.isEmpty(debugInfo)) { - return debugInfo; - } - } - } - return null; - } - - private static void setLayoutWeight(View v, float weight, int height) { - final ViewGroup.LayoutParams lp = v.getLayoutParams(); - if (lp instanceof LinearLayout.LayoutParams) { - final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; - llp.weight = weight; - llp.width = 0; - llp.height = height; - } - } - - private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) { - paint.setTextScaleX(1.0f); - final int width = getTextWidth(text, paint); - if (width <= maxWidth) { - return 1.0f; - } - return maxWidth / (float)width; - } - - private static CharSequence getEllipsizedText(CharSequence text, int maxWidth, - TextPaint paint) { - if (text == null) return null; - paint.setTextScaleX(1.0f); - final int width = getTextWidth(text, paint); - if (width <= maxWidth) { - return text; - } - final float scaleX = maxWidth / (float)width; - if (scaleX >= MIN_TEXT_XSCALE) { - paint.setTextScaleX(scaleX); - return text; - } - - // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To - // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). - final CharSequence ellipsized = TextUtils.ellipsize( - text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); - paint.setTextScaleX(MIN_TEXT_XSCALE); - return ellipsized; - } - - private static int getTextWidth(CharSequence text, TextPaint paint) { - if (TextUtils.isEmpty(text)) return 0; - final Typeface savedTypeface = paint.getTypeface(); - paint.setTypeface(getTextTypeface(text)); - final int len = text.length(); - final float[] widths = new float[len]; - final int count = paint.getTextWidths(text, 0, len, widths); - int width = 0; - for (int i = 0; i < count; i++) { - width += Math.round(widths[i] + 0.5f); - } - paint.setTypeface(savedTypeface); - return width; - } - - private static Typeface getTextTypeface(CharSequence text) { - if (!(text instanceof SpannableString)) - return Typeface.DEFAULT; - - final SpannableString ss = (SpannableString)text; - final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class); - if (styles.length == 0) - return Typeface.DEFAULT; - - switch (styles[0].getStyle()) { - case Typeface.BOLD: return Typeface.DEFAULT_BOLD; - // TODO: BOLD_ITALIC, ITALIC case? - default: return Typeface.DEFAULT; - } - } - } - - /** - * Construct a {@link SuggestionsView} for showing suggested words for completion. - * @param context - * @param attrs - */ - public SuggestionsView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.suggestionsViewStyle); - } - - public SuggestionsView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - final LayoutInflater inflater = LayoutInflater.from(context); - inflater.inflate(R.layout.suggestions_strip, this); - - mPreviewPopup = new PopupWindow(context); - mPreviewText = (TextView) inflater.inflate(R.layout.suggestion_preview, null); - mPreviewPopup.setWindowLayoutMode( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - mPreviewPopup.setContentView(mPreviewText); - mPreviewPopup.setBackgroundDrawable(null); - - mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); - for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) { - final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null); - word.setTag(pos); - word.setOnClickListener(this); - word.setOnLongClickListener(this); - mWords.add(word); - final View divider = inflater.inflate(R.layout.suggestion_divider, null); - divider.setTag(pos); - divider.setOnClickListener(this); - mDividers.add(divider); - mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); - } - - mParams = new SuggestionsViewParams(context, attrs, defStyle, mWords, mDividers, mInfos); - mParams.mWordToSaveView.setOnClickListener(this); - - mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); - mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer - .findViewById(R.id.more_suggestions_view); - mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView); - - final PopupWindow moreWindow = new PopupWindow(context); - moreWindow.setWindowLayoutMode( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - moreWindow.setBackgroundDrawable(new ColorDrawable(android.R.color.transparent)); - moreWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - moreWindow.setFocusable(true); - moreWindow.setOutsideTouchable(true); - moreWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { - @Override - public void onDismiss() { - mKeyboardView.dimEntireKeyboard(false); - } - }); - mMoreSuggestionsWindow = moreWindow; - - final Resources res = context.getResources(); - mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset( - R.dimen.more_suggestions_modal_tolerance); - mMoreSuggestionsSlidingDetector = new GestureDetector( - context, mMoreSuggestionsSlidingListener); - } - - /** - * A connection back to the input method. - * @param listener - */ - public void setListener(Listener listener, View inputView) { - mListener = listener; - mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view); - } - - public void setSuggestions(SuggestedWords suggestions) { - if (suggestions == null || suggestions.size() == 0) - return; - - clear(); - mSuggestions = suggestions; - mParams.layout(mSuggestions, mSuggestionsStrip, this, getWidth()); - } - - - public boolean isShowingAddToDictionaryHint() { - return mSuggestionsStrip.getChildCount() > 0 - && mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView; - } - - public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) { - clear(); - mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText); - } - - public boolean dismissAddToDictionaryHint() { - if (isShowingAddToDictionaryHint()) { - clear(); - return true; - } - return false; - } - - public SuggestedWords getSuggestions() { - return mSuggestions; - } - - public void clear() { - mSuggestionsStrip.removeAllViews(); - removeAllViews(); - addView(mSuggestionsStrip); - dismissMoreSuggestions(); - } - - private void hidePreview() { - mPreviewPopup.dismiss(); - } - - private void addToDictionary(CharSequence word) { - mListener.addWordToDictionary(word.toString()); - } - - private final KeyboardActionListener mMoreSuggestionsListener = - new KeyboardActionListener.Adapter() { - @Override - public boolean onCustomRequest(int requestCode) { - final int index = requestCode; - final CharSequence word = mSuggestions.getWord(index); - mListener.pickSuggestionManually(index, word); - dismissMoreSuggestions(); - return true; - } - - @Override - public void onCancelInput() { - dismissMoreSuggestions(); - } - }; - - private final MoreKeysPanel.Controller mMoreSuggestionsController = - new MoreKeysPanel.Controller() { - @Override - public boolean dismissMoreKeysPanel() { - return dismissMoreSuggestions(); - } - }; - - private boolean dismissMoreSuggestions() { - if (mMoreSuggestionsWindow.isShowing()) { - mMoreSuggestionsWindow.dismiss(); - return true; - } - return false; - } - - public boolean handleBack() { - return dismissMoreSuggestions(); - } - - @Override - public boolean onLongClick(View view) { - return showMoreSuggestions(); - } - - private boolean showMoreSuggestions() { - final SuggestionsViewParams params = mParams; - if (params.mMoreSuggestionsAvailable) { - final int stripWidth = getWidth(); - final View container = mMoreSuggestionsContainer; - final int maxWidth = stripWidth - container.getPaddingLeft() - - container.getPaddingRight(); - final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestions, params.mSuggestionsCountInStrip, maxWidth, - (int)(maxWidth * params.mMinMoreSuggestionsWidth), - params.mMaxMoreSuggestionsRow); - mMoreSuggestionsView.setKeyboard(builder.build()); - container.measure( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; - final int pointX = stripWidth / 2; - final int pointY = -params.mMoreSuggestionsBottomGap; - moreKeysPanel.showMoreKeysPanel( - this, mMoreSuggestionsController, pointX, pointY, - mMoreSuggestionsWindow, mMoreSuggestionsListener); - mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; - mOriginX = mLastX; - mOriginY = mLastY; - mKeyboardView.dimEntireKeyboard(true); - for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { - mWords.get(i).setPressed(false); - } - return true; - } - return false; - } - - // Working variables for onLongClick and dispatchTouchEvent. - private int mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; - private static final int MORE_SUGGESTIONS_IN_MODAL_MODE = 0; - private static final int MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING = 1; - private static final int MORE_SUGGESTIONS_IN_SLIDING_MODE = 2; - private int mLastX; - private int mLastY; - private int mOriginX; - private int mOriginY; - private final int mMoreSuggestionsModalTolerance; - private final GestureDetector mMoreSuggestionsSlidingDetector; - private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onScroll(MotionEvent down, MotionEvent me, float deltaX, float deltaY) { - final float dy = me.getY() - down.getY(); - if (deltaY > 0 && dy < 0) { - return showMoreSuggestions(); - } - return false; - } - }; - - @Override - public boolean dispatchTouchEvent(MotionEvent me) { - if (!mMoreSuggestionsWindow.isShowing() - || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) { - mLastX = (int)me.getX(); - mLastY = (int)me.getY(); - if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) { - return true; - } - return super.dispatchTouchEvent(me); - } - - final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; - final int action = me.getAction(); - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final PointerTracker tracker = PointerTracker.getPointerTracker(id, moreKeysPanel); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - final int translatedX = moreKeysPanel.translateX(x); - final int translatedY = moreKeysPanel.translateY(y); - - if (mMoreSuggestionsMode == MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING) { - if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance - || mOriginY - y >= mMoreSuggestionsModalTolerance) { - // Decided to be in the sliding input mode only when the touch point has been moved - // upward. - mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE; - tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); - } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { - // Decided to be in the modal input mode - mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; - } - return true; - } - - // MORE_SUGGESTIONS_IN_SLIDING_MODE - tracker.processMotionEvent(action, translatedX, translatedY, eventTime, moreKeysPanel); - return true; - } - - @Override - public void onClick(View view) { - if (view == mParams.mWordToSaveView) { - addToDictionary((CharSequence)view.getTag()); - clear(); - return; - } - - final Object tag = view.getTag(); - if (!(tag instanceof Integer)) - return; - final int index = (Integer) tag; - if (index >= mSuggestions.size()) - return; - - final CharSequence word = mSuggestions.getWord(index); - mListener.pickSuggestionManually(index, word); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mHandler.cancelAllMessages(); - hidePreview(); - } -} diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java new file mode 100644 index 000000000..7f5918991 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.suggestions; + +import android.content.res.Resources; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.internal.KeyboardBuilder; +import com.android.inputmethod.keyboard.internal.KeyboardParams; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + +public class MoreSuggestions extends Keyboard { + public static final int SUGGESTION_CODE_BASE = 1024; + + private MoreSuggestions(Builder.MoreSuggestionsParam params) { + super(params); + } + + public static class Builder extends KeyboardBuilder { + private static final boolean DBG = LatinImeLogger.sDBG; + + private final MoreSuggestionsView mPaneView; + private SuggestedWords mSuggestions; + private int mFromPos; + private int mToPos; + + public static class MoreSuggestionsParam extends KeyboardParams { + private final int[] mWidths = new int[SuggestionsView.MAX_SUGGESTIONS]; + private final int[] mRowNumbers = new int[SuggestionsView.MAX_SUGGESTIONS]; + private final int[] mColumnOrders = new int[SuggestionsView.MAX_SUGGESTIONS]; + private final int[] mNumColumnsInRow = new int[SuggestionsView.MAX_SUGGESTIONS]; + private static final int MAX_COLUMNS_IN_ROW = 3; + private int mNumRows; + public Drawable mDivider; + public int mDividerWidth; + + public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth, + int maxRow, KeyboardView view) { + clearKeys(); + final Paint paint = new Paint(); + paint.setAntiAlias(true); + final Resources res = view.getContext().getResources(); + mDivider = res.getDrawable(R.drawable.more_suggestions_divider); + // TODO: Drawable itself should has an alpha value. + mDivider.setAlpha(128); + mDividerWidth = mDivider.getIntrinsicWidth(); + final int padding = (int) res.getDimension( + R.dimen.more_suggestions_key_horizontal_padding); + + int row = 0; + int pos = fromPos, rowStartPos = fromPos; + final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS); + while (pos < size) { + final CharSequence word = suggestions.getWord(pos); + // TODO: Should take care of text x-scaling. + mWidths[pos] = (int)view.getDefaultLabelWidth(word, paint) + padding; + final int numColumn = pos - rowStartPos + 1; + final int columnWidth = + (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn; + if (numColumn > MAX_COLUMNS_IN_ROW + || !fitInWidth(rowStartPos, pos + 1, columnWidth)) { + if ((row + 1) >= maxRow) { + break; + } + mNumColumnsInRow[row] = pos - rowStartPos; + rowStartPos = pos; + row++; + } + mColumnOrders[pos] = pos - rowStartPos; + mRowNumbers[pos] = row; + pos++; + } + mNumColumnsInRow[row] = pos - rowStartPos; + mNumRows = row + 1; + mBaseWidth = mOccupiedWidth = Math.max( + minWidth, calcurateMaxRowWidth(fromPos, pos)); + mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; + return pos - fromPos; + } + + private boolean fitInWidth(int startPos, int endPos, int width) { + for (int pos = startPos; pos < endPos; pos++) { + if (mWidths[pos] > width) + return false; + } + return true; + } + + private int calcurateMaxRowWidth(int startPos, int endPos) { + int maxRowWidth = 0; + int pos = startPos; + for (int row = 0; row < mNumRows; row++) { + final int numColumnInRow = mNumColumnsInRow[row]; + int maxKeyWidth = 0; + while (pos < endPos && mRowNumbers[pos] == row) { + maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]); + pos++; + } + maxRowWidth = Math.max(maxRowWidth, + maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1)); + } + return maxRowWidth; + } + + private static final int[][] COLUMN_ORDER_TO_NUMBER = { + { 0, }, + { 1, 0, }, + { 2, 0, 1}, + }; + + public int getNumColumnInRow(int pos) { + return mNumColumnsInRow[mRowNumbers[pos]]; + } + + public int getColumnNumber(int pos) { + final int columnOrder = mColumnOrders[pos]; + final int numColumn = getNumColumnInRow(pos); + return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; + } + + public int getX(int pos) { + final int columnNumber = getColumnNumber(pos); + return columnNumber * (getWidth(pos) + mDividerWidth); + } + + public int getY(int pos) { + final int row = mRowNumbers[pos]; + return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; + } + + public int getWidth(int pos) { + final int numColumnInRow = getNumColumnInRow(pos); + return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; + } + + public void markAsEdgeKey(Key key, int pos) { + final int row = mRowNumbers[pos]; + if (row == 0) + key.markAsBottomEdge(this); + if (row == mNumRows - 1) + key.markAsTopEdge(this); + + final int numColumnInRow = mNumColumnsInRow[row]; + final int column = getColumnNumber(pos); + if (column == 0) + key.markAsLeftEdge(this); + if (column == numColumnInRow - 1) + key.markAsRightEdge(this); + } + } + + public Builder(MoreSuggestionsView paneView) { + super(paneView.getContext(), new MoreSuggestionsParam()); + mPaneView = paneView; + } + + public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth, + int minWidth, int maxRow) { + final Keyboard keyboard = KeyboardSwitcher.getInstance().getLatinKeyboard(); + final int xmlId = R.xml.kbd_suggestions_pane_template; + load(keyboard.mId.cloneWithNewXml(xmlId)); + mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2; + + final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow, + mPaneView); + mFromPos = fromPos; + mToPos = fromPos + count; + mSuggestions = suggestions; + return this; + } + + private static String getDebugInfo(SuggestedWords suggestions, int pos) { + if (!DBG) return null; + final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); + if (wordInfo == null) return null; + final String info = wordInfo.getDebugString(); + if (TextUtils.isEmpty(info)) return null; + return info; + } + + @Override + public MoreSuggestions build() { + final MoreSuggestionsParam params = mParams; + for (int pos = mFromPos; pos < mToPos; pos++) { + final int x = params.getX(pos); + final int y = params.getY(pos); + final int width = params.getWidth(pos); + final String word = mSuggestions.getWord(pos).toString(); + final String info = getDebugInfo(mSuggestions, pos); + final int index = pos + SUGGESTION_CODE_BASE; + final Key key = new Key( + params, word, info, null, index, null, x, y, width, + params.mDefaultRowHeight); + params.markAsEdgeKey(key, pos); + params.onAddKey(key); + final int columnNumber = params.getColumnNumber(pos); + final int numColumnInRow = params.getNumColumnInRow(pos); + if (columnNumber < numColumnInRow - 1) { + final Key.Spacer spacer = new Key.Spacer(params, params.mDivider, x + width, y, + params.mDividerWidth, params.mDefaultRowHeight); + params.onAddKey(spacer); + } + } + return new MoreSuggestions(params); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java new file mode 100644 index 000000000..b5f67ace0 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.suggestions; + +import android.content.Context; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.PopupWindow; + +import com.android.inputmethod.keyboard.KeyDetector; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.MoreKeysDetector; +import com.android.inputmethod.keyboard.MoreKeysPanel; +import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; +import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler; +import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; +import com.android.inputmethod.latin.R; + +/** + * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting + * key presses and touch movements. + */ +public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { + private final int[] mCoordinates = new int[2]; + + private final KeyDetector mModalPanelKeyDetector; + private final KeyDetector mSlidingPanelKeyDetector; + + private Controller mController; + private KeyboardActionListener mListener; + private int mOriginX; + private int mOriginY; + + private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter(); + + private final KeyboardActionListener mSuggestionsPaneListener = + new KeyboardActionListener.Adapter() { + @Override + public void onPress(int primaryCode, boolean withSliding) { + mListener.onPress(primaryCode, withSliding); + } + + @Override + public void onRelease(int primaryCode, boolean withSliding) { + mListener.onRelease(primaryCode, withSliding); + } + + @Override + public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { + final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE; + if (index >= 0 && index < SuggestionsView.MAX_SUGGESTIONS) { + mListener.onCustomRequest(index); + } + } + + @Override + public void onCancelInput() { + mListener.onCancelInput(); + } + }; + + public MoreSuggestionsView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.moreSuggestionsViewStyle); + } + + public MoreSuggestionsView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final Resources res = context.getResources(); + mModalPanelKeyDetector = new KeyDetector(/* keyHysteresisDistance */ 0); + mSlidingPanelKeyDetector = new MoreKeysDetector( + res.getDimension(R.dimen.more_suggestions_slide_allowance)); + setKeyPreviewPopupEnabled(false, 0); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final Keyboard keyboard = getKeyboard(); + if (keyboard != null) { + final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); + final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); + setMeasuredDimension(width, height); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override + public void setKeyboard(Keyboard keyboard) { + super.setKeyboard(keyboard); + mModalPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop()); + mSlidingPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), + -getPaddingTop() + mVerticalCorrection); + } + + @Override + public KeyDetector getKeyDetector() { + return mSlidingPanelKeyDetector; + } + + @Override + public KeyboardActionListener getKeyboardActionListener() { + return mSuggestionsPaneListener; + } + + @Override + public DrawingProxy getDrawingProxy() { + return this; + } + + @Override + public TimerProxy getTimerProxy() { + return EMPTY_TIMER_PROXY; + } + + @Override + public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { + // Suggestions pane needs no pop-up key preview displayed, so we pass always false with a + // delay of 0. The delay does not matter actually since the popup is not shown anyway. + super.setKeyPreviewPopupEnabled(false, 0); + } + + @Override + public void setShifted(boolean shifted) { + // Nothing to do with. + } + + @Override + public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, + PopupWindow window, KeyboardActionListener listener) { + mController = controller; + mListener = listener; + final View container = (View)getParent(); + final MoreSuggestions pane = (MoreSuggestions)getKeyboard(); + + parentView.getLocationInWindow(mCoordinates); + final int paneLeft = pointX - (pane.mOccupiedWidth / 2) + parentView.getPaddingLeft(); + final int x = wrapUp(Math.max(0, Math.min(paneLeft, + parentView.getWidth() - pane.mOccupiedWidth)) + - container.getPaddingLeft() + mCoordinates[0], + container.getMeasuredWidth(), 0, parentView.getWidth()); + final int y = pointY + - (container.getMeasuredHeight() - container.getPaddingBottom()) + + parentView.getPaddingTop() + mCoordinates[1]; + + window.setContentView(container); + window.setWidth(container.getMeasuredWidth()); + window.setHeight(container.getMeasuredHeight()); + window.showAtLocation(parentView, Gravity.NO_GRAVITY, x, y); + + mOriginX = x + container.getPaddingLeft() - mCoordinates[0]; + mOriginY = y + container.getPaddingTop() - mCoordinates[1]; + } + + private static int wrapUp(int x, int width, int left, int right) { + if (x < left) + return left; + if (x + width > right) + return right - width; + return x; + } + + private boolean mIsDismissing; + + @Override + public boolean dismissMoreKeysPanel() { + if (mIsDismissing) return false; + mIsDismissing = true; + final boolean dismissed = mController.dismissMoreKeysPanel(); + mIsDismissing = false; + return dismissed; + } + + @Override + public int translateX(int x) { + return x - mOriginX; + } + + @Override + public int translateY(int y) { + return y - mOriginY; + } + + private final KeyEventHandler mModalPanelKeyEventHandler = new KeyEventHandler() { + @Override + public KeyDetector getKeyDetector() { + return mModalPanelKeyDetector; + } + + @Override + public KeyboardActionListener getKeyboardActionListener() { + return mSuggestionsPaneListener; + } + + @Override + public DrawingProxy getDrawingProxy() { + return MoreSuggestionsView.this; + } + + @Override + public TimerProxy getTimerProxy() { + return EMPTY_TIMER_PROXY; + } + }; + + @Override + public boolean onTouchEvent(MotionEvent me) { + final int action = me.getAction(); + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + tracker.processMotionEvent(action, x, y, eventTime, mModalPanelKeyEventHandler); + return true; + } +} diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java new file mode 100644 index 000000000..40d782640 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java @@ -0,0 +1,851 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.suggestions; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +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.CharacterStyle; +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.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.android.inputmethod.compat.FrameLayoutCompatUtils; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.MoreKeysPanel; +import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.Utils; + +import java.util.ArrayList; +import java.util.List; + +public class SuggestionsView extends RelativeLayout implements OnClickListener, + OnLongClickListener { + public interface Listener { + public boolean addWordToDictionary(String word); + public void pickSuggestionManually(int index, CharSequence word); + } + + // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. + public static final int MAX_SUGGESTIONS = 18; + + static final boolean DBG = LatinImeLogger.sDBG; + + private final ViewGroup mSuggestionsStrip; + private KeyboardView mKeyboardView; + + private final View mMoreSuggestionsContainer; + private final MoreSuggestionsView mMoreSuggestionsView; + private final MoreSuggestions.Builder mMoreSuggestionsBuilder; + private final PopupWindow mMoreSuggestionsWindow; + + private final ArrayList mWords = new ArrayList(); + private final ArrayList mInfos = new ArrayList(); + private final ArrayList mDividers = new ArrayList(); + + private final PopupWindow mPreviewPopup; + private final TextView mPreviewText; + + private Listener mListener; + private SuggestedWords mSuggestions = SuggestedWords.EMPTY; + + private final SuggestionsViewParams mParams; + private static final float MIN_TEXT_XSCALE = 0.70f; + + private final UiHandler mHandler = new UiHandler(this); + + private static class UiHandler extends StaticInnerHandlerWrapper { + private static final int MSG_HIDE_PREVIEW = 0; + + public UiHandler(SuggestionsView outerInstance) { + super(outerInstance); + } + + @Override + public void dispatchMessage(Message msg) { + final SuggestionsView suggestionsView = getOuterInstance(); + switch (msg.what) { + case MSG_HIDE_PREVIEW: + suggestionsView.hidePreview(); + break; + } + } + + public void cancelHidePreview() { + removeMessages(MSG_HIDE_PREVIEW); + } + + public void cancelAllMessages() { + cancelHidePreview(); + } + } + + private static class SuggestionsViewParams { + private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; + private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40; + private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; + private static final int PUNCTUATIONS_IN_STRIP = 5; + + public final int mPadding; + public final int mDividerWidth; + public final int mSuggestionsStripHeight; + public final int mSuggestionsCountInStrip; + public final int mMaxMoreSuggestionsRow; + public final float mMinMoreSuggestionsWidth; + public final int mMoreSuggestionsBottomGap; + + private final List mWords; + private final List mDividers; + private final List mInfos; + + private final int mColorValidTypedWord; + private final int mColorTypedWord; + private final int mColorAutoCorrect; + private final int mColorSuggested; + private final float mAlphaObsoleted; + private final float mCenterSuggestionWeight; + private final int mCenterSuggestionIndex; + private final Drawable mMoreSuggestionsHint; + private static final String MORE_SUGGESTIONS_HINT = "\u2026"; + + private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); + private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); + private static final int AUTO_CORRECT_BOLD = 0x01; + private static final int AUTO_CORRECT_UNDERLINE = 0x02; + private static final int VALID_TYPED_WORD_BOLD = 0x04; + + private final int mSuggestionStripOption; + + private final ArrayList mTexts = new ArrayList(); + + public boolean mMoreSuggestionsAvailable; + + public final TextView mWordToSaveView; + private final TextView mHintToSaveView; + + public SuggestionsViewParams(Context context, AttributeSet attrs, int defStyle, + List words, List dividers, List infos) { + mWords = words; + mDividers = dividers; + mInfos = infos; + + final TextView word = words.get(0); + final View divider = dividers.get(0); + mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight(); + divider.measure( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mDividerWidth = divider.getMeasuredWidth(); + + final Resources res = word.getResources(); + mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height); + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle); + mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0); + final float alphaValidTypedWord = getPercent(a, + R.styleable.SuggestionsView_alphaValidTypedWord, 100); + final float alphaTypedWord = getPercent(a, + R.styleable.SuggestionsView_alphaTypedWord, 100); + final float alphaAutoCorrect = getPercent(a, + R.styleable.SuggestionsView_alphaAutoCorrect, 100); + final float alphaSuggested = getPercent(a, + R.styleable.SuggestionsView_alphaSuggested, 100); + mAlphaObsoleted = getPercent(a, R.styleable.SuggestionsView_alphaSuggested, 100); + mColorValidTypedWord = applyAlpha( + a.getColor(R.styleable.SuggestionsView_colorValidTypedWord, 0), + alphaValidTypedWord); + mColorTypedWord = applyAlpha( + a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0), alphaTypedWord); + mColorAutoCorrect = applyAlpha( + a.getColor(R.styleable.SuggestionsView_colorAutoCorrect, 0), alphaAutoCorrect); + mColorSuggested = applyAlpha( + a.getColor(R.styleable.SuggestionsView_colorSuggested, 0), alphaSuggested); + mSuggestionsCountInStrip = a.getInt( + R.styleable.SuggestionsView_suggestionsCountInStrip, + DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); + mCenterSuggestionWeight = getPercent(a, + R.styleable.SuggestionsView_centerSuggestionPercentile, + DEFAULT_CENTER_SUGGESTION_PERCENTILE); + mMaxMoreSuggestionsRow = a.getInt( + R.styleable.SuggestionsView_maxMoreSuggestionsRow, + DEFAULT_MAX_MORE_SUGGESTIONS_ROW); + mMinMoreSuggestionsWidth = getRatio(a, + R.styleable.SuggestionsView_minMoreSuggestionsWidth); + a.recycle(); + + mMoreSuggestionsHint = getMoreSuggestionsHint(res, + res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect); + mCenterSuggestionIndex = mSuggestionsCountInStrip / 2; + mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( + R.dimen.more_suggestions_bottom_gap); + + final LayoutInflater inflater = LayoutInflater.from(context); + mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); + mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); + } + + private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) { + final Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setTextAlign(Align.CENTER); + paint.setTextSize(textSize); + paint.setColor(color); + final Rect bounds = new Rect(); + paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); + final int width = Math.round(bounds.width() + 0.5f); + final int height = Math.round(bounds.height() + 0.5f); + final Bitmap buffer = Bitmap.createBitmap( + width, (height * 3 / 2), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(buffer); + canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); + return new BitmapDrawable(res, buffer); + } + + // Read integer value in TypedArray as percent. + private static float getPercent(TypedArray a, int index, int defValue) { + return a.getInt(index, defValue) / 100.0f; + } + + // Read fraction value in TypedArray as float. + private static float getRatio(TypedArray a, int index) { + return a.getFraction(index, 1000, 1000, 1) / 1000.0f; + } + + private CharSequence getStyledSuggestionWord(SuggestedWords suggestions, int pos) { + final CharSequence word = suggestions.getWord(pos); + final boolean isAutoCorrect = pos == 1 && Utils.willAutoCorrect(suggestions); + final boolean isTypedWordValid = pos == 0 && suggestions.mTypedWordValid; + if (!isAutoCorrect && !isTypedWordValid) + return word; + + final int len = word.length(); + final Spannable spannedWord = new SpannableString(word); + final int option = mSuggestionStripOption; + if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0) + || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) { + spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) { + spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + return spannedWord; + } + + private int getWordPosition(int index, SuggestedWords suggestions) { + // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more + // suggestions. + final int centerPos = Utils.willAutoCorrect(suggestions) ? 1 : 0; + if (index == mCenterSuggestionIndex) { + return centerPos; + } else if (index == centerPos) { + return mCenterSuggestionIndex; + } else { + return index; + } + } + + private int getSuggestionTextColor(int index, SuggestedWords suggestions, int pos) { + // TODO: Need to revisit this logic with bigram suggestions + final boolean isSuggested = (pos != 0); + + final int color; + if (index == mCenterSuggestionIndex && Utils.willAutoCorrect(suggestions)) { + color = mColorAutoCorrect; + } else if (index == mCenterSuggestionIndex && suggestions.mTypedWordValid) { + color = mColorValidTypedWord; + } else if (isSuggested) { + color = mColorSuggested; + } else { + color = mColorTypedWord; + } + if (LatinImeLogger.sDBG) { + if (index == mCenterSuggestionIndex && suggestions.mHasAutoCorrectionCandidate + && suggestions.shouldBlockAutoCorrection()) { + return 0xFFFF0000; + } + } + + final SuggestedWordInfo info = (pos < suggestions.size()) + ? suggestions.getInfo(pos) : null; + if (info != null && info.isObsoleteSuggestedWord()) { + return applyAlpha(color, mAlphaObsoleted); + } else { + return color; + } + } + + private static int applyAlpha(final int color, final float alpha) { + final int newAlpha = (int)(Color.alpha(color) * alpha); + return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + private static void addDivider(final ViewGroup stripView, final View divider) { + stripView.addView(divider); + final LinearLayout.LayoutParams params = + (LinearLayout.LayoutParams)divider.getLayoutParams(); + params.gravity = Gravity.CENTER; + } + + public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer, + int stripWidth) { + if (suggestions.isPunctuationSuggestions()) { + layoutPunctuationSuggestions(suggestions, stripView); + return; + } + + final int countInStrip = mSuggestionsCountInStrip; + setupTexts(suggestions, countInStrip); + mMoreSuggestionsAvailable = (suggestions.size() > countInStrip); + int x = 0; + for (int index = 0; index < countInStrip; index++) { + final int pos = getWordPosition(index, suggestions); + + if (index != 0) { + final View divider = mDividers.get(pos); + // Add divider if this isn't the left most suggestion in suggestions strip. + addDivider(stripView, divider); + x += divider.getMeasuredWidth(); + } + + final CharSequence styled = mTexts.get(pos); + final TextView word = mWords.get(pos); + if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) { + // TODO: This "more suggestions hint" should have nicely designed icon. + word.setCompoundDrawablesWithIntrinsicBounds( + null, null, null, mMoreSuggestionsHint); + // HACK: To align with other TextView that has no compound drawables. + word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); + } else { + word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } + + // Disable this suggestion if the suggestion is null or empty. + word.setEnabled(!TextUtils.isEmpty(styled)); + word.setTextColor(getSuggestionTextColor(index, suggestions, pos)); + final int width = getSuggestionWidth(index, stripWidth); + final CharSequence text = getEllipsizedText(styled, width, word.getPaint()); + final float scaleX = word.getTextScaleX(); + word.setText(text); // TextView.setText() resets text scale x to 1.0. + word.setTextScaleX(scaleX); + stripView.addView(word); + setLayoutWeight( + word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT); + x += word.getMeasuredWidth(); + + if (DBG) { + final CharSequence debugInfo = getDebugInfo(suggestions, pos); + if (debugInfo != null) { + final TextView info = mInfos.get(pos); + info.setText(debugInfo); + placer.addView(info); + info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + final int infoWidth = info.getMeasuredWidth(); + final int y = info.getMeasuredHeight(); + FrameLayoutCompatUtils.placeViewAt( + info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); + } + } + } + } + + private int getSuggestionWidth(int index, int maxWidth) { + final int paddings = mPadding * mSuggestionsCountInStrip; + final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); + final int availableWidth = maxWidth - paddings - dividers; + return (int)(availableWidth * getSuggestionWeight(index)); + } + + private float getSuggestionWeight(int index) { + if (index == mCenterSuggestionIndex) { + return mCenterSuggestionWeight; + } else { + // TODO: Revisit this for cases of 5 or more suggestions + return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1); + } + } + + private void setupTexts(SuggestedWords suggestions, int countInStrip) { + mTexts.clear(); + final int count = Math.min(suggestions.size(), countInStrip); + for (int pos = 0; pos < count; pos++) { + final CharSequence styled = getStyledSuggestionWord(suggestions, pos); + mTexts.add(styled); + } + for (int pos = count; pos < countInStrip; pos++) { + // Make this inactive for touches in layout(). + mTexts.add(null); + } + } + + private void layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) { + final int countInStrip = Math.min(suggestions.size(), PUNCTUATIONS_IN_STRIP); + for (int index = 0; index < countInStrip; index++) { + if (index != 0) { + // Add divider if this isn't the left most suggestion in suggestions strip. + addDivider(stripView, mDividers.get(index)); + } + + final TextView word = mWords.get(index); + word.setEnabled(true); + word.setTextColor(mColorAutoCorrect); + final CharSequence text = suggestions.getWord(index); + word.setText(text); + word.setTextScaleX(1.0f); + word.setCompoundDrawables(null, null, null, null); + stripView.addView(word); + setLayoutWeight(word, 1.0f, mSuggestionsStripHeight); + } + mMoreSuggestionsAvailable = false; + } + + public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView, + int stripWidth, CharSequence hintText) { + final int width = stripWidth - mDividerWidth - mPadding * 2; + + final TextView wordView = mWordToSaveView; + wordView.setTextColor(mColorTypedWord); + final int wordWidth = (int)(width * mCenterSuggestionWeight); + final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint()); + final float wordScaleX = wordView.getTextScaleX(); + wordView.setTag(word); + wordView.setText(text); + wordView.setTextScaleX(wordScaleX); + stripView.addView(wordView); + setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); + + stripView.addView(mDividers.get(0)); + + final TextView hintView = mHintToSaveView; + hintView.setTextColor(mColorAutoCorrect); + final int hintWidth = width - wordWidth; + final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); + hintView.setText(hintText); + hintView.setTextScaleX(hintScaleX); + stripView.addView(hintView); + setLayoutWeight( + hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); + } + + private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) { + if (DBG && pos < suggestions.size()) { + final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); + if (wordInfo != null) { + final CharSequence debugInfo = wordInfo.getDebugString(); + if (!TextUtils.isEmpty(debugInfo)) { + return debugInfo; + } + } + } + return null; + } + + private static void setLayoutWeight(View v, float weight, int height) { + final ViewGroup.LayoutParams lp = v.getLayoutParams(); + if (lp instanceof LinearLayout.LayoutParams) { + final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; + llp.weight = weight; + llp.width = 0; + llp.height = height; + } + } + + private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) { + paint.setTextScaleX(1.0f); + final int width = getTextWidth(text, paint); + if (width <= maxWidth) { + return 1.0f; + } + return maxWidth / (float)width; + } + + private static CharSequence getEllipsizedText(CharSequence text, int maxWidth, + TextPaint paint) { + if (text == null) return null; + paint.setTextScaleX(1.0f); + final int width = getTextWidth(text, paint); + if (width <= maxWidth) { + return text; + } + final float scaleX = maxWidth / (float)width; + if (scaleX >= MIN_TEXT_XSCALE) { + paint.setTextScaleX(scaleX); + return text; + } + + // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To + // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). + final CharSequence ellipsized = TextUtils.ellipsize( + text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); + paint.setTextScaleX(MIN_TEXT_XSCALE); + return ellipsized; + } + + private static int getTextWidth(CharSequence text, TextPaint paint) { + if (TextUtils.isEmpty(text)) return 0; + final Typeface savedTypeface = paint.getTypeface(); + paint.setTypeface(getTextTypeface(text)); + final int len = text.length(); + final float[] widths = new float[len]; + final int count = paint.getTextWidths(text, 0, len, widths); + int width = 0; + for (int i = 0; i < count; i++) { + width += Math.round(widths[i] + 0.5f); + } + paint.setTypeface(savedTypeface); + return width; + } + + private static Typeface getTextTypeface(CharSequence text) { + if (!(text instanceof SpannableString)) + return Typeface.DEFAULT; + + final SpannableString ss = (SpannableString)text; + final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class); + if (styles.length == 0) + return Typeface.DEFAULT; + + switch (styles[0].getStyle()) { + case Typeface.BOLD: return Typeface.DEFAULT_BOLD; + // TODO: BOLD_ITALIC, ITALIC case? + default: return Typeface.DEFAULT; + } + } + } + + /** + * Construct a {@link SuggestionsView} for showing suggested words for completion. + * @param context + * @param attrs + */ + public SuggestionsView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.suggestionsViewStyle); + } + + public SuggestionsView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final LayoutInflater inflater = LayoutInflater.from(context); + inflater.inflate(R.layout.suggestions_strip, this); + + mPreviewPopup = new PopupWindow(context); + mPreviewText = (TextView) inflater.inflate(R.layout.suggestion_preview, null); + mPreviewPopup.setWindowLayoutMode( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mPreviewPopup.setContentView(mPreviewText); + mPreviewPopup.setBackgroundDrawable(null); + + mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); + for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) { + final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null); + word.setTag(pos); + word.setOnClickListener(this); + word.setOnLongClickListener(this); + mWords.add(word); + final View divider = inflater.inflate(R.layout.suggestion_divider, null); + divider.setTag(pos); + divider.setOnClickListener(this); + mDividers.add(divider); + mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); + } + + mParams = new SuggestionsViewParams(context, attrs, defStyle, mWords, mDividers, mInfos); + mParams.mWordToSaveView.setOnClickListener(this); + + mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); + mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer + .findViewById(R.id.more_suggestions_view); + mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView); + + final PopupWindow moreWindow = new PopupWindow(context); + moreWindow.setWindowLayoutMode( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + moreWindow.setBackgroundDrawable(new ColorDrawable(android.R.color.transparent)); + moreWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + moreWindow.setFocusable(true); + moreWindow.setOutsideTouchable(true); + moreWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { + @Override + public void onDismiss() { + mKeyboardView.dimEntireKeyboard(false); + } + }); + mMoreSuggestionsWindow = moreWindow; + + final Resources res = context.getResources(); + mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset( + R.dimen.more_suggestions_modal_tolerance); + mMoreSuggestionsSlidingDetector = new GestureDetector( + context, mMoreSuggestionsSlidingListener); + } + + /** + * A connection back to the input method. + * @param listener + */ + public void setListener(Listener listener, View inputView) { + mListener = listener; + mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view); + } + + public void setSuggestions(SuggestedWords suggestions) { + if (suggestions == null || suggestions.size() == 0) + return; + + clear(); + mSuggestions = suggestions; + mParams.layout(mSuggestions, mSuggestionsStrip, this, getWidth()); + } + + + public boolean isShowingAddToDictionaryHint() { + return mSuggestionsStrip.getChildCount() > 0 + && mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView; + } + + public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) { + clear(); + mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText); + } + + public boolean dismissAddToDictionaryHint() { + if (isShowingAddToDictionaryHint()) { + clear(); + return true; + } + return false; + } + + public SuggestedWords getSuggestions() { + return mSuggestions; + } + + public void clear() { + mSuggestionsStrip.removeAllViews(); + removeAllViews(); + addView(mSuggestionsStrip); + dismissMoreSuggestions(); + } + + private void hidePreview() { + mPreviewPopup.dismiss(); + } + + private void addToDictionary(CharSequence word) { + mListener.addWordToDictionary(word.toString()); + } + + private final KeyboardActionListener mMoreSuggestionsListener = + new KeyboardActionListener.Adapter() { + @Override + public boolean onCustomRequest(int requestCode) { + final int index = requestCode; + final CharSequence word = mSuggestions.getWord(index); + mListener.pickSuggestionManually(index, word); + dismissMoreSuggestions(); + return true; + } + + @Override + public void onCancelInput() { + dismissMoreSuggestions(); + } + }; + + private final MoreKeysPanel.Controller mMoreSuggestionsController = + new MoreKeysPanel.Controller() { + @Override + public boolean dismissMoreKeysPanel() { + return dismissMoreSuggestions(); + } + }; + + private boolean dismissMoreSuggestions() { + if (mMoreSuggestionsWindow.isShowing()) { + mMoreSuggestionsWindow.dismiss(); + return true; + } + return false; + } + + public boolean handleBack() { + return dismissMoreSuggestions(); + } + + @Override + public boolean onLongClick(View view) { + return showMoreSuggestions(); + } + + private boolean showMoreSuggestions() { + final SuggestionsViewParams params = mParams; + if (params.mMoreSuggestionsAvailable) { + final int stripWidth = getWidth(); + final View container = mMoreSuggestionsContainer; + final int maxWidth = stripWidth - container.getPaddingLeft() + - container.getPaddingRight(); + final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; + builder.layout(mSuggestions, params.mSuggestionsCountInStrip, maxWidth, + (int)(maxWidth * params.mMinMoreSuggestionsWidth), + params.mMaxMoreSuggestionsRow); + mMoreSuggestionsView.setKeyboard(builder.build()); + container.measure( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; + final int pointX = stripWidth / 2; + final int pointY = -params.mMoreSuggestionsBottomGap; + moreKeysPanel.showMoreKeysPanel( + this, mMoreSuggestionsController, pointX, pointY, + mMoreSuggestionsWindow, mMoreSuggestionsListener); + mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; + mOriginX = mLastX; + mOriginY = mLastY; + mKeyboardView.dimEntireKeyboard(true); + for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { + mWords.get(i).setPressed(false); + } + return true; + } + return false; + } + + // Working variables for onLongClick and dispatchTouchEvent. + private int mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; + private static final int MORE_SUGGESTIONS_IN_MODAL_MODE = 0; + private static final int MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING = 1; + private static final int MORE_SUGGESTIONS_IN_SLIDING_MODE = 2; + private int mLastX; + private int mLastY; + private int mOriginX; + private int mOriginY; + private final int mMoreSuggestionsModalTolerance; + private final GestureDetector mMoreSuggestionsSlidingDetector; + private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onScroll(MotionEvent down, MotionEvent me, float deltaX, float deltaY) { + final float dy = me.getY() - down.getY(); + if (deltaY > 0 && dy < 0) { + return showMoreSuggestions(); + } + return false; + } + }; + + @Override + public boolean dispatchTouchEvent(MotionEvent me) { + if (!mMoreSuggestionsWindow.isShowing() + || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) { + mLastX = (int)me.getX(); + mLastY = (int)me.getY(); + if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) { + return true; + } + return super.dispatchTouchEvent(me); + } + + final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; + final int action = me.getAction(); + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final PointerTracker tracker = PointerTracker.getPointerTracker(id, moreKeysPanel); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + final int translatedX = moreKeysPanel.translateX(x); + final int translatedY = moreKeysPanel.translateY(y); + + if (mMoreSuggestionsMode == MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING) { + if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance + || mOriginY - y >= mMoreSuggestionsModalTolerance) { + // Decided to be in the sliding input mode only when the touch point has been moved + // upward. + mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE; + tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { + // Decided to be in the modal input mode + mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; + } + return true; + } + + // MORE_SUGGESTIONS_IN_SLIDING_MODE + tracker.processMotionEvent(action, translatedX, translatedY, eventTime, moreKeysPanel); + return true; + } + + @Override + public void onClick(View view) { + if (view == mParams.mWordToSaveView) { + addToDictionary((CharSequence)view.getTag()); + clear(); + return; + } + + final Object tag = view.getTag(); + if (!(tag instanceof Integer)) + return; + final int index = (Integer) tag; + if (index >= mSuggestions.size()) + return; + + final CharSequence word = mSuggestions.getWord(index); + mListener.pickSuggestionManually(index, word); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mHandler.cancelAllMessages(); + hidePreview(); + } +} -- cgit v1.2.3-83-g751a