1/* 2 * Copyright (C) 2008-2012 OMRON SOFTWARE Co., Ltd. 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/* 17 * This file is porting from Android framework. 18 * frameworks/base/core/java/android/inputmethodservice/Keyboard.java 19 * 20 * package android.inputmethodservice; 21 */ 22package jp.co.omronsoft.openwnn; 23 24import android.content.Context; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.content.res.XmlResourceParser; 28import android.graphics.drawable.Drawable; 29import android.text.TextUtils; 30import android.util.Log; 31import android.util.TypedValue; 32import android.util.Xml; 33import android.util.DisplayMetrics; 34 35import java.io.IOException; 36import java.util.ArrayList; 37import java.util.List; 38import java.util.StringTokenizer; 39 40import org.xmlpull.v1.XmlPullParserException; 41 42/** 43 * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard 44 * consists of rows of keys. 45 * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> 46 * <pre> 47 * <Keyboard 48 * android:keyWidth="%10p" 49 * android:keyHeight="50px" 50 * android:horizontalGap="2px" 51 * android:verticalGap="2px" > 52 * <Row android:keyWidth="32px" > 53 * <Key android:keyLabel="A" /> 54 * ... 55 * </Row> 56 * ... 57 * </Keyboard> 58 * </pre> 59 */ 60public class Keyboard { 61 62 static final String TAG = "Keyboard"; 63 64 private static final String TAG_KEYBOARD = "Keyboard"; 65 private static final String TAG_ROW = "Row"; 66 private static final String TAG_KEY = "Key"; 67 68 /** Edge of left */ 69 public static final int EDGE_LEFT = 0x01; 70 71 /** Edge of right */ 72 public static final int EDGE_RIGHT = 0x02; 73 74 /** Edge of top */ 75 public static final int EDGE_TOP = 0x04; 76 77 /** Edge of bottom */ 78 public static final int EDGE_BOTTOM = 0x08; 79 80 /** Keycode of SHIFT */ 81 public static final int KEYCODE_SHIFT = -1; 82 83 /** Keycode of MODE_CHANGE */ 84 public static final int KEYCODE_MODE_CHANGE = -2; 85 86 /** Keycode of CANCEL */ 87 public static final int KEYCODE_CANCEL = -3; 88 89 /** Keycode of DONE */ 90 public static final int KEYCODE_DONE = -4; 91 92 /** Keycode of DELETE */ 93 public static final int KEYCODE_DELETE = -5; 94 95 /** Keycode of ALT */ 96 public static final int KEYCODE_ALT = -6; 97 98 /** Keyboard label **/ 99 private CharSequence mLabel; 100 101 /** Horizontal gap default for all rows */ 102 private int mDefaultHorizontalGap; 103 104 /** Default key width */ 105 private int mDefaultWidth; 106 107 /** Default key height */ 108 private int mDefaultHeight; 109 110 /** Default gap between rows */ 111 private int mDefaultVerticalGap; 112 113 /** Is the keyboard in the shifted state */ 114 private boolean mShifted; 115 116 /** Key instance for the shift key, if present */ 117 private Key mShiftKey; 118 119 /** Key index for the shift key, if present */ 120 private int mShiftKeyIndex = -1; 121 122 /** Current key width, while loading the keyboard */ 123 private int mKeyWidth; 124 125 /** Current key height, while loading the keyboard */ 126 private int mKeyHeight; 127 128 /** Total height of the keyboard, including the padding and keys */ 129 private int mTotalHeight; 130 131 /** 132 * Total width of the keyboard, including left side gaps and keys, but not any gaps on the 133 * right side. 134 */ 135 private int mTotalWidth; 136 137 /** List of keys in this keyboard */ 138 private List<Key> mKeys; 139 140 /** List of modifier keys such as Shift & Alt, if any */ 141 private List<Key> mModifierKeys; 142 143 /** Width of the screen available to fit the keyboard */ 144 private int mDisplayWidth; 145 146 /** Height of the screen */ 147 private int mDisplayHeight; 148 149 /** Keyboard mode, or zero, if none. */ 150 private int mKeyboardMode; 151 152 153 private static final int GRID_WIDTH = 10; 154 private static final int GRID_HEIGHT = 5; 155 private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT; 156 private int mCellWidth; 157 private int mCellHeight; 158 private int[][] mGridNeighbors; 159 private int mProximityThreshold; 160 /** Number of key widths from current touch point to search for nearest keys. */ 161 private static float SEARCH_DISTANCE = 1.8f; 162 163 /** 164 * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. 165 * Some of the key size defaults can be overridden per row from what the {@link Keyboard} 166 * defines. 167 */ 168 public static class Row { 169 /** Default width of a key in this row. */ 170 public int defaultWidth; 171 /** Default height of a key in this row. */ 172 public int defaultHeight; 173 /** Default horizontal gap between keys in this row. */ 174 public int defaultHorizontalGap; 175 /** Vertical gap following this row. */ 176 public int verticalGap; 177 /** 178 * Edge flags for this row of keys. Possible values that can be assigned are 179 * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM} 180 */ 181 public int rowEdgeFlags; 182 183 /** The keyboard mode for this row */ 184 public int mode; 185 186 private Keyboard parent; 187 188 /** Constructor */ 189 public Row(Keyboard parent) { 190 this.parent = parent; 191 } 192 193 /** Constructor */ 194 public Row(Resources res, Keyboard parent, XmlResourceParser parser) { 195 this.parent = parent; 196 TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 197 android.R.styleable.Keyboard); 198 defaultWidth = getDimensionOrFraction(a, 199 android.R.styleable.Keyboard_keyWidth, 200 parent.mDisplayWidth, parent.mDefaultWidth); 201 defaultHeight = getDimensionOrFraction(a, 202 android.R.styleable.Keyboard_keyHeight, 203 parent.mDisplayHeight, parent.mDefaultHeight); 204 defaultHorizontalGap = getDimensionOrFraction(a, 205 android.R.styleable.Keyboard_horizontalGap, 206 parent.mDisplayWidth, parent.mDefaultHorizontalGap); 207 verticalGap = getDimensionOrFraction(a, 208 android.R.styleable.Keyboard_verticalGap, 209 parent.mDisplayHeight, parent.mDefaultVerticalGap); 210 a.recycle(); 211 a = res.obtainAttributes(Xml.asAttributeSet(parser), 212 android.R.styleable.Keyboard_Row); 213 rowEdgeFlags = a.getInt(android.R.styleable.Keyboard_Row_rowEdgeFlags, 0); 214 mode = a.getResourceId(android.R.styleable.Keyboard_Row_keyboardMode, 215 0); 216 } 217 } 218 219 /** 220 * Class for describing the position and characteristics of a single key in the keyboard. 221 */ 222 public static class Key { 223 /** 224 * All the key codes (unicode or custom code) that this key could generate, zero'th 225 * being the most important. 226 */ 227 public int[] codes; 228 229 /** Label to display */ 230 public CharSequence label; 231 232 /** Icon to display instead of a label. Icon takes precedence over a label */ 233 public Drawable icon; 234 /** Preview version of the icon, for the preview popup */ 235 public Drawable iconPreview; 236 /** Width of the key, not including the gap */ 237 public int width; 238 /** Height of the key, not including the gap */ 239 public int height; 240 /** The horizontal gap before this key */ 241 public int gap; 242 /** Whether this key is sticky, i.e., a toggle key */ 243 public boolean sticky; 244 /** X coordinate of the key in the keyboard layout */ 245 public int x; 246 /** Y coordinate of the key in the keyboard layout */ 247 public int y; 248 /** The current pressed state of this key */ 249 public boolean pressed; 250 /** If this is a sticky key, is it on? */ 251 public boolean on; 252 /** Text to output when pressed. This can be multiple characters, like ".com" */ 253 public CharSequence text; 254 /** Popup characters */ 255 public CharSequence popupCharacters; 256 257 /** 258 * Flags that specify the anchoring to edges of the keyboard for detecting touch events 259 * that are just out of the boundary of the key. This is a bit mask of 260 * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and 261 * {@link Keyboard#EDGE_BOTTOM}. 262 */ 263 public int edgeFlags; 264 /** Whether this is a modifier key, such as Shift or Alt */ 265 public boolean modifier; 266 /** The keyboard that this key belongs to */ 267 private Keyboard keyboard; 268 /** 269 * If this key pops up a mini keyboard, this is the resource id for the XML layout for that 270 * keyboard. 271 */ 272 public int popupResId; 273 /** Whether this key repeats itself when held down */ 274 public boolean repeatable; 275 /** Whether this key is 2nd key */ 276 public boolean isSecondKey; 277 278 private final static int[] KEY_STATE_NORMAL_ON = { 279 android.R.attr.state_checkable, 280 android.R.attr.state_checked 281 }; 282 283 private final static int[] KEY_STATE_PRESSED_ON = { 284 android.R.attr.state_pressed, 285 android.R.attr.state_checkable, 286 android.R.attr.state_checked 287 }; 288 289 private final static int[] KEY_STATE_NORMAL_OFF = { 290 android.R.attr.state_checkable 291 }; 292 293 private final static int[] KEY_STATE_PRESSED_OFF = { 294 android.R.attr.state_pressed, 295 android.R.attr.state_checkable 296 }; 297 298 private final static int[] KEY_STATE_NORMAL = { 299 }; 300 301 private final static int[] KEY_STATE_PRESSED = { 302 android.R.attr.state_pressed 303 }; 304 305 /** Create an empty key with no attributes. */ 306 public Key(Row parent) { 307 keyboard = parent.parent; 308 height = parent.defaultHeight; 309 width = parent.defaultWidth; 310 gap = parent.defaultHorizontalGap; 311 edgeFlags = parent.rowEdgeFlags; 312 } 313 314 /** Create a key with the given top-left coordinate and extract its attributes from 315 * the XML parser. 316 * @param res resources associated with the caller's context 317 * @param parent the row that this key belongs to. The row must already be attached to 318 * a {@link Keyboard}. 319 * @param x the x coordinate of the top-left 320 * @param y the y coordinate of the top-left 321 * @param parser the XML parser containing the attributes for this key 322 */ 323 public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) { 324 this(parent); 325 326 this.x = x; 327 this.y = y; 328 329 TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 330 android.R.styleable.Keyboard); 331 332 width = getDimensionOrFraction(a, 333 android.R.styleable.Keyboard_keyWidth, 334 keyboard.mDisplayWidth, parent.defaultWidth); 335 height = getDimensionOrFraction(a, 336 android.R.styleable.Keyboard_keyHeight, 337 keyboard.mDisplayHeight, parent.defaultHeight); 338 gap = getDimensionOrFraction(a, 339 android.R.styleable.Keyboard_horizontalGap, 340 keyboard.mDisplayWidth, parent.defaultHorizontalGap); 341 a.recycle(); 342 a = res.obtainAttributes(Xml.asAttributeSet(parser), 343 android.R.styleable.Keyboard_Key); 344 this.x += gap; 345 TypedValue codesValue = new TypedValue(); 346 a.getValue(android.R.styleable.Keyboard_Key_codes, 347 codesValue); 348 if (codesValue.type == TypedValue.TYPE_INT_DEC 349 || codesValue.type == TypedValue.TYPE_INT_HEX) { 350 codes = new int[] { codesValue.data }; 351 } else if (codesValue.type == TypedValue.TYPE_STRING) { 352 codes = parseCSV(codesValue.string.toString()); 353 } 354 355 iconPreview = a.getDrawable(android.R.styleable.Keyboard_Key_iconPreview); 356 if (iconPreview != null) { 357 iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(), 358 iconPreview.getIntrinsicHeight()); 359 } 360 popupCharacters = a.getText( 361 android.R.styleable.Keyboard_Key_popupCharacters); 362 popupResId = a.getResourceId( 363 android.R.styleable.Keyboard_Key_popupKeyboard, 0); 364 repeatable = a.getBoolean( 365 android.R.styleable.Keyboard_Key_isRepeatable, false); 366 modifier = a.getBoolean( 367 android.R.styleable.Keyboard_Key_isModifier, false); 368 sticky = a.getBoolean( 369 android.R.styleable.Keyboard_Key_isSticky, false); 370 edgeFlags = a.getInt(android.R.styleable.Keyboard_Key_keyEdgeFlags, 0); 371 edgeFlags |= parent.rowEdgeFlags; 372 373 icon = a.getDrawable( 374 android.R.styleable.Keyboard_Key_keyIcon); 375 if (icon != null) { 376 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 377 } 378 label = a.getText(android.R.styleable.Keyboard_Key_keyLabel); 379 text = a.getText(android.R.styleable.Keyboard_Key_keyOutputText); 380 381 if (codes == null && !TextUtils.isEmpty(label)) { 382 codes = new int[] { label.charAt(0) }; 383 } 384 a.recycle(); 385 a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.WnnKeyboard_Key); 386 isSecondKey = a.getBoolean(R.styleable.WnnKeyboard_Key_isSecondKey, false); 387 a.recycle(); 388 } 389 390 /** 391 * Informs the key that it has been pressed, in case it needs to change its appearance or 392 * state. 393 * @see #onReleased(boolean) 394 */ 395 public void onPressed() { 396 pressed = !pressed; 397 } 398 399 /** 400 * Changes the pressed state of the key. If it is a sticky key, it will also change the 401 * toggled state of the key if the finger was release inside. 402 * @param inside whether the finger was released inside the key 403 * @see #onPressed() 404 */ 405 public void onReleased(boolean inside) { 406 pressed = !pressed; 407 if (sticky) { 408 on = !on; 409 } 410 } 411 412 int[] parseCSV(String value) { 413 int count = 0; 414 int lastIndex = 0; 415 if (value.length() > 0) { 416 count++; 417 while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) { 418 count++; 419 } 420 } 421 int[] values = new int[count]; 422 count = 0; 423 StringTokenizer st = new StringTokenizer(value, ","); 424 while (st.hasMoreTokens()) { 425 try { 426 values[count++] = Integer.parseInt(st.nextToken()); 427 } catch (NumberFormatException nfe) { 428 Log.e(TAG, "Error parsing keycodes " + value); 429 } 430 } 431 return values; 432 } 433 434 /** 435 * Detects if a point falls inside this key. 436 * @param x the x-coordinate of the point 437 * @param y the y-coordinate of the point 438 * @return whether or not the point falls inside the key. If the key is attached to an edge, 439 * it will assume that all points between the key and the edge are considered to be inside 440 * the key. 441 */ 442 public boolean isInside(int x, int y) { 443 boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0; 444 boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0; 445 boolean topEdge = (edgeFlags & EDGE_TOP) > 0; 446 boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0; 447 if ((x >= this.x || (leftEdge && x <= this.x + this.width)) 448 && (x < this.x + this.width || (rightEdge && x >= this.x)) 449 && (y >= this.y || (topEdge && y <= this.y + this.height)) 450 && (y < this.y + this.height || (bottomEdge && y >= this.y))) { 451 return true; 452 } else { 453 return false; 454 } 455 } 456 457 /** 458 * Detects if a area falls inside this key. 459 * @param x the x-coordinate of the area 460 * @param y the y-coordinate of the area 461 * @param w the width of the area 462 * @param h the height of the area 463 * @return whether or not the area falls inside the key. 464 */ 465 public boolean isInside(int x, int y, int w, int h) { 466 if ((this.x <= (x + w)) && (x <= (this.x + this.width)) 467 && (this.y <= (y + h)) && (y <= (this.y + this.height))) { 468 return true; 469 } else { 470 return false; 471 } 472 } 473 474 /** 475 * Returns the square of the distance between the center of the key and the given point. 476 * @param x the x-coordinate of the point 477 * @param y the y-coordinate of the point 478 * @return the square of the distance of the point from the center of the key 479 */ 480 public int squaredDistanceFrom(int x, int y) { 481 int xDist = this.x + width / 2 - x; 482 int yDist = this.y + height / 2 - y; 483 return xDist * xDist + yDist * yDist; 484 } 485 486 /** 487 * Returns the drawable state for the key, based on the current state and type of the key. 488 * @return the drawable state of the key. 489 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 490 */ 491 public int[] getCurrentDrawableState() { 492 int[] states = KEY_STATE_NORMAL; 493 494 if (on) { 495 if (pressed) { 496 states = KEY_STATE_PRESSED_ON; 497 } else { 498 states = KEY_STATE_NORMAL_ON; 499 } 500 } else { 501 if (sticky) { 502 if (pressed) { 503 states = KEY_STATE_PRESSED_OFF; 504 } else { 505 states = KEY_STATE_NORMAL_OFF; 506 } 507 } else { 508 if (pressed) { 509 states = KEY_STATE_PRESSED; 510 } 511 } 512 } 513 return states; 514 } 515 } 516 517 /** 518 * Creates a keyboard from the given xml key layout file. 519 * @param context the application or service context 520 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. 521 */ 522 public Keyboard(Context context, int xmlLayoutResId) { 523 this(context, xmlLayoutResId, 0); 524 } 525 526 /** 527 * Creates a keyboard from the given xml key layout file. Weeds out rows 528 * that have a keyboard mode defined but don't match the specified mode. 529 * @param context the application or service context 530 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. 531 * @param modeId keyboard mode identifier 532 */ 533 public Keyboard(Context context, int xmlLayoutResId, int modeId) { 534 DisplayMetrics dm = context.getResources().getDisplayMetrics(); 535 mDisplayWidth = dm.widthPixels; 536 mDisplayHeight = dm.heightPixels; 537 538 mDefaultHorizontalGap = 0; 539 mDefaultWidth = mDisplayWidth / 10; 540 mDefaultVerticalGap = 0; 541 mDefaultHeight = mDefaultWidth; 542 mKeys = new ArrayList<Key>(); 543 mModifierKeys = new ArrayList<Key>(); 544 mKeyboardMode = modeId; 545 loadKeyboard(context, context.getResources().getXml(xmlLayoutResId)); 546 } 547 548 /** 549 * <p>Creates a blank keyboard from the given resource file and populates it with the specified 550 * characters in left-to-right, top-to-bottom fashion, using the specified number of columns. 551 * </p> 552 * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as 553 * possible in each row.</p> 554 * @param context the application or service context 555 * @param layoutTemplateResId the layout template file, containing no keys. 556 * @param characters the list of characters to display on the keyboard. One key will be created 557 * for each character. 558 * @param columns the number of columns of keys to display. If this number is greater than the 559 * number of keys that can fit in a row, it will be ignored. If this number is -1, the 560 * keyboard will fit as many keys as possible in each row. 561 */ 562 public Keyboard(Context context, int layoutTemplateResId, 563 CharSequence characters, int columns, int horizontalPadding) { 564 this(context, layoutTemplateResId); 565 int x = 0; 566 int y = 0; 567 int column = 0; 568 mTotalWidth = 0; 569 570 Row row = new Row(this); 571 row.defaultHeight = mDefaultHeight; 572 row.defaultWidth = mDefaultWidth; 573 row.defaultHorizontalGap = mDefaultHorizontalGap; 574 row.verticalGap = mDefaultVerticalGap; 575 row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM; 576 final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; 577 for (int i = 0; i < characters.length(); i++) { 578 char c = characters.charAt(i); 579 if (column >= maxColumns 580 || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { 581 x = 0; 582 y += mDefaultVerticalGap + mDefaultHeight; 583 column = 0; 584 } 585 final Key key = new Key(row); 586 key.x = x; 587 key.y = y; 588 key.label = String.valueOf(c); 589 key.codes = new int[] { c }; 590 column++; 591 x += key.width + key.gap; 592 mKeys.add(key); 593 if (x > mTotalWidth) { 594 mTotalWidth = x; 595 } 596 } 597 mTotalHeight = y + mDefaultHeight; 598 } 599 600 /** 601 * Get the list of keys in this keyboard. 602 * 603 * @return The list of keys. 604 */ 605 public List<Key> getKeys() { 606 return mKeys; 607 } 608 609 /** 610 * Get the list of modifier keys such as Shift & Alt, if any. 611 * 612 * @return The list of modifier keys. 613 */ 614 public List<Key> getModifierKeys() { 615 return mModifierKeys; 616 } 617 618 protected int getHorizontalGap() { 619 return mDefaultHorizontalGap; 620 } 621 622 protected void setHorizontalGap(int gap) { 623 mDefaultHorizontalGap = gap; 624 } 625 626 protected int getVerticalGap() { 627 return mDefaultVerticalGap; 628 } 629 630 protected void setVerticalGap(int gap) { 631 mDefaultVerticalGap = gap; 632 } 633 634 protected int getKeyHeight() { 635 return mDefaultHeight; 636 } 637 638 protected void setKeyHeight(int height) { 639 mDefaultHeight = height; 640 } 641 642 protected int getKeyWidth() { 643 return mDefaultWidth; 644 } 645 646 protected void setKeyWidth(int width) { 647 mDefaultWidth = width; 648 } 649 650 /** 651 * Returns the total height of the keyboard 652 * @return the total height of the keyboard 653 */ 654 public int getHeight() { 655 return mTotalHeight; 656 } 657 658 /** 659 * Returns the total minimum width of the keyboard 660 * @return the total minimum width of the keyboard 661 */ 662 public int getMinWidth() { 663 return mTotalWidth; 664 } 665 666 /** 667 * Sets the keyboard to be shifted. 668 * 669 * @param shiftState the keyboard shift state. 670 * @return {@code true} if shift state changed. 671 */ 672 public boolean setShifted(boolean shiftState) { 673 if (mShiftKey != null) { 674 mShiftKey.on = shiftState; 675 } 676 if (mShifted != shiftState) { 677 mShifted = shiftState; 678 return true; 679 } 680 return false; 681 } 682 683 /** 684 * Returns whether keyboard is shift state or not. 685 * 686 * @return {@code true} if keyboard is shift state; otherwise, {@code false}. 687 */ 688 public boolean isShifted() { 689 return mShifted; 690 } 691 692 /** 693 * Returns the shift key index. 694 * 695 * @return the shift key index. 696 */ 697 public int getShiftKeyIndex() { 698 return mShiftKeyIndex; 699 } 700 701 private void computeNearestNeighbors() { 702 mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; 703 mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; 704 mGridNeighbors = new int[GRID_SIZE][]; 705 int[] indices = new int[mKeys.size()]; 706 final int gridWidth = GRID_WIDTH * mCellWidth; 707 final int gridHeight = GRID_HEIGHT * mCellHeight; 708 for (int x = 0; x < gridWidth; x += mCellWidth) { 709 for (int y = 0; y < gridHeight; y += mCellHeight) { 710 int count = 0; 711 for (int i = 0; i < mKeys.size(); i++) { 712 final Key key = mKeys.get(i); 713 if (key.squaredDistanceFrom(x, y) < mProximityThreshold || 714 key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold || 715 key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) 716 < mProximityThreshold || 717 key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold || 718 key.isInside(x, y, mCellWidth, mCellHeight)) { 719 indices[count++] = i; 720 } 721 } 722 int [] cell = new int[count]; 723 System.arraycopy(indices, 0, cell, 0, count); 724 mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; 725 } 726 } 727 } 728 729 /** 730 * Returns the indices of the keys that are closest to the given point. 731 * @param x the x-coordinate of the point 732 * @param y the y-coordinate of the point 733 * @return the array of integer indices for the nearest keys to the given point. If the given 734 * point is out of range, then an array of size zero is returned. 735 */ 736 public int[] getNearestKeys(int x, int y) { 737 if (mGridNeighbors == null) computeNearestNeighbors(); 738 if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) { 739 int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth); 740 if (index < GRID_SIZE) { 741 return mGridNeighbors[index]; 742 } 743 } 744 return new int[0]; 745 } 746 747 protected Row createRowFromXml(Resources res, XmlResourceParser parser) { 748 return new Row(res, this, parser); 749 } 750 751 protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 752 XmlResourceParser parser) { 753 return new Key(res, parent, x, y, parser); 754 } 755 756 private void loadKeyboard(Context context, XmlResourceParser parser) { 757 boolean inKey = false; 758 boolean inRow = false; 759 boolean leftMostKey = false; 760 int row = 0; 761 int x = 0; 762 int y = 0; 763 Key key = null; 764 Row currentRow = null; 765 Resources res = context.getResources(); 766 boolean skipRow = false; 767 768 try { 769 int event; 770 while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { 771 if (event == XmlResourceParser.START_TAG) { 772 String tag = parser.getName(); 773 if (TAG_ROW.equals(tag)) { 774 inRow = true; 775 x = 0; 776 currentRow = createRowFromXml(res, parser); 777 skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode; 778 if (skipRow) { 779 skipToEndOfRow(parser); 780 inRow = false; 781 } 782 } else if (TAG_KEY.equals(tag)) { 783 inKey = true; 784 key = createKeyFromXml(res, currentRow, x, y, parser); 785 mKeys.add(key); 786 if (key.codes[0] == KEYCODE_SHIFT) { 787 mShiftKey = key; 788 mShiftKeyIndex = mKeys.size()-1; 789 mModifierKeys.add(key); 790 } else if (key.codes[0] == KEYCODE_ALT) { 791 mModifierKeys.add(key); 792 } 793 } else if (TAG_KEYBOARD.equals(tag)) { 794 parseKeyboardAttributes(res, parser); 795 } 796 } else if (event == XmlResourceParser.END_TAG) { 797 if (inKey) { 798 inKey = false; 799 x += key.gap + key.width; 800 if (x > mTotalWidth) { 801 mTotalWidth = x; 802 } 803 } else if (inRow) { 804 inRow = false; 805 y += currentRow.verticalGap; 806 y += currentRow.defaultHeight; 807 row++; 808 } else { 809 } 810 } 811 } 812 } catch (Exception e) { 813 Log.e(TAG, "Parse error:" + e); 814 e.printStackTrace(); 815 } 816 mTotalHeight = y - mDefaultVerticalGap; 817 } 818 819 private void skipToEndOfRow(XmlResourceParser parser) 820 throws XmlPullParserException, IOException { 821 int event; 822 while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { 823 if (event == XmlResourceParser.END_TAG 824 && parser.getName().equals(TAG_ROW)) { 825 break; 826 } 827 } 828 } 829 830 private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) { 831 TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 832 android.R.styleable.Keyboard); 833 834 mDefaultWidth = getDimensionOrFraction(a, 835 android.R.styleable.Keyboard_keyWidth, 836 mDisplayWidth, mDisplayWidth / 10); 837 mDefaultHeight = getDimensionOrFraction(a, 838 android.R.styleable.Keyboard_keyHeight, 839 mDisplayHeight, 75); 840 mDefaultHorizontalGap = getDimensionOrFraction(a, 841 android.R.styleable.Keyboard_horizontalGap, 842 mDisplayWidth, 0); 843 mDefaultVerticalGap = getDimensionOrFraction(a, 844 android.R.styleable.Keyboard_verticalGap, 845 mDisplayHeight, 0); 846 mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE); 847 mProximityThreshold = mProximityThreshold * mProximityThreshold; 848 a.recycle(); 849 } 850 851 static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { 852 TypedValue value = a.peekValue(index); 853 if (value == null) return defValue; 854 if (value.type == TypedValue.TYPE_DIMENSION) { 855 return a.getDimensionPixelOffset(index, defValue); 856 } else if (value.type == TypedValue.TYPE_FRACTION) { 857 return Math.round(a.getFraction(index, base, base, defValue)); 858 } 859 return defValue; 860 } 861} 862