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
17package jp.co.omronsoft.openwnn;
18
19import java.nio.channels.Selector;
20import java.util.ArrayList;
21import java.util.LinkedList;
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.content.res.Resources;
25import android.content.res.Configuration;
26import android.graphics.drawable.Drawable;
27import android.graphics.Rect;
28import android.media.AudioManager;
29import android.os.Handler;
30import android.os.Message;
31import android.os.Vibrator;
32import android.preference.PreferenceManager;
33import android.text.TextUtils;
34import android.text.TextPaint;
35import android.text.SpannableString;
36import android.text.Spanned;
37import android.text.style.ImageSpan;
38import android.text.style.DynamicDrawableSpan;
39import android.util.Log;
40import android.util.DisplayMetrics;
41import android.util.TypedValue;
42import android.view.Gravity;
43import android.view.KeyEvent;
44import android.view.MotionEvent;
45import android.view.View;
46import android.view.ViewGroup;
47import android.view.View.OnClickListener;
48import android.view.View.OnLongClickListener;
49import android.view.GestureDetector;
50import android.view.LayoutInflater;
51import android.widget.Button;
52import android.widget.LinearLayout;
53import android.widget.ScrollView;
54import android.widget.TextView;
55import android.widget.EditText;
56import android.widget.AbsoluteLayout;
57import android.widget.ImageView;
58
59/**
60 * The default candidates view manager class using {@link EditText}.
61 *
62 * @author Copyright (C) 2009-2011 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
63 */
64public class TextCandidatesViewManager extends CandidatesViewManager implements GestureDetector.OnGestureListener {
65    /** Number of lines to display (Portrait) */
66    public static final int LINE_NUM_PORTRAIT       = 2;
67    /** Number of lines to display (Landscape) */
68    public static final int LINE_NUM_LANDSCAPE      = 1;
69
70    /** Maximum lines */
71    private static final int DISPLAY_LINE_MAX_COUNT = 1000;
72    /** Maximum number of displaying candidates par one line (full view mode) */
73    private static final int FULL_VIEW_DIV = 4;
74    /** Maximum number of displaying candidates par one line (full view mode)(symbol)(portrait) */
75    private static final int FULL_VIEW_SYMBOL_DIV_PORT = 6;
76    /** Maximum number of displaying candidates par one line (full view mode)(symbol)(landscape) */
77    private static final int FULL_VIEW_SYMBOL_DIV_LAND = 10;
78    /** Delay of set candidate */
79    private static final int SET_CANDIDATE_DELAY = 50;
80    /** First line count */
81    private static final int SET_CANDIDATE_FIRST_LINE_COUNT = 7;
82    /** Delay line count */
83    private static final int SET_CANDIDATE_DELAY_LINE_COUNT = 1;
84
85    /** Focus is none now */
86    private static final int FOCUS_NONE = -1;
87    /** Handler for focus Candidate */
88    private static final int MSG_MOVE_FOCUS = 0;
89    /** Handler for set  Candidate */
90    private static final int MSG_SET_CANDIDATES = 1;
91    /** Handler for select Candidate */
92    private static final int MSG_SELECT_CANDIDATES = 2;
93
94    /** NUmber of Candidate display lines */
95    private static final int SETTING_NUMBER_OF_LINEMAX = 5;
96
97    /** Body view of the candidates list */
98    private ViewGroup  mViewBody = null;
99
100    /** The view of the Symbol Tab */
101    private TextView mViewTabSymbol;
102    /** The view of the Emoticon Tab */
103    private TextView mViewTabEmoticon;
104    /** Scroller of {@code mViewBodyText} */
105    private ScrollView mViewBodyScroll;
106    /** Base of {@code mViewCandidateList1st}, {@code mViewCandidateList2nd} */
107    private ViewGroup mViewCandidateBase;
108    /** Button displayed bottom of the view when there are more candidates. */
109    private ImageView mReadMoreButton;
110    /** Layout for the candidates list on normal view */
111    private LinearLayout mViewCandidateList1st;
112    /** Layout for the candidates list on full view */
113    private AbsoluteLayout mViewCandidateList2nd;
114    /** View for symbol tab */
115    private LinearLayout mViewCandidateListTab;
116    /** {@link OpenWnn} instance using this manager */
117    private OpenWnn mWnn;
118    /** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */
119    private int mViewType;
120    /** Portrait display({@code true}) or landscape({@code false}) */
121    private boolean mPortrait;
122
123    /** Width of the view */
124    private int mViewWidth;
125    /** Minimum width of a candidate (density support) */
126    private int mCandidateMinimumWidth;
127    /** Maximum width of a candidate (density support) */
128    private int mCandidateMinimumHeight;
129    /** Minimum height of the category candidate view */
130    private int mCandidateCategoryMinimumHeight;
131    /** Left align threshold of the candidate view */
132    private int mCandidateLeftAlignThreshold;
133    /** Height of keyboard */
134    private int mKeyboardHeight;
135    /** Height of symbol keyboard */
136    private int mSymbolKeyboardHeight;
137    /** Height of symbol keyboard tab */
138    private int mSymbolKeyboardTabHeight;
139    /** Whether being able to use Emoticon */
140    private boolean mEnableEmoticon = false;
141
142    /** Whether hide the view if there is no candidate */
143    private boolean mAutoHideMode = true;
144    /** The converter to get candidates from and notice the selected candidate to. */
145    private WnnEngine mConverter;
146    /** Limitation of displaying candidates */
147    private int mDisplayLimit;
148
149    /** Vibrator for touch vibration */
150    private Vibrator mVibrator = null;
151    /** AudioManager for click sound */
152    private AudioManager mSound = null;
153
154    /** Number of candidates displaying for 1st */
155    private int mWordCount1st;
156    /** Number of candidates displaying for 2nd */
157    private int mWordCount2nd;
158    /** List of candidates for 1st */
159    private ArrayList<WnnWord> mWnnWordArray1st = new ArrayList<WnnWord>();
160    /** List of candidates for 2nd */
161    private ArrayList<WnnWord> mWnnWordArray2nd = new ArrayList<WnnWord>();
162    /** List of select candidates */
163    private LinkedList<WnnWord> mWnnWordSelectedList = new LinkedList<WnnWord>();
164
165    /** Gesture detector */
166    private GestureDetector mGestureDetector;
167    /** Character width of the candidate area */
168    private int mLineLength = 0;
169    /** Number of lines displayed */
170    private int mLineCount = 1;
171
172    /** {@code true} if the full screen mode is selected */
173    private boolean mIsFullView = false;
174
175    /** The event object for "touch" */
176    private MotionEvent mMotionEvent = null;
177
178    /** The offset when the candidates is flowed out the candidate window */
179    private int mDisplayEndOffset = 0;
180    /** {@code true} if there are more candidates to display. */
181    private boolean mCanReadMore = false;
182    /** Color of the candidates */
183    private int mTextColor = 0;
184    /** Template object for each candidate and normal/full view change button */
185    private TextView mViewCandidateTemplate;
186    /** Number of candidates in full view */
187    private int mFullViewWordCount;
188    /** Number of candidates in the current line (in full view) */
189    private int mFullViewOccupyCount;
190    /** View of the previous candidate (in full view) */
191    private TextView mFullViewPrevView;
192    /** Id of the top line view (in full view) */
193    private int mFullViewPrevLineTopId;
194    /** Layout of the previous candidate (in full view) */
195    private ViewGroup.LayoutParams mFullViewPrevParams;
196    /** Whether all candidates are displayed */
197    private boolean mCreateCandidateDone;
198    /** Number of lines in normal view */
199    private int mNormalViewWordCountOfLine;
200
201    /** List of textView for CandiData List 1st for Symbol mode */
202    private ArrayList<TextView> mTextViewArray1st = new ArrayList<TextView>();
203    /** List of textView for CandiData List 2st for Symbol mode */
204    private ArrayList<TextView> mTextViewArray2nd = new ArrayList<TextView>();
205    /** Now focus textView index */
206    private int mCurrentFocusIndex = FOCUS_NONE;
207    /** Focused View */
208    private View mFocusedView = null;
209    /** Focused View Background */
210    private Drawable mFocusedViewBackground = null;
211    /** Axis to find next TextView for Up/Down */
212    private int mFocusAxisX = 0;
213    /** Now focused TextView in mTextViewArray1st */
214    private boolean mHasFocusedArray1st = true;
215
216    /** Portrait Number of Lines from Preference */
217    private int mPortraitNumberOfLine = LINE_NUM_PORTRAIT;
218    /** Landscape Number of Lines from Preference */
219    private int mLandscapeNumberOfLine = LINE_NUM_LANDSCAPE;
220
221    /** Coordinates of line */
222    private int mLineY = 0;
223
224    /** {@code true} if the candidate is selected */
225    private boolean mIsSymbolSelected = false;
226
227    /** Whether candidates is symbol */
228    private boolean mIsSymbolMode = false;
229
230    /** Symbol mode */
231    private int mSymbolMode = OpenWnnJAJP.ENGINE_MODE_SYMBOL;
232
233    /** Text size of candidates */
234    private float mCandNormalTextSize;
235
236    /** Text size of category */
237    private float mCandCategoryTextSize;
238
239    /** HardKeyboard hidden({@code true}) or disp({@code false}) */
240    private boolean mHardKeyboardHidden = true;
241
242    /** Minimum height of the candidate 1line view */
243    private int mCandidateOneLineMinimumHeight;
244
245    /** Whether candidates long click enable */
246    private boolean mEnableCandidateLongClick = true;
247
248    /** Keyboard vertical gap value */
249    private static final float KEYBOARD_VERTICAL_GAP = 0.009f;
250
251    /** Keyboard vertical gap count */
252    private static final int KEYBOARD_VERTICAL_GAP_COUNT = 3;
253
254    /** {@code Handler} Handler for focus Candidate wait delay */
255    private Handler mHandler = new Handler() {
256            @Override public void handleMessage(Message msg) {
257                switch (msg.what) {
258                case MSG_MOVE_FOCUS:
259                    moveFocus(msg.arg1, msg.arg2 == 1);
260                    break;
261
262                case MSG_SET_CANDIDATES:
263                    if (mViewType == CandidatesViewManager.VIEW_TYPE_FULL && mIsSymbolMode) {
264                        displayCandidates(mConverter, false, SET_CANDIDATE_DELAY_LINE_COUNT);
265                    }
266                    break;
267
268                case MSG_SELECT_CANDIDATES:
269                    WnnWord word = null;
270                    while ((word = mWnnWordSelectedList.poll()) != null) {
271                        selectCandidate(word);
272                    }
273                    break;
274
275                default:
276                    break;
277                }
278            }
279        };
280
281    /** Event listener for touching a candidate for 1st */
282    private OnClickListener mCandidateOnClick1st = new OnClickListener() {
283        public void onClick(View v) {
284            onClickCandidate(v, mWnnWordArray1st);
285        }
286    };
287
288    /** Event listener for touching a candidate for 2nd */
289    private OnClickListener mCandidateOnClick2nd = new OnClickListener() {
290        public void onClick(View v) {
291            onClickCandidate(v, mWnnWordArray2nd);
292        }
293    };
294
295    /** Event listener for long-clicking a candidate for 1st */
296    private OnLongClickListener mCandidateOnLongClick1st = new OnLongClickListener() {
297        public boolean onLongClick(View v) {
298            return onLongClickCandidate(v, mWnnWordArray1st);
299        }
300    };
301
302    /** Event listener for long-clicking a candidate for for 2nd */
303    private OnLongClickListener mCandidateOnLongClick2nd = new OnLongClickListener() {
304        public boolean onLongClick(View v) {
305            return onLongClickCandidate(v, mWnnWordArray2nd);
306        }
307    };
308
309    /** Event listener for click a symbol tab */
310    private OnClickListener mTabOnClick = new OnClickListener() {
311        public void onClick(View v) {
312            if (!v.isShown()) {
313                return;
314            }
315            playSoundAndVibration();
316
317            if (v instanceof TextView) {
318                TextView text = (TextView)v;
319                switch (text.getId()) {
320                case R.id.candview_symbol:
321                    if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL) {
322                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CHANGE_MODE,
323                                                      OpenWnnJAJP.ENGINE_MODE_SYMBOL));
324                    }
325                    break;
326
327                case R.id.candview_emoticon:
328                    if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) {
329                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CHANGE_MODE,
330                                OpenWnnJAJP.ENGINE_MODE_SYMBOL));
331                    }
332                    break;
333
334                default:
335                    break;
336                }
337            }
338        }
339    };
340
341    /**
342     * Constructor
343     */
344    public TextCandidatesViewManager() {
345        this(-1);
346    }
347
348    /**
349     * Constructor
350     *
351     * @param displayLimit      The limit of display
352     */
353    public TextCandidatesViewManager(int displayLimit) {
354        mDisplayLimit = displayLimit;
355    }
356
357    /**
358     * Handle a click event on the candidate.
359     * @param v  View
360     * @param list  List of candidates
361     */
362    private void onClickCandidate(View v, ArrayList<WnnWord> list) {
363        if (!v.isShown()) {
364            return;
365        }
366        playSoundAndVibration();
367
368        if (v instanceof TextView) {
369            TextView text = (TextView)v;
370            int wordcount = text.getId();
371            WnnWord word = list.get(wordcount);
372
373            if (mHandler.hasMessages(MSG_SET_CANDIDATES)) {
374                mWnnWordSelectedList.add(word);
375                return;
376            }
377            clearFocusCandidate();
378            selectCandidate(word);
379        }
380    }
381
382    /**
383     * Handle a long click event on the candidate.
384     * @param v  View
385     * @param list  List of candidates
386     */
387    public boolean onLongClickCandidate(View v, ArrayList<WnnWord> list) {
388        if (mViewLongPressDialog == null) {
389            return false;
390        }
391
392        if (mIsSymbolMode) {
393            return false;
394        }
395
396        if (!mEnableCandidateLongClick) {
397            return false;
398        }
399
400        if (!v.isShown()) {
401            return true;
402        }
403
404        Drawable d = v.getBackground();
405        if (d != null) {
406            if(d.getState().length == 0){
407                return true;
408            }
409        }
410
411        int wordcount = ((TextView)v).getId();
412        mWord = list.get(wordcount);
413        clearFocusCandidate();
414        displayDialog(v, mWord);
415
416        return true;
417    }
418
419    /**
420     * Set auto-hide mode.
421     * @param hide      {@code true} if the view will hidden when no candidate exists;
422     *                  {@code false} if the view is always shown.
423     */
424    public void setAutoHide(boolean hide) {
425        mAutoHideMode = hide;
426    }
427
428    /** @see CandidatesViewManager#initView */
429    public View initView(OpenWnn parent, int width, int height) {
430        mWnn = parent;
431        mViewWidth = width;
432        Resources r = mWnn.getResources();
433        mCandidateMinimumWidth = r.getDimensionPixelSize(R.dimen.cand_minimum_width);
434        mCandidateMinimumHeight = r.getDimensionPixelSize(R.dimen.cand_minimum_height);
435        if (OpenWnn.isXLarge()) {
436            mCandidateOneLineMinimumHeight = r.getDimensionPixelSize(R.dimen.candidate_layout_height);
437        }
438        mCandidateCategoryMinimumHeight = r.getDimensionPixelSize(R.dimen.cand_category_minimum_height);
439        mCandidateLeftAlignThreshold = r.getDimensionPixelSize(R.dimen.cand_left_align_threshold);
440        mKeyboardHeight = r.getDimensionPixelSize(R.dimen.keyboard_height);
441        if (OpenWnn.isXLarge()) {
442            mKeyboardHeight += Math.round(height * KEYBOARD_VERTICAL_GAP)
443                                * KEYBOARD_VERTICAL_GAP_COUNT;
444        }
445        mSymbolKeyboardHeight = r.getDimensionPixelSize(R.dimen.symbol_keyboard_height);
446        Drawable d = r.getDrawable(R.drawable.tab_no_select);
447        mSymbolKeyboardTabHeight = d.getMinimumHeight();
448
449        mPortrait =
450            (r.getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE);
451
452        mCandNormalTextSize = r.getDimensionPixelSize(R.dimen.cand_normal_text_size);
453        mCandCategoryTextSize = r.getDimensionPixelSize(R.dimen.cand_category_text_size);
454
455        LayoutInflater inflater = parent.getLayoutInflater();
456        mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates, null);
457
458        mViewTabSymbol = (TextView)mViewBody.findViewById(R.id.candview_symbol);
459        mViewTabEmoticon = (TextView)mViewBody.findViewById(R.id.candview_emoticon);
460
461        mViewBodyScroll = (ScrollView)mViewBody.findViewById(R.id.candview_scroll);
462
463        mViewCandidateBase = (ViewGroup)mViewBody.findViewById(R.id.candview_base);
464
465        setNumeberOfDisplayLines();
466        createNormalCandidateView();
467        mViewCandidateList2nd = (AbsoluteLayout)mViewBody.findViewById(R.id.candidates_2nd_view);
468
469        mTextColor = r.getColor(R.color.candidate_text);
470
471        mReadMoreButton = (ImageView)mViewBody.findViewById(R.id.read_more_button);
472        mReadMoreButton.setOnTouchListener(new View.OnTouchListener() {
473                public boolean onTouch(View v, MotionEvent event) {
474                    int resid = 0;
475                    switch (event.getAction()) {
476                    case MotionEvent.ACTION_DOWN:
477                        if (mIsFullView) {
478                            resid = R.drawable.cand_up_press;
479                        } else {
480                            resid = R.drawable.cand_down_press;
481                        }
482                        break;
483                    case MotionEvent.ACTION_UP:
484                        if (mIsFullView) {
485                            resid = R.drawable.cand_up;
486                        } else {
487                            resid = R.drawable.cand_down;
488                        }
489                        break;
490                    default:
491                        break;
492                    }
493
494                    if (resid != 0) {
495                        mReadMoreButton.setImageResource(resid);
496                    }
497                    return false;
498                }
499            });
500        mReadMoreButton.setOnClickListener(new View.OnClickListener() {
501                public void onClick(View v) {
502                    if (!v.isShown()) {
503                        return;
504                    }
505                    playSoundAndVibration();
506
507                    if (mIsFullView) {
508                        mIsFullView = false;
509                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
510                    } else {
511                        mIsFullView = true;
512                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
513                    }
514                }
515            });
516
517        setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
518
519        mGestureDetector = new GestureDetector(this);
520
521        mViewLongPressDialog = (View)inflater.inflate(R.layout.candidate_longpress_dialog, null);
522
523        /* select button */
524        Button longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_select);
525        longPressDialogButton.setOnClickListener(new View.OnClickListener() {
526                public void onClick(View v) {
527                    playSoundAndVibration();
528                    clearFocusCandidate();
529                    selectCandidate(mWord);
530                    closeDialog();
531                }
532            });
533
534        /* cancel button */
535        longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_cancel);
536        longPressDialogButton.setOnClickListener(new View.OnClickListener() {
537                public void onClick(View v) {
538                    playSoundAndVibration();
539                    mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
540                    mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE));
541                    closeDialog();
542                }
543            });
544
545        return mViewBody;
546    }
547
548    /**
549     * Create the normal candidate view
550     */
551    private void createNormalCandidateView() {
552        mViewCandidateList1st = (LinearLayout)mViewBody.findViewById(R.id.candidates_1st_view);
553        mViewCandidateList1st.setOnClickListener(mCandidateOnClick1st);
554
555        mViewCandidateListTab = (LinearLayout)mViewBody.findViewById(R.id.candview_tab);
556        TextView tSymbol = mViewTabSymbol;
557        tSymbol.setOnClickListener(mTabOnClick);
558        TextView tEmoticon = mViewTabEmoticon;
559        tEmoticon.setOnClickListener(mTabOnClick);
560
561        int line = SETTING_NUMBER_OF_LINEMAX;
562        int width = mViewWidth;
563        for (int i = 0; i < line; i++) {
564            LinearLayout lineView = new LinearLayout(mViewBodyScroll.getContext());
565            lineView.setOrientation(LinearLayout.HORIZONTAL);
566            LinearLayout.LayoutParams layoutParams =
567                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
568                                              ViewGroup.LayoutParams.WRAP_CONTENT);
569            lineView.setLayoutParams(layoutParams);
570            for (int j = 0; j < (width / getCandidateMinimumWidth()); j++) {
571                TextView tv = createCandidateView();
572                lineView.addView(tv);
573            }
574
575            if (i == 0) {
576                TextView tv = createCandidateView();
577                layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
578                                                             ViewGroup.LayoutParams.WRAP_CONTENT);
579                layoutParams.weight = 0;
580                layoutParams.gravity = Gravity.RIGHT;
581                tv.setLayoutParams(layoutParams);
582
583                lineView.addView(tv);
584                mViewCandidateTemplate = tv;
585            }
586            mViewCandidateList1st.addView(lineView);
587        }
588    }
589
590    /** @see CandidatesViewManager#getCurrentView */
591    public View getCurrentView() {
592        return mViewBody;
593    }
594
595    /** @see CandidatesViewManager#setViewType */
596    public void setViewType(int type) {
597        boolean readMore = setViewLayout(type);
598
599        if (readMore) {
600            displayCandidates(this.mConverter, false, -1);
601        } else {
602            if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) {
603                mIsFullView = false;
604                if (mDisplayEndOffset > 0) {
605                    int maxLine = getMaxLine();
606                    displayCandidates(this.mConverter, false, maxLine);
607                } else {
608                    setReadMore();
609                }
610            } else {
611                if (mViewBody.isShown()) {
612                    mWnn.setCandidatesViewShown(false);
613                }
614            }
615        }
616    }
617
618    /**
619     * Set the view layout
620     *
621     * @param type      View type
622     * @return          {@code true} if display is updated; {@code false} if otherwise
623     */
624    private boolean setViewLayout(int type) {
625
626        ViewGroup.LayoutParams params;
627        int line = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine;
628
629        if ((mViewType == CandidatesViewManager.VIEW_TYPE_FULL)
630                && (type == CandidatesViewManager.VIEW_TYPE_NORMAL)) {
631            clearFocusCandidate();
632        }
633
634        mViewType = type;
635
636        switch (type) {
637        case CandidatesViewManager.VIEW_TYPE_CLOSE:
638            params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
639                                                   getCandidateMinimumHeight() * line);
640            mViewBodyScroll.setLayoutParams(params);
641            mViewCandidateListTab.setVisibility(View.GONE);
642            mViewCandidateBase.setMinimumHeight(-1);
643            mHandler.removeMessages(MSG_SET_CANDIDATES);
644            return false;
645
646        case CandidatesViewManager.VIEW_TYPE_NORMAL:
647            params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
648                                                   getCandidateMinimumHeight() * line);
649            mViewBodyScroll.setLayoutParams(params);
650            mViewBodyScroll.scrollTo(0, 0);
651            mViewCandidateListTab.setVisibility(View.GONE);
652            mViewCandidateList1st.setVisibility(View.VISIBLE);
653            mViewCandidateList2nd.setVisibility(View.GONE);
654            mViewCandidateBase.setMinimumHeight(-1);
655            return false;
656
657        case CandidatesViewManager.VIEW_TYPE_FULL:
658        default:
659            params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
660                                                   getCandidateViewHeight());
661            mViewBodyScroll.setLayoutParams(params);
662            if (mIsSymbolMode) {
663                updateSymbolType();
664                mViewCandidateListTab.setVisibility(View.VISIBLE);
665            } else {
666                mViewCandidateListTab.setVisibility(View.GONE);
667            }
668            mViewCandidateList2nd.setVisibility(View.VISIBLE);
669            mViewCandidateBase.setMinimumHeight(-1);
670            return true;
671        }
672    }
673
674    /** @see CandidatesViewManager#getViewType */
675    public int getViewType() {
676        return mViewType;
677    }
678
679    /** @see CandidatesViewManager#displayCandidates */
680    public void displayCandidates(WnnEngine converter) {
681
682        mHandler.removeMessages(MSG_SET_CANDIDATES);
683
684        if (mIsSymbolSelected) {
685            mIsSymbolSelected = false;
686            if (mSymbolMode == OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) {
687                return;
688            }
689
690            int prevLineCount = mLineCount;
691            int prevWordCount1st = mWordCount1st;
692            clearNormalViewCandidate();
693            mWordCount1st = 0;
694            mLineCount = 1;
695            mLineLength = 0;
696            mNormalViewWordCountOfLine = 0;
697            mWnnWordArray1st.clear();
698            mTextViewArray1st.clear();
699            if (((prevWordCount1st == 0) && (mWordCount1st == 1)) ||
700                (prevLineCount < mLineCount)) {
701                mViewBodyScroll.scrollTo(0, mViewBodyScroll.getScrollY() + getCandidateMinimumHeight());
702            }
703            if (isFocusCandidate() && mHasFocusedArray1st) {
704                mCurrentFocusIndex = 0;
705                Message m = mHandler.obtainMessage(MSG_MOVE_FOCUS, 0, 0);
706                mHandler.sendMessage(m);
707            }
708            return;
709        }
710
711        mCanReadMore = false;
712        mDisplayEndOffset = 0;
713        mIsFullView = false;
714        mFullViewWordCount = 0;
715        mFullViewOccupyCount = 0;
716        mFullViewPrevLineTopId = 0;
717        mFullViewPrevView = null;
718        mCreateCandidateDone = false;
719        mNormalViewWordCountOfLine = 0;
720
721        clearCandidates();
722        mConverter = converter;
723        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
724
725        mViewCandidateTemplate.setVisibility(View.VISIBLE);
726        mViewCandidateTemplate.setBackgroundResource(R.drawable.cand_back);
727
728        displayCandidates(converter, true, getMaxLine());
729
730        if (mIsSymbolMode) {
731            mIsFullView = true;
732            mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
733        }
734    }
735
736    /** @see CandidatesViewManager#getMaxLine */
737    private int getMaxLine() {
738        int maxLine = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine;
739        return maxLine;
740    }
741
742    /**
743     * Get categories text.
744     * @param String Source string replacement
745     * @return String Categories text
746     */
747    private String getCategoriesText(String categoriesString) {
748        String ret = null;
749
750        Resources r = mWnn.getResources();
751        if(categoriesString.equals(r.getString(R.string.half_symbol_categories_txt))) {
752            ret = r.getString(R.string.half_symbol_txt);
753        } else if (categoriesString.equals(r.getString(R.string.full_symbol_categories_txt))) {
754            ret = r.getString(R.string.full_symbol_txt);
755        } else {
756            ret = new String("");
757        }
758
759        return ret;
760    }
761
762    /**
763     * Display the candidates.
764     *
765     * @param converter  {@link WnnEngine} which holds candidates.
766     * @param dispFirst  Whether it is the first time displaying the candidates
767     * @param maxLine    The maximum number of displaying lines
768     */
769    private void displayCandidates(WnnEngine converter, boolean dispFirst, int maxLine) {
770        if (converter == null) {
771            return;
772        }
773
774        /* Concatenate the candidates already got and the last one in dispFirst mode */
775        int displayLimit = mDisplayLimit;
776
777        boolean isDelay = false;
778        boolean isBreak = false;
779
780        if (converter instanceof SymbolList) {
781            if (!dispFirst) {
782                if (maxLine == -1) {
783                    isDelay = true;
784                    maxLine = mLineCount + SET_CANDIDATE_FIRST_LINE_COUNT;
785
786                    mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY);
787
788                } else if (maxLine == SET_CANDIDATE_DELAY_LINE_COUNT) {
789                    isDelay = true;
790                    maxLine = mLineCount + SET_CANDIDATE_DELAY_LINE_COUNT;
791
792                    mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY);
793                }
794            }
795            if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) {
796                displayLimit = -1;
797            }
798        }
799
800        /* Get candidates */
801        WnnWord result = null;
802        String prevCandidate = null;
803        while ((displayLimit == -1 || getWordCount() < displayLimit)) {
804            for (int i = 0; i < DISPLAY_LINE_MAX_COUNT; i++) {
805                result = converter.getNextCandidate();
806                if (result == null) {
807                    break;
808                }
809
810                if (converter instanceof SymbolList) {
811                    break;
812                }
813
814                if ((prevCandidate == null) || !prevCandidate.equals(result.candidate)) {
815                    break;
816                }
817            }
818
819            if (result == null) {
820                break;
821            } else {
822                prevCandidate = result.candidate;
823            }
824
825            if (converter instanceof SymbolList) {
826                if (isCategory(result)) {
827                    if (getWordCount() != 0) {
828                        createNextLine(false);
829                    }
830                    result.candidate = getCategoriesText(result.candidate);
831                    setCandidate(true, result);
832                    createNextLine(true);
833                    continue;
834                }
835            }
836
837            setCandidate(false, result);
838
839            if ((dispFirst || isDelay) && (maxLine < mLineCount)) {
840                mCanReadMore = true;
841                isBreak = true;
842                break;
843            }
844        }
845
846        if (!isBreak && !mCreateCandidateDone) {
847            /* align left if necessary */
848            createNextLine(false);
849            mCreateCandidateDone = true;
850            mHandler.removeMessages(MSG_SET_CANDIDATES);
851            mHandler.sendMessage(mHandler.obtainMessage(MSG_SELECT_CANDIDATES));
852        }
853
854        if (getWordCount() < 1) { /* no candidates */
855            if (mAutoHideMode) {
856                mWnn.setCandidatesViewShown(false);
857                return;
858            } else {
859                mCanReadMore = false;
860                mIsFullView = false;
861                setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
862            }
863        }
864
865        setReadMore();
866
867        if (!(mViewBody.isShown())) {
868            mWnn.setCandidatesViewShown(true);
869        }
870        return;
871    }
872
873    /**
874     * Add a candidate into the list.
875     * @param isCategory  {@code true}:caption of category, {@code false}:normal word
876     * @param word        A candidate word
877     */
878    private void setCandidate(boolean isCategory, WnnWord word) {
879        int textLength = measureText(word.candidate, 0, word.candidate.length());
880        TextView template = mViewCandidateTemplate;
881        textLength += template.getPaddingLeft() + template.getPaddingRight();
882        int maxWidth = mViewWidth;
883        boolean isEmojiSymbol = false;
884        if (mIsSymbolMode && (word.candidate.length() < 3)) {
885            isEmojiSymbol = true;
886        }
887        TextView textView;
888
889        boolean is2nd = isFirstListOver(mIsFullView, mLineCount, word);
890        if (is2nd) {
891            /* Full view */
892            int viewDivison = getCandidateViewDivison();
893            int indentWidth = mViewWidth / viewDivison;
894            int occupyCount = Math.min((textLength + indentWidth + getCandidateSpaceWidth(isEmojiSymbol)) / indentWidth, viewDivison);
895            if (isCategory) {
896                occupyCount = viewDivison;
897            }
898
899            if (viewDivison < (mFullViewOccupyCount + occupyCount)) {
900                if (viewDivison != mFullViewOccupyCount) {
901                    mFullViewPrevParams.width += (viewDivison - mFullViewOccupyCount) * indentWidth;
902                    if (mFullViewPrevView != null) {
903                        mViewCandidateList2nd.updateViewLayout(mFullViewPrevView, mFullViewPrevParams);
904                    }
905                }
906                mFullViewOccupyCount = 0;
907                if (mFullViewPrevView != null) {
908                    mFullViewPrevLineTopId = mFullViewPrevView.getId();
909                }
910                mLineCount++;
911                if (isCategory) {
912                    mLineY += mCandidateCategoryMinimumHeight;
913                } else {
914                    mLineY += getCandidateMinimumHeight();
915                }
916            }
917            if (mFullViewWordCount == 0) {
918                mLineY = 0;
919            }
920
921            ViewGroup layout = mViewCandidateList2nd;
922
923            int width = indentWidth * occupyCount;
924            int height = getCandidateMinimumHeight();
925            if (isCategory) {
926                height = mCandidateCategoryMinimumHeight;
927            }
928
929            ViewGroup.LayoutParams params = buildLayoutParams(mViewCandidateList2nd, width, height);
930
931            textView = (TextView) layout.getChildAt(mFullViewWordCount);
932            if (textView == null) {
933                textView = createCandidateView();
934                textView.setLayoutParams(params);
935
936                mViewCandidateList2nd.addView(textView);
937            } else {
938                mViewCandidateList2nd.updateViewLayout(textView, params);
939            }
940
941            mFullViewOccupyCount += occupyCount;
942            mFullViewWordCount++;
943            mFullViewPrevView = textView;
944            mFullViewPrevParams = params;
945
946        } else {
947            int viewDivison = getCandidateViewDivison();
948            int indentWidth = mViewWidth / viewDivison;
949            textLength = Math.max(textLength, indentWidth);
950
951            /* Normal view */
952            int nextEnd = mLineLength + textLength;
953            nextEnd += getCandidateSpaceWidth(isEmojiSymbol);
954
955            if (mLineCount == 1 && !mIsSymbolMode) {
956                maxWidth -= getCandidateMinimumWidth();
957            }
958
959            if ((maxWidth < nextEnd) && (getWordCount() != 0)) {
960
961                createNextLineFor1st();
962                if (getMaxLine() < mLineCount) {
963                    mLineLength = 0;
964                    /* Call this method again to add the candidate in the full view */
965                    if (!mIsSymbolSelected) {
966                        setCandidate(isCategory, word);
967                    }
968                    return;
969                }
970
971                mLineLength = textLength;
972                mLineLength += getCandidateSpaceWidth(isEmojiSymbol);
973            } else {
974                mLineLength = nextEnd;
975            }
976
977            LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1);
978            textView = (TextView) lineView.getChildAt(mNormalViewWordCountOfLine);
979
980            if (isCategory) {
981                if (mLineCount == 1) {
982                    mViewCandidateTemplate.setBackgroundDrawable(null);
983                }
984                mLineLength += mCandidateLeftAlignThreshold;
985            } else {
986                int CompareLength = textLength;
987                CompareLength += getCandidateSpaceWidth(isEmojiSymbol);
988            }
989
990            mNormalViewWordCountOfLine++;
991        }
992
993        textView.setText(word.candidate);
994        if (is2nd) {
995            textView.setId(mWordCount2nd);
996        } else {
997            textView.setId(mWordCount1st);
998        }
999        textView.setVisibility(View.VISIBLE);
1000        textView.setPressed(false);
1001        textView.setFocusable(false);
1002
1003        if (isCategory) {
1004            textView.setText("      " + word.candidate);
1005
1006            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandCategoryTextSize);
1007            textView.setBackgroundDrawable(null);
1008            textView.setGravity(Gravity.CENTER_VERTICAL);
1009            textView.setMinHeight(mCandidateCategoryMinimumHeight);
1010            textView.setHeight(mCandidateCategoryMinimumHeight);
1011
1012            textView.setOnClickListener(null);
1013            textView.setOnLongClickListener(null);
1014            textView.setTextColor(mTextColor);
1015        } else {
1016            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandNormalTextSize);
1017            textView.setGravity(Gravity.CENTER);
1018            textView.setMinHeight(getCandidateMinimumHeight());
1019            textView.setHeight(getCandidateMinimumHeight());
1020
1021            if (is2nd) {
1022                textView.setOnClickListener(mCandidateOnClick2nd);
1023                textView.setOnLongClickListener(mCandidateOnLongClick2nd);
1024            } else {
1025                textView.setOnClickListener(mCandidateOnClick1st);
1026                textView.setOnLongClickListener(mCandidateOnLongClick1st);
1027            }
1028
1029            textView.setBackgroundResource(R.drawable.cand_back);
1030
1031            textView.setTextColor(mTextColor);
1032        }
1033
1034        if (maxWidth < textLength) {
1035            textView.setEllipsize(TextUtils.TruncateAt.END);
1036        } else {
1037            textView.setEllipsize(null);
1038        }
1039
1040        ImageSpan span = null;
1041        if (word.candidate.equals(" ")) {
1042            span = new ImageSpan(mWnn, R.drawable.word_half_space,
1043                                 DynamicDrawableSpan.ALIGN_BASELINE);
1044        } else if (word.candidate.equals("\u3000" /* full-width space */)) {
1045            span = new ImageSpan(mWnn, R.drawable.word_full_space,
1046                                 DynamicDrawableSpan.ALIGN_BASELINE);
1047        }
1048
1049        if (span != null) {
1050            SpannableString spannable = new SpannableString("   ");
1051            spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1052            textView.setText(spannable);
1053        }
1054        textView.setPadding(0, 0, 0, 0);
1055
1056        if (is2nd) {
1057            mWnnWordArray2nd.add(mWordCount2nd, word);
1058            mWordCount2nd++;
1059            mTextViewArray2nd.add(textView);
1060        } else {
1061            mWnnWordArray1st.add(mWordCount1st, word);
1062            mWordCount1st++;
1063            mTextViewArray1st.add(textView);
1064        }
1065    }
1066
1067    /**
1068     * Create AbsoluteLayout.LayoutParams
1069     * @param layout AbsoluteLayout
1070     * @param width  The width of the display
1071     * @param height The height of the display
1072     * @return Layout parameter
1073     */
1074    private ViewGroup.LayoutParams buildLayoutParams(AbsoluteLayout layout, int width, int height) {
1075
1076        int viewDivison = getCandidateViewDivison();
1077        int indentWidth = mViewWidth / viewDivison;
1078        int x         = indentWidth * mFullViewOccupyCount;
1079        int y         = mLineY;
1080        ViewGroup.LayoutParams params
1081              = new AbsoluteLayout.LayoutParams(width, height, x, y);
1082
1083        return params;
1084    }
1085
1086    /**
1087     * Create a view for a candidate.
1088     * @return the view
1089     */
1090    private TextView createCandidateView() {
1091        TextView text = new CandidateTextView(mViewBodyScroll.getContext());
1092        text.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandNormalTextSize);
1093        text.setBackgroundResource(R.drawable.cand_back);
1094        text.setCompoundDrawablePadding(0);
1095        text.setGravity(Gravity.CENTER);
1096        text.setSingleLine();
1097        text.setPadding(0, 0, 0, 0);
1098        text.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
1099                                                           ViewGroup.LayoutParams.WRAP_CONTENT,
1100                                                           1.0f));
1101        text.setMinHeight(getCandidateMinimumHeight());
1102        text.setMinimumWidth(getCandidateMinimumWidth());
1103        text.setSoundEffectsEnabled(false);
1104        return text;
1105    }
1106
1107    /**
1108     * Display {@code mReadMoreText} if there are more candidates.
1109     */
1110    private void setReadMore() {
1111        if (mIsSymbolMode) {
1112            mReadMoreButton.setVisibility(View.GONE);
1113            mViewCandidateTemplate.setVisibility(View.GONE);
1114            return;
1115        }
1116
1117        int resid = 0;
1118
1119        if (mIsFullView) {
1120            mReadMoreButton.setVisibility(View.VISIBLE);
1121            resid = R.drawable.cand_up;
1122        } else {
1123            if (mCanReadMore) {
1124                mReadMoreButton.setVisibility(View.VISIBLE);
1125                resid = R.drawable.cand_down;
1126            } else {
1127                mReadMoreButton.setVisibility(View.GONE);
1128                mViewCandidateTemplate.setVisibility(View.GONE);
1129            }
1130        }
1131
1132        if (resid != 0) {
1133            mReadMoreButton.setImageResource(resid);
1134        }
1135    }
1136
1137    /**
1138     * Clear the list of the normal candidate view.
1139     */
1140    private void clearNormalViewCandidate() {
1141        LinearLayout candidateList = mViewCandidateList1st;
1142        int lineNum = candidateList.getChildCount();
1143        for (int i = 0; i < lineNum; i++) {
1144
1145            LinearLayout lineView = (LinearLayout)candidateList.getChildAt(i);
1146            int size = lineView.getChildCount();
1147            for (int j = 0; j < size; j++) {
1148                View v = lineView.getChildAt(j);
1149                v.setVisibility(View.GONE);
1150            }
1151        }
1152    }
1153
1154    /** @see CandidatesViewManager#clearCandidates */
1155    public void clearCandidates() {
1156        closeDialog();
1157        clearFocusCandidate();
1158        clearNormalViewCandidate();
1159
1160        ViewGroup layout = mViewCandidateList2nd;
1161        int size = layout.getChildCount();
1162        for (int i = 0; i < size; i++) {
1163            View v = layout.getChildAt(i);
1164            v.setVisibility(View.GONE);
1165        }
1166
1167        mLineCount = 1;
1168        mWordCount1st = 0;
1169        mWordCount2nd = 0;
1170        mWnnWordArray1st.clear();
1171        mWnnWordArray2nd.clear();
1172        mTextViewArray1st.clear();
1173        mTextViewArray2nd.clear();
1174        mLineLength = 0;
1175
1176        mLineY = 0;
1177
1178        mIsFullView = false;
1179        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
1180        if (mAutoHideMode) {
1181            setViewLayout(CandidatesViewManager.VIEW_TYPE_CLOSE);
1182        }
1183
1184        if (mAutoHideMode && mViewBody.isShown()) {
1185            mWnn.setCandidatesViewShown(false);
1186        }
1187        mCanReadMore = false;
1188        setReadMore();
1189    }
1190
1191    /** @see CandidatesViewManager#setPreferences */
1192    public void setPreferences(SharedPreferences pref) {
1193        try {
1194            if (pref.getBoolean("key_vibration", false)) {
1195                mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE);
1196            } else {
1197                mVibrator = null;
1198            }
1199            if (pref.getBoolean("key_sound", false)) {
1200                mSound = (AudioManager)mWnn.getSystemService(Context.AUDIO_SERVICE);
1201            } else {
1202                mSound = null;
1203            }
1204            setNumeberOfDisplayLines();
1205        } catch (Exception ex) {
1206            Log.e("OpenWnn", "NO VIBRATOR");
1207        }
1208    }
1209
1210    /**
1211     * Set normal mode.
1212     */
1213    public void setNormalMode() {
1214        setReadMore();
1215        mIsFullView = false;
1216        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
1217    }
1218
1219    /**
1220     * Set full mode.
1221     */
1222    public void setFullMode() {
1223        mIsFullView = true;
1224        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
1225    }
1226
1227    /**
1228     * Set symbol mode.
1229     */
1230    public void setSymbolMode(boolean enable, int mode) {
1231        if (mIsSymbolMode && !enable) {
1232            setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
1233        }
1234        mSymbolMode = mode;
1235        mIsSymbolMode = enable;
1236    }
1237
1238    /**
1239     * Set scroll up.
1240     */
1241    public void setScrollUp() {
1242        if (!mViewBodyScroll.pageScroll(ScrollView.FOCUS_UP)) {
1243            mViewBodyScroll.scrollTo(0, mViewBodyScroll.getChildAt(0).getHeight());
1244        }
1245    }
1246
1247    /**
1248     * Set scroll down.
1249     */
1250    public void setScrollDown() {
1251        if (!mViewBodyScroll.pageScroll(ScrollView.FOCUS_DOWN)) {
1252            mViewBodyScroll.scrollTo(0, 0);
1253        }
1254    }
1255
1256    /**
1257     * Set scroll full up.
1258     */
1259    public void setScrollFullUp() {
1260        if (!mViewBodyScroll.fullScroll(ScrollView.FOCUS_UP)) {
1261            mViewBodyScroll.scrollTo(0, mViewBodyScroll.getChildAt(0).getHeight());
1262        }
1263    }
1264
1265    /**
1266     * Set scroll full down.
1267     */
1268    public void setScrollFullDown() {
1269        if (!mViewBodyScroll.fullScroll(ScrollView.FOCUS_DOWN)) {
1270            mViewBodyScroll.scrollTo(0, 0);
1271        }
1272    }
1273
1274    /**
1275     * Process {@link OpenWnnEvent#CANDIDATE_VIEW_TOUCH} event.
1276     *
1277     * @return      {@code true} if event is processed; {@code false} if otherwise
1278     */
1279    public boolean onTouchSync() {
1280        return mGestureDetector.onTouchEvent(mMotionEvent);
1281    }
1282
1283    /**
1284     * Select a candidate.
1285     * <br>
1286     * This method notices the selected word to {@link OpenWnn}.
1287     *
1288     * @param word  The selected word
1289     */
1290    private void selectCandidate(WnnWord word) {
1291        if (!mIsSymbolMode) {
1292            mIsFullView = false;
1293            mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
1294        }
1295        mIsSymbolSelected = mIsSymbolMode;
1296        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word));
1297    }
1298
1299    private void playSoundAndVibration() {
1300        if (mVibrator != null) {
1301            try {
1302                mVibrator.vibrate(5);
1303            } catch (Exception ex) {
1304                Log.e("OpenWnn", "TextCandidatesViewManager::selectCandidate Vibrator " + ex.toString());
1305            }
1306        }
1307        if (mSound != null) {
1308            try {
1309                mSound.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, -1);
1310            } catch (Exception ex) {
1311                Log.e("OpenWnn", "TextCandidatesViewManager::selectCandidate Sound " + ex.toString());
1312            }
1313        }
1314    }
1315
1316    /** @see android.view.GestureDetector.OnGestureListener#onDown */
1317    public boolean onDown(MotionEvent arg0) {
1318        return false;
1319    }
1320
1321    /** @see android.view.GestureDetector.OnGestureListener#onFling */
1322    public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
1323        boolean consumed = false;
1324        if (arg1 != null && arg0 != null && arg1.getY() < arg0.getY()) {
1325            if ((mViewType == CandidatesViewManager.VIEW_TYPE_NORMAL) && mCanReadMore) {
1326                if (mVibrator != null) {
1327                    try {
1328                        mVibrator.vibrate(5);
1329                    } catch (Exception ex) {
1330                        Log.e("iwnn", "TextCandidatesViewManager::onFling Vibrator " + ex.toString());
1331                    }
1332                }
1333                mIsFullView = true;
1334                mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
1335                consumed = true;
1336            }
1337        } else {
1338            if (mViewBodyScroll.getScrollY() == 0) {
1339                if (mVibrator != null) {
1340                    try {
1341                        mVibrator.vibrate(5);
1342                    } catch (Exception ex) {
1343                        Log.e("iwnn", "TextCandidatesViewManager::onFling Sound " + ex.toString());
1344                    }
1345                }
1346                mIsFullView = false;
1347                mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
1348                consumed = true;
1349            }
1350        }
1351
1352        return consumed;
1353    }
1354
1355    /** @see android.view.GestureDetector.OnGestureListener#onLongPress */
1356    public void onLongPress(MotionEvent arg0) {
1357        return;
1358    }
1359
1360    /** @see android.view.GestureDetector.OnGestureListener#onScroll */
1361    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
1362        return false;
1363    }
1364
1365    /** @see android.view.GestureDetector.OnGestureListener#onShowPress */
1366    public void onShowPress(MotionEvent arg0) {
1367    }
1368
1369    /** @see android.view.GestureDetector.OnGestureListener#onSingleTapUp */
1370    public boolean onSingleTapUp(MotionEvent arg0) {
1371        return false;
1372    }
1373
1374    /**
1375     * Retrieve the width of string to draw.
1376     *
1377     * @param text          The string
1378     * @param start         The start position (specified by the number of character)
1379     * @param end           The end position (specified by the number of character)
1380     * @return          The width of string to draw
1381     */
1382    public int measureText(CharSequence text, int start, int end) {
1383        if (end - start < 3) {
1384            return getCandidateMinimumWidth();
1385        }
1386
1387        TextPaint paint = mViewCandidateTemplate.getPaint();
1388        return (int)paint.measureText(text, start, end);
1389    }
1390
1391    /**
1392     * Create a layout for the next line.
1393     */
1394    private void createNextLine(boolean isCategory) {
1395        if (isFirstListOver(mIsFullView, mLineCount, null)) {
1396            /* Full view */
1397            mFullViewOccupyCount = 0;
1398            if (mFullViewPrevView != null) {
1399                mFullViewPrevLineTopId = mFullViewPrevView.getId();
1400            }
1401            if (isCategory) {
1402                mLineY += mCandidateCategoryMinimumHeight;
1403            } else {
1404                mLineY += getCandidateMinimumHeight();
1405            }
1406            mLineCount++;
1407        } else {
1408            createNextLineFor1st();
1409        }
1410    }
1411
1412    /**
1413     * Create a layout for the next line.
1414     */
1415    private void createNextLineFor1st() {
1416        LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1);
1417        float weight = 0;
1418        if (mLineLength < mCandidateLeftAlignThreshold) {
1419            if (mLineCount == 1) {
1420                mViewCandidateTemplate.setVisibility(View.GONE);
1421            }
1422        } else {
1423            weight = 1.0f;
1424        }
1425
1426        LinearLayout.LayoutParams params
1427            = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
1428                                            ViewGroup.LayoutParams.WRAP_CONTENT,
1429                                            weight);
1430
1431        int child = lineView.getChildCount();
1432        for (int i = 0; i < child; i++) {
1433            View view = lineView.getChildAt(i);
1434
1435            if (view != mViewCandidateTemplate) {
1436                view.setLayoutParams(params);
1437                view.setPadding(0, 0, 0, 0);
1438            }
1439        }
1440
1441        mLineLength = 0;
1442        mNormalViewWordCountOfLine = 0;
1443        mLineCount++;
1444    }
1445
1446    /**
1447     * Judge if it's a category.
1448     *
1449     * @return {@code true} if category
1450     */
1451    boolean isCategory(WnnWord word) {
1452        int length = word.candidate.length();
1453        return ((length > 3) && (word.candidate.charAt(0) == '['));
1454    }
1455
1456    /**
1457     * Get a minimum width of a candidate view.
1458     *
1459     * @return the minimum width of a candidate view.
1460     */
1461    private int getCandidateMinimumWidth() {
1462        return mCandidateMinimumWidth;
1463    }
1464
1465    /**
1466     * @return the minimum height of a candidate view.
1467     */
1468    private int getCandidateMinimumHeight() {
1469        return mCandidateMinimumHeight;
1470    }
1471
1472    /**
1473     * Get a height of a candidate view.
1474     *
1475     * @return the height of a candidate view.
1476     */
1477    private int getCandidateViewHeight() {
1478        if (OpenWnn.isXLarge()) {
1479           return mKeyboardHeight + mCandidateOneLineMinimumHeight - mSymbolKeyboardHeight
1480                         - mSymbolKeyboardTabHeight;
1481        } else {
1482            int numberOfLine = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine;
1483            Resources resource = mWnn.getResources();
1484            Drawable keyboardBackground = resource.getDrawable(R.drawable.keyboard_background);
1485            Rect keyboardPadding = new Rect(0 ,0 ,0 ,0);
1486            keyboardBackground.getPadding(keyboardPadding);
1487            int keyboardTotalPadding = keyboardPadding.top + keyboardPadding.bottom;
1488            if (mIsSymbolMode) {
1489                return mKeyboardHeight + numberOfLine * getCandidateMinimumHeight()
1490                       - mSymbolKeyboardHeight - mSymbolKeyboardTabHeight;
1491            } else if (!mHardKeyboardHidden) {
1492                return mKeyboardHeight + numberOfLine * getCandidateMinimumHeight()
1493                       - mSymbolKeyboardHeight;
1494            } else {
1495                return mKeyboardHeight + keyboardTotalPadding
1496                       + numberOfLine * getCandidateMinimumHeight();
1497            }
1498        }
1499    }
1500
1501    /**
1502     * Update symbol type.
1503     */
1504    private void updateSymbolType() {
1505        switch (mSymbolMode) {
1506        case OpenWnnJAJP.ENGINE_MODE_SYMBOL:
1507            updateTabStatus(mViewTabSymbol, true, true);
1508            updateTabStatus(mViewTabEmoticon, mEnableEmoticon, false);
1509            break;
1510
1511        case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI:
1512            updateTabStatus(mViewTabSymbol, true, false);
1513            updateTabStatus(mViewTabEmoticon, mEnableEmoticon, true);
1514            break;
1515
1516        default:
1517            updateTabStatus(mViewTabSymbol, true, false);
1518            updateTabStatus(mViewTabEmoticon, mEnableEmoticon, false);
1519            break;
1520        }
1521    }
1522
1523    /**
1524     * Update tab status.
1525     *
1526     * @param tab           The tab view.
1527     * @param enabled       The tab is enabled.
1528     * @param selected      The tab is selected.
1529     */
1530    private void updateTabStatus(TextView tab, boolean enabled, boolean selected) {
1531        tab.setVisibility(View.VISIBLE);
1532        tab.setEnabled(enabled);
1533        int backgroundId = 0;
1534        int colorId = 0;
1535        if (enabled) {
1536            if (selected) {
1537                backgroundId = R.drawable.cand_tab;
1538                colorId = R.color.tab_textcolor_select;
1539            } else {
1540                backgroundId = R.drawable.cand_tab_noselect;
1541                colorId = R.color.tab_textcolor_no_select;
1542            }
1543        } else {
1544            backgroundId = R.drawable.cand_tab_noselect;
1545            colorId = R.color.tab_textcolor_disable;
1546        }
1547        tab.setBackgroundResource(backgroundId);
1548        tab.setTextColor(mWnn.getResources().getColor(colorId));
1549    }
1550
1551    /**
1552     * Get candidate number of division.
1553     * @return Number of division
1554     */
1555    private int getCandidateViewDivison() {
1556        int viewDivison;
1557
1558        if (mIsSymbolMode) {
1559            int mode = mSymbolMode;
1560            switch (mode) {
1561            case OpenWnnJAJP.ENGINE_MODE_SYMBOL:
1562                viewDivison = (mPortrait) ? FULL_VIEW_SYMBOL_DIV_PORT : FULL_VIEW_SYMBOL_DIV_LAND;
1563                break;
1564            case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI:
1565            default:
1566                viewDivison = FULL_VIEW_DIV;
1567                break;
1568            }
1569        } else {
1570             viewDivison = FULL_VIEW_DIV;
1571        }
1572        return viewDivison;
1573    }
1574
1575    /**
1576     * @return Word count
1577     */
1578    private int getWordCount() {
1579        return mWordCount1st + mWordCount2nd;
1580    }
1581
1582    /**
1583     * @return Add second
1584     */
1585    private boolean isFirstListOver(boolean isFullView, int lineCount, WnnWord word) {
1586
1587        if (mIsSymbolMode) {
1588            switch (mSymbolMode) {
1589            case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI:
1590                return true;
1591            case OpenWnnJAJP.ENGINE_MODE_SYMBOL:
1592				return true;
1593            default:
1594                return (isFullView || getMaxLine() < lineCount);
1595            }
1596        } else {
1597            return (isFullView || getMaxLine() < lineCount);
1598        }
1599    }
1600
1601    /**
1602     * @return Candidate space width
1603     */
1604    private int getCandidateSpaceWidth(boolean isEmojiSymbol) {
1605        Resources r = mWnn.getResources();
1606        if (mPortrait) {
1607            if (isEmojiSymbol) {
1608                return 0;
1609            } else {
1610                return r.getDimensionPixelSize(R.dimen.cand_space_width);
1611            }
1612        } else {
1613            if (isEmojiSymbol) {
1614                return r.getDimensionPixelSize(R.dimen.cand_space_width_emoji_symbol);
1615            } else {
1616                return r.getDimensionPixelSize(R.dimen.cand_space_width);
1617            }
1618        }
1619    }
1620
1621    /**
1622     * KeyEvent action for the candidate view.
1623     *
1624     * @param key    Key event
1625     */
1626    public void processMoveKeyEvent(int key) {
1627        if (!mViewBody.isShown()) {
1628            return;
1629        }
1630
1631        switch (key) {
1632        case KeyEvent.KEYCODE_DPAD_UP:
1633            moveFocus(-1, true);
1634            break;
1635
1636        case KeyEvent.KEYCODE_DPAD_DOWN:
1637            moveFocus(1, true);
1638            break;
1639
1640        case KeyEvent.KEYCODE_DPAD_LEFT:
1641            moveFocus(-1, false);
1642            break;
1643
1644        case KeyEvent.KEYCODE_DPAD_RIGHT:
1645            moveFocus(1, false);
1646            break;
1647
1648        default:
1649            break;
1650        }
1651    }
1652
1653    /**
1654     * Get a flag candidate is focused now.
1655     *
1656     * @return the Candidate is focused of a flag.
1657     */
1658    public boolean isFocusCandidate(){
1659        if (mCurrentFocusIndex != FOCUS_NONE) {
1660            return true;
1661        }
1662        return false;
1663    }
1664
1665    /**
1666     * Give focus to View of candidate.
1667     */
1668    public void setViewStatusOfFocusedCandidate(){
1669        View view = mFocusedView;
1670        if (view != null) {
1671            view.setBackgroundDrawable(mFocusedViewBackground);
1672            view.setPadding(0, 0, 0, 0);
1673        }
1674
1675        TextView v = getFocusedView();
1676        mFocusedView = v;
1677        if (v != null) {
1678            mFocusedViewBackground = v.getBackground();
1679            v.setBackgroundResource(R.drawable.cand_back_focuse);
1680            v.setPadding(0, 0, 0, 0);
1681
1682            int viewBodyTop = getViewTopOnScreen(mViewBodyScroll);
1683            int viewBodyBottom = viewBodyTop + mViewBodyScroll.getHeight();
1684            int focusedViewTop = getViewTopOnScreen(v);
1685            int focusedViewBottom = focusedViewTop + v.getHeight();
1686
1687            if (focusedViewBottom > viewBodyBottom) {
1688                mViewBodyScroll.scrollBy(0, (focusedViewBottom - viewBodyBottom));
1689            } else if (focusedViewTop < viewBodyTop) {
1690                mViewBodyScroll.scrollBy(0, (focusedViewTop - viewBodyTop));
1691            }
1692        }
1693    }
1694
1695    /**
1696     * Clear focus to selected candidate.
1697     */
1698    public void clearFocusCandidate(){
1699        View view = mFocusedView;
1700        if (view != null) {
1701            view.setBackgroundDrawable(mFocusedViewBackground);
1702            view.setPadding(0, 0, 0, 0);
1703            mFocusedView = null;
1704        }
1705
1706        mFocusAxisX = 0;
1707        mHasFocusedArray1st = true;
1708        mCurrentFocusIndex = FOCUS_NONE;
1709        mHandler.removeMessages(MSG_MOVE_FOCUS);
1710        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_END));
1711    }
1712
1713    /**
1714     * @see CandidatesViewManager#selectFocusCandidate
1715     */
1716    public void selectFocusCandidate(){
1717        if (mCurrentFocusIndex != FOCUS_NONE) {
1718            WnnWord word = getFocusedWnnWord();
1719
1720            if (mHandler.hasMessages(MSG_SET_CANDIDATES)) {
1721                mWnnWordSelectedList.add(word);
1722            } else {
1723                selectCandidate(word);
1724            }
1725        }
1726    }
1727
1728    /** @see CandidatesViewManager#getFocusedWnnWord */
1729    public WnnWord getFocusedWnnWord() {
1730        return getWnnWord(mCurrentFocusIndex);
1731    }
1732
1733    /**
1734     * Get WnnWord.
1735     *
1736     * @return WnnWord word
1737     */
1738    public WnnWord getWnnWord(int index) {
1739        WnnWord word = null;
1740        if (index < 0) {
1741            index = 0;
1742            mHandler.removeMessages(MSG_MOVE_FOCUS);
1743            Log.i("iwnn", "TextCandidatesViewManager::getWnnWord  index < 0 ");
1744        } else {
1745            int size = mHasFocusedArray1st ? mWnnWordArray1st.size() : mWnnWordArray2nd.size();
1746            if (index >= size) {
1747                index = size - 1;
1748                mHandler.removeMessages(MSG_MOVE_FOCUS);
1749                Log.i("iwnn", "TextCandidatesViewManager::getWnnWord  index > candidate max ");
1750            }
1751        }
1752
1753        if (mHasFocusedArray1st) {
1754            word = mWnnWordArray1st.get(index);
1755        } else {
1756            word = mWnnWordArray2nd.get(index);
1757        }
1758        return word;
1759    }
1760
1761    /**
1762     * Set display candidate line from SharedPreferences.
1763     */
1764    private void setNumeberOfDisplayLines(){
1765        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mWnn);
1766        mPortraitNumberOfLine = Integer.parseInt(pref.getString("setting_portrait", "2"));
1767        mLandscapeNumberOfLine = Integer.parseInt(pref.getString("setting_landscape", "1"));
1768    }
1769
1770    /**
1771     * Set emoticon enabled.
1772     */
1773    public void setEnableEmoticon(boolean enableEmoticon) {
1774        mEnableEmoticon = enableEmoticon;
1775    }
1776
1777    /**
1778     * Get View of focus candidate.
1779     */
1780    public TextView getFocusedView() {
1781        if (mCurrentFocusIndex == FOCUS_NONE) {
1782            return null;
1783        }
1784        TextView t;
1785        if (mHasFocusedArray1st) {
1786            t = mTextViewArray1st.get(mCurrentFocusIndex);
1787        } else {
1788            t = mTextViewArray2nd.get(mCurrentFocusIndex);
1789        }
1790        return t;
1791    }
1792
1793    /**
1794     * Move the focus to next candidate.
1795     *
1796     * @param direction  The direction of increment or decrement.
1797     * @param updown     {@code true} if move is up or down.
1798     */
1799    public void moveFocus(int direction, boolean updown) {
1800        boolean isStart = (mCurrentFocusIndex == FOCUS_NONE);
1801        if (direction == 0) {
1802            setViewStatusOfFocusedCandidate();
1803        }
1804
1805        int size1st = mTextViewArray1st.size();
1806        if (mHasFocusedArray1st && (size1st == 0)) {
1807            mHasFocusedArray1st = false;
1808        }
1809        ArrayList<TextView> list = mHasFocusedArray1st ? mTextViewArray1st : mTextViewArray2nd;
1810        int size = list.size();
1811        int start = (mCurrentFocusIndex == FOCUS_NONE) ? 0 : (mCurrentFocusIndex + direction);
1812
1813        int index = -1;
1814        boolean hasChangedLine = false;
1815        for (int i = start; (0 <= i) && (i < size); i += direction) {
1816            TextView view = list.get(i);
1817            if (!view.isShown()) {
1818                break;
1819            }
1820
1821            if (mIsSymbolMode && (view.getBackground() == null)) {
1822                continue;
1823            }
1824
1825            if (updown) {
1826                int left = view.getLeft();
1827                if ((left <= mFocusAxisX)
1828                        && (mFocusAxisX < view.getRight())) {
1829                    index = i;
1830                    break;
1831                }
1832
1833                if (left == 0) {
1834                    hasChangedLine = true;
1835                }
1836            } else {
1837                index = i;
1838                break;
1839            }
1840        }
1841
1842        if ((index < 0) && hasChangedLine && !mHasFocusedArray1st && (0 < direction)) {
1843            index = mTextViewArray2nd.size() - 1;
1844        }
1845
1846        if (0 <= index) {
1847            mCurrentFocusIndex = index;
1848            setViewStatusOfFocusedCandidate();
1849            if (!updown) {
1850                mFocusAxisX = getFocusedView().getLeft();
1851            }
1852        } else {
1853            if (mCanReadMore && (0 < size1st)) {
1854
1855                if ((mHasFocusedArray1st && (direction < 0))
1856                        || (!mHasFocusedArray1st && (0 < direction))) {
1857                    updown = false;
1858                }
1859
1860                mHasFocusedArray1st = !mHasFocusedArray1st;
1861
1862                if (!mHasFocusedArray1st && !mIsFullView) {
1863                    setFullMode();
1864                }
1865            }
1866
1867            if (size1st == 0) {
1868                updown = false;
1869            }
1870
1871            if (0 < direction) {
1872                mCurrentFocusIndex = -1;
1873            } else {
1874                mCurrentFocusIndex = (mHasFocusedArray1st ? size1st : mTextViewArray2nd.size());
1875            }
1876            Message m = mHandler.obtainMessage(MSG_MOVE_FOCUS, direction, updown ? 1 : 0);
1877            mHandler.sendMessage(m);
1878        }
1879
1880        if (isStart) {
1881            mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_START));
1882        }
1883    }
1884
1885    /**
1886     * Set hardkeyboard hidden.
1887     *
1888     * @param hardKeyboardHidden hardkeyaboard hidden.
1889     */
1890    public void setHardKeyboardHidden(boolean hardKeyboardHidden) {
1891        mHardKeyboardHidden = hardKeyboardHidden;
1892    }
1893
1894    /**
1895     * Get view top position on screen.
1896     *
1897     * @param view target view.
1898     * @return int view top position on screen
1899     */
1900    public int getViewTopOnScreen(View view) {
1901        int[] location = new int[2];
1902        view.getLocationOnScreen(location);
1903        return location[1];
1904    }
1905
1906
1907    /** @see CandidatesViewManager#setCandidateMsgRemove */
1908    public void setCandidateMsgRemove() {
1909        mHandler.removeMessages(MSG_SET_CANDIDATES);
1910    }
1911}
1912