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