/tags/20110120/src/com/menny/android/anysoftkeyboard/keyboards/AnyKeyboard.java

http://softkeyboard.googlecode.com/ · Java · 984 lines · 630 code · 113 blank · 241 comment · 128 complexity · c87fdd617372bf81c39c7dfda49c15e8 MD5 · raw file

  1. package com.menny.android.anysoftkeyboard.keyboards;
  2. import java.io.IOException;
  3. import java.util.List;
  4. import java.util.StringTokenizer;
  5. import org.xmlpull.v1.XmlPullParserException;
  6. import android.content.Context;
  7. import android.content.res.Configuration;
  8. import android.content.res.Resources;
  9. import android.content.res.XmlResourceParser;
  10. import android.graphics.drawable.Drawable;
  11. import android.inputmethodservice.Keyboard;
  12. import android.text.TextUtils;
  13. import android.util.Log;
  14. import android.view.inputmethod.EditorInfo;
  15. import com.menny.android.anysoftkeyboard.AnyApplication;
  16. import com.menny.android.anysoftkeyboard.AnyKeyboardContextProvider;
  17. import com.menny.android.anysoftkeyboard.AnySoftKeyboardConfiguration;
  18. import com.menny.android.anysoftkeyboard.R;
  19. import com.menny.android.anysoftkeyboard.Workarounds;
  20. public abstract class AnyKeyboard extends Keyboard
  21. {
  22. //public static final String POPUP_FOR_QUESTION = "!/@\u0026\u00bf\u00a1";
  23. //public static final String POPUP_FOR_AT = "!/?\u0026\u00bf\u00a1";
  24. private final static String TAG = "ASK - AK";
  25. public final static int KEYCODE_LANG_CHANGE = -99;
  26. //public final static int KEYCODE_ALTER_LAYOUT = -98;
  27. public final static int KEYCODE_KEYBOARD_CYCLE = -97;
  28. public final static int KEYCODE_KEYBOARD_REVERSE_CYCLE = -96;
  29. public final static int KEYCODE_SMILEY = -10;
  30. public final static int KEYCODE_DOMAIN = -9;
  31. public static final int KEYCODE_LEFT = -20;
  32. public static final int KEYCODE_RIGHT = -21;
  33. public static final int KEYCODE_UP = -22;
  34. public static final int KEYCODE_DOWN = -23;
  35. public static final int KEYCODE_CTRL = -11;
  36. public static final int KEYCODE_CLIPBOARD = -12;
  37. public interface HardKeyboardAction
  38. {
  39. int getKeyCode();
  40. boolean isAltActive();
  41. boolean isShiftActive();
  42. void setNewKeyCode(int keyCode);
  43. }
  44. public interface HardKeyboardTranslator
  45. {
  46. /*
  47. * Gets the current state of the hard keyboard, and may change the output key-code.
  48. */
  49. void translatePhysicalCharacter(HardKeyboardAction action);
  50. }
  51. private static final String TAG_ROW = "Row";
  52. private static final String TAG_KEY = "Key";
  53. private static class KeyboardMetadata
  54. {
  55. public int keysCount = 0;
  56. public int rowHeight = 0;
  57. public int rowWidth = 0;
  58. public int verticalGap = 0;
  59. public boolean isTopRow = false;
  60. }
  61. private static final int SHIFT_OFF = 0;
  62. private static final int SHIFT_ON = 1;
  63. private static final int SHIFT_LOCKED = 2;
  64. private int mShiftState = SHIFT_OFF;
  65. //private final boolean mDebug;
  66. private final Drawable mOffShiftIcon;
  67. private final Drawable mOnShiftIcon;
  68. private final Drawable mOffShiftFeedbackIcon;
  69. private final Drawable mOnShiftFeedbackIcon;
  70. private final int mKeyboardMode;
  71. // private final int mDomainsIconId;
  72. private Key mShiftKey;
  73. private EnterKey mEnterKey;
  74. public Key langSwitch;
  75. //private Key mSwitchableKey;
  76. //private Key mQuestionMarkKey;
  77. private boolean mRightToLeftLayout = false;//the "super" ctor will create keys, and we'll set the correct value there.
  78. private final Context mKeyboardContext;
  79. private final AnyKeyboardContextProvider mASKContext;
  80. private boolean mTopRowWasCreated;
  81. private boolean mBottomRowWasCreated;
  82. private int mGenericRowsHeight = 0;
  83. private int mTopRowKeysCount = 0;
  84. // max(generic row widths)
  85. private int mMaxGenericRowsWidth = 0;
  86. protected AnyKeyboard(AnyKeyboardContextProvider askContext, Context context,//note: the context can be from a different package!
  87. int xmlLayoutResId, int mode)
  88. {
  89. //should use the package context for creating the layout
  90. super(context, xmlLayoutResId, mode);
  91. mKeyboardMode = mode;
  92. mKeyboardContext = context;
  93. mASKContext = askContext;
  94. addGenericRows(askContext, context, mode);
  95. //in wide shifts, we'll use the shift with the Globe
  96. Resources resources = askContext.getApplicationContext().getResources();
  97. if (mShiftKey != null)
  98. {
  99. // Drawable shiftWithGlobes = resources.getDrawable(R.drawable.sym_keyboard_shift_with_globe);
  100. //Log.v(TAG, "Deciding which icon to use for the SHIFT. Shift key width is "+mShiftKey.width+" and sym_keyboard_shift_with_globe width is "+shiftWithGlobes.getMinimumWidth());
  101. // if (mShiftKey.width > (shiftWithGlobes.getMinimumWidth() * 1.2))
  102. // {
  103. // mOnShiftIcon = resources.getDrawable(R.drawable.sym_keyboard_shift_with_globes_on);
  104. // mOffShiftIcon = shiftWithGlobes;
  105. // mOnShiftFeedbackIcon = resources.getDrawable(R.drawable.sym_keyboard_shift_with_globes_on_feedback);
  106. // mOffShiftFeedbackIcon = resources.getDrawable(R.drawable.sym_keyboard_shift_with_globe_feedback);
  107. // }
  108. // else
  109. // {
  110. mOnShiftIcon = resources.getDrawable(R.drawable.sym_keyboard_shift_on);
  111. mOffShiftIcon = resources.getDrawable(R.drawable.sym_keyboard_shift);
  112. mOnShiftFeedbackIcon = resources.getDrawable(R.drawable.sym_keyboard_feedback_shift_on);
  113. mOffShiftFeedbackIcon = resources.getDrawable(R.drawable.sym_keyboard_feedback_shift);
  114. // }
  115. mOnShiftFeedbackIcon.setBounds(0, 0,
  116. mOnShiftFeedbackIcon.getIntrinsicWidth(), mOnShiftFeedbackIcon.getIntrinsicHeight());
  117. mOffShiftFeedbackIcon.setBounds(0, 0,
  118. mOffShiftFeedbackIcon.getIntrinsicWidth(), mOffShiftFeedbackIcon.getIntrinsicHeight());
  119. mShiftKey.icon = mOffShiftIcon;
  120. mShiftKey.iconPreview = mOffShiftFeedbackIcon;
  121. }
  122. else
  123. {
  124. mOnShiftIcon = null;
  125. mOffShiftIcon = null;
  126. mOnShiftFeedbackIcon = null;
  127. mOffShiftFeedbackIcon = null;
  128. Log.v(TAG, "No shift key, so no handling images.");
  129. }
  130. }
  131. public void initKeysMembers()
  132. {
  133. final Resources localResources = getASKContext().getApplicationContext().getResources();
  134. for(final Key key : getKeys())
  135. {
  136. if (key.y == 0) key.edgeFlags = Keyboard.EDGE_TOP;
  137. //Log.d(TAG, "Key x:"+key.x+" y:"+key.y+" width:"+key.width+" height:"+key.height);
  138. if ((key.codes != null) && (key.codes.length > 0))
  139. {
  140. final int primaryCode = key.codes[0];
  141. //detecting LTR languages
  142. if (Workarounds.isRightToLeftCharacter((char)primaryCode))
  143. mRightToLeftLayout = true;//one is enough
  144. switch(primaryCode)
  145. {
  146. case AnyKeyboard.KEYCODE_DELETE:
  147. setIconIfNeeded(key, localResources, R.drawable.sym_keyboard_delete_small , R.drawable.sym_keyboard_feedback_delete);
  148. break;
  149. // case AnyKeyboard.KEYCODE_SHIFT:
  150. // key.icon = localResources.getDrawable(R.drawable.sym_keyboard_shift);
  151. // break;
  152. case AnyKeyboard.KEYCODE_CTRL:
  153. setIconIfNeeded(key, localResources, R.drawable.sym_keyboard_ctrl, -1);
  154. break;
  155. case 32://SPACE
  156. setIconIfNeeded(key, localResources, R.drawable.sym_keyboard_space, R.drawable.sym_keyboard_feedback_space);
  157. break;
  158. case 9://TAB
  159. setIconIfNeeded(key, localResources, R.drawable.tab_key, -1);
  160. break;
  161. case AnyKeyboard.KEYCODE_LANG_CHANGE:
  162. setIconIfNeeded(key, localResources, R.drawable.globe, -1);
  163. break;
  164. case AnyKeyboard.KEYCODE_SMILEY:
  165. //fixing icons
  166. if (AnyApplication.getConfig().showIconForSmileyKey())
  167. {
  168. setIconIfNeeded(key, localResources, R.drawable.sym_keyboard_smiley, R.drawable.sym_keyboard_smiley_feedback);
  169. }
  170. else
  171. {
  172. key.label = AnyApplication.getConfig().getSmileyText().trim();
  173. key.icon = null;
  174. key.iconPreview = null;
  175. }
  176. key.popupResId = R.xml.popup_smileys;
  177. break;
  178. case AnyKeyboard.KEYCODE_DOMAIN:
  179. //fixing icons
  180. //setIconIfNeeded(key, localResources, R.drawable.sym_keyboard_key_domain, R.drawable.sym_keyboard_key_domain_wide, R.drawable.sym_keyboard_key_domain_preview);
  181. key.label = AnyApplication.getConfig().getDomainText().trim();
  182. key.popupResId = R.xml.popup_domains;
  183. break;
  184. // case 63:
  185. // if ((key.edgeFlags & Keyboard.EDGE_BOTTOM) != 0)
  186. // {
  187. // mQuestionMarkKey = key;
  188. // }
  189. // break;
  190. default:
  191. //setting the character label
  192. if (isAlphabetKey(key) && (key.label == null || key.label.length() == 0) && (key.icon == null))
  193. {
  194. final char code = (char)key.codes[0];
  195. if (code > 0 && !Character.isWhitespace(code))
  196. key.label = ""+code;
  197. else
  198. key.label = " ";
  199. }
  200. }
  201. }
  202. }
  203. }
  204. private void addGenericRows(AnyKeyboardContextProvider askContext, Context context, int mode) {
  205. final String keysMode = AnyApplication.getConfig().getChangeLayoutKeysSize();
  206. final KeyboardMetadata topMd;
  207. if (!mTopRowWasCreated)
  208. {
  209. if (keysMode.equals("None"))
  210. {
  211. topMd = null;
  212. }
  213. else if (keysMode.equals("Big"))
  214. {
  215. topMd = addKeyboardRow(askContext.getApplicationContext(), R.xml.generic_top_row, mode);
  216. }
  217. else
  218. {
  219. topMd = addKeyboardRow(askContext.getApplicationContext(), R.xml.generic_half_top_row, mode);
  220. }
  221. if (topMd != null)
  222. fixKeyboardDueToGenericRow(topMd);
  223. }
  224. if (!mBottomRowWasCreated)
  225. {
  226. KeyboardMetadata bottomMd = addKeyboardRow(askContext.getApplicationContext(), R.xml.generic_bottom_row, mode);
  227. fixKeyboardDueToGenericRow(bottomMd);
  228. }
  229. }
  230. private void fixKeyboardDueToGenericRow(KeyboardMetadata md) {
  231. mGenericRowsHeight += md.rowHeight + md.verticalGap;
  232. if (md.isTopRow)
  233. {
  234. mTopRowKeysCount += md.keysCount;
  235. List<Key> keys = getKeys();
  236. for(int keyIndex = md.keysCount; keyIndex < keys.size(); keyIndex++)
  237. {
  238. final Key key = keys.get(keyIndex);
  239. key.y += md.rowHeight + md.verticalGap;
  240. if (key instanceof LessSensitiveAnyKey)
  241. ((LessSensitiveAnyKey)key).resetSenitivity();//reseting cause the key may be offseted now (generic rows)
  242. }
  243. } else {
  244. // The height should not include any gap below that last row
  245. // this corresponds to
  246. // mTotalHeight = y - mDefaultVerticalGap;
  247. // in the Keyboard class from Android sources
  248. // Note that we are using keyboard default vertical gap (instead of row vertical gap)
  249. // as this is done also in Android sources.
  250. mGenericRowsHeight -= getVerticalGap();
  251. }
  252. }
  253. private KeyboardMetadata addKeyboardRow(Context context, int rowResId, int mode) {
  254. XmlResourceParser parser = context.getResources().getXml(rowResId);
  255. List<Key> keys = getKeys();
  256. boolean inKey = false;
  257. boolean inRow = false;
  258. //boolean leftMostKey = false;
  259. boolean skipRow = false;
  260. int row = 0;
  261. int x = 0;
  262. int y = 0;
  263. Key key = null;
  264. Row currentRow = null;
  265. Resources res = context.getResources();
  266. KeyboardMetadata m = new KeyboardMetadata();
  267. try {
  268. int event;
  269. while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
  270. if (event == XmlResourceParser.START_TAG) {
  271. String tag = parser.getName();
  272. if (TAG_ROW.equals(tag)) {
  273. inRow = true;
  274. x = 0;
  275. currentRow = createRowFromXml(res, parser);
  276. skipRow = currentRow.mode != 0 && currentRow.mode != mode;
  277. if (skipRow) {
  278. currentRow = null;
  279. skipToEndOfRow(parser);
  280. inRow = false;
  281. }
  282. else
  283. {
  284. m.isTopRow = currentRow.rowEdgeFlags == Keyboard.EDGE_TOP;
  285. if (!m.isTopRow) {
  286. //the bottom row Y should be last
  287. // The last coordinate is height + keyboard's default vertical gap
  288. // since mTotalHeight = y - mDefaultVerticalGap; (see loadKeyboard
  289. // in the android sources)
  290. // We use our overriden getHeight method which
  291. // is just fixed so that it includes the first generic row.
  292. y = getHeight() + getVerticalGap();
  293. }
  294. m.rowHeight = currentRow.defaultHeight;
  295. m.verticalGap = currentRow.verticalGap;
  296. }
  297. } else if (TAG_KEY.equals(tag)) {
  298. inKey = true;
  299. key = createKeyFromXml(res, currentRow, x, y, parser);
  300. if (m.isTopRow)
  301. keys.add(m.keysCount, key);
  302. else
  303. keys.add(key);
  304. m.keysCount++;
  305. }
  306. } else if (event == XmlResourceParser.END_TAG) {
  307. if (inKey) {
  308. inKey = false;
  309. x += (key.gap + key.width);
  310. if (x > m.rowWidth) {
  311. m.rowWidth = x;
  312. // We keep generic row max width updated
  313. mMaxGenericRowsWidth = Math.max(mMaxGenericRowsWidth, m.rowWidth);
  314. }
  315. } else if (inRow) {
  316. inRow = false;
  317. y += currentRow.verticalGap;
  318. y += currentRow.defaultHeight;
  319. row++;
  320. } else {
  321. // TODO: error or extend?
  322. }
  323. }
  324. }
  325. } catch (Exception e) {
  326. Log.e(TAG, "Parse error:" + e);
  327. e.printStackTrace();
  328. }
  329. //mTotalHeight = y - mDefaultVerticalGap;
  330. return m;
  331. }
  332. private void skipToEndOfRow(XmlResourceParser parser) throws XmlPullParserException, IOException
  333. {
  334. int event;
  335. while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
  336. if (event == XmlResourceParser.END_TAG
  337. && parser.getName().equals(TAG_ROW)) {
  338. break;
  339. }
  340. }
  341. }
  342. /*required overrides*/
  343. @Override
  344. public int getHeight() {
  345. return super.getHeight() + mGenericRowsHeight;
  346. }
  347. // minWidth is actually 'total width', see android framework source code
  348. @Override
  349. public int getMinWidth() {
  350. return Math.max(mMaxGenericRowsWidth, super.getMinWidth());
  351. }
  352. @Override
  353. public int getShiftKeyIndex() {
  354. return super.getShiftKeyIndex() + mTopRowKeysCount;
  355. }
  356. private void setIconIfNeeded(Key key, Resources localResources,
  357. int iconId, int iconWideId,
  358. int iconFeedbackId) {
  359. Drawable wider = localResources.getDrawable(iconWideId);
  360. if ((wider.getMinimumWidth()*1.1) < key.width)
  361. iconId = iconWideId;
  362. setKeyIcons(key, localResources, iconId, iconFeedbackId);
  363. }
  364. private void setIconIfNeeded(Key key, Resources localResources, int iconId, int iconFeedbackId) {
  365. if ((key.icon != null) || (!TextUtils.isEmpty(key.label)))
  366. return;
  367. setKeyIcons(key, localResources, iconId, iconFeedbackId);
  368. }
  369. private void setKeyIcons(Key key, Resources localResources, int iconId,
  370. int iconFeedbackId) {
  371. key.icon = localResources.getDrawable(iconId);
  372. if (iconFeedbackId > 0)
  373. {
  374. Drawable preview = localResources.getDrawable(iconFeedbackId);
  375. preview.setBounds(0, 0,
  376. preview.getIntrinsicWidth(), preview.getIntrinsicHeight());
  377. key.iconPreview = preview;
  378. key.label = null;
  379. }
  380. }
  381. protected AnyKeyboardContextProvider getASKContext()
  382. {
  383. return mASKContext;
  384. }
  385. public Context getKeyboardContext()
  386. {
  387. return mKeyboardContext;
  388. }
  389. public abstract String getDefaultDictionaryLocale();
  390. //this function is called from within the super constructor.
  391. @Override
  392. protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
  393. XmlResourceParser parser) {
  394. AnyKey key = new AnyKey(res, parent, x, y, parser);
  395. if ((key.codes != null) && (key.codes.length > 0))
  396. {
  397. final int primaryCode = key.codes[0];
  398. //creating less sensitive keys if required
  399. switch(primaryCode)
  400. {
  401. case 0://disabled
  402. key.disable();
  403. break;
  404. case 10://enter
  405. key = mEnterKey = new EnterKey(res, parent, x, y, parser);
  406. break;
  407. case KEYCODE_SHIFT:
  408. mShiftKey = key;//I want the reference used by the super.
  409. break;
  410. case KEYCODE_DELETE://delete
  411. key = new LessSensitiveAnyKey(res, parent, x, y, parser);
  412. break;
  413. case -99:
  414. langSwitch = key;
  415. break;
  416. }
  417. }
  418. // if (mDebug)
  419. // {
  420. // final int primaryKey = ((key.codes != null) && key.codes.length > 0)?
  421. // key.codes[0] : -1;
  422. // Log.v(TAG, "Key '"+primaryKey+"' will have - width: "+key.width+", height:"+key.height+", text: '"+key.label+"'.");
  423. // }
  424. setPopupKeyChars(key);
  425. if (!TextUtils.isEmpty(key.label))
  426. key.label = Workarounds.workaroundCorrectStringDirection(key.label);
  427. return key;
  428. }
  429. @Override
  430. protected Row createRowFromXml(Resources res, XmlResourceParser parser)
  431. {
  432. Row aRow = super.createRowFromXml(res, parser);
  433. if (aRow.mode > 0)
  434. aRow.mode = res.getInteger(aRow.mode);//switching to the mode!
  435. //Log.d(TAG, "Row mode: "+aRow.mode);
  436. AnySoftKeyboardConfiguration config = AnyApplication.getConfig();
  437. final int orientation = config.getDeviceOrientation();
  438. if (orientation != Configuration.ORIENTATION_LANDSCAPE)//I want to support other orientations too (like square)
  439. aRow.defaultHeight = (int)(aRow.defaultHeight * config.getKeysHeightFactorInPortrait());
  440. else
  441. aRow.defaultHeight = (int)(aRow.defaultHeight * config.getKeysHeightFactorInLandscape());
  442. if ((aRow.rowEdgeFlags & Keyboard.EDGE_TOP) != 0)
  443. mTopRowWasCreated = true;
  444. if ((aRow.rowEdgeFlags & Keyboard.EDGE_BOTTOM) != 0)
  445. mBottomRowWasCreated = true;
  446. return aRow;
  447. }
  448. private boolean isAlphabetKey(Key key) {
  449. return (!key.modifier) &&
  450. (!key.sticky) &&
  451. (!key.repeatable) &&
  452. (key.icon == null) &&
  453. (key.codes[0] > 0);
  454. }
  455. public boolean isLetter(char keyValue)
  456. {
  457. return (Character.isLetter(keyValue) || (keyValue == '\''));
  458. }
  459. /**
  460. * This looks at the ime options given by the current editor, to set the
  461. * appropriate label on the keyboard's enter key (if it has one).
  462. */
  463. public void setImeOptions(Resources res, EditorInfo editor) {
  464. if (AnySoftKeyboardConfiguration.DEBUG)
  465. {
  466. if (editor == null)
  467. Log.d(TAG, "AnyKeyboard.setImeOptions");
  468. else
  469. Log.d(TAG, "AnyKeyboard.setImeOptions. package: "+editor.packageName+", id:"+editor.fieldId);
  470. }
  471. if (mEnterKey == null) {
  472. return;
  473. }
  474. //Issue 254: we know of a known Android Messaging bug
  475. //http://code.google.com/p/android/issues/detail?id=2739
  476. if (Workarounds.doubleActionKeyDisableWorkAround(editor))
  477. {//package: com.android.mms, id:2131361817
  478. mEnterKey.disable();
  479. return;
  480. }
  481. int options = (editor == null)? 0 : editor.imeOptions;
  482. // CharSequence imeLabel = (editor == null)? null :editor.actionLabel;
  483. // int imeActionId = (editor == null)? -1 :editor.actionId;
  484. mEnterKey.enable();
  485. //Used in conjunction with a custom action, this indicates that the action should not be available in-line
  486. //as a replacement for the "enter" key. Typically this is because the action has such a significant impact
  487. //or is not recoverable enough that accidentally hitting it should be avoided, such as sending a message.
  488. //Note that TextView will automatically set this flag for you on multi-line text views.
  489. boolean inNoEnterActionMode = ((options&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0);
  490. final int action = (options&EditorInfo.IME_MASK_ACTION);
  491. if (AnySoftKeyboardConfiguration.DEBUG)
  492. Log.d(TAG, "Input Connection ENTER key with action: "+action + " and NO_ACTION flag is: "+inNoEnterActionMode);
  493. if (inNoEnterActionMode)
  494. {
  495. //this means that the ENTER should not be replaced with a custom action.
  496. //maybe in future ASK releases, we'll add the custom action key.
  497. setKeyIcons(mEnterKey, res, R.drawable.sym_keyboard_return, R.drawable.sym_keyboard_feedback_return);
  498. }
  499. else
  500. {
  501. switch (action) {
  502. case EditorInfo.IME_ACTION_GO:
  503. // mEnterKey.iconPreview = null;
  504. // mEnterKey.icon = null;
  505. // //there is a problem with LTR languages
  506. // mEnterKey.label = Workarounds.workaroundCorrectStringDirection(res.getText(R.string.label_go_key));
  507. //
  508. setKeyIcons(mEnterKey, res, R.drawable.sym_keyboard_go, R.drawable.sym_keyboard_go_feedback);
  509. break;
  510. case EditorInfo.IME_ACTION_NEXT:
  511. mEnterKey.iconPreview = null;
  512. mEnterKey.icon = null;
  513. //there is a problem with LTR languages
  514. mEnterKey.label = Workarounds.workaroundCorrectStringDirection(res.getText(R.string.label_next_key));
  515. break;
  516. case EditorInfo.IME_ACTION_DONE:
  517. // mEnterKey.iconPreview = null;
  518. // mEnterKey.icon = null;
  519. // //there is a problem with LTR languages
  520. // mEnterKey.label = Workarounds.workaroundCorrectStringDirection(res.getText(R.string.label_done_key));
  521. setKeyIcons(mEnterKey, res, R.drawable.sym_keyboard_done, R.drawable.sym_keyboard_done_feedback);
  522. break;
  523. case EditorInfo.IME_ACTION_SEARCH:
  524. setKeyIcons(mEnterKey, res, R.drawable.sym_keyboard_search, R.drawable.sym_keyboard_feedback_search);
  525. break;
  526. case EditorInfo.IME_ACTION_SEND:
  527. // mEnterKey.iconPreview = null;
  528. // mEnterKey.icon = null;
  529. // //there is a problem with LTR languages
  530. // mEnterKey.label = Workarounds.workaroundCorrectStringDirection(res.getText(R.string.label_send_key));
  531. setKeyIcons(mEnterKey, res, R.drawable.sym_keyboard_send, R.drawable.sym_keyboard_send_feedback);
  532. break;
  533. case EditorInfo.IME_ACTION_NONE:
  534. case EditorInfo.IME_ACTION_UNSPECIFIED:
  535. default:
  536. //TODO: Maybe someday we will support this functionality
  537. // if ((imeLabel != null) && (imeLabel.length() > 0) && (imeActionId > 0))
  538. // {
  539. // Log.d(TAG, "Input has provided its own ENTER label: "+ imeLabel);
  540. // mEnterKey.iconPreview = null;
  541. // mEnterKey.icon = null;
  542. // //there is a problem with LTR languages
  543. // mEnterKey.label = Workarounds.workaroundCorrectStringDirection(imeLabel);
  544. // }
  545. // else
  546. // {
  547. setKeyIcons(mEnterKey, res, R.drawable.sym_keyboard_return, R.drawable.sym_keyboard_feedback_return);
  548. // }
  549. break;
  550. }
  551. }
  552. }
  553. protected abstract int getKeyboardNameResId();
  554. public String getKeyboardName()
  555. {
  556. return mKeyboardContext.getResources().getString(getKeyboardNameResId());
  557. }
  558. public boolean isLeftToRightLanguage()
  559. {
  560. return !mRightToLeftLayout;
  561. }
  562. public abstract int getKeyboardIconResId();
  563. public void setShiftLocked(boolean shiftLocked) {
  564. if (mShiftKey != null) {
  565. if (AnySoftKeyboardConfiguration.DEBUG) Log.d(TAG, "setShiftLocked: Switching to locked: "+shiftLocked);
  566. mShiftKey.on = shiftLocked;
  567. if (shiftLocked)
  568. mShiftState = SHIFT_LOCKED;
  569. }
  570. }
  571. @Override
  572. public boolean isShifted() {
  573. if (mShiftKey != null) {
  574. return mShiftState != SHIFT_OFF;
  575. } else {
  576. return false;
  577. }
  578. }
  579. @Override
  580. public boolean setShifted(boolean shiftState)
  581. {
  582. //final boolean superResult = super.setShifted(shiftState);
  583. //making sure it is off. Only caps turn it on. The super will turn the lit on when
  584. //shift is ON, and not when CAPS is on.
  585. if (mShiftKey == null)
  586. return false;
  587. /*My shift state - parameter - changed
  588. * OFF - true - true
  589. * ON - true - false
  590. * LOCKED - true - false
  591. * OFF - false - false
  592. * ON - false - true
  593. * LOCKED - false - true
  594. *
  595. * in Other words, ON and LOCKED act the same
  596. */
  597. final boolean changed = (shiftState == (mShiftState == SHIFT_OFF));
  598. if (AnySoftKeyboardConfiguration.DEBUG) Log.d(TAG, "setShifted: shiftState:"+shiftState+", caps:"+(mShiftState == SHIFT_LOCKED)+". changed: "+changed);
  599. if (changed)
  600. {//layout changed. Need to change labels.
  601. mShiftState = shiftState? SHIFT_ON : SHIFT_OFF;
  602. if (mShiftKey != null) {
  603. if (shiftState) {
  604. if (AnySoftKeyboardConfiguration.DEBUG) Log.d(TAG, "Switching to regular ON shift icon - shifted");
  605. mShiftKey.icon = mOnShiftIcon;
  606. mShiftKey.iconPreview = mOnShiftFeedbackIcon;
  607. } else {
  608. if (AnySoftKeyboardConfiguration.DEBUG) Log.d(TAG, "Switching to regular OFF shift icon - un-shifted");
  609. mShiftKey.icon = mOffShiftIcon;
  610. mShiftKey.iconPreview = mOffShiftFeedbackIcon;
  611. }
  612. }
  613. }
  614. mShiftKey.on = (mShiftState == SHIFT_LOCKED);
  615. return changed;
  616. }
  617. public boolean isShiftLocked() {
  618. return mShiftState == SHIFT_LOCKED;
  619. }
  620. protected void setPopupKeyChars(Key aKey)
  621. {
  622. }
  623. // protected void setPopupKeyChars(Key aKey)
  624. // {
  625. // if (aKey.popupResId > 0)
  626. // return;//if the keyboard XML already specified the popup, then no need to override
  627. //
  628. // if ((aKey.codes != null) && (aKey.codes.length > 0))
  629. // {
  630. // switch(((char)aKey.codes[0]))
  631. // {
  632. // case '\''://in the generic bottom row
  633. // aKey.popupResId = R.xml.popup;
  634. // aKey.popupCharacters = "\"\u201e\u201d";
  635. // break;
  636. // case '-':
  637. // aKey.popupResId = R.xml.popup;
  638. // aKey.popupCharacters = "\u2013";
  639. // break;
  640. // case '.'://in the generic bottom row
  641. // aKey.popupResId = R.xml.popup;
  642. // aKey.popupCharacters = ";:-_\u00b7\u2026";
  643. // break;
  644. // case ','://in the generic bottom row
  645. // aKey.popupResId = R.xml.popup;
  646. // aKey.popupCharacters = "()";
  647. // break;
  648. // case '_':
  649. // aKey.popupResId = R.xml.popup;
  650. // aKey.popupCharacters = ",-";
  651. // break;
  652. //the two below are switched in regular and Internet mode
  653. // case '?'://in the generic bottom row
  654. // aKey.popupResId = R.xml.popup;
  655. // aKey.popupCharacters = POPUP_FOR_QUESTION;
  656. // break;
  657. // case '@'://in the generic Internet mode
  658. // aKey.popupResId = R.xml.popup;
  659. // aKey.popupCharacters = POPUP_FOR_AT;
  660. // break;
  661. // }
  662. // }
  663. // }
  664. // public void setTextVariation(Resources res, int inputType)
  665. // {
  666. // if (mDebug)
  667. // Log.d(TAG, "setTextVariation");
  668. // int variation = inputType & EditorInfo.TYPE_MASK_VARIATION;
  669. //
  670. // switch (variation) {
  671. // case EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS:
  672. // case EditorInfo.TYPE_TEXT_VARIATION_URI:
  673. // if (mSwitchableKey != null)
  674. // {
  675. // //Log.d("AnySoftKeyboard", "Changing smiley key to domains.");
  676. // setKeyIcons(mSwitchableKey, res, mDomainsIconId, R.drawable.sym_keyboard_key_domain_preview);
  677. // mSwitchableKey.text = AnySoftKeyboardConfiguration.getInstance().getDomainText();
  678. // mSwitchableKey.popupResId = R.xml.popup_domains;
  679. // }
  680. // if (mQuestionMarkKey != null)
  681. // {
  682. // //Log.d("AnySoftKeyboard", "Changing question mark key to AT.");
  683. // mQuestionMarkKey.codes[0] = (int)'@';
  684. // mQuestionMarkKey.label = "@";
  685. // mQuestionMarkKey.popupCharacters = POPUP_FOR_AT;
  686. // }
  687. // break;
  688. // default:
  689. // if (mSwitchableKey != null)
  690. // {
  691. // setKeyIcons(mSwitchableKey, res, R.drawable.sym_keyboard_smiley, R.drawable.sym_keyboard_smiley_feedback);
  692. // mSwitchableKey.text = null;// ":-) ";
  693. // mSwitchableKey.popupResId = R.xml.popup_smileys;
  694. // }
  695. // if (mQuestionMarkKey != null)
  696. // {
  697. // //Log.d("AnySoftKeyboard", "Changing question mark key to question.");
  698. // mQuestionMarkKey.codes[0] = (int)'?';
  699. // mQuestionMarkKey.label = "?";
  700. // mQuestionMarkKey.popupCharacters = POPUP_FOR_QUESTION;
  701. // }
  702. // break;
  703. // }
  704. // }
  705. //
  706. // public int getShiftedKeyValue(int primaryCode)
  707. // {
  708. //// if ((primaryCode>0) && (primaryCode<Character.MAX_VALUE))
  709. //// {
  710. //// Character c = new Character((char)primaryCode);
  711. //// if (mSpecialShiftKeys.containsKey(c))
  712. //// {
  713. //// char shifted = mSpecialShiftKeys.get(c).ShiftCharacter;
  714. //// if (mDebug)
  715. //// Log.v(TAG, "Returned the shifted mapping ("+c+"->"+shifted+") from mSpecialShiftKeys.");
  716. //// return shifted;
  717. //// }
  718. //// }
  719. // //else...best try.
  720. // return Character.toUpperCase(primaryCode);
  721. // }
  722. static class AnyKey extends Keyboard.Key {
  723. private boolean mEnabled;
  724. public AnyKey(Resources res, Keyboard.Row parent, int x, int y,
  725. XmlResourceParser parser) {
  726. super(res, parent, x, y, parser);
  727. mEnabled = true;
  728. if (popupCharacters != null && popupCharacters.length() == 0) {
  729. // If there is a keyboard with no keys specified in popupCharacters
  730. popupResId = 0;
  731. }
  732. parseCSV("");//TODO lado parseCSV is removed by proGuard if it here not set. I'm on it to leave this method
  733. //untasted without this call.
  734. }
  735. public void enable()
  736. {
  737. mEnabled = true;
  738. }
  739. public void disable()
  740. {
  741. iconPreview = null;
  742. icon = null;
  743. label = " ";//can not use NULL.
  744. mEnabled = false;
  745. }
  746. public boolean isInside(int clickedX, int clickedY) {
  747. if (mEnabled)
  748. return super.isInside(clickedX, clickedY);
  749. else
  750. return false;//disabled.
  751. }
  752. //Issue 395
  753. protected int[] parseCSV(String value) {
  754. int count = 0;
  755. int lastIndex = 0;
  756. if (value.length() > 0) {
  757. count++;
  758. while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
  759. count++;
  760. }
  761. }
  762. int[] values = new int[count];
  763. count = 0;
  764. StringTokenizer st = new StringTokenizer(value, ",");
  765. while (st.hasMoreTokens()) {
  766. String nextToken = st.nextToken();
  767. try {
  768. //default behavior
  769. if(nextToken.length() != 1 ){
  770. values[count++] = Integer.parseInt(nextToken);
  771. }else {
  772. // length == 1, assume a char!
  773. values[count++] = (int)nextToken.charAt(0);
  774. }
  775. } catch (NumberFormatException nfe) {
  776. Log.e(TAG, "Error parsing keycodes " + value);
  777. }
  778. }
  779. return values;
  780. }
  781. // void enableShiftLock() {
  782. // mShiftLockEnabled = true;
  783. // }
  784. //
  785. // @Override
  786. // public void onReleased(boolean inside) {
  787. // if (!mShiftLockEnabled) {
  788. // super.onReleased(inside);
  789. // } else {
  790. // pressed = !pressed;
  791. // }
  792. // }
  793. }
  794. protected static class LessSensitiveAnyKey extends AnyKey {
  795. private int mStartX;
  796. private int mStartY;
  797. private int mEndX;
  798. private int mEndY;
  799. public LessSensitiveAnyKey(Resources res, Keyboard.Row parent, int x, int y,
  800. XmlResourceParser parser) {
  801. super(res, parent, x, y, parser);
  802. resetSenitivity();
  803. }
  804. void resetSenitivity()
  805. {
  806. mStartX = this.x;
  807. mStartY = this.y;
  808. mEndX = this.width + this.x;
  809. mEndY = this.height + this.y;
  810. if ((this.edgeFlags & Keyboard.EDGE_BOTTOM) != 0)
  811. {//the enter key!
  812. //we want to "click" it only if it in the lower
  813. mStartY += (this.height * 0.15);
  814. }
  815. else
  816. {
  817. if ((this.edgeFlags & Keyboard.EDGE_LEFT) != 0)
  818. {//usually, shift
  819. mEndX -= (this.width * 0.1);
  820. }
  821. if ((this.edgeFlags & Keyboard.EDGE_RIGHT) != 0)
  822. {//usually, delete
  823. //this is below the ENTER.. We want to be careful with this.
  824. mStartY += (this.height * 0.05);
  825. mEndY -= (this.height * 0.05);
  826. mStartX += (this.width * 0.15);
  827. }
  828. }
  829. }
  830. /**
  831. * Overriding this method so that we can reduce the target area for certain keys.
  832. */
  833. @Override
  834. public boolean isInside(int clickedX, int clickedY)
  835. {
  836. return clickedX >= mStartX &&
  837. clickedX <= mEndX &&
  838. clickedY >= mStartY &&
  839. clickedY <= mEndY;
  840. }
  841. }
  842. private static class EnterKey extends LessSensitiveAnyKey
  843. {
  844. private final int mOriginalHeight;
  845. public EnterKey(Resources res, Row parent, int x, int y,
  846. XmlResourceParser parser) {
  847. super(res, parent, x, y, parser);
  848. mOriginalHeight = this.height;
  849. }
  850. @Override
  851. public void disable()
  852. {
  853. if (AnyApplication.getConfig().getActionKeyInvisibleWhenRequested())
  854. this.height = 0;
  855. super.disable();
  856. }
  857. @Override
  858. public void enable()
  859. {
  860. this.height = mOriginalHeight;
  861. super.enable();
  862. }
  863. }
  864. public abstract String getKeyboardPrefId();
  865. public boolean requiresProximityCorrection() {
  866. return getKeys().size() > 20;
  867. }
  868. public int getKeyboardMode() {
  869. return mKeyboardMode;
  870. }
  871. }