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