Home | History | Annotate | Download | only in suggestions
      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