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