/* * 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 com.android.inputmethod.pinyin.SoftKeyboard.KeyRow; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Paint.FontMetricsInt; import android.graphics.drawable.Drawable; import android.os.Vibrator; import android.util.AttributeSet; import android.view.View; /** * Class used to show a soft keyboard. * * A soft keyboard view should not handle touch event itself, because we do bias * correction, need a global strategy to map an event into a proper view to * achieve better user experience. */ public class SoftKeyboardView extends View { /** * The definition of the soft keyboard for the current this soft keyboard * view. */ private SoftKeyboard mSoftKeyboard; /** * The popup balloon hint for key press/release. */ private BalloonHint mBalloonPopup; /** * The on-key balloon hint for key press/release. If it is null, on-key * highlight will be drawn on th soft keyboard view directly. */ private BalloonHint mBalloonOnKey; /** Used to play key sounds. */ private SoundManager mSoundManager; /** The last key pressed. */ private SoftKey mSoftKeyDown; /** Used to indicate whether the user is holding on a key. */ private boolean mKeyPressed = false; /** * The location offset of the view to the keyboard container. */ private int mOffsetToSkbContainer[] = new int[2]; /** * The location of the desired hint view to the keyboard container. */ private int mHintLocationToSkbContainer[] = new int[2]; /** * Text size for normal key. */ private int mNormalKeyTextSize; /** * Text size for function key. */ private int mFunctionKeyTextSize; /** * Long press timer used to response long-press. */ private SkbContainer.LongPressTimer mLongPressTimer; /** * Repeated events for long press */ private boolean mRepeatForLongPress = false; /** * If this parameter is true, the balloon will never be dismissed even if * user moves a lot from the pressed point. */ private boolean mMovingNeverHidePopupBalloon = false; /** Vibration for key press. */ private Vibrator mVibrator; /** Vibration pattern for key press. */ protected long[] mVibratePattern = new long[] {1, 20}; /** * The dirty rectangle used to mark the area to re-draw during key press and * release. Currently, whenever we can invalidate(Rect), view will call * onDraw() and we MUST draw the whole view. This dirty information is for * future use. */ private Rect mDirtyRect = new Rect(); private Paint mPaint; private FontMetricsInt mFmi; private boolean mDimSkb; public SoftKeyboardView(Context context, AttributeSet attrs) { super(context, attrs); mSoundManager = SoundManager.getInstance(mContext); mPaint = new Paint(); mPaint.setAntiAlias(true); mFmi = mPaint.getFontMetricsInt(); } public boolean setSoftKeyboard(SoftKeyboard softSkb) { if (null == softSkb) { return false; } mSoftKeyboard = softSkb; Drawable bg = softSkb.getSkbBackground(); if (null != bg) setBackgroundDrawable(bg); return true; } public SoftKeyboard getSoftKeyboard() { return mSoftKeyboard; } public void resizeKeyboard(int skbWidth, int skbHeight) { mSoftKeyboard.setSkbCoreSize(skbWidth, skbHeight); } public void setBalloonHint(BalloonHint balloonOnKey, BalloonHint balloonPopup, boolean movingNeverHidePopup) { mBalloonOnKey = balloonOnKey; mBalloonPopup = balloonPopup; mMovingNeverHidePopupBalloon = movingNeverHidePopup; } public void setOffsetToSkbContainer(int offsetToSkbContainer[]) { mOffsetToSkbContainer[0] = offsetToSkbContainer[0]; mOffsetToSkbContainer[1] = offsetToSkbContainer[1]; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int measuredWidth = 0; int measuredHeight = 0; if (null != mSoftKeyboard) { measuredWidth = mSoftKeyboard.getSkbCoreWidth(); measuredHeight = mSoftKeyboard.getSkbCoreHeight(); measuredWidth += mPaddingLeft + mPaddingRight; measuredHeight += mPaddingTop + mPaddingBottom; } setMeasuredDimension(measuredWidth, measuredHeight); } private void showBalloon(BalloonHint balloon, int balloonLocationToSkb[], boolean movePress) { long delay = BalloonHint.TIME_DELAY_SHOW; if (movePress) delay = 0; if (balloon.needForceDismiss()) { balloon.delayedDismiss(0); } if (!balloon.isShowing()) { balloon.delayedShow(delay, balloonLocationToSkb); } else { balloon.delayedUpdate(delay, balloonLocationToSkb, balloon .getWidth(), balloon.getHeight()); } long b = System.currentTimeMillis(); } public void resetKeyPress(long balloonDelay) { if (!mKeyPressed) return; mKeyPressed = false; if (null != mBalloonOnKey) { mBalloonOnKey.delayedDismiss(balloonDelay); } else { if (null != mSoftKeyDown) { if (mDirtyRect.isEmpty()) { mDirtyRect.set(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, mSoftKeyDown.mRight, mSoftKeyDown.mBottom); } invalidate(mDirtyRect); } else { invalidate(); } } mBalloonPopup.delayedDismiss(balloonDelay); } // If movePress is true, means that this function is called because user // moves his finger to this button. If movePress is false, means that this // function is called when user just presses this key. public SoftKey onKeyPress(int x, int y, SkbContainer.LongPressTimer longPressTimer, boolean movePress) { mKeyPressed = false; boolean moveWithinPreviousKey = false; if (movePress) { SoftKey newKey = mSoftKeyboard.mapToKey(x, y); if (newKey == mSoftKeyDown) moveWithinPreviousKey = true; mSoftKeyDown = newKey; } else { mSoftKeyDown = mSoftKeyboard.mapToKey(x, y); } if (moveWithinPreviousKey || null == mSoftKeyDown) return mSoftKeyDown; mKeyPressed = true; if (!movePress) { tryPlayKeyDown(); tryVibrate(); } mLongPressTimer = longPressTimer; if (!movePress) { if (mSoftKeyDown.getPopupResId() > 0 || mSoftKeyDown.repeatable()) { mLongPressTimer.startTimer(); } } else { mLongPressTimer.removeTimer(); } int desired_width; int desired_height; float textSize; Environment env = Environment.getInstance(); if (null != mBalloonOnKey) { Drawable keyHlBg = mSoftKeyDown.getKeyHlBg(); mBalloonOnKey.setBalloonBackground(keyHlBg); // Prepare the on-key balloon int keyXMargin = mSoftKeyboard.getKeyXMargin(); int keyYMargin = mSoftKeyboard.getKeyYMargin(); desired_width = mSoftKeyDown.width() - 2 * keyXMargin; desired_height = mSoftKeyDown.height() - 2 * keyYMargin; textSize = env .getKeyTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId); Drawable icon = mSoftKeyDown.getKeyIcon(); if (null != icon) { mBalloonOnKey.setBalloonConfig(icon, desired_width, desired_height); } else { mBalloonOnKey.setBalloonConfig(mSoftKeyDown.getKeyLabel(), textSize, true, mSoftKeyDown.getColorHl(), desired_width, desired_height); } mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft - (mBalloonOnKey.getWidth() - mSoftKeyDown.width()) / 2; mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0]; mHintLocationToSkbContainer[1] = mPaddingTop + (mSoftKeyDown.mBottom - keyYMargin) - mBalloonOnKey.getHeight(); mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1]; showBalloon(mBalloonOnKey, mHintLocationToSkbContainer, movePress); } else { mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, mSoftKeyDown.mRight, mSoftKeyDown.mBottom); invalidate(mDirtyRect); } // Prepare the popup balloon if (mSoftKeyDown.needBalloon()) { Drawable balloonBg = mSoftKeyboard.getBalloonBackground(); mBalloonPopup.setBalloonBackground(balloonBg); desired_width = mSoftKeyDown.width() + env.getKeyBalloonWidthPlus(); desired_height = mSoftKeyDown.height() + env.getKeyBalloonHeightPlus(); textSize = env .getBalloonTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId); Drawable iconPopup = mSoftKeyDown.getKeyIconPopup(); if (null != iconPopup) { mBalloonPopup.setBalloonConfig(iconPopup, desired_width, desired_height); } else { mBalloonPopup.setBalloonConfig(mSoftKeyDown.getKeyLabel(), textSize, mSoftKeyDown.needBalloon(), mSoftKeyDown .getColorBalloon(), desired_width, desired_height); } // The position to show. mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft + -(mBalloonPopup.getWidth() - mSoftKeyDown.width()) / 2; mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0]; mHintLocationToSkbContainer[1] = mPaddingTop + mSoftKeyDown.mTop - mBalloonPopup.getHeight(); mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1]; showBalloon(mBalloonPopup, mHintLocationToSkbContainer, movePress); } else { mBalloonPopup.delayedDismiss(0); } if (mRepeatForLongPress) longPressTimer.startTimer(); return mSoftKeyDown; } public SoftKey onKeyRelease(int x, int y) { mKeyPressed = false; if (null == mSoftKeyDown) return null; mLongPressTimer.removeTimer(); if (null != mBalloonOnKey) { mBalloonOnKey.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS); } else { mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, mSoftKeyDown.mRight, mSoftKeyDown.mBottom); invalidate(mDirtyRect); } if (mSoftKeyDown.needBalloon()) { mBalloonPopup.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS); } if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) { return mSoftKeyDown; } return null; } public SoftKey onKeyMove(int x, int y) { if (null == mSoftKeyDown) return null; if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) { return mSoftKeyDown; } // The current key needs to be updated. mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop, mSoftKeyDown.mRight, mSoftKeyDown.mBottom); if (mRepeatForLongPress) { if (mMovingNeverHidePopupBalloon) { return onKeyPress(x, y, mLongPressTimer, true); } if (null != mBalloonOnKey) { mBalloonOnKey.delayedDismiss(0); } else { invalidate(mDirtyRect); } if (mSoftKeyDown.needBalloon()) { mBalloonPopup.delayedDismiss(0); } if (null != mLongPressTimer) { mLongPressTimer.removeTimer(); } return onKeyPress(x, y, mLongPressTimer, true); } else { // When user moves between keys, repeated response is disabled. return onKeyPress(x, y, mLongPressTimer, true); } } private void tryVibrate() { if (!Settings.getVibrate()) { return; } if (mVibrator == null) { mVibrator = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); } mVibrator.vibrate(mVibratePattern, -1); } private void tryPlayKeyDown() { if (Settings.getKeySound()) { mSoundManager.playKeyDown(); } } public void dimSoftKeyboard(boolean dimSkb) { mDimSkb = dimSkb; invalidate(); } @Override protected void onDraw(Canvas canvas) { if (null == mSoftKeyboard) return; canvas.translate(mPaddingLeft, mPaddingTop); Environment env = Environment.getInstance(); mNormalKeyTextSize = env.getKeyTextSize(false); mFunctionKeyTextSize = env.getKeyTextSize(true); // Draw the last soft keyboard int rowNum = mSoftKeyboard.getRowNum(); int keyXMargin = mSoftKeyboard.getKeyXMargin(); int keyYMargin = mSoftKeyboard.getKeyYMargin(); for (int row = 0; row < rowNum; row++) { KeyRow keyRow = mSoftKeyboard.getKeyRowForDisplay(row); if (null == keyRow) continue; List softKeys = keyRow.mSoftKeys; int keyNum = softKeys.size(); for (int i = 0; i < keyNum; i++) { SoftKey softKey = softKeys.get(i); if (SoftKeyType.KEYTYPE_ID_NORMAL_KEY == softKey.mKeyType.mKeyTypeId) { mPaint.setTextSize(mNormalKeyTextSize); } else { mPaint.setTextSize(mFunctionKeyTextSize); } drawSoftKey(canvas, softKey, keyXMargin, keyYMargin); } } if (mDimSkb) { mPaint.setColor(0xa0000000); canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); } mDirtyRect.setEmpty(); } private void drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin, int keyYMargin) { Drawable bg; int textColor; if (mKeyPressed && softKey == mSoftKeyDown) { bg = softKey.getKeyHlBg(); textColor = softKey.getColorHl(); } else { bg = softKey.getKeyBg(); textColor = softKey.getColor(); } if (null != bg) { bg.setBounds(softKey.mLeft + keyXMargin, softKey.mTop + keyYMargin, softKey.mRight - keyXMargin, softKey.mBottom - keyYMargin); bg.draw(canvas); } String keyLabel = softKey.getKeyLabel(); Drawable keyIcon = softKey.getKeyIcon(); if (null != keyIcon) { Drawable icon = keyIcon; int marginLeft = (softKey.width() - icon.getIntrinsicWidth()) / 2; int marginRight = softKey.width() - icon.getIntrinsicWidth() - marginLeft; int marginTop = (softKey.height() - icon.getIntrinsicHeight()) / 2; int marginBottom = softKey.height() - icon.getIntrinsicHeight() - marginTop; icon.setBounds(softKey.mLeft + marginLeft, softKey.mTop + marginTop, softKey.mRight - marginRight, softKey.mBottom - marginBottom); icon.draw(canvas); } else if (null != keyLabel) { mPaint.setColor(textColor); float x = softKey.mLeft + (softKey.width() - mPaint.measureText(keyLabel)) / 2.0f; int fontHeight = mFmi.bottom - mFmi.top; float marginY = (softKey.height() - fontHeight) / 2.0f; float y = softKey.mTop + marginY - mFmi.top + mFmi.bottom / 1.5f; canvas.drawText(keyLabel, x, y + 1, mPaint); } } }