diff --git a/check_layout.output b/check_layout.output index e9e78ee57..201ea66d6 100644 --- a/check_layout.output +++ b/check_layout.output @@ -18,6 +18,8 @@ cyrl_yawerty: Layout includes some ASCII punctuation but not all, missing: #, $, cyrl_yxukeng_os: Layout includes some ASCII punctuation but not all, missing: ", #, $, &, ', @, [, ], ~ deva_alt: Layout includes some ASCII punctuation but not all, missing: #, $, %, &, ', (, ), *, +, ., /, :, <, =, >, [, \, ], ^, _, `, {, |, }, ~ deva_inscript: Layout includes some ASCII punctuation but not all, missing: ", $, ', ^, _, `, | +hang_dubeolsik_custom: Layout contains editing keys, unexpected: copy, cut, paste, pasteAsPlainText, redo, selectAll, shareText, undo +hang_dubeolsik_kr: Layout contains editing keys, unexpected: copy, cut, paste, pasteAsPlainText, redo, selectAll, shareText, undo hebr_1_il: Layout includes some ASCII punctuation but not all, missing: (, ), <, >, [, ], {, } hebr_2_il: Layout includes some ASCII punctuation but not all, missing: (, ), <, >, [, ], {, } kann_kannada: Layout includes some ASCII punctuation but not all, missing: #, $, %, (, ), *, +, /, <, =, >, [, \, ], ^, `, {, |, }, ~ @@ -27,6 +29,7 @@ latin_kbdtuf_tr: Missing programming keys, missing: loc esc, loc tab latn_colemak: Some keys contain whitespaces, unexpected: ́ latn_dvorak: Missing important key, missing: loc capslock latn_neo2: Layout redefines the bottom row but some important keys are missing, missing: loc switch_clipboard +latn_qwerty_custom: Layout contains editing keys, unexpected: copy, cut, paste, pasteAsPlainText, redo, selectAll, shareText, undo latn_qwertz_cz_multifunctional: Layout includes some ASCII punctuation but not all, missing: ` latn_qwertz_sk: Layout includes some ASCII punctuation but not all, missing: ` latn_qzerty_it: Layout includes some ASCII punctuation but not all, missing: #, $, \, `, ~ diff --git a/res/values/layouts.xml b/res/values/layouts.xml index 11ac6375e..e8c6d69b5 100644 --- a/res/values/layouts.xml +++ b/res/values/layouts.xml @@ -38,6 +38,7 @@ georgian_qwerty grek_qwerty guj_phonetic_in + hang_dubeolsik_custom hang_dubeolsik_kr hebr_1_il hebr_2_il @@ -52,6 +53,7 @@ latn_qwerty_az latn_qwerty_bqn latn_qwerty_br + latn_qwerty_custom latn_qwerty_cy latn_qwerty_cz latn_qwerty_da @@ -133,6 +135,7 @@ QWERTY (Greek) ગુજરાતી ફોનેટિક - Gujarati Phonetic 두벌식 (Korean) + 두벌식 (Korean) Hebrew 1 Hebrew 2 ಕನ್ನಡ - Kannada @@ -146,6 +149,7 @@ QWERTY (Azərbaycanca) QWERTY (BQN) QWERTY (Brasileiro) + QWERTY Custom QWERTY (Welsh) QWERTY (Czech) QWERTY (Danish) @@ -226,6 +230,7 @@ @xml/georgian_qwerty @xml/grek_qwerty @xml/guj_phonetic_in + @xml/hang_dubeolsik_custom @xml/hang_dubeolsik_kr @xml/hebr_1_il @xml/hebr_2_il @@ -240,6 +245,7 @@ @xml/latn_qwerty_az @xml/latn_qwerty_bqn @xml/latn_qwerty_br + @xml/latn_qwerty_custom @xml/latn_qwerty_cy @xml/latn_qwerty_cz @xml/latn_qwerty_da diff --git a/res/values/styles.xml b/res/values/styles.xml index 9963c9ea9..befe9a95d 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/srcs/compose/shift.json b/srcs/compose/shift.json index 0a44829ed..27d891416 100644 --- a/srcs/compose/shift.json +++ b/srcs/compose/shift.json @@ -95,6 +95,14 @@ // Tamil alternate characters "௹": "₨", + // Korean double consonants and iotized vowels + "ㄱ": ":ㄲ", + "ㄷ": ":ㄸ", + "ㅂ": ":ㅃ", + "ㅅ": ":ㅆ", + "ㅈ": ":ㅉ", + "ㅐ": ":ㅒ", + "ㅔ": ":ㅖ", // Modern Hindi and Sanskrit "अ": "आ", "इ": "ई", diff --git a/srcs/juloo.keyboard2/Autocapitalisation.java b/srcs/juloo.keyboard2/Autocapitalisation.java index 31d9a7b6f..985dcd564 100644 --- a/srcs/juloo.keyboard2/Autocapitalisation.java +++ b/srcs/juloo.keyboard2/Autocapitalisation.java @@ -49,6 +49,8 @@ public void started(Config config, InputConnection ic) public void typed(CharSequence c) { + if (!_enabled) + return; for (int i = 0; i < c.length(); i++) type_one_char(c.charAt(i)); callback(false); @@ -75,6 +77,14 @@ public void event_sent(int code, int meta) callback(true); } + public void text_replaced() + { + if (!_enabled) + return; + _should_update_caps_mode = true; + callback(false); + } + public void stop() { _should_enable_shift = false; @@ -108,6 +118,8 @@ public static interface Callback /** Returns [true] if shift might be disabled. */ public void selection_updated(int old_cursor, int new_cursor) { + if (!_enabled) + return; if (new_cursor == _cursor) // Just typing return; if (new_cursor == 0 && _ic != null) diff --git a/srcs/juloo.keyboard2/ComposeKey.java b/srcs/juloo.keyboard2/ComposeKey.java index 3d97d6932..fb29791f6 100644 --- a/srcs/juloo.keyboard2/ComposeKey.java +++ b/srcs/juloo.keyboard2/ComposeKey.java @@ -11,7 +11,10 @@ public static KeyValue apply(int state, KeyValue kv) switch (kv.getKind()) { case Char: return apply(state, kv.getChar()); - case String: return apply(state, kv.getString()); + case String: + case Hangul_initial: + case Hangul_medial: + return apply(state, kv.getString()); case Editing: switch (kv.getEditing()) { diff --git a/srcs/juloo.keyboard2/ComposeKeyData.java b/srcs/juloo.keyboard2/ComposeKeyData.java index 47dab22fb..c3cc7d4a7 100644 --- a/srcs/juloo.keyboard2/ComposeKeyData.java +++ b/srcs/juloo.keyboard2/ComposeKeyData.java @@ -126,24 +126,24 @@ public final class ComposeKeyData "\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u00000123456789\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u00000123456789\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u00000123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u000001" + "23456789\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u00000123456789\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u00000123456789\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0000\u00df\u0131\u01f0\u0237\u02b0\u02b2\u02b3\u02b7\u02e1\u0905\u0907" + "\u0909\u090b\u090c\u090f\u0913\u0915\u0917\u091a\u091c\u091f\u0921\u0924\u0926\u0928\u092c\u092e\u0932\u0938\u0939\u093f\u0941\u0943\u0945\u0947\u0949\u094b\u0952\u0962\u0a85\u0a87\u0a89\u0a8f\u0a93\u0a95\u0a97\u0a9a\u0a9c\u0a9f\u0aa1\u0aa4\u0aa6\u0aa8\u0aaa\u0aac\u0aae\u0ab2\u0ab8\u0ab9\u0abf\u0ac1\u0ac7\u0acb\u0bf9\u1d43\u1d47\u1d48\u1d49\u1d4d\u1d4f\u1d50\u1d52\u1d56\u1d57\u1d58\u1d5b\u1d60\u1d9c\u1da0\u1dbe\u1e97\u1e98\u1e99" + - "\u2071\u207f\u20b9\u2190\u2191\u2192\u2193\u2196\u2197\u2198\u2199\u2208\u220b\u2282\u2283\u2286\u2287\u2500\u2502\u250c\u2510\u2514\u2518\u251c\u2524\u252c\u2534\u253c\ud835\uFFFF\u004a\u030c\uFFFF\u004a\u0307\u1d34\u1d36\u1d3f\u1d42\u1d38\u0906\u0908\u090a\u0910\u0914\u0916\u0918\u091b\u091d\u0920\u0922\u0925\u0927\u0923\u092d\u0902\u0933\u0936\u0903\u0940\u0942\u0948\u094c\u0951\u0a86\u0a88\u0a8a\u0a90\u0a94\u0a96\u0a98\u0a9b" + - "\u0a9d\u0aa0\u0aa2\u0aa5\u0aa7\u0aa3\u0aab\u0aad\u0a82\u0ab3\u0ab6\u0a83\u0ac0\u0ac2\u0ac8\u0acc\u1d2c\u1d2e\u1d30\u1d31\u1d33\u1d37\u1d39\u1d3c\u1d3e\u1d40\u1d41\u2c7d\u1db2\uFFFF\ua7f2\uFFFF\ua7f3\uFFFF\u0054\u0308\uFFFF\u0057\u030a\uFFFF\u0059\u030a\u1d35\u1d3a\u2550\u2551\u2554\u2557\u255a\u255d\u2560\u2563\u2566\u2569\u256c\u0000\udd57\udd58\udd64\udd68\udd69\uFFFF\ud835\udd3d\uFFFF\ud835\udd3e\uFFFF\ud835\udd4a\uFFFF\ud835" + - "\udd4e\uFFFF\ud835\udd4f\u0000ABCDEFGHIJKLMNOPQRSTUVWXYZx\u00a2\u00a3\u00a5\u00aa\u00b5\u00ba\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5" + - "\u00e6\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd\u00ff\u0100\u0101\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e\u010f\u0110\u0111\u0112\u0113\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u0120\u0121\u0122\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012e\u012f\u0130\u0131\u0134\u0135\u0136\u0137\u0139\u013a\u013b" + - "\u013c\u013d\u013e\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u014c\u014d\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016e\u016f\u0170\u0171\u0172\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u0180\u0194\u0197\u019a\u019f\u01a0\u01a1\u01a6\u01af\u01b0\u01b5\u01b6\u01cd\u01ce" + - "\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f4\u01f5\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe\u01ff\u0200\u0201\u0204\u0205\u0208\u0209\u020c\u020d\u0210\u0211\u0214\u0215\u021e\u021f\u0226\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0237" + - "\u023a\u023b\u023c\u023d\u023e\u0243\u0244\u0246\u0247\u0248\u0249\u024c\u024d\u024e\u024f\u025f\u0262\u0263\u0268\u026a\u0274\u0275\u0276\u0280\u0289\u028f\u0299\u029b\u029c\u029f\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02e0\u02e1\u02e2\u02e3\u0386\u0388\u0389\u038a\u038c\u038e\u0392\u0395\u0398\u039a\u039c\u03a0\u03a1\u03a3\u03a6\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03c2\u03ca\u03cb\u03cc\u03cd\u03d0\u03d1" + - "\u03d2\u03d4\u03d5\u03d6\u03f0\u03f1\u03f4\u03f5\u0400\u0402\u0403\u0404\u0405\u0406\u0408\u040b\u040c\u040d\u040f\u0418\u0419\u0427\u042a\u042b\u042c\u042f\u0438\u0439\u0447\u044a\u044b\u044c\u044f\u0450\u0452\u0453\u0454\u0455\u0456\u0458\u045b\u045c\u045d\u045f\u0462\u0463\u0466\u0467\u046a\u046b\u0470\u0471\u0472\u0473\u0476\u0477\u047a\u047b\u0480\u0481\u048c\u048d\u0490\u0491\u0498\u0499\u049a\u049b\u04b0\u04b1\u04b6\u04b7" + - "\u04ba\u04bb\u04d2\u04d3\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e2\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f8\u04f9\u04fe\u04ff\u0528\u0529\u052e\u052f\u0621\u0622\u0630\u0635\u0636\u0637\u0638\u063a\u0640\u0643\u0647\u064a\u066e\u067e\u0695\u0698\u06a1\u06a4\u06a9\u06af\u06b5\u06ba\u06be\u06c1\u06cc\u0902\u0903\u0906\u0908\u090a\u090d\u090e\u0910\u0911" + - "\u0912\u0914\u0916\u0918\u091b\u091d\u0920\u0922\u0923\u0925\u0927\u0929\u092d\u0931\u0933\u0934\u0936\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e\u097f\u0a82\u0a83\u0a86\u0a88\u0a8a\u0a90\u0a94\u0a96\u0a98\u0a9b\u0a9d\u0aa0\u0aa2\u0aa3\u0aa5\u0aa7\u0aab\u0aad\u0ab3\u0ab6\u1d00\u1d01\u1d03\u1d04\u1d05\u1d06\u1d07\u1d0a\u1d0b\u1d0c\u1d0d" + - "\u1d0e\u1d0f\u1d10\u1d15\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d20\u1d21\u1d22\u1d23\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d43\u1d44\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5d\u1d5f\u1d60\u1d61\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67\u1d68\u1d69\u1d6a\u1d7b\u1d7d\u1d7e\u1d9b\u1d9c\u1d9d\u1d9f\u1da0\u1da1\u1da3\u1da4\u1da5\u1dac\u1dad\u1db1\u1db3\u1db4\u1db6" + - "\u1db7\u1dba\u1dbb\u1dbe\u1dbf\u1e02\u1e03\u1e04\u1e05\u1e08\u1e09\u1e0a\u1e0b\u1e0c\u1e0d\u1e10\u1e11\u1e14\u1e15\u1e16\u1e17\u1e1c\u1e1d\u1e1e\u1e1f\u1e20\u1e21\u1e22\u1e23\u1e24\u1e25\u1e26\u1e27\u1e28\u1e29\u1e2e\u1e2f\u1e30\u1e31\u1e32\u1e33\u1e36\u1e37\u1e38\u1e39\u1e3e\u1e3f\u1e40\u1e41\u1e42\u1e43\u1e44\u1e45\u1e46\u1e47\u1e4c\u1e4d\u1e4e\u1e4f\u1e50\u1e51\u1e52\u1e53\u1e54\u1e55\u1e56\u1e57\u1e58\u1e59\u1e5a\u1e5b\u1e5c" + - "\u1e5d\u1e60\u1e61\u1e62\u1e63\u1e64\u1e65\u1e66\u1e67\u1e68\u1e69\u1e6a\u1e6b\u1e6c\u1e6d\u1e78\u1e79\u1e7c\u1e7d\u1e7e\u1e7f\u1e80\u1e81\u1e82\u1e83\u1e84\u1e85\u1e86\u1e87\u1e88\u1e89\u1e8a\u1e8b\u1e8c\u1e8d\u1e8e\u1e8f\u1e90\u1e91\u1e92\u1e93\u1e97\u1e98\u1e99\u1e9b\u1e9e\u1ea0\u1ea1\u1ea2\u1ea3\u1ea4\u1ea5\u1ea6\u1ea7\u1ea8\u1ea9\u1eaa\u1eab\u1eac\u1ead\u1eae\u1eaf\u1eb0\u1eb1\u1eb2\u1eb3\u1eb4\u1eb5\u1eb6\u1eb7\u1eb8\u1eb9" + - "\u1eba\u1ebb\u1ebc\u1ebd\u1ebe\u1ebf\u1ec0\u1ec1\u1ec2\u1ec3\u1ec4\u1ec5\u1ec6\u1ec7\u1ec8\u1ec9\u1eca\u1ecb\u1ecc\u1ecd\u1ece\u1ecf\u1ed0\u1ed1\u1ed2\u1ed3\u1ed4\u1ed5\u1ed6\u1ed7\u1ed8\u1ed9\u1eda\u1edb\u1edc\u1edd\u1ede\u1edf\u1ee0\u1ee1\u1ee2\u1ee3\u1ee4\u1ee5\u1ee6\u1ee7\u1ee8\u1ee9\u1eea\u1eeb\u1eec\u1eed\u1eee\u1eef\u1ef0\u1ef1\u1ef2\u1ef3\u1ef4\u1ef5\u1ef6\u1ef7\u1ef8\u1ef9\u1f70\u1f72\u1f74\u1f76\u1f78\u1f7a\u1f7c\u1fb1" + - "\u1fb9\u1fba\u1fc8\u1fca\u1fd1\u1fd9\u1fda\u1fe1\u1fe2\u1fe7\u1fe9\u1fea\u1ff8\u1ffa\u2071\u207f\u2090\u2091\u2092\u2093\u2094\u2095\u2096\u2097\u2098\u2099\u209a\u209b\u209c\u20ac\u20b1\u20b4\u20b9\u20bd\u20bf\u220f\u2211\u2422\u2c63\u2c65\u2c66\u2c7b\u2c7c\ua640\ua641\ua648\ua649\ua64a\ua64b\ua64c\ua64d\ua650\ua651\ua656\ua657\ua730\ua731\ua740\ua741\ua742\ua743\ua756\ua757\ua75e\ua75f\ua776\ua792\ua793\ua798\ua799\ua7a0\ua7a1" + - "\ua7a4\ua7a5\ua7a6\ua7a7\ua7a8\ua7a9\ua7ae\ua7af\ua7b8\ua7b9\ua7f4\ua7f9\ua7fa\uab3f\uab65\uab69abcdefghijklmnopqrstuvwyz\u03b3\u0292\u0260\u0266\u0279\u027b\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03b2\u03b8\u03ba\u03c0\u03c1\u03c3\u03c6\u0435\u0433\u044d\u0441\u043a\u043d\u043c\u0448\u0444\u043e\u0434\u04af" + - "\u0445\u0430\u0436\u0437\u0443\u043b\u0639\u0627\u062f\u0633\u062a\u0632\u0642\u0645\u0628\u0631\u0648\u0641\u0644\u0646\u062d\u092e\u0939\u0905\u0907\u0909\u090f\u0913\u0915\u0917\u091a\u091c\u091f\u0921\u0928\u0924\u0926\u092c\u0930\u0932\u0938\u092b\u092f\u090b\u090c\u0935\u093d\u0aae\u0ab9\u0a85\u0a87\u0a89\u0a8f\u0a93\u0a95\u0a97\u0a9a\u0a9c\u0a9f\u0aa1\u0aa8\u0aa4\u0aa6\u0aaa\u0aac\u0ab2\u0ab8\u0254\u0223\u03bb\u03c8\u0250" + - "\u1d02\u025b\u1d08\u1d09\u1d1d\u026f\u03b4\u03c7\u0252\u0255\u025c\u0265\u0271\u0270\u0282\u0283\u028a\u028c\u03c9\u01dd\uFFFF\ua775\u028d").toCharArray(); + "\u2071\u207f\u20b9\u2190\u2191\u2192\u2193\u2196\u2197\u2198\u2199\u2208\u220b\u2282\u2283\u2286\u2287\u2500\u2502\u250c\u2510\u2514\u2518\u251c\u2524\u252c\u2534\u253c\u3131\u3137\u3142\u3145\u3148\u3150\u3154\ud835\uFFFF\u004a\u030c\uFFFF\u004a\u0307\u1d34\u1d36\u1d3f\u1d42\u1d38\u0906\u0908\u090a\u0910\u0914\u0916\u0918\u091b\u091d\u0920\u0922\u0925\u0927\u0923\u092d\u0902\u0933\u0936\u0903\u0940\u0942\u0948\u094c\u0951\u0a86" + + "\u0a88\u0a8a\u0a90\u0a94\u0a96\u0a98\u0a9b\u0a9d\u0aa0\u0aa2\u0aa5\u0aa7\u0aa3\u0aab\u0aad\u0a82\u0ab3\u0ab6\u0a83\u0ac0\u0ac2\u0ac8\u0acc\u1d2c\u1d2e\u1d30\u1d31\u1d33\u1d37\u1d39\u1d3c\u1d3e\u1d40\u1d41\u2c7d\u1db2\uFFFF\ua7f2\uFFFF\ua7f3\uFFFF\u0054\u0308\uFFFF\u0057\u030a\uFFFF\u0059\u030a\u1d35\u1d3a\u2550\u2551\u2554\u2557\u255a\u255d\u2560\u2563\u2566\u2569\u256c\uFFFF\u3132\uFFFF\u3138\uFFFF\u3143\uFFFF\u3146\uFFFF\u3149" + + "\uFFFF\u3152\uFFFF\u3156\u0000\udd57\udd58\udd64\udd68\udd69\uFFFF\ud835\udd3d\uFFFF\ud835\udd3e\uFFFF\ud835\udd4a\uFFFF\ud835\udd4e\uFFFF\ud835\udd4f\u0000ABCDEFGHIJKLMNOPQRSTUVWXYZx\u00a2\u00a3\u00a5\u00aa\u00b5\u00ba\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc" + + "\u00cd\u00ce\u00cf\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd\u00ff\u0100\u0101\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e\u010f\u0110\u0111\u0112\u0113\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u0120\u0121" + + "\u0122\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012e\u012f\u0130\u0131\u0134\u0135\u0136\u0137\u0139\u013a\u013b\u013c\u013d\u013e\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u014c\u014d\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016e\u016f\u0170\u0171\u0172\u0173\u0174\u0175\u0176\u0177" + + "\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u0180\u0194\u0197\u019a\u019f\u01a0\u01a1\u01a6\u01af\u01b0\u01b5\u01b6\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f4\u01f5\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe\u01ff\u0200\u0201\u0204\u0205\u0208\u0209\u020c\u020d" + + "\u0210\u0211\u0214\u0215\u021e\u021f\u0226\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0237\u023a\u023b\u023c\u023d\u023e\u0243\u0244\u0246\u0247\u0248\u0249\u024c\u024d\u024e\u024f\u025f\u0262\u0263\u0268\u026a\u0274\u0275\u0276\u0280\u0289\u028f\u0299\u029b\u029c\u029f\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02e0\u02e1\u02e2\u02e3\u0386\u0388\u0389\u038a\u038c\u038e\u0392\u0395" + + "\u0398\u039a\u039c\u03a0\u03a1\u03a3\u03a6\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03c2\u03ca\u03cb\u03cc\u03cd\u03d0\u03d1\u03d2\u03d4\u03d5\u03d6\u03f0\u03f1\u03f4\u03f5\u0400\u0402\u0403\u0404\u0405\u0406\u0408\u040b\u040c\u040d\u040f\u0418\u0419\u0427\u042a\u042b\u042c\u042f\u0438\u0439\u0447\u044a\u044b\u044c\u044f\u0450\u0452\u0453\u0454\u0455\u0456\u0458\u045b\u045c\u045d\u045f\u0462\u0463\u0466\u0467\u046a\u046b\u0470" + + "\u0471\u0472\u0473\u0476\u0477\u047a\u047b\u0480\u0481\u048c\u048d\u0490\u0491\u0498\u0499\u049a\u049b\u04b0\u04b1\u04b6\u04b7\u04ba\u04bb\u04d2\u04d3\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e2\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f8\u04f9\u04fe\u04ff\u0528\u0529\u052e\u052f\u0621\u0622\u0630\u0635\u0636\u0637\u0638\u063a\u0640\u0643\u0647\u064a\u066e" + + "\u067e\u0695\u0698\u06a1\u06a4\u06a9\u06af\u06b5\u06ba\u06be\u06c1\u06cc\u0902\u0903\u0906\u0908\u090a\u090d\u090e\u0910\u0911\u0912\u0914\u0916\u0918\u091b\u091d\u0920\u0922\u0923\u0925\u0927\u0929\u092d\u0931\u0933\u0934\u0936\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e\u097f\u0a82\u0a83\u0a86\u0a88\u0a8a\u0a90\u0a94\u0a96\u0a98\u0a9b" + + "\u0a9d\u0aa0\u0aa2\u0aa3\u0aa5\u0aa7\u0aab\u0aad\u0ab3\u0ab6\u1d00\u1d01\u1d03\u1d04\u1d05\u1d06\u1d07\u1d0a\u1d0b\u1d0c\u1d0d\u1d0e\u1d0f\u1d10\u1d15\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d20\u1d21\u1d22\u1d23\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d43\u1d44\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5d\u1d5f\u1d60\u1d61\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67" + + "\u1d68\u1d69\u1d6a\u1d7b\u1d7d\u1d7e\u1d9b\u1d9c\u1d9d\u1d9f\u1da0\u1da1\u1da3\u1da4\u1da5\u1dac\u1dad\u1db1\u1db3\u1db4\u1db6\u1db7\u1dba\u1dbb\u1dbe\u1dbf\u1e02\u1e03\u1e04\u1e05\u1e08\u1e09\u1e0a\u1e0b\u1e0c\u1e0d\u1e10\u1e11\u1e14\u1e15\u1e16\u1e17\u1e1c\u1e1d\u1e1e\u1e1f\u1e20\u1e21\u1e22\u1e23\u1e24\u1e25\u1e26\u1e27\u1e28\u1e29\u1e2e\u1e2f\u1e30\u1e31\u1e32\u1e33\u1e36\u1e37\u1e38\u1e39\u1e3e\u1e3f\u1e40\u1e41\u1e42\u1e43" + + "\u1e44\u1e45\u1e46\u1e47\u1e4c\u1e4d\u1e4e\u1e4f\u1e50\u1e51\u1e52\u1e53\u1e54\u1e55\u1e56\u1e57\u1e58\u1e59\u1e5a\u1e5b\u1e5c\u1e5d\u1e60\u1e61\u1e62\u1e63\u1e64\u1e65\u1e66\u1e67\u1e68\u1e69\u1e6a\u1e6b\u1e6c\u1e6d\u1e78\u1e79\u1e7c\u1e7d\u1e7e\u1e7f\u1e80\u1e81\u1e82\u1e83\u1e84\u1e85\u1e86\u1e87\u1e88\u1e89\u1e8a\u1e8b\u1e8c\u1e8d\u1e8e\u1e8f\u1e90\u1e91\u1e92\u1e93\u1e97\u1e98\u1e99\u1e9b\u1e9e\u1ea0\u1ea1\u1ea2\u1ea3\u1ea4" + + "\u1ea5\u1ea6\u1ea7\u1ea8\u1ea9\u1eaa\u1eab\u1eac\u1ead\u1eae\u1eaf\u1eb0\u1eb1\u1eb2\u1eb3\u1eb4\u1eb5\u1eb6\u1eb7\u1eb8\u1eb9\u1eba\u1ebb\u1ebc\u1ebd\u1ebe\u1ebf\u1ec0\u1ec1\u1ec2\u1ec3\u1ec4\u1ec5\u1ec6\u1ec7\u1ec8\u1ec9\u1eca\u1ecb\u1ecc\u1ecd\u1ece\u1ecf\u1ed0\u1ed1\u1ed2\u1ed3\u1ed4\u1ed5\u1ed6\u1ed7\u1ed8\u1ed9\u1eda\u1edb\u1edc\u1edd\u1ede\u1edf\u1ee0\u1ee1\u1ee2\u1ee3\u1ee4\u1ee5\u1ee6\u1ee7\u1ee8\u1ee9\u1eea\u1eeb\u1eec" + + "\u1eed\u1eee\u1eef\u1ef0\u1ef1\u1ef2\u1ef3\u1ef4\u1ef5\u1ef6\u1ef7\u1ef8\u1ef9\u1f70\u1f72\u1f74\u1f76\u1f78\u1f7a\u1f7c\u1fb1\u1fb9\u1fba\u1fc8\u1fca\u1fd1\u1fd9\u1fda\u1fe1\u1fe2\u1fe7\u1fe9\u1fea\u1ff8\u1ffa\u2071\u207f\u2090\u2091\u2092\u2093\u2094\u2095\u2096\u2097\u2098\u2099\u209a\u209b\u209c\u20ac\u20b1\u20b4\u20b9\u20bd\u20bf\u220f\u2211\u2422\u2c63\u2c65\u2c66\u2c7b\u2c7c\ua640\ua641\ua648\ua649\ua64a\ua64b\ua64c\ua64d" + + "\ua650\ua651\ua656\ua657\ua730\ua731\ua740\ua741\ua742\ua743\ua756\ua757\ua75e\ua75f\ua776\ua792\ua793\ua798\ua799\ua7a0\ua7a1\ua7a4\ua7a5\ua7a6\ua7a7\ua7a8\ua7a9\ua7ae\ua7af\ua7b8\ua7b9\ua7f4\ua7f9\ua7fa\uab3f\uab65\uab69abcdefghijklmnopqrstuvwyz\u03b3\u0292\u0260\u0266\u0279\u027b\u03b1\u03b5\u03b7\u03b9" + + "\u03bf\u03c5\u03b2\u03b8\u03ba\u03c0\u03c1\u03c3\u03c6\u0435\u0433\u044d\u0441\u043a\u043d\u043c\u0448\u0444\u043e\u0434\u04af\u0445\u0430\u0436\u0437\u0443\u043b\u0639\u0627\u062f\u0633\u062a\u0632\u0642\u0645\u0628\u0631\u0648\u0641\u0644\u0646\u062d\u092e\u0939\u0905\u0907\u0909\u090f\u0913\u0915\u0917\u091a\u091c\u091f\u0921\u0928\u0924\u0926\u092c\u0930\u0932\u0938\u092b\u092f\u090b\u090c\u0935\u093d\u0aae\u0ab9\u0a85\u0a87" + + "\u0a89\u0a8f\u0a93\u0a95\u0a97\u0a9a\u0a9c\u0a9f\u0aa1\u0aa8\u0aa4\u0aa6\u0aaa\u0aac\u0ab2\u0ab8\u0254\u0223\u03bb\u03c8\u0250\u1d02\u025b\u1d08\u1d09\u1d1d\u026f\u03b4\u03c7\u0252\u0255\u025c\u0265\u0271\u0270\u0282\u0283\u028a\u028c\u03c9\u01dd\uFFFF\ua775\u028d").toCharArray(); public static final char[] edges = ("\u0001\u0036\u0037\u0038\u0039\u003a\u003b\u003c\u003f\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0059\u005a\u005b\\\u005d\u005e\u005f\u0060\u0061\u0062\u0063\u0064\u0067\u0068\u006b\u006e\u006f\u0072\u0075\u0078\u007b\u007e\u0081\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" + @@ -265,26 +265,26 @@ public final class ComposeKeyData "\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0004\u0000\u0000\u0000\u0001\u0001\u0001\u0004\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0001\u0001\u0004\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0019\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u000b\u212c\u212d\u212e\u212f\u2130\u2131\u2132\u2133\u2134\u2135\u0001\u0001\u0001\u0001" + "\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u2141\u2142\u2143\u2144\u2145\u2146\u2147\u2148\u2149\u214a\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u2156\u2157\u2158\u2159\u215a\u215b\u215c\u215d\u215e\u215f\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u216b\u216c\u216d\u216e\u216f\u2170\u2171\u2172\u2173\u2174\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u2180\u2181" + - "\u2182\u2183\u2184\u2185\u2186\u2187\u2188\u2189\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u2195\u2196\u2197\u2198\u2199\u219a\u219b\u219c\u219d\u219e\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u21aa\u21ab\u21ac\u21ad\u21ae\u21af\u21b0\u21b1\u21b2\u21b3\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0071\u1051\u0dea\u2225\u2228\u222b\u222c\u222d\u222e\u222f\u2230\u2231" + - "\u2232\u20bb\u20bc\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u20e6\u20b5\u2245\u20b6\u2246\u2247\u20ed\u2248\u2249\u224a\u224b\u224c\u224d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u1043\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226f\u0424\u2271\u2274\u2277" + - "\u227a\u227b\u1043\u0ef4\u0f00\u0ef5\u0f03\u20fc\u20fd\u20fe\u20ff\u1e11\u1e16\u1e6c\u1e70\u1e73\u1e76\u227c\u227d\u227e\u227f\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" + - "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0002\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0006\u228d\u2290\u2293\u2296\u2299\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000" + - "\u0000\u0003\u0000\u0000\u03b4\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u2661\u2662\u2663\u2664\u2665\u2666\u0415\u2667\u2668\u0415\u2652\u265b\u2667\u2650\u2664\u265e\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2652\u2654\u2654\u2654\u2654\u2658\u2658\u2658\u2658\u265d\u265e\u265e\u265e\u265e\u265e\u265e\u2664\u2664\u2664\u2664\u2667\u2650\u2650\u2650\u2650\u2650\u2650" + - "\u2650\u2652\u2654\u2654\u2654\u2654\u2658\u2658\u2658\u2658\u265d\u265e\u265e\u265e\u265e\u265e\u265e\u2664\u2664\u2664\u2664\u2667\u2667\u2650\u2650\u2650\u2650\u2652\u2652\u2652\u2652\u2652\u2652\u2652\u2652\u2653\u2653\u2653\u2653\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2656\u2656\u2656\u2656\u2656\u2656\u2657\u2657\u2657\u2657\u2658\u2658\u2658\u2658\u2658\u2658\u2658\u2658\u2659\u2659\u265a\u265a\u265b\u265b\u265b" + - "\u265b\u265b\u265b\u265b\u265b\u265d\u265d\u265d\u265d\u265d\u265d\u265e\u265e\u265e\u265e\u265e\u265e\u2661\u2661\u2661\u2661\u2661\u2661\u2662\u2662\u0184\u0184\u2662\u2662\u2662\u2662\u2663\u2663\u2663\u2663\u2663\u2663\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2666\u2666\u2667\u2667\u2667\u2668\u2668\u2668\u2668\u2668\u2668\u2651\u2669\u2658\u265b\u265e\u265e\u265e\u2661\u2664\u2664\u2668\u2668\u2650\u2650" + - "\u2658\u2658\u265e\u265e\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2650\u2650\u2650\u2650\u2650\u2650\u2656\u2656\u2656\u2656\u265a\u265a\u265e\u265e\u265e\u265e\u266a\u266a\u2659\u2656\u2656\u265d\u265d\u2650\u2650\u2650\u2650\u265e\u265e\u2650\u2650\u2654\u2654\u2658\u2658\u265e\u265e\u2661\u2661\u2664\u2664\u2657\u2657\u2650\u2650\u2654\u2654\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u2667\u2667\u2659" + - "\u2650\u2652\u2652\u265b\u2663\u2651\u2664\u2654\u2654\u2659\u2659\u2661\u2661\u2667\u2667\u2659\u2656\u2669\u2658\u2658\u265d\u265e\u265e\u2661\u2664\u2667\u2651\u266b\u2657\u265b\u2657\u266c\u2659\u2661\u266d\u266e\u042a\u2666\u2667\u2669\u265b\u2662\u0415\u266f\u2670\u2671\u2672\u2673\u2674\u2675\u2670\u2676\u2677\u2664\u2678\u2679\u267a\u267b\u2672\u2674\u266f\u2670\u2671\u2672\u2674\u267a\u2672\u2674\u2673\u2674\u2675\u2676" + - "\u2674\u2674\u267b\u2678\u2677\u2679\u2676\u2670\u267c\u1833\u267d\u267e\u267f\u2024\u10b7\u2025\u2680\u2024\u0e09\u2016\u2017\u18b6\u201f\u2020\u2022\u1959\u2016\u2017\u18b6\u201f\u2020\u2022\u1959\u267c\u1833\u267d\u267e\u267f\u2024\u10b7\u2025\u2680\u2024\u0e09\u267c\u267c\u2681\u2681\u2682\u2682\u2683\u2683\u2684\u2684\u1854\u1854\u2685\u2685\u0e05\u0e05\u201f\u201f\u267d\u267d\u2686\u2686\u182b\u182b\u2687\u2687\u1835\u1835" + - "\u2688\u2688\u2689\u2689\u1805\u1805\u268a\u268a\u268b\u268b\u2024\u2024\u2024\u2024\u2685\u2685\u2685\u2685\u2685\u2685\u267e\u267e\u268c\u268c\u268c\u268c\u268c\u268c\u2025\u2025\u2106\u2106\u2688\u2688\u0d40\u0d40\u268d\u268d\u268e\u268f\u2690\u2691\u2691\u2692\u2693\u2694\u2695\u20ab\u20ae\u20af\u2696\u1a22\u2697\u2693\u2698\u2699\u20b0\u2694\u269a\u269b\u20b1\u269c\u20b2\u269d\u269e\u269f\u26a0\u26a1\u26a2\u26a2\u26a2\u269f" + - "\u26a3\u26a3\u26a4\u26a5\u26a6\u26a7\u26a8\u26a9\u26aa\u26ab\u26ac\u26aa\u26ad\u26ae\u26af\u26af\u26b0\u26a4\u26a4\u26a5\u26a7\u26a9\u26a9\u26b1\u26b2\u26b3\u26b4\u269f\u26a0\u26a0\u26a3\u26a1\u26a1\u26a8\u26a7\u26b5\u26a5\u26a6\u26b6\u26aa\u26ad\u26b7\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u26c5\u26c6\u26c7\u26c8\u26c9\u26ca\u2650\u2650\u2651\u2652\u2653\u1391\u2654\u2659\u265a\u265b\u265c" + - "\u13f1\u265e\u26cb\u26cc\u265f\u2107\u266d\u2663\u2664\u2665\u2666\u2668\u266a\u2669\u26cd\u2678\u2679\u26ce\u268d\u2650\u26cf\u26d0\u2651\u2653\u2654\u13a4\u26d1\u26d2\u2656\u26d3\u265a\u265c\u13f1\u265e\u26cb\u265f\u2663\u2664\u26d4\u26d5\u2665\u2675\u26d6\u267b\u26d7\u2658\u2661\u2664\u2665\u2675\u2669\u2679\u267b\u26d7\u2658\u265f\u2664\u26d8\u2652\u26d9\u26da\u2655\u2659\u26db\u2658\u2672\u26dc\u26dd\u265e\u26de\u26df\u2664" + - "\u26e0\u26e1\u2668\u266a\u2676\u2651\u2651\u2651\u2651\u2652\u2652\u2653\u2653\u2653\u2653\u2653\u2653\u2654\u2654\u2654\u2654\u10a0\u10a0\u2655\u2655\u2656\u2656\u2657\u2657\u2657\u2657\u2657\u2657\u2657\u2657\u2658\u2658\u265a\u265a\u265a\u265a\u265b\u265b\u265b\u265b\u265c\u265c\u265c\u265c\u265c\u265c\u265d\u265d\u265d\u265d\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265f\u265f\u265f\u265f\u2661\u2661\u2661\u2661\u2661" + - "\u2661\u2662\u2662\u2662\u2662\u2662\u2662\u2662\u2662\u2662\u2662\u2663\u2663\u2663\u2663\u2664\u2664\u2665\u2665\u2665\u2665\u2666\u2666\u2666\u2666\u2666\u2666\u2666\u2666\u2666\u2666\u0415\u0415\u0415\u0415\u2667\u2667\u2668\u2668\u2668\u2668\u2663\u2666\u2667\u13ac\u1427\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u2650\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u2654\u2654" + - "\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2654\u2658\u2658\u2658\u2658\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u265e\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2664\u2667\u2667\u2667\u2667\u2667\u2667\u2667\u2667\u266f\u2670\u2671\u2672\u2673\u2674\u26e2\u266f" + - "\u266f\u266f\u2670\u2671\u2672\u2672\u2672\u2674\u2674\u1519\u2674\u2674\u2673\u26e2\u2658\u265d\u2650\u2654\u265e\u0415\u13a4\u2657\u265a\u265b\u265c\u265d\u265f\u2662\u2663\u2654\u2651\u2657\u2661\u265f\u2668\u2678\u267a\u2651\u265f\u2650\u2663\u26e3\u2659\u268b\u268b\u1838\u1838\u268c\u268c\u17f2\u17f2\u2106\u2106\u2107\u2107\u2655\u2662\u265a\u265a\u265a\u265a\u2660\u2660\u2665\u2665\u26e4\u2652\u2652\u2655\u2655\u2656\u2656" + - "\u265d\u265d\u2661\u2661\u2662\u2662\u2658\u2660\u2664\u2664\u2660\u265e\u26d5\u26cb\u26e2\u26e6\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" + + "\u2182\u2183\u2184\u2185\u2186\u2187\u2188\u2189\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u2195\u2196\u2197\u2198\u2199\u219a\u219b\u219c\u219d\u219e\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u21aa\u21ab\u21ac\u21ad\u21ae\u21af\u21b0\u21b1\u21b2\u21b3\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0078\u1051\u0dea\u222c\u222f\u2232\u2233\u2234\u2235\u2236\u2237\u2238" + + "\u2239\u20bb\u20bc\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224a\u224b\u20e6\u20b5\u224c\u20b6\u224d\u224e\u20ed\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u1043\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226e\u226f\u2270\u2271\u2272\u2273\u2274\u2276\u0424\u2278\u227b\u227e" + + "\u2281\u2282\u1043\u0ef4\u0f00\u0ef5\u0f03\u20fc\u20fd\u20fe\u20ff\u1e11\u1e16\u1e6c\u1e70\u1e73\u1e76\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228a\u228b\u228c\u228d\u228e\u2290\u2292\u2294\u2296\u2298\u229a\u229c\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" + + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0002\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000" + + "\u0002\u0000\u0002\u0000\u0006\u22a2\u22a5\u22a8\u22ab\u22ae\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u03b4\u2665\u2666\u2667\u2668\u2669\u266a\u266b\u266c\u266d\u266e\u266f\u2670\u2671\u2672\u2673\u2674\u2675\u2676\u2677\u2678\u2679\u267a\u267b\u0415\u267c\u267d\u0415\u2667\u2670\u267c\u2665\u2679\u2673\u2665\u2665\u2665\u2665\u2665\u2665\u2665\u2667\u2669\u2669\u2669\u2669\u266d" + + "\u266d\u266d\u266d\u2672\u2673\u2673\u2673\u2673\u2673\u2673\u2679\u2679\u2679\u2679\u267c\u2665\u2665\u2665\u2665\u2665\u2665\u2665\u2667\u2669\u2669\u2669\u2669\u266d\u266d\u266d\u266d\u2672\u2673\u2673\u2673\u2673\u2673\u2673\u2679\u2679\u2679\u2679\u267c\u267c\u2665\u2665\u2665\u2665\u2667\u2667\u2667\u2667\u2667\u2667\u2667\u2667\u2668\u2668\u2668\u2668\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u266b\u266b\u266b\u266b" + + "\u266b\u266b\u266c\u266c\u266c\u266c\u266d\u266d\u266d\u266d\u266d\u266d\u266d\u266d\u266e\u266e\u266f\u266f\u2670\u2670\u2670\u2670\u2670\u2670\u2670\u2670\u2672\u2672\u2672\u2672\u2672\u2672\u2673\u2673\u2673\u2673\u2673\u2673\u2676\u2676\u2676\u2676\u2676\u2676\u2677\u2677\u0184\u0184\u2677\u2677\u2677\u2677\u2678\u2678\u2678\u2678\u2678\u2678\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u267b\u267b\u267c\u267c" + + "\u267c\u267d\u267d\u267d\u267d\u267d\u267d\u2666\u267e\u266d\u2670\u2673\u2673\u2673\u2676\u2679\u2679\u267d\u267d\u2665\u2665\u266d\u266d\u2673\u2673\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2665\u2665\u2665\u2665\u2665\u2665\u266b\u266b\u266b\u266b\u266f\u266f\u2673\u2673\u2673\u2673\u267f\u267f\u266e\u266b\u266b\u2672\u2672\u2665\u2665\u2665\u2665\u2673\u2673\u2665\u2665\u2669\u2669\u266d\u266d\u2673\u2673" + + "\u2676\u2676\u2679\u2679\u266c\u266c\u2665\u2665\u2669\u2669\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u267c\u267c\u266e\u2665\u2667\u2667\u2670\u2678\u2666\u2679\u2669\u2669\u266e\u266e\u2676\u2676\u267c\u267c\u266e\u266b\u267e\u266d\u266d\u2672\u2673\u2673\u2676\u2679\u267c\u2666\u2680\u266c\u2670\u266c\u2681\u266e\u2676\u2682\u2683\u042a\u267b\u267c\u267e\u2670\u2677\u0415\u2684\u2685\u2686\u2687\u2688\u2689\u268a\u2685" + + "\u268b\u268c\u2679\u268d\u268e\u268f\u2690\u2687\u2689\u2684\u2685\u2686\u2687\u2689\u268f\u2687\u2689\u2688\u2689\u268a\u268b\u2689\u2689\u2690\u268d\u268c\u268e\u268b\u2685\u2691\u1833\u2692\u2693\u2694\u2024\u10b7\u2025\u2695\u2024\u0e09\u2016\u2017\u18b6\u201f\u2020\u2022\u1959\u2016\u2017\u18b6\u201f\u2020\u2022\u1959\u2691\u1833\u2692\u2693\u2694\u2024\u10b7\u2025\u2695\u2024\u0e09\u2691\u2691\u2696\u2696\u2697\u2697\u2698" + + "\u2698\u2699\u2699\u1854\u1854\u269a\u269a\u0e05\u0e05\u201f\u201f\u2692\u2692\u269b\u269b\u182b\u182b\u269c\u269c\u1835\u1835\u269d\u269d\u269e\u269e\u1805\u1805\u269f\u269f\u26a0\u26a0\u2024\u2024\u2024\u2024\u269a\u269a\u269a\u269a\u269a\u269a\u2693\u2693\u26a1\u26a1\u26a1\u26a1\u26a1\u26a1\u2025\u2025\u2106\u2106\u269d\u269d\u0d40\u0d40\u26a2\u26a2\u26a3\u26a4\u26a5\u26a6\u26a6\u26a7\u26a8\u26a9\u26aa\u20ab\u20ae\u20af\u26ab" + + "\u1a22\u26ac\u26a8\u26ad\u26ae\u20b0\u26a9\u26af\u26b0\u20b1\u26b1\u20b2\u26b2\u26b3\u26b4\u26b5\u26b6\u26b7\u26b7\u26b7\u26b4\u26b8\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26bf\u26c2\u26c3\u26c4\u26c4\u26c5\u26b9\u26b9\u26ba\u26bc\u26be\u26be\u26c6\u26c7\u26c8\u26c9\u26b4\u26b5\u26b5\u26b8\u26b6\u26b6\u26bd\u26bc\u26ca\u26ba\u26bb\u26cb\u26bf\u26c2\u26cc\u26cd\u26ce\u26cf\u26d0\u26d1\u26d2\u26d3\u26d4\u26d5" + + "\u26d6\u26d7\u26d8\u26d9\u26da\u26db\u26dc\u26dd\u26de\u26df\u2665\u2665\u2666\u2667\u2668\u1391\u2669\u266e\u266f\u2670\u2671\u13f1\u2673\u26e0\u26e1\u2674\u2107\u2682\u2678\u2679\u267a\u267b\u267d\u267f\u267e\u26e2\u268d\u268e\u26e3\u26a2\u2665\u26e4\u26e5\u2666\u2668\u2669\u13a4\u26e6\u26e7\u266b\u26e8\u266f\u2671\u13f1\u2673\u26e0\u2674\u2678\u2679\u26e9\u26ea\u267a\u268a\u26eb\u2690\u26ec\u266d\u2676\u2679\u267a\u268a\u267e" + + "\u268e\u2690\u26ec\u266d\u2674\u2679\u26ed\u2667\u26ee\u26ef\u266a\u266e\u26f0\u266d\u2687\u26f1\u26f2\u2673\u26f3\u26f4\u2679\u26f5\u26f6\u267d\u267f\u268b\u2666\u2666\u2666\u2666\u2667\u2667\u2668\u2668\u2668\u2668\u2668\u2668\u2669\u2669\u2669\u2669\u10a0\u10a0\u266a\u266a\u266b\u266b\u266c\u266c\u266c\u266c\u266c\u266c\u266c\u266c\u266d\u266d\u266f\u266f\u266f\u266f\u2670\u2670\u2670\u2670\u2671\u2671\u2671\u2671\u2671\u2671" + + "\u2672\u2672\u2672\u2672\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2674\u2674\u2674\u2674\u2676\u2676\u2676\u2676\u2676\u2676\u2677\u2677\u2677\u2677\u2677\u2677\u2677\u2677\u2677\u2677\u2678\u2678\u2678\u2678\u2679\u2679\u267a\u267a\u267a\u267a\u267b\u267b\u267b\u267b\u267b\u267b\u267b\u267b\u267b\u267b\u0415\u0415\u0415\u0415\u267c\u267c\u267d\u267d\u267d\u267d\u2678\u267b\u267c\u13ac\u1427\u2665\u2665\u2665\u2665\u2665" + + "\u2665\u2665\u2665\u2665\u2665\u2665\u2665\u2665\u2665\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u109f\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u2669\u266d\u266d\u266d\u266d\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2673\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679\u2679" + + "\u2679\u2679\u2679\u2679\u2679\u267c\u267c\u267c\u267c\u267c\u267c\u267c\u267c\u2684\u2685\u2686\u2687\u2688\u2689\u26f7\u2684\u2684\u2684\u2685\u2686\u2687\u2687\u2687\u2689\u2689\u1519\u2689\u2689\u2688\u26f7\u266d\u2672\u2665\u2669\u2673\u0415\u13a4\u266c\u266f\u2670\u2671\u2672\u2674\u2677\u2678\u2669\u2666\u266c\u2676\u2674\u267d\u268d\u268f\u2666\u2674\u2665\u2678\u26f8\u266e\u26a0\u26a0\u1838\u1838\u26a1\u26a1\u17f2\u17f2" + + "\u2106\u2106\u2107\u2107\u266a\u2677\u266f\u266f\u266f\u266f\u2675\u2675\u267a\u267a\u26f9\u2667\u2667\u266a\u266a\u266b\u266b\u2672\u2672\u2676\u2676\u2677\u2677\u266d\u2675\u2679\u2679\u2675\u2673\u26ea\u26e0\u26f7\u26fb\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" + - "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0001").toCharArray(); + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0001").toCharArray(); public static final int accent_aigu = 1; public static final int accent_arrows = 130; @@ -320,5 +320,5 @@ public final class ComposeKeyData public static final int numpad_persian = 8586; public static final int numpad_tamil = 8607; public static final int shift = 8628; - public static final int substitutions = 8860; + public static final int substitutions = 8881; } diff --git a/srcs/juloo.keyboard2/Config.java b/srcs/juloo.keyboard2/Config.java index e4694e8b6..0b3f9d2d1 100644 --- a/srcs/juloo.keyboard2/Config.java +++ b/srcs/juloo.keyboard2/Config.java @@ -85,6 +85,7 @@ public final class Config public Map extra_keys_custom; public Cdict current_dictionary = null; // Might be 'null'. public Cdict emoji_dictionary = null; // Might be 'null'. + public boolean current_dictionary_missing = false; public IKeyEventHandler handler; public boolean orientation_landscape = false; public boolean foldable_unfolded = false; diff --git a/srcs/juloo.keyboard2/CurrentlyTypedWord.java b/srcs/juloo.keyboard2/CurrentlyTypedWord.java index 2852fa8ad..ccd399ed8 100644 --- a/srcs/juloo.keyboard2/CurrentlyTypedWord.java +++ b/srcs/juloo.keyboard2/CurrentlyTypedWord.java @@ -111,6 +111,13 @@ public void event_sent(int code, int meta) delayed_refresh(); } + public void text_replaced() + { + if (!_enabled) + return; + delayed_refresh(); + } + void callback() { String w = _w.toString(); diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index 78a4095ea..9bc072812 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -5,6 +5,7 @@ import android.os.Looper; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.ViewConfiguration; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; @@ -35,6 +36,14 @@ public final class KeyEventHandler /** Remember the action that was handled. This is used by autocorrect. */ LastAction _last_action = null; LastAction _next_last_action = null; + long _last_space_bar_time = 0; + /** Current Hangul sequential input state. -1 = component missing. */ + int _hangul_initial = -1; + int _hangul_medial = -1; + int _hangul_final = 0; + boolean _hangul_final_is_combined = false; + boolean _hangul_selection_update_pending = false; + int _hangul_expected_selection_delta = 0; public KeyEventHandler(IReceiver recv, Config config) { @@ -50,6 +59,7 @@ public KeyEventHandler(IReceiver recv, Config config) /** Editing just started. */ public void started(Config conf) { + reset_hangul(); InputConnection ic = _recv.getCurrentInputConnection(); _autocap.started(conf, ic); _typedword.started(conf, ic); @@ -58,11 +68,520 @@ public void started(Config conf) conf.editor_config.should_move_cursor_force_fallback; _space_bar_auto_complete = conf.space_bar_auto_complete; _last_action = null; + _last_space_bar_time = 0; + } + + static final char[] HANGUL_INITIALS = + { + 'ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ','ㅅ','ㅆ','ㅇ','ㅈ','ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ' + }; + static final char[] HANGUL_MEDIALS = + { + 'ㅏ','ㅐ','ㅑ','ㅒ','ㅓ','ㅔ','ㅕ','ㅖ','ㅗ','ㅘ','ㅙ','ㅚ', + 'ㅛ','ㅜ','ㅝ','ㅞ','ㅟ','ㅠ','ㅡ','ㅢ','ㅣ' + }; + static final char[] HANGUL_FINALS = + { + 0,'ㄱ','ㄲ','ㄳ','ㄴ','ㄵ','ㄶ','ㄷ','ㄹ','ㄺ','ㄻ','ㄼ','ㄽ', + 'ㄾ','ㄿ','ㅀ','ㅁ','ㅂ','ㅄ','ㅅ','ㅆ','ㅇ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ' + }; + + static char make_hangul_syllable(int initial, int medial, int fin) + { + return (char)(0xAC00 + initial * 588 + medial * 28 + fin); + } + + static int combine_medial(int a, int b) + { + // ㅗ: 8, ㅜ: 13, ㅡ: 18 + if (a == 8 && b == 0) return 9; // ㅗ+ㅏ = ㅘ + if (a == 8 && b == 1) return 10; // ㅗ+ㅐ = ㅙ + if (a == 8 && b == 20) return 11; // ㅗ+ㅣ = ㅚ + if (a == 13 && b == 4) return 14; // ㅜ+ㅓ = ㅝ + if (a == 13 && b == 5) return 15; // ㅜ+ㅔ = ㅞ + if (a == 13 && b == 20) return 16; // ㅜ+ㅣ = ㅟ + if (a == 18 && b == 20) return 19; // ㅡ+ㅣ = ㅢ + return -1; + } + + static int combine_final(int a, int b) + { + // simple final indices + if (a == 1 && b == 19) return 3; // ㄱ+ㅅ = ㄳ + if (a == 4 && b == 22) return 5; // ㄴ+ㅈ = ㄵ + if (a == 4 && b == 27) return 6; // ㄴ+ㅎ = ㄶ + if (a == 8 && b == 1) return 9; // ㄹ+ㄱ = ㄺ + if (a == 8 && b == 16) return 10; // ㄹ+ㅁ = ㄻ + if (a == 8 && b == 17) return 11; // ㄹ+ㅂ = ㄼ + if (a == 8 && b == 19) return 12; // ㄹ+ㅅ = ㄽ + if (a == 8 && b == 25) return 13; // ㄹ+ㅌ = ㄾ + if (a == 8 && b == 26) return 14; // ㄹ+ㅍ = ㄿ + if (a == 8 && b == 27) return 15; // ㄹ+ㅎ = ㅀ + if (a == 17 && b == 19) return 18; // ㅂ+ㅅ = ㅄ + return 0; + } + + static int combine_double_initial(int initial) + { + switch (initial) + { + case 0: return 1; // ㄱ+ㄱ = ㄲ + case 3: return 4; // ㄷ+ㄷ = ㄸ + case 7: return 8; // ㅂ+ㅂ = ㅃ + case 9: return 10; // ㅅ+ㅅ = ㅆ + case 12: return 13; // ㅈ+ㅈ = ㅉ + default: return -1; + } + } + + static int combine_double_final(int fin) + { + switch (fin) + { + case 1: return 2; // ㄱ+ㄱ = ㄲ + case 19: return 20; // ㅅ+ㅅ = ㅆ + default: return 0; + } + } + + static int split_compound_final_first(int fin) + { + switch (fin) + { + case 2: return 1; // ㄲ→ㄱ + case 3: return 1; // ㄳ→ㄱ + case 5: return 4; // ㄵ→ㄴ + case 6: return 4; // ㄶ→ㄴ + case 9: return 8; // ㄺ→ㄹ + case 10: return 8; // ㄻ→ㄹ + case 11: return 8; // ㄼ→ㄹ + case 12: return 8; // ㄽ→ㄹ + case 13: return 8; // ㄾ→ㄹ + case 14: return 8; // ㄿ→ㄹ + case 15: return 8; // ㅀ→ㄹ + case 18: return 17; // ㅄ→ㅂ + case 20: return 19; // ㅆ→ㅅ + default: return 0; + } + } + + static int split_compound_final_second_initial(int fin) + { + switch (fin) + { + case 2: return 0; // ㄲ→ㄱ + case 3: return 9; // ㄳ→ㅅ + case 5: return 12; // ㄵ→ㅈ + case 6: return 18; // ㄶ→ㅎ + case 9: return 0; // ㄺ→ㄱ + case 10: return 6; // ㄻ→ㅁ + case 11: return 7; // ㄼ→ㅂ + case 12: return 9; // ㄽ→ㅅ + case 13: return 16; // ㄾ→ㅌ + case 14: return 17; // ㄿ→ㅍ + case 15: return 18; // ㅀ→ㅎ + case 18: return 9; // ㅄ→ㅅ + case 20: return 9; // ㅆ→ㅅ + default: return -1; + } + } + + static int initial_from_final(int fin) + { + // final_idx -> initial_idx mapping + if (fin >= 1 && fin <= 27) + { + char c = HANGUL_FINALS[fin]; + for (int i = 0; i < HANGUL_INITIALS.length; i++) + if (HANGUL_INITIALS[i] == c) + return i; + } + return -1; + } + + static int final_from_initial(int initial) + { + // initial_idx -> final_idx mapping + if (initial >= 0 && initial < HANGUL_INITIALS.length) + { + char c = HANGUL_INITIALS[initial]; + for (int i = 1; i < HANGUL_FINALS.length; i++) + if (HANGUL_FINALS[i] == c) + return i; + } + return 0; + } + + static int split_compound_medial_first(int medial) + { + switch (medial) + { + case 9: return 8; // ㅘ→ㅗ + case 10: return 8; // ㅙ→ㅗ + case 11: return 8; // ㅚ→ㅗ + case 14: return 13; // ㅝ→ㅜ + case 15: return 13; // ㅞ→ㅜ + case 16: return 13; // ㅟ→ㅜ + case 19: return 18; // ㅢ→ㅡ + default: return -1; + } + } + + void reset_hangul() + { + _hangul_initial = -1; + _hangul_medial = -1; + _hangul_final = 0; + _hangul_final_is_combined = false; + _hangul_selection_update_pending = false; + _hangul_expected_selection_delta = 0; + } + + void expect_hangul_selection_update(int cursor_delta) + { + if (!has_hangul_state()) + return; + _hangul_selection_update_pending = true; + _hangul_expected_selection_delta = cursor_delta; + } + + boolean has_hangul_state() + { + return _hangul_initial >= 0 || _hangul_medial >= 0; + } + + int hangul_initial_index(char c) + { + for (int i = 0; i < HANGUL_INITIALS.length; i++) + if (HANGUL_INITIALS[i] == c) + return i; + return -1; + } + + int hangul_medial_index(char c) + { + for (int i = 0; i < HANGUL_MEDIALS.length; i++) + if (HANGUL_MEDIALS[i] == c) + return i; + return -1; + } + + int hangul_final_index(char c) + { + for (int i = 1; i < HANGUL_FINALS.length; i++) + if (HANGUL_FINALS[i] == c) + return i; + return -1; + } + + int hangul_medial_from_precomposed(int precomposed) + { + return ((precomposed - 0xAC00) % 588) / 28; + } + + void replace_hangul_current() + { + if (_hangul_initial >= 0 && _hangul_medial >= 0) + { + char c = make_hangul_syllable(_hangul_initial, _hangul_medial, _hangul_final); + replace_surrounding_text(1, 0, String.valueOf(c)); + } + else if (_hangul_initial >= 0) + { + replace_surrounding_text(1, 0, String.valueOf(HANGUL_INITIALS[_hangul_initial])); + } + else if (_hangul_medial >= 0) + { + replace_surrounding_text(1, 0, String.valueOf(HANGUL_MEDIALS[_hangul_medial])); + } + } + + void send_hangul_current() + { + if (_hangul_initial >= 0 && _hangul_medial >= 0) + { + char c = make_hangul_syllable(_hangul_initial, _hangul_medial, _hangul_final); + send_text(String.valueOf(c)); + } + else if (_hangul_initial >= 0) + { + send_text(String.valueOf(HANGUL_INITIALS[_hangul_initial])); + } + else if (_hangul_medial >= 0) + { + send_text(String.valueOf(HANGUL_MEDIALS[_hangul_medial])); + } + } + + void start_hangul_initial(int idx) + { + _hangul_initial = idx; + _hangul_medial = -1; + _hangul_final = 0; + _hangul_final_is_combined = false; + send_text(String.valueOf(HANGUL_INITIALS[idx])); + } + + void start_hangul_medial(int idx) + { + _hangul_initial = -1; + _hangul_medial = idx; + _hangul_final = 0; + _hangul_final_is_combined = false; + send_text(String.valueOf(HANGUL_MEDIALS[idx])); + } + + void start_hangul_ieung_medial(int idx) + { + _hangul_initial = 11; + _hangul_medial = idx; + _hangul_final = 0; + _hangul_final_is_combined = false; + send_hangul_current(); + } + + boolean handle_hangul_char(char c) + { + int medial = hangul_medial_index(c); + if (medial >= 0) + { + handle_hangul_medial(medial); + return true; + } + int initial = hangul_initial_index(c); + if (initial >= 0) + { + handle_hangul_initial(c); + return true; + } + int fin = hangul_final_index(c); + if (fin >= 0) + { + handle_hangul_initial(c); + return true; + } + return false; + } + + void handle_hangul_initial(char c) + { + int initial = hangul_initial_index(c); + int fin = hangul_final_index(c); + + if (_hangul_initial >= 0 && _hangul_medial < 0) + { + int combined = (_hangul_initial == initial) ? combine_double_initial(initial) : -1; + if (combined >= 0) + { + _hangul_initial = combined; + replace_hangul_current(); + return; + } + } + + if (_hangul_initial >= 0 && _hangul_medial >= 0) + { + // We have initial + medial, try to add final + if (_hangul_final == 0 && fin > 0) + { + _hangul_final = fin; + _hangul_final_is_combined = false; + replace_hangul_current(); + return; + } + // Try compound final + if (_hangul_final > 0) + { + int double_final = (_hangul_final == fin) ? combine_double_final(fin) : 0; + if (double_final > 0) + { + _hangul_final = double_final; + _hangul_final_is_combined = true; + replace_hangul_current(); + return; + } + int compound = combine_final(_hangul_final, fin); + if (compound > 0) + { + _hangul_final = compound; + _hangul_final_is_combined = true; + replace_hangul_current(); + return; + } + } + } + + if (initial >= 0) + start_hangul_initial(initial); + else + reset_hangul(); + } + + void handle_hangul_medial(int medial) + { + if (medial < 0) + return; + + if (_hangul_initial >= 0 && _hangul_medial >= 0) + { + // If we have a final, split or move it before trying medial combination. + if (_hangul_final > 0) + { + int moved_initial; + if (_hangul_final_is_combined) + { + // Compound final: split to first+second + int first = split_compound_final_first(_hangul_final); + int second = split_compound_final_second_initial(_hangul_final); + if (first > 0 && second >= 0) + { + // Split: previous syllable keeps first final, new syllable starts with second + char prev = make_hangul_syllable(_hangul_initial, _hangul_medial, first); + char next = make_hangul_syllable(second, medial, 0); + replace_surrounding_text(1, 0, String.valueOf(prev) + String.valueOf(next)); + _hangul_initial = second; + _hangul_medial = medial; + _hangul_final = 0; + _hangul_final_is_combined = false; + return; + } + } + + // Simple final → next initial + moved_initial = initial_from_final(_hangul_final); + if (moved_initial >= 0) + { + char prev = make_hangul_syllable(_hangul_initial, _hangul_medial, 0); + char next = make_hangul_syllable(moved_initial, medial, 0); + replace_surrounding_text(1, 0, String.valueOf(prev) + String.valueOf(next)); + _hangul_initial = moved_initial; + _hangul_medial = medial; + _hangul_final = 0; + _hangul_final_is_combined = false; + return; + } + } + + // We have a full syllable without final. Try medial combination. + int combined = combine_medial(_hangul_medial, medial); + if (combined >= 0) + { + _hangul_medial = combined; + _hangul_final = 0; + _hangul_final_is_combined = false; + replace_hangul_current(); + return; + } + + // No combination, start new vowel syllable + start_hangul_medial(medial); + return; + } + + if (_hangul_initial >= 0 && _hangul_medial < 0) + { + // initial + medial = syllable + _hangul_medial = medial; + _hangul_final = 0; + _hangul_final_is_combined = false; + replace_hangul_current(); + return; + } + + if (_hangul_medial >= 0) + { + // Previous medial, try combination + int combined = combine_medial(_hangul_medial, medial); + if (combined >= 0) + { + _hangul_medial = combined; + replace_hangul_current(); + return; + } + start_hangul_medial(medial); + return; + } + + // No state, start new medial + start_hangul_medial(medial); + } + + boolean handle_hangul_backspace() + { + if (!has_hangul_state()) + return false; + + // Final exists -> remove or split compound + if (_hangul_final > 0) + { + int first = split_compound_final_first(_hangul_final); + if (_hangul_final_is_combined && first > 0) + { + _hangul_final = first; + _hangul_final_is_combined = false; + replace_hangul_current(); + } + else + { + _hangul_final = 0; + _hangul_final_is_combined = false; + replace_hangul_current(); + } + return true; + } + + // No final, medial exists + if (_hangul_medial >= 0) + { + int first = split_compound_medial_first(_hangul_medial); + if (first >= 0) + { + _hangul_medial = first; + replace_hangul_current(); + } + else if (_hangul_initial >= 0) + { + // Remove medial, back to initial + _hangul_medial = -1; + replace_hangul_current(); + } + else + { + // Standalone medial, delete it + replace_surrounding_text(1, 0, ""); + reset_hangul(); + } + return true; + } + + // Initial only + if (_hangul_initial >= 0) + { + replace_surrounding_text(1, 0, ""); + reset_hangul(); + return true; + } + + return false; } /** Selection has been updated. */ public void selection_updated(int oldSelStart, int newSelStart, int newSelEnd) { + if (has_hangul_state()) + { + boolean selection_is_cursor = newSelStart == newSelEnd; + boolean expected_update = _hangul_selection_update_pending + && selection_is_cursor + && (oldSelStart < 0 || oldSelStart + _hangul_expected_selection_delta == newSelStart); + if (expected_update) + { + _hangul_selection_update_pending = false; + _hangul_expected_selection_delta = 0; + } + else if (!selection_is_cursor || oldSelStart != newSelStart) + reset_hangul(); + } _autocap.selection_updated(oldSelStart, newSelStart); _typedword.selection_updated(oldSelStart, newSelStart, newSelEnd); } @@ -93,6 +612,7 @@ public void key_down(KeyValue key, boolean isSwipe) case Slider: // Don't wait for the next key_up and move the cursor right away. This // is called after the trigger distance have been travelled. + reset_hangul(); handle_slider(key.getSlider(), key.getSliderRepeat(), true); break; default: break; @@ -110,15 +630,57 @@ public void key_up(KeyValue key, Pointers.Modifiers mods) update_meta_state(mods); switch (key.getKind()) { - case Char: send_text(String.valueOf(key.getChar())); break; - case String: send_text(key.getString()); break; - case Event: _recv.handle_event_key(key.getEvent()); break; - case Keyevent: send_key_down_up(key.getKeyevent()); break; + case Char: + if (!handle_hangul_char(key.getChar())) + { + reset_hangul(); + send_text(String.valueOf(key.getChar())); + } + break; + case Hangul_initial: + handle_hangul_initial(key.getString().charAt(0)); + break; + case Hangul_medial: + handle_hangul_medial(hangul_medial_from_precomposed( + key.getHangulPrecomposed())); + break; + case String: + if (key.getString().length() == 1 && handle_hangul_char(key.getString().charAt(0))) + { + // Handled by Hangul automaton + } + else + { + reset_hangul(); + send_text(key.getString()); + } + break; + case Event: + reset_hangul(); + _recv.handle_event_key(key.getEvent()); + break; + case Keyevent: + reset_hangul(); + send_key_down_up(key.getKeyevent()); + break; case Modifier: break; - case Editing: handle_editing_key(key.getEditing()); break; - case Compose_pending: _recv.set_compose_pending(true); break; - case Slider: handle_slider(key.getSlider(), key.getSliderRepeat(), false); break; - case Macro: evaluate_macro(key.getMacro()); break; + case Editing: + if (key.getEditing() != KeyValue.Editing.BACKSPACE) + reset_hangul(); + handle_editing_key(key.getEditing()); + break; + case Compose_pending: + reset_hangul(); + _recv.set_compose_pending(true); + break; + case Slider: + reset_hangul(); + handle_slider(key.getSlider(), key.getSliderRepeat(), false); + break; + case Macro: + reset_hangul(); + evaluate_macro(key.getMacro()); + break; } update_meta_state(old_mods); _last_action = _next_last_action; @@ -256,6 +818,7 @@ void send_text(String text) InputConnection conn = _recv.getCurrentInputConnection(); if (conn == null) return; + expect_hangul_selection_update(text.length()); _autocap.typed(text); _typedword.typed(text); conn.commitText(text, 1); @@ -267,10 +830,13 @@ void replace_surrounding_text(int remove_before, int remove_after, InputConnection conn = _recv.getCurrentInputConnection(); if (conn == null) return; + expect_hangul_selection_update(new_text.length() - remove_before); conn.beginBatchEdit(); conn.deleteSurroundingText(remove_before, remove_after); conn.commitText(new_text, 1); conn.endBatchEdit(); + _autocap.text_replaced(); + _typedword.text_replaced(); } /** See {!InputConnection.performContextMenuAction}. */ @@ -536,13 +1102,44 @@ void handle_space_bar() && !_typedword.is_selection_not_empty() && _typedword.cursor_relative() == 0) suggestion_entered(_suggestions.suggestions[0]); - else + else if (!handle_space_bar_double_tap()) + { send_text(" "); + _last_space_bar_time = System.currentTimeMillis(); + _next_last_action = LastAction.SPACE_BAR; + } + } + + boolean handle_space_bar_double_tap() + { + if (_last_action != LastAction.SPACE_BAR) + return false; + if (System.currentTimeMillis() - _last_space_bar_time > ViewConfiguration.getDoubleTapTimeout()) + return false; + if (_typedword.is_selection_not_empty() || _typedword.cursor_relative() != 0) + return false; + InputConnection conn = _recv.getCurrentInputConnection(); + if (conn == null) + return false; + CharSequence before = conn.getTextBeforeCursor(2, 0); + if (before == null || before.length() < 2 || before.charAt(before.length() - 1) != ' ') + return false; + char previous = before.charAt(before.length() - 2); + if (Character.isWhitespace(previous) || ".!?。!?".indexOf(previous) >= 0) + return false; + replace_surrounding_text(1, 0, ". "); + _autocap.typed(". "); + _typedword.delayed_refresh(); + _last_space_bar_time = 0; + _next_last_action = LastAction.OTHER; + return true; } /** Undo the last autocorrect. */ void handle_backspace() { + if (handle_hangul_backspace()) + return; if (_last_action == LastAction.SUGGESTION_ENTERED && last_replaced_word != null) { @@ -581,6 +1178,7 @@ else if (should_disable) public static enum LastAction { SUGGESTION_ENTERED, + SPACE_BAR, OTHER } } diff --git a/srcs/juloo.keyboard2/KeyModifier.java b/srcs/juloo.keyboard2/KeyModifier.java index d45c9a1e0..89401c32d 100644 --- a/srcs/juloo.keyboard2/KeyModifier.java +++ b/srcs/juloo.keyboard2/KeyModifier.java @@ -49,11 +49,9 @@ public static KeyValue modify(KeyValue k, KeyValue mod) case Compose_pending: return apply_compose_pending(mod.getPendingCompose(), k); case Hangul_initial: - if (k.equals(mod)) // Allow typing the initial in letter form - return KeyValue.makeStringKey(k.getString(), KeyValue.FLAG_GREYED); - return combine_hangul_initial(k, mod.getHangulPrecomposed()); case Hangul_medial: - return combine_hangul_medial(k, mod.getHangulPrecomposed()); + // Sequential input handled in KeyEventHandler, not via latch + return k; } return k; } diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java index 04f695070..b269a95d4 100644 --- a/srcs/juloo.keyboard2/KeyValue.java +++ b/srcs/juloo.keyboard2/KeyValue.java @@ -459,14 +459,14 @@ public static KeyValue makeComposePending(int symbol, int state, int flags) public static KeyValue makeHangulInitial(String symbol, int initial_idx) { return new KeyValue(symbol, Kind.Hangul_initial, initial_idx * 588 + 44032, - FLAG_LATCH); + 0); } public static KeyValue makeHangulMedial(int precomposed, int medial_idx) { precomposed += medial_idx * 28; return new KeyValue(String.valueOf((char)precomposed), Kind.Hangul_medial, - precomposed, FLAG_LATCH); + precomposed, 0); } public static KeyValue makeHangulFinal(int precomposed, int final_idx) diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java index 7493956ae..f002a154f 100644 --- a/srcs/juloo.keyboard2/Keyboard2.java +++ b/srcs/juloo.keyboard2/Keyboard2.java @@ -175,16 +175,19 @@ private void refresh_current_dictionary() { _config.current_dictionary = null; _config.emoji_dictionary = null; + _config.current_dictionary_missing = false; if (_device_locales.default_ == null) return; String current = _device_locales.default_.dictionary; if (current == null) return; + _config.current_dictionary_missing = true; Cdict[] dicts = _dictionaries.load(current); if (dicts == null) return; _config.current_dictionary = Dictionaries.find_by_name(dicts, "main"); _config.emoji_dictionary = Dictionaries.find_by_name(dicts, "emoji"); + _config.current_dictionary_missing = _config.current_dictionary == null; } private void refresh_candidates_view() @@ -192,9 +195,11 @@ private void refresh_candidates_view() boolean should_show = _config.suggestions_enabled && _config.editor_config.should_show_candidates_view; - if (should_show) + if (should_show) { _candidates_view.refresh_config(_config); - _candidates_view.setVisibility(should_show ? View.VISIBLE : View.GONE); + } else { + _candidates_view.setVisibility(View.GONE); + } } /** Might re-create the keyboard view. [_keyboard_layout_view.setKeyboard()] and diff --git a/srcs/juloo.keyboard2/suggestions/CandidatesView.java b/srcs/juloo.keyboard2/suggestions/CandidatesView.java index 5485ccfde..5d97275b7 100644 --- a/srcs/juloo.keyboard2/suggestions/CandidatesView.java +++ b/srcs/juloo.keyboard2/suggestions/CandidatesView.java @@ -70,6 +70,7 @@ public void set_candidates(Suggestions s) v.setVisibility(View.GONE); } } + update_visibility(); } public void clear_candidates() @@ -79,6 +80,7 @@ public void clear_candidates() _items[i] = null; _item_views[i].setVisibility(View.GONE); } + update_visibility(); } public void refresh_config(Config config) @@ -87,9 +89,10 @@ public void refresh_config(Config config) // The status message indicates whether the dictionaries should be // installed. _status_no_dict = inflate_and_show(_status_no_dict, - (config.current_dictionary == null), + config.current_dictionary_missing, R.layout.candidates_status_no_dict); set_sizes(config); + update_visibility(); } /** Set the height of the suggestion row and the text size. */ @@ -136,6 +139,32 @@ View inflate_and_show(View v, boolean show, int layout_id) return v; } + /** Hide the entire CandidatesView when only the 'No dictionary installed' + message would be visible; show it when there are actual candidates. */ + void update_visibility() + { + boolean has_visible_candidates = false; + for (int i = 0; i < NUM_CANDIDATES; i++) + { + if (_item_views[i].getVisibility() == View.VISIBLE) + { + has_visible_candidates = true; + break; + } + } + if (has_visible_candidates) + { + setVisibility(View.VISIBLE); + return; + } + // No candidates visible; if the 'No dictionary' status is visible, hide + // the whole view so it doesn't occupy empty space. + if (_status_no_dict != null && _status_no_dict.getVisibility() == View.VISIBLE) + setVisibility(View.GONE); + else + setVisibility(View.VISIBLE); + } + private void setup_item_view(final int item_index, int item_id) { TextView v = (TextView)findViewById(item_id); diff --git a/srcs/layouts/hang_dubeolsik_custom.xml b/srcs/layouts/hang_dubeolsik_custom.xml new file mode 100644 index 000000000..c8fea2dd4 --- /dev/null +++ b/srcs/layouts/hang_dubeolsik_custom.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcs/layouts/hang_dubeolsik_kr.xml b/srcs/layouts/hang_dubeolsik_kr.xml index 5eb883310..c8fea2dd4 100644 --- a/srcs/layouts/hang_dubeolsik_kr.xml +++ b/srcs/layouts/hang_dubeolsik_kr.xml @@ -1,37 +1,53 @@ + - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + diff --git a/srcs/layouts/latn_qwerty_custom.xml b/srcs/layouts/latn_qwerty_custom.xml new file mode 100644 index 000000000..e18e929aa --- /dev/null +++ b/srcs/layouts/latn_qwerty_custom.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/juloo.keyboard2/HangulInputTest.java b/test/juloo.keyboard2/HangulInputTest.java new file mode 100644 index 000000000..8d7da9b95 --- /dev/null +++ b/test/juloo.keyboard2/HangulInputTest.java @@ -0,0 +1,260 @@ +package juloo.keyboard2; + +import android.os.Handler; +import android.view.KeyEvent; +import android.view.inputmethod.InputConnection; +import org.junit.Test; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import static org.junit.Assert.*; +import juloo.keyboard2.suggestions.Suggestions; + +public class HangulInputTest +{ + static class TestReceiver implements KeyEventHandler.IReceiver + { + final StringBuilder text = new StringBuilder(); + final InputConnection inputConnection = (InputConnection)Proxy.newProxyInstance( + InputConnection.class.getClassLoader(), + new Class[] { InputConnection.class }, + new InvocationHandler() + { + @Override + public Object invoke(Object proxy, Method method, Object[] args) + { + String name = method.getName(); + if (name.equals("commitText")) + { + text.append((CharSequence)args[0]); + return true; + } + if (name.equals("deleteSurroundingText")) + { + int before = (Integer)args[0]; + int start = Math.max(0, text.length() - before); + text.delete(start, text.length()); + return true; + } + if (name.equals("beginBatchEdit") || name.equals("endBatchEdit")) + return true; + if (name.equals("sendKeyEvent")) + { + KeyEvent event = (KeyEvent)args[0]; + if (event.getAction() == KeyEvent.ACTION_UP + && event.getKeyCode() == KeyEvent.KEYCODE_DEL + && text.length() > 0) + text.delete(text.length() - 1, text.length()); + return true; + } + if (name.equals("getTextAfterCursor")) + return ""; + if (name.equals("getCursorCapsMode")) + return 0; + if (method.getReturnType() == Boolean.TYPE) + return false; + if (method.getReturnType() == Integer.TYPE) + return 0; + return null; + } + }); + + @Override + public void handle_event_key(KeyValue.Event ev) {} + @Override + public void set_shift_state(boolean state, boolean lock) {} + @Override + public void set_compose_pending(boolean pending) {} + @Override + public void selection_state_changed(boolean selection_is_ongoing) {} + @Override + public InputConnection getCurrentInputConnection() { return inputConnection; } + @Override + public Handler getHandler() { return null; } + @Override + public void set_suggestions(Suggestions suggestions) {} + } + + private static String typeHangul(String... keys) + { + TestReceiver receiver = new TestReceiver(); + KeyEventHandler handler = new KeyEventHandler(receiver, null); + for (String key : keys) + handler.key_up(KeyValue.getKeyByName(key), Pointers.Modifiers.EMPTY); + return receiver.text.toString(); + } + + private static void type(KeyEventHandler handler, String key) + { + handler.key_up(KeyValue.getKeyByName(key), Pointers.Modifiers.EMPTY); + } + + @Test + public void doubleInitialsCombine() + { + assertEquals(1, KeyEventHandler.combine_double_initial(0)); // ㄱ+ㄱ = ㄲ + assertEquals(4, KeyEventHandler.combine_double_initial(3)); // ㄷ+ㄷ = ㄸ + assertEquals(8, KeyEventHandler.combine_double_initial(7)); // ㅂ+ㅂ = ㅃ + assertEquals(10, KeyEventHandler.combine_double_initial(9)); // ㅅ+ㅅ = ㅆ + assertEquals(13, KeyEventHandler.combine_double_initial(12)); // ㅈ+ㅈ = ㅉ + assertEquals(-1, KeyEventHandler.combine_double_initial(2)); + } + + @Test + public void doubleFinalsCombine() + { + assertEquals(2, KeyEventHandler.combine_double_final(1)); // ㄱ+ㄱ = ㄲ + assertEquals(20, KeyEventHandler.combine_double_final(19)); // ㅅ+ㅅ = ㅆ + assertEquals(0, KeyEventHandler.combine_double_final(4)); + } + + @Test + public void compoundMedialsRemainSequential() + { + assertEquals(9, KeyEventHandler.combine_medial(8, 0)); // ㅗ+ㅏ = ㅘ + assertEquals(10, KeyEventHandler.combine_medial(8, 1)); // ㅗ+ㅐ = ㅙ + assertEquals(11, KeyEventHandler.combine_medial(8, 20)); // ㅗ+ㅣ = ㅚ + assertEquals(14, KeyEventHandler.combine_medial(13, 4)); // ㅜ+ㅓ = ㅝ + assertEquals(15, KeyEventHandler.combine_medial(13, 5)); // ㅜ+ㅔ = ㅞ + assertEquals(16, KeyEventHandler.combine_medial(13, 20)); // ㅜ+ㅣ = ㅟ + assertEquals(19, KeyEventHandler.combine_medial(18, 20)); // ㅡ+ㅣ = ㅢ + } + + @Test + public void compoundFinalsSplitToInitialIndices() + { + assertEquals(0, KeyEventHandler.split_compound_final_second_initial(2)); // ㄲ→ㄱ + assertEquals(9, KeyEventHandler.split_compound_final_second_initial(3)); // ㄳ→ㅅ + assertEquals(12, KeyEventHandler.split_compound_final_second_initial(5)); // ㄵ→ㅈ + assertEquals(18, KeyEventHandler.split_compound_final_second_initial(6)); // ㄶ→ㅎ + assertEquals(0, KeyEventHandler.split_compound_final_second_initial(9)); // ㄺ→ㄱ + assertEquals(6, KeyEventHandler.split_compound_final_second_initial(10)); // ㄻ→ㅁ + assertEquals(7, KeyEventHandler.split_compound_final_second_initial(11)); // ㄼ→ㅂ + assertEquals(9, KeyEventHandler.split_compound_final_second_initial(12)); // ㄽ→ㅅ + assertEquals(16, KeyEventHandler.split_compound_final_second_initial(13)); // ㄾ→ㅌ + assertEquals(17, KeyEventHandler.split_compound_final_second_initial(14)); // ㄿ→ㅍ + assertEquals(18, KeyEventHandler.split_compound_final_second_initial(15)); // ㅀ→ㅎ + assertEquals(9, KeyEventHandler.split_compound_final_second_initial(18)); // ㅄ→ㅅ + assertEquals(9, KeyEventHandler.split_compound_final_second_initial(20)); // ㅆ→ㅅ + } + + + @Test + public void wordsKeepFinalsBeforeNextInitials() + { + assertEquals("만화", typeHangul("ㅁ", "ㅏ", "ㄴ", "ㅎ", "ㅗ", "ㅏ")); + assertEquals("만화", typeHangul("ㅁ", "ㅏ", "ㄴ", "ㅎ", "ㅘ")); + assertEquals("한글", typeHangul("ㅎ", "ㅏ", "ㄴ", "ㄱ", "ㅡ", "ㄹ")); + assertEquals("안녕", typeHangul("ㅇ", "ㅏ", "ㄴ", "ㄴ", "ㅕ", "ㅇ")); + } + + @Test + public void simpleBatchimWordsKeepFinalsBeforeNextInitials() + { + assertEquals("한국", typeHangul("ㅎ", "ㅏ", "ㄴ", "ㄱ", "ㅜ", "ㄱ")); + assertEquals("감기", typeHangul("ㄱ", "ㅏ", "ㅁ", "ㄱ", "ㅣ")); + assertEquals("밥상", typeHangul("ㅂ", "ㅏ", "ㅂ", "ㅅ", "ㅏ", "ㅇ")); + assertEquals("꽃길", typeHangul("ㄲ", "ㅗ", "ㅊ", "ㄱ", "ㅣ", "ㄹ")); + assertEquals("하면", typeHangul("ㅎ", "ㅏ", "ㅁ", "ㅕ", "ㄴ")); + } + + @Test + public void iotizedMedialsMoveSimpleFinals() + { + assertEquals("가냐", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅑ")); + assertEquals("가냬", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅒ")); + assertEquals("가녀", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅕ")); + assertEquals("가녜", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅖ")); + assertEquals("가뇨", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅛ")); + assertEquals("가뉴", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅠ")); + } + + @Test + public void directDoubleFinalsMoveAsWholeFinals() + { + assertEquals("바께", typeHangul("ㅂ", "ㅏ", "ㄲ", "ㅔ")); + assertEquals("이써", typeHangul("ㅇ", "ㅣ", "ㅆ", "ㅓ")); + } + + @Test + public void sequentialDoubleFinalsSplitBeforeMedials() + { + assertEquals("박게", typeHangul("ㅂ", "ㅏ", "ㄱ", "ㄱ", "ㅔ")); + assertEquals("잇서", typeHangul("ㅇ", "ㅣ", "ㅅ", "ㅅ", "ㅓ")); + } + + @Test + public void compoundBatchimWordsKeepFinalsBeforeNextInitials() + { + assertEquals("읽다", typeHangul("ㅇ", "ㅣ", "ㄹ", "ㄱ", "ㄷ", "ㅏ")); + assertEquals("많다", typeHangul("ㅁ", "ㅏ", "ㄴ", "ㅎ", "ㄷ", "ㅏ")); + assertEquals("없다", typeHangul("ㅇ", "ㅓ", "ㅂ", "ㅅ", "ㄷ", "ㅏ")); + assertEquals("앉다", typeHangul("ㅇ", "ㅏ", "ㄴ", "ㅈ", "ㄷ", "ㅏ")); + assertEquals("핥다", typeHangul("ㅎ", "ㅏ", "ㄹ", "ㅌ", "ㄷ", "ㅏ")); + assertEquals("읊다", typeHangul("ㅇ", "ㅡ", "ㄹ", "ㅍ", "ㄷ", "ㅏ")); + assertEquals("싫다", typeHangul("ㅅ", "ㅣ", "ㄹ", "ㅎ", "ㄷ", "ㅏ")); + } + + @Test + public void compoundBatchimSplitsBeforePlainMedials() + { + assertEquals("넉서", typeHangul("ㄴ", "ㅓ", "ㄱ", "ㅅ", "ㅓ")); + assertEquals("안자", typeHangul("ㅇ", "ㅏ", "ㄴ", "ㅈ", "ㅏ")); + assertEquals("만하", typeHangul("ㅁ", "ㅏ", "ㄴ", "ㅎ", "ㅏ")); + assertEquals("달가", typeHangul("ㄷ", "ㅏ", "ㄹ", "ㄱ", "ㅏ")); + assertEquals("살마", typeHangul("ㅅ", "ㅏ", "ㄹ", "ㅁ", "ㅏ")); + assertEquals("발바", typeHangul("ㅂ", "ㅏ", "ㄹ", "ㅂ", "ㅏ")); + assertEquals("골사", typeHangul("ㄱ", "ㅗ", "ㄹ", "ㅅ", "ㅏ")); + assertEquals("할타", typeHangul("ㅎ", "ㅏ", "ㄹ", "ㅌ", "ㅏ")); + assertEquals("을퍼", typeHangul("ㅇ", "ㅡ", "ㄹ", "ㅍ", "ㅓ")); + assertEquals("실허", typeHangul("ㅅ", "ㅣ", "ㄹ", "ㅎ", "ㅓ")); + assertEquals("업서", typeHangul("ㅇ", "ㅓ", "ㅂ", "ㅅ", "ㅓ")); + } + + @Test + public void explicitIeungKeepsCompoundBatchimBeforeMedials() + { + assertEquals("간아", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅇ", "ㅏ")); + assertEquals("난예", typeHangul("ㄴ", "ㅏ", "ㄴ", "ㅇ", "ㅖ")); + assertEquals("많아", typeHangul("ㅁ", "ㅏ", "ㄴ", "ㅎ", "ㅇ", "ㅏ")); + assertEquals("읽어", typeHangul("ㅇ", "ㅣ", "ㄹ", "ㄱ", "ㅇ", "ㅓ")); + assertEquals("없어", typeHangul("ㅇ", "ㅓ", "ㅂ", "ㅅ", "ㅇ", "ㅓ")); + assertEquals("앉아", typeHangul("ㅇ", "ㅏ", "ㄴ", "ㅈ", "ㅇ", "ㅏ")); + } + + @Test + public void expectedSelectionUpdatesKeepHangulState() + { + TestReceiver receiver = new TestReceiver(); + KeyEventHandler handler = new KeyEventHandler(receiver, null); + + type(handler, "ㄱ"); + handler.selection_updated(0, 1, 1); + type(handler, "ㅏ"); + + assertEquals("가", receiver.text.toString()); + } + + @Test + public void externalSelectionChangesResetHangulState() + { + TestReceiver receiver = new TestReceiver(); + KeyEventHandler handler = new KeyEventHandler(receiver, null); + + type(handler, "ㄱ"); + type(handler, "ㅏ"); + type(handler, "ㄴ"); + handler.selection_updated(1, 0, 0); + type(handler, "ㅏ"); + + assertEquals("간ㅏ", receiver.text.toString()); + } + + @Test + public void plainMedialsStillMoveFinals() + { + assertEquals("가나", typeHangul("ㄱ", "ㅏ", "ㄴ", "ㅏ")); + assertEquals("나녜", typeHangul("ㄴ", "ㅏ", "ㄴ", "ㅖ")); + assertEquals("적극적", typeHangul("ㅈ", "ㅓ", "ㄱ", "ㄱ", "ㅡ", "ㄱ", "ㅈ", "ㅓ", "ㄱ")); + } +} diff --git a/test/juloo.keyboard2/ModmapTest.java b/test/juloo.keyboard2/ModmapTest.java index 3457c3b3c..567fc46bd 100644 --- a/test/juloo.keyboard2/ModmapTest.java +++ b/test/juloo.keyboard2/ModmapTest.java @@ -29,6 +29,20 @@ public void keyevent_mappings() Utils.apply(mm, "љ", KeyValue.Modifier.CTRL, KeyValue.getKeyByName("љ").withKeyevent(45)); } + @Test + public void hangul_shift_mappings() + { + Utils.apply(null, "ㅂ", KeyValue.Modifier.SHIFT, "ㅃ"); + Utils.apply(null, "ㅈ", KeyValue.Modifier.SHIFT, "ㅉ"); + Utils.apply(null, "ㄷ", KeyValue.Modifier.SHIFT, "ㄸ"); + Utils.apply(null, "ㄱ", KeyValue.Modifier.SHIFT, "ㄲ"); + Utils.apply(null, "ㅅ", KeyValue.Modifier.SHIFT, "ㅆ"); + Utils.apply(null, "ㅐ", KeyValue.Modifier.SHIFT, "ㅒ"); + Utils.apply(null, "ㅔ", KeyValue.Modifier.SHIFT, "ㅖ"); + Utils.apply(null, "ㅁ", KeyValue.Modifier.SHIFT, "ㅁ"); + Utils.apply(null, "ㅏ", KeyValue.Modifier.SHIFT, "ㅏ"); + } + static class Utils { static void apply(Modmap mm, String a, KeyValue.Modifier mod, String expected)