/CircleIME/src/com/marvin/circleime/CircleGestureView.java
Java | 594 lines | 496 code | 68 blank | 30 comment | 64 complexity | 6a7baa3a1fd9ff414bb481b4779d1884 MD5 | raw file
1 2package com.marvin.circleime; 3 4import com.google.tts.TTSEarcon; 5 6import android.content.Context; 7import android.graphics.Canvas; 8import android.graphics.Color; 9import android.graphics.Paint; 10import android.graphics.Typeface; 11import android.inputmethodservice.KeyboardView; 12import android.os.Vibrator; 13import android.util.AttributeSet; 14import android.util.Log; 15import android.view.KeyCharacterMap; 16import android.view.KeyEvent; 17import android.view.MotionEvent; 18import android.view.View; 19 20/** 21 * A transparent overlay which catches all touch events and uses a call back to 22 * return the gesture that the user performed. 23 * 24 * @author clchen@google.com (Charles L. Chen) 25 */ 26 27public class CircleGestureView extends KeyboardView { 28 private static final char alphabet[] = { 29 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 30 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' 31 }; 32 33 private static final long[] PATTERN = { 34 0, 1, 40, 41 35 }; 36 37 private static final int AE = 0; 38 39 private static final int IM = 1; 40 41 private static final int QU = 2; 42 43 private static final int Y = 4; 44 45 private static final int NONE = 5; 46 47 private static final int NUM0 = 6; 48 49 private static final int NUM1 = 7; 50 51 52 private final double left = 0; 53 54 private final double upleft = Math.PI * .25; 55 56 private final double up = Math.PI * .5; 57 58 private final double upright = Math.PI * .75; 59 60 private final double downright = -Math.PI * .75; 61 62 private final double down = -Math.PI * .5; 63 64 private final double downleft = -Math.PI * .25; 65 66 private final double right = Math.PI; 67 68 private final double rightWrap = -Math.PI; 69 70 private double downX; 71 72 private double downY; 73 74 private int currentWheel = 5; 75 76 private String currentCharacter = ""; 77 78 private String currentString = ""; 79 80 private double lastX; 81 82 private double lastY; 83 84 private int currentValue; 85 86 private boolean screenIsBeingTouched; 87 88 private Vibrator vibe; 89 90 private SoftKeyboard parent; 91 92 public CircleGestureView(Context context, AttributeSet attrs) { 93 super(context, attrs); 94 parent = (SoftKeyboard) context; 95 vibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 96 } 97 98 @Override 99 public boolean onTouchEvent(MotionEvent event) { 100 int action = event.getAction(); 101 float x = event.getX(); 102 float y = event.getY(); 103 if (action == MotionEvent.ACTION_DOWN) { 104 initiateMotion(x, y); 105 return true; 106 } else if (action == MotionEvent.ACTION_UP) { 107 confirmEntry(); 108 return true; 109 } else { 110 screenIsBeingTouched = true; 111 lastX = x; 112 lastY = y; 113 int prevVal = currentValue; 114 currentValue = evalMotion(x, y); 115 // Do nothing since we want a deadzone here; 116 // restore the state to the previous value. 117 if (currentValue == -1) { 118 currentValue = prevVal; 119 return true; 120 } 121 // There is a wheel that is active 122 if (currentValue != 5) { 123 if (currentWheel == NONE) { 124 currentWheel = getWheel(currentValue); 125 } 126 currentCharacter = getCharacter(currentWheel, currentValue); 127 } else { 128 currentCharacter = ""; 129 } 130 invalidate(); 131 if (prevVal != currentValue) { 132 parent.mTts.playEarcon("[tock]", 2, null); 133 if (currentCharacter.equals("")) { 134 // parent.tts.playEarcon(TTSEarcon.TOCK.toString(), 0, 135 // null); 136 } else { 137 String[] params = new String[1]; 138 // params[0] = TTSParams.VOICE_FEMALE.toString(); 139 // parent.tts.speak(currentCharacter, 0, null); //TODO: Fix 140 // me! 141 } 142 } 143 vibe.vibrate(PATTERN, -1); 144 } 145 return true; 146 } 147 148 public int getWheel(int value) { 149 switch (value) { 150 case 1: 151 return AE; 152 case 2: 153 return IM; 154 case 3: 155 return QU; 156 case 4: 157 return Y; 158 case 5: 159 return NONE; 160 case 6: 161 return Y; 162 case 7: 163 return QU; 164 case 8: 165 return IM; 166 case 9: 167 return AE; 168 default: 169 return NONE; 170 } 171 } 172 173 public String getCharacter(int wheel, int value) { 174 switch (wheel) { 175 case AE: 176 switch (value) { 177 case 1: 178 return "A"; 179 case 2: 180 return "B"; 181 case 3: 182 return "C"; 183 case 4: 184 return "H"; 185 case 5: 186 return ""; 187 case 6: 188 return "D"; 189 case 7: 190 return "G"; 191 case 8: 192 return "F"; 193 case 9: 194 return "E"; 195 default: 196 return ""; 197 } 198 case IM: 199 switch (value) { 200 case 1: 201 return "P"; 202 case 2: 203 return "I"; 204 case 3: 205 return "J"; 206 case 4: 207 return "O"; 208 case 5: 209 return ""; 210 case 6: 211 return "K"; 212 case 7: 213 return "N"; 214 case 8: 215 return "M"; 216 case 9: 217 return "L"; 218 default: 219 return ""; 220 } 221 case QU: 222 switch (value) { 223 case 1: 224 return "W"; 225 case 2: 226 return "X"; 227 case 3: 228 return "Q"; 229 case 4: 230 return "V"; 231 case 5: 232 return ""; 233 case 6: 234 return "R"; 235 case 7: 236 return "U"; 237 case 8: 238 return "T"; 239 case 9: 240 return "S"; 241 default: 242 return ""; 243 } 244 case Y: 245 switch (value) { 246 case 1: 247 return ","; 248 case 2: 249 return "!"; 250 case 3: 251 return "SPACE"; // return "MODE"; 252 case 4: 253 return "<-"; 254 case 5: 255 return ""; 256 case 6: 257 return "Y"; 258 case 7: 259 return "."; 260 case 8: 261 return "?"; 262 case 9: 263 return "Z"; 264 default: 265 return ""; 266 } 267 case NUM0: 268 switch (value) { 269 case 1: 270 return "0"; 271 case 2: 272 return "1"; 273 case 3: 274 return "2"; // return "MODE"; 275 case 4: 276 return "3"; 277 case 5: 278 return ""; 279 case 6: 280 return "4"; 281 case 7: 282 return "5"; 283 case 8: 284 return "6"; 285 case 9: 286 return "7"; 287 default: 288 return ""; 289 } 290 case NUM1: 291 switch (value) { 292 case 1: 293 return "8"; 294 case 2: 295 return "9"; 296 case 3: 297 return "<-"; 298 case 4: 299 return "*"; 300 case 5: 301 return ""; 302 case 6: 303 return "@"; 304 case 7: 305 return "&"; 306 case 8: 307 return "#"; 308 case 9: 309 return "$"; 310 default: 311 return "%"; 312 } 313 default: 314 return ""; 315 } 316 } 317 318 private void confirmEntry() { 319 screenIsBeingTouched = false; 320 int prevVal = currentValue; 321 currentValue = evalMotion(lastX, lastY); 322 // Do some correction if the user lifts up on deadspace 323 if (currentValue == -1) { 324 currentValue = prevVal; 325 } 326 // The user never got a number that wasn't deadspace, 327 // so assume 5. 328 if (currentValue == -1) { 329 currentValue = 5; 330 } 331 currentCharacter = getCharacter(currentWheel, currentValue); 332 if (currentCharacter.equals("<-")) { 333 currentCharacter = ""; 334 backspace(); 335 } else { 336 if (currentCharacter.equals("SPACE")) { 337 currentString = currentString + " "; 338 } 339 /* 340 * else if (currentCharacter.equals("MODE")) { // Do nothing } 341 */ 342 else { 343 currentString = currentString + currentCharacter; 344 } 345 // parent.tts.speak(currentCharacter, 0, null); 346 if (currentCharacter.equals("SPACE")) { 347 currentCharacter = " "; 348 } 349 if (currentCharacter.length() > 0) { 350 parent.sendKeyChar(currentCharacter.toLowerCase().charAt(0)); 351 } 352 } 353 invalidate(); 354 initiateMotion(lastX, lastY); 355 } 356 357 private void initiateMotion(double x, double y) { 358 downX = x; 359 downY = y; 360 lastX = x; 361 lastY = y; 362 currentValue = -1; 363 currentWheel = NONE; 364 currentCharacter = ""; 365 } 366 367 @Override 368 public void onDraw(Canvas canvas) { 369 // super.onDraw(canvas); 370 371 setBackgroundColor(Color.TRANSPARENT); 372 373 // Draw an indication that the IME is up - doing a border for now 374 Paint imeStatusPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 375 imeStatusPaint.setColor(Color.RED); 376 imeStatusPaint.setTextSize(14); 377 imeStatusPaint.setTypeface(Typeface.DEFAULT_BOLD); 378 Paint imeBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 379 imeBgPaint.setColor(Color.BLACK); 380 int fudgeFactor = 15; 381 int startY = 0; 382 383 canvas.drawRect(0, startY, getWidth(), startY + fudgeFactor, imeBgPaint); 384 canvas.drawText("IME Active", 10, startY + 13, imeStatusPaint); 385 386 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 387 paint.setColor(Color.WHITE); 388 paint.setTextAlign(Paint.Align.CENTER); 389 paint.setTypeface(Typeface.DEFAULT_BOLD); 390 391 int x = 5; 392 int y = 50; 393 paint.setTextSize(50); 394 paint.setTextAlign(Paint.Align.LEFT); 395 y -= paint.ascent() / 2; 396 397 // canvas.drawText(currentString, x, y, paint); 398 399 if (!screenIsBeingTouched) { 400 x = 5; 401 y = getHeight() - 40; 402 paint.setTextSize(20); 403 paint.setTextAlign(Paint.Align.LEFT); 404 y -= paint.ascent() / 2; 405 // canvas.drawText("Scroll apps with trackball.", x, y, paint); 406 407 x = 5; 408 y = getHeight() - 20; 409 paint.setTextSize(20); 410 paint.setTextAlign(Paint.Align.LEFT); 411 y -= paint.ascent() / 2; 412 // canvas.drawText("Press CALL to launch app.", x, y, paint); 413 } else { 414 int offset = 90; 415 416 int x1 = (int) downX - offset; 417 int y1 = (int) downY - offset; 418 int x2 = (int) downX; 419 int y2 = (int) downY - offset; 420 int x3 = (int) downX + offset; 421 int y3 = (int) downY - offset; 422 int x4 = (int) downX - offset; 423 int y4 = (int) downY; 424 int x6 = (int) downX + offset; 425 int y6 = (int) downY; 426 int x7 = (int) downX - offset; 427 int y7 = (int) downY + offset; 428 int x8 = (int) downX; 429 int y8 = (int) downY + offset; 430 int x9 = (int) downX + offset; 431 int y9 = (int) downY + offset; 432 433 y1 -= paint.ascent() / 2; 434 y2 -= paint.ascent() / 2; 435 y3 -= paint.ascent() / 2; 436 y4 -= paint.ascent() / 2; 437 y6 -= paint.ascent() / 2; 438 y7 -= paint.ascent() / 2; 439 y8 -= paint.ascent() / 2; 440 y9 -= paint.ascent() / 2; 441 442 switch (currentWheel) { 443 case AE: 444 paint.setColor(Color.RED); 445 drawCharacter("A", x1, y1, canvas, paint, currentCharacter.equals("A")); 446 drawCharacter("B", x2, y2, canvas, paint, currentCharacter.equals("B")); 447 drawCharacter("C", x3, y3, canvas, paint, currentCharacter.equals("C")); 448 drawCharacter("H", x4, y4, canvas, paint, currentCharacter.equals("H")); 449 drawCharacter("D", x6, y6, canvas, paint, currentCharacter.equals("D")); 450 drawCharacter("G", x7, y7, canvas, paint, currentCharacter.equals("G")); 451 drawCharacter("F", x8, y8, canvas, paint, currentCharacter.equals("F")); 452 drawCharacter("E", x9, y9, canvas, paint, currentCharacter.equals("E")); 453 break; 454 case IM: 455 paint.setColor(Color.BLUE); 456 drawCharacter("P", x1, y1, canvas, paint, currentCharacter.equals("P")); 457 drawCharacter("I", x2, y2, canvas, paint, currentCharacter.equals("I")); 458 drawCharacter("J", x3, y3, canvas, paint, currentCharacter.equals("J")); 459 drawCharacter("O", x4, y4, canvas, paint, currentCharacter.equals("O")); 460 drawCharacter("K", x6, y6, canvas, paint, currentCharacter.equals("K")); 461 drawCharacter("N", x7, y7, canvas, paint, currentCharacter.equals("N")); 462 drawCharacter("M", x8, y8, canvas, paint, currentCharacter.equals("M")); 463 drawCharacter("L", x9, y9, canvas, paint, currentCharacter.equals("L")); 464 break; 465 case QU: 466 paint.setColor(Color.GREEN); 467 drawCharacter("W", x1, y1, canvas, paint, currentCharacter.equals("W")); 468 drawCharacter("X", x2, y2, canvas, paint, currentCharacter.equals("X")); 469 drawCharacter("Q", x3, y3, canvas, paint, currentCharacter.equals("Q")); 470 drawCharacter("V", x4, y4, canvas, paint, currentCharacter.equals("V")); 471 drawCharacter("R", x6, y6, canvas, paint, currentCharacter.equals("R")); 472 drawCharacter("U", x7, y7, canvas, paint, currentCharacter.equals("U")); 473 drawCharacter("T", x8, y8, canvas, paint, currentCharacter.equals("T")); 474 drawCharacter("S", x9, y9, canvas, paint, currentCharacter.equals("S")); 475 break; 476 case Y: 477 paint.setColor(Color.YELLOW); 478 drawCharacter(",", x1, y1, canvas, paint, currentCharacter.equals(",")); 479 drawCharacter("!", x2, y2, canvas, paint, currentCharacter.equals("!")); 480 drawCharacter("SPACE", x3, y3, canvas, paint, currentCharacter.equals("SPACE")); 481 drawCharacter("<-", x4, y4, canvas, paint, currentCharacter.equals("<-")); 482 drawCharacter("Y", x6, y6, canvas, paint, currentCharacter.equals("Y")); 483 drawCharacter(".", x7, y7, canvas, paint, currentCharacter.equals(".")); 484 drawCharacter("?", x8, y8, canvas, paint, currentCharacter.equals("?")); 485 drawCharacter("Z", x9, y9, canvas, paint, currentCharacter.equals("Z")); 486 break; 487 default: 488 paint.setColor(Color.RED); 489 canvas.drawText("A", x1, y1, paint); 490 canvas.drawText("E", x9, y9, paint); 491 paint.setColor(Color.BLUE); 492 canvas.drawText("I", x2, y2, paint); 493 canvas.drawText("M", x8, y8, paint); 494 paint.setColor(Color.GREEN); 495 canvas.drawText("Q", x3, y3, paint); 496 canvas.drawText("U", x7, y7, paint); 497 paint.setColor(Color.YELLOW); 498 canvas.drawText("Y", x6, y6, paint); 499 canvas.drawText("<-", x4, y4, paint); 500 break; 501 } 502 } 503 504 } 505 506 public void backspace() { 507 parent.handleBackspace(); 508 String deletedCharacter = ""; 509 if (currentString.length() > 0) { 510 deletedCharacter = "" + currentString.charAt(currentString.length() - 1); 511 currentString = currentString.substring(0, currentString.length() - 1); 512 } 513 if (!deletedCharacter.equals("")) { 514 // parent.tts.speak(deletedCharacter + " deleted.", 0, null); 515 } else { 516 // parent.tts.playEarcon(TTSEarcon.TOCK.toString(), 0, null); 517 // parent.tts.playEarcon(TTSEarcon.TOCK.toString(), 1, null); 518 } 519 invalidate(); 520 } 521 522 private void drawCharacter(String character, int x, int y, Canvas canvas, Paint paint, 523 boolean isSelected) { 524 int regSize = 50; 525 int selectedSize = regSize * 2; 526 if (isSelected) { 527 paint.setTextSize(selectedSize); 528 } else { 529 paint.setTextSize(regSize); 530 } 531 canvas.drawText(character, x, y, paint); 532 } 533 534 public int evalMotion(double x, double y) { 535 float rTolerance = 25; 536 double thetaTolerance = (Math.PI / 16); 537 538 double r = Math.sqrt(((downX - x) * (downX - x)) + ((downY - y) * (downY - y))); 539 540 if (r < rTolerance) { 541 return 5; 542 } 543 544 double theta = Math.atan2(downY - y, downX - x); 545 546 if (Math.abs(theta - left) < thetaTolerance) { 547 return 4; 548 } else if (Math.abs(theta - upleft) < thetaTolerance) { 549 return 1; 550 } else if (Math.abs(theta - up) < thetaTolerance) { 551 return 2; 552 } else if (Math.abs(theta - upright) < thetaTolerance) { 553 return 3; 554 } else if (Math.abs(theta - downright) < thetaTolerance) { 555 return 9; 556 } else if (Math.abs(theta - down) < thetaTolerance) { 557 return 8; 558 } else if (Math.abs(theta - downleft) < thetaTolerance) { 559 return 7; 560 } else if ((theta > right - thetaTolerance) || (theta < rightWrap + thetaTolerance)) { 561 return 6; 562 } 563 564 // Off by more than the threshold, so it doesn't count 565 return -1; 566 } 567 568 569 private int keyboardMode = 0; 570 571 private void toggleKeyboardMode(){ 572 if (keyboardMode == 0){ 573 keyboardMode = 1; 574 parent.mTts.speak("Numbers", 2, null); 575 } else { 576 keyboardMode = 0; 577 parent.mTts.speak("Alpha", 2, null); 578 } 579 } 580 581 582 @Override 583 public boolean onKeyDown(int keyCode, KeyEvent event) { 584 String input = ""; 585 switch (keyCode) { 586 case KeyEvent.KEYCODE_MENU: 587 toggleKeyboardMode(); 588 return true; 589 } 590 return false; 591 } 592 593 594}