summaryrefslogtreecommitdiff
path: root/src/com/android/inputmethod/pinyin/CandidateView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/inputmethod/pinyin/CandidateView.java')
-rw-r--r--src/com/android/inputmethod/pinyin/CandidateView.java760
1 files changed, 760 insertions, 0 deletions
diff --git a/src/com/android/inputmethod/pinyin/CandidateView.java b/src/com/android/inputmethod/pinyin/CandidateView.java
new file mode 100644
index 0000000..8dc1bf1
--- /dev/null
+++ b/src/com/android/inputmethod/pinyin/CandidateView.java
@@ -0,0 +1,760 @@
+/*
+ * 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.PinyinIME.DecodingInfo;
+
+import java.util.Vector;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * View to show candidate list. There two candidate view instances which are
+ * used to show animation when user navigates between pages.
+ */
+public class CandidateView extends View {
+ /**
+ * The minimum width to show a item.
+ */
+ private static final float MIN_ITEM_WIDTH = 22;
+
+ /**
+ * Suspension points used to display long items.
+ */
+ private static final String SUSPENSION_POINTS = "...";
+
+ /**
+ * The width to draw candidates.
+ */
+ private int mContentWidth;
+
+ /**
+ * The height to draw candidate content.
+ */
+ private int mContentHeight;
+
+ /**
+ * Whether footnotes are displayed. Footnote is shown when hardware keyboard
+ * is available.
+ */
+ private boolean mShowFootnote = true;
+
+ /**
+ * Balloon hint for candidate press/release.
+ */
+ private BalloonHint mBalloonHint;
+
+ /**
+ * Desired position of the balloon to the input view.
+ */
+ private int mHintPositionToInputView[] = new int[2];
+
+ /**
+ * Decoding result to show.
+ */
+ private DecodingInfo mDecInfo;
+
+ /**
+ * Listener used to notify IME that user clicks a candidate, or navigate
+ * between them.
+ */
+ private CandidateViewListener mCvListener;
+
+ /**
+ * Used to notify the container to update the status of forward/backward
+ * arrows.
+ */
+ private ArrowUpdater mArrowUpdater;
+
+ /**
+ * If true, update the arrow status when drawing candidates.
+ */
+ private boolean mUpdateArrowStatusWhenDraw = false;
+
+ /**
+ * Page number of the page displayed in this view.
+ */
+ private int mPageNo;
+
+ /**
+ * Active candidate position in this page.
+ */
+ private int mActiveCandInPage;
+
+ /**
+ * Used to decided whether the active candidate should be highlighted or
+ * not. If user changes focus to composing view (The view to show Pinyin
+ * string), the highlight in candidate view should be removed.
+ */
+ private boolean mEnableActiveHighlight = true;
+
+ /**
+ * The page which is just calculated.
+ */
+ private int mPageNoCalculated = -1;
+
+ /**
+ * The Drawable used to display as the background of the high-lighted item.
+ */
+ private Drawable mActiveCellDrawable;
+
+ /**
+ * The Drawable used to display as separators between candidates.
+ */
+ private Drawable mSeparatorDrawable;
+
+ /**
+ * Color to draw normal candidates generated by IME.
+ */
+ private int mImeCandidateColor;
+
+ /**
+ * Color to draw normal candidates Recommended by application.
+ */
+ private int mRecommendedCandidateColor;
+
+ /**
+ * Color to draw the normal(not highlighted) candidates, it can be one of
+ * {@link #mImeCandidateColor} or {@link #mRecommendedCandidateColor}.
+ */
+ private int mNormalCandidateColor;
+
+ /**
+ * Color to draw the active(highlighted) candidates, including candidates
+ * from IME and candidates from application.
+ */
+ private int mActiveCandidateColor;
+
+ /**
+ * Text size to draw candidates generated by IME.
+ */
+ private int mImeCandidateTextSize;
+
+ /**
+ * Text size to draw candidates recommended by application.
+ */
+ private int mRecommendedCandidateTextSize;
+
+ /**
+ * The current text size to draw candidates. It can be one of
+ * {@link #mImeCandidateTextSize} or {@link #mRecommendedCandidateTextSize}.
+ */
+ private int mCandidateTextSize;
+
+ /**
+ * Paint used to draw candidates.
+ */
+ private Paint mCandidatesPaint;
+
+ /**
+ * Used to draw footnote.
+ */
+ private Paint mFootnotePaint;
+
+ /**
+ * The width to show suspension points.
+ */
+ private float mSuspensionPointsWidth;
+
+ /**
+ * Rectangle used to draw the active candidate.
+ */
+ private RectF mActiveCellRect;
+
+ /**
+ * Left and right margins for a candidate. It is specified in xml, and is
+ * the minimum margin for a candidate. The actual gap between two candidates
+ * is 2 * {@link #mCandidateMargin} + {@link #mSeparatorDrawable}.
+ * getIntrinsicWidth(). Because length of candidate is not fixed, there can
+ * be some extra space after the last candidate in the current page. In
+ * order to achieve best look-and-feel, this extra space will be divided and
+ * allocated to each candidates.
+ */
+ private float mCandidateMargin;
+
+ /**
+ * Left and right extra margins for a candidate.
+ */
+ private float mCandidateMarginExtra;
+
+ /**
+ * Rectangles for the candidates in this page.
+ **/
+ private Vector<RectF> mCandRects;
+
+ /**
+ * FontMetricsInt used to measure the size of candidates.
+ */
+ private FontMetricsInt mFmiCandidates;
+
+ /**
+ * FontMetricsInt used to measure the size of footnotes.
+ */
+ private FontMetricsInt mFmiFootnote;
+
+ private PressTimer mTimer = new PressTimer();
+
+ private GestureDetector mGestureDetector;
+
+ private int mLocationTmp[] = new int[2];
+
+ public CandidateView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ Resources r = context.getResources();
+
+ Configuration conf = r.getConfiguration();
+ if (conf.keyboard == Configuration.KEYBOARD_NOKEYS
+ || conf.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ mShowFootnote = false;
+ }
+
+ mActiveCellDrawable = r.getDrawable(R.drawable.candidate_hl_bg);
+ mSeparatorDrawable = r.getDrawable(R.drawable.candidates_vertical_line);
+ mCandidateMargin = r.getDimension(R.dimen.candidate_margin_left_right);
+
+ mImeCandidateColor = r.getColor(R.color.candidate_color);
+ mRecommendedCandidateColor = r.getColor(R.color.recommended_candidate_color);
+ mNormalCandidateColor = mImeCandidateColor;
+ mActiveCandidateColor = r.getColor(R.color.active_candidate_color);
+
+ mCandidatesPaint = new Paint();
+ mCandidatesPaint.setAntiAlias(true);
+
+ mFootnotePaint = new Paint();
+ mFootnotePaint.setAntiAlias(true);
+ mFootnotePaint.setColor(r.getColor(R.color.footnote_color));
+ mActiveCellRect = new RectF();
+
+ mCandRects = new Vector<RectF>();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int mOldWidth = mMeasuredWidth;
+ int mOldHeight = mMeasuredHeight;
+
+ setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
+ widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),
+ heightMeasureSpec));
+
+ if (mOldWidth != mMeasuredWidth || mOldHeight != mMeasuredHeight) {
+ onSizeChanged();
+ }
+ }
+
+ public void initialize(ArrowUpdater arrowUpdater, BalloonHint balloonHint,
+ GestureDetector gestureDetector, CandidateViewListener cvListener) {
+ mArrowUpdater = arrowUpdater;
+ mBalloonHint = balloonHint;
+ mGestureDetector = gestureDetector;
+ mCvListener = cvListener;
+ }
+
+ public void setDecodingInfo(DecodingInfo decInfo) {
+ if (null == decInfo) return;
+ mDecInfo = decInfo;
+ mPageNoCalculated = -1;
+
+ if (mDecInfo.candidatesFromApp()) {
+ mNormalCandidateColor = mRecommendedCandidateColor;
+ mCandidateTextSize = mRecommendedCandidateTextSize;
+ } else {
+ mNormalCandidateColor = mImeCandidateColor;
+ mCandidateTextSize = mImeCandidateTextSize;
+ }
+ if (mCandidatesPaint.getTextSize() != mCandidateTextSize) {
+ mCandidatesPaint.setTextSize(mCandidateTextSize);
+ mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+ mSuspensionPointsWidth =
+ mCandidatesPaint.measureText(SUSPENSION_POINTS);
+ }
+
+ // Remove any pending timer for the previous list.
+ mTimer.removeTimer();
+ }
+
+ public int getActiveCandiatePosInPage() {
+ return mActiveCandInPage;
+ }
+
+ public int getActiveCandiatePosGlobal() {
+ return mDecInfo.mPageStart.get(mPageNo) + mActiveCandInPage;
+ }
+
+ /**
+ * Show a page in the decoding result set previously.
+ *
+ * @param pageNo Which page to show.
+ * @param activeCandInPage Which candidate should be set as active item.
+ * @param enableActiveHighlight When false, active item will not be
+ * highlighted.
+ */
+ public void showPage(int pageNo, int activeCandInPage,
+ boolean enableActiveHighlight) {
+ if (null == mDecInfo) return;
+ mPageNo = pageNo;
+ mActiveCandInPage = activeCandInPage;
+ if (mEnableActiveHighlight != enableActiveHighlight) {
+ mEnableActiveHighlight = enableActiveHighlight;
+ }
+
+ if (!calculatePage(mPageNo)) {
+ mUpdateArrowStatusWhenDraw = true;
+ } else {
+ mUpdateArrowStatusWhenDraw = false;
+ }
+
+ invalidate();
+ }
+
+ public void enableActiveHighlight(boolean enableActiveHighlight) {
+ if (enableActiveHighlight == mEnableActiveHighlight) return;
+
+ mEnableActiveHighlight = enableActiveHighlight;
+ invalidate();
+ }
+
+ public boolean activeCursorForward() {
+ if (!mDecInfo.pageReady(mPageNo)) return false;
+ int pageSize = mDecInfo.mPageStart.get(mPageNo + 1)
+ - mDecInfo.mPageStart.get(mPageNo);
+ if (mActiveCandInPage + 1 < pageSize) {
+ showPage(mPageNo, mActiveCandInPage + 1, true);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean activeCurseBackward() {
+ if (mActiveCandInPage > 0) {
+ showPage(mPageNo, mActiveCandInPage - 1, true);
+ return true;
+ }
+ return false;
+ }
+
+ private void onSizeChanged() {
+ mContentWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+ mContentHeight = (int) ((mMeasuredHeight - mPaddingTop - mPaddingBottom) * 0.95f);
+ /**
+ * How to decide the font size if the height for display is given?
+ * Now it is implemented in a stupid way.
+ */
+ int textSize = 1;
+ mCandidatesPaint.setTextSize(textSize);
+ mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+ while (mFmiCandidates.bottom - mFmiCandidates.top < mContentHeight) {
+ textSize++;
+ mCandidatesPaint.setTextSize(textSize);
+ mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+ }
+
+ mImeCandidateTextSize = textSize;
+ mRecommendedCandidateTextSize = textSize * 3 / 4;
+ if (null == mDecInfo) {
+ mCandidateTextSize = mImeCandidateTextSize;
+ mCandidatesPaint.setTextSize(mCandidateTextSize);
+ mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+ mSuspensionPointsWidth =
+ mCandidatesPaint.measureText(SUSPENSION_POINTS);
+ } else {
+ // Reset the decoding information to update members for painting.
+ setDecodingInfo(mDecInfo);
+ }
+
+ textSize = 1;
+ mFootnotePaint.setTextSize(textSize);
+ mFmiFootnote = mFootnotePaint.getFontMetricsInt();
+ while (mFmiFootnote.bottom - mFmiFootnote.top < mContentHeight / 2) {
+ textSize++;
+ mFootnotePaint.setTextSize(textSize);
+ mFmiFootnote = mFootnotePaint.getFontMetricsInt();
+ }
+ textSize--;
+ mFootnotePaint.setTextSize(textSize);
+ mFmiFootnote = mFootnotePaint.getFontMetricsInt();
+
+ // When the size is changed, the first page will be displayed.
+ mPageNo = 0;
+ mActiveCandInPage = 0;
+ }
+
+ private boolean calculatePage(int pageNo) {
+ if (pageNo == mPageNoCalculated) return true;
+
+ mContentWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+ mContentHeight = (int) ((mMeasuredHeight - mPaddingTop - mPaddingBottom) * 0.95f);
+
+ if (mContentWidth <= 0 || mContentHeight <= 0) return false;
+
+ int candSize = mDecInfo.mCandidatesList.size();
+
+ // If the size of page exists, only calculate the extra margin.
+ boolean onlyExtraMargin = false;
+ int fromPage = mDecInfo.mPageStart.size() - 1;
+ if (mDecInfo.mPageStart.size() > pageNo + 1) {
+ onlyExtraMargin = true;
+ fromPage = pageNo;
+ }
+
+ // If the previous pages have no information, calculate them first.
+ for (int p = fromPage; p <= pageNo; p++) {
+ int pStart = mDecInfo.mPageStart.get(p);
+ int pSize = 0;
+ int charNum = 0;
+ float lastItemWidth = 0;
+
+ float xPos;
+ xPos = 0;
+ xPos += mSeparatorDrawable.getIntrinsicWidth();
+ while (xPos < mContentWidth && pStart + pSize < candSize) {
+ int itemPos = pStart + pSize;
+ String itemStr = mDecInfo.mCandidatesList.get(itemPos);
+ float itemWidth = mCandidatesPaint.measureText(itemStr);
+ if (itemWidth < MIN_ITEM_WIDTH) itemWidth = MIN_ITEM_WIDTH;
+
+ itemWidth += mCandidateMargin * 2;
+ itemWidth += mSeparatorDrawable.getIntrinsicWidth();
+ if (xPos + itemWidth < mContentWidth || 0 == pSize) {
+ xPos += itemWidth;
+ lastItemWidth = itemWidth;
+ pSize++;
+ charNum += itemStr.length();
+ } else {
+ break;
+ }
+ }
+ if (!onlyExtraMargin) {
+ mDecInfo.mPageStart.add(pStart + pSize);
+ mDecInfo.mCnToPage.add(mDecInfo.mCnToPage.get(p) + charNum);
+ }
+
+ float marginExtra = (mContentWidth - xPos) / pSize / 2;
+
+ if (mContentWidth - xPos > lastItemWidth) {
+ // Must be the last page, because if there are more items,
+ // the next item's width must be less than lastItemWidth.
+ // In this case, if the last margin is less than the current
+ // one, the last margin can be used, so that the
+ // look-and-feeling will be the same as the previous page.
+ if (mCandidateMarginExtra <= marginExtra) {
+ marginExtra = mCandidateMarginExtra;
+ }
+ } else if (pSize == 1) {
+ marginExtra = 0;
+ }
+ mCandidateMarginExtra = marginExtra;
+ }
+ mPageNoCalculated = pageNo;
+ return true;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ // The invisible candidate view(the one which is not in foreground) can
+ // also be called to drawn, but its decoding result and candidate list
+ // may be empty.
+ if (null == mDecInfo || mDecInfo.isCandidatesListEmpty()) return;
+
+ // Calculate page. If the paging information is ready, the function will
+ // return at once.
+ calculatePage(mPageNo);
+
+ int pStart = mDecInfo.mPageStart.get(mPageNo);
+ int pSize = mDecInfo.mPageStart.get(mPageNo + 1) - pStart;
+ float candMargin = mCandidateMargin + mCandidateMarginExtra;
+ if (mActiveCandInPage > pSize - 1) {
+ mActiveCandInPage = pSize - 1;
+ }
+
+ mCandRects.removeAllElements();
+
+ float xPos = mPaddingLeft;
+ int yPos = (getMeasuredHeight() -
+ (mFmiCandidates.bottom - mFmiCandidates.top)) / 2
+ - mFmiCandidates.top;
+ xPos += drawVerticalSeparator(canvas, xPos);
+ for (int i = 0; i < pSize; i++) {
+ float footnoteSize = 0;
+ String footnote = null;
+ if (mShowFootnote) {
+ footnote = Integer.toString(i + 1);
+ footnoteSize = mFootnotePaint.measureText(footnote);
+ assert (footnoteSize < candMargin);
+ }
+ String cand = mDecInfo.mCandidatesList.get(pStart + i);
+ float candidateWidth = mCandidatesPaint.measureText(cand);
+ float centerOffset = 0;
+ if (candidateWidth < MIN_ITEM_WIDTH) {
+ centerOffset = (MIN_ITEM_WIDTH - candidateWidth) / 2;
+ candidateWidth = MIN_ITEM_WIDTH;
+ }
+
+ float itemTotalWidth = candidateWidth + 2 * candMargin;
+
+ if (mActiveCandInPage == i && mEnableActiveHighlight) {
+ mActiveCellRect.set(xPos, mPaddingTop + 1, xPos
+ + itemTotalWidth, getHeight() - mPaddingBottom - 1);
+ mActiveCellDrawable.setBounds((int) mActiveCellRect.left,
+ (int) mActiveCellRect.top, (int) mActiveCellRect.right,
+ (int) mActiveCellRect.bottom);
+ mActiveCellDrawable.draw(canvas);
+ }
+
+ if (mCandRects.size() < pSize) mCandRects.add(new RectF());
+ mCandRects.elementAt(i).set(xPos - 1, yPos + mFmiCandidates.top,
+ xPos + itemTotalWidth + 1, yPos + mFmiCandidates.bottom);
+
+ // Draw footnote
+ if (mShowFootnote) {
+ canvas.drawText(footnote, xPos + (candMargin - footnoteSize)
+ / 2, yPos, mFootnotePaint);
+ }
+
+ // Left margin
+ xPos += candMargin;
+ if (candidateWidth > mContentWidth - xPos - centerOffset) {
+ cand = getLimitedCandidateForDrawing(cand,
+ mContentWidth - xPos - centerOffset);
+ }
+ if (mActiveCandInPage == i && mEnableActiveHighlight) {
+ mCandidatesPaint.setColor(mActiveCandidateColor);
+ } else {
+ mCandidatesPaint.setColor(mNormalCandidateColor);
+ }
+ canvas.drawText(cand, xPos + centerOffset, yPos,
+ mCandidatesPaint);
+
+ // Candidate and right margin
+ xPos += candidateWidth + candMargin;
+
+ // Draw the separator between candidates.
+ xPos += drawVerticalSeparator(canvas, xPos);
+ }
+
+ // Update the arrow status of the container.
+ if (null != mArrowUpdater && mUpdateArrowStatusWhenDraw) {
+ mArrowUpdater.updateArrowStatus();
+ mUpdateArrowStatusWhenDraw = false;
+ }
+ }
+
+ private String getLimitedCandidateForDrawing(String rawCandidate,
+ float widthToDraw) {
+ int subLen = rawCandidate.length();
+ if (subLen <= 1) return rawCandidate;
+ do {
+ subLen--;
+ float width = mCandidatesPaint.measureText(rawCandidate, 0, subLen);
+ if (width + mSuspensionPointsWidth <= widthToDraw || 1 >= subLen) {
+ return rawCandidate.substring(0, subLen) +
+ SUSPENSION_POINTS;
+ }
+ } while (true);
+ }
+
+ private float drawVerticalSeparator(Canvas canvas, float xPos) {
+ mSeparatorDrawable.setBounds((int) xPos, mPaddingTop, (int) xPos
+ + mSeparatorDrawable.getIntrinsicWidth(), getMeasuredHeight()
+ - mPaddingBottom);
+ mSeparatorDrawable.draw(canvas);
+ return mSeparatorDrawable.getIntrinsicWidth();
+ }
+
+ private int mapToItemInPage(int x, int y) {
+ // mCandRects.size() == 0 happens when the page is set, but
+ // touch events occur before onDraw(). It usually happens with
+ // monkey test.
+ if (!mDecInfo.pageReady(mPageNo) || mPageNoCalculated != mPageNo
+ || mCandRects.size() == 0) {
+ return -1;
+ }
+
+ int pageStart = mDecInfo.mPageStart.get(mPageNo);
+ int pageSize = mDecInfo.mPageStart.get(mPageNo + 1) - pageStart;
+ if (mCandRects.size() < pageSize) {
+ return -1;
+ }
+
+ // If not found, try to find the nearest one.
+ float nearestDis = Float.MAX_VALUE;
+ int nearest = -1;
+ for (int i = 0; i < pageSize; i++) {
+ RectF r = mCandRects.elementAt(i);
+ if (r.left < x && r.right > x && r.top < y && r.bottom > y) {
+ return i;
+ }
+ float disx = (r.left + r.right) / 2 - x;
+ float disy = (r.top + r.bottom) / 2 - y;
+ float dis = disx * disx + disy * disy;
+ if (dis < nearestDis) {
+ nearestDis = dis;
+ nearest = i;
+ }
+ }
+
+ return nearest;
+ }
+
+ // Because the candidate view under the current focused one may also get
+ // touching events. Here we just bypass the event to the container and let
+ // it decide which view should handle the event.
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return super.onTouchEvent(event);
+ }
+
+ public boolean onTouchEventReal(MotionEvent event) {
+ // The page in the background can also be touched.
+ if (null == mDecInfo || !mDecInfo.pageReady(mPageNo)
+ || mPageNoCalculated != mPageNo) return true;
+
+ int x, y;
+ x = (int) event.getX();
+ y = (int) event.getY();
+
+ if (mGestureDetector.onTouchEvent(event)) {
+ mTimer.removeTimer();
+ mBalloonHint.delayedDismiss(0);
+ return true;
+ }
+
+ int clickedItemInPage = -1;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ clickedItemInPage = mapToItemInPage(x, y);
+ if (clickedItemInPage >= 0) {
+ invalidate();
+ mCvListener.onClickChoice(clickedItemInPage
+ + mDecInfo.mPageStart.get(mPageNo));
+ }
+ mBalloonHint.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ clickedItemInPage = mapToItemInPage(x, y);
+ if (clickedItemInPage >= 0) {
+ showBalloon(clickedItemInPage, true);
+ mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
+ clickedItemInPage);
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ clickedItemInPage = mapToItemInPage(x, y);
+ if (clickedItemInPage >= 0
+ && (clickedItemInPage != mTimer.getActiveCandOfPageToShow() || mPageNo != mTimer
+ .getPageToShow())) {
+ showBalloon(clickedItemInPage, true);
+ mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
+ clickedItemInPage);
+ }
+ }
+ return true;
+ }
+
+ private void showBalloon(int candPos, boolean delayedShow) {
+ mBalloonHint.removeTimer();
+
+ RectF r = mCandRects.elementAt(candPos);
+ int desired_width = (int) (r.right - r.left);
+ int desired_height = (int) (r.bottom - r.top);
+ mBalloonHint.setBalloonConfig(mDecInfo.mCandidatesList
+ .get(mDecInfo.mPageStart.get(mPageNo) + candPos), 44, true,
+ mImeCandidateColor, desired_width, desired_height);
+
+ getLocationOnScreen(mLocationTmp);
+ mHintPositionToInputView[0] = mLocationTmp[0]
+ + (int) (r.left - (mBalloonHint.getWidth() - desired_width) / 2);
+ mHintPositionToInputView[1] = -mBalloonHint.getHeight();
+
+ long delay = BalloonHint.TIME_DELAY_SHOW;
+ if (!delayedShow) delay = 0;
+ mBalloonHint.dismiss();
+ if (!mBalloonHint.isShowing()) {
+ mBalloonHint.delayedShow(delay, mHintPositionToInputView);
+ } else {
+ mBalloonHint.delayedUpdate(0, mHintPositionToInputView, -1, -1);
+ }
+ }
+
+ private class PressTimer extends Handler implements Runnable {
+ private boolean mTimerPending = false;
+ private int mPageNoToShow;
+ private int mActiveCandOfPage;
+
+ public PressTimer() {
+ super();
+ }
+
+ public void startTimer(long afterMillis, int pageNo, int activeInPage) {
+ mTimer.removeTimer();
+ postDelayed(this, afterMillis);
+ mTimerPending = true;
+ mPageNoToShow = pageNo;
+ mActiveCandOfPage = activeInPage;
+ }
+
+ public int getPageToShow() {
+ return mPageNoToShow;
+ }
+
+ public int getActiveCandOfPageToShow() {
+ return mActiveCandOfPage;
+ }
+
+ public boolean removeTimer() {
+ if (mTimerPending) {
+ mTimerPending = false;
+ removeCallbacks(this);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isPending() {
+ return mTimerPending;
+ }
+
+ public void run() {
+ if (mPageNoToShow >= 0 && mActiveCandOfPage >= 0) {
+ // Always enable to highlight the clicked one.
+ showPage(mPageNoToShow, mActiveCandOfPage, true);
+ invalidate();
+ }
+ mTimerPending = false;
+ }
+ }
+}