OpenWnnEN.java revision 3feb3d26d59be1626c59b72654020179e2b2ebe0
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 jp.co.omronsoft.openwnn.EN.*;
20import android.content.SharedPreferences;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.inputmethodservice.InputMethodService;
24import android.os.Bundle;
25import android.os.Handler;
26import android.preference.PreferenceManager;
27import android.text.SpannableStringBuilder;
28import android.text.Spanned;
29import android.text.method.MetaKeyKeyListener;
30import android.text.style.BackgroundColorSpan;
31import android.text.style.CharacterStyle;
32import android.text.style.UnderlineSpan;
33import android.util.Log;
34import android.view.KeyCharacterMap;
35import android.view.KeyEvent;
36import android.view.View;
37import android.view.inputmethod.EditorInfo;
38
39/**
40 * The OpenWnn English IME class.
41 *
42 * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
43 */
44public class OpenWnnEN extends OpenWnn {
45	/** A space character */
46	private static final char[] SPACE = {' '};
47
48	/** Character style of underline */
49	private static final CharacterStyle SPAN_UNDERLINE   = new UnderlineSpan();
50    /** Highlight color style for the selected string  */
51    private static final CharacterStyle SPAN_EXACT_BGCOLOR_HL     = new BackgroundColorSpan(0xFF66CDAA);
52    /** Highlight color style for the composing text */
53    private static final CharacterStyle SPAN_REMAIN_BGCOLOR_HL    = new BackgroundColorSpan(0xFFF0FFFF);
54
55	/** A private area code(ALT+SHIFT+X) to be ignore (G1 specific). */
56	private static final int PRIVATE_AREA_CODE = 61184;
57	/** Never move cursor in to the composing text (adapting to IMF's specification change) */
58    private static final boolean FIX_CURSOR_TEXT_END = true;
59
60	/** Whether using Emoji or not */
61    private static final boolean ENABLE_EMOJI_LIMITATION = true;
62
63	/** Spannable string for the composing text */
64	protected SpannableStringBuilder mDisplayText;
65
66	/** Handler for drawing the candidates view */
67	private Handler mDelayUpdateHandler;
68	/** Characters treated as a separator */
69	private String mWordSeparators;
70	/** Previous event's code */
71	private int mPreviousEventCode;
72
73	/** Array of words from the user dictionary */
74	private WnnWord[] mUserDictionaryWords = null;
75
76	/** The converter for English prediction/spell correction */
77	private OpenWnnEngineEN mConverterEN;
78	/** The symbol list generator */
79	private SymbolList mSymbolList;
80	/** Whether it is displaying symbol list */
81	private boolean mSymbolMode;
82	/** Whether prediction is enabled */
83	private boolean mOptPrediction;
84	/** Whether spell correction is enabled */
85	private boolean mOptSpellCorrection;
86	/** Whether learning is enabled */
87	private boolean mOptLearning;
88
89	/** SHIFT key state */
90	private int mHardShift;
91    /** SHIFT key state (pressing) */
92	private boolean mShiftPressing;
93	/** ALT key state */
94	private int mHardAlt;
95    /** ALT key state (pressing) */
96	private boolean mAltPressing;
97
98	/** Instance of this service */
99	private static OpenWnnEN mSelf = null;
100
101	/** Shift lock toggle definition */
102	private static final int[] mShiftKeyToggle = {0, MetaKeyKeyListener.META_SHIFT_ON, MetaKeyKeyListener.META_CAP_LOCKED};
103	/** Alt lock toggle definition */
104	private static final int[] mAltKeyToggle = {0, MetaKeyKeyListener.META_ALT_ON, MetaKeyKeyListener.META_ALT_LOCKED};
105	/** Auto caps mode */
106	private boolean mAutoCaps = false;
107
108	private CandidateFilter mFilter;
109
110    /**
111     * Constructor
112     */
113	public OpenWnnEN() {
114		super();
115        mSelf = this;
116
117		/* used by OpenWnn */
118		mComposingText = new ComposingText();
119		mCandidatesViewManager = new TextCandidatesViewManager(-1);
120		mInputViewManager = new DefaultSoftKeyboardEN();
121		mConverterEN = new OpenWnnEngineEN("/data/data/jp.co.omronsoft.openwnn/writableEN.dic");
122		mConverter = mConverterEN;
123		mFilter = new CandidateFilter();
124		mSymbolList = null;
125
126		/* etc */
127		mDisplayText = new SpannableStringBuilder();
128		mAutoHideMode = false;
129		mDelayUpdateHandler = new Handler();
130		mSymbolMode = false;
131		mOptPrediction = true;
132		mOptSpellCorrection = true;
133		mOptLearning = true;
134	}
135
136    /**
137     * Constructor
138     *
139     * @param context       The context
140     */
141    public OpenWnnEN(Context context) {
142        this();
143        attachBaseContext(context);
144    }
145	/**
146	 * Get the instance of this service.
147	 * <br>
148	 * Before using this method, the constructor of this service must be invoked.
149	 *
150	 * @return		The instance of this object
151	 */
152	public static OpenWnnEN getInstance() {
153		return mSelf;
154	}
155
156	/**
157	 * Insert a character into the composing text.
158	 *
159	 * @param chars		A array of character
160	 */
161	private void insertCharToComposingText(char[] chars) {
162		StrSegment seg = new StrSegment(chars);
163
164		if (chars[0] == SPACE[0] || chars[0] == '\u0009') {
165			/* if the character is a space, commit the composing text */
166			commitText(1);
167			commitText(seg.string);
168			mComposingText.clear();
169		} else if (mWordSeparators.contains(seg.string)) {
170			/* if the character is a separator, remove an auto-inserted space and commit the composing text. */
171			if (mPreviousEventCode == OpenWnnEvent.SELECT_CANDIDATE) {
172				mInputConnection.deleteSurroundingText(1, 0);
173			}
174			commitText(1);
175			commitText(seg.string);
176			mComposingText.clear();
177		} else {
178			mComposingText.insertStrSegment(0, 1, seg);
179			updateComposingText(1);
180		}
181	}
182
183	/**
184	 * Insert a character into the composing text.
185	 *
186	 * @param charCode		A character code
187     * @return				{@code true} if success; {@code false} if an error occurs.
188	 */
189	private boolean insertCharToComposingText(int charCode) {
190        if (charCode == 0) {
191            return false;
192        }
193        insertCharToComposingText(Character.toChars(charCode));
194        return true;
195    }
196
197	/**
198	 * Get the shift key state from the editor.
199	 *
200	 * @param editor	Editor
201	 *
202	 * @return			State ID of the shift key (0:off, 1:on)
203	 */
204	protected int getShiftKeyState(EditorInfo editor) {
205		return (getCurrentInputConnection().getCursorCapsMode(editor.inputType) == 0) ? 0 : 1;
206	}
207
208    /**
209     * Set the mode of the symbol list.
210     *
211     * @param mode 		{@code SymbolList.SYMBOL_ENGLISH} or {@code null}.
212     */
213	private void setSymbolMode(String mode) {
214		if (mode != null) {
215			mDelayUpdateHandler.removeCallbacks(updatePredictionRunnable);
216			mSymbolMode = true;
217			mSymbolList.setDictionary(mode);
218			mConverter = mSymbolList;
219		} else {
220			if (!mSymbolMode) {
221				return;
222			}
223			mDelayUpdateHandler.removeCallbacks(updatePredictionRunnable);
224			mSymbolMode = false;
225			mConverter = mConverterEN;
226		}
227	}
228
229	/***********************************************************************
230	 * InputMethodServer
231	 ***********************************************************************/
232    /** @see jp.co.omronsoft.openwnn.OpenWnn#onCreate */
233	@Override public void onCreate() {
234		super.onCreate();
235		mWordSeparators = getResources().getString(R.string.en_word_separators);
236
237		if (mSymbolList == null) {
238			mSymbolList = new SymbolList(this, SymbolList.LANG_EN);
239		}
240	}
241
242    /** @see jp.co.omronsoft.openwnn.OpenWnn#onCreateInputView */
243    @Override public View onCreateInputView() {
244    	int hiddenState = getResources().getConfiguration().hardKeyboardHidden;
245    	boolean hidden = (hiddenState == Configuration.HARDKEYBOARDHIDDEN_YES);
246    	((DefaultSoftKeyboardEN) mInputViewManager).setHardKeyboardHidden(hidden);
247    	((TextCandidatesViewManager)
248    			mCandidatesViewManager).setHardKeyboardHidden(hidden);
249
250        return super.onCreateInputView();
251    }
252
253    /** @see jp.co.omronsoft.openwnn.OpenWnn#onStartInputView */
254	@Override public void onStartInputView(EditorInfo attribute, boolean restarting) {
255		super.onStartInputView(attribute, restarting);
256
257		/* initialize views */
258		mCandidatesViewManager.clearCandidates();
259
260		mHardShift = 0;
261		mHardAlt   = 0;
262        updateMetaKeyStateDisplay();
263
264        /* load preferences */
265		SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
266
267		/* auto caps mode */
268		mAutoCaps = pref.getBoolean("auto_caps", true);
269
270		/* set TextCandidatesViewManager's option */
271		((TextCandidatesViewManager)mCandidatesViewManager).setAutoHide(false);
272
273
274		/* display status icon */
275 		showStatusIcon(R.drawable.immodeic_half_alphabet);
276
277		/* set prediction & spell correction mode */
278		mOptPrediction      = pref.getBoolean("opt_en_prediction", true);
279		mOptSpellCorrection = pref.getBoolean("opt_en_spell_correction", true);
280		mOptLearning        = pref.getBoolean("opt_en_enable_learning", true);
281
282		/* prediction on/off */
283        switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
284        case EditorInfo.TYPE_CLASS_NUMBER:
285        case EditorInfo.TYPE_CLASS_DATETIME:
286        case EditorInfo.TYPE_CLASS_PHONE:
287            mOptPrediction = false;
288    		mOptLearning = false;
289            break;
290
291        case EditorInfo.TYPE_CLASS_TEXT:
292        	switch (attribute.inputType & EditorInfo.TYPE_MASK_VARIATION) {
293        	case EditorInfo.TYPE_TEXT_VARIATION_PASSWORD:
294            case EditorInfo.TYPE_TEXT_VARIATION_PHONETIC:
295            	mOptLearning = false;
296        		mOptPrediction = false;
297        		break;
298        	default:
299        		break;
300        	}
301        }
302
303        /* set engine's mode */
304        if (mOptSpellCorrection) {
305        	mConverterEN.setDictionary(OpenWnnEngineEN.DICT_FOR_CORRECT_MISTYPE);
306        } else {
307        	mConverterEN.setDictionary(OpenWnnEngineEN.DICT_DEFAULT);
308        }
309        /* emoji */
310        if (ENABLE_EMOJI_LIMITATION) {
311            Bundle bundle = attribute.extras;
312            if (bundle != null && bundle.getBoolean("allowEmoji")) {
313            	mConverterEN.setFilter(null);
314            } else {
315            	mFilter.setFilter(CandidateFilter.FILTER_EMOJI);
316            	mConverterEN.setFilter(mFilter);
317            }
318        } else {
319        	mConverterEN.setFilter(null);
320        }
321
322        /* doesn't learn any word if it is not prediction mode */
323        if (!mOptPrediction) {
324        	mOptLearning = false;
325        }
326
327        if (mComposingText != null) {
328        	mComposingText.clear();
329        }
330        /* initialize the engine's state */
331        fitInputType(pref, attribute);
332	}
333
334    /** @see jp.co.omronsoft.openwnn.OpenWnn#onComputeInsets */
335	@Override public void onComputeInsets(InputMethodService.Insets outInsets) {
336		if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
337			outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_FRAME;
338		} else {
339			super.onComputeInsets(outInsets);
340		}
341	}
342
343    /** @see jp.co.omronsoft.openwnn.OpenWnn#isFullscreenMode */
344	@Override public boolean isFullscreenMode() {
345		boolean ret;
346		if (mInputViewManager == null) {
347			ret = (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL);
348		} else {
349			ret = false;
350		}
351		return ret;
352	}
353
354    /** @see jp.co.omronsoft.openwnn.OpenWnn#onUpdateSelection */
355	@Override public void onUpdateSelection(int oldSelStart, int oldSelEnd,
356			int newSelStart, int newSelEnd, int candidatesStart,
357			int candidatesEnd) {
358		if (mComposingText.size(1) != 0) {
359			updateComposingText(1);
360		}
361	}
362
363    /** @see jp.co.omronsoft.openwnn.OpenWnn#onConfigurationChanged */
364	@Override public void onConfigurationChanged(Configuration newConfig) {
365		try {
366			super.onConfigurationChanged(newConfig);
367			if (mInputConnection != null) {
368				updateComposingText(1);
369			}
370		} catch (Exception ex) {
371		}
372	}
373
374    /** @see jp.co.omronsoft.openwnn.OpenWnn#onEvaluateFullscreenMode */
375	@Override public boolean onEvaluateFullscreenMode() {
376		return false;
377	}
378
379    /** @see jp.co.omronsoft.openwnn.OpenWnn#onEvaluateInputViewShown */
380	@Override public boolean onEvaluateInputViewShown() {
381		return true;
382	}
383
384	/***********************************************************************
385	 * OpenWnn
386	 ***********************************************************************/
387    /** @see jp.co.omronsoft.openwnn.OpenWnn#onEvent */
388	@Override synchronized public boolean onEvent(OpenWnnEvent ev) {
389        /* handling events which are valid when InputConnection is not active. */
390        switch (ev.code) {
391
392        case OpenWnnEvent.KEYUP:
393            onKeyUpEvent(ev.keyEvent);
394            return true;
395
396        case OpenWnnEvent.INITIALIZE_LEARNING_DICTIONARY:
397            return mConverterEN.initializeDictionary( WnnEngine.DICTIONARY_TYPE_LEARN );
398
399        case OpenWnnEvent.INITIALIZE_USER_DICTIONARY:
400            return mConverterEN.initializeDictionary( WnnEngine.DICTIONARY_TYPE_USER );
401
402        case OpenWnnEvent.LIST_WORDS_IN_USER_DICTIONARY:
403            mUserDictionaryWords = mConverterEN.getUserDictionaryWords( );
404            return true;
405
406		case OpenWnnEvent.GET_WORD:
407			if( mUserDictionaryWords != null ) {
408				ev.word = mUserDictionaryWords[ 0 ];
409				for( int i = 0 ; i < mUserDictionaryWords.length-1 ; i++ ) {
410					mUserDictionaryWords[ i ] = mUserDictionaryWords[ i + 1 ];
411				}
412				mUserDictionaryWords[ mUserDictionaryWords.length-1 ] = null;
413				if( mUserDictionaryWords[ 0 ] == null ) {
414					mUserDictionaryWords = null;
415				}
416				return true;
417			}
418            break;
419
420		case OpenWnnEvent.ADD_WORD:
421			mConverterEN.addWord(ev.word);
422			return true;
423
424		case OpenWnnEvent.DELETE_WORD:
425			mConverterEN.deleteWord(ev.word);
426			return true;
427
428		case OpenWnnEvent.CHANGE_MODE:
429            return false;
430
431        case OpenWnnEvent.CHANGE_INPUT_VIEW:
432        	setInputView(onCreateInputView());
433            return true;
434
435        case OpenWnnEvent.CANDIDATE_VIEW_TOUCH:
436            boolean ret;
437                ret = ((TextCandidatesViewManager)mCandidatesViewManager).onTouchSync();
438            return ret;
439
440        default:
441            break;
442		}
443
444		dismissPopupKeyboard();
445        KeyEvent keyEvent = ev.keyEvent;
446        int keyCode = 0;
447        if (keyEvent != null) {
448            keyCode = keyEvent.getKeyCode();
449        }
450		if (mDirectInputMode) {
451            if (ev.code == OpenWnnEvent.INPUT_SOFT_KEY && mInputConnection != null) {
452                mInputConnection.sendKeyEvent(keyEvent);
453				mInputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
454                                                           keyEvent.getKeyCode()));
455            }
456			return false;
457		}
458
459		if (ev.code == OpenWnnEvent.LIST_CANDIDATES_FULL) {
460			mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_FULL);
461			return true;
462		}
463
464		boolean ret = false;
465		switch (ev.code) {
466		case OpenWnnEvent.INPUT_CHAR:
467			EditorInfo edit = getCurrentInputEditorInfo();
468			if( edit.inputType == EditorInfo.TYPE_CLASS_PHONE){
469				commitText(new String(ev.chars));
470			}else{
471				setSymbolMode(null);
472				insertCharToComposingText(ev.chars);
473				ret = true;
474				mPreviousEventCode = ev.code;
475			}
476			break;
477
478		case OpenWnnEvent.INPUT_KEY:
479			keyCode = ev.keyEvent.getKeyCode();
480			/* update shift/alt state */
481			switch (keyCode) {
482			case KeyEvent.KEYCODE_ALT_LEFT:
483			case KeyEvent.KEYCODE_ALT_RIGHT:
484				if (ev.keyEvent.getRepeatCount() == 0) {
485					if (++mHardAlt > 2) { mHardAlt = 0; }
486				}
487                mAltPressing   = true;
488				updateMetaKeyStateDisplay();
489				return true;
490
491			case KeyEvent.KEYCODE_SHIFT_LEFT:
492			case KeyEvent.KEYCODE_SHIFT_RIGHT:
493				if (ev.keyEvent.getRepeatCount() == 0) {
494					if (++mHardShift > 2) { mHardShift = 0; }
495				}
496                mShiftPressing = true;
497				updateMetaKeyStateDisplay();
498				return true;
499			}
500			setSymbolMode(null);
501			updateComposingText(1);
502			/* handle other key event */
503			ret = processKeyEvent(ev.keyEvent);
504			mPreviousEventCode = ev.code;
505			break;
506
507		case OpenWnnEvent.INPUT_SOFT_KEY:
508			setSymbolMode(null);
509			updateComposingText(1);
510			ret = processKeyEvent(ev.keyEvent);
511			if (!ret) {
512				mInputConnection.sendKeyEvent(ev.keyEvent);
513				mInputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, ev.keyEvent.getKeyCode()));
514                ret = true;
515			}
516			mPreviousEventCode = ev.code;
517			break;
518
519		case OpenWnnEvent.SELECT_CANDIDATE:
520            if (mSymbolMode) {
521                commitText(ev.word, false);
522            } else {
523                if (mWordSeparators.contains(ev.word.candidate) &&
524                    mPreviousEventCode == OpenWnnEvent.SELECT_CANDIDATE) {
525                    mInputConnection.deleteSurroundingText(1, 0);
526                }
527                commitText(ev.word, true);
528            }
529            mComposingText.clear();
530            mPreviousEventCode = ev.code;
531            updateComposingText(1);
532            break;
533
534		case OpenWnnEvent.LIST_SYMBOLS:
535			commitText(1);
536			mComposingText.clear();
537			setSymbolMode(SymbolList.SYMBOL_ENGLISH);
538			updateComposingText(1);
539			break;
540
541		default:
542			break;
543		}
544
545		if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
546			mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
547		}
548
549		return ret;
550	}
551
552	/***********************************************************************
553	 * OpenWnnEN
554	 ***********************************************************************/
555	/**
556	 * Handling KeyEvent
557	 * <br>
558	 * This method is called from {@link #onEvent()}.
559	 *
560	 * @param ev   A key event
561	 * @return		{@code true} if the event is processed in this method; {@code false} if the event is not processed in this method
562	 */
563	private boolean processKeyEvent(KeyEvent ev) {
564
565		int key = ev.getKeyCode();
566		EditorInfo edit = getCurrentInputEditorInfo();
567		/* keys which produce a glyph */
568		if (ev.isPrintingKey()) {
569			/* do nothing if the character is not able to display or the character is dead key */
570			if ((mHardShift > 0 && mHardAlt > 0) || (ev.isAltPressed() && ev.isShiftPressed())) {
571				int charCode = ev.getUnicodeChar(MetaKeyKeyListener.META_SHIFT_ON | MetaKeyKeyListener.META_ALT_ON);
572				if (charCode == 0 || (charCode & KeyCharacterMap.COMBINING_ACCENT) != 0 || charCode == PRIVATE_AREA_CODE) {
573                    if(mHardShift == 1){
574                        mShiftPressing = false;
575                    }
576                    if(mHardAlt == 1){
577                        mAltPressing   = false;
578                    }
579	            	if(!ev.isAltPressed()){
580	            		if (mHardAlt == 1) {
581	            			mHardAlt = 0;
582	            		}
583	            	}
584	            	if(!ev.isShiftPressed()){
585	            		if (mHardShift == 1) {
586	            			mHardShift = 0;
587	            		}
588	            	}
589	            	if(!ev.isShiftPressed() && !ev.isAltPressed()){
590	                    updateMetaKeyStateDisplay();
591	            	}
592					return true;
593				}
594			}
595
596            /* get the key character */
597			if (mHardShift== 0  && mHardAlt == 0) {
598                /* no meta key is locked */
599				int shift = (mAutoCaps) ? getShiftKeyState(edit) : 0;
600				if (shift != mHardShift && (key >= KeyEvent.KEYCODE_A && key <= KeyEvent.KEYCODE_Z)) {
601                    /* handling auto caps for a alphabet character */
602                    insertCharToComposingText(ev.getUnicodeChar(MetaKeyKeyListener.META_SHIFT_ON));
603                } else {
604                    insertCharToComposingText(ev.getUnicodeChar());
605                }
606			} else {
607                insertCharToComposingText(ev.getUnicodeChar(mShiftKeyToggle[mHardShift]
608                                                            | mAltKeyToggle[mHardAlt]));
609                if(mHardShift == 1){
610                    mShiftPressing = false;
611                }
612                if(mHardAlt == 1){
613                    mAltPressing   = false;
614                }
615                /* back to 0 (off) if 1 (on/not locked) */
616            	if(!ev.isAltPressed()){
617            		if (mHardAlt == 1) {
618            			mHardAlt = 0;
619            		}
620            	}
621            	if(!ev.isShiftPressed()){
622            		if (mHardShift == 1) {
623            			mHardShift = 0;
624            		}
625            	}
626            	if(!ev.isShiftPressed() && !ev.isAltPressed()){
627                    updateMetaKeyStateDisplay();
628            	}
629			}
630
631            if (edit.inputType == EditorInfo.TYPE_CLASS_PHONE) {
632				commitText(1);
633				mComposingText.clear();
634				return true;
635			}
636			return true;
637
638		} else if (key == KeyEvent.KEYCODE_SPACE) {
639			if (ev.isAltPressed()) {
640                /* display the symbol list (G1 specific. same as KEYCODE_SYM) */
641				commitText(1);
642				mComposingText.clear();
643				setSymbolMode(SymbolList.SYMBOL_ENGLISH);
644				updateComposingText(1);
645				mHardAlt = 0;
646                updateMetaKeyStateDisplay();
647			} else {
648				insertCharToComposingText(SPACE);
649			}
650			return true;
651        } else if (key == KeyEvent.KEYCODE_SYM) {
652            /* display the symbol list */
653            commitText(1);
654            mComposingText.clear();
655            setSymbolMode(SymbolList.SYMBOL_ENGLISH);
656            updateComposingText(1);
657            mHardAlt = 0;
658            updateMetaKeyStateDisplay();
659		}
660
661
662		/* Functional key */
663		if (mComposingText.size(1) > 0) {
664			switch (key) {
665			case KeyEvent.KEYCODE_DEL:
666				mComposingText.delete(1, false);
667				updateComposingText(1);
668				return true;
669
670			case KeyEvent.KEYCODE_BACK:
671				if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
672					mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
673				} else {
674					mComposingText.clear();
675					updateComposingText(1);
676				}
677				return true;
678
679			case KeyEvent.KEYCODE_DPAD_LEFT:
680				mComposingText.moveCursor(1, -1);
681				updateComposingText(1);
682				return true;
683
684			case KeyEvent.KEYCODE_DPAD_RIGHT:
685				mComposingText.moveCursor(1, 1);
686				updateComposingText(1);
687				return true;
688
689			case KeyEvent.KEYCODE_ENTER:
690			case KeyEvent.KEYCODE_DPAD_CENTER:
691				commitText(1);
692				mComposingText.clear();
693				return true;
694
695			default:
696				break;
697			}
698		}
699
700		return false;
701	}
702
703	/**
704	 * Runnable for a thread getting and displaying candidates.
705	 */
706	private final Runnable updatePredictionRunnable = new Runnable() {
707		public void run() {
708			int candidates = 0;
709			if (mConverter != null) {
710				/* normal prediction */
711				candidates = mConverter.predict(mComposingText, 0, -1);
712			}
713			/* update the candidates view */
714			if (candidates > 0) {
715				mCandidatesViewManager.displayCandidates(mConverter);
716			} else {
717				mCandidatesViewManager.clearCandidates();
718			}
719		}
720	};
721
722	/**
723	 * Update the composing text.
724	 *
725	 * @param layer  {@link mComposingText}'s layer to display
726	 */
727	private void updateComposingText(int layer) {
728		/* update the candidates view */
729		if (!mOptPrediction) {
730			commitText(1);
731			mComposingText.clear();
732            if (mSymbolMode) {
733				mDelayUpdateHandler.removeCallbacks(updatePredictionRunnable);
734				mDelayUpdateHandler.postDelayed(updatePredictionRunnable, 0);
735            }
736		} else {
737			if (mComposingText.size(1) != 0) {
738				mDelayUpdateHandler.removeCallbacks(updatePredictionRunnable);
739				mDelayUpdateHandler.postDelayed(updatePredictionRunnable, 250);
740			} else {
741				mDelayUpdateHandler.removeCallbacks(updatePredictionRunnable);
742				mDelayUpdateHandler.postDelayed(updatePredictionRunnable, 0);
743			}
744
745			/* notice to the input view */
746			this.mInputViewManager.onUpdateState(this);
747
748			/* set the candidates view to the normal size */
749			if (mCandidatesViewManager.getViewType() != CandidatesViewManager.VIEW_TYPE_NORMAL) {
750				mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
751			}
752			/* set the text for displaying as the composing text */
753			SpannableStringBuilder disp = mDisplayText;
754			disp.clear();
755			disp.insert(0, mComposingText.toString(layer));
756
757			/* add decoration to the text */
758			int cursor = mComposingText.getCursor(layer);
759			if (disp.length() != 0) {
760				if (cursor > 0 && cursor < disp.length()) {
761					disp.setSpan(SPAN_EXACT_BGCOLOR_HL, 0, cursor,
762							Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
763				}
764				if (cursor < disp.length()) {
765                    mDisplayText.setSpan(SPAN_REMAIN_BGCOLOR_HL, cursor, disp.length(),
766                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
767				}
768
769				disp.setSpan(SPAN_UNDERLINE, 0, disp.length(),
770						Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
771			}
772
773            int displayCursor = cursor;
774            if (FIX_CURSOR_TEXT_END) {
775                displayCursor = (cursor == 0) ?  0 : 1;
776            }
777			/* update the composing text on the EditView */
778			mInputConnection.setComposingText(disp, displayCursor);
779		}
780	}
781
782	/**
783	 * Commit the composing text.
784	 *
785	 * @param layer  {@link mComposingText}'s layer to commit.
786	 */
787	private void commitText(int layer) {
788		String tmp = mComposingText.toString(layer);
789
790		if (mOptLearning && mConverter != null && tmp.length() > 0) {
791			WnnWord word = new WnnWord(tmp, tmp);
792			mConverter.learn(word);
793		}
794
795        mInputConnection.commitText(tmp, (FIX_CURSOR_TEXT_END ? 1 : tmp.length()));
796		mCandidatesViewManager.clearCandidates();
797	}
798
799	/**
800	 * Commit a word
801	 *
802	 * @param word 		 	A word to commit
803	 * @param withSpace		Append a space after the word if {@code true}.
804	 */
805	private void commitText(WnnWord word, boolean withSpace) {
806
807		if (mOptLearning && mConverter != null) {
808			mConverter.learn(word);
809		}
810
811        mInputConnection.commitText(word.candidate, (FIX_CURSOR_TEXT_END ? 1 : word.candidate.length()));
812
813		if (withSpace) {
814			commitText(" ");
815		}
816	}
817
818	/**
819	 * Commit a string
820     * <br>
821     * The string is not registered into the learning dictionary.
822	 *
823	 * @param str  A string to commit
824	 */
825	private void commitText(String str) {
826        mInputConnection.commitText(str, (FIX_CURSOR_TEXT_END ? 1 : str.length()));
827		mCandidatesViewManager.clearCandidates();
828	}
829
830	/**
831	 * Dismiss the pop-up keyboard
832	 */
833	protected void dismissPopupKeyboard() {
834		DefaultSoftKeyboardEN kbd = (DefaultSoftKeyboardEN)mInputViewManager;
835		if (kbd != null) {
836			kbd.dismissPopupKeyboard();
837		}
838	}
839
840	/**
841	 * Display current meta-key state.
842	 */
843	private void updateMetaKeyStateDisplay() {
844        int mode = 0;
845        if(mHardShift == 0 && mHardAlt == 0){
846            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_OFF;
847        }else if(mHardShift == 1 && mHardAlt == 0){
848            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_OFF;
849        }else if(mHardShift == 2  && mHardAlt == 0){
850            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_OFF;
851        }else if(mHardShift == 0 && mHardAlt == 1){
852            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_ON;
853        }else if(mHardShift == 0 && mHardAlt == 2){
854            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_LOCK;
855        }else if(mHardShift == 1 && mHardAlt == 1){
856            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_ON;
857        }else if(mHardShift == 1 && mHardAlt == 2){
858            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_LOCK;
859        }else if(mHardShift == 2 && mHardAlt == 1){
860            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_ON;
861        }else if(mHardShift == 2 && mHardAlt == 2){
862            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_LOCK;
863        }else{
864            mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_OFF;
865        }
866
867        ((DefaultSoftKeyboard) mInputViewManager).updateIndicator(mode);
868	}
869
870	/**
871	 * Handling KeyEvent(KEYUP)
872	 * <br>
873	 * This method is called from {@link #onEvent()}.
874	 *
875	 * @param ev   An up key event
876	 */
877    private void onKeyUpEvent(KeyEvent ev) {
878        int key = ev.getKeyCode();
879        if(!mShiftPressing){
880            if(key == KeyEvent.KEYCODE_SHIFT_LEFT || key == KeyEvent.KEYCODE_SHIFT_RIGHT){
881                mHardShift = 0;
882                mShiftPressing = true;
883                updateMetaKeyStateDisplay();
884            }
885        }
886        if(!mAltPressing ){
887            if(key == KeyEvent.KEYCODE_ALT_LEFT || key == KeyEvent.KEYCODE_ALT_RIGHT){
888                mHardAlt = 0;
889                mAltPressing   = true;
890                updateMetaKeyStateDisplay();
891            }
892        }
893    }
894    /**
895     * Fits an editor info.
896     *
897     * @param preferences  The preference data.
898     * @param info  		The editor info.
899     */
900    private void fitInputType(SharedPreferences preference, EditorInfo info) {
901        if (info.inputType == EditorInfo.TYPE_NULL) {
902            mDirectInputMode = true;
903            return;
904        }
905    }
906}
907
908
909
910
911
912
913
914
915