Key.java revision 639c93f43bc51a8328d7dc11a09a3bd77974aeae
1/* 2 * Copyright (C) 2010 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 17package com.android.inputmethod.keyboard; 18 19import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; 20import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; 21import static com.android.inputmethod.latin.Constants.CODE_SHIFT; 22import static com.android.inputmethod.latin.Constants.CODE_SWITCH_ALPHA_SYMBOL; 23import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; 24 25import android.content.res.TypedArray; 26import android.graphics.Rect; 27import android.graphics.Typeface; 28import android.graphics.drawable.Drawable; 29import android.text.TextUtils; 30 31import com.android.inputmethod.keyboard.internal.KeyDrawParams; 32import com.android.inputmethod.keyboard.internal.KeySpecParser; 33import com.android.inputmethod.keyboard.internal.KeyStyle; 34import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; 35import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 36import com.android.inputmethod.keyboard.internal.KeyboardParams; 37import com.android.inputmethod.keyboard.internal.KeyboardRow; 38import com.android.inputmethod.keyboard.internal.MoreKeySpec; 39import com.android.inputmethod.latin.Constants; 40import com.android.inputmethod.latin.R; 41import com.android.inputmethod.latin.utils.StringUtils; 42 43import java.util.Arrays; 44import java.util.Locale; 45 46/** 47 * Class for describing the position and characteristics of a single key in the keyboard. 48 */ 49public class Key implements Comparable<Key> { 50 /** 51 * The key code (unicode or custom code) that this key generates. 52 */ 53 private final int mCode; 54 55 /** Label to display */ 56 private final String mLabel; 57 /** Hint label to display on the key in conjunction with the label */ 58 private final String mHintLabel; 59 /** Flags of the label */ 60 private final int mLabelFlags; 61 private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08; 62 // Font typeface specification. 63 private static final int LABEL_FLAGS_FONT_MASK = 0x30; 64 private static final int LABEL_FLAGS_FONT_NORMAL = 0x10; 65 private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20; 66 private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30; 67 // Start of key text ratio enum values 68 private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0; 69 private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40; 70 private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80; 71 private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0; 72 private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO = 0x100; 73 private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140; 74 // End of key text ratio mask enum values 75 private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200; 76 private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400; 77 private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800; 78 // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on 79 // and autoYScale bit is off, the key label may be shrunk only for X-direction. 80 // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled. 81 private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000; 82 private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000; 83 private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE 84 | LABEL_FLAGS_AUTO_Y_SCALE; 85 private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000; 86 private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000; 87 private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000; 88 private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000; 89 private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000; 90 private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000; 91 92 /** Icon to display instead of a label. Icon takes precedence over a label */ 93 private final int mIconId; 94 95 /** Width of the key, not including the gap */ 96 private final int mWidth; 97 /** Height of the key, not including the gap */ 98 private final int mHeight; 99 /** X coordinate of the key in the keyboard layout */ 100 private final int mX; 101 /** Y coordinate of the key in the keyboard layout */ 102 private final int mY; 103 /** Hit bounding box of the key */ 104 private final Rect mHitBox = new Rect(); 105 106 /** More keys. It is guaranteed that this is null or an array of one or more elements */ 107 private final MoreKeySpec[] mMoreKeys; 108 /** More keys column number and flags */ 109 private final int mMoreKeysColumnAndFlags; 110 private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff; 111 private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000; 112 private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000; 113 private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000; 114 private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000; 115 private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!"; 116 private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!"; 117 private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!"; 118 private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!"; 119 private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!"; 120 121 /** Background type that represents different key background visual than normal one. */ 122 private final int mBackgroundType; 123 public static final int BACKGROUND_TYPE_EMPTY = 0; 124 public static final int BACKGROUND_TYPE_NORMAL = 1; 125 public static final int BACKGROUND_TYPE_FUNCTIONAL = 2; 126 public static final int BACKGROUND_TYPE_ACTION = 3; 127 public static final int BACKGROUND_TYPE_STICKY_OFF = 4; 128 public static final int BACKGROUND_TYPE_STICKY_ON = 5; 129 130 private final int mActionFlags; 131 private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01; 132 private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02; 133 private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04; 134 private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08; 135 136 private final KeyVisualAttributes mKeyVisualAttributes; 137 138 private final OptionalAttributes mOptionalAttributes; 139 140 private static final class OptionalAttributes { 141 /** Text to output when pressed. This can be multiple characters, like ".com" */ 142 public final String mOutputText; 143 public final int mAltCode; 144 /** Icon for disabled state */ 145 public final int mDisabledIconId; 146 /** The visual insets */ 147 public final int mVisualInsetsLeft; 148 public final int mVisualInsetsRight; 149 150 private OptionalAttributes(final String outputText, final int altCode, 151 final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { 152 mOutputText = outputText; 153 mAltCode = altCode; 154 mDisabledIconId = disabledIconId; 155 mVisualInsetsLeft = visualInsetsLeft; 156 mVisualInsetsRight = visualInsetsRight; 157 } 158 159 public static OptionalAttributes newInstance(final String outputText, final int altCode, 160 final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { 161 if (outputText == null && altCode == CODE_UNSPECIFIED 162 && disabledIconId == ICON_UNDEFINED && visualInsetsLeft == 0 163 && visualInsetsRight == 0) { 164 return null; 165 } 166 return new OptionalAttributes(outputText, altCode, disabledIconId, visualInsetsLeft, 167 visualInsetsRight); 168 } 169 } 170 171 private final int mHashCode; 172 173 /** The current pressed state of this key */ 174 private boolean mPressed; 175 /** Key is enabled and responds on press */ 176 private boolean mEnabled = true; 177 178 /** 179 * Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>, 180 * and in a <GridRows/>. 181 */ 182 public Key(final String label, final int iconId, final int code, final String outputText, 183 final String hintLabel, final int labelFlags, final int backgroundType, final int x, 184 final int y, final int width, final int height, final int horizontalGap, 185 final int verticalGap) { 186 mHeight = height - verticalGap; 187 mWidth = width - horizontalGap; 188 mHintLabel = hintLabel; 189 mLabelFlags = labelFlags; 190 mBackgroundType = backgroundType; 191 // TODO: Pass keyActionFlags as an argument. 192 mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW; 193 mMoreKeys = null; 194 mMoreKeysColumnAndFlags = 0; 195 mLabel = label; 196 mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED, 197 ICON_UNDEFINED, 0 /* visualInsetsLeft */, 0 /* visualInsetsRight */); 198 mCode = code; 199 mEnabled = (code != CODE_UNSPECIFIED); 200 mIconId = iconId; 201 // Horizontal gap is divided equally to both sides of the key. 202 mX = x + horizontalGap / 2; 203 mY = y; 204 mHitBox.set(x, y, x + width + 1, y + height); 205 mKeyVisualAttributes = null; 206 207 mHashCode = computeHashCode(this); 208 } 209 210 /** 211 * Create a key with the given top-left coordinate and extract its attributes from a key 212 * specification string, Key attribute array, key style, and etc. 213 * 214 * @param keySpec the key specification. 215 * @param keyAttr the Key XML attributes array. 216 * @param style the {@link KeyStyle} of this key. 217 * @param params the keyboard building parameters. 218 * @param row the row that this key belongs to. row's x-coordinate will be the right edge of 219 * this key. 220 */ 221 public Key(final String keySpec, final TypedArray keyAttr, final KeyStyle style, 222 final KeyboardParams params, final KeyboardRow row) { 223 final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap; 224 final int rowHeight = row.getRowHeight(); 225 mHeight = rowHeight - params.mVerticalGap; 226 227 final float keyXPos = row.getKeyX(keyAttr); 228 final float keyWidth = row.getKeyWidth(keyAttr, keyXPos); 229 final int keyYPos = row.getKeyY(); 230 231 // Horizontal gap is divided equally to both sides of the key. 232 mX = Math.round(keyXPos + horizontalGap / 2); 233 mY = keyYPos; 234 mWidth = Math.round(keyWidth - horizontalGap); 235 mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1, 236 keyYPos + rowHeight); 237 // Update row to have current x coordinate. 238 row.setXPos(keyXPos + keyWidth); 239 240 mBackgroundType = style.getInt(keyAttr, 241 R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType()); 242 243 final int baseWidth = params.mBaseWidth; 244 final int visualInsetsLeft = Math.round(keyAttr.getFraction( 245 R.styleable.Keyboard_Key_visualInsetsLeft, baseWidth, baseWidth, 0)); 246 final int visualInsetsRight = Math.round(keyAttr.getFraction( 247 R.styleable.Keyboard_Key_visualInsetsRight, baseWidth, baseWidth, 0)); 248 249 mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) 250 | row.getDefaultKeyLabelFlags(); 251 final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId); 252 final Locale locale = params.mId.mLocale; 253 int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); 254 String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); 255 256 int moreKeysColumn = style.getInt(keyAttr, 257 R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn); 258 int value; 259 if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { 260 moreKeysColumn = value & MORE_KEYS_COLUMN_MASK; 261 } 262 if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { 263 moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK); 264 } 265 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { 266 moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS; 267 } 268 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { 269 moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS; 270 } 271 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) { 272 moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY; 273 } 274 mMoreKeysColumnAndFlags = moreKeysColumn; 275 276 final String[] additionalMoreKeys; 277 if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { 278 additionalMoreKeys = null; 279 } else { 280 additionalMoreKeys = style.getStringArray(keyAttr, 281 R.styleable.Keyboard_Key_additionalMoreKeys); 282 } 283 moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); 284 if (moreKeys != null) { 285 actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; 286 mMoreKeys = new MoreKeySpec[moreKeys.length]; 287 for (int i = 0; i < moreKeys.length; i++) { 288 mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpperCase, locale); 289 } 290 } else { 291 mMoreKeys = null; 292 } 293 mActionFlags = actionFlags; 294 295 mIconId = KeySpecParser.getIconId(keySpec); 296 final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, 297 R.styleable.Keyboard_Key_keyIconDisabled)); 298 299 final int code = KeySpecParser.getCode(keySpec); 300 if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) { 301 mLabel = params.mId.mCustomActionLabel; 302 } else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) { 303 // This is a workaround to have a key that has a supplementary code point in its label. 304 // Because we can put a string in resource neither as a XML entity of a supplementary 305 // code point nor as a surrogate pair. 306 mLabel = new StringBuilder().appendCodePoint(code).toString(); 307 } else { 308 mLabel = StringUtils.toUpperCaseOfStringForLocale( 309 KeySpecParser.getLabel(keySpec), needsToUpperCase, locale); 310 } 311 if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { 312 mHintLabel = null; 313 } else { 314 mHintLabel = StringUtils.toUpperCaseOfStringForLocale(style.getString(keyAttr, 315 R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale); 316 } 317 String outputText = StringUtils.toUpperCaseOfStringForLocale( 318 KeySpecParser.getOutputText(keySpec), needsToUpperCase, locale); 319 // Choose the first letter of the label as primary code if not specified. 320 if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) 321 && !TextUtils.isEmpty(mLabel)) { 322 if (StringUtils.codePointCount(mLabel) == 1) { 323 // Use the first letter of the hint label if shiftedLetterActivated flag is 324 // specified. 325 if (hasShiftedLetterHint() && isShiftedLetterActivated()) { 326 mCode = mHintLabel.codePointAt(0); 327 } else { 328 mCode = mLabel.codePointAt(0); 329 } 330 } else { 331 // In some locale and case, the character might be represented by multiple code 332 // points, such as upper case Eszett of German alphabet. 333 outputText = mLabel; 334 mCode = CODE_OUTPUT_TEXT; 335 } 336 } else if (code == CODE_UNSPECIFIED && outputText != null) { 337 if (StringUtils.codePointCount(outputText) == 1) { 338 mCode = outputText.codePointAt(0); 339 outputText = null; 340 } else { 341 mCode = CODE_OUTPUT_TEXT; 342 } 343 } else { 344 mCode = StringUtils.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale); 345 } 346 final int altCodeInAttr = KeySpecParser.parseCode( 347 style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED); 348 final int altCode = StringUtils.toUpperCaseOfCodeForLocale( 349 altCodeInAttr, needsToUpperCase, locale); 350 mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, 351 disabledIconId, visualInsetsLeft, visualInsetsRight); 352 mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); 353 mHashCode = computeHashCode(this); 354 } 355 356 /** 357 * Copy constructor for DynamicGridKeyboard.GridKey. 358 * 359 * @param key the original key. 360 */ 361 protected Key(final Key key) { 362 // Final attributes. 363 mCode = key.mCode; 364 mLabel = key.mLabel; 365 mHintLabel = key.mHintLabel; 366 mLabelFlags = key.mLabelFlags; 367 mIconId = key.mIconId; 368 mWidth = key.mWidth; 369 mHeight = key.mHeight; 370 mX = key.mX; 371 mY = key.mY; 372 mHitBox.set(key.mHitBox); 373 mMoreKeys = key.mMoreKeys; 374 mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags; 375 mBackgroundType = key.mBackgroundType; 376 mActionFlags = key.mActionFlags; 377 mKeyVisualAttributes = key.mKeyVisualAttributes; 378 mOptionalAttributes = key.mOptionalAttributes; 379 mHashCode = key.mHashCode; 380 // Key state. 381 mPressed = key.mPressed; 382 mEnabled = key.mEnabled; 383 } 384 385 private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) { 386 if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; 387 switch (keyboardElementId) { 388 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 389 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 390 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 391 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 392 return true; 393 default: 394 return false; 395 } 396 } 397 398 private static int computeHashCode(final Key key) { 399 return Arrays.hashCode(new Object[] { 400 key.mX, 401 key.mY, 402 key.mWidth, 403 key.mHeight, 404 key.mCode, 405 key.mLabel, 406 key.mHintLabel, 407 key.mIconId, 408 key.mBackgroundType, 409 Arrays.hashCode(key.mMoreKeys), 410 key.getOutputText(), 411 key.mActionFlags, 412 key.mLabelFlags, 413 // Key can be distinguishable without the following members. 414 // key.mOptionalAttributes.mAltCode, 415 // key.mOptionalAttributes.mDisabledIconId, 416 // key.mOptionalAttributes.mPreviewIconId, 417 // key.mHorizontalGap, 418 // key.mVerticalGap, 419 // key.mOptionalAttributes.mVisualInsetLeft, 420 // key.mOptionalAttributes.mVisualInsetRight, 421 // key.mMaxMoreKeysColumn, 422 }); 423 } 424 425 private boolean equalsInternal(final Key o) { 426 if (this == o) return true; 427 return o.mX == mX 428 && o.mY == mY 429 && o.mWidth == mWidth 430 && o.mHeight == mHeight 431 && o.mCode == mCode 432 && TextUtils.equals(o.mLabel, mLabel) 433 && TextUtils.equals(o.mHintLabel, mHintLabel) 434 && o.mIconId == mIconId 435 && o.mBackgroundType == mBackgroundType 436 && Arrays.equals(o.mMoreKeys, mMoreKeys) 437 && TextUtils.equals(o.getOutputText(), getOutputText()) 438 && o.mActionFlags == mActionFlags 439 && o.mLabelFlags == mLabelFlags; 440 } 441 442 @Override 443 public int compareTo(Key o) { 444 if (equalsInternal(o)) return 0; 445 if (mHashCode > o.mHashCode) return 1; 446 return -1; 447 } 448 449 @Override 450 public int hashCode() { 451 return mHashCode; 452 } 453 454 @Override 455 public boolean equals(final Object o) { 456 return o instanceof Key && equalsInternal((Key)o); 457 } 458 459 @Override 460 public String toString() { 461 return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight(); 462 } 463 464 public String toShortString() { 465 final int code = getCode(); 466 if (code == Constants.CODE_OUTPUT_TEXT) { 467 return getOutputText(); 468 } 469 return Constants.printableCode(code); 470 } 471 472 public String toLongString() { 473 final int iconId = getIconId(); 474 final String topVisual = (iconId == KeyboardIconsSet.ICON_UNDEFINED) 475 ? KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(iconId) : getLabel(); 476 final String hintLabel = getHintLabel(); 477 final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel; 478 return toString() + " " + visual + "/" + backgroundName(mBackgroundType); 479 } 480 481 private static String backgroundName(final int backgroundType) { 482 switch (backgroundType) { 483 case BACKGROUND_TYPE_EMPTY: return "empty"; 484 case BACKGROUND_TYPE_NORMAL: return "normal"; 485 case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; 486 case BACKGROUND_TYPE_ACTION: return "action"; 487 case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff"; 488 case BACKGROUND_TYPE_STICKY_ON: return "stickyOn"; 489 default: return null; 490 } 491 } 492 493 public int getCode() { 494 return mCode; 495 } 496 497 public String getLabel() { 498 return mLabel; 499 } 500 501 public String getHintLabel() { 502 return mHintLabel; 503 } 504 505 public MoreKeySpec[] getMoreKeys() { 506 return mMoreKeys; 507 } 508 509 public void markAsLeftEdge(final KeyboardParams params) { 510 mHitBox.left = params.mLeftPadding; 511 } 512 513 public void markAsRightEdge(final KeyboardParams params) { 514 mHitBox.right = params.mOccupiedWidth - params.mRightPadding; 515 } 516 517 public void markAsTopEdge(final KeyboardParams params) { 518 mHitBox.top = params.mTopPadding; 519 } 520 521 public void markAsBottomEdge(final KeyboardParams params) { 522 mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; 523 } 524 525 public final boolean isSpacer() { 526 return this instanceof Spacer; 527 } 528 529 public final boolean isShift() { 530 return mCode == CODE_SHIFT; 531 } 532 533 public final boolean isModifier() { 534 return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL; 535 } 536 537 public final boolean isRepeatable() { 538 return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; 539 } 540 541 public final boolean noKeyPreview() { 542 return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; 543 } 544 545 public final boolean altCodeWhileTyping() { 546 return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; 547 } 548 549 public final boolean isLongPressEnabled() { 550 // We need not start long press timer on the key which has activated shifted letter. 551 return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 552 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; 553 } 554 555 public KeyVisualAttributes getVisualAttributes() { 556 return mKeyVisualAttributes; 557 } 558 559 public final Typeface selectTypeface(final KeyDrawParams params) { 560 switch (mLabelFlags & LABEL_FLAGS_FONT_MASK) { 561 case LABEL_FLAGS_FONT_NORMAL: 562 return Typeface.DEFAULT; 563 case LABEL_FLAGS_FONT_MONO_SPACE: 564 return Typeface.MONOSPACE; 565 case LABEL_FLAGS_FONT_DEFAULT: 566 default: 567 // The type-face is specified by keyTypeface attribute. 568 return params.mTypeface; 569 } 570 } 571 572 public final int selectTextSize(final KeyDrawParams params) { 573 switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { 574 case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: 575 return params.mLetterSize; 576 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO: 577 return params.mLargeLetterSize; 578 case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO: 579 return params.mLabelSize; 580 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO: 581 return params.mLargeLabelSize; 582 case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO: 583 return params.mHintLabelSize; 584 default: // No follow key ratio flag specified. 585 return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize; 586 } 587 } 588 589 public final int selectTextColor(final KeyDrawParams params) { 590 if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) { 591 return params.mFunctionalTextColor; 592 } 593 return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor; 594 } 595 596 public final int selectHintTextSize(final KeyDrawParams params) { 597 if (hasHintLabel()) { 598 return params.mHintLabelSize; 599 } 600 if (hasShiftedLetterHint()) { 601 return params.mShiftedLetterHintSize; 602 } 603 return params.mHintLetterSize; 604 } 605 606 public final int selectHintTextColor(final KeyDrawParams params) { 607 if (hasHintLabel()) { 608 return params.mHintLabelColor; 609 } 610 if (hasShiftedLetterHint()) { 611 return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor 612 : params.mShiftedLetterHintInactivatedColor; 613 } 614 return params.mHintLetterColor; 615 } 616 617 public final int selectMoreKeyTextSize(final KeyDrawParams params) { 618 return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize; 619 } 620 621 public final String getPreviewLabel() { 622 return isShiftedLetterActivated() ? mHintLabel : mLabel; 623 } 624 625 private boolean previewHasLetterSize() { 626 return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0 627 || StringUtils.codePointCount(getPreviewLabel()) == 1; 628 } 629 630 public final int selectPreviewTextSize(final KeyDrawParams params) { 631 if (previewHasLetterSize()) { 632 return params.mPreviewTextSize; 633 } 634 return params.mLetterSize; 635 } 636 637 public Typeface selectPreviewTypeface(final KeyDrawParams params) { 638 if (previewHasLetterSize()) { 639 return selectTypeface(params); 640 } 641 return Typeface.DEFAULT_BOLD; 642 } 643 644 public final boolean isAlignLeftOfCenter() { 645 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0; 646 } 647 648 public final boolean hasPopupHint() { 649 return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; 650 } 651 652 public final boolean hasShiftedLetterHint() { 653 return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 654 && !TextUtils.isEmpty(mHintLabel); 655 } 656 657 public final boolean hasHintLabel() { 658 return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; 659 } 660 661 public final boolean needsAutoXScale() { 662 return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; 663 } 664 665 public final boolean needsAutoScale() { 666 return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE; 667 } 668 669 private final boolean isShiftedLetterActivated() { 670 return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0 671 && !TextUtils.isEmpty(mHintLabel); 672 } 673 674 public final int getMoreKeysColumn() { 675 return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK; 676 } 677 678 public final boolean isFixedColumnOrderMoreKeys() { 679 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0; 680 } 681 682 public final boolean hasLabelsInMoreKeys() { 683 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0; 684 } 685 686 public final int getMoreKeyLabelFlags() { 687 return hasLabelsInMoreKeys() 688 ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO 689 : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; 690 } 691 692 public final boolean needsDividersInMoreKeys() { 693 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0; 694 } 695 696 public final boolean hasNoPanelAutoMoreKey() { 697 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0; 698 } 699 700 public final String getOutputText() { 701 final OptionalAttributes attrs = mOptionalAttributes; 702 return (attrs != null) ? attrs.mOutputText : null; 703 } 704 705 public final int getAltCode() { 706 final OptionalAttributes attrs = mOptionalAttributes; 707 return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; 708 } 709 710 public int getIconId() { 711 return mIconId; 712 } 713 714 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 715 final OptionalAttributes attrs = mOptionalAttributes; 716 final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; 717 final int iconId = mEnabled ? getIconId() : disabledIconId; 718 final Drawable icon = iconSet.getIconDrawable(iconId); 719 if (icon != null) { 720 icon.setAlpha(alpha); 721 } 722 return icon; 723 } 724 725 public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { 726 return iconSet.getIconDrawable(getIconId()); 727 } 728 729 public int getWidth() { 730 return mWidth; 731 } 732 733 public int getHeight() { 734 return mHeight; 735 } 736 737 public int getX() { 738 return mX; 739 } 740 741 public int getY() { 742 return mY; 743 } 744 745 public final int getDrawX() { 746 final int x = getX(); 747 final OptionalAttributes attrs = mOptionalAttributes; 748 return (attrs == null) ? x : x + attrs.mVisualInsetsLeft; 749 } 750 751 public final int getDrawWidth() { 752 final OptionalAttributes attrs = mOptionalAttributes; 753 return (attrs == null) ? mWidth 754 : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; 755 } 756 757 /** 758 * Informs the key that it has been pressed, in case it needs to change its appearance or 759 * state. 760 * @see #onReleased() 761 */ 762 public void onPressed() { 763 mPressed = true; 764 } 765 766 /** 767 * Informs the key that it has been released, in case it needs to change its appearance or 768 * state. 769 * @see #onPressed() 770 */ 771 public void onReleased() { 772 mPressed = false; 773 } 774 775 public final boolean isEnabled() { 776 return mEnabled; 777 } 778 779 public void setEnabled(final boolean enabled) { 780 mEnabled = enabled; 781 } 782 783 public Rect getHitBox() { 784 return mHitBox; 785 } 786 787 /** 788 * Detects if a point falls on this key. 789 * @param x the x-coordinate of the point 790 * @param y the y-coordinate of the point 791 * @return whether or not the point falls on the key. If the key is attached to an edge, it 792 * will assume that all points between the key and the edge are considered to be on the key. 793 * @see #markAsLeftEdge(KeyboardParams) etc. 794 */ 795 public boolean isOnKey(final int x, final int y) { 796 return mHitBox.contains(x, y); 797 } 798 799 /** 800 * Returns the square of the distance to the nearest edge of the key and the given point. 801 * @param x the x-coordinate of the point 802 * @param y the y-coordinate of the point 803 * @return the square of the distance of the point from the nearest edge of the key 804 */ 805 public int squaredDistanceToEdge(final int x, final int y) { 806 final int left = getX(); 807 final int right = left + mWidth; 808 final int top = getY(); 809 final int bottom = top + mHeight; 810 final int edgeX = x < left ? left : (x > right ? right : x); 811 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 812 final int dx = x - edgeX; 813 final int dy = y - edgeY; 814 return dx * dx + dy * dy; 815 } 816 817 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = { 818 android.R.attr.state_checkable, 819 android.R.attr.state_checked 820 }; 821 822 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = { 823 android.R.attr.state_pressed, 824 android.R.attr.state_checkable, 825 android.R.attr.state_checked 826 }; 827 828 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = { 829 android.R.attr.state_checkable 830 }; 831 832 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = { 833 android.R.attr.state_pressed, 834 android.R.attr.state_checkable 835 }; 836 837 private final static int[] KEY_STATE_NORMAL = { 838 }; 839 840 private final static int[] KEY_STATE_PRESSED = { 841 android.R.attr.state_pressed 842 }; 843 844 private final static int[] KEY_STATE_EMPTY = { 845 android.R.attr.state_empty 846 }; 847 848 // action normal state (with properties) 849 private static final int[] KEY_STATE_ACTIVE_NORMAL = { 850 android.R.attr.state_active 851 }; 852 853 // action pressed state (with properties) 854 private static final int[] KEY_STATE_ACTIVE_PRESSED = { 855 android.R.attr.state_active, 856 android.R.attr.state_pressed 857 }; 858 859 /** 860 * Returns the background drawable for the key, based on the current state and type of the key. 861 * @return the background drawable of the key. 862 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 863 */ 864 public final Drawable selectBackgroundDrawable(final Drawable keyBackground, 865 final Drawable functionalKeyBackground, final Drawable spacebarBackground) { 866 final Drawable background; 867 if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) { 868 background = functionalKeyBackground; 869 } else if (getCode() == Constants.CODE_SPACE) { 870 background = spacebarBackground; 871 } else { 872 background = keyBackground; 873 } 874 final int[] stateSet; 875 switch (mBackgroundType) { 876 case BACKGROUND_TYPE_ACTION: 877 stateSet = mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL; 878 break; 879 case BACKGROUND_TYPE_STICKY_OFF: 880 stateSet = mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF; 881 break; 882 case BACKGROUND_TYPE_STICKY_ON: 883 stateSet = mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON; 884 break; 885 case BACKGROUND_TYPE_EMPTY: 886 stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY; 887 break; 888 case BACKGROUND_TYPE_FUNCTIONAL: 889 stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL; 890 break; 891 default: /* BACKGROUND_TYPE_NORMAL */ 892 stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL; 893 break; 894 } 895 background.setState(stateSet); 896 return background; 897 } 898 899 public static class Spacer extends Key { 900 public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle, 901 final KeyboardParams params, final KeyboardRow row) { 902 super(null /* keySpec */, keyAttr, keyStyle, params, row); 903 } 904 905 /** 906 * This constructor is being used only for divider in more keys keyboard. 907 */ 908 protected Spacer(final KeyboardParams params, final int x, final int y, final int width, 909 final int height) { 910 super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */, 911 null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width, 912 height, params.mHorizontalGap, params.mVerticalGap); 913 } 914 } 915} 916