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 
17 package jp.co.omronsoft.openwnn;
18 
19 import java.nio.channels.Selector;
20 import java.util.ArrayList;
21 import java.util.LinkedList;
22 import android.content.Context;
23 import android.content.SharedPreferences;
24 import android.content.res.Resources;
25 import android.content.res.Configuration;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.Rect;
28 import android.media.AudioManager;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.Vibrator;
32 import android.preference.PreferenceManager;
33 import android.text.TextUtils;
34 import android.text.TextPaint;
35 import android.text.SpannableString;
36 import android.text.Spanned;
37 import android.text.style.ImageSpan;
38 import android.text.style.DynamicDrawableSpan;
39 import android.util.Log;
40 import android.util.DisplayMetrics;
41 import android.util.TypedValue;
42 import android.view.Gravity;
43 import android.view.KeyEvent;
44 import android.view.MotionEvent;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.View.OnClickListener;
48 import android.view.View.OnLongClickListener;
49 import android.view.GestureDetector;
50 import android.view.LayoutInflater;
51 import android.widget.Button;
52 import android.widget.LinearLayout;
53 import android.widget.ScrollView;
54 import android.widget.TextView;
55 import android.widget.EditText;
56 import android.widget.AbsoluteLayout;
57 import 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  */
64 public 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      */
TextCandidatesViewManager()344     public TextCandidatesViewManager() {
345         this(-1);
346     }
347 
348     /**
349      * Constructor
350      *
351      * @param displayLimit      The limit of display
352      */
TextCandidatesViewManager(int displayLimit)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      */
onClickCandidate(View v, ArrayList<WnnWord> list)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      */
onLongClickCandidate(View v, ArrayList<WnnWord> list)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      */
setAutoHide(boolean hide)424     public void setAutoHide(boolean hide) {
425         mAutoHideMode = hide;
426     }
427 
428     /** @see CandidatesViewManager#initView */
initView(OpenWnn parent, int width, int height)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      */
createNormalCandidateView()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 */
getCurrentView()591     public View getCurrentView() {
592         return mViewBody;
593     }
594 
595     /** @see CandidatesViewManager#setViewType */
setViewType(int type)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      */
setViewLayout(int type)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 */
getViewType()675     public int getViewType() {
676         return mViewType;
677     }
678 
679     /** @see CandidatesViewManager#displayCandidates */
displayCandidates(WnnEngine converter)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 */
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      */
getCategoriesText(String categoriesString)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      */
displayCandidates(WnnEngine converter, boolean dispFirst, int maxLine)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      */
setCandidate(boolean isCategory, WnnWord word)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      */
buildLayoutParams(AbsoluteLayout layout, int width, int height)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      */
createCandidateView()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      */
setReadMore()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      */
clearNormalViewCandidate()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 */
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 */
setPreferences(SharedPreferences pref)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      */
setNormalMode()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      */
setFullMode()1222     public void setFullMode() {
1223         mIsFullView = true;
1224         mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
1225     }
1226 
1227     /**
1228      * Set symbol mode.
1229      */
setSymbolMode(boolean enable, int mode)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      */
setScrollUp()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      */
setScrollDown()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      */
setScrollFullUp()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      */
setScrollFullDown()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      */
onTouchSync()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      */
selectCandidate(WnnWord word)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 
playSoundAndVibration()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 */
onDown(MotionEvent arg0)1317     public boolean onDown(MotionEvent arg0) {
1318         return false;
1319     }
1320 
1321     /** @see android.view.GestureDetector.OnGestureListener#onFling */
onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3)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 */
onLongPress(MotionEvent arg0)1356     public void onLongPress(MotionEvent arg0) {
1357         return;
1358     }
1359 
1360     /** @see android.view.GestureDetector.OnGestureListener#onScroll */
onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3)1361     public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
1362         return false;
1363     }
1364 
1365     /** @see android.view.GestureDetector.OnGestureListener#onShowPress */
onShowPress(MotionEvent arg0)1366     public void onShowPress(MotionEvent arg0) {
1367     }
1368 
1369     /** @see android.view.GestureDetector.OnGestureListener#onSingleTapUp */
onSingleTapUp(MotionEvent arg0)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      */
measureText(CharSequence text, int start, int end)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      */
createNextLine(boolean isCategory)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      */
createNextLineFor1st()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      */
isCategory(WnnWord word)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      */
getCandidateMinimumWidth()1461     private int getCandidateMinimumWidth() {
1462         return mCandidateMinimumWidth;
1463     }
1464 
1465     /**
1466      * @return the minimum height of a candidate view.
1467      */
getCandidateMinimumHeight()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      */
getCandidateViewHeight()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      */
updateSymbolType()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      */
updateTabStatus(TextView tab, boolean enabled, boolean selected)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      */
getCandidateViewDivison()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      */
getWordCount()1578     private int getWordCount() {
1579         return mWordCount1st + mWordCount2nd;
1580     }
1581 
1582     /**
1583      * @return Add second
1584      */
isFirstListOver(boolean isFullView, int lineCount, WnnWord word)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      */
getCandidateSpaceWidth(boolean isEmojiSymbol)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      */
processMoveKeyEvent(int key)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      */
isFocusCandidate()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      */
setViewStatusOfFocusedCandidate()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      */
clearFocusCandidate()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      */
selectFocusCandidate()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 */
getFocusedWnnWord()1729     public WnnWord getFocusedWnnWord() {
1730         return getWnnWord(mCurrentFocusIndex);
1731     }
1732 
1733     /**
1734      * Get WnnWord.
1735      *
1736      * @return WnnWord word
1737      */
getWnnWord(int index)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      */
setNumeberOfDisplayLines()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      */
setEnableEmoticon(boolean enableEmoticon)1773     public void setEnableEmoticon(boolean enableEmoticon) {
1774         mEnableEmoticon = enableEmoticon;
1775     }
1776 
1777     /**
1778      * Get View of focus candidate.
1779      */
getFocusedView()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      */
moveFocus(int direction, boolean updown)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      */
setHardKeyboardHidden(boolean hardKeyboardHidden)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      */
getViewTopOnScreen(View view)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 */
setCandidateMsgRemove()1908     public void setCandidateMsgRemove() {
1909         mHandler.removeMessages(MSG_SET_CANDIDATES);
1910     }
1911 }
1912