From e49bd1c43acad08f103b38430a8bbcba23f325b3 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" <takaoka@google.com> Date: Mon, 6 Dec 2010 21:26:38 +0900 Subject: [PATCH] Implement CandidateView using HorizontalScrollView This change re-implements CandidateView using HorizontalScrollView and customized Button, so that the candidate strip can be flingable. This change also introduces the following features. - Highlighting candidate strip and space bar is configurable by @bool/config_candidate_highlight_enabled. - Candidate strip can be centered with left/right padding if @dimen/candidate_strip_padding size is greater than 0. - Candidate text size is configurable by @dimen/candidate_text_size. Bug: 1575885 Bug: 3230726 Change-Id: Iedf78ff31bc9f2f6291a8e8fb7faa0e6b961dd6d --- .../drawable-hdpi/btn_candidate_normal.9.png | Bin 0 -> 215 bytes ...ssed.9.png => btn_candidate_pressed.9.png} | Bin .../drawable-mdpi/btn_candidate_normal.9.png | Bin 0 -> 205 bytes ...ssed.9.png => btn_candidate_pressed.9.png} | Bin java/res/drawable/btn_candidate.xml | 29 ++ java/res/layout/candidate.xml | 49 ++ java/res/layout/candidates.xml | 51 +- java/res/values-xlarge-land/dimens.xml | 1 + java/res/values-xlarge/bools.xml | 1 + java/res/values-xlarge/dimens.xml | 4 +- java/res/values/bools.xml | 1 + java/res/values/dimens.xml | 2 + .../inputmethod/latin/CandidateView.java | 483 +++++------------- .../android/inputmethod/latin/LatinIME.java | 20 +- 14 files changed, 261 insertions(+), 380 deletions(-) create mode 100644 java/res/drawable-hdpi/btn_candidate_normal.9.png rename java/res/drawable-hdpi/{list_selector_background_pressed.9.png => btn_candidate_pressed.9.png} (100%) create mode 100644 java/res/drawable-mdpi/btn_candidate_normal.9.png rename java/res/drawable-mdpi/{list_selector_background_pressed.9.png => btn_candidate_pressed.9.png} (100%) create mode 100644 java/res/drawable/btn_candidate.xml create mode 100644 java/res/layout/candidate.xml diff --git a/java/res/drawable-hdpi/btn_candidate_normal.9.png b/java/res/drawable-hdpi/btn_candidate_normal.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0ccdb6ab293eaaa169f335a05e1729d964e9aeeb GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^ia>0~!3HFkrWAoVk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XDm`5sLn>~)xp9&6fB}bVpzh5BhIi_9(l;1tdl<I<R(LR7 zM#X;BOwG9~Ugb@^^(v6V__e20Cr51Pj^9%r`h4<P#3gw~S)7H1LqNfyfq{_;#IU=6 x=-cU!o+aj4|4_t%3O=SEnZ<S`^O0x*!>vfEFQ4W1+ypv;!PC{xWt~$(6978}NZ$Ye literal 0 HcmV?d00001 diff --git a/java/res/drawable-hdpi/list_selector_background_pressed.9.png b/java/res/drawable-hdpi/btn_candidate_pressed.9.png similarity index 100% rename from java/res/drawable-hdpi/list_selector_background_pressed.9.png rename to java/res/drawable-hdpi/btn_candidate_pressed.9.png diff --git a/java/res/drawable-mdpi/btn_candidate_normal.9.png b/java/res/drawable-mdpi/btn_candidate_normal.9.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6c0fefffbbda6e931438d6387cb1d967d1bf05 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^VnD3P!3HD^XWVoKQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS>Jit;^O978H@y}4@0cR+!I^#Xrl`L}=V#m|^7B?V2_Q@ghG zXT8af77GQz#;F}AH1@nsU1|4lPl%V}ds*wi&>g=k?#y#Gc3xY`owxSv%e3P^EV`T( zeP$RutUt6f?UBdv)z&@p-Demi9%|v-Ke_sgPy)loP0GRVOpeY3TFv0;>gTe~DWM4f Dy~|Ig literal 0 HcmV?d00001 diff --git a/java/res/drawable-mdpi/list_selector_background_pressed.9.png b/java/res/drawable-mdpi/btn_candidate_pressed.9.png similarity index 100% rename from java/res/drawable-mdpi/list_selector_background_pressed.9.png rename to java/res/drawable-mdpi/btn_candidate_pressed.9.png diff --git a/java/res/drawable/btn_candidate.xml b/java/res/drawable/btn_candidate.xml new file mode 100644 index 0000000000..b0c1c30384 --- /dev/null +++ b/java/res/drawable/btn_candidate.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2010, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<selector + xmlns:android="http://schemas.android.com/apk/res/android" +> + <item + android:state_pressed="true" + android:drawable="@drawable/btn_candidate_pressed" /> + <item + android:drawable="@drawable/btn_candidate_normal" /> +</selector> diff --git a/java/res/layout/candidate.xml b/java/res/layout/candidate.xml new file mode 100644 index 0000000000..37179d2afc --- /dev/null +++ b/java/res/layout/candidate.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2010, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/candidate_strip_height" + android:orientation="horizontal" +> + <ImageView + android:id="@+id/candidate_divider" + android:layout_width="wrap_content" + android:layout_height="@dimen/candidate_strip_height" + android:visibility="gone" + android:focusable="false" + android:clickable="false" + android:src="@drawable/keyboard_suggest_strip_divider" + android:gravity="center_vertical|center_horizontal" /> + <Button + android:id="@+id/candidate_word" + android:layout_width="wrap_content" + android:layout_height="@dimen/candidate_strip_height" + android:minWidth="@dimen/candidate_min_touchable_width" + android:textSize="@dimen/candidate_text_size" + android:textColor="@color/candidate_normal" + android:background="@drawable/btn_candidate" + android:focusable="true" + android:clickable="true" + android:gravity="center_vertical|center_horizontal" + android:paddingLeft="12dip" + android:paddingRight="12dip" /> +</LinearLayout> diff --git a/java/res/layout/candidates.xml b/java/res/layout/candidates.xml index b89d442906..1b8d04183e 100644 --- a/java/res/layout/candidates.xml +++ b/java/res/layout/candidates.xml @@ -1,38 +1,45 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* +/* ** -** Copyright 2008, The Android Open Source Project +** Copyright 2010, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at +** 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 +** 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 +** 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. */ --> <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="@dimen/candidate_strip_height" - android:background="@drawable/keyboard_suggest_strip" - > - - <com.android.inputmethod.latin.CandidateView - android:id="@+id/candidates" + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="@dimen/candidate_strip_height" + android:background="@drawable/keyboard_suggest_strip" + android:paddingRight="@dimen/candidate_strip_padding" + android:paddingLeft="@dimen/candidate_strip_padding" +> + <HorizontalScrollView + android:id="@+id/candidates_scroll_view" android:layout_width="wrap_content" android:layout_height="@dimen/candidate_strip_height" - android:layout_weight="1" android:fadingEdge="horizontal" android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length" - /> - + android:scrollbars="none" + > + <com.android.inputmethod.latin.CandidateView + android:id="@+id/candidates" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="@dimen/candidate_strip_height" + android:background="@drawable/keyboard_suggest_strip" /> + </HorizontalScrollView> </LinearLayout> diff --git a/java/res/values-xlarge-land/dimens.xml b/java/res/values-xlarge-land/dimens.xml index 45d6dfa933..e1a53f9dce 100644 --- a/java/res/values-xlarge-land/dimens.xml +++ b/java/res/values-xlarge-land/dimens.xml @@ -22,4 +22,5 @@ <dimen name="key_label_text_size">18dip</dimen> <!-- left or right padding of label alignment --> <dimen name="key_label_horizontal_alignment_padding">18dip</dimen> + <dimen name="candidate_strip_padding">40.0mm</dimen> </resources> diff --git a/java/res/values-xlarge/bools.xml b/java/res/values-xlarge/bools.xml index 8c68d9dc37..abacfa18b3 100644 --- a/java/res/values-xlarge/bools.xml +++ b/java/res/values-xlarge/bools.xml @@ -22,4 +22,5 @@ <bool name="default_popup_preview">false</bool> <bool name="config_enable_show_settings_key_option">false</bool> <bool name="config_enable_show_voice_key_option">false</bool> + <bool name="config_candidate_highlight_font_color_enabled">false</bool> </resources> diff --git a/java/res/values-xlarge/dimens.xml b/java/res/values-xlarge/dimens.xml index f2d9ded05e..dc1b5c3782 100644 --- a/java/res/values-xlarge/dimens.xml +++ b/java/res/values-xlarge/dimens.xml @@ -40,5 +40,7 @@ <!-- left or right padding of label alignment --> <dimen name="key_label_horizontal_alignment_padding">4dip</dimen> - <dimen name="candidate_strip_height">46dip</dimen> + <dimen name="candidate_strip_height">56dip</dimen> + <dimen name="candidate_strip_padding">15.0mm</dimen> + <dimen name="candidate_text_size">22dip</dimen> </resources> diff --git a/java/res/values/bools.xml b/java/res/values/bools.xml index 2be95458e6..84b0fe1825 100644 --- a/java/res/values/bools.xml +++ b/java/res/values/bools.xml @@ -32,4 +32,5 @@ <bool name="config_long_press_comma_for_settings_enabled">true</bool> <bool name="config_enable_show_settings_key_option">true</bool> <bool name="config_enable_show_voice_key_option">true</bool> + <bool name="config_candidate_highlight_font_color_enabled">true</bool> </resources> diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml index 3839ff064b..833963cf74 100644 --- a/java/res/values/dimens.xml +++ b/java/res/values/dimens.xml @@ -46,6 +46,8 @@ <dimen name="candidate_strip_height">42dip</dimen> <dimen name="candidate_strip_fading_edge_length">63dip</dimen> + <dimen name="candidate_strip_padding">0dip</dimen> + <dimen name="candidate_text_size">18dip</dimen> <dimen name="spacebar_vertical_correction">4dip</dimen> <!-- If the screen height in landscape is larger than the below value, then the keyboard will not go into extract (fullscreen) mode. --> diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index 68f2889254..460ef8650d 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -1,12 +1,12 @@ /* - * Copyright (C) 2008 The Android Open Source Project - * + * Copyright (C) 2010 The Android Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,75 +18,55 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; import android.graphics.Typeface; -import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.text.TextUtils; import android.util.AttributeSet; -import android.view.GestureDetector; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup.LayoutParams; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.TextView; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -public class CandidateView extends View { - - private static final int OUT_OF_BOUNDS_WORD_INDEX = -1; - private static final int OUT_OF_BOUNDS_X_COORD = -1; - +public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener { private LatinIME mService; private final ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); - private boolean mShowingCompletions; - private CharSequence mSelectedString; - private int mSelectedIndex; - private int mTouchX = OUT_OF_BOUNDS_X_COORD; - private final Drawable mSelectionHighlight; - private boolean mTypedWordValid; - - private boolean mHaveMinimalSuggestion; - - private Rect mBgPadding; + private final ArrayList<View> mWords = new ArrayList<View>(); private final TextView mPreviewText; private final PopupWindow mPreviewPopup; - private int mCurrentWordIndex; - private Drawable mDivider; - - private static final int MAX_SUGGESTIONS = 32; - private static final int SCROLL_PIXELS = 20; - private final int[] mWordWidth = new int[MAX_SUGGESTIONS]; - private final int[] mWordX = new int[MAX_SUGGESTIONS]; - private int mPopupPreviewX; - private int mPopupPreviewY; + private static final int MAX_SUGGESTIONS = 16; - private static final int X_GAP = 10; - + private final boolean mConfigCandidateHighlightFontColorEnabled; private final int mColorNormal; private final int mColorRecommended; private final int mColorOther; - private final Paint mPaint; - private final int mDescent; - private boolean mScrolled; - private boolean mShowingAddToDictionary; - private CharSequence mAddToDictionaryHint; - private int mTargetScrollX; + private boolean mShowingCompletions; - private final int mMinTouchableWidth; + private boolean mShowingAddToDictionary; - private int mTotalWidth; - - private final GestureDetector mGestureDetector; + private static final long DELAY_HIDE_PREVIEW = 1000; + private static final int MSG_HIDE_PREVIEW = 0; + private final Handler mHandler = new Handler() { + @Override + public void dispatchMessage(Message msg) { + switch (msg.what) { + case MSG_HIDE_PREVIEW: + hidePreview(); + break; + } + } + }; /** * Construct a CandidateView for showing suggested words for completion. @@ -95,95 +75,35 @@ public class CandidateView extends View { */ public CandidateView(Context context, AttributeSet attrs) { super(context, attrs); - mSelectionHighlight = context.getResources().getDrawable( - R.drawable.list_selector_background_pressed); - LayoutInflater inflate = - (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); Resources res = context.getResources(); mPreviewPopup = new PopupWindow(context); - mPreviewText = (TextView) inflate.inflate(R.layout.candidate_preview, null); + LayoutInflater inflater = LayoutInflater.from(context); + mPreviewText = (TextView) inflater.inflate(R.layout.candidate_preview, null); mPreviewPopup.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); mPreviewPopup.setContentView(mPreviewText); mPreviewPopup.setBackgroundDrawable(null); mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation); + mConfigCandidateHighlightFontColorEnabled = + res.getBoolean(R.bool.config_candidate_highlight_font_color_enabled); mColorNormal = res.getColor(R.color.candidate_normal); mColorRecommended = res.getColor(R.color.candidate_recommended); mColorOther = res.getColor(R.color.candidate_other); - mDivider = res.getDrawable(R.drawable.keyboard_suggest_strip_divider); - mAddToDictionaryHint = res.getString(R.string.hint_add_to_dictionary); - mPaint = new Paint(); - mPaint.setColor(mColorNormal); - mPaint.setAntiAlias(true); - mPaint.setTextSize(mPreviewText.getTextSize()); - mPaint.setStrokeWidth(0); - mPaint.setTextAlign(Align.CENTER); - mDescent = (int) mPaint.descent(); - mMinTouchableWidth = (int)res.getDimension(R.dimen.candidate_min_touchable_width); - - mGestureDetector = new GestureDetector( - new CandidateStripGestureListener(mMinTouchableWidth)); - setWillNotDraw(false); - setHorizontalScrollBarEnabled(false); - setVerticalScrollBarEnabled(false); - scrollTo(0, getScrollY()); - } - - private class CandidateStripGestureListener extends GestureDetector.SimpleOnGestureListener { - private final int mTouchSlopSquare; - - public CandidateStripGestureListener(int touchSlop) { - // Slightly reluctant to scroll to be able to easily choose the suggestion - mTouchSlopSquare = touchSlop * touchSlop; - } - - @Override - public void onLongPress(MotionEvent me) { - if (mSuggestions.size() > 0) { - if (me.getX() + getScrollX() < mWordWidth[0] && getScrollX() < 10) { - longPressFirstWord(); - } - } + for (int i = 0; i < MAX_SUGGESTIONS; i++) { + View v = inflater.inflate(R.layout.candidate, null); + TextView tv = (TextView)v.findViewById(R.id.candidate_word); + tv.setTag(i); + tv.setOnClickListener(this); + if (i == 0) + tv.setOnLongClickListener(this); + ImageView divider = (ImageView)v.findViewById(R.id.candidate_divider); + // Do not display divider of first candidate. + divider.setVisibility(i == 0 ? View.GONE : View.VISIBLE); + mWords.add(v); } - @Override - public boolean onDown(MotionEvent e) { - mScrolled = false; - return false; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - if (!mScrolled) { - // This is applied only when we recognize that scrolling is starting. - final int deltaX = (int) (e2.getX() - e1.getX()); - final int deltaY = (int) (e2.getY() - e1.getY()); - final int distance = (deltaX * deltaX) + (deltaY * deltaY); - if (distance < mTouchSlopSquare) { - return true; - } - mScrolled = true; - } - - final int width = getWidth(); - mScrolled = true; - int scrollX = getScrollX(); - scrollX += (int) distanceX; - if (scrollX < 0) { - scrollX = 0; - } - if (distanceX > 0 && scrollX + width > mTotalWidth) { - scrollX -= (int) distanceX; - } - mTargetScrollX = scrollX; - scrollTo(scrollX, getScrollY()); - hidePreview(); - invalidate(); - return true; - } + scrollTo(0, getScrollY()); } /** @@ -193,145 +113,56 @@ public class CandidateView extends View { public void setService(LatinIME listener) { mService = listener; } - - @Override - public int computeHorizontalScrollRange() { - return mTotalWidth; - } - /** - * If the canvas is null, then only touch calculations are performed to pick the target - * candidate. - */ - @Override - protected void onDraw(Canvas canvas) { - if (canvas != null) { - super.onDraw(canvas); - } - mTotalWidth = 0; - - final int height = getHeight(); - if (mBgPadding == null) { - mBgPadding = new Rect(0, 0, 0, 0); - if (getBackground() != null) { - getBackground().getPadding(mBgPadding); + public void setSuggestions(List<CharSequence> suggestions, boolean completions, + boolean typedWordValid, boolean haveMinimalSuggestion) { + clear(); + if (suggestions != null) { + int insertCount = Math.min(suggestions.size(), MAX_SUGGESTIONS); + for (CharSequence suggestion : suggestions) { + mSuggestions.add(suggestion); + if (--insertCount == 0) + break; } - mDivider.setBounds(0, 0, mDivider.getIntrinsicWidth(), - mDivider.getIntrinsicHeight()); } final int count = mSuggestions.size(); - final Rect bgPadding = mBgPadding; - final Paint paint = mPaint; - final int touchX = mTouchX; - final int scrollX = getScrollX(); - final boolean scrolled = mScrolled; - final boolean typedWordValid = mTypedWordValid; - final int y = (int) (height + mPaint.getTextSize() - mDescent) / 2; - boolean existsAutoCompletion = false; - int x = 0; for (int i = 0; i < count; i++) { CharSequence suggestion = mSuggestions.get(i); if (suggestion == null) continue; final int wordLength = suggestion.length(); - paint.setColor(mColorNormal); - if (mHaveMinimalSuggestion - && ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) { - paint.setTypeface(Typeface.DEFAULT_BOLD); - paint.setColor(mColorRecommended); - existsAutoCompletion = true; - } else if (i != 0 || (wordLength == 1 && count > 1)) { - // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1 and - // there are multiple suggestions, such as the default punctuation list. - paint.setColor(mColorOther); - } - int wordWidth; - if ((wordWidth = mWordWidth[i]) == 0) { - float textWidth = paint.measureText(suggestion, 0, wordLength); - wordWidth = Math.max(mMinTouchableWidth, (int) textWidth + X_GAP * 2); - mWordWidth[i] = wordWidth; - } - - mWordX[i] = x; - - if (touchX != OUT_OF_BOUNDS_X_COORD && !scrolled - && touchX + scrollX >= x && touchX + scrollX < x + wordWidth) { - if (canvas != null && !mShowingAddToDictionary) { - canvas.translate(x, 0); - mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); - mSelectionHighlight.draw(canvas); - canvas.translate(-x, 0); + View v = mWords.get(i); + TextView tv = (TextView)v.findViewById(R.id.candidate_word); + tv.setTypeface(Typeface.DEFAULT); + tv.setTextColor(mColorNormal); + if (mConfigCandidateHighlightFontColorEnabled) { + if (haveMinimalSuggestion + && ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) { + tv.setTypeface(Typeface.DEFAULT_BOLD); + tv.setTextColor(mColorRecommended); + existsAutoCompletion = true; + } else if (i != 0 || (wordLength == 1 && count > 1)) { + // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1 + // and there are multiple suggestions, such as the default punctuation list. + tv.setTextColor(mColorOther); } - mSelectedString = suggestion; - mSelectedIndex = i; - } - - if (canvas != null) { - canvas.drawText(suggestion, 0, wordLength, x + wordWidth / 2, y, paint); - paint.setColor(mColorOther); - canvas.translate(x + wordWidth, 0); - // Draw a divider unless it's after the hint - if (!(mShowingAddToDictionary && i == 1)) { - mDivider.draw(canvas); - } - canvas.translate(-x - wordWidth, 0); - } - paint.setTypeface(Typeface.DEFAULT); - x += wordWidth; - } - mService.onAutoCompletionStateChanged(existsAutoCompletion); - mTotalWidth = x; - if (mTargetScrollX != scrollX) { - scrollToTarget(); - } - } - - private void scrollToTarget() { - int scrollX = getScrollX(); - if (mTargetScrollX > scrollX) { - scrollX += SCROLL_PIXELS; - if (scrollX >= mTargetScrollX) { - scrollX = mTargetScrollX; - scrollTo(scrollX, getScrollY()); - requestLayout(); } else { - scrollTo(scrollX, getScrollY()); - } - } else { - scrollX -= SCROLL_PIXELS; - if (scrollX <= mTargetScrollX) { - scrollX = mTargetScrollX; - scrollTo(scrollX, getScrollY()); - requestLayout(); - } else { - scrollTo(scrollX, getScrollY()); - } - } - invalidate(); - } - - public void setSuggestions(List<CharSequence> suggestions, boolean completions, - boolean typedWordValid, boolean haveMinimalSuggestion) { - clear(); - if (suggestions != null) { - int insertCount = Math.min(suggestions.size(), MAX_SUGGESTIONS); - for (CharSequence suggestion : suggestions) { - mSuggestions.add(suggestion); - if (--insertCount == 0) - break; + // TODO: Display underline for the auto-correction word } + tv.setText(suggestion); + tv.setClickable(true); + addView(v); } + mShowingCompletions = completions; - mTypedWordValid = typedWordValid; + // TODO: Move this call back to LatinIME + if (mConfigCandidateHighlightFontColorEnabled) + mService.onAutoCompletionStateChanged(existsAutoCompletion); + scrollTo(0, getScrollY()); - mTargetScrollX = 0; - mHaveMinimalSuggestion = haveMinimalSuggestion; - // Compute the total width - onDraw(null); - invalidate(); requestLayout(); } @@ -342,9 +173,12 @@ public class CandidateView extends View { public void showAddToDictionaryHint(CharSequence word) { ArrayList<CharSequence> suggestions = new ArrayList<CharSequence>(); suggestions.add(word); - suggestions.add(mAddToDictionaryHint); + suggestions.add(getContext().getText(R.string.hint_add_to_dictionary)); setSuggestions(suggestions, false, false, false); mShowingAddToDictionary = true; + // Disable R.string.hint_add_to_dictionary button + TextView tv = (TextView)getChildAt(1).findViewById(R.id.candidate_word); + tv.setClickable(false); } public boolean dismissAddToDictionaryHint() { @@ -361,127 +195,72 @@ public class CandidateView extends View { // Don't call mSuggestions.clear() because it's being used for logging // in LatinIME.pickSuggestionManually(). mSuggestions.clear(); - mTouchX = OUT_OF_BOUNDS_X_COORD; - mSelectedString = null; - mSelectedIndex = -1; mShowingAddToDictionary = false; - invalidate(); - Arrays.fill(mWordWidth, 0); - Arrays.fill(mWordX, 0); - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - - if (mGestureDetector.onTouchEvent(me)) { - return true; - } - - int action = me.getAction(); - int x = (int) me.getX(); - int y = (int) me.getY(); - mTouchX = x; - - switch (action) { - case MotionEvent.ACTION_DOWN: - invalidate(); - break; - case MotionEvent.ACTION_MOVE: - if (y <= 0) { - // Fling up!? - if (mSelectedString != null) { - // If there are completions from the application, we don't change the state to - // STATE_PICKED_SUGGESTION - if (!mShowingCompletions) { - // This "acceptedSuggestion" will not be counted as a word because - // it will be counted in pickSuggestion instead. - TextEntryState.acceptedSuggestion(mSuggestions.get(0), - mSelectedString); - } - mService.pickSuggestionManually(mSelectedIndex, mSelectedString); - mSelectedString = null; - mSelectedIndex = -1; - } - } - break; - case MotionEvent.ACTION_UP: - if (!mScrolled) { - if (mSelectedString != null) { - if (mShowingAddToDictionary) { - longPressFirstWord(); - clear(); - } else { - if (!mShowingCompletions) { - TextEntryState.acceptedSuggestion(mSuggestions.get(0), - mSelectedString); - } - mService.pickSuggestionManually(mSelectedIndex, mSelectedString); - } - } - } - mSelectedString = null; - mSelectedIndex = -1; - requestLayout(); - hidePreview(); - invalidate(); - break; - } - return true; + removeAllViews(); } private void hidePreview() { - mTouchX = OUT_OF_BOUNDS_X_COORD; - mCurrentWordIndex = OUT_OF_BOUNDS_WORD_INDEX; mPreviewPopup.dismiss(); } - - private void showPreview(int wordIndex, String altText) { - int oldWordIndex = mCurrentWordIndex; - mCurrentWordIndex = wordIndex; - // If index changed or changing text - if (oldWordIndex != mCurrentWordIndex || altText != null) { - if (wordIndex == OUT_OF_BOUNDS_WORD_INDEX) { - hidePreview(); - } else { - CharSequence word = altText != null? altText : mSuggestions.get(wordIndex); - mPreviewText.setText(word); - mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - int wordWidth = (int) (mPaint.measureText(word, 0, word.length()) + X_GAP * 2); - final int popupWidth = wordWidth - + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight(); - final int popupHeight = mPreviewText.getMeasuredHeight(); - //mPreviewText.setVisibility(INVISIBLE); - mPopupPreviewX = mWordX[wordIndex] - mPreviewText.getPaddingLeft() - getScrollX() - + (mWordWidth[wordIndex] - wordWidth) / 2; - mPopupPreviewY = - popupHeight; - int [] offsetInWindow = new int[2]; - getLocationInWindow(offsetInWindow); - if (mPreviewPopup.isShowing()) { - mPreviewPopup.update(mPopupPreviewX, mPopupPreviewY + offsetInWindow[1], - popupWidth, popupHeight); - } else { - mPreviewPopup.setWidth(popupWidth); - mPreviewPopup.setHeight(popupHeight); - mPreviewPopup.showAtLocation(this, Gravity.NO_GRAVITY, mPopupPreviewX, - mPopupPreviewY + offsetInWindow[1]); - } - mPreviewText.setVisibility(VISIBLE); - } + + private void showPreview(int index, CharSequence word) { + if (TextUtils.isEmpty(word)) + return; + + final TextView previewText = mPreviewText; + previewText.setText(word); + previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + View v = getChildAt(index); + final int[] offsetInWindow = new int[2]; + v.getLocationInWindow(offsetInWindow); + final int posX = offsetInWindow[0]; + final int posY = offsetInWindow[1] - previewText.getMeasuredHeight(); + final PopupWindow previewPopup = mPreviewPopup; + if (previewPopup.isShowing()) { + previewPopup.update(posX, posY, previewPopup.getWidth(), previewPopup.getHeight()); + } else { + previewPopup.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY); } + previewText.setVisibility(VISIBLE); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW); } - private void longPressFirstWord() { - CharSequence word = mSuggestions.get(0); - if (word.length() < 2) return; + private void addToDictionary(CharSequence word) { if (mService.addWordToDictionary(word.toString())) { - showPreview(0, getContext().getResources().getString(R.string.added_word, word)); + showPreview(0, getContext().getString(R.string.added_word, word)); + } + } + + @Override + public boolean onLongClick(View view) { + int index = (Integer) view.getTag(); + CharSequence word = mSuggestions.get(index); + if (word.length() < 2) + return false; + addToDictionary(word); + return true; + } + + @Override + public void onClick(View view) { + int index = (Integer) view.getTag(); + CharSequence word = mSuggestions.get(index); + if (mShowingAddToDictionary && index == 0) { + addToDictionary(word); + } else { + if (!mShowingCompletions) { + TextEntryState.acceptedSuggestion(mSuggestions.get(0), word); + } + mService.pickSuggestionManually(index, word); } } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); + mHandler.removeMessages(MSG_HIDE_PREVIEW); hidePreview(); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 3089153ad9..16a369a9ed 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -51,8 +51,10 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -65,6 +67,7 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; +import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import java.io.FileDescriptor; @@ -127,7 +130,7 @@ public class LatinIME extends InputMethodService SUGGESTION_VISIBILILTY_HIDE_VALUE }; - private LinearLayout mCandidateViewContainer; + private View mCandidateViewContainer; private CandidateView mCandidateView; private Suggest mSuggest; private CompletionInfo[] mCompletions; @@ -496,12 +499,19 @@ public class LatinIME extends InputMethodService @Override public View onCreateCandidatesView() { - mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate( - R.layout.candidates, null); - mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates); + LayoutInflater inflater = getLayoutInflater(); + LinearLayout container = (LinearLayout)inflater.inflate(R.layout.candidates, null); + mCandidateViewContainer = container; + if (container.getPaddingRight() != 0) { + HorizontalScrollView scrollView = + (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view); + scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); + container.setGravity(Gravity.CENTER_HORIZONTAL); + } + mCandidateView = (CandidateView) container.findViewById(R.id.candidates); mCandidateView.setService(this); setCandidatesViewShown(true); - return mCandidateViewContainer; + return container; } private static boolean isPasswordVariation(int variation) { -- GitLab