diff options
Diffstat (limited to 'PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java')
-rw-r--r-- | PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java b/PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java new file mode 100644 index 0000000..2294860 --- /dev/null +++ b/PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.pinyin; + +import android.content.Context; +import android.content.res.Resources; +import android.inputmethodservice.InputMethodService; +import android.os.Handler; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; +import android.widget.ViewFlipper; + +/** + * The top container to host soft keyboard view(s). + */ +public class SkbContainer extends RelativeLayout implements OnTouchListener { + /** + * For finger touch, user tends to press the bottom part of the target key, + * or he/she even presses the area out of it, so it is necessary to make a + * simple bias correction. If the input method runs on emulator, no bias + * correction will be used. + */ + private static final int Y_BIAS_CORRECTION = -10; + + /** + * Used to skip these move events whose position is too close to the + * previous touch events. + */ + private static final int MOVE_TOLERANCE = 6; + + /** + * If this member is true, PopupWindow is used to show on-key highlight + * effect. + */ + private static boolean POPUPWINDOW_FOR_PRESSED_UI = false; + + /** + * The current soft keyboard layout. + * + * @see com.android.inputmethod.pinyin.InputModeSwitcher for detailed layout + * definitions. + */ + private int mSkbLayout = 0; + + /** + * The input method service. + */ + private InputMethodService mService; + + /** + * Input mode switcher used to switch between different modes like Chinese, + * English, etc. + */ + private InputModeSwitcher mInputModeSwitcher; + + /** + * The gesture detector. + */ + private GestureDetector mGestureDetector; + + private Environment mEnvironment; + + private ViewFlipper mSkbFlipper; + + /** + * The popup balloon hint for key press/release. + */ + private BalloonHint mBalloonPopup; + + /** + * The on-key balloon hint for key press/release. + */ + private BalloonHint mBalloonOnKey = null; + + /** The major sub soft keyboard. */ + private SoftKeyboardView mMajorView; + + /** + * The last parameter when function {@link #toggleCandidateMode(boolean)} + * was called. + */ + private boolean mLastCandidatesShowing; + + /** Used to indicate whether a popup soft keyboard is shown. */ + private boolean mPopupSkbShow = false; + + /** + * Used to indicate whether a popup soft keyboard is just shown, and waits + * for the touch event to release. After the release, the popup window can + * response to touch events. + **/ + private boolean mPopupSkbNoResponse = false; + + /** Popup sub keyboard. */ + private PopupWindow mPopupSkb; + + /** The view of the popup sub soft keyboard. */ + private SoftKeyboardView mPopupSkbView; + + private int mPopupX; + + private int mPopupY; + + /** + * When user presses a key, a timer is started, when it times out, it is + * necessary to detect whether user still holds the key. + */ + private volatile boolean mWaitForTouchUp = false; + + /** + * When user drags on the soft keyboard and the distance is enough, this + * drag will be recognized as a gesture and a gesture-based action will be + * taken, in this situation, ignore the consequent events. + */ + private volatile boolean mDiscardEvent = false; + + /** + * For finger touch, user tends to press the bottom part of the target key, + * or he/she even presses the area out of it, so it is necessary to make a + * simple bias correction in Y. + */ + private int mYBiasCorrection = 0; + + /** + * The x coordination of the last touch event. + */ + private int mXLast; + + /** + * The y coordination of the last touch event. + */ + private int mYLast; + + /** + * The soft keyboard view. + */ + private SoftKeyboardView mSkv; + + /** + * The position of the soft keyboard view in the container. + */ + private int mSkvPosInContainer[] = new int[2]; + + /** + * The key pressed by user. + */ + private SoftKey mSoftKeyDown = null; + + /** + * Used to timeout a press if user holds the key for a long time. + */ + private LongPressTimer mLongPressTimer; + + /** + * For temporary use. + */ + private int mXyPosTmp[] = new int[2]; + + public SkbContainer(Context context, AttributeSet attrs) { + super(context, attrs); + + mEnvironment = Environment.getInstance(); + + mLongPressTimer = new LongPressTimer(this); + + // If it runs on an emulator, no bias correction + if ("1".equals(SystemProperties.get("ro.kernel.qemu"))) { + mYBiasCorrection = 0; + } else { + mYBiasCorrection = Y_BIAS_CORRECTION; + } + mBalloonPopup = new BalloonHint(context, this, MeasureSpec.AT_MOST); + if (POPUPWINDOW_FOR_PRESSED_UI) { + mBalloonOnKey = new BalloonHint(context, this, MeasureSpec.AT_MOST); + } + + mPopupSkb = new PopupWindow(mContext); + mPopupSkb.setBackgroundDrawable(null); + mPopupSkb.setClippingEnabled(false); + } + + public void setService(InputMethodService service) { + mService = service; + } + + public void setInputModeSwitcher(InputModeSwitcher inputModeSwitcher) { + mInputModeSwitcher = inputModeSwitcher; + } + + public void setGestureDetector(GestureDetector gestureDetector) { + mGestureDetector = gestureDetector; + } + + public boolean isCurrentSkbSticky() { + if (null == mMajorView) return true; + SoftKeyboard skb = mMajorView.getSoftKeyboard(); + if (null != skb) { + return skb.getStickyFlag(); + } + return true; + } + + public void toggleCandidateMode(boolean candidatesShowing) { + if (null == mMajorView || !mInputModeSwitcher.isChineseText() + || mLastCandidatesShowing == candidatesShowing) return; + mLastCandidatesShowing = candidatesShowing; + + SoftKeyboard skb = mMajorView.getSoftKeyboard(); + if (null == skb) return; + + int state = mInputModeSwitcher.getTooggleStateForCnCand(); + if (!candidatesShowing) { + skb.disableToggleState(state, false); + skb.enableToggleStates(mInputModeSwitcher.getToggleStates()); + } else { + skb.enableToggleState(state, false); + } + + mMajorView.invalidate(); + } + + public void updateInputMode() { + int skbLayout = mInputModeSwitcher.getSkbLayout(); + if (mSkbLayout != skbLayout) { + mSkbLayout = skbLayout; + updateSkbLayout(); + } + + mLastCandidatesShowing = false; + + if (null == mMajorView) return; + + SoftKeyboard skb = mMajorView.getSoftKeyboard(); + if (null == skb) return; + skb.enableToggleStates(mInputModeSwitcher.getToggleStates()); + invalidate(); + return; + } + + private void updateSkbLayout() { + int screenWidth = mEnvironment.getScreenWidth(); + int keyHeight = mEnvironment.getKeyHeight(); + int skbHeight = mEnvironment.getSkbHeight(); + + Resources r = mContext.getResources(); + if (null == mSkbFlipper) { + mSkbFlipper = (ViewFlipper) findViewById(R.id.alpha_floatable); + } + mMajorView = (SoftKeyboardView) mSkbFlipper.getChildAt(0); + + SoftKeyboard majorSkb = null; + SkbPool skbPool = SkbPool.getInstance(); + + switch (mSkbLayout) { + case R.xml.skb_qwerty: + majorSkb = skbPool.getSoftKeyboard(R.xml.skb_qwerty, + R.xml.skb_qwerty, screenWidth, skbHeight, mContext); + break; + + case R.xml.skb_sym1: + majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym1, R.xml.skb_sym1, + screenWidth, skbHeight, mContext); + break; + + case R.xml.skb_sym2: + majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym2, R.xml.skb_sym2, + screenWidth, skbHeight, mContext); + break; + + case R.xml.skb_smiley: + majorSkb = skbPool.getSoftKeyboard(R.xml.skb_smiley, + R.xml.skb_smiley, screenWidth, skbHeight, mContext); + break; + + case R.xml.skb_phone: + majorSkb = skbPool.getSoftKeyboard(R.xml.skb_phone, + R.xml.skb_phone, screenWidth, skbHeight, mContext); + break; + default: + } + + if (null == majorSkb || !mMajorView.setSoftKeyboard(majorSkb)) { + return; + } + mMajorView.setBalloonHint(mBalloonOnKey, mBalloonPopup, false); + mMajorView.invalidate(); + } + + private void responseKeyEvent(SoftKey sKey) { + if (null == sKey) return; + ((PinyinIME) mService).responseSoftKeyEvent(sKey); + return; + } + + private SoftKeyboardView inKeyboardView(int x, int y, + int positionInParent[]) { + if (mPopupSkbShow) { + if (mPopupX <= x && mPopupX + mPopupSkb.getWidth() > x + && mPopupY <= y && mPopupY + mPopupSkb.getHeight() > y) { + positionInParent[0] = mPopupX; + positionInParent[1] = mPopupY; + mPopupSkbView.setOffsetToSkbContainer(positionInParent); + return mPopupSkbView; + } + return null; + } + + return mMajorView; + } + + private void popupSymbols() { + int popupResId = mSoftKeyDown.getPopupResId(); + if (popupResId > 0) { + int skbContainerWidth = getWidth(); + int skbContainerHeight = getHeight(); + // The paddings of the background are not included. + int miniSkbWidth = (int) (skbContainerWidth * 0.8); + int miniSkbHeight = (int) (skbContainerHeight * 0.23); + + SkbPool skbPool = SkbPool.getInstance(); + SoftKeyboard skb = skbPool.getSoftKeyboard(popupResId, popupResId, + miniSkbWidth, miniSkbHeight, mContext); + if (null == skb) return; + + mPopupX = (skbContainerWidth - skb.getSkbTotalWidth()) / 2; + mPopupY = (skbContainerHeight - skb.getSkbTotalHeight()) / 2; + + if (null == mPopupSkbView) { + mPopupSkbView = new SoftKeyboardView(mContext, null); + mPopupSkbView.onMeasure(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + } + mPopupSkbView.setOnTouchListener(this); + mPopupSkbView.setSoftKeyboard(skb); + mPopupSkbView.setBalloonHint(mBalloonOnKey, mBalloonPopup, true); + + mPopupSkb.setContentView(mPopupSkbView); + mPopupSkb.setWidth(skb.getSkbCoreWidth() + + mPopupSkbView.getPaddingLeft() + + mPopupSkbView.getPaddingRight()); + mPopupSkb.setHeight(skb.getSkbCoreHeight() + + mPopupSkbView.getPaddingTop() + + mPopupSkbView.getPaddingBottom()); + + getLocationInWindow(mXyPosTmp); + mPopupSkb.showAtLocation(this, Gravity.NO_GRAVITY, mPopupX, mPopupY + + mXyPosTmp[1]); + mPopupSkbShow = true; + mPopupSkbNoResponse = true; + // Invalidate itself to dim the current soft keyboards. + dimSoftKeyboard(true); + resetKeyPress(0); + } + } + + private void dimSoftKeyboard(boolean dimSkb) { + mMajorView.dimSoftKeyboard(dimSkb); + } + + private void dismissPopupSkb() { + mPopupSkb.dismiss(); + mPopupSkbShow = false; + dimSoftKeyboard(false); + resetKeyPress(0); + } + + private void resetKeyPress(long delay) { + mLongPressTimer.removeTimer(); + + if (null != mSkv) { + mSkv.resetKeyPress(delay); + } + } + + public boolean handleBack(boolean realAction) { + if (mPopupSkbShow) { + if (!realAction) return true; + + dismissPopupSkb(); + mDiscardEvent = true; + return true; + } + return false; + } + + public void dismissPopups() { + handleBack(true); + resetKeyPress(0); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Environment env = Environment.getInstance(); + int measuredWidth = env.getScreenWidth(); + int measuredHeight = getPaddingTop(); + measuredHeight += env.getSkbHeight(); + widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, + MeasureSpec.EXACTLY); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight, + MeasureSpec.EXACTLY); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + + if (mSkbFlipper.isFlipping()) { + resetKeyPress(0); + return true; + } + + int x = (int) event.getX(); + int y = (int) event.getY(); + // Bias correction + y = y + mYBiasCorrection; + + // Ignore short-distance movement event to get better performance. + if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (Math.abs(x - mXLast) <= MOVE_TOLERANCE + && Math.abs(y - mYLast) <= MOVE_TOLERANCE) { + return true; + } + } + + mXLast = x; + mYLast = y; + + if (!mPopupSkbShow) { + if (mGestureDetector.onTouchEvent(event)) { + resetKeyPress(0); + mDiscardEvent = true; + return true; + } + } + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + resetKeyPress(0); + + mWaitForTouchUp = true; + mDiscardEvent = false; + + mSkv = null; + mSoftKeyDown = null; + mSkv = inKeyboardView(x, y, mSkvPosInContainer); + if (null != mSkv) { + mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y + - mSkvPosInContainer[1], mLongPressTimer, false); + } + break; + + case MotionEvent.ACTION_MOVE: + if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) { + break; + } + if (mDiscardEvent) { + resetKeyPress(0); + break; + } + + if (mPopupSkbShow && mPopupSkbNoResponse) { + break; + } + + SoftKeyboardView skv = inKeyboardView(x, y, mSkvPosInContainer); + if (null != skv) { + if (skv != mSkv) { + mSkv = skv; + mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y + - mSkvPosInContainer[1], mLongPressTimer, true); + } else if (null != skv) { + if (null != mSkv) { + mSoftKeyDown = mSkv.onKeyMove( + x - mSkvPosInContainer[0], y + - mSkvPosInContainer[1]); + if (null == mSoftKeyDown) { + mDiscardEvent = true; + } + } + } + } + break; + + case MotionEvent.ACTION_UP: + if (mDiscardEvent) { + resetKeyPress(0); + break; + } + + mWaitForTouchUp = false; + + // The view which got the {@link MotionEvent#ACTION_DOWN} event is + // always used to handle this event. + if (null != mSkv) { + mSkv.onKeyRelease(x - mSkvPosInContainer[0], y + - mSkvPosInContainer[1]); + } + + if (!mPopupSkbShow || !mPopupSkbNoResponse) { + responseKeyEvent(mSoftKeyDown); + } + + if (mSkv == mPopupSkbView && !mPopupSkbNoResponse) { + dismissPopupSkb(); + } + mPopupSkbNoResponse = false; + break; + + case MotionEvent.ACTION_CANCEL: + break; + } + + if (null == mSkv) { + return false; + } + + return true; + } + + // Function for interface OnTouchListener, it is used to handle touch events + // which will be delivered to the popup soft keyboard view. + public boolean onTouch(View v, MotionEvent event) { + // Translate the event to fit to the container. + MotionEvent newEv = MotionEvent.obtain(event.getDownTime(), event + .getEventTime(), event.getAction(), event.getX() + mPopupX, + event.getY() + mPopupY, event.getPressure(), event.getSize(), + event.getMetaState(), event.getXPrecision(), event + .getYPrecision(), event.getDeviceId(), event + .getEdgeFlags()); + boolean ret = onTouchEvent(newEv); + return ret; + } + + class LongPressTimer extends Handler implements Runnable { + /** + * When user presses a key for a long time, the timeout interval to + * generate first {@link #LONG_PRESS_KEYNUM1} key events. + */ + public static final int LONG_PRESS_TIMEOUT1 = 500; + + /** + * When user presses a key for a long time, after the first + * {@link #LONG_PRESS_KEYNUM1} key events, this timeout interval will be + * used. + */ + private static final int LONG_PRESS_TIMEOUT2 = 100; + + /** + * When user presses a key for a long time, after the first + * {@link #LONG_PRESS_KEYNUM2} key events, this timeout interval will be + * used. + */ + private static final int LONG_PRESS_TIMEOUT3 = 100; + + /** + * When user presses a key for a long time, after the first + * {@link #LONG_PRESS_KEYNUM1} key events, timeout interval + * {@link #LONG_PRESS_TIMEOUT2} will be used instead. + */ + public static final int LONG_PRESS_KEYNUM1 = 1; + + /** + * When user presses a key for a long time, after the first + * {@link #LONG_PRESS_KEYNUM2} key events, timeout interval + * {@link #LONG_PRESS_TIMEOUT3} will be used instead. + */ + public static final int LONG_PRESS_KEYNUM2 = 3; + + SkbContainer mSkbContainer; + + private int mResponseTimes = 0; + + public LongPressTimer(SkbContainer skbContainer) { + mSkbContainer = skbContainer; + } + + public void startTimer() { + postAtTime(this, SystemClock.uptimeMillis() + LONG_PRESS_TIMEOUT1); + mResponseTimes = 0; + } + + public boolean removeTimer() { + removeCallbacks(this); + return true; + } + + public void run() { + if (mWaitForTouchUp) { + mResponseTimes++; + if (mSoftKeyDown.repeatable()) { + if (mSoftKeyDown.isUserDefKey()) { + if (1 == mResponseTimes) { + if (mInputModeSwitcher + .tryHandleLongPressSwitch(mSoftKeyDown.mKeyCode)) { + mDiscardEvent = true; + resetKeyPress(0); + } + } + } else { + responseKeyEvent(mSoftKeyDown); + long timeout; + if (mResponseTimes < LONG_PRESS_KEYNUM1) { + timeout = LONG_PRESS_TIMEOUT1; + } else if (mResponseTimes < LONG_PRESS_KEYNUM2) { + timeout = LONG_PRESS_TIMEOUT2; + } else { + timeout = LONG_PRESS_TIMEOUT3; + } + postAtTime(this, SystemClock.uptimeMillis() + timeout); + } + } else { + if (1 == mResponseTimes) { + popupSymbols(); + } + } + } + } + } +} |