/CircleIME/src/com/marvin/circleime/CircleGestureView.java

http://eyes-free.googlecode.com/ · Java · 594 lines · 496 code · 68 blank · 30 comment · 64 complexity · 6a7baa3a1fd9ff414bb481b4779d1884 MD5 · raw file

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