1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.latin.suggestions; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.util.AttributeSet; 22 import android.view.GestureDetector; 23 import android.view.LayoutInflater; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.View.OnClickListener; 27 import android.view.View.OnLongClickListener; 28 import android.view.ViewGroup; 29 import android.widget.RelativeLayout; 30 import android.widget.TextView; 31 32 import com.android.inputmethod.keyboard.Keyboard; 33 import com.android.inputmethod.keyboard.KeyboardSwitcher; 34 import com.android.inputmethod.keyboard.MainKeyboardView; 35 import com.android.inputmethod.keyboard.MoreKeysPanel; 36 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; 37 import com.android.inputmethod.latin.Constants; 38 import com.android.inputmethod.latin.LatinImeLogger; 39 import com.android.inputmethod.latin.R; 40 import com.android.inputmethod.latin.SuggestedWords; 41 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 42 import com.android.inputmethod.latin.define.ProductionFlag; 43 import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener; 44 import com.android.inputmethod.latin.utils.CollectionUtils; 45 import com.android.inputmethod.research.ResearchLogger; 46 47 import java.util.ArrayList; 48 49 public final class SuggestionStripView extends RelativeLayout implements OnClickListener, 50 OnLongClickListener { 51 public interface Listener { 52 public void addWordToUserDictionary(String word); 53 public void pickSuggestionManually(int index, SuggestedWordInfo word); 54 } 55 56 // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. 57 public static final int MAX_SUGGESTIONS = 18; 58 59 static final boolean DBG = LatinImeLogger.sDBG; 60 61 private final ViewGroup mSuggestionsStrip; 62 MainKeyboardView mMainKeyboardView; 63 64 private final View mMoreSuggestionsContainer; 65 private final MoreSuggestionsView mMoreSuggestionsView; 66 private final MoreSuggestions.Builder mMoreSuggestionsBuilder; 67 68 private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList(); 69 private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList(); 70 private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList(); 71 72 Listener mListener; 73 private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; 74 75 private final SuggestionStripLayoutHelper mLayoutHelper; 76 77 /** 78 * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user. 79 * @param context 80 * @param attrs 81 */ 82 public SuggestionStripView(final Context context, final AttributeSet attrs) { 83 this(context, attrs, R.attr.suggestionStripViewStyle); 84 } 85 86 public SuggestionStripView(final Context context, final AttributeSet attrs, 87 final int defStyle) { 88 super(context, attrs, defStyle); 89 90 final LayoutInflater inflater = LayoutInflater.from(context); 91 inflater.inflate(R.layout.suggestions_strip, this); 92 93 mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); 94 for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) { 95 final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null); 96 word.setOnClickListener(this); 97 word.setOnLongClickListener(this); 98 mWordViews.add(word); 99 final View divider = inflater.inflate(R.layout.suggestion_divider, null); 100 divider.setOnClickListener(this); 101 mDividerViews.add(divider); 102 mDebugInfoViews.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); 103 } 104 105 mLayoutHelper = new SuggestionStripLayoutHelper( 106 context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews); 107 108 mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); 109 mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer 110 .findViewById(R.id.more_suggestions_view); 111 mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView); 112 113 final Resources res = context.getResources(); 114 mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset( 115 R.dimen.more_suggestions_modal_tolerance); 116 mMoreSuggestionsSlidingDetector = new GestureDetector( 117 context, mMoreSuggestionsSlidingListener); 118 } 119 120 /** 121 * A connection back to the input method. 122 * @param listener 123 */ 124 public void setListener(final Listener listener, final View inputView) { 125 mListener = listener; 126 mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view); 127 } 128 129 public void setSuggestions(final SuggestedWords suggestedWords) { 130 clear(); 131 mSuggestedWords = suggestedWords; 132 mLayoutHelper.layout(mSuggestedWords, mSuggestionsStrip, this); 133 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 134 ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords); 135 } 136 } 137 138 public int setMoreSuggestionsHeight(final int remainingHeight) { 139 return mLayoutHelper.setMoreSuggestionsHeight(remainingHeight); 140 } 141 142 public boolean isShowingAddToDictionaryHint() { 143 return mSuggestionsStrip.getChildCount() > 0 144 && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); 145 } 146 147 public void showAddToDictionaryHint(final String word, final CharSequence hintText) { 148 clear(); 149 mLayoutHelper.layoutAddToDictionaryHint( 150 word, mSuggestionsStrip, getWidth(), hintText, this); 151 } 152 153 public boolean dismissAddToDictionaryHint() { 154 if (isShowingAddToDictionaryHint()) { 155 clear(); 156 return true; 157 } 158 return false; 159 } 160 161 public void clear() { 162 mSuggestionsStrip.removeAllViews(); 163 removeAllViews(); 164 addView(mSuggestionsStrip); 165 mMoreSuggestionsView.dismissMoreKeysPanel(); 166 } 167 168 private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() { 169 @Override 170 public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) { 171 mListener.pickSuggestionManually(index, wordInfo); 172 mMoreSuggestionsView.dismissMoreKeysPanel(); 173 } 174 175 @Override 176 public void onCancelInput() { 177 mMoreSuggestionsView.dismissMoreKeysPanel(); 178 } 179 }; 180 181 private final MoreKeysPanel.Controller mMoreSuggestionsController = 182 new MoreKeysPanel.Controller() { 183 @Override 184 public void onDismissMoreKeysPanel(final MoreKeysPanel panel) { 185 mMainKeyboardView.onDismissMoreKeysPanel(panel); 186 } 187 188 @Override 189 public void onShowMoreKeysPanel(final MoreKeysPanel panel) { 190 mMainKeyboardView.onShowMoreKeysPanel(panel); 191 } 192 193 @Override 194 public void onCancelMoreKeysPanel(final MoreKeysPanel panel) { 195 mMoreSuggestionsView.dismissMoreKeysPanel(); 196 } 197 }; 198 199 @Override 200 public boolean onLongClick(final View view) { 201 AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( 202 Constants.NOT_A_CODE, this); 203 return showMoreSuggestions(); 204 } 205 206 boolean showMoreSuggestions() { 207 final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard(); 208 if (parentKeyboard == null) { 209 return false; 210 } 211 final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper; 212 if (!layoutHelper.mMoreSuggestionsAvailable) { 213 return false; 214 } 215 final int stripWidth = getWidth(); 216 final View container = mMoreSuggestionsContainer; 217 final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight(); 218 final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; 219 builder.layout(mSuggestedWords, layoutHelper.mSuggestionsCountInStrip, maxWidth, 220 (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth), 221 layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard); 222 mMoreSuggestionsView.setKeyboard(builder.build()); 223 container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 224 225 final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; 226 final int pointX = stripWidth / 2; 227 final int pointY = -layoutHelper.mMoreSuggestionsBottomGap; 228 moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY, 229 mMoreSuggestionsListener); 230 mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; 231 mOriginX = mLastX; 232 mOriginY = mLastY; 233 for (int i = 0; i < layoutHelper.mSuggestionsCountInStrip; i++) { 234 mWordViews.get(i).setPressed(false); 235 } 236 return true; 237 } 238 239 // Working variables for onLongClick and dispatchTouchEvent. 240 private int mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; 241 private static final int MORE_SUGGESTIONS_IN_MODAL_MODE = 0; 242 private static final int MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING = 1; 243 private static final int MORE_SUGGESTIONS_IN_SLIDING_MODE = 2; 244 private int mLastX; 245 private int mLastY; 246 private int mOriginX; 247 private int mOriginY; 248 private final int mMoreSuggestionsModalTolerance; 249 private final GestureDetector mMoreSuggestionsSlidingDetector; 250 private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener = 251 new GestureDetector.SimpleOnGestureListener() { 252 @Override 253 public boolean onScroll(MotionEvent down, MotionEvent me, float deltaX, float deltaY) { 254 final float dy = me.getY() - down.getY(); 255 if (deltaY > 0 && dy < 0) { 256 return showMoreSuggestions(); 257 } 258 return false; 259 } 260 }; 261 262 @Override 263 public boolean dispatchTouchEvent(final MotionEvent me) { 264 if (!mMoreSuggestionsView.isShowingInParent()) { 265 mLastX = (int)me.getX(); 266 mLastY = (int)me.getY(); 267 if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) { 268 return true; 269 } 270 return super.dispatchTouchEvent(me); 271 } 272 273 final int action = me.getAction(); 274 final int index = me.getActionIndex(); 275 final int x = (int)me.getX(index); 276 final int y = (int)me.getY(index); 277 278 if (mMoreSuggestionsMode == MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING) { 279 if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance 280 || mOriginY - y >= mMoreSuggestionsModalTolerance) { 281 // Decided to be in the sliding input mode only when the touch point has been moved 282 // upward. 283 mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE; 284 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { 285 // Decided to be in the modal input mode 286 mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; 287 mMoreSuggestionsView.adjustVerticalCorrectionForModalMode(); 288 } 289 return true; 290 } 291 292 // MORE_SUGGESTIONS_IN_SLIDING_MODE 293 me.setLocation(mMoreSuggestionsView.translateX(x), mMoreSuggestionsView.translateY(y)); 294 mMoreSuggestionsView.onTouchEvent(me); 295 return true; 296 } 297 298 @Override 299 public void onClick(final View view) { 300 if (mLayoutHelper.isAddToDictionaryShowing(view)) { 301 mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord()); 302 clear(); 303 return; 304 } 305 306 final Object tag = view.getTag(); 307 // Integer tag is set at 308 // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and 309 // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup} 310 if (!(tag instanceof Integer)) { 311 return; 312 } 313 final int index = (Integer) tag; 314 if (index >= mSuggestedWords.size()) { 315 return; 316 } 317 318 final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index); 319 mListener.pickSuggestionManually(index, wordInfo); 320 } 321 322 @Override 323 protected void onDetachedFromWindow() { 324 super.onDetachedFromWindow(); 325 mMoreSuggestionsView.dismissMoreKeysPanel(); 326 } 327 } 328