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)