Key.java revision 13d5f6605be6a6e8d9e5dde5b204dc050a862550
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.keyboard; 18 19import android.content.res.Resources; 20import android.content.res.TypedArray; 21import android.graphics.Rect; 22import android.graphics.Typeface; 23import android.graphics.drawable.Drawable; 24import android.text.TextUtils; 25import android.util.Xml; 26 27import com.android.inputmethod.keyboard.internal.KeyStyles; 28import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle; 29import com.android.inputmethod.keyboard.internal.KeyboardBuilder; 30import com.android.inputmethod.keyboard.internal.KeyboardBuilder.ParseException; 31import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 32import com.android.inputmethod.keyboard.internal.KeyboardParams; 33import com.android.inputmethod.keyboard.internal.MoreKeySpecParser; 34import com.android.inputmethod.latin.R; 35 36import org.xmlpull.v1.XmlPullParser; 37 38import java.util.HashMap; 39import java.util.Map; 40 41/** 42 * Class for describing the position and characteristics of a single key in the keyboard. 43 */ 44public class Key { 45 /** 46 * The key code (unicode or custom code) that this key generates. 47 */ 48 public final int mCode; 49 50 /** Label to display */ 51 public final CharSequence mLabel; 52 /** Hint label to display on the key in conjunction with the label */ 53 public final CharSequence mHintLabel; 54 /** Flags of the label */ 55 private final int mLabelFlags; 56 private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01; 57 private static final int LABEL_FLAGS_ALIGN_RIGHT = 0x02; 58 private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08; 59 private static final int LABEL_FLAGS_LARGE_LETTER = 0x10; 60 private static final int LABEL_FLAGS_FONT_NORMAL = 0x20; 61 private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x40; 62 private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80; 63 private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x100; 64 private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200; 65 private static final int LABEL_FLAGS_HAS_UPPERCASE_LETTER = 0x400; 66 private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800; 67 private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000; 68 private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000; 69 private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000; 70 71 /** Icon to display instead of a label. Icon takes precedence over a label */ 72 private Drawable mIcon; 73 /** Preview version of the icon, for the preview popup */ 74 private Drawable mPreviewIcon; 75 76 /** Width of the key, not including the gap */ 77 public final int mWidth; 78 /** Height of the key, not including the gap */ 79 public final int mHeight; 80 /** The horizontal gap around this key */ 81 public final int mHorizontalGap; 82 /** The vertical gap below this key */ 83 public final int mVerticalGap; 84 /** The visual insets */ 85 public final int mVisualInsetsLeft; 86 public final int mVisualInsetsRight; 87 /** X coordinate of the key in the keyboard layout */ 88 public final int mX; 89 /** Y coordinate of the key in the keyboard layout */ 90 public final int mY; 91 /** Hit bounding box of the key */ 92 public final Rect mHitBox = new Rect(); 93 94 /** Text to output when pressed. This can be multiple characters, like ".com" */ 95 public final CharSequence mOutputText; 96 /** More keys */ 97 public final CharSequence[] mMoreKeys; 98 /** More keys maximum column number */ 99 public final int mMaxMoreKeysColumn; 100 101 /** Background type that represents different key background visual than normal one. */ 102 public final int mBackgroundType; 103 public static final int BACKGROUND_TYPE_NORMAL = 0; 104 public static final int BACKGROUND_TYPE_FUNCTIONAL = 1; 105 public static final int BACKGROUND_TYPE_ACTION = 2; 106 public static final int BACKGROUND_TYPE_STICKY = 3; 107 108 private final int mActionFlags; 109 private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01; 110 private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02; 111 112 /** The current pressed state of this key */ 113 private boolean mPressed; 114 /** If this is a sticky key, is its highlight on? */ 115 private boolean mHighlightOn; 116 /** Key is enabled and responds on press */ 117 private boolean mEnabled = true; 118 /** Whether this key needs to show the "..." popup hint for special purposes */ 119 private boolean mNeedsSpecialPopupHint; 120 121 // RTL parenthesis character swapping map. 122 private static final Map<Integer, Integer> sRtlParenthesisMap = new HashMap<Integer, Integer>(); 123 124 static { 125 // The all letters need to be mirrored are found at 126 // http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt 127 addRtlParenthesisPair('(', ')'); 128 addRtlParenthesisPair('[', ']'); 129 addRtlParenthesisPair('{', '}'); 130 addRtlParenthesisPair('<', '>'); 131 // \u00ab: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK 132 // \u00bb: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK 133 addRtlParenthesisPair('\u00ab', '\u00bb'); 134 // \u2039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK 135 // \u203a: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK 136 addRtlParenthesisPair('\u2039', '\u203a'); 137 // \u2264: LESS-THAN OR EQUAL TO 138 // \u2265: GREATER-THAN OR EQUAL TO 139 addRtlParenthesisPair('\u2264', '\u2265'); 140 } 141 142 private static void addRtlParenthesisPair(int left, int right) { 143 sRtlParenthesisMap.put(left, right); 144 sRtlParenthesisMap.put(right, left); 145 } 146 147 public static int getRtlParenthesisCode(int code, boolean isRtl) { 148 if (isRtl && sRtlParenthesisMap.containsKey(code)) { 149 return sRtlParenthesisMap.get(code); 150 } else { 151 return code; 152 } 153 } 154 155 private static int getCode(Resources res, KeyboardParams params, String moreKeySpec) { 156 return getRtlParenthesisCode( 157 MoreKeySpecParser.getCode(res, moreKeySpec), params.mIsRtlKeyboard); 158 } 159 160 private static Drawable getIcon(KeyboardParams params, String moreKeySpec) { 161 return params.mIconsSet.getIcon(MoreKeySpecParser.getIconId(moreKeySpec)); 162 } 163 164 /** 165 * This constructor is being used only for key in more keys keyboard. 166 */ 167 public Key(Resources res, KeyboardParams params, String moreKeySpec, 168 int x, int y, int width, int height) { 169 this(params, MoreKeySpecParser.getLabel(moreKeySpec), null, getIcon(params, moreKeySpec), 170 getCode(res, params, moreKeySpec), MoreKeySpecParser.getOutputText(moreKeySpec), 171 x, y, width, height); 172 } 173 174 /** 175 * This constructor is being used only for key in popup suggestions pane. 176 */ 177 public Key(KeyboardParams params, CharSequence label, CharSequence hintLabel, Drawable icon, 178 int code, CharSequence outputText, int x, int y, int width, int height) { 179 mHeight = height - params.mVerticalGap; 180 mHorizontalGap = params.mHorizontalGap; 181 mVerticalGap = params.mVerticalGap; 182 mVisualInsetsLeft = mVisualInsetsRight = 0; 183 mWidth = width - mHorizontalGap; 184 mHintLabel = hintLabel; 185 mLabelFlags = 0; 186 mBackgroundType = BACKGROUND_TYPE_NORMAL; 187 mActionFlags = 0; 188 mMoreKeys = null; 189 mMaxMoreKeysColumn = 0; 190 mLabel = label; 191 mOutputText = outputText; 192 mCode = code; 193 mIcon = icon; 194 // Horizontal gap is divided equally to both sides of the key. 195 mX = x + mHorizontalGap / 2; 196 mY = y; 197 mHitBox.set(x, y, x + width + 1, y + height); 198 } 199 200 /** 201 * Create a key with the given top-left coordinate and extract its attributes from the XML 202 * parser. 203 * @param res resources associated with the caller's context 204 * @param params the keyboard building parameters. 205 * @param row the row that this key belongs to. row's x-coordinate will be the right edge of 206 * this key. 207 * @param parser the XML parser containing the attributes for this key 208 * @param keyStyles active key styles set 209 */ 210 public Key(Resources res, KeyboardParams params, KeyboardBuilder.Row row, 211 XmlPullParser parser, KeyStyles keyStyles) { 212 final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap; 213 final int keyHeight = row.mRowHeight; 214 mVerticalGap = params.mVerticalGap; 215 mHeight = keyHeight - mVerticalGap; 216 217 final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), 218 R.styleable.Keyboard_Key); 219 220 final KeyStyle style; 221 if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) { 222 String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle); 223 style = keyStyles.getKeyStyle(styleName); 224 if (style == null) 225 throw new ParseException("Unknown key style: " + styleName, parser); 226 } else { 227 style = KeyStyles.getEmptyKeyStyle(); 228 } 229 230 final float keyXPos = row.getKeyX(keyAttr); 231 final float keyWidth = row.getKeyWidth(keyAttr, keyXPos); 232 final int keyYPos = row.getKeyY(); 233 234 // Horizontal gap is divided equally to both sides of the key. 235 mX = (int) (keyXPos + horizontalGap / 2); 236 mY = keyYPos; 237 mWidth = (int) (keyWidth - horizontalGap); 238 mHorizontalGap = (int) horizontalGap; 239 mHitBox.set((int)keyXPos, keyYPos, (int)(keyXPos + keyWidth) + 1, keyYPos + keyHeight); 240 // Update row to have current x coordinate. 241 row.setXPos(keyXPos + keyWidth); 242 243 final CharSequence[] moreKeys = style.getTextArray(keyAttr, 244 R.styleable.Keyboard_Key_moreKeys); 245 // In Arabic symbol layouts, we'd like to keep digits in more keys regardless of 246 // config_digit_more_keys_enabled. 247 if (params.mId.isAlphabetKeyboard() 248 && !res.getBoolean(R.bool.config_digit_more_keys_enabled)) { 249 mMoreKeys = MoreKeySpecParser.filterOut(res, moreKeys, MoreKeySpecParser.DIGIT_FILTER); 250 } else { 251 mMoreKeys = moreKeys; 252 } 253 mMaxMoreKeysColumn = style.getInt(keyAttr, 254 R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMiniKeyboardColumn); 255 256 mBackgroundType = style.getInt(keyAttr, 257 R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL); 258 mActionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags, 0); 259 260 final KeyboardIconsSet iconsSet = params.mIconsSet; 261 mVisualInsetsLeft = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr, 262 R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0); 263 mVisualInsetsRight = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr, 264 R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0); 265 mPreviewIcon = iconsSet.getIcon(style.getInt(keyAttr, 266 R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED)); 267 mIcon = iconsSet.getIcon(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIcon, 268 KeyboardIconsSet.ICON_UNDEFINED)); 269 final int shiftedIconId = style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted, 270 KeyboardIconsSet.ICON_UNDEFINED); 271 if (shiftedIconId != KeyboardIconsSet.ICON_UNDEFINED) { 272 final Drawable shiftedIcon = iconsSet.getIcon(shiftedIconId); 273 params.addShiftedIcon(this, shiftedIcon); 274 } 275 mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel); 276 277 mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel); 278 mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags, 0); 279 mOutputText = style.getText(keyAttr, R.styleable.Keyboard_Key_keyOutputText); 280 // Choose the first letter of the label as primary code if not 281 // specified. 282 final int code = style.getInt(keyAttr, R.styleable.Keyboard_Key_code, 283 Keyboard.CODE_UNSPECIFIED); 284 if (code == Keyboard.CODE_UNSPECIFIED && !TextUtils.isEmpty(mLabel)) { 285 final int firstChar = mLabel.charAt(0); 286 mCode = getRtlParenthesisCode(firstChar, params.mIsRtlKeyboard); 287 } else if (code != Keyboard.CODE_UNSPECIFIED) { 288 mCode = code; 289 } else { 290 mCode = Keyboard.CODE_DUMMY; 291 } 292 293 keyAttr.recycle(); 294 } 295 296 public void markAsLeftEdge(KeyboardParams params) { 297 mHitBox.left = params.mHorizontalEdgesPadding; 298 } 299 300 public void markAsRightEdge(KeyboardParams params) { 301 mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding; 302 } 303 304 public void markAsTopEdge(KeyboardParams params) { 305 mHitBox.top = params.mTopPadding; 306 } 307 308 public void markAsBottomEdge(KeyboardParams params) { 309 mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; 310 } 311 312 public boolean isSticky() { 313 return mBackgroundType == BACKGROUND_TYPE_STICKY; 314 } 315 316 public boolean isSpacer() { 317 return false; 318 } 319 320 public boolean isRepeatable() { 321 return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; 322 } 323 324 public boolean noKeyPreview() { 325 return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; 326 } 327 328 public Typeface selectTypeface(Typeface defaultTypeface) { 329 // TODO: Handle "bold" here too? 330 if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) { 331 return Typeface.DEFAULT; 332 } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) { 333 return Typeface.MONOSPACE; 334 } else { 335 return defaultTypeface; 336 } 337 } 338 339 public int selectTextSize(int letter, int largeLetter, int label, int hintLabel) { 340 if (mLabel.length() > 1 341 && (mLabelFlags & (LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO 342 | LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) { 343 return label; 344 } else if ((mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO) != 0) { 345 return hintLabel; 346 } else if ((mLabelFlags & LABEL_FLAGS_LARGE_LETTER) != 0) { 347 return largeLetter; 348 } else { 349 return letter; 350 } 351 } 352 353 public boolean isAlignLeft() { 354 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0; 355 } 356 357 public boolean isAlignRight() { 358 return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0; 359 } 360 361 public boolean isAlignLeftOfCenter() { 362 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0; 363 } 364 365 public boolean hasPopupHint() { 366 return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; 367 } 368 369 public void setNeedsSpecialPopupHint(boolean needsSpecialPopupHint) { 370 mNeedsSpecialPopupHint = needsSpecialPopupHint; 371 } 372 373 public boolean needsSpecialPopupHint() { 374 return mNeedsSpecialPopupHint; 375 } 376 377 public boolean hasUppercaseLetter() { 378 return (mLabelFlags & LABEL_FLAGS_HAS_UPPERCASE_LETTER) != 0; 379 } 380 381 public boolean hasHintLabel() { 382 return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; 383 } 384 385 public boolean hasLabelWithIconLeft() { 386 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0; 387 } 388 389 public boolean hasLabelWithIconRight() { 390 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0; 391 } 392 393 public boolean needsXScale() { 394 return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; 395 } 396 397 public Drawable getIcon() { 398 return mIcon; 399 } 400 401 public Drawable getPreviewIcon() { 402 return mPreviewIcon; 403 } 404 405 public void setIcon(Drawable icon) { 406 mIcon = icon; 407 } 408 409 public void setPreviewIcon(Drawable icon) { 410 mPreviewIcon = icon; 411 } 412 413 /** 414 * Informs the key that it has been pressed, in case it needs to change its appearance or 415 * state. 416 * @see #onReleased() 417 */ 418 public void onPressed() { 419 mPressed = true; 420 } 421 422 /** 423 * Informs the key that it has been released, in case it needs to change its appearance or 424 * state. 425 * @see #onPressed() 426 */ 427 public void onReleased() { 428 mPressed = false; 429 } 430 431 public void setHighlightOn(boolean highlightOn) { 432 mHighlightOn = highlightOn; 433 } 434 435 public boolean isEnabled() { 436 return mEnabled; 437 } 438 439 public void setEnabled(boolean enabled) { 440 mEnabled = enabled; 441 } 442 443 /** 444 * Detects if a point falls on this key. 445 * @param x the x-coordinate of the point 446 * @param y the y-coordinate of the point 447 * @return whether or not the point falls on the key. If the key is attached to an edge, it will 448 * assume that all points between the key and the edge are considered to be on the key. 449 * @see {@link #markAsLeftEdge(KeyboardParams)} etc. 450 */ 451 public boolean isOnKey(int x, int y) { 452 return mHitBox.contains(x, y); 453 } 454 455 /** 456 * Returns the square of the distance to the nearest edge of the key and the given point. 457 * @param x the x-coordinate of the point 458 * @param y the y-coordinate of the point 459 * @return the square of the distance of the point from the nearest edge of the key 460 */ 461 public int squaredDistanceToEdge(int x, int y) { 462 final int left = mX; 463 final int right = left + mWidth; 464 final int top = mY; 465 final int bottom = top + mHeight; 466 final int edgeX = x < left ? left : (x > right ? right : x); 467 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 468 final int dx = x - edgeX; 469 final int dy = y - edgeY; 470 return dx * dx + dy * dy; 471 } 472 473 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = { 474 android.R.attr.state_checkable, 475 android.R.attr.state_checked 476 }; 477 478 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = { 479 android.R.attr.state_pressed, 480 android.R.attr.state_checkable, 481 android.R.attr.state_checked 482 }; 483 484 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = { 485 android.R.attr.state_checkable 486 }; 487 488 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = { 489 android.R.attr.state_pressed, 490 android.R.attr.state_checkable 491 }; 492 493 private final static int[] KEY_STATE_NORMAL = { 494 }; 495 496 private final static int[] KEY_STATE_PRESSED = { 497 android.R.attr.state_pressed 498 }; 499 500 // functional normal state (with properties) 501 private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = { 502 android.R.attr.state_single 503 }; 504 505 // functional pressed state (with properties) 506 private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = { 507 android.R.attr.state_single, 508 android.R.attr.state_pressed 509 }; 510 511 // action normal state (with properties) 512 private static final int[] KEY_STATE_ACTIVE_NORMAL = { 513 android.R.attr.state_active 514 }; 515 516 // action pressed state (with properties) 517 private static final int[] KEY_STATE_ACTIVE_PRESSED = { 518 android.R.attr.state_active, 519 android.R.attr.state_pressed 520 }; 521 522 /** 523 * Returns the drawable state for the key, based on the current state and type of the key. 524 * @return the drawable state of the key. 525 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 526 */ 527 public int[] getCurrentDrawableState() { 528 final boolean pressed = mPressed; 529 530 switch (mBackgroundType) { 531 case BACKGROUND_TYPE_FUNCTIONAL: 532 return pressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL; 533 case BACKGROUND_TYPE_ACTION: 534 return pressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL; 535 case BACKGROUND_TYPE_STICKY: 536 if (mHighlightOn) { 537 return pressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON; 538 } else { 539 return pressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF; 540 } 541 default: /* BACKGROUND_TYPE_NORMAL */ 542 return pressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL; 543 } 544 } 545 546 public static class Spacer extends Key { 547 public Spacer(Resources res, KeyboardParams params, KeyboardBuilder.Row row, 548 XmlPullParser parser, KeyStyles keyStyles) { 549 super(res, params, row, parser, keyStyles); 550 } 551 552 /** 553 * This constructor is being used only for divider in more keys keyboard. 554 */ 555 public Spacer(KeyboardParams params, Drawable icon, int x, int y, int width, int height) { 556 super(params, null, null, icon, Keyboard.CODE_DUMMY, null, x, y, width, height); 557 } 558 559 @Override 560 public boolean isSpacer() { 561 return true; 562 } 563 } 564} 565