OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / packages / inputmethods / OpenWnn / src / jp / co / omronsoft / openwnn / TextCandidatesViewManager.java
1 /*
2  * Copyright (C) 2008,2009  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.util.ArrayList;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.content.res.Configuration;
23 import android.content.res.Resources;
24 import android.media.MediaPlayer;
25 import android.os.Vibrator;
26 import android.text.TextUtils;
27 import android.text.TextPaint;
28 import android.text.SpannableString;
29 import android.text.Spanned;
30 import android.text.style.ImageSpan;
31 import android.text.style.DynamicDrawableSpan;
32 import android.util.Log;
33 import android.util.DisplayMetrics;
34 import android.view.Gravity;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.View.OnClickListener;
39 import android.view.View.OnLongClickListener;
40 import android.view.View.OnTouchListener;
41 import android.view.GestureDetector;
42 import android.view.LayoutInflater;
43 import android.widget.Button;
44 import android.widget.LinearLayout;
45 import android.widget.ScrollView;
46 import android.widget.TextView;
47 import android.widget.EditText;
48 import android.widget.AbsoluteLayout;
49 import android.widget.ImageView;
50 import android.graphics.drawable.Drawable;
51
52 /**
53  * The default candidates view manager class using {@link EditText}.
54  *
55  * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
56  */
57 public class TextCandidatesViewManager implements CandidatesViewManager, GestureDetector.OnGestureListener {
58     /** Height of a line */
59     public static final int LINE_HEIGHT = 34;
60     /** Number of lines to display (Portrait) */
61     public static final int LINE_NUM_PORTRAIT       = 2;
62     /** Number of lines to display (Landscape) */
63     public static final int LINE_NUM_LANDSCAPE      = 1;
64
65     /** Maximum lines */
66     private static final int DISPLAY_LINE_MAX_COUNT = 1000;
67     /** Width of the view */
68     private static final int CANDIDATE_MINIMUM_WIDTH = 48;
69     /** Height of the view */
70     private static final int CANDIDATE_MINIMUM_HEIGHT = 35;
71     /** Align the candidate left if the width of the string exceeds this threshold */
72     private static final int CANDIDATE_LEFT_ALIGN_THRESHOLD = 120;
73     /** Maximum number of displaying candidates par one line (full view mode) */
74     private static final int FULL_VIEW_DIV = 4;
75
76     /** Body view of the candidates list */
77     private ViewGroup  mViewBody;
78     /** Scroller of {@code mViewBodyText} */
79     private ScrollView mViewBodyScroll;
80     /** Base of {@code mViewCandidateList1st}, {@code mViewCandidateList2nd} */
81     private ViewGroup mViewCandidateBase;
82     /** Button displayed bottom of the view when there are more candidates. */
83     private ImageView mReadMoreButton;
84     /** The view of the scaling up candidate */
85     private View mViewScaleUp;
86     /** Layout for the candidates list on normal view */
87     private LinearLayout mViewCandidateList1st;
88     /** Layout for the candidates list on full view */
89     private AbsoluteLayout mViewCandidateList2nd;
90     /** {@link OpenWnn} instance using this manager */
91     private OpenWnn mWnn;
92     /** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */
93     private int mViewType;
94     /** Portrait display({@code true}) or landscape({@code false}) */
95     private boolean mPortrait;
96
97     /** Width of the view */
98     private int mViewWidth;
99     /** Height of the view */
100     private int mViewHeight;
101     /** Minimum width of a candidate (density support) */
102     private int mCandidateMinimumWidth;
103     /** Maximum width of a candidate (density support) */
104     private int mCandidateMinimumHeight;
105
106     /** Whether hide the view if there is no candidates */
107     private boolean mAutoHideMode;
108     /** The converter to be get candidates from and notice the selected candidate to. */
109     private WnnEngine mConverter;
110     /** Limitation of displaying candidates */
111     private int mDisplayLimit;
112
113     /** Vibrator for touch vibration */
114     private Vibrator mVibrator = null;
115     /** MediaPlayer for click sound */
116     private MediaPlayer mSound = null;
117
118     /** Number of candidates displaying */
119     private int mWordCount;
120     /** List of candidates */
121     private ArrayList<WnnWord> mWnnWordArray;
122
123     /** Gesture detector */
124     private GestureDetector mGestureDetector;
125     /** The word pressed */
126     private WnnWord mWord;
127     /** Character width of the candidate area */
128     private int mLineLength = 0;
129     /** Number of lines displayed */
130     private int mLineCount = 1;
131
132     /** {@code true} if the candidate delete state is selected */
133     private boolean mIsScaleUp = false;
134
135     /** {@code true} if the full screen mode is selected */
136     private boolean mIsFullView = false;
137
138     /** The event object for "touch" */
139     private MotionEvent mMotionEvent = null;
140
141     /** The offset when the candidates is flowed out the candidate window */
142     private int mDisplayEndOffset = 0;
143     /** {@code true} if there are more candidates to display. */
144     private boolean mCanReadMore = false;
145     /** Width of {@code mReadMoreButton} */
146     private int mReadMoreButtonWidth = 0;
147     /** Color of the candidates */
148     private int mTextColor = 0;
149     /** Template object for each candidate and normal/full view change button */
150     private TextView mViewCandidateTemplate;
151     /** Number of candidates in full view */
152     private int mFullViewWordCount;
153     /** Number of candidates in the current line (in full view) */
154     private int mFullViewOccupyCount;
155     /** View of the previous candidate (in full view) */
156     private TextView mFullViewPrevView;
157     /** Id of the top line view (in full view) */
158     private int mFullViewPrevLineTopId;
159     /** Layout of the previous candidate (in full view) */
160     private ViewGroup.LayoutParams mFullViewPrevParams;
161     /** Whether all candidates is displayed */
162     private boolean mCreateCandidateDone;
163     /** Number of lines in normal view */
164     private int mNormalViewWordCountOfLine;
165     /** general infomation about a display */
166     private final DisplayMetrics mMetrics = new DisplayMetrics();
167
168     /** Event listener for touching a candidate */
169     private OnTouchListener mCandidateOnTouch = new OnTouchListener() {
170             public boolean onTouch(View v, MotionEvent event) {
171                 if (mMotionEvent != null) {
172                     return true;
173                 }
174
175                 if ((event.getAction() == MotionEvent.ACTION_UP)
176                     && (v instanceof TextView)) {
177                     Drawable d = v.getBackground();
178                     if (d != null) {
179                         d.setState(new int[] {});
180                     }
181                 }
182
183                 mMotionEvent = event;
184                 boolean ret = mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CANDIDATE_VIEW_TOUCH));
185                 mMotionEvent = null;
186                 return ret;
187             }
188         };
189     
190     
191     /** Event listener for clicking a candidate */
192     private OnClickListener mCandidateOnClick = new OnClickListener() {
193             public void onClick(View v) {
194                 if (!v.isShown()) {
195                     return;
196                 }
197                 
198                 if (v instanceof TextView) {
199                     TextView text = (TextView)v;
200                     int wordcount = text.getId();
201                     WnnWord word = null;
202                     word = mWnnWordArray.get(wordcount);
203                     selectCandidate(word);
204                 }
205             }
206         };
207
208     /** Event listener for long-clicking a candidate */
209     private OnLongClickListener mCandidateOnLongClick = new OnLongClickListener() {
210             public boolean onLongClick(View v) {
211                 if (mViewScaleUp == null) {
212                     return false;
213                 }
214
215                 if (!v.isShown()) {
216                     return true;
217                 }
218
219                 Drawable d = v.getBackground();
220                 if (d != null) {
221                     if(d.getState().length == 0){
222                         return true;
223                     }
224                 }
225             
226                 int wordcount = ((TextView)v).getId();
227                 mWord = mWnnWordArray.get(wordcount);
228                 setViewScaleUp(true, mWord);
229             
230                 return true;
231             }
232         };
233
234
235     /**
236      * Constructor
237      */
238     public TextCandidatesViewManager() {
239         this(-1);
240     }
241
242     /**
243      * Constructor
244      *
245      * @param displayLimit      The limit of display
246      */
247     public TextCandidatesViewManager(int displayLimit) {
248         this.mDisplayLimit = displayLimit;
249         this.mWnnWordArray = new ArrayList<WnnWord>();
250         this.mAutoHideMode = true;
251         mMetrics.setToDefaults();
252     }
253
254     /**
255      * Set auto-hide mode.
256      * @param hide      {@code true} if the view will hidden when no candidate exists;
257      *                  {@code false} if the view is always shown.
258      */
259     public void setAutoHide(boolean hide) {
260         mAutoHideMode = hide;
261     }
262
263     /** @see CandidatesViewManager */
264     public View initView(OpenWnn parent, int width, int height) {
265         mWnn = parent;
266         mViewWidth = width;
267         mViewHeight = height;
268         mCandidateMinimumWidth = (int)(CANDIDATE_MINIMUM_WIDTH * mMetrics.density);
269         mCandidateMinimumHeight = (int)(CANDIDATE_MINIMUM_HEIGHT * mMetrics.density);
270         mPortrait = 
271             (parent.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE);
272
273         Resources r = mWnn.getResources();
274
275         LayoutInflater inflater = parent.getLayoutInflater();
276         mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates, null);
277
278         mViewBodyScroll = (ScrollView)mViewBody.findViewById(R.id.candview_scroll);
279         mViewBodyScroll.setOnTouchListener(mCandidateOnTouch);
280
281         mViewCandidateBase = (ViewGroup)mViewBody.findViewById(R.id.candview_base);
282
283         createNormalCandidateView();
284         mViewCandidateList2nd = (AbsoluteLayout)mViewBody.findViewById(R.id.candidates_2nd_view);
285
286         mReadMoreButtonWidth = r.getDrawable(R.drawable.cand_up).getMinimumWidth();
287
288         mTextColor = r.getColor(R.color.candidate_text);
289         
290         mReadMoreButton = (ImageView)mViewBody.findViewById(R.id.read_more_text);
291         mReadMoreButton.setOnTouchListener(new View.OnTouchListener() {
292                 public boolean onTouch(View v, MotionEvent event) {
293                     switch (event.getAction()) {
294                     case MotionEvent.ACTION_DOWN:
295                         if (mIsFullView) {
296                             mReadMoreButton.setImageResource(R.drawable.cand_down_press);
297                         } else {
298                             mReadMoreButton.setImageResource(R.drawable.cand_up_press);
299                         }
300                             break;
301                     case MotionEvent.ACTION_UP:
302                         if (mIsFullView) {
303                             mReadMoreButton.setImageResource(R.drawable.cand_down);
304                         } else {
305                             mReadMoreButton.setImageResource(R.drawable.cand_up);
306                         }
307                         break;
308                     default:
309                         break;
310                     }
311                     return false;
312                 }
313             });
314         mReadMoreButton.setOnClickListener(new View.OnClickListener() {
315                 public void onClick(View v) {
316                     if (!v.isShown()) {
317                         return;
318                     }
319
320                     if (mIsFullView) {
321                         mIsFullView = false;
322                         mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
323                     } else {
324                         mIsFullView = true;
325                         mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
326                     }
327                 }
328             });
329
330         setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
331
332         mGestureDetector = new GestureDetector(this);
333
334         View scaleUp = (View)inflater.inflate(R.layout.candidate_scale_up, null);
335         mViewScaleUp = scaleUp;
336
337         /* select button */
338         Button b = (Button)scaleUp.findViewById(R.id.candidate_select);
339         b.setOnClickListener(new View.OnClickListener() {
340                 public void onClick(View v) {
341                     selectCandidate(mWord);
342                 }
343             });
344
345         /* cancel button */
346         b = (Button)scaleUp.findViewById(R.id.candidate_cancel);
347         b.setOnClickListener(new View.OnClickListener() {
348                 public void onClick(View v) {
349                     setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
350                     mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE));
351                 }
352             });
353
354         return mViewBody;
355     }
356
357     /**
358      * Create the normal candidate view
359      */
360     private void createNormalCandidateView() {
361         mViewCandidateList1st = (LinearLayout)mViewBody.findViewById(R.id.candidates_1st_view);
362         mViewCandidateList1st.setOnTouchListener(mCandidateOnTouch);
363         mViewCandidateList1st.setOnClickListener(mCandidateOnClick);
364
365         int line = getMaxLine();
366         int width = mViewWidth;
367         for (int i = 0; i < line; i++) {
368             LinearLayout lineView = new LinearLayout(mViewBodyScroll.getContext());
369             lineView.setOrientation(LinearLayout.HORIZONTAL);
370             LinearLayout.LayoutParams layoutParams = 
371                 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
372                                               ViewGroup.LayoutParams.WRAP_CONTENT);
373             lineView.setLayoutParams(layoutParams);
374             for (int j = 0; j < (width / getCandidateMinimumWidth()); j++) {
375                 TextView tv = createCandidateView();
376                 lineView.addView(tv);
377             }
378
379             if (i == 0) {
380                 TextView tv = createCandidateView();
381                 layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
382                                                              ViewGroup.LayoutParams.WRAP_CONTENT);
383                 layoutParams.weight = 0;
384                 layoutParams.gravity = Gravity.RIGHT;
385                 tv.setLayoutParams(layoutParams);
386
387                 lineView.addView(tv);
388                 mViewCandidateTemplate = tv;
389             }
390             mViewCandidateList1st.addView(lineView);
391         }
392     }
393
394     /** @see CandidatesViewManager#getCurrentView */
395     public View getCurrentView() {
396         return mViewBody;
397     }
398
399     /** @see CandidatesViewManager#setViewType */
400     public void setViewType(int type) {
401         boolean readMore = setViewLayout(type);
402
403         if (readMore) {
404             displayCandidates(this.mConverter, false, -1);
405         } else { 
406             if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) {
407                 mIsFullView = false;
408                 if (mDisplayEndOffset > 0) {
409                     int maxLine = getMaxLine();
410                     displayCandidates(this.mConverter, false, maxLine);
411                 } else {
412                     setReadMore();
413                 }
414             } else {
415                 if (mViewBody.isShown()) {
416                     mWnn.setCandidatesViewShown(false);
417                 }
418             }
419         }
420     }
421
422     /**
423      * Set the view layout
424      *
425      * @param type      View type
426      * @return          {@code true} if display is updated; {@code false} if otherwise
427      */
428     private boolean setViewLayout(int type) {
429         mViewType = type;
430         setViewScaleUp(false, null);
431
432         switch (type) {
433         case CandidatesViewManager.VIEW_TYPE_CLOSE:
434             mViewCandidateBase.setMinimumHeight(-1);
435             return false;
436
437         case CandidatesViewManager.VIEW_TYPE_NORMAL:
438             mViewBodyScroll.scrollTo(0, 0);
439             mViewCandidateList1st.setVisibility(View.VISIBLE);
440             mViewCandidateList2nd.setVisibility(View.GONE);
441             mViewCandidateBase.setMinimumHeight(-1);
442             int line = (mPortrait) ? LINE_NUM_PORTRAIT : LINE_NUM_LANDSCAPE;
443             mViewCandidateList1st.setMinimumHeight(getCandidateMinimumHeight() * line);
444             return false;
445
446         case CandidatesViewManager.VIEW_TYPE_FULL:
447         default:
448             mViewCandidateList2nd.setVisibility(View.VISIBLE);
449             mViewCandidateBase.setMinimumHeight(mViewHeight);
450             return true;
451         }
452     }
453
454     /** @see CandidatesViewManager#getViewType */
455     public int getViewType() {
456         return mViewType;
457     }
458
459     /** @see CandidatesViewManager#displayCandidates */
460     public void displayCandidates(WnnEngine converter) {
461
462         mCanReadMore = false;
463         mDisplayEndOffset = 0;
464         mIsFullView = false;
465         mFullViewWordCount = 0;
466         mFullViewOccupyCount = 0;
467         mFullViewPrevLineTopId = 0;
468         mCreateCandidateDone = false;
469         mNormalViewWordCountOfLine = 0;
470
471         clearCandidates();
472         mConverter = converter;
473         setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
474         
475         mViewCandidateTemplate.setVisibility(View.VISIBLE);
476         mViewCandidateTemplate.setBackgroundResource(R.drawable.cand_back);
477
478         displayCandidates(converter, true, getMaxLine());
479     }
480
481     /** @see CandidatesViewManager#getMaxLine */
482     private int getMaxLine() {
483         int maxLine = (mPortrait) ? LINE_NUM_PORTRAIT : LINE_NUM_LANDSCAPE;
484         return maxLine;
485     }
486
487     /**
488      * Display the candidates.
489      * 
490      * @param converter  {@link WnnEngine} which holds candidates.
491      * @param dispFirst  Whether it is the first time displaying the candidates
492      * @param maxLine    The maximum number of displaying lines
493      */
494     synchronized private void displayCandidates(WnnEngine converter, boolean dispFirst, int maxLine) {
495         if (converter == null) {
496             return;
497         }
498
499         /* Concatenate the candidates already got and the last one in dispFirst mode */
500         int displayLimit = mDisplayLimit;
501
502         boolean isHistorySequence = false;
503         boolean isBreak = false;
504
505         /* Get candidates */
506         WnnWord result = null;
507         while ((displayLimit == -1 || mWordCount < displayLimit)) {
508             result = converter.getNextCandidate();
509
510             if (result == null) {
511                 break;
512             }
513
514             setCandidate(false, result);
515
516             if (dispFirst && (maxLine < mLineCount)) {
517                 mCanReadMore = true;
518                 isBreak = true;
519                 break;
520             }
521         }
522
523         if (!isBreak && !mCreateCandidateDone) {
524             /* align left if necessary */
525             createNextLine();
526             mCreateCandidateDone = true;
527         }
528         
529         if (mWordCount < 1) { /* no candidates */
530             if (mAutoHideMode) {
531                 mWnn.setCandidatesViewShown(false);
532                 return;
533             } else {
534                 mCanReadMore = false;
535                 mIsFullView = false;
536                 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
537             }
538         }
539
540         setReadMore();
541
542         if (!(mViewBody.isShown())) {
543             mWnn.setCandidatesViewShown(true);
544         }
545         return;
546     }
547
548     /**
549      * Add a candidate into the list.
550      * @param isCategory  {@code true}:caption of category, {@code false}:normal word
551      * @param word        A candidate word
552      */
553     private void setCandidate(boolean isCategory, WnnWord word) {
554         int textLength = measureText(word.candidate, 0, word.candidate.length());
555         TextView template = mViewCandidateTemplate;
556         textLength += template.getPaddingLeft() + template.getPaddingRight();
557         int maxWidth = mViewWidth;
558
559         TextView textView;
560         if (mIsFullView || getMaxLine() < mLineCount) {
561             /* Full view */
562             int indentWidth = mViewWidth / FULL_VIEW_DIV;
563             int occupyCount = Math.min((textLength + indentWidth) / indentWidth, FULL_VIEW_DIV);
564             if (isCategory) {
565                 occupyCount = FULL_VIEW_DIV;
566             }
567
568             if (FULL_VIEW_DIV < (mFullViewOccupyCount + occupyCount)) {
569                 if (FULL_VIEW_DIV != mFullViewOccupyCount) {
570                     mFullViewPrevParams.width += (FULL_VIEW_DIV - mFullViewOccupyCount) * indentWidth;
571                     mViewCandidateList2nd.updateViewLayout(mFullViewPrevView, mFullViewPrevParams);
572                 }
573                 mFullViewOccupyCount = 0;
574                 mFullViewPrevLineTopId = mFullViewPrevView.getId();
575                 mLineCount++;
576             }
577
578             ViewGroup layout = mViewCandidateList2nd;
579
580             int width = indentWidth * occupyCount;
581             int height = getCandidateMinimumHeight();
582
583
584             ViewGroup.LayoutParams params = buildLayoutParams(mViewCandidateList2nd, width, height);
585
586             textView = (TextView) layout.getChildAt(mFullViewWordCount);
587             if (textView == null) {
588                 textView = createCandidateView();
589                 textView.setLayoutParams(params);
590
591                 mViewCandidateList2nd.addView(textView);
592             } else {
593                 mViewCandidateList2nd.updateViewLayout(textView, params);
594             }
595
596             mFullViewOccupyCount += occupyCount;
597             mFullViewWordCount++;
598             mFullViewPrevView = textView;
599             mFullViewPrevParams = params;
600
601         } else {
602             textLength = Math.max(textLength, getCandidateMinimumWidth());
603
604             /* Normal view */
605             int nextEnd = mLineLength + textLength;
606             if (mLineCount == 1) {
607                 maxWidth -= getCandidateMinimumWidth();
608             }
609
610             if ((maxWidth < nextEnd) && (mWordCount != 0)) {
611                 createNextLine();
612                 if (getMaxLine() < mLineCount) {
613                     mLineLength = 0;
614                     /* Call this method again to add the candidate in the full view */
615                     setCandidate(isCategory, word);
616                     return;
617                 }
618                 
619                 mLineLength = textLength;
620             } else {
621                 mLineLength = nextEnd;
622             }
623
624             LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1);
625             textView = (TextView) lineView.getChildAt(mNormalViewWordCountOfLine);
626
627             if (isCategory) {
628                 if (mLineCount == 1) {
629                     mViewCandidateTemplate.setBackgroundDrawable(null);
630                 }
631                 mLineLength += CANDIDATE_LEFT_ALIGN_THRESHOLD;
632             }
633
634             mNormalViewWordCountOfLine++;
635         }
636
637         textView.setText(word.candidate);
638         textView.setTextColor(mTextColor);
639         textView.setId(mWordCount);
640         textView.setVisibility(View.VISIBLE);
641         textView.setPressed(false);
642
643         if (isCategory) {
644             textView.setOnClickListener(null);
645             textView.setOnLongClickListener(null);
646             textView.setBackgroundDrawable(null);
647         } else {
648             textView.setOnClickListener(mCandidateOnClick);
649             textView.setOnLongClickListener(mCandidateOnLongClick);
650             textView.setBackgroundResource(R.drawable.cand_back);
651         }
652         textView.setOnTouchListener(mCandidateOnTouch);
653
654         if (maxWidth < textLength) {
655             textView.setEllipsize(TextUtils.TruncateAt.END);
656         } else {
657             textView.setEllipsize(null);
658         }
659
660         ImageSpan span = null;
661         if (word.candidate.equals(" ")) {
662             span = new ImageSpan(mWnn, R.drawable.word_half_space,
663                                  DynamicDrawableSpan.ALIGN_BASELINE);
664         } else if (word.candidate.equals("\u3000" /* full-width space */)) {
665             span = new ImageSpan(mWnn, R.drawable.word_full_space,
666                                  DynamicDrawableSpan.ALIGN_BASELINE);
667         }
668
669         if (span != null) {
670             SpannableString spannable = new SpannableString("   ");
671             spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 
672             textView.setText(spannable);
673         }
674
675         mWnnWordArray.add(mWordCount, word);
676         mWordCount++;
677     }
678
679     /**
680      * Create AbsoluteLayout.LayoutParams
681      * @param layout AbsoluteLayout
682      * @param width
683      * @param height
684      * @return ViewGroup.LayoutParams
685      */
686     private ViewGroup.LayoutParams buildLayoutParams(AbsoluteLayout layout, int width, int height) {
687
688         int indentWidth = mViewWidth / FULL_VIEW_DIV;
689         int x         = indentWidth * mFullViewOccupyCount;
690         int nomalLine = (mPortrait) ? LINE_NUM_PORTRAIT : LINE_NUM_LANDSCAPE;
691         int y         = getCandidateMinimumHeight() * (mLineCount - nomalLine - 1);
692         ViewGroup.LayoutParams params
693               = new AbsoluteLayout.LayoutParams(width, height, x, y);
694
695         return params;
696     }
697
698
699
700             
701
702
703     /**
704      * Create a view for a candidate.
705      * @return the view
706      */
707     private TextView createCandidateView() {
708         TextView text = new TextView(mViewBodyScroll.getContext());
709         text.setTextSize(20);
710         text.setBackgroundResource(R.drawable.cand_back);
711         text.setGravity(Gravity.CENTER);
712         text.setSingleLine();
713         text.setPadding(4, 4, 4, 4);
714         text.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
715                                                            ViewGroup.LayoutParams.WRAP_CONTENT,
716                                                            1.0f));
717         text.setMinHeight(getCandidateMinimumHeight());
718         text.setMinimumWidth(getCandidateMinimumWidth());
719         return text;
720     }
721
722     /**
723      * Display {@code mReadMoreText} if there are more candidates.
724      */
725     private void setReadMore() {
726         if (mIsScaleUp) {
727             mReadMoreButton.setVisibility(View.GONE);
728             mViewCandidateTemplate.setVisibility(View.GONE);
729             return;
730         }
731
732         if (mIsFullView) {
733             mReadMoreButton.setVisibility(View.VISIBLE);
734             mReadMoreButton.setImageResource(R.drawable.cand_down);
735         } else {
736             if (mCanReadMore) {
737                 mReadMoreButton.setVisibility(View.VISIBLE);
738                 mReadMoreButton.setImageResource(R.drawable.cand_up);
739             } else {
740                 mReadMoreButton.setVisibility(View.GONE);
741                 mViewCandidateTemplate.setVisibility(View.GONE);
742             }
743         }
744     }
745
746     /**
747      * Clear the list of the normal candidate view.
748      */
749     private void clearNormalViewCandidate() {
750         LinearLayout candidateList = mViewCandidateList1st;
751         int lineNum = candidateList.getChildCount();
752         for (int i = 0; i < lineNum; i++) {
753
754             LinearLayout lineView = (LinearLayout)candidateList.getChildAt(i);
755             int size = lineView.getChildCount();
756             for (int j = 0; j < size; j++) {
757                 View v = lineView.getChildAt(j);
758                 v.setVisibility(View.GONE);
759             }
760         }
761     }
762         
763     /** @see CandidatesViewManager#clearCandidates */
764     public void clearCandidates() {
765         clearNormalViewCandidate();
766
767         ViewGroup layout = mViewCandidateList2nd;
768         int size = layout.getChildCount();
769         for (int i = 0; i < size; i++) {
770             View v = layout.getChildAt(i);
771             v.setVisibility(View.GONE);
772         }
773     
774         mLineCount = 1;
775         mWordCount = 0;
776         mWnnWordArray.clear();
777
778         mLineLength = 0;
779
780         mIsFullView = false;
781         setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
782         if (mAutoHideMode) {
783             setViewLayout(CandidatesViewManager.VIEW_TYPE_CLOSE);
784         }
785
786         if (mAutoHideMode && mViewBody.isShown()) {
787             mWnn.setCandidatesViewShown(false);
788         }
789         mCanReadMore = false;
790         setReadMore();
791     }
792
793     /** @see CandidatesViewManager#setPreferences */
794     public void setPreferences(SharedPreferences pref) {
795         try {
796             if (pref.getBoolean("key_vibration", false)) {
797                 mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE);
798             } else {
799                 mVibrator = null;
800             }
801             if (pref.getBoolean("key_sound", false)) {
802                 mSound = MediaPlayer.create(mWnn, R.raw.type);
803             } else {
804                 mSound = null;
805             }
806         } catch (Exception ex) {
807             Log.d("iwnn", "NO VIBRATOR");
808         }
809     }
810     
811     /**
812      * Process {@code OpenWnnEvent.CANDIDATE_VIEW_TOUCH} event.
813      * 
814      * @return      {@code true} if event is processed; {@code false} if otherwise
815      */
816     public boolean onTouchSync() {
817         return mGestureDetector.onTouchEvent(mMotionEvent);
818     }
819
820     /**
821      * Select a candidate.
822      * <br>
823      * This method notices the selected word to {@link OpenWnn}.
824      *
825      * @param word  The selected word
826      */
827     private void selectCandidate(WnnWord word) {
828         setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
829         if (mVibrator != null) {
830             try { mVibrator.vibrate(30); } catch (Exception ex) { }
831         }
832         if (mSound != null) {
833             try { mSound.seekTo(0); mSound.start(); } catch (Exception ex) { }
834         }
835         mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word));
836     }
837
838     /** @see android.view.GestureDetector.OnGestureListener#onDown */
839     public boolean onDown(MotionEvent arg0) {
840         return false;
841     }
842
843     /** @see android.view.GestureDetector.OnGestureListener#onFling */
844     public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
845         if (mIsScaleUp) {
846             return false;
847         }
848
849         boolean consumed = false;
850         if (arg1 != null && arg0 != null && arg1.getY() < arg0.getY()) {
851             if ((mViewType == CandidatesViewManager.VIEW_TYPE_NORMAL) && mCanReadMore) {
852                 if (mVibrator != null) {
853                     try { mVibrator.vibrate(30); } catch (Exception ex) { }
854                 }
855                 mIsFullView = true;
856                 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
857                 consumed = true;
858             }
859         } else {
860             if (mViewBodyScroll.getScrollY() == 0) {
861                 if (mVibrator != null) {
862                     try { mVibrator.vibrate(30); } catch (Exception ex) { }
863                 }
864                 mIsFullView = false;
865                 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
866                 consumed = true;
867             }
868         }
869
870         return consumed;
871     }
872
873     /** @see android.view.GestureDetector.OnGestureListener#onLongPress */
874     public void onLongPress(MotionEvent arg0) {
875         return;
876     }
877
878     /** @see android.view.GestureDetector.OnGestureListener#onScroll */
879     public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
880         return false;
881     }
882
883     /** @see android.view.GestureDetector.OnGestureListener#onShowPress */
884     public void onShowPress(MotionEvent arg0) {
885     }
886
887     /** @see android.view.GestureDetector.OnGestureListener#onSingleTapUp */
888     public boolean onSingleTapUp(MotionEvent arg0) {
889         return false;
890     }
891     
892     /**
893      * Retrieve the width of string to draw.
894      * 
895      * @param text          The string
896      * @param start         The start position (specified by the number of character)
897      * @param end           The end position (specified by the number of character)
898      * @return          The width of string to draw
899      */ 
900     public int measureText(CharSequence text, int start, int end) {
901         if (end - start < 3) {
902             return getCandidateMinimumWidth();
903         }
904
905         TextPaint paint = mViewCandidateTemplate.getPaint();
906         return (int)paint.measureText(text, start, end);
907     }
908
909     /**
910      * Switch list/enlarge view mode.
911      * @param up  {@code true}:enlarge, {@code false}:list
912      * @param word  The candidate word to be enlarged.
913      */
914     private void setViewScaleUp(boolean up, WnnWord word) {
915         if (up == mIsScaleUp || (mViewScaleUp == null)) {
916             return;
917         }
918
919         if (up) {
920             setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
921             mViewCandidateList1st.setVisibility(View.GONE);
922             mViewCandidateBase.setMinimumHeight(-1);
923             mViewCandidateBase.addView(mViewScaleUp);
924             TextView text = (TextView)mViewScaleUp.findViewById(R.id.candidate_scale_up_text);
925             text.setText(word.candidate);
926             if (!mPortrait) {
927                 Resources r = mViewBodyScroll.getContext().getResources();
928                 text.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_delete_word_size_landscape));
929             }
930
931             mIsScaleUp = true;
932             setReadMore();
933         } else {
934             mIsScaleUp = false;
935             mViewCandidateBase.removeView(mViewScaleUp);
936         }
937     }
938
939     /**
940      * Create a layout for the next line.
941      */
942     private void createNextLine() {
943         int lineCount = mLineCount;
944         if (mIsFullView || getMaxLine() < lineCount) {
945             /* Full view */
946             mFullViewOccupyCount = 0;
947             mFullViewPrevLineTopId = mFullViewPrevView.getId();
948         } else {
949             /* Normal view */
950             LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(lineCount - 1);
951             float weight = 0;
952             if (mLineLength < CANDIDATE_LEFT_ALIGN_THRESHOLD) {
953                 if (lineCount == 1) {
954                     mViewCandidateTemplate.setVisibility(View.GONE);
955                 }
956             } else {
957                 weight = 1.0f;
958             }
959
960             LinearLayout.LayoutParams params
961                 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
962                                                 ViewGroup.LayoutParams.WRAP_CONTENT,
963                                                 weight);
964             
965             int child = lineView.getChildCount();
966             for (int i = 0; i < child; i++) {
967                 View view = lineView.getChildAt(i);
968
969                 if (view != mViewCandidateTemplate) {
970                     view.setLayoutParams(params);
971                 }
972             }
973
974             mLineLength = 0;
975             mNormalViewWordCountOfLine = 0;
976         }
977         mLineCount++;
978     }
979
980     /**
981      * @return the minimum width of a candidate view.
982      */
983     private int getCandidateMinimumWidth() {
984         return mCandidateMinimumWidth;
985     }
986
987     /**
988      * @return the minimum height of a candidate view.
989      */
990     private int getCandidateMinimumHeight() {
991         return mCandidateMinimumHeight;
992     }
993 }