Home | History | Annotate | Download | only in pinyin
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      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 
     17 package com.android.inputmethod.pinyin;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Canvas;
     22 import android.graphics.Paint;
     23 import android.graphics.Paint.FontMetricsInt;
     24 import android.graphics.drawable.Drawable;
     25 import android.util.AttributeSet;
     26 import android.view.KeyEvent;
     27 import android.view.View;
     28 import android.view.ViewGroup.LayoutParams;
     29 
     30 /**
     31  * View used to show composing string (The Pinyin string for the unselected
     32  * syllables and the Chinese string for the selected syllables.)
     33  */
     34 public class ComposingView extends View {
     35     /**
     36      * <p>
     37      * There are three statuses for the composing view.
     38      * </p>
     39      *
     40      * <p>
     41      * {@link #SHOW_PINYIN} is used to show the current Pinyin string without
     42      * highlighted effect. When user inputs Pinyin characters one by one, the
     43      * Pinyin string will be shown in this mode.
     44      * </p>
     45      * <p>
     46      * {@link #SHOW_STRING_LOWERCASE} is used to show the Pinyin string in
     47      * lowercase with highlighted effect. When user presses UP key and there is
     48      * no fixed Chinese characters, composing view will switch from
     49      * {@link #SHOW_PINYIN} to this mode, and in this mode, user can press
     50      * confirm key to input the lower-case string, so that user can input
     51      * English letter in Chinese mode.
     52      * </p>
     53      * <p>
     54      * {@link #EDIT_PINYIN} is used to edit the Pinyin string (shown with
     55      * highlighted effect). When current status is {@link #SHOW_PINYIN} and user
     56      * presses UP key, if there are fixed Characters, the input method will
     57      * switch to {@link #EDIT_PINYIN} thus user can modify some characters in
     58      * the middle of the Pinyin string. If the current status is
     59      * {@link #SHOW_STRING_LOWERCASE} and user presses LEFT and RIGHT key, it
     60      * will also switch to {@link #EDIT_PINYIN}.
     61      * </p>
     62      * <p>
     63      * Whenever user presses down key, the status switches to
     64      * {@link #SHOW_PINYIN}.
     65      * </p>
     66      * <p>
     67      * When composing view's status is {@link #SHOW_PINYIN}, the IME's status is
     68      * {@link PinyinIME.ImeState#STATE_INPUT}, otherwise, the IME's status
     69      * should be {@link PinyinIME.ImeState#STATE_COMPOSING}.
     70      * </p>
     71      */
     72     public enum ComposingStatus {
     73         SHOW_PINYIN, SHOW_STRING_LOWERCASE, EDIT_PINYIN,
     74     }
     75 
     76     private static final int LEFT_RIGHT_MARGIN = 5;
     77 
     78     /**
     79      * Used to draw composing string. When drawing the active and idle part of
     80      * the spelling(Pinyin) string, the color may be changed.
     81      */
     82     private Paint mPaint;
     83 
     84     /**
     85      * Drawable used to draw highlight effect.
     86      */
     87     private Drawable mHlDrawable;
     88 
     89     /**
     90      * Drawable used to draw cursor for editing mode.
     91      */
     92     private Drawable mCursor;
     93 
     94     /**
     95      * Used to estimate dimensions to show the string .
     96      */
     97     private FontMetricsInt mFmi;
     98 
     99     private int mStrColor;
    100     private int mStrColorHl;
    101     private int mStrColorIdle;
    102 
    103     private int mFontSize;
    104 
    105     private ComposingStatus mComposingStatus;
    106 
    107     PinyinIME.DecodingInfo mDecInfo;
    108 
    109     public ComposingView(Context context, AttributeSet attrs) {
    110         super(context, attrs);
    111 
    112         Resources r = context.getResources();
    113         mHlDrawable = r.getDrawable(R.drawable.composing_hl_bg);
    114         mCursor = r.getDrawable(R.drawable.composing_area_cursor);
    115 
    116         mStrColor = r.getColor(R.color.composing_color);
    117         mStrColorHl = r.getColor(R.color.composing_color_hl);
    118         mStrColorIdle = r.getColor(R.color.composing_color_idle);
    119 
    120         mFontSize = r.getDimensionPixelSize(R.dimen.composing_height);
    121 
    122         mPaint = new Paint();
    123         mPaint.setColor(mStrColor);
    124         mPaint.setAntiAlias(true);
    125         mPaint.setTextSize(mFontSize);
    126 
    127         mFmi = mPaint.getFontMetricsInt();
    128     }
    129 
    130     public void reset() {
    131         mComposingStatus = ComposingStatus.SHOW_PINYIN;
    132     }
    133 
    134     /**
    135      * Set the composing string to show. If the IME status is
    136      * {@link PinyinIME.ImeState#STATE_INPUT}, the composing view's status will
    137      * be set to {@link ComposingStatus#SHOW_PINYIN}, otherwise the composing
    138      * view will set its status to {@link ComposingStatus#SHOW_STRING_LOWERCASE}
    139      * or {@link ComposingStatus#EDIT_PINYIN} automatically.
    140      */
    141     public void setDecodingInfo(PinyinIME.DecodingInfo decInfo,
    142             PinyinIME.ImeState imeStatus) {
    143         mDecInfo = decInfo;
    144 
    145         if (PinyinIME.ImeState.STATE_INPUT == imeStatus) {
    146             mComposingStatus = ComposingStatus.SHOW_PINYIN;
    147             mDecInfo.moveCursorToEdge(false);
    148         } else {
    149             if (decInfo.getFixedLen() != 0
    150                     || ComposingStatus.EDIT_PINYIN == mComposingStatus) {
    151                 mComposingStatus = ComposingStatus.EDIT_PINYIN;
    152             } else {
    153                 mComposingStatus = ComposingStatus.SHOW_STRING_LOWERCASE;
    154             }
    155             mDecInfo.moveCursor(0);
    156         }
    157 
    158         measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    159         requestLayout();
    160         invalidate();
    161     }
    162 
    163     public boolean moveCursor(int keyCode) {
    164         if (keyCode != KeyEvent.KEYCODE_DPAD_LEFT
    165                 && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) return false;
    166 
    167         if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
    168             int offset = 0;
    169             if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT)
    170                 offset = -1;
    171             else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) offset = 1;
    172             mDecInfo.moveCursor(offset);
    173         } else if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {
    174             if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
    175                     || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
    176                 mComposingStatus = ComposingStatus.EDIT_PINYIN;
    177 
    178                 measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    179                 requestLayout();
    180             }
    181 
    182         }
    183         invalidate();
    184         return true;
    185     }
    186 
    187     public ComposingStatus getComposingStatus() {
    188         return mComposingStatus;
    189     }
    190 
    191     @Override
    192     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    193         float width;
    194         int height;
    195         height = mFmi.bottom - mFmi.top + mPaddingTop + mPaddingBottom;
    196 
    197         if (null == mDecInfo) {
    198             width = 0;
    199         } else {
    200             width = mPaddingLeft + mPaddingRight + LEFT_RIGHT_MARGIN * 2;
    201 
    202             String str;
    203             if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {
    204                 str = mDecInfo.getOrigianlSplStr().toString();
    205             } else {
    206                 str = mDecInfo.getComposingStrForDisplay();
    207             }
    208             width += mPaint.measureText(str, 0, str.length());
    209         }
    210         setMeasuredDimension((int) (width + 0.5f), height);
    211     }
    212 
    213     @Override
    214     protected void onDraw(Canvas canvas) {
    215         if (ComposingStatus.EDIT_PINYIN == mComposingStatus
    216                 || ComposingStatus.SHOW_PINYIN == mComposingStatus) {
    217             drawForPinyin(canvas);
    218             return;
    219         }
    220 
    221         float x, y;
    222         x = mPaddingLeft + LEFT_RIGHT_MARGIN;
    223         y = -mFmi.top + mPaddingTop;
    224 
    225         mPaint.setColor(mStrColorHl);
    226         mHlDrawable.setBounds(mPaddingLeft, mPaddingTop, getWidth()
    227                 - mPaddingRight, getHeight() - mPaddingBottom);
    228         mHlDrawable.draw(canvas);
    229 
    230         String splStr = mDecInfo.getOrigianlSplStr().toString();
    231         canvas.drawText(splStr, 0, splStr.length(), x, y, mPaint);
    232     }
    233 
    234     private void drawCursor(Canvas canvas, float x) {
    235         mCursor.setBounds((int) x, mPaddingTop, (int) x
    236                 + mCursor.getIntrinsicWidth(), getHeight() - mPaddingBottom);
    237         mCursor.draw(canvas);
    238     }
    239 
    240     private void drawForPinyin(Canvas canvas) {
    241         float x, y;
    242         x = mPaddingLeft + LEFT_RIGHT_MARGIN;
    243         y = -mFmi.top + mPaddingTop;
    244 
    245         mPaint.setColor(mStrColor);
    246 
    247         int cursorPos = mDecInfo.getCursorPosInCmpsDisplay();
    248         int cmpsPos = cursorPos;
    249         String cmpsStr = mDecInfo.getComposingStrForDisplay();
    250         int activeCmpsLen = mDecInfo.getActiveCmpsDisplayLen();
    251         if (cursorPos > activeCmpsLen) cmpsPos = activeCmpsLen;
    252         canvas.drawText(cmpsStr, 0, cmpsPos, x, y, mPaint);
    253         x += mPaint.measureText(cmpsStr, 0, cmpsPos);
    254         if (cursorPos <= activeCmpsLen) {
    255             if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
    256                 drawCursor(canvas, x);
    257             }
    258             canvas.drawText(cmpsStr, cmpsPos, activeCmpsLen, x, y, mPaint);
    259         }
    260 
    261         x += mPaint.measureText(cmpsStr, cmpsPos, activeCmpsLen);
    262 
    263         if (cmpsStr.length() > activeCmpsLen) {
    264             mPaint.setColor(mStrColorIdle);
    265             int oriPos = activeCmpsLen;
    266             if (cursorPos > activeCmpsLen) {
    267                 if (cursorPos > cmpsStr.length()) cursorPos = cmpsStr.length();
    268                 canvas.drawText(cmpsStr, oriPos, cursorPos, x, y, mPaint);
    269                 x += mPaint.measureText(cmpsStr, oriPos, cursorPos);
    270 
    271                 if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
    272                     drawCursor(canvas, x);
    273                 }
    274 
    275                 oriPos = cursorPos;
    276             }
    277             canvas.drawText(cmpsStr, oriPos, cmpsStr.length(), x, y, mPaint);
    278         }
    279     }
    280 }
    281