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 com.android.inputmethod.pinyin.InputModeSwitcher.ToggleStates; 20 21 import android.graphics.Rect; 22 import android.graphics.drawable.Drawable; 23 import android.view.KeyEvent; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 28 /** 29 * Class used to represent a soft keyboard definition, including the height, the 30 * background image, the image for high light, the keys, etc. 31 */ 32 public class SoftKeyboard { 33 /** The XML resource id for this soft keyboard. */ 34 private int mSkbXmlId; 35 36 /** Do we need to cache this soft keyboard? */ 37 private boolean mCacheFlag; 38 39 /** 40 * After user switches to this soft keyboard, if this flag is true, this 41 * soft keyboard will be kept unless explicit switching operation is 42 * performed, otherwise IME will switch back to the previous keyboard layout 43 * whenever user clicks on any none-function key. 44 **/ 45 private boolean mStickyFlag; 46 47 /** 48 * The cache id for this soft keyboard. It is used to identify it in the 49 * soft keyboard pool. 50 */ 51 private int mCacheId; 52 53 /** 54 * Used to indicate whether this soft keyboard is newly loaded from an XML 55 * file or is just gotten from the soft keyboard pool. 56 */ 57 private boolean mNewlyLoadedFlag = true; 58 59 /** The width of the soft keyboard. */ 60 private int mSkbCoreWidth; 61 62 /** The height of the soft keyboard. */ 63 private int mSkbCoreHeight; 64 65 /** The soft keyboard template for this soft keyboard. */ 66 private SkbTemplate mSkbTemplate; 67 68 /** Used to indicate whether this soft keyboard is a QWERTY keyboard. */ 69 private boolean mIsQwerty; 70 71 /** 72 * When {@link #mIsQwerty} is true, this member is Used to indicate that the 73 * soft keyboard should be displayed in uppercase. 74 */ 75 private boolean mIsQwertyUpperCase; 76 77 /** 78 * The id of the rows which are enabled. Rows with id 79 * {@link KeyRow#ALWAYS_SHOW_ROW_ID} are always enabled. 80 */ 81 private int mEnabledRowId; 82 83 /** 84 * Rows in this soft keyboard. Each row has a id. Only matched rows will be 85 * enabled. 86 */ 87 private List<KeyRow> mKeyRows; 88 89 /** 90 * Background of the soft keyboard. If it is null, the one in the soft 91 * keyboard template will be used. 92 **/ 93 public Drawable mSkbBg; 94 95 /** 96 * Background for key balloon. If it is null, the one in the soft keyboard 97 * template will be used. 98 **/ 99 private Drawable mBalloonBg; 100 101 /** 102 * Background for popup mini soft keyboard. If it is null, the one in the 103 * soft keyboard template will be used. 104 **/ 105 private Drawable mPopupBg; 106 107 /** The left and right margin of a key. */ 108 private float mKeyXMargin = 0; 109 110 /** The top and bottom margin of a key. */ 111 private float mKeyYMargin = 0; 112 113 private Rect mTmpRect = new Rect(); 114 115 public SoftKeyboard(int skbXmlId, SkbTemplate skbTemplate, int skbWidth, 116 int skbHeight) { 117 mSkbXmlId = skbXmlId; 118 mSkbTemplate = skbTemplate; 119 mSkbCoreWidth = skbWidth; 120 mSkbCoreHeight = skbHeight; 121 } 122 123 public void setFlags(boolean cacheFlag, boolean stickyFlag, 124 boolean isQwerty, boolean isQwertyUpperCase) { 125 mCacheFlag = cacheFlag; 126 mStickyFlag = stickyFlag; 127 mIsQwerty = isQwerty; 128 mIsQwertyUpperCase = isQwertyUpperCase; 129 } 130 131 public boolean getCacheFlag() { 132 return mCacheFlag; 133 } 134 135 public void setCacheId(int cacheId) { 136 mCacheId = cacheId; 137 } 138 139 public boolean getStickyFlag() { 140 return mStickyFlag; 141 } 142 143 public void setSkbBackground(Drawable skbBg) { 144 mSkbBg = skbBg; 145 } 146 147 public void setPopupBackground(Drawable popupBg) { 148 mPopupBg = popupBg; 149 } 150 151 public void setKeyBalloonBackground(Drawable balloonBg) { 152 mBalloonBg = balloonBg; 153 } 154 155 public void setKeyMargins(float xMargin, float yMargin) { 156 mKeyXMargin = xMargin; 157 mKeyYMargin = yMargin; 158 } 159 160 public int getCacheId() { 161 return mCacheId; 162 } 163 164 public void reset() { 165 if (null != mKeyRows) mKeyRows.clear(); 166 } 167 168 public void setNewlyLoadedFlag(boolean newlyLoadedFlag) { 169 mNewlyLoadedFlag = newlyLoadedFlag; 170 } 171 172 public boolean getNewlyLoadedFlag() { 173 return mNewlyLoadedFlag; 174 } 175 176 public void beginNewRow(int rowId, float yStartingPos) { 177 if (null == mKeyRows) mKeyRows = new ArrayList<KeyRow>(); 178 KeyRow keyRow = new KeyRow(); 179 keyRow.mRowId = rowId; 180 keyRow.mTopF = yStartingPos; 181 keyRow.mBottomF = yStartingPos; 182 keyRow.mSoftKeys = new ArrayList<SoftKey>(); 183 mKeyRows.add(keyRow); 184 } 185 186 public boolean addSoftKey(SoftKey softKey) { 187 if (mKeyRows.size() == 0) return false; 188 KeyRow keyRow = mKeyRows.get(mKeyRows.size() - 1); 189 if (null == keyRow) return false; 190 List<SoftKey> softKeys = keyRow.mSoftKeys; 191 192 softKey.setSkbCoreSize(mSkbCoreWidth, mSkbCoreHeight); 193 softKeys.add(softKey); 194 if (softKey.mTopF < keyRow.mTopF) { 195 keyRow.mTopF = softKey.mTopF; 196 } 197 if (softKey.mBottomF > keyRow.mBottomF) { 198 keyRow.mBottomF = softKey.mBottomF; 199 } 200 return true; 201 } 202 203 public int getSkbXmlId() { 204 return mSkbXmlId; 205 } 206 207 // Set the size of the soft keyboard core. In other words, the background's 208 // padding are not counted. 209 public void setSkbCoreSize(int skbCoreWidth, int skbCoreHeight) { 210 if (null == mKeyRows 211 || (skbCoreWidth == mSkbCoreWidth && skbCoreHeight == mSkbCoreHeight)) { 212 return; 213 } 214 for (int row = 0; row < mKeyRows.size(); row++) { 215 KeyRow keyRow = mKeyRows.get(row); 216 keyRow.mBottom = (int) (skbCoreHeight * keyRow.mBottomF); 217 keyRow.mTop = (int) (skbCoreHeight * keyRow.mTopF); 218 219 List<SoftKey> softKeys = keyRow.mSoftKeys; 220 for (int i = 0; i < softKeys.size(); i++) { 221 SoftKey softKey = softKeys.get(i); 222 softKey.setSkbCoreSize(skbCoreWidth, skbCoreHeight); 223 } 224 } 225 mSkbCoreWidth = skbCoreWidth; 226 mSkbCoreHeight = skbCoreHeight; 227 } 228 229 public int getSkbCoreWidth() { 230 return mSkbCoreWidth; 231 } 232 233 public int getSkbCoreHeight() { 234 return mSkbCoreHeight; 235 } 236 237 public int getSkbTotalWidth() { 238 Rect padding = getPadding(); 239 return mSkbCoreWidth + padding.left + padding.right; 240 } 241 242 public int getSkbTotalHeight() { 243 Rect padding = getPadding(); 244 return mSkbCoreHeight + padding.top + padding.bottom; 245 } 246 247 public int getKeyXMargin() { 248 Environment env = Environment.getInstance(); 249 return (int) (mKeyXMargin * mSkbCoreWidth * env.getKeyXMarginFactor()); 250 } 251 252 public int getKeyYMargin() { 253 Environment env = Environment.getInstance(); 254 return (int) (mKeyYMargin * mSkbCoreHeight * env.getKeyYMarginFactor()); 255 } 256 257 public Drawable getSkbBackground() { 258 if (null != mSkbBg) return mSkbBg; 259 return mSkbTemplate.getSkbBackground(); 260 } 261 262 public Drawable getBalloonBackground() { 263 if (null != mBalloonBg) return mBalloonBg; 264 return mSkbTemplate.getBalloonBackground(); 265 } 266 267 public Drawable getPopupBackground() { 268 if (null != mPopupBg) return mPopupBg; 269 return mSkbTemplate.getPopupBackground(); 270 } 271 272 public int getRowNum() { 273 if (null != mKeyRows) { 274 return mKeyRows.size(); 275 } 276 return 0; 277 } 278 279 public KeyRow getKeyRowForDisplay(int row) { 280 if (null != mKeyRows && mKeyRows.size() > row) { 281 KeyRow keyRow = mKeyRows.get(row); 282 if (KeyRow.ALWAYS_SHOW_ROW_ID == keyRow.mRowId 283 || keyRow.mRowId == mEnabledRowId) { 284 return keyRow; 285 } 286 } 287 return null; 288 } 289 290 public SoftKey getKey(int row, int location) { 291 if (null != mKeyRows && mKeyRows.size() > row) { 292 List<SoftKey> softKeys = mKeyRows.get(row).mSoftKeys; 293 if (softKeys.size() > location) { 294 return softKeys.get(location); 295 } 296 } 297 return null; 298 } 299 300 public SoftKey mapToKey(int x, int y) { 301 if (null == mKeyRows) { 302 return null; 303 } 304 // If the position is inside the rectangle of a certain key, return that 305 // key. 306 int rowNum = mKeyRows.size(); 307 for (int row = 0; row < rowNum; row++) { 308 KeyRow keyRow = mKeyRows.get(row); 309 if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId 310 && keyRow.mRowId != mEnabledRowId) continue; 311 if (keyRow.mTop > y && keyRow.mBottom <= y) continue; 312 313 List<SoftKey> softKeys = keyRow.mSoftKeys; 314 int keyNum = softKeys.size(); 315 for (int i = 0; i < keyNum; i++) { 316 SoftKey sKey = softKeys.get(i); 317 if (sKey.mLeft <= x && sKey.mTop <= y && sKey.mRight > x 318 && sKey.mBottom > y) { 319 return sKey; 320 } 321 } 322 } 323 324 // If the position is outside the rectangles of all keys, find the 325 // nearest one. 326 SoftKey nearestKey = null; 327 float nearestDis = Float.MAX_VALUE; 328 for (int row = 0; row < rowNum; row++) { 329 KeyRow keyRow = mKeyRows.get(row); 330 if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId 331 && keyRow.mRowId != mEnabledRowId) continue; 332 if (keyRow.mTop > y && keyRow.mBottom <= y) continue; 333 334 List<SoftKey> softKeys = keyRow.mSoftKeys; 335 int keyNum = softKeys.size(); 336 for (int i = 0; i < keyNum; i++) { 337 SoftKey sKey = softKeys.get(i); 338 int disx = (sKey.mLeft + sKey.mRight) / 2 - x; 339 int disy = (sKey.mTop + sKey.mBottom) / 2 - y; 340 float dis = disx * disx + disy * disy; 341 if (dis < nearestDis) { 342 nearestDis = dis; 343 nearestKey = sKey; 344 } 345 } 346 } 347 return nearestKey; 348 } 349 350 public void switchQwertyMode(int toggle_state_id, boolean upperCase) { 351 if (!mIsQwerty) return; 352 353 int rowNum = mKeyRows.size(); 354 for (int row = 0; row < rowNum; row++) { 355 KeyRow keyRow = mKeyRows.get(row); 356 List<SoftKey> softKeys = keyRow.mSoftKeys; 357 int keyNum = softKeys.size(); 358 for (int i = 0; i < keyNum; i++) { 359 SoftKey sKey = softKeys.get(i); 360 if (sKey instanceof SoftKeyToggle) { 361 ((SoftKeyToggle) sKey).enableToggleState(toggle_state_id, 362 true); 363 } 364 if (sKey.mKeyCode >= KeyEvent.KEYCODE_A 365 && sKey.mKeyCode <= KeyEvent.KEYCODE_Z) { 366 sKey.changeCase(upperCase); 367 } 368 } 369 } 370 } 371 372 public void enableToggleState(int toggleStateId, boolean resetIfNotFound) { 373 int rowNum = mKeyRows.size(); 374 for (int row = 0; row < rowNum; row++) { 375 KeyRow keyRow = mKeyRows.get(row); 376 List<SoftKey> softKeys = keyRow.mSoftKeys; 377 int keyNum = softKeys.size(); 378 for (int i = 0; i < keyNum; i++) { 379 SoftKey sKey = softKeys.get(i); 380 if (sKey instanceof SoftKeyToggle) { 381 ((SoftKeyToggle) sKey).enableToggleState(toggleStateId, 382 resetIfNotFound); 383 } 384 } 385 } 386 } 387 388 public void disableToggleState(int toggleStateId, boolean resetIfNotFound) { 389 int rowNum = mKeyRows.size(); 390 for (int row = 0; row < rowNum; row++) { 391 KeyRow keyRow = mKeyRows.get(row); 392 List<SoftKey> softKeys = keyRow.mSoftKeys; 393 int keyNum = softKeys.size(); 394 for (int i = 0; i < keyNum; i++) { 395 SoftKey sKey = softKeys.get(i); 396 if (sKey instanceof SoftKeyToggle) { 397 ((SoftKeyToggle) sKey).disableToggleState(toggleStateId, 398 resetIfNotFound); 399 } 400 } 401 } 402 } 403 404 public void enableToggleStates(ToggleStates toggleStates) { 405 if (null == toggleStates) return; 406 407 enableRow(toggleStates.mRowIdToEnable); 408 409 boolean isQwerty = toggleStates.mQwerty; 410 boolean isQwertyUpperCase = toggleStates.mQwertyUpperCase; 411 boolean needUpdateQwerty = (isQwerty && mIsQwerty && (mIsQwertyUpperCase != isQwertyUpperCase)); 412 int states[] = toggleStates.mKeyStates; 413 int statesNum = toggleStates.mKeyStatesNum; 414 415 int rowNum = mKeyRows.size(); 416 for (int row = 0; row < rowNum; row++) { 417 KeyRow keyRow = mKeyRows.get(row); 418 if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId 419 && keyRow.mRowId != mEnabledRowId) { 420 continue; 421 } 422 List<SoftKey> softKeys = keyRow.mSoftKeys; 423 int keyNum = softKeys.size(); 424 for (int keyPos = 0; keyPos < keyNum; keyPos++) { 425 SoftKey sKey = softKeys.get(keyPos); 426 if (sKey instanceof SoftKeyToggle) { 427 for (int statePos = 0; statePos < statesNum; statePos++) { 428 ((SoftKeyToggle) sKey).enableToggleState( 429 states[statePos], statePos == 0); 430 } 431 if (0 == statesNum) { 432 ((SoftKeyToggle) sKey).disableAllToggleStates(); 433 } 434 } 435 if (needUpdateQwerty) { 436 if (sKey.mKeyCode >= KeyEvent.KEYCODE_A 437 && sKey.mKeyCode <= KeyEvent.KEYCODE_Z) { 438 sKey.changeCase(isQwertyUpperCase); 439 } 440 } 441 } 442 } 443 mIsQwertyUpperCase = isQwertyUpperCase; 444 } 445 446 private Rect getPadding() { 447 mTmpRect.set(0, 0, 0, 0); 448 Drawable skbBg = getSkbBackground(); 449 if (null == skbBg) return mTmpRect; 450 skbBg.getPadding(mTmpRect); 451 return mTmpRect; 452 } 453 454 /** 455 * Enable a row with the give toggle Id. Rows with other toggle ids (except 456 * the id {@link KeyRow#ALWAYS_SHOW_ROW_ID}) will be disabled. 457 * 458 * @param rowId The row id to enable. 459 * @return True if the soft keyboard requires redrawing. 460 */ 461 private boolean enableRow(int rowId) { 462 if (KeyRow.ALWAYS_SHOW_ROW_ID == rowId) return false; 463 464 boolean enabled = false; 465 int rowNum = mKeyRows.size(); 466 for (int row = rowNum - 1; row >= 0; row--) { 467 if (mKeyRows.get(row).mRowId == rowId) { 468 enabled = true; 469 break; 470 } 471 } 472 if (enabled) { 473 mEnabledRowId = rowId; 474 } 475 return enabled; 476 } 477 478 @Override 479 public String toString() { 480 String str = "------------------SkbInfo----------------------\n"; 481 String endStr = "-----------------------------------------------\n"; 482 str += "Width: " + String.valueOf(mSkbCoreWidth) + "\n"; 483 str += "Height: " + String.valueOf(mSkbCoreHeight) + "\n"; 484 str += "KeyRowNum: " + mKeyRows == null ? "0" : String.valueOf(mKeyRows 485 .size()) 486 + "\n"; 487 if (null == mKeyRows) return str + endStr; 488 int rowNum = mKeyRows.size(); 489 for (int row = 0; row < rowNum; row++) { 490 KeyRow keyRow = mKeyRows.get(row); 491 List<SoftKey> softKeys = keyRow.mSoftKeys; 492 int keyNum = softKeys.size(); 493 for (int i = 0; i < softKeys.size(); i++) { 494 str += "-key " + String.valueOf(i) + ":" 495 + softKeys.get(i).toString(); 496 } 497 } 498 return str + endStr; 499 } 500 501 public String toShortString() { 502 return super.toString(); 503 } 504 505 class KeyRow { 506 static final int ALWAYS_SHOW_ROW_ID = -1; 507 static final int DEFAULT_ROW_ID = 0; 508 509 List<SoftKey> mSoftKeys; 510 /** 511 * If the row id is {@link #ALWAYS_SHOW_ROW_ID}, this row will always be 512 * enabled. 513 */ 514 int mRowId; 515 float mTopF; 516 float mBottomF; 517 int mTop; 518 int mBottom; 519 } 520 } 521