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