Home | History | Annotate | Download | only in openwnn
      1 /*
      2  * Copyright (C) 2008-2012  OMRON SOFTWARE Co., Ltd.
      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 package jp.co.omronsoft.openwnn;
     17 
     18 import android.app.AlertDialog;
     19 import android.content.Context;
     20 import android.content.DialogInterface;
     21 import android.content.SharedPreferences;
     22 import android.content.res.Resources;
     23 import android.graphics.drawable.Drawable;
     24 import android.media.AudioManager;
     25 import android.os.Handler;
     26 import android.os.Message;
     27 import android.os.Vibrator;
     28 import android.text.Layout;
     29 import android.text.SpannableString;
     30 import android.text.Spanned;
     31 import android.text.style.AbsoluteSizeSpan;
     32 import android.text.style.AlignmentSpan;
     33 import android.util.DisplayMetrics;
     34 import android.util.Log;
     35 import android.view.KeyEvent;
     36 import android.view.LayoutInflater;
     37 import android.view.MotionEvent;
     38 import android.view.View;
     39 import android.view.ViewGroup;
     40 import android.view.View.OnClickListener;
     41 import android.view.View.OnLongClickListener;
     42 import android.widget.Button;
     43 import android.widget.ImageView;
     44 import android.widget.LinearLayout;
     45 import android.widget.HorizontalScrollView;
     46 import android.widget.TextView;
     47 
     48 import java.util.ArrayList;
     49 
     50 /**
     51  * The default candidates view manager using {@link android.widget.EditText}.
     52  *
     53  * @author Copyright (C) 2011 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
     54  */
     55 public class TextCandidates1LineViewManager extends CandidatesViewManager {
     56 
     57     /** displayCandidates() normal display */
     58     private static final int IS_NEXTCANDIDATE_NORMAL = 1;
     59     /** displayCandidates() delay display */
     60     private static final int IS_NEXTCANDIDATE_DELAY = 2;
     61     /** displayCandidates() display end */
     62     private static final int IS_NEXTCANDIDATE_END = 3;
     63 
     64     /** Delay of set candidate */
     65     private static final int SET_CANDIDATE_DELAY = 50;
     66     /** Delay Millis */
     67     private static final int CANDIDATE_DELAY_MILLIS = 500;
     68 
     69     /** Scroll distance */
     70     private static final float SCROLL_DISTANCE = 0.9f;
     71 
     72     /** Body view of the candidates list */
     73     private ViewGroup  mViewBody;
     74     /** Scroller */
     75     private HorizontalScrollView mViewBodyScroll;
     76     /** Left more button */
     77     private ImageView mLeftMoreButton;
     78     /** Right more button */
     79     private ImageView mRightMoreButton;
     80     /** Candidate view */
     81     private LinearLayout mViewCandidateList;
     82 
     83     /** {@link OpenWnn} instance using this manager */
     84     private OpenWnn mWnn;
     85     /** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */
     86     private int mViewType;
     87 
     88     /** view width */
     89     private int mViewWidth;
     90     /** Minimum width of candidate view */
     91     private int mCandidateMinimumWidth;
     92     /** Minimum height of candidate view */
     93     private int mCandidateMinimumHeight;
     94 
     95     /** Minimum width of candidate view */
     96     private static final int CANDIDATE_MINIMUM_WIDTH = 48;
     97 
     98     /** Whether hide the view if there is no candidate */
     99     private boolean mAutoHideMode;
    100     /** The converter to get candidates from and notice the selected candidate to. */
    101     private WnnEngine mConverter;
    102     /** Limitation of displaying candidates */
    103     private int mDisplayLimit;
    104 
    105     /** Vibrator for touch vibration */
    106     private Vibrator mVibrator = null;
    107     /** AudioManager for click sound */
    108     private AudioManager mSound = null;
    109 
    110     /** Number of candidates displaying */
    111     private int mWordCount;
    112     /** List of candidates */
    113     private ArrayList<WnnWord> mWnnWordArray;
    114 
    115     /** Character width of the candidate area */
    116     private int mLineLength = 0;
    117     /** Maximum width of candidate view */
    118     private int mCandidateMaxWidth = 0;
    119     /** general information about a display */
    120     private final DisplayMetrics mMetrics = new DisplayMetrics();
    121     /** Focus is none now */
    122     private static final int FOCUS_NONE = -1;
    123     /** Handler for set  Candidate */
    124     private static final int MSG_SET_CANDIDATES = 1;
    125     /** List of textView for CandiData List */
    126     private ArrayList<TextView> mTextViewArray = new ArrayList<TextView>();
    127     /** Now focus textView index */
    128     private int mCurrentFocusIndex = FOCUS_NONE;
    129     /** Focused View */
    130     private View mFocusedView = null;
    131     /** Focused View Background */
    132     private Drawable mFocusedViewBackground = null;
    133     /** Scale up text size */
    134     private AbsoluteSizeSpan mSizeSpan;
    135     /** Scale up text alignment */
    136     private AlignmentSpan.Standard mCenterSpan;
    137     /** Whether candidates long click enable */
    138     private boolean mEnableCandidateLongClick = true;
    139 
    140    /** {@code Handler} Handler for focus Candidate wait delay */
    141     private Handler mHandler = new Handler() {
    142             @Override public void handleMessage(Message msg) {
    143 
    144             switch (msg.what) {
    145                 case MSG_SET_CANDIDATES:
    146                     displayCandidatesDelay(mConverter);
    147                     break;
    148 
    149                 default:
    150                     break;
    151                 }
    152             }
    153         };
    154 
    155     /** Event listener for touching a candidate */
    156     private OnClickListener mCandidateOnClick = new OnClickListener() {
    157         public void onClick(View v) {
    158             if (!v.isShown()) {
    159                 return;
    160             }
    161             playSoundAndVibration();
    162 
    163             if (v instanceof CandidateTextView) {
    164                 CandidateTextView text = (CandidateTextView)v;
    165                 int wordcount = text.getId();
    166                 WnnWord word = getWnnWord(wordcount);
    167                 clearFocusCandidate();
    168                 selectCandidate(word);
    169             }
    170         }
    171     };
    172 
    173     /** Event listener for long-clicking a candidate */
    174     private OnLongClickListener mCandidateOnLongClick = new OnLongClickListener() {
    175         public boolean onLongClick(View v) {
    176             if (!v.isShown()) {
    177                 return true;
    178             }
    179 
    180             if (!mEnableCandidateLongClick) {
    181                 return false;
    182             }
    183 
    184             clearFocusCandidate();
    185 
    186             int wordcount = ((TextView)v).getId();
    187             mWord = mWnnWordArray.get(wordcount);
    188 
    189             displayDialog(v, mWord);
    190             return true;
    191         }
    192     };
    193 
    194     /**
    195      * Constructor
    196      */
    197     public TextCandidates1LineViewManager() {
    198         this(300);
    199     }
    200 
    201     /**
    202      * Constructor
    203      *
    204      * @param displayLimit      The limit of display
    205      */
    206     public TextCandidates1LineViewManager(int displayLimit) {
    207         mDisplayLimit = displayLimit;
    208         mWnnWordArray = new ArrayList<WnnWord>();
    209         mAutoHideMode = true;
    210         mMetrics.setToDefaults();
    211     }
    212 
    213     /**
    214      * Set auto-hide mode.
    215      * @param hide      {@code true} if the view will hidden when no candidate exists;
    216      *                  {@code false} if the view is always shown.
    217      */
    218     public void setAutoHide(boolean hide) {
    219         mAutoHideMode = hide;
    220     }
    221 
    222     /** @see CandidatesViewManager */
    223     public View initView(OpenWnn parent, int width, int height) {
    224         mWnn = parent;
    225         mViewWidth = width;
    226 
    227         Resources r = mWnn.getResources();
    228 
    229         mCandidateMinimumWidth = (int)(CANDIDATE_MINIMUM_WIDTH * mMetrics.density);
    230         mCandidateMinimumHeight = r.getDimensionPixelSize(R.dimen.candidate_layout_height);
    231 
    232         LayoutInflater inflater = parent.getLayoutInflater();
    233         mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates_1line, null);
    234         mViewBodyScroll = (HorizontalScrollView)mViewBody.findViewById(R.id.candview_scroll_1line);
    235         mViewBodyScroll.setOverScrollMode(View.OVER_SCROLL_NEVER);
    236         mViewBodyScroll.setOnTouchListener(new View.OnTouchListener() {
    237             public boolean onTouch(View v, MotionEvent event) {
    238                 switch (event.getAction()) {
    239                 case MotionEvent.ACTION_DOWN:
    240                 case MotionEvent.ACTION_MOVE:
    241                     if (mHandler.hasMessages(MSG_SET_CANDIDATES)) {
    242                         mHandler.removeMessages(MSG_SET_CANDIDATES);
    243                         mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, CANDIDATE_DELAY_MILLIS);
    244                     }
    245                     break;
    246 
    247                 default:
    248                     break;
    249 
    250                 }
    251                 return false;
    252             }
    253         });
    254 
    255         mLeftMoreButton = (ImageView)mViewBody.findViewById(R.id.left_more_imageview);
    256         mLeftMoreButton.setOnClickListener(new View.OnClickListener() {
    257             public void onClick(View v) {
    258                 if (!v.isShown()) {
    259                     return;
    260                 }
    261                 playSoundAndVibration();
    262                 if (mViewBodyScroll.getScrollX() > 0) {
    263                     mViewBodyScroll.smoothScrollBy(
    264                                         (int)(mViewBodyScroll.getWidth() * -SCROLL_DISTANCE), 0);
    265                 }
    266             }
    267         });
    268         mLeftMoreButton.setOnLongClickListener(new View.OnLongClickListener() {
    269             public boolean onLongClick(View v) {
    270                 if (!v.isShown()) {
    271                     return false;
    272                 }
    273                 if (!mViewBodyScroll.fullScroll(View.FOCUS_LEFT)) {
    274                     mViewBodyScroll.scrollTo(mViewBodyScroll.getChildAt(0).getWidth(), 0);
    275                 }
    276                 return true;
    277             }
    278         });
    279 
    280         mRightMoreButton = (ImageView)mViewBody.findViewById(R.id.right_more_imageview);
    281         mRightMoreButton.setOnClickListener(new View.OnClickListener() {
    282             public void onClick(View v) {
    283                 if (!v.isShown()) {
    284                     return;
    285                 }
    286                 int width = mViewBodyScroll.getWidth();
    287                 int scrollMax = mViewBodyScroll.getChildAt(0).getRight();
    288 
    289                 if ((mViewBodyScroll.getScrollX() + width) < scrollMax) {
    290                     mViewBodyScroll.smoothScrollBy((int)(width * SCROLL_DISTANCE), 0);
    291                 }
    292             }
    293         });
    294         mRightMoreButton.setOnLongClickListener(new View.OnLongClickListener() {
    295             public boolean onLongClick(View v) {
    296                 if (!v.isShown()) {
    297                     return false;
    298                 }
    299                 if (!mViewBodyScroll.fullScroll(View.FOCUS_RIGHT)) {
    300                     mViewBodyScroll.scrollTo(0, 0);
    301                 }
    302                 return true;
    303             }
    304         });
    305 
    306         mViewLongPressDialog = (View)inflater.inflate(R.layout.candidate_longpress_dialog, null);
    307 
    308         /* select button */
    309         Button longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_select);
    310         longPressDialogButton.setOnClickListener(new View.OnClickListener() {
    311                 public void onClick(View v) {
    312                     playSoundAndVibration();
    313                     clearFocusCandidate();
    314                     selectCandidate(mWord);
    315                     closeDialog();
    316                 }
    317             });
    318 
    319         /* cancel button */
    320         longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_cancel);
    321         longPressDialogButton.setOnClickListener(new View.OnClickListener() {
    322                 public void onClick(View v) {
    323                     playSoundAndVibration();
    324                     mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
    325                     mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE));
    326                     closeDialog();
    327                 }
    328             });
    329 
    330         int buttonWidth = r.getDimensionPixelSize(R.dimen.candidate_layout_width);
    331         mCandidateMaxWidth = (mViewWidth - buttonWidth * 2) / 2;
    332 
    333         mSizeSpan = new AbsoluteSizeSpan(r.getDimensionPixelSize(R.dimen.candidate_delete_word_size));
    334         mCenterSpan = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);
    335 
    336         createNormalCandidateView();
    337 
    338         setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
    339 
    340         return mViewBody;
    341     }
    342 
    343     /**
    344      * Create normal candidate view
    345      */
    346     private void createNormalCandidateView() {
    347         mViewCandidateList = (LinearLayout)mViewBody.findViewById(R.id.candidates_view_1line);
    348 
    349         Context context = mViewBodyScroll.getContext();
    350         for (int i = 0; i < mDisplayLimit; i++) {
    351             mViewCandidateList.addView(new CandidateTextView(context,
    352                                                             mCandidateMinimumHeight,
    353                                                             mCandidateMinimumWidth,
    354                                                             mCandidateMaxWidth));
    355         }
    356     }
    357 
    358     /** @see CandidatesViewManager#getCurrentView */
    359     public View getCurrentView() {
    360         return mViewBody;
    361     }
    362 
    363     /** @see CandidatesViewManager#setViewType */
    364     public void setViewType(int type) {
    365         mViewType = type;
    366 
    367         if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) {
    368             mViewCandidateList.setMinimumHeight(mCandidateMinimumHeight);
    369         } else {
    370             mViewCandidateList.setMinimumHeight(-1);
    371             mHandler.removeMessages(MSG_SET_CANDIDATES);
    372 
    373             if (mViewBody.isShown()) {
    374                 mWnn.setCandidatesViewShown(false);
    375             }
    376         }
    377     }
    378 
    379     /** @see CandidatesViewManager#getViewType */
    380     public int getViewType() {
    381         return mViewType;
    382     }
    383 
    384     /** @see CandidatesViewManager#displayCandidates */
    385     public void displayCandidates(WnnEngine converter) {
    386 
    387         mHandler.removeMessages(MSG_SET_CANDIDATES);
    388 
    389         closeDialog();
    390         clearCandidates();
    391         mConverter = converter;
    392 
    393         int isNextCandidate = IS_NEXTCANDIDATE_NORMAL;
    394         while(isNextCandidate == IS_NEXTCANDIDATE_NORMAL) {
    395             isNextCandidate = displayCandidatesNormal(converter);
    396         }
    397 
    398         if (isNextCandidate == IS_NEXTCANDIDATE_DELAY) {
    399             isNextCandidate = displayCandidatesDelay(converter);
    400         }
    401 
    402         mViewBodyScroll.scrollTo(0,0);
    403     }
    404 
    405 
    406     /**
    407      * Display the candidates.
    408      * @param converter  {@link WnnEngine} which holds candidates.
    409      */
    410     private int displayCandidatesNormal(WnnEngine converter) {
    411         int isNextCandidate = IS_NEXTCANDIDATE_NORMAL;
    412 
    413         if (converter == null) {
    414             return IS_NEXTCANDIDATE_END;
    415         }
    416 
    417         /* Get candidates */
    418         WnnWord result = converter.getNextCandidate();
    419         if (result == null) {
    420             return IS_NEXTCANDIDATE_END;
    421         }
    422 
    423         mLineLength += setCandidate(result);
    424         if (mLineLength >= mViewWidth) {
    425             isNextCandidate = IS_NEXTCANDIDATE_DELAY;
    426         }
    427 
    428         if (mWordCount < 1) { /* no candidates */
    429             if (mAutoHideMode) {
    430                 mWnn.setCandidatesViewShown(false);
    431                 return IS_NEXTCANDIDATE_END;
    432             }
    433         }
    434 
    435         if (mWordCount > mDisplayLimit) {
    436             return IS_NEXTCANDIDATE_END;
    437         }
    438 
    439         if (!(mViewBody.isShown())) {
    440             mWnn.setCandidatesViewShown(true);
    441         }
    442         return isNextCandidate;
    443     }
    444 
    445     /**
    446      * Display the candidates.
    447      * @param converter  {@link WnnEngine} which holds candidates.
    448      */
    449     private int displayCandidatesDelay(WnnEngine converter) {
    450         int isNextCandidate = IS_NEXTCANDIDATE_DELAY;
    451 
    452         if (converter == null) {
    453             return IS_NEXTCANDIDATE_END;
    454         }
    455 
    456         /* Get candidates */
    457         WnnWord result = converter.getNextCandidate();
    458         if (result == null) {
    459             return IS_NEXTCANDIDATE_END;
    460         }
    461 
    462         setCandidate(result);
    463 
    464         if (mWordCount > mDisplayLimit) {
    465             return IS_NEXTCANDIDATE_END;
    466         }
    467 
    468         mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY);
    469 
    470         return isNextCandidate;
    471     }
    472 
    473     /**
    474      * Set the candidate for candidate view
    475      * @param word set word
    476      * @return int Set width
    477      */
    478     private int setCandidate(WnnWord word) {
    479         CandidateTextView candidateTextView =
    480                 (CandidateTextView) mViewCandidateList.getChildAt(mWordCount);
    481         candidateTextView.setCandidateTextView(word, mWordCount, mCandidateOnClick,
    482                                                     mCandidateOnLongClick);
    483         mWnnWordArray.add(mWordCount, word);
    484         mWordCount++;
    485         mTextViewArray.add(candidateTextView);
    486 
    487         return candidateTextView.getWidth();
    488     }
    489 
    490     /**
    491      * Clear the candidate view
    492      */
    493     private void clearNormalViewCandidate() {
    494         int candidateNum = mViewCandidateList.getChildCount();
    495         for (int i = 0; i < candidateNum; i++) {
    496             View v = mViewCandidateList.getChildAt(i);
    497             v.setVisibility(View.GONE);
    498         }
    499     }
    500 
    501     /** @see CandidatesViewManager#clearCandidates */
    502     public void clearCandidates() {
    503         clearFocusCandidate();
    504         clearNormalViewCandidate();
    505 
    506         mLineLength = 0;
    507 
    508         mWordCount = 0;
    509         mWnnWordArray.clear();
    510         mTextViewArray.clear();
    511 
    512         if (mAutoHideMode && mViewBody.isShown()) {
    513             mWnn.setCandidatesViewShown(false);
    514         }
    515     }
    516 
    517     /** @see CandidatesViewManager#setPreferences */
    518     public void setPreferences(SharedPreferences pref) {
    519         try {
    520             if (pref.getBoolean("key_vibration", false)) {
    521                 mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE);
    522             } else {
    523                 mVibrator = null;
    524             }
    525             if (pref.getBoolean("key_sound", false)) {
    526                 mSound = (AudioManager)mWnn.getSystemService(Context.AUDIO_SERVICE);
    527             } else {
    528                 mSound = null;
    529             }
    530         } catch (Exception ex) {
    531             Log.d("OpenWnn", "NO VIBRATOR");
    532         }
    533     }
    534 
    535     /**
    536      * Select a candidate.
    537      * <br>
    538      * This method notices the selected word to {@link OpenWnn}.
    539      *
    540      * @param word  The selected word
    541      */
    542     private void selectCandidate(WnnWord word) {
    543         mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word));
    544     }
    545 
    546     private void playSoundAndVibration() {
    547         if (mVibrator != null) {
    548             try {
    549                 mVibrator.vibrate(5);
    550             } catch (Exception ex) {
    551                 Log.e("OpenWnn", "TextCandidates1LineViewManager::selectCandidate Vibrator " + ex.toString());
    552             }
    553         }
    554         if (mSound != null) {
    555             try {
    556                 mSound.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 1.0f);
    557             } catch (Exception ex) {
    558                 Log.e("OpenWnn", "TextCandidates1LineViewManager::selectCandidate Sound " + ex.toString());
    559             }
    560         }
    561     }
    562 
    563     /**
    564      * KeyEvent action for the candidate view.
    565      *
    566      * @param key    Key event
    567      */
    568     public void processMoveKeyEvent(int key) {
    569         if (!mViewBody.isShown()) {
    570             return;
    571         }
    572 
    573         switch (key) {
    574         case KeyEvent.KEYCODE_DPAD_LEFT:
    575             moveFocus(-1);
    576             break;
    577 
    578         case KeyEvent.KEYCODE_DPAD_RIGHT:
    579             moveFocus(1);
    580             break;
    581 
    582         case KeyEvent.KEYCODE_DPAD_UP:
    583             moveFocus(-1);
    584             break;
    585 
    586         case KeyEvent.KEYCODE_DPAD_DOWN:
    587             moveFocus(1);
    588             break;
    589 
    590         default:
    591             break;
    592 
    593         }
    594     }
    595 
    596     /**
    597      * Get a flag candidate is focused now.
    598      *
    599      * @return the Candidate is focused of a flag.
    600      */
    601     public boolean isFocusCandidate(){
    602         if (mCurrentFocusIndex != FOCUS_NONE) {
    603             return true;
    604         }
    605         return false;
    606     }
    607 
    608     /**
    609      * Give focus to View of candidate.
    610      */
    611     private void setViewStatusOfFocusedCandidate() {
    612         View view = mFocusedView;
    613         if (view != null) {
    614             view.setBackgroundDrawable(mFocusedViewBackground);
    615         }
    616 
    617         TextView v = getFocusedView();
    618         mFocusedView = v;
    619         if (v != null) {
    620             mFocusedViewBackground = v.getBackground();
    621             v.setBackgroundResource(R.drawable.cand_back_focuse);
    622 
    623             int viewBodyLeft = getViewLeftOnScreen(mViewBodyScroll);
    624             int viewBodyRight = viewBodyLeft + mViewBodyScroll.getWidth();
    625             int focusedViewLeft = getViewLeftOnScreen(v);
    626             int focusedViewRight = focusedViewLeft + v.getWidth();
    627 
    628             if (focusedViewRight > viewBodyRight) {
    629                 mViewBodyScroll.scrollBy((focusedViewRight - viewBodyRight), 0);
    630             } else if (focusedViewLeft < viewBodyLeft) {
    631                 mViewBodyScroll.scrollBy((focusedViewLeft - viewBodyLeft), 0);
    632             }
    633         }
    634     }
    635 
    636     /**
    637      * Clear focus to selected candidate.
    638      */
    639     private void clearFocusCandidate(){
    640         View view = mFocusedView;
    641         if (view != null) {
    642             view.setBackgroundDrawable(mFocusedViewBackground);
    643             mFocusedView = null;
    644         }
    645 
    646         mCurrentFocusIndex = FOCUS_NONE;
    647 
    648         mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_END));
    649     }
    650 
    651     /**
    652      * Select candidate that has focus.
    653      */
    654     public void selectFocusCandidate(){
    655         if (mCurrentFocusIndex != FOCUS_NONE) {
    656             selectCandidate(getFocusedWnnWord());
    657         }
    658     }
    659 
    660     /**
    661      * Get View of focus candidate.
    662      */
    663     private TextView getFocusedView() {
    664         if (mCurrentFocusIndex == FOCUS_NONE) {
    665             return null;
    666         }
    667         return mTextViewArray.get(mCurrentFocusIndex);
    668     }
    669 
    670     /**
    671      * Move the focus to next candidate.
    672      *
    673      * @param direction  The direction of increment or decrement.
    674      */
    675     private void moveFocus(int direction) {
    676         boolean isStart = (mCurrentFocusIndex == FOCUS_NONE);
    677         int size = mTextViewArray.size();
    678         int index = isStart ? 0 : (mCurrentFocusIndex + direction);
    679 
    680         if (index < 0) {
    681             index = size - 1;
    682         } else {
    683             if (index >= size) {
    684                 index = 0;
    685             }
    686         }
    687 
    688         mCurrentFocusIndex = index;
    689         setViewStatusOfFocusedCandidate();
    690 
    691         if (isStart) {
    692             mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_START));
    693         }
    694     }
    695 
    696     /**
    697      * Get view top position on screen.
    698      *
    699      * @param view target view.
    700      * @return int view top position on screen
    701      */
    702     private int getViewLeftOnScreen(View view) {
    703         int[] location = new int[2];
    704         view.getLocationOnScreen(location);
    705         return location[0];
    706     }
    707 
    708     /** @see CandidatesViewManager#getFocusedWnnWord */
    709     public WnnWord getFocusedWnnWord() {
    710         return getWnnWord(mCurrentFocusIndex);
    711     }
    712 
    713     /**
    714      * Get WnnWord.
    715      *
    716      * @return WnnWord word
    717      */
    718     public WnnWord getWnnWord(int index) {
    719         return mWnnWordArray.get(index);
    720     }
    721 
    722     /** @see CandidatesViewManager#setCandidateMsgRemove */
    723     public void setCandidateMsgRemove() {
    724         mHandler.removeMessages(MSG_SET_CANDIDATES);
    725     }
    726 }
    727