gt:                         / 2, yPos, mFootnotePaint);
context: /*
   * 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 = getMeasuredWidth();
          int mOldHeight = getMeasuredHeight();
  
          setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
                  widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),
                  heightMeasureSpec));
  
          if (mOldWidth != getMeasuredWidth() || mOldHeight != getMeasuredHeight()) {
              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 = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
          mContentHeight = (int) ((getMeasuredHeight() - 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 = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
          mContentHeight = (int) ((getMeasuredHeight() - 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)
  