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