Key.java revision 9f01ed51d78d9a236d3c321a00ab74165a34630a
1/* 2 * Copyright (C) 2010 Google Inc. 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 com.android.inputmethod.keyboard.KeyboardParser.ParseException; 20import com.android.inputmethod.keyboard.KeyStyles.KeyStyle; 21import com.android.inputmethod.latin.R; 22 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.content.res.XmlResourceParser; 26import android.graphics.drawable.Drawable; 27import android.text.TextUtils; 28import android.util.Xml; 29 30/** 31 * Class for describing the position and characteristics of a single key in the keyboard. 32 */ 33public class Key { 34 /** 35 * All the key codes (unicode or custom code) that this key could generate, zero'th 36 * being the most important. 37 */ 38 public final int[] mCodes; 39 /** The unicode that this key generates in manual temporary upper case mode. */ 40 public final int mManualTemporaryUpperCaseCode; 41 42 /** Label to display */ 43 public final CharSequence mLabel; 44 /** Option of the label */ 45 public final int mLabelOption; 46 47 /** Icon to display instead of a label. Icon takes precedence over a label */ 48 private Drawable mIcon; 49 /** Preview version of the icon, for the preview popup */ 50 private Drawable mPreviewIcon; 51 /** Hint icon to display on the key in conjunction with the label */ 52 public final Drawable mHintIcon; 53 /** 54 * The hint icon to display on the key when keyboard is in manual temporary upper case 55 * mode. 56 */ 57 public final Drawable mManualTemporaryUpperCaseHintIcon; 58 59 /** Width of the key, not including the gap */ 60 public final int mWidth; 61 /** Height of the key, not including the gap */ 62 public final int mHeight; 63 /** The horizontal gap before this key */ 64 public final int mGap; 65 /** Whether this key is sticky, i.e., a toggle key */ 66 public final boolean mSticky; 67 /** X coordinate of the key in the keyboard layout */ 68 public final int mX; 69 /** Y coordinate of the key in the keyboard layout */ 70 public final int mY; 71 /** Text to output when pressed. This can be multiple characters, like ".com" */ 72 public final CharSequence mOutputText; 73 /** Popup characters */ 74 public final CharSequence mPopupCharacters; 75 /** 76 * If this key pops up a mini keyboard, this is the resource id for the XML layout for that 77 * keyboard. 78 */ 79 public final int mPopupResId; 80 81 /** 82 * Flags that specify the anchoring to edges of the keyboard for detecting touch events 83 * that are just out of the boundary of the key. This is a bit mask of 84 * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, 85 * {@link Keyboard#EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM}. 86 */ 87 public final int mEdgeFlags; 88 /** Whether this is a modifier key, such as Shift or Alt */ 89 public final boolean mModifier; 90 /** Whether this key repeats itself when held down */ 91 public final boolean mRepeatable; 92 93 /** The Keyboard that this key belongs to */ 94 private final Keyboard mKeyboard; 95 96 /** The current pressed state of this key */ 97 public boolean mPressed; 98 /** If this is a sticky key, is it on? */ 99 public boolean mOn; 100 101 private final static int[] KEY_STATE_NORMAL_ON = { 102 android.R.attr.state_checkable, 103 android.R.attr.state_checked 104 }; 105 106 private final static int[] KEY_STATE_PRESSED_ON = { 107 android.R.attr.state_pressed, 108 android.R.attr.state_checkable, 109 android.R.attr.state_checked 110 }; 111 112 private final static int[] KEY_STATE_NORMAL_OFF = { 113 android.R.attr.state_checkable 114 }; 115 116 private final static int[] KEY_STATE_PRESSED_OFF = { 117 android.R.attr.state_pressed, 118 android.R.attr.state_checkable 119 }; 120 121 private final static int[] KEY_STATE_NORMAL = { 122 }; 123 124 private final static int[] KEY_STATE_PRESSED = { 125 android.R.attr.state_pressed 126 }; 127 128 // functional normal state (with properties) 129 private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = { 130 android.R.attr.state_single 131 }; 132 133 // functional pressed state (with properties) 134 private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = { 135 android.R.attr.state_single, 136 android.R.attr.state_pressed 137 }; 138 139 /** Create an empty key with no attributes. */ 140 public Key(Row row, char letter, int x, int y) { 141 mKeyboard = row.getKeyboard(); 142 mHeight = row.mDefaultHeight - row.mVerticalGap; 143 mGap = row.mDefaultHorizontalGap; 144 mWidth = row.mDefaultWidth - mGap; 145 mEdgeFlags = row.mRowEdgeFlags; 146 mHintIcon = null; 147 mManualTemporaryUpperCaseHintIcon = null; 148 mManualTemporaryUpperCaseCode = 0; 149 mLabelOption = 0; 150 mModifier = false; 151 mSticky = false; 152 mRepeatable = false; 153 mOutputText = null; 154 mPopupCharacters = null; 155 mPopupResId = 0; 156 mLabel = String.valueOf(letter); 157 mCodes = new int[] { letter }; 158 // Horizontal gap is divided equally to both sides of the key. 159 mX = x + mGap / 2; 160 mY = y; 161 } 162 163 /** Create a key with the given top-left coordinate and extract its attributes from 164 * the XML parser. 165 * @param res resources associated with the caller's context 166 * @param row the row that this key belongs to. The row must already be attached to 167 * a {@link Keyboard}. 168 * @param x the x coordinate of the top-left 169 * @param y the y coordinate of the top-left 170 * @param parser the XML parser containing the attributes for this key 171 */ 172 public Key(Resources res, Row row, int x, int y, XmlResourceParser parser, 173 KeyStyles keyStyles) { 174 mKeyboard = row.getKeyboard(); 175 176 TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 177 R.styleable.Keyboard); 178 mHeight = KeyboardParser.getDimensionOrFraction(a, 179 R.styleable.Keyboard_rowHeight, 180 mKeyboard.getKeyboardHeight(), row.mDefaultHeight) - row.mVerticalGap; 181 mGap = KeyboardParser.getDimensionOrFraction(a, 182 R.styleable.Keyboard_horizontalGap, 183 mKeyboard.getDisplayWidth(), row.mDefaultHorizontalGap); 184 mWidth = KeyboardParser.getDimensionOrFraction(a, 185 R.styleable.Keyboard_keyWidth, 186 mKeyboard.getDisplayWidth(), row.mDefaultWidth) - mGap; 187 a.recycle(); 188 189 a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Key); 190 191 final KeyStyle style; 192 if (a.hasValue(R.styleable.Keyboard_Key_keyStyle)) { 193 String styleName = a.getString(R.styleable.Keyboard_Key_keyStyle); 194 style = keyStyles.getKeyStyle(styleName); 195 if (style == null) 196 throw new ParseException("Unknown key style: " + styleName, parser); 197 } else { 198 style = keyStyles.getEmptyKeyStyle(); 199 } 200 201 // Horizontal gap is divided equally to both sides of the key. 202 this.mX = x + mGap / 2; 203 this.mY = y; 204 205 mPreviewIcon = style.getDrawable(a, R.styleable.Keyboard_Key_iconPreview); 206 Keyboard.setDefaultBounds(mPreviewIcon); 207 final CharSequence popupCharacters = style.getText(a, 208 R.styleable.Keyboard_Key_popupCharacters); 209 final int popupResId = style.getResourceId(a, R.styleable.Keyboard_Key_popupKeyboard, 0); 210 // Set popup keyboard resource and characters only when both are specified. 211 if (popupResId != 0 && !TextUtils.isEmpty(popupCharacters)) { 212 mPopupResId = popupResId; 213 mPopupCharacters = popupCharacters; 214 } else { 215 mPopupResId = 0; 216 mPopupCharacters = null; 217 } 218 mRepeatable = style.getBoolean(a, R.styleable.Keyboard_Key_isRepeatable, false); 219 mModifier = style.getBoolean(a, R.styleable.Keyboard_Key_isModifier, false); 220 mSticky = style.getBoolean(a, R.styleable.Keyboard_Key_isSticky, false); 221 mEdgeFlags = style.getFlag(a, R.styleable.Keyboard_Key_keyEdgeFlags, 0) 222 | row.mRowEdgeFlags; 223 224 mIcon = style.getDrawable(a, R.styleable.Keyboard_Key_keyIcon); 225 Keyboard.setDefaultBounds(mIcon); 226 mHintIcon = style.getDrawable(a, R.styleable.Keyboard_Key_keyHintIcon); 227 Keyboard.setDefaultBounds(mHintIcon); 228 mManualTemporaryUpperCaseHintIcon = style.getDrawable(a, 229 R.styleable.Keyboard_Key_manualTemporaryUpperCaseHintIcon); 230 Keyboard.setDefaultBounds(mManualTemporaryUpperCaseHintIcon); 231 232 mLabel = style.getText(a, R.styleable.Keyboard_Key_keyLabel); 233 mLabelOption = style.getFlag(a, R.styleable.Keyboard_Key_keyLabelOption, 0); 234 mManualTemporaryUpperCaseCode = style.getInt(a, 235 R.styleable.Keyboard_Key_manualTemporaryUpperCaseCode, 0); 236 mOutputText = style.getText(a, R.styleable.Keyboard_Key_keyOutputText); 237 // Choose the first letter of the label as primary code if not specified. 238 final int[] codes = style.getIntArray(a, R.styleable.Keyboard_Key_codes); 239 if (codes == null && !TextUtils.isEmpty(mLabel)) { 240 mCodes = new int[] { mLabel.charAt(0) }; 241 } else { 242 mCodes = codes; 243 } 244 245 final Drawable shiftedIcon = style.getDrawable(a, 246 R.styleable.Keyboard_Key_shiftedIcon); 247 if (shiftedIcon != null) 248 mKeyboard.getShiftedIcons().put(this, shiftedIcon); 249 250 a.recycle(); 251 } 252 253 public Drawable getIcon() { 254 return mIcon; 255 } 256 257 public Drawable getPreviewIcon() { 258 return mPreviewIcon; 259 } 260 261 public void setIcon(Drawable icon) { 262 mIcon = icon; 263 } 264 265 public void setPreviewIcon(Drawable icon) { 266 mPreviewIcon = icon; 267 } 268 269 /** 270 * Informs the key that it has been pressed, in case it needs to change its appearance or 271 * state. 272 * @see #onReleased(boolean) 273 */ 274 public void onPressed() { 275 mPressed = !mPressed; 276 } 277 278 /** 279 * Changes the pressed state of the key. If it is a sticky key, it will also change the 280 * toggled state of the key if the finger was release inside. 281 * @param inside whether the finger was released inside the key 282 * @see #onPressed() 283 */ 284 public void onReleased(boolean inside) { 285 mPressed = !mPressed; 286 if (mSticky && !mKeyboard.isShiftLockEnabled(this)) 287 mOn = !mOn; 288 } 289 290 public boolean isInside(int x, int y) { 291 return mKeyboard.isInside(this, x, y); 292 } 293 294 /** 295 * Detects if a point falls on this key. 296 * @param x the x-coordinate of the point 297 * @param y the y-coordinate of the point 298 * @return whether or not the point falls on the key. If the key is attached to an edge, it will 299 * assume that all points between the key and the edge are considered to be on the key. 300 */ 301 public boolean isOnKey(int x, int y) { 302 final int flags = mEdgeFlags; 303 final boolean leftEdge = (flags & Keyboard.EDGE_LEFT) != 0; 304 final boolean rightEdge = (flags & Keyboard.EDGE_RIGHT) != 0; 305 final boolean topEdge = (flags & Keyboard.EDGE_TOP) != 0; 306 final boolean bottomEdge = (flags & Keyboard.EDGE_BOTTOM) != 0; 307 final int left = this.mX; 308 final int right = left + this.mWidth; 309 final int top = this.mY; 310 final int bottom = top + this.mHeight; 311 return (x >= left || leftEdge) && (x < right || rightEdge) 312 && (y >= top || topEdge) && (y < bottom || bottomEdge); 313 } 314 315 /** 316 * Returns the square of the distance to the nearest edge of the key and the given point. 317 * @param x the x-coordinate of the point 318 * @param y the y-coordinate of the point 319 * @return the square of the distance of the point from the nearest edge of the key 320 */ 321 public int squaredDistanceToEdge(int x, int y) { 322 final int left = this.mX; 323 final int right = left + this.mWidth; 324 final int top = this.mY; 325 final int bottom = top + this.mHeight; 326 final int edgeX = x < left ? left : (x > right ? right : x); 327 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 328 final int dx = x - edgeX; 329 final int dy = y - edgeY; 330 return dx * dx + dy * dy; 331 } 332 333 // sticky is used for shift key. If a key is not sticky and is modifier, 334 // the key will be treated as functional. 335 private boolean isFunctionalKey() { 336 return !mSticky && mModifier; 337 } 338 339 /** 340 * Returns the drawable state for the key, based on the current state and type of the key. 341 * @return the drawable state of the key. 342 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 343 */ 344 public int[] getCurrentDrawableState() { 345 if (isFunctionalKey()) { 346 if (mPressed) { 347 return KEY_STATE_FUNCTIONAL_PRESSED; 348 } else { 349 return KEY_STATE_FUNCTIONAL_NORMAL; 350 } 351 } 352 353 int[] states = KEY_STATE_NORMAL; 354 355 if (mOn) { 356 if (mPressed) { 357 states = KEY_STATE_PRESSED_ON; 358 } else { 359 states = KEY_STATE_NORMAL_ON; 360 } 361 } else { 362 if (mSticky) { 363 if (mPressed) { 364 states = KEY_STATE_PRESSED_OFF; 365 } else { 366 states = KEY_STATE_NORMAL_OFF; 367 } 368 } else { 369 if (mPressed) { 370 states = KEY_STATE_PRESSED; 371 } 372 } 373 } 374 return states; 375 } 376} 377