diff --git a/java/res/layout/more_suggestions.xml b/java/res/layout/more_suggestions.xml new file mode 100644 index 000000000..d387a1573 --- /dev/null +++ b/java/res/layout/more_suggestions.xml @@ -0,0 +1,37 @@ + + + + + diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml index 73e1aff96..4bf0e2092 100644 --- a/java/res/values-land/dimens.xml +++ b/java/res/values-land/dimens.xml @@ -59,6 +59,8 @@ 0.01in 36dip + 36dip + 160sp 63dip diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml index 942bc726f..6ce23cc70 100644 --- a/java/res/values-sw600dp/dimens.xml +++ b/java/res/values-sw600dp/dimens.xml @@ -67,6 +67,7 @@ 0.05in 44dip + 44dip 15.0mm 0.3in 12dip diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml index f0340bc70..9b307e6f3 100644 --- a/java/res/values-sw768dp/dimens.xml +++ b/java/res/values-sw768dp/dimens.xml @@ -70,9 +70,8 @@ 0.05in 44dip - - 18mm + 44dip + 200sp 15.0mm 46dip 8dip diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index fdeca258f..9d3426842 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -27,6 +27,7 @@ + diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml index 2c4b35ef8..b7609a4ac 100644 --- a/java/res/values/dimens.xml +++ b/java/res/values/dimens.xml @@ -77,9 +77,11 @@ 0.05in 40dip - - 100sp + 12dip + 40dip + 0.2in + 12% + 200sp 63dip 0dip 44dip diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml index ddc0634f4..4cc2a5306 100644 --- a/java/res/values/styles.xml +++ b/java/res/values/styles.xml @@ -79,6 +79,11 @@ @dimen/mini_keyboard_horizontal_edges_padding @dimen/mini_keyboard_horizontal_edges_padding + @@ -235,6 +240,11 @@ parent="MiniKeyboardPanelStyle.IceCreamSandwich" > + diff --git a/java/res/values/themes-stone.xml b/java/res/values/themes-stone.xml index 494bae600..35390d111 100644 --- a/java/res/values/themes-stone.xml +++ b/java/res/values/themes-stone.xml @@ -22,6 +22,7 @@ @style/MiniKeyboardView.Stone @style/MiniKeyboardPanelStyle @style/SuggestionsStripBackgroundStyle + @style/SuggestionsPaneViewStyle @style/SuggestionBackgroundStyle @style/SuggestionPreviewBackgroundStyle @style/CandidateViewStyle diff --git a/java/res/xml-sw600dp/kbd_mini_keyboard_template.xml b/java/res/xml-sw600dp/kbd_mini_keyboard_template.xml index d97649965..9955fe8fd 100644 --- a/java/res/xml-sw600dp/kbd_mini_keyboard_template.xml +++ b/java/res/xml-sw600dp/kbd_mini_keyboard_template.xml @@ -20,8 +20,6 @@ diff --git a/java/res/xml-sw768dp/kbd_mini_keyboard_template.xml b/java/res/xml-sw768dp/kbd_mini_keyboard_template.xml index 7d39d1a3a..1c15a5e9c 100644 --- a/java/res/xml-sw768dp/kbd_mini_keyboard_template.xml +++ b/java/res/xml-sw768dp/kbd_mini_keyboard_template.xml @@ -20,8 +20,6 @@ diff --git a/java/res/xml/kbd_mini_keyboard_template.xml b/java/res/xml/kbd_mini_keyboard_template.xml index 79db081a1..d25878b48 100644 --- a/java/res/xml/kbd_mini_keyboard_template.xml +++ b/java/res/xml/kbd_mini_keyboard_template.xml @@ -20,8 +20,6 @@ diff --git a/java/res/xml/kbd_suggestions_pane_template.xml b/java/res/xml/kbd_suggestions_pane_template.xml new file mode 100644 index 000000000..21316e6bb --- /dev/null +++ b/java/res/xml/kbd_suggestions_pane_template.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java index 520466c2a..f331662d7 100644 --- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java @@ -88,16 +88,7 @@ public class MiniKeyboardView extends KeyboardView implements MoreKeysPanel { } } - private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy() { - @Override - public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {} - @Override - public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {} - @Override - public void cancelLongPressTimer() {} - @Override - public void cancelKeyTimers() {} - }; + private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter(); private final KeyboardActionListener mMiniKeyboardListener = new KeyboardActionListener.Adapter() { diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index d4f580d21..0314867b3 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -76,6 +76,17 @@ public class PointerTracker { public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker); public void cancelLongPressTimer(); public void cancelKeyTimers(); + + public static class Adapter implements TimerProxy { + @Override + public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {} + @Override + public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {} + @Override + public void cancelLongPressTimer() {} + @Override + public void cancelKeyTimers() {} + } } private static KeyboardSwitcher sKeyboardSwitcher; diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index b9ded31cb..0d355d01e 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -23,6 +23,7 @@ import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Message; +import android.os.SystemClock; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; @@ -34,8 +35,10 @@ import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -46,6 +49,9 @@ import android.widget.TextView; import com.android.inputmethod.compat.FrameLayoutCompatUtils; import com.android.inputmethod.compat.LinearLayoutCompatUtils; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.MoreKeysPanel; +import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import java.util.ArrayList; @@ -58,16 +64,22 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. - private static final int MAX_SUGGESTIONS = 18; + public static final int MAX_SUGGESTIONS = 18; private static final boolean DBG = LatinImeLogger.sDBG; private final ViewGroup mCandidatesPlacer; private final ViewGroup mCandidatesStrip; + // TODO: Remove these pane related fields and stuffs. private ViewGroup mCandidatesPane; private ViewGroup mCandidatesPaneContainer; private View mKeyboardView; + private final View mMoreSuggestionsContainer; + private final MoreSuggestionsView mMoreSuggestionsView; + private final MoreSuggestions.Builder mMoreSuggestionsBuilder; + private final PopupWindow mMoreSuggestionsWindow; + private final ArrayList mWords = new ArrayList(); private final ArrayList mInfos = new ArrayList(); private final ArrayList mDividers = new ArrayList(); @@ -159,7 +171,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo mDividerHeight = divider.getMeasuredHeight(); final Resources res = word.getResources(); - mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height); + mCandidateStripHeight = res.getDimensionPixelSize(R.dimen.candidate_strip_height); } } @@ -257,7 +269,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo private final int mColorTypedWord; private final int mColorAutoCorrect; private final int mColorSuggestedCandidate; - private final int mCandidateCountInStrip; + public final int mCandidateCountInStrip; private final float mCenterCandidateWeight; private final int mCenterCandidateIndex; private final Drawable mMoreCandidateHint; @@ -449,7 +461,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } } } - return countInStrip; } @@ -585,6 +596,15 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo mInfos); mPaneParams = new SuggestionsPaneParams(mWords, mDividers, mInfos); mStripParams.mWordToSaveView.setOnClickListener(this); + + mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); + mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer + .findViewById(R.id.more_suggestions_view); + mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView); + mMoreSuggestionsWindow = new PopupWindow(context); + mMoreSuggestionsWindow.setWindowLayoutMode( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mMoreSuggestionsWindow.setBackgroundDrawable(null); } /** @@ -621,8 +641,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo final int width = getWidth(); final int countInStrip = mStripParams.layout( mSuggestions, mCandidatesStrip, mCandidatesPlacer, width); - mPaneParams.layout( - mSuggestions, mCandidatesPane, countInStrip, mStripParams.getTextColor(), width); } private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) { @@ -787,6 +805,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo mCandidatesStrip.removeAllViews(); mCandidatesPane.removeAllViews(); closeCandidatesPane(); + mMoreSuggestionsWindow.dismiss(); } private void hidePreview() { @@ -823,15 +842,102 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } } + private final KeyboardActionListener mMoreSuggestionsListener = + new KeyboardActionListener.Adapter() { + @Override + public boolean onCustomRequest(int requestCode) { + final int index = requestCode; + final CharSequence word = mSuggestions.getWord(index); + mListener.pickSuggestionManually(index, word); + mMoreSuggestionsView.dismissMoreKeysPanel(); + return true; + } + + @Override + public void onCancelInput() { + mMoreSuggestionsView.dismissMoreKeysPanel(); + } + }; + + private final MoreKeysPanel.Controller mMoreSuggestionsController = + new MoreKeysPanel.Controller() { + @Override + public boolean dismissMoreKeysPanel() { + if (mMoreSuggestionsWindow.isShowing()) { + mMoreSuggestionsWindow.dismiss(); + return true; + } + return false; + } + }; + @Override public boolean onLongClick(View view) { - if (mStripParams.mMoreSuggestionsAvailable) { - toggleCandidatesPane(); + final SuggestionsStripParams params = mStripParams; + if (params.mMoreSuggestionsAvailable) { + final int stripWidth = getWidth(); + final View container = mMoreSuggestionsContainer; + final int maxWidth = stripWidth - container.getPaddingLeft() + - container.getPaddingRight(); + final DisplayMetrics dm = getContext().getResources().getDisplayMetrics(); + // TODO: Revise how we determine the height + final int maxHeight = dm.heightPixels - mKeyboardView.getHeight() - getHeight() * 3; + final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; + builder.layout(mSuggestions, params.mCandidateCountInStrip, maxWidth, maxHeight); + mMoreSuggestionsView.setKeyboard(builder.build()); + container.measure( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; + final int pointX = stripWidth / 2; + final int pointY = 0; + moreKeysPanel.showMoreKeysPanel( + this, mMoreSuggestionsController, pointX, pointY, + mMoreSuggestionsWindow, mMoreSuggestionsListener); + // TODO: Should figure out how to select the pointer tracker correctly. + final PointerTracker tracker = PointerTracker.getPointerTracker(0, moreKeysPanel); + final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); + final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); + tracker.onShowMoreKeysPanel( + translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel); + view.setPressed(false); + // TODO: Should gray out the keyboard here as well? return true; } return false; } + @Override + public boolean dispatchTouchEvent(MotionEvent me) { + if (!mMoreSuggestionsWindow.isShowing()) { + return super.dispatchTouchEvent(me); + } + final int action = me.getAction(); + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final PointerTracker tracker = PointerTracker.getPointerTracker(id, mMoreSuggestionsView); + final int x = mMoreSuggestionsView.translateX((int)me.getX(index)); + final int y = mMoreSuggestionsView.translateY((int)me.getY(index)); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + tracker.onDownEvent(x, y, eventTime, mMoreSuggestionsView); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + tracker.onUpEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_MOVE: + tracker.onMoveEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_CANCEL: + tracker.onCancelEvent(x, y, eventTime); + break; + } + return true; + } + @Override public void onClick(View view) { if (view == mStripParams.mWordToSaveView) { diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/MoreSuggestions.java new file mode 100644 index 000000000..0446fb2a8 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/MoreSuggestions.java @@ -0,0 +1,203 @@ +/* + * 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.graphics.Paint; +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[CandidateView.MAX_SUGGESTIONS]; + private final int[] mRowNumbers = new int[CandidateView.MAX_SUGGESTIONS]; + private final int[] mColumnOrders = new int[CandidateView.MAX_SUGGESTIONS]; + private final int[] mNumColumnsInRow = new int[CandidateView.MAX_SUGGESTIONS]; + private static final int MAX_COLUMNS_IN_ROW = 3; + private int mNumRows; + + public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int maxHeight, + KeyboardView view) { + clearKeys(); + final Paint paint = new Paint(); + paint.setAntiAlias(true); + final int padding = (int) view.getContext().getResources() + .getDimension(R.dimen.more_suggestions_key_horizontal_padding); + + int row = 0; + int pos = fromPos, rowStartPos = fromPos; + final int size = Math.min(suggestions.size(), CandidateView.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; + if (numColumn > MAX_COLUMNS_IN_ROW + || !fitInWidth(rowStartPos, pos + 1, maxWidth / numColumn)) { + if ((row + 1) * mDefaultRowHeight > maxHeight) { + break; + } + mNumColumnsInRow[row] = pos - rowStartPos; + rowStartPos = pos; + row++; + } + mColumnOrders[pos] = pos - rowStartPos; + mRowNumbers[pos] = row; + pos++; + } + mNumColumnsInRow[row] = pos - rowStartPos; + mNumRows = row + 1; + mWidth = mOccupiedWidth = calcurateMaxRowWidth(fromPos, pos); + mHeight = 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 numColumn = mNumColumnsInRow[row]; + int maxKeyWidth = 0; + while (pos < endPos && mRowNumbers[pos] == row) { + maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]); + pos++; + } + maxRowWidth = Math.max(maxRowWidth, maxKeyWidth * numColumn); + } + return maxRowWidth; + } + + private static final int[][] COLUMN_ORDER_TO_NUMBER = { + { 0, }, + { 1, 0, }, + { 2, 0, 1}, + }; + + private int getColumnNumber(int pos) { + final int columnOrder = mColumnOrders[pos]; + final int numColumn = mNumColumnsInRow[mRowNumbers[pos]]; + return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; + } + + public int getX(int pos) { + final int columnNumber = getColumnNumber(pos); + return columnNumber * getWidth(pos); + } + + public int getY(int pos) { + final int row = mRowNumbers[pos]; + return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; + } + + public int getWidth(int pos) { + final int row = mRowNumbers[pos]; + final int numColumn = mNumColumnsInRow[row]; + return mWidth / numColumn; + } + + public int getFlags(int pos) { + int rowFlags = 0; + + final int row = mRowNumbers[pos]; + if (row == 0) + rowFlags |= Keyboard.EDGE_BOTTOM; + if (row == mNumRows - 1) + rowFlags |= Keyboard.EDGE_TOP; + + final int numColumn = mNumColumnsInRow[row]; + final int column = getColumnNumber(pos); + if (column == 0) + rowFlags |= Keyboard.EDGE_LEFT; + if (column == numColumn - 1) + rowFlags |= Keyboard.EDGE_RIGHT; + + return rowFlags; + } + } + + public Builder(MoreSuggestionsView paneView) { + super(paneView.getContext(), new MoreSuggestionsParam()); + mPaneView = paneView; + } + + public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth, + int maxHeight) { + final Keyboard keyboard = KeyboardSwitcher.getInstance().getLatinKeyboard(); + final int xmlId = R.xml.kbd_suggestions_pane_template; + load(keyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId)); + mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2; + + final int count = mParams.layout(suggestions, fromPos, maxWidth, maxHeight, 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 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, params.getX(pos), params.getY(pos), + params.getWidth(pos), params.mDefaultRowHeight, params.getFlags(pos)); + params.onAddKey(key); + } + return new MoreSuggestions(params); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java new file mode 100644 index 000000000..828490112 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java @@ -0,0 +1,237 @@ +/* + * 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.View; +import android.widget.PopupWindow; + +import com.android.inputmethod.keyboard.Key; +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.MoreKeysPanel; +import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; +import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; + +import java.util.List; + +/** + * 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 mKeyDetector; + + private Controller mController; + private KeyboardActionListener mListener; + private int mOriginX; + private int mOriginY; + + private static class SuggestionsPaneKeyDetector extends KeyDetector { + private final int mSlideAllowanceSquare; + private final int mSlideAllowanceSquareTop; + + public SuggestionsPaneKeyDetector(float slideAllowance) { + super(/* keyHysteresisDistance */0); + mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance); + // Top slide allowance is slightly longer (sqrt(2) times) than other edges. + mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2; + } + + @Override + public boolean alwaysAllowsSlidingInput() { + return true; + } + + @Override + protected int getMaxNearbyKeys() { + // No nearby key will be returned. + return 1; + } + + @Override + public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) { + final List keys = getKeyboard().mKeys; + final int touchX = getTouchX(x); + final int touchY = getTouchY(y); + + int nearestIndex = NOT_A_KEY; + int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; + final int keyCount = keys.size(); + for (int index = 0; index < keyCount; index++) { + final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY); + if (dist < nearestDist) { + nearestIndex = index; + nearestDist = dist; + } + } + + if (allCodes != null && nearestIndex != NOT_A_KEY) + allCodes[0] = keys.get(nearestIndex).mCode; + return nearestIndex; + } + } + + 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) { + mListener.onCustomRequest(primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE); + } + + @Override + public void onCancelInput() { + mListener.onCancelInput(); + } + }; + + public MoreSuggestionsView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.suggestionsPaneViewStyle); + } + + public MoreSuggestionsView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final Resources res = context.getResources(); + // Override default ProximityKeyDetector. + mKeyDetector = new SuggestionsPaneKeyDetector(res.getDimension( + R.dimen.more_suggestions_slide_allowance)); + // Remove gesture detector on suggestions pane + 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); + mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), + -getPaddingTop() + mVerticalCorrection); + } + + @Override + public KeyDetector getKeyDetector() { + return mKeyDetector; + } + + @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; + } + + @Override + public boolean dismissMoreKeysPanel() { + return mController.dismissMoreKeysPanel(); + } + + @Override + public int translateX(int x) { + return x - mOriginX; + } + + @Override + public int translateY(int y) { + return y - mOriginY; + } +}