UserDictionaryToolsList.java revision 77ffa9b0b986a2d70143f63cdaa8451bf1674f84
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
17package jp.co.omronsoft.openwnn;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.Dialog;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.graphics.Color;
25import android.os.Bundle;
26import android.os.Handler;
27import android.text.TextUtils;
28import android.util.Log;
29import android.view.Display;
30import android.view.Menu;
31import android.view.MenuItem;
32import android.view.MotionEvent;
33import android.view.KeyEvent;
34import android.view.View;
35import android.view.ViewGroup;
36import android.view.Window;
37import android.view.WindowManager;
38import android.view.View.OnFocusChangeListener;
39import android.view.View.OnTouchListener;
40import android.widget.TableLayout;
41import android.widget.TableRow;
42import android.widget.TextView;
43import android.widget.Toast;
44
45/**
46 * The abstract class for user dictionary tool.
47 *
48 * @author Copyright (C) 2009, OMRON SOFTWARE CO., LTD.  All Rights Reserved.
49 */
50public abstract class UserDictionaryToolsList extends Activity
51    implements View.OnClickListener, OnTouchListener, OnFocusChangeListener {
52
53    /** The class name of the user dictionary tool */
54    protected String  mListViewName;
55    /** The class name of the user dictionary editor */
56    protected String  mEditViewName;
57    /** The package name of the user dictionary editor */
58    protected String  mPackageName;
59
60    /** ID of the menu item (add) */
61    private final int MENU_ITEM_ADD = 0;
62    /** ID of the menu item (edit) */
63    private final int MENU_ITEM_EDIT = 1;
64    /** ID of the menu item (delete) */
65    private final int MENU_ITEM_DELETE = 2;
66    /** ID of the menu item (initialize) */
67    private final int MENU_ITEM_INIT = 3;
68
69    /** ID of the dialog control (confirm deletion) */
70    private final int DIALOG_CONTROL_DELETE_CONFIRM = 0;
71    /** ID of the dialog control (confirm initialize) */
72    private final int DIALOG_CONTROL_INIT_CONFIRM = 1;
73
74    /** The size of font*/
75    private final int WORD_TEXT_SIZE = 16;
76
77    /** The color of background */
78    private final int UNFOCUS_BACKGROUND_COLOR = 0xFF242424;
79    private final int FOCUS_BACKGROUND_COLOR = 0xFFFF8500;
80
81    /** The minimum count of registered words */
82    private final int MIN_WORD_COUNT = 0;
83    /** The maximum count of registered words */
84    private final int MAX_WORD_COUNT = 100;
85    /** Maximum word count to display */
86    private final int MAX_LIST_WORD_COUNT = 50;
87    /** Maximum word count to display (at the first step) */
88    private final int MAX_LIST_WORD_DELAY_COUNT = 50;
89
90    /** The threshold time of the double tapping */
91    private final int DOUBLE_TAP_TIME = 300;
92
93    /** Widgets which constitute this screen of activity */
94    private Menu mMenu;
95    private TableLayout mTableLayout;
96    private static View sFocusingView = null;
97    private static View sFocusingPairView = null;
98
99    /** Objects which control state transitions */
100    private Intent mIntent;
101    private OpenWnnEvent mEvent;
102    private Handler mDelayUpdateHandler;
103
104    /** The number of the registered words */
105    private int mWordCount = -1;
106
107    /** The state of menu items */
108    private boolean mAddMenuEnabled;
109    private boolean mEditMenuEnabled;
110    private boolean mDeleteMenuEnabled;
111    private boolean mInitMenuEnabled;
112
113    /** {@code true} if the menu option is initialized */
114    private boolean mInitializedMenu = false;
115    /** {@code true} if one of word is selected */
116    private boolean mSelectedWords;
117    /** The viewID which is selected */
118    private int mSelectedViewID = -1;
119    /** The viewID which was selected previously */
120    private static int sBeforeSelectedViewID = -1;
121    /** The time of previous action */
122    private static long sJustBeforeActionTime = -1;
123
124    private boolean mHasCreatedList = false;
125
126    /**
127     * Send the specified event to IME
128     *
129     * @param ev    The event object
130     * @return      {@code true} if this event is processed
131     */
132    protected abstract boolean sendEventToIME(OpenWnnEvent ev);
133
134    /**
135     * Create the header
136     */
137    protected abstract void  headerCreate();
138
139    /** @see android.app.Activity#onCreate */
140    @Override protected void onCreate(Bundle savedInstanceState) {
141
142
143        super.onCreate(savedInstanceState);
144
145        mDelayUpdateHandler = new Handler();
146        /* create XML layout */
147        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
148        setContentView(R.layout.user_dictionary_tools_list);
149
150    }
151
152    /** @see android.app.Activity#onStart */
153    @Override protected void onStart() {
154        super.onStart();
155        sBeforeSelectedViewID = -1;
156        sJustBeforeActionTime = -1;
157        updateWordList();
158    }
159
160    /** @see android.app.Activity#onStop */
161    @Override protected void onStop() {
162        super.onStop();
163        mDelayUpdateHandler.removeCallbacks(updateWordListRunnable);
164    }
165
166    /**
167     * Set parameters of table
168     *
169     * @param  w        The width of the table
170     * @param  h        The height of the table
171     * @return          The information of the layout
172     */
173    private TableLayout.LayoutParams tableCreateParam(int w, int h) {
174        return new TableLayout.LayoutParams(w, h);
175    }
176
177    /** @see android.app.Activity#onCreateOptionsMenu */
178    @Override public boolean onCreateOptionsMenu(Menu menu) {
179
180
181        /* initialize the menu */
182        menu.clear();
183        /* set the menu item enable/disable */
184        setOptionsMenuEnabled();
185        /* [menu] add a word */
186        menu.add(0, MENU_ITEM_ADD, 0, R.string.user_dictionary_add)
187            .setIcon(android.R.drawable.ic_menu_add)
188            .setEnabled(mAddMenuEnabled);
189        /* [menu] edit a word */
190        menu.add(0, MENU_ITEM_EDIT, 0, R.string.user_dictionary_edit)
191            .setIcon(android.R.drawable.ic_menu_edit)
192            .setEnabled(mEditMenuEnabled);
193        /* [menu] delete a word */
194        menu.add(0, MENU_ITEM_DELETE, 0, R.string.user_dictionary_delete)
195            .setIcon(android.R.drawable.ic_menu_delete)
196            .setEnabled(mDeleteMenuEnabled);
197        /* [menu] clear the dictionary */
198        menu.add(1, MENU_ITEM_INIT, 0, R.string.user_dictionary_init)
199            .setIcon(android.R.drawable.ic_menu_delete)
200            .setEnabled(mInitMenuEnabled);
201
202        mMenu = menu;
203        mInitializedMenu = true;
204
205
206        return super.onCreateOptionsMenu(menu);
207    }
208
209    /**
210     * Change state of the option menus according to a current state of the list widget
211     */
212    private void setOptionsMenuEnabled() {
213
214
215        /* [menu] add a word */
216        if ((mWordCount >= MAX_WORD_COUNT) || !mHasCreatedList) {
217            /* disable if the number of registered word exceeds MAX_WORD_COUNT */
218            mAddMenuEnabled = false;
219        } else {
220            mAddMenuEnabled = true;
221        }
222
223        /* [menu] edit a word/delete a word */
224        if (mWordCount <= MIN_WORD_COUNT) {
225            /* disable if no word is registered or no word is selected */
226            mEditMenuEnabled = false;
227            mDeleteMenuEnabled = false;
228        } else {
229            mEditMenuEnabled = true;
230            mDeleteMenuEnabled = true;
231        }
232
233        /* [menu] clear the dictionary (always enabled) */
234        mInitMenuEnabled = true;
235
236    }
237
238    /** @see android.app.Activity#onOptionsItemSelected */
239    @Override public boolean onOptionsItemSelected(MenuItem item) {
240
241        boolean ret;
242        switch (item.getItemId()) {
243        case MENU_ITEM_ADD:
244            /* add a word */
245            wordAdd();
246            ret = true;
247            break;
248
249        case MENU_ITEM_EDIT:
250            /* edit the word (show dialog) */
251            wordEdit(sFocusingView, sFocusingPairView);
252            ret = true;
253            break;
254
255        case MENU_ITEM_DELETE:
256            /* delete the word (show dialog) */
257            showDialog(DIALOG_CONTROL_DELETE_CONFIRM);
258            ret = true;
259            break;
260
261        case MENU_ITEM_INIT:
262            /* clear the dictionary (show dialog) */
263            showDialog(DIALOG_CONTROL_INIT_CONFIRM);
264            ret = true;
265            break;
266
267        default:
268            ret = false;
269        }
270
271        return ret;
272    }
273
274    /** @see android.app.Activity#onKeyUp */
275    @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
276        /* open the menu if KEYCODE_DPAD_CENTER is pressed */
277        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
278            openOptionsMenu();
279            return true;
280        }
281        return false;
282    }
283
284    /** @see android.app.Activity#onCreateDialog */
285    @Override protected Dialog onCreateDialog(int id) {
286        switch (id) {
287        case DIALOG_CONTROL_DELETE_CONFIRM:
288            return new AlertDialog.Builder(UserDictionaryToolsList.this)
289                .setMessage(R.string.user_dictionary_delete_confirm)
290                .setNegativeButton(android.R.string.cancel, null)
291                .setPositiveButton(android.R.string.ok, mDialogDeleteWords)
292                .setCancelable(true)
293                .create();
294
295        case DIALOG_CONTROL_INIT_CONFIRM:
296            return new AlertDialog.Builder(UserDictionaryToolsList.this)
297                .setMessage(R.string.dialog_clear_user_dictionary_message)
298                .setNegativeButton(android.R.string.cancel, null)
299                .setPositiveButton(android.R.string.ok, mDialogInitWords)
300                .setCancelable(true)
301                .create();
302
303        default:
304            Log.e("OpenWnn", "onCreateDialog : Invaled Get DialogID. ID=" + id);
305            break;
306        }
307
308
309        return super.onCreateDialog(id);
310    }
311
312    /**
313     * Process the event when the button on the "Delete word" dialog is pushed
314     *
315     * @param  dialog    The information of the dialog
316     * @param  button    The button that is pushed
317     */
318    private DialogInterface.OnClickListener mDialogDeleteWords =
319        new DialogInterface.OnClickListener() {
320            public void onClick(DialogInterface dialog, int button) {
321
322                CharSequence focusString = ((TextView)sFocusingView).getText();
323                CharSequence focusPairString = ((TextView)sFocusingPairView).getText();
324                WnnWord wnnWordSearch = new WnnWord();
325
326            if (mSelectedViewID > MAX_WORD_COUNT) {
327                    wnnWordSearch.stroke = focusPairString.toString();
328                    wnnWordSearch.candidate = focusString.toString();
329                } else {
330                    wnnWordSearch.stroke = focusString.toString();
331                    wnnWordSearch.candidate = focusPairString.toString();
332                }
333                boolean deleted = deleteWord(wnnWordSearch);
334                if (deleted) {
335                    Toast.makeText(getApplicationContext(),
336                                   R.string.user_dictionary_delete_complete,
337                                   Toast.LENGTH_LONG).show();
338                } else {
339                    Toast.makeText(getApplicationContext(),
340                                   R.string.user_dictionary_delete_fail,
341                                   Toast.LENGTH_LONG).show();
342                return;
343                }
344
345                int id = mSelectedViewID;
346            id = (MAX_WORD_COUNT < id) ? id - MAX_WORD_COUNT : id;
347                View v = null;
348
349                mTableLayout.removeView((View)sFocusingView.getParent());
350
351                for (int i = id; i < MAX_WORD_COUNT; i++) {
352                    v = mTableLayout.findViewById(i);
353                    if (v != null) {
354                        break;
355                    }
356                }
357
358                if (v == null) {
359                    for (int i = id; 0 <= i; i--) {
360                        v = mTableLayout.findViewById(i);
361                        if (v != null) {
362                            break;
363                        }
364                    }
365                }
366
367                if (v != null) {
368                    ((View)v.getParent()).requestFocus();
369                }
370                mWordCount--;
371
372                TextView leftText = (TextView) findViewById(R.id.user_dictionary_tools_list_title_words_count);
373                leftText.setText(mWordCount + "/" + MAX_WORD_COUNT);
374
375                if (mInitializedMenu) {
376                    onCreateOptionsMenu(mMenu);
377                }
378            }
379        };
380
381    /**
382     * Process the event when the button on the "Initialize" dialog is pushed
383     *
384     * @param  dialog    The information of the dialog
385     * @param  button    The button that is pushed
386     */
387    private DialogInterface.OnClickListener mDialogInitWords =
388        new DialogInterface.OnClickListener() {
389            public void onClick(DialogInterface dialog, int button) {
390
391                /* clear the user dictionary */
392                OpenWnnEvent ev = new OpenWnnEvent(OpenWnnEvent.INITIALIZE_USER_DICTIONARY, new WnnWord());
393
394                sendEventToIME(ev);
395                /* show the message */
396                Toast.makeText(getApplicationContext(), R.string.dialog_clear_user_dictionary_done,
397                               Toast.LENGTH_LONG).show();
398                updateWordList();
399            if (mInitializedMenu) {
400                onCreateOptionsMenu(mMenu);
401            }
402            }
403        };
404
405
406    /** @see android.view.View.OnClickListener#onClick */
407    public void onClick(View arg0) {
408    }
409
410    /** @see android.view.View.OnTouchListener#onTouch */
411    public boolean onTouch(View v, MotionEvent e) {
412
413
414        mSelectedViewID = ((TextView)v).getId();
415        switch (e.getAction()) {
416        case MotionEvent.ACTION_DOWN:
417            /* double tap handling */
418            if (sBeforeSelectedViewID != ((TextView)v).getId()) {
419                /* save the view id if the id is not same as previously selected one */
420                sBeforeSelectedViewID = ((TextView)v).getId();
421            } else {
422                if ((e.getDownTime() - sJustBeforeActionTime) < DOUBLE_TAP_TIME) {
423                    /* edit the word if double tapped */
424                    sFocusingView = v;
425                    sFocusingPairView = ((UserDictionaryToolsListFocus)v).getPairView();
426                    wordEdit(sFocusingView, sFocusingPairView);
427                }
428            }
429            /* save the action time */
430            sJustBeforeActionTime = e.getDownTime();
431            break;
432        }
433
434        return false;
435    }
436
437    /** @see android.view.View.OnFocusChangeListener#onFocusChange */
438    public void onFocusChange(View v, boolean hasFocus) {
439
440        mSelectedViewID = ((TextView)v).getId();
441        sFocusingView = v;
442        sFocusingPairView = ((UserDictionaryToolsListFocus)v).getPairView();
443        if (hasFocus) {
444            ((TextView)v).setTextColor(Color.BLACK);
445            v.setBackgroundColor(FOCUS_BACKGROUND_COLOR);
446            ((TextView)sFocusingPairView).setTextColor(Color.BLACK);
447            sFocusingPairView.setBackgroundColor(FOCUS_BACKGROUND_COLOR);
448            mSelectedWords = true;
449        } else {
450            if (mSelectedViewID == 0) {
451                mSelectedWords = false;
452            }
453            ((TextView)v).setTextColor(Color.LTGRAY);
454            v.setBackgroundColor(UNFOCUS_BACKGROUND_COLOR);
455            ((TextView)sFocusingPairView).setTextColor(Color.LTGRAY);
456            sFocusingPairView.setBackgroundColor(UNFOCUS_BACKGROUND_COLOR);
457        }
458        if (mInitializedMenu) {
459            onCreateOptionsMenu(mMenu);
460        }
461    }
462
463    /**
464     * Add the word
465     */
466    public void wordAdd() {
467        screenTransition(Intent.ACTION_INSERT, mEditViewName);
468    }
469
470    /**
471     * Edit the specified word
472     *
473     * @param  focusView       The information of view
474     * @param  focusPairView   The information of pair of view
475     */
476    public void wordEdit(View focusView, View focusPairView) {
477        if (mSelectedViewID > MAX_WORD_COUNT) {
478            createUserDictionaryToolsEdit(focusPairView, focusView);
479        } else {
480            createUserDictionaryToolsEdit(focusView, focusPairView);
481        }
482        screenTransition(Intent.ACTION_EDIT, mEditViewName);
483    }
484
485    /**
486     * The internal process of editing the specified word
487     *
488     * @param  focusView        The information of view
489     * @param  focusPairView    The information of pair of view
490     */
491    protected abstract UserDictionaryToolsEdit createUserDictionaryToolsEdit(View focusView, View focusPairView);
492
493    /**
494     * Delete the specified word
495     *
496     * @param  searchword   The information of searching
497     * @return          {@code true} if success; {@code false} if fail.
498     */
499    public boolean deleteWord(WnnWord searchword) {
500        OpenWnnEvent event = new OpenWnnEvent(OpenWnnEvent.LIST_WORDS_IN_USER_DICTIONARY,
501                                              WnnEngine.DICTIONARY_TYPE_USER,
502                                              searchword);
503
504        boolean deleted = false;
505        sendEventToIME(event);
506        for( int i=0; i < MAX_WORD_COUNT; i++) {
507            WnnWord getword = new WnnWord();
508            event = new OpenWnnEvent(OpenWnnEvent.GET_WORD,
509                                     getword);
510            sendEventToIME(event);
511            getword = event.word;
512            int len = getword.candidate.length();
513            if (len == 0) {
514                break;
515            }
516            if (searchword.candidate.equals(getword.candidate)) {
517                WnnWord delword = new WnnWord();
518                delword.stroke = searchword.stroke;
519                delword.candidate = searchword.candidate;
520                delword.id = i;
521                event = new OpenWnnEvent(OpenWnnEvent.DELETE_WORD,
522                                         delword);
523                deleted = sendEventToIME(event);
524                break;
525            }
526        }
527
528        if (mInitializedMenu) {
529            onCreateOptionsMenu(mMenu);
530        }
531
532        return deleted;
533    }
534
535
536    /**
537     * Processing the transition of screen
538     *
539     * @param  action       The string of action
540     * @param  classname    The class name
541     */
542    private void screenTransition(String action, String classname) {
543
544        if (action.equals("")) {
545            mIntent = new Intent();
546        } else {
547            mIntent = new Intent(action);
548        }
549        mIntent.setClassName(mPackageName, classname);
550        startActivity(mIntent);
551        finish();
552    }
553
554
555    /**
556     * Update the word list.
557     */
558    private void updateWordList() {
559        mWordCount = 0;
560        WnnWord wnnWordSearch = new WnnWord();
561        mEvent = new OpenWnnEvent(OpenWnnEvent.LIST_WORDS_IN_USER_DICTIONARY,
562                                  WnnEngine.DICTIONARY_TYPE_USER,
563                                  wnnWordSearch);
564        sendEventToIME(mEvent);
565
566        mTableLayout = (TableLayout)findViewById(R.id.user_dictionary_tools_table);
567        mTableLayout.removeAllViews();
568
569        if (createWordList(mWordCount,MAX_LIST_WORD_COUNT, this)) {
570            headerCreate();
571            final TextView leftText = (TextView) findViewById(R.id.user_dictionary_tools_list_title_words_count);
572            leftText.setText(R.string.user_dictionary_creating_wordlist);
573            mDelayUpdateHandler.removeCallbacks(updateWordListRunnable);
574            mDelayUpdateHandler.postDelayed(updateWordListRunnable, 0);
575        } else {
576            if (mSelectedViewID >= 0) {
577                View v;
578                v = mTableLayout.findViewById(mSelectedViewID);
579                if (v != null) {
580                    ((View)v.getParent()).requestFocus();
581                } else {
582                    mSelectedViewID--;
583                    if (mSelectedViewID < 0) {
584                        mSelectedViewID = 0;
585                    }
586                    v = mTableLayout.findViewById(mSelectedViewID);
587                    if (v != null) {
588                        ((View)v.getParent()).requestFocus();
589                    }
590                }
591            }
592            headerCreate();
593            final TextView leftText = (TextView) findViewById(R.id.user_dictionary_tools_list_title_words_count);
594            leftText.setText(mWordCount + "/" + MAX_WORD_COUNT);
595            mHasCreatedList = true;
596        }
597    }
598
599    /**
600     * Handler for updating the word list.
601     */
602    private final Runnable updateWordListRunnable = new Runnable() {
603            public void run() {
604                UserDictionaryToolsList self = UserDictionaryToolsList.this;
605                if (createWordList(mWordCount,mWordCount+MAX_LIST_WORD_DELAY_COUNT, self)) {
606                    mDelayUpdateHandler.removeCallbacks(updateWordListRunnable);
607                    mDelayUpdateHandler.postDelayed(updateWordListRunnable, 0);
608                } else {
609                    mDelayUpdateHandler.removeCallbacks(updateWordListRunnable);
610                    final TextView leftText = (TextView) findViewById(R.id.user_dictionary_tools_list_title_words_count);
611                    leftText.setText(mWordCount + "/" + MAX_WORD_COUNT);
612                    mHasCreatedList = true;
613                    if (mInitializedMenu) {
614                        onCreateOptionsMenu(mMenu);
615                    }
616                }
617            }
618        };
619
620    /**
621     * Create the list of words.
622     *
623     * @param  position     Start position to create the list
624     * @param  max          Maximum number of words to display
625     * @param  self         UserDictionaryToolsList
626     * @return          {@code true} if more words undisplayed; {@code false} if no more.
627     */
628    private boolean createWordList(int position ,int max, UserDictionaryToolsList self) {
629        boolean ret = true;
630
631        if (position >= MAX_WORD_COUNT) {
632            return false;
633        }
634        Window window = getWindow();
635        WindowManager windowManager = window.getWindowManager();
636        Display display = windowManager.getDefaultDisplay();
637        int system_width = display.getWidth();
638        WnnWord wnnWordGet = new WnnWord();
639        mEvent = new OpenWnnEvent(OpenWnnEvent.GET_WORD, wnnWordGet);
640
641        int i;
642        for (i = position; i < max; i++) {
643            if (!sendEventToIME(mEvent)) {
644                ret =  false;
645                break;
646            }
647            wnnWordGet = mEvent.word;
648            int len_stroke = wnnWordGet.stroke.length();
649            int len_candidate = wnnWordGet.candidate.length();
650            if (len_stroke == 0 || len_candidate == 0) {
651                ret =  false;
652                break;
653            }
654
655            mWordCount++;
656            TableRow row = new TableRow(self);
657            UserDictionaryToolsListFocus stroke = new UserDictionaryToolsListFocus(self);
658            stroke.setId(mWordCount);
659            stroke.setText(wnnWordGet.stroke);
660            stroke.setWidth(system_width/2);
661            stroke.setTextSize(WORD_TEXT_SIZE);
662            stroke.setTextColor(Color.LTGRAY);
663            stroke.setBackgroundColor(UNFOCUS_BACKGROUND_COLOR);
664            stroke.setSingleLine();
665            stroke.setPadding(1,0,1,1);
666            stroke.setEllipsize(TextUtils.TruncateAt.END);
667            stroke.setClickable(true);
668            stroke.setFocusable(true);
669            stroke.setFocusableInTouchMode(true);
670            stroke.setOnTouchListener(self);
671            stroke.setOnFocusChangeListener(self);
672
673            UserDictionaryToolsListFocus candidate = new UserDictionaryToolsListFocus(self);
674            candidate.setId(mWordCount+MAX_WORD_COUNT);
675            candidate.setText(wnnWordGet.candidate);
676            candidate.setWidth(system_width/2);
677            candidate.setTextSize(WORD_TEXT_SIZE);
678            candidate.setTextColor(Color.LTGRAY);
679            candidate.setBackgroundColor(UNFOCUS_BACKGROUND_COLOR);
680            candidate.setSingleLine();
681            candidate.setPadding(1,0,1,1);
682            candidate.setEllipsize(TextUtils.TruncateAt.END);
683            candidate.setClickable(true);
684            candidate.setFocusable(true);
685            candidate.setFocusableInTouchMode(true);
686            candidate.setOnTouchListener(self);
687            candidate.setOnFocusChangeListener(self);
688
689            stroke.setPairView(candidate);
690            candidate.setPairView(stroke);
691
692            row.addView(stroke);
693            row.addView(candidate);
694            mTableLayout.addView(row, tableCreateParam(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
695        }
696        mTableLayout.requestLayout();
697        return ret;
698    }
699}
700